----
url: https://docs.flutter.dev/ui/widgets/assets
----

Manage assets, display images, and show icons.

[Asset​Bundle](https://api.flutter.dev/flutter/services/AssetBundle-class.html)

[Asset bundles contain resources, such as images and strings, that can be used by an application. Access to these resources is asynchronous so that they...](https://api.flutter.dev/flutter/services/AssetBundle-class.html)

[Icon](https://api.flutter.dev/flutter/widgets/Icon-class.html)

[A Material Design icon.](https://api.flutter.dev/flutter/widgets/Icon-class.html)

[Image](https://api.flutter.dev/flutter/widgets/Image-class.html)

[A widget that displays an image.](https://api.flutter.dev/flutter/widgets/Image-class.html)

[Raw​Image](https://api.flutter.dev/flutter/widgets/RawImage-class.html)

[A widget that displays a dart:ui.Image directly.](https://api.flutter.dev/flutter/widgets/RawImage-class.html)

Find more widgets in the [widget catalog](/ui/widgets).

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-09-29. [Report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/ui/widgets/assets "Report an issue with this page").

----
url: https://docs.flutter.dev/platform-integration/android/chromeos
----

This page discusses considerations unique to building Android apps that support ChromeOS with Flutter.

## Flutter & ChromeOS tips & tricks

[#](#flutter-chromeos-tips-tricks)

For the current versions of ChromeOS, only certain ports from Linux are exposed to the rest of the environment. Here's an example of how to launch Flutter DevTools for an Android app with ports that will work:

```
$ flutter pub global run devtools --port 8000
$ cd path/to/your/app
$ flutter run --observatory-port=8080
```

Then, navigate to http\://127.0.0.1:8000/# in your Chrome browser and enter the URL to your application. The last `flutter run` command you just ran should output a URL similar to the format of `http://127.0.0.1:8080/auth_code=/`. Use this URL and select "Connect" to start the Flutter DevTools for your Android app.

#### Flutter ChromeOS lint analysis

[#](#flutter-chromeos-lint-analysis)

Flutter has ChromeOS-specific lint analysis checks to make sure that the app that you're building works well on ChromeOS. It looks for things like required hardware in your Android Manifest that aren't available on ChromeOS devices, permissions that imply requests for unsupported hardware, as well as other properties or code that would bring a lesser experience on these devices.

To activate these, you need to create a new analysis\_options.yaml file in your project folder to include these options. (If you have an existing analysis\_options.yaml file, you can update it)

yaml

```
include: package:flutter/analysis_options_user.yaml
analyzer:
 optional-checks:
   chrome-os-manifest-checks
```

To run these from the command line, use the following command:

```
$ flutter analyze
```

Sample output for this command might look like:

```
Analyzing ...
warning • This hardware feature is not supported on ChromeOS •
android/app/src/main/AndroidManifest.xml:4:33 • unsupported_chrome_os_hardware
```

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/platform-integration/android/chromeos.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/platform-integration/android/chromeos\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/platform-integration/android/chromeos.md "Report an issue with this page").

----
url: https://docs.flutter.dev/tools/devtools
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Flutter and Dart DevTools](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

## What is DevTools?

[#](#what-is-devtools)

DevTools is a suite of performance and debugging tools for Dart and Flutter. *Flutter DevTools* and *Dart DevTools* refer to the same set of tools.

For a video introduction to DevTools, check out the following deep dive and use-case walkthrough:

[Watch on YouTube in a new tab: "Dive in to Flutter and Dart DevTools"](https://www.youtube.com/watch/_EYk-E29edo)

## What can I do with DevTools?

[#](#what-can-i-do-with-devtools)

Here are some of the things you can do with DevTools:

* Inspect the UI layout and state of a Flutter app.
* Diagnose UI jank performance issues in a Flutter app.
* CPU profiling for a Flutter or Dart app.
* Network profiling for a Flutter app.
* Source-level debugging of a Flutter or Dart app.
* Debug memory issues in a Flutter or Dart command-line app.
* View general log and diagnostics information about a running Flutter or Dart command-line app.
* Analyze code and app size.
* Validate deep links in your Android or iOS app.

We expect you to use DevTools in conjunction with your existing IDE or command-line based development workflow.

[]()[]()

## How to launch DevTools

[#](#start)

You can launch DevTools with the following tools:

* [VS Code](/tools/devtools/vscode)
* [Android Studio/IntelliJ](/tools/devtools/android-studio)
* [command line](/tools/devtools/cli)

## Troubleshooting some standard issues

[#](#troubleshooting-some-standard-issues)

**Question**: My app looks janky or stutters. How do I fix it?

**Answer**: Performance issues can cause [UI frames](/perf/ui-performance) to be janky and/or slow down some operations.

1. To detect which code impacts concrete late frames, start at [Performance > Timeline](/tools/devtools/performance#timeline-events-tab).
2. To learn which code takes the most CPU time in the background, use the [CPU profiler](/tools/devtools/cpu-profiler).

For more information, check out the [Performance](/perf) page.

**Question**: I see a lot of garbage collection (GC) events occurring. Is this a problem?

**Answer**: Frequent GC events might display on the DevTools > Memory > Memory chart. In most cases, it's not a problem.

If your app has frequent background activity with some idle time, Flutter might use that opportunity to collect the created objects without performance impact.

## Providing feedback

[#](#providing-feedback)

Please give DevTools a try, provide feedback, and file issues in the [DevTools issue tracker](https://github.com/flutter/devtools/issues). Thanks!

## DevTools versioning

[#](#devtools-versioning)

DevTools is distributed as part of the Flutter SDK. To get access to the latest DevTools functionality, run `flutter upgrade` to get the most up-to-date version of Flutter. To access DevTools features before they hit the Flutter `stable` channel, consider switching to the `beta` or `main` channels.

## Other resources

[#](#other-resources)

For more information on debugging and profiling Flutter apps, see the [Debugging](/testing/debugging) page and, in particular, its list of [other resources](/testing/debugging#other-resources).

For more information on using DevTools with Dart command-line apps, see the [DevTools documentation on dart.dev](https://dart.dev/tools/dart-devtools).

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-07-31. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/index.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/tools/devtools\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/index.md "Report an issue with this page").

----
url: https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.50.0
----

The 2.50.0 release of the Dart and Flutter DevTools includes the following changes among other general improvements. To learn more about DevTools, check out the [DevTools overview](/tools/devtools/overview).

## CPU profiler updates

[#](#cpu-profiler-updates)

* Deleted the "Profile app start up" button in favor of the new Dart/Flutter `--profile-startup` CLI flags. - [#9358](https://github.com/flutter/devtools/pull/9358)

## Full commit history

[#](#full-commit-history)

To find a complete list of changes in this release, check out the [DevTools git log](https://github.com/flutter/devtools/tree/v2.50.0).

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/release-notes/release-notes-2.50.0.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.50.0\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/release-notes/release-notes-2.50.0.md "Report an issue with this page").

----
url: https://docs.flutter.dev/tos
----

The Flutter website (the "Website") is hosted by Google. By using and / or visiting the Website, you consent to be bound by Google's general [Terms of Service](https://policies.google.com/terms) and [Privacy Policy](https://policies.google.com/privacy).

The "Flutter" name and the Flutter logo (the "Flutter Marks") are trademarks owned by Google and are not included within the assets licensed under the Creative Commons Attribution 4.0 International License. Google grants you a non-transferable, non-exclusive, royalty-free limited license to use the Flutter Marks subject to your compliance with the [Flutter Brand Guidelines](/brand). Except as set forth above, nothing herein grants or should be deemed to grant to you any right, title or interest in or to the Flutter Marks.

As noted above, Google owns the Flutter trademarks, but we license them so they can be used by the developer community to support the Flutter SDK, including through training materials and other community content.

At the same time, it's important to make sure that people don't use the marks in ways that could cause confusion or otherwise misuse the marks, so we have prepared [brand guidelines](/brand) that describe the allowed uses of the marks. Our goal in protecting the Flutter trademarks is to benefit the entire community by ensuring that the marks are only used in ways that are consistent with Google's mission to provide a free and open source SDK.

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/tos/index.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/tos\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/tos/index.md "Report an issue with this page").

----
url: https://docs.flutter.dev/cookbook/networking
----

[Make authenticated requests](/cookbook/networking/authenticated-requests)

[How to fetch authorized data from a web service.](/cookbook/networking/authenticated-requests)

[Communicate with WebSockets](/cookbook/networking/web-sockets)

[How to connect to a web socket.](/cookbook/networking/web-sockets)

[Delete data on the internet](/cookbook/networking/delete-data)

[How to use the http package to delete data on the internet.](/cookbook/networking/delete-data)

[Update data over the internet](/cookbook/networking/update-data)

[How to use the http package to update data over the internet.](/cookbook/networking/update-data)

[Send data to the internet](/cookbook/networking/send-data)

[How to use the http package to send data over the internet.](/cookbook/networking/send-data)

[Parse JSON in the background](/cookbook/networking/background-parsing)

[How to perform a task in the background.](/cookbook/networking/background-parsing)

[Fetch data from the internet](/cookbook/networking/fetch-data)

[How to fetch data over the internet using the http package.](/cookbook/networking/fetch-data)

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/networking/index.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/cookbook/networking\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/networking/index.md "Report an issue with this page").

----
url: https://docs.flutter.dev/cookbook/navigation/passing-data.md
----

\# Send data to a new screen > How to pass data to a new route.

<!--?code-excerpt path-base="cookbook/navigation/passing_data"?-->

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. This recipe uses the following steps: 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. ## 1. Define a todo class 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.

<!--?code-excerpt "lib/main.dart (Todo)"?-->

\`\`\`dart class Todo { final String title; final String description; const Todo(this.title, this.description); } \`\`\` ## 2. Create a list of todos 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 \[Use lists]\[] recipe. ### Generate the list of todos

<!--?code-excerpt "lib/main.dart (Generate)" replace="/^todos:/final todos =/g/^\),$/);/g"?-->

\`\`\`dart final todos = List.generate( 20, (i) => Todo( 'Todo $i', 'A description of what needs to be done for Todo $i', ), ); \`\`\` ### Display the list of todos using a ListView

<!--?code-excerpt "lib/main_todoscreen.dart (ListViewBuilder)" replace="/^body: //g;/^\),$/)/g"?-->

\`\`\`dart ListView\.builder( itemCount: todos.length, itemBuilder: (context, index) { return ListTile(title: Text(todos\[index].title)); }, ) \`\`\` So far, so good. This generates 20 todos and displays them in a ListView. ## 3. Create a Todo screen to display the list For this, we create a \`StatelessWidget\`. We call it \`TodosScreen\`. Since the contents of this page won't change during runtime, we'll have to require the list of todos within the scope of this widget. We pass in our \`ListView\.builder\` as body of the widget we're returning to \`build()\`. This'll render the list on to the screen for you to get going!

<!--?code-excerpt "lib/main_todoscreen.dart (TodosScreen)"?-->

\`\`\`dart class TodosScreen extends StatelessWidget { // Requiring the list of todos. const TodosScreen({super.key, required this.todos}); final List todos; @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('Todos')), //passing in the ListView\.builder body: ListView\.builder( itemCount: todos.length, itemBuilder: (context, index) { return ListTile(title: Text(todos\[index].title)); }, ), ); } } \`\`\` With Flutter's default styling, you're good to go without sweating about things that you'd like to do later on! ## 4. Create a detail screen to display information about a todo 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 the detail screen is a normal \`StatelessWidget\`, require the user to enter a \`Todo\` in the UI. Then, build the UI using the given todo.

<!--?code-excerpt "lib/main.dart (detail)"?-->

\`\`\`dart class DetailScreen extends StatelessWidget { // In the constructor, require a Todo. const DetailScreen({super.key, required this.todo}); // Declare a field that holds the Todo. final Todo todo; @override Widget build(BuildContext context) { // Use the Todo to create the UI. return Scaffold( appBar: AppBar(title: Text(todo.title)), body: Padding( padding: const EdgeInsets.all(16), child: Text(todo.description), ), ); } } \`\`\` ## 5. Navigate and pass data to the detail screen 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. Pass the todo to the \`DetailScreen\`. To capture the user's tap in the \`TodosScreen\`, write an \[\`onTap()\`]\[] callback for the \`ListTile\` widget. Within the \`onTap()\` callback, use the \[\`Navigator.push()\`]\[] method.

<!--?code-excerpt "lib/main.dart (builder)"?-->

\`\`\`dart body: ListView\.builder( itemCount: todos.length, itemBuilder: (context, index) { return ListTile( title: Text(todos\[index].title), // 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, MaterialPageRoute( builder: (context) => DetailScreen(todo: todos\[index]), ), ); }, ); }, ), \`\`\` ### Interactive example

<!--?code-excerpt "lib/main.dart"?-->

\`\`\`dartpad title="Flutter passing data hands-on example in DartPad" run="true" import 'package:flutter/material.dart'; class Todo { final String title; final String description; const Todo(this.title, this.description); } void main() { runApp( MaterialApp( title: 'Passing Data', home: TodosScreen( todos: List.generate( 20, (i) => Todo( 'Todo $i', 'A description of what needs to be done for Todo $i', ), ), ), ), ); } class TodosScreen extends StatelessWidget { const TodosScreen({super.key, required this.todos}); final List todos; @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('Todos')), body: ListView\.builder( itemCount: todos.length, itemBuilder: (context, index) { return ListTile( title: Text(todos\[index].title), // 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, MaterialPageRoute( builder: (context) => DetailScreen(todo: todos\[index]), ), ); }, ); }, ), ); } } class DetailScreen extends StatelessWidget { // In the constructor, require a Todo. const DetailScreen({super.key, required this.todo}); // Declare a field that holds the Todo. final Todo todo; @override Widget build(BuildContext context) { // Use the Todo to create the UI. return Scaffold( appBar: AppBar(title: Text(todo.title)), body: Padding( padding: const EdgeInsets.all(16), child: Text(todo.description), ), ); } } \`\`\` ## Alternatively, pass the arguments using RouteSettings Repeat the first two steps. ### Create a detail screen to extract the arguments Next, create a detail screen that extracts and displays the title and description from the \`Todo\`. To access the \`Todo\`, use the \[\`ModalRoute.of()\`]\[] method. This method returns the current route with the arguments.

<!--?code-excerpt "lib/main_routesettings.dart (DetailScreen)"?-->

\`\`\`dart class DetailScreen extends StatelessWidget { const DetailScreen({super.key}); @override Widget build(BuildContext context) { final todo = ModalRoute.of(context)!.settings.arguments as Todo; // Use the Todo to create the UI. return Scaffold( appBar: AppBar(title: Text(todo.title)), body: Padding( padding: const EdgeInsets.all(16), child: Text(todo.description), ), ); } } \`\`\` ### Navigate and pass the arguments to the detail screen Finally, navigate to the \`DetailScreen\` when a user taps a \`ListTile\` widget using \`Navigator.push()\`. Pass the arguments as part of the \[\`RouteSettings\`]\[]. The \`DetailScreen\` extracts these arguments.

<!--?code-excerpt "lib/main_routesettings.dart (builder)" replace="/^body: //g;/^\),$/)/g"?-->

\`\`\`dart ListView\.builder( itemCount: todos.length, itemBuilder: (context, index) { return ListTile( title: Text(todos\[index].title), // 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, MaterialPageRoute( builder: (context) => const DetailScreen(), // Pass the arguments as part of the RouteSettings. The // DetailScreen reads the arguments from these settings. settings: RouteSettings(arguments: todos\[index]), ), ); }, ); }, ) \`\`\` ### Complete example

<!--?code-excerpt "lib/main_routesettings.dart"?-->

\`\`\`dart import 'package:flutter/material.dart'; class Todo { final String title; final String description; const Todo(this.title, this.description); } void main() { runApp( MaterialApp( title: 'Passing Data', home: TodosScreen( todos: List.generate( 20, (i) => Todo( 'Todo $i', 'A description of what needs to be done for Todo $i', ), ), ), ), ); } class TodosScreen extends StatelessWidget { const TodosScreen({super.key, required this.todos}); final List todos; @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('Todos')), body: ListView\.builder( itemCount: todos.length, itemBuilder: (context, index) { return ListTile( title: Text(todos\[index].title), // 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, MaterialPageRoute( builder: (context) => const DetailScreen(), // Pass the arguments as part of the RouteSettings. The // DetailScreen reads the arguments from these settings. settings: RouteSettings(arguments: todos\[index]), ), ); }, ); }, ), ); } } class DetailScreen extends StatelessWidget { const DetailScreen({super.key}); @override Widget build(BuildContext context) { final todo = ModalRoute.of(context)!.settings.arguments as Todo; // Use the Todo to create the UI. return Scaffold( appBar: AppBar(title: Text(todo.title)), body: Padding( padding: const EdgeInsets.all(16), child: Text(todo.description), ), ); } } \`\`\` \[\`ModalRoute.of()\`]: https\://api.flutter.dev/flutter/widgets/ModalRoute/of.html \[\`Navigator.push()\`]: https\://api.flutter.dev/flutter/widgets/Navigator/push.html \[\`onTap()\`]: https\://pub.dev/documentation/material\_ui/latest/material\_ui/ListTile/onTap.html \[\`RouteSettings\`]: https\://api.flutter.dev/flutter/widgets/RouteSettings-class.html \[Use lists]: /cookbook/lists/basic-list

----
url: https://docs.flutter.dev/platform-integration/ios/restore-state-ios.md
----

\# Restore state on iOS > How to restore the state of your iOS app after it's been killed by the OS. When a user runs a mobile app and then selects another app to run, the first app is moved to the background, or \_backgrounded\_. The operating system (both iOS and Android) often kills the backgrounded app to release memory or improve performance for the app running in the foreground. You can use the \[\`RestorationManager\`]\[] (and related) classes to handle state restoration. An iOS app requires \[a bit of extra setup]\[] in Xcode, but the restoration classes otherwise work the same on both iOS and Android. For more information, check out \[State restoration on Android]\[]. \[a bit of extra setup]: https\://api.flutter.dev/flutter/services/RestorationManager-class.html#state-restoration-on-ios \[\`RestorationManager\`]: https\://api.flutter.dev/flutter/services/RestorationManager-class.html \[State restoration on Android]: /platform-integration/android/restore-state-android

----
url: https://docs.flutter.dev/learn/pathway/tutorial/set-up-state-project
----

Preview the Wikipedia reader app you'll build and set up the initial project with required packages.

preview Preview the Wikipedia reader app you'll build

inventory\_2 Add packages for handling HTTP requests and Wikipedia data

code Set up the initial project structure

***

## Steps

1### Introductionkeyboard\_arrow\_up

In the next few lessons, you'll learn how to work with data in a Flutter app. You'll build an app that fetches and displays article summaries from the [Wikipedia API](https://en.wikipedia.org/api/rest_v1/).

These lessons explore:

* Making HTTP requests in Flutter.
* Managing application state with `ChangeNotifier`.
* Using the MVVM architecture pattern.
* Creating responsive user interfaces that update automatically when data changes.

This tutorial assumes you've completed the [Getting started with Dart](https://dart.dev/learn/tutorial) and the [Introduction to Flutter UI](/learn/pathway/tutorial/create-an-app) tutorials, and therefore doesn't explain concepts like HTTP, JSON, or widget basics.

2### Create a new Flutter projectkeyboard\_arrow\_up

Create a new Flutter project using the [Flutter CLI](/reference/flutter-cli). In your preferred terminal, run the following command to create a minimal Flutter app:

```
$ flutter create wikipedia_reader --empty
```

3### Add required dependencieskeyboard\_arrow\_up

Your app needs the [`http` package](https://pub.dev/packages/http) to make HTTP requests, and the [`material_ui` package](https://pub.dev/packages/material_ui) for some UI components. Add them to your project:

```
$ cd wikipedia_reader && flutter pub add http material_ui && flutter pub get
```

4### Examine the starter codekeyboard\_arrow\_up

First, create a new file `lib/summary.dart` to define the data model for Wikipedia article summaries. This file has no special logic, and is simply a collection of classes that represent the data returned by the Wikipedia API. It's sufficient to copy the code below into the file and then ignore it. If you aren't comfortable with basic Dart classes, you should read the [Dart Getting Started](https://dart.dev/tutorial) tutorial first.

lib/summary.dart

dart

```
/// Representation of the JSON data returned by the Wikipedia API.
class Summary {
  /// Returns a new [Summary] instance.
  Summary({
    required this.titles,
    required this.pageId,
    required this.extract,
    required this.extractHtml,
    required this.lang,
    required this.dir,
    required this.url,
    this.description,
    this.thumbnail,
    this.originalImage,
  });
​
  /// The title information of this article.
  final TitlesSet titles;
​
  /// The page ID of this article.
  final int pageId;
​
  /// The first few sentences of the article in plain text.
  final String extract;
​
  /// The first few sentences of the article in HTML format.
  final String extractHtml;
​
  /// The language code of the article's content, such as "en" for English.
  final String lang;
​
  /// The text directionality of the article's content, such as "ltr" or "rtl".
  final String dir;
​
  /// The URL of the page.
  final String url;
​
  /// A description of the article, if available.
  final String? description;
​
  /// A thumbnail-sized version of the article's primary image, if available.
  final ImageFile? thumbnail;
​
  /// The original full-sized article's primary image, if available.
  final ImageFile? originalImage;
​
  /// Whether this article has an image.
  bool get hasImage => originalImage != null && thumbnail != null;
​
  /// Returns a new [Summary] instance and imports its values from a JSON map
  static Summary fromJson(Map<String, Object?> json) {
    return switch (json) {
      {
        'titles': final Map<String, Object?> titles,
        'pageid': final int pageId,
        'extract': final String extract,
        'extract_html': final String extractHtml,
        'thumbnail': final Map<String, Object?> thumbnail,
        'originalimage': final Map<String, Object?> originalImage,
        'lang': final String lang,
        'dir': final String dir,
        'description': final String description,
        'content_urls': {
          'desktop': {'page': final String url},
          'mobile': {'page': String _},
        },
      } =>
        Summary(
          titles: TitlesSet.fromJson(titles),
          pageId: pageId,
          extract: extract,
          extractHtml: extractHtml,
          thumbnail: ImageFile.fromJson(thumbnail),
          originalImage: ImageFile.fromJson(originalImage),
          lang: lang,
          dir: dir,
          description: description,
          url: url,
        ),
      {
        'titles': final Map<String, Object?> titles,
        'pageid': final int pageId,
        'extract': final String extract,
        'extract_html': final String extractHtml,
        'lang': final String lang,
        'dir': final String dir,
        'description': final String description,
        'content_urls': {
          'desktop': {'page': final String url},
          'mobile': {'page': String _},
        },
      } =>
        Summary(
          titles: TitlesSet.fromJson(titles),
          pageId: pageId,
          extract: extract,
          extractHtml: extractHtml,
          lang: lang,
          dir: dir,
          description: description,
          url: url,
        ),
      {
        'titles': final Map<String, Object?> titles,
        'pageid': final int pageId,
        'extract': final String extract,
        'extract_html': final String extractHtml,
        'lang': final String lang,
        'dir': final String dir,
        'content_urls': {
          'desktop': {'page': final String url},
          'mobile': {'page': String _},
        },
      } =>
        Summary(
          titles: TitlesSet.fromJson(titles),
          pageId: pageId,
          extract: extract,
          extractHtml: extractHtml,
          lang: lang,
          dir: dir,
          url: url,
        ),
      _ => throw FormatException('Could not deserialize Summary, json=$json'),
    };
  }
​
  @override
  String toString() =>
      'Summary['
      'titles=$titles, '
      'pageId=$pageId, '
      'extract=$extract, '
      'extractHtml=$extractHtml, '
      'thumbnail=${thumbnail ?? 'null'}, '
      'originalImage=${originalImage ?? 'null'}, '
      'lang=$lang, '
      'dir=$dir, '
      'description=$description'
      ']';
}
​
// Image path and size, but doesn't contain any Wikipedia descriptions.
class ImageFile {
  /// Returns a new [ImageFile] instance.
  ImageFile({required this.source, required this.width, required this.height});
​
  /// The URI of the original image.
  final String source;
​
  /// The width of the original image.
  final int width;
​
  /// The height of the original image.
  final int height;
​
  /// The file extension of the image, or 'err' if one can't be determined.
  String get extension {
    final extension = getFileExtension(source);
    // By default, return a non-viable image extension.
    return extension ?? 'err';
  }
​
  /// Returns a JSON map representation of this [ImageFile].
  Map<String, Object?> toJson() {
    return <String, Object?>{
      'source': source,
      'width': width,
      'height': height,
    };
  }
​
  /// Returns a new [ImageFile] instance with its values populated from [json].
  static ImageFile fromJson(Map<String, Object?> json) {
    if (json case {
      'source': final String source,
      'height': final int height,
      'width': final int width,
    }) {
      return ImageFile(source: source, width: width, height: height);
    }
    throw FormatException('Could not deserialize OriginalImage, json=$json');
  }
​
  @override
  String toString() =>
      'OriginalImage[source_=$source, width=$width, height=$height]';
}
​
class TitlesSet {
  /// Returns a new [TitlesSet] instance.
  TitlesSet({
    required this.canonical,
    required this.normalized,
    required this.display,
  });
​
  /// The non-prefixed DB key for the article.
  ///
  /// Might contain changes such as underscores instead of spaces.
  /// Best suited for making request URIs, but still requires percent-encoding.
  final String canonical;
​
  /// The [normalized title](https://www.mediawiki.org/wiki/API:Query#Example_2:_Title_normalization)
  /// of the article.
  final String normalized;
​
  /// The title as it should be displayed to the user.
  final String display;
​
  /// Returns a new [TitlesSet] instance with its values populated from [json].
  static TitlesSet fromJson(Map<String, Object?> json) {
    if (json case {
      'canonical': final String canonical,
      'normalized': final String normalized,
      'display': final String display,
    }) {
      return TitlesSet(
        canonical: canonical,
        normalized: normalized,
        display: display,
      );
    }
    throw FormatException('Could not deserialize TitleSet, json=$json');
  }
​
  @override
  String toString() =>
      'TitlesSet['
      'canonical=$canonical, '
      'normalized=$normalized, '
      'display=$display'
      ']';
}
​
String? getFileExtension(String file) {
  final segments = file.split('.');
  if (segments.isNotEmpty) return segments.last;
  return null;
}
​
const acceptableImageFormats = ['png', 'jpg', 'jpeg'];
```

Then, open `lib/main.dart` and replace the existing code with this basic structure, which adds required imports that the app uses:

lib/main.dart

dart

```
import 'dart:convert';
import 'dart:io';
​
import 'package:http/http.dart';
import 'package:material_ui/material_ui.dart';
​
import 'summary.dart';
​
void main() {
  runApp(const MainApp());
}
​
class MainApp extends StatelessWidget {
  const MainApp({super.key});
​
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: const Text('Wikipedia Flutter')),
        body: const Center(child: Text('Loading...')),
      ),
    );
  }
}
```

This code provides a basic app structure with a title bar and placeholder content. The imports at the top include everything you need for HTTP requests, JSON parsing, and the Wikipedia data model.

5### Run your appkeyboard\_arrow\_up

Test that everything works by running your app:

```
$ flutter run -d chrome
```

You should see a simple app with "Wikipedia Flutter" in the app bar and "Loading..." in the center of the screen.

6### Reviewkeyboard\_arrow\_up

preview Previewed the Wikipedia reader app keyboard\_arrow\_up

You're starting a new tutorial section focused on working with data. You'll learn HTTP requests, state management with `ChangeNotifier`, and the MVVM architectural pattern.

inventory\_2 Added the http package and created a data model keyboard\_arrow\_up

You used `flutter pub add` to install the http package for making HTTP requests and created the `Summary` class for Wikipedia data. Packages let you leverage existing code built by the community instead of building everything from scratch.

code Set up the initial project structure keyboard\_arrow\_up

Your app has the basic structure with all necessary imports for HTTP requests, JSON parsing, and Wikipedia data. You're ready to start fetching real data from the Wikipedia API!

7### Test yourselfkeyboard\_arrow\_up

### Project Setup Quiz

1 / 2

**What does the `--empty` flag do when running `flutter create`?**

1. Creates a project with no files at all.

   Not quite.

   The project still has essential files; it just uses a minimal template.

2. Creates a minimal Flutter project with less boilerplate code.

   That's right!

   The `--empty` flag generates a minimal starter template without the default counter app.

3. Creates a project without any dependencies.

   Not quite.

   The project still includes core Flutter dependencies.

4. Creates a project that can only run on web.

   Not quite.

   The flag doesn't restrict platforms; it only affects the starter template.

**What command is used to add a package dependency to a Flutter project?**

1. `flutter install [package_name]`

   Not quite.

   The correct command uses `pub add`, not `install`.

2. `flutter pub add [package_name]`

   That's right!

   Running `flutter pub add` adds the package to pubspec.yaml and downloads it.

3. `dart get [package_name]`

   Not quite.

   The command for adding packages is `flutter pub add` or editing pubspec.yaml.

4. `flutter package install [package_name]`

   Not quite.

   There is no `flutter package` command; use `flutter pub add`.

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-10-02. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/learn/pathway/tutorial/set-up-state-project.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/learn/pathway/tutorial/set-up-state-project\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/learn/pathway/tutorial/set-up-state-project.md "Report an issue with this page").

----
url: https://docs.flutter.dev/cookbook/testing/widget/orientation
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Test orientation](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

In Flutter, you can build different layouts depending on a given [orientation](https://api.flutter.dev/flutter/widgets/Orientation.html). For example, you could present data in two columns if the app is in portrait mode, and three columns if in landscape mode. Additionally, you can create tests that check if the correct number of columns are being shown for each orientation.

In this recipe, you can learn how check if the orientation of an app is `portrait` or `landscape`, and also how many columns are displayed for each orientation.

This recipe uses the following steps:

1. Create an app that updates the layout of the content, based on the orientation.
2. Create an orientation test group.
3. Create a portrait orientation test.
4. Create a landscape orientation test.
5. Run the tests.

## 1. Create an app that updates based on orientation

[#](#1-create-an-app-that-updates-based-on-orientation)

Create a Flutter app that changes how many columns are shown when an app is in portrait or landscape mode:

1. Create a new Flutter project called `orientation_tests`.

   ```
   flutter create orientation_tests
   ```

2. Follow the steps in [Update the UI based on orientation](/cookbook/design/orientation) to set up the project.

## 2. Create an orientation test group

[#](#2-create-an-orientation-test-group)

After you've set up your `orientation_tests` project, complete these steps to group your future orientation tests:

1. In your Flutter project, open `test/widget_test.dart`.

2. Replace the existing contents with the following:

   widget\_test.dart

   dart

   ```
   import 'package:flutter_test/flutter_test.dart';
   import 'package:material_ui/material_ui.dart';
   import 'package:orientation_tests/main.dart';
   ​
   void main() {
     group('Orientation', () {
       // ···
     });
   }
   ```

## 3. Create a portrait orientation test

[#](#3-create-a-portrait-orientation-test)

Add the portrait orientation test to the `Orientation` group. This test makes sure that the orientation is `portrait` and that only `2` columns of data appear in the app:

1. In `test/widget_test.dart`, replace `...` inside of the `Orientation` group with the following test:

   widget\_test.dart

   dart

   ```
   // Check if portrait mode displays correctly.
   testWidgets('Displays 2 columns in portrait mode', (tester) async {
     // Build the app.
     await tester.pumpWidget(const MyApp());
   ​
     // Change to portrait.
     tester.view.physicalSize = const Size(600, 800);
     tester.view.devicePixelRatio = 1.0;
     addTearDown(() {
       tester.view.resetPhysicalSize();
     });
     await tester.pump();
   ​
     // Verify initial orientation is portrait.
     final orientation = MediaQuery.of(
       tester.element(find.byType(OrientationList)),
     ).orientation;
     expect(orientation, Orientation.portrait);
   ​
     // Verify there are only 2 columns in portrait mode.
     final gridViewFinder = find.byType(GridView);
     final gridView = tester.widget<GridView>(gridViewFinder);
     final delegate =
         gridView.gridDelegate as SliverGridDelegateWithFixedCrossAxisCount;
     expect(delegate.crossAxisCount, 2);
   });
   ```

## 4. Create a landscape orientation test

[#](#4-create-a-landscape-orientation-test)

Add the landscape orientation test to the `Orientation` group. This test makes sure that the orientation is `landscape` and that only `3` columns of data appear in the app:

1. In `test/widget_test.dart`, inside of the `Orientation` group, add the following test after the landscape test:

   widget\_test.dart

   dart

   ```
   // Check if landscape mode displays correctly.
   testWidgets('Displays 3 columns in landscape mode', (tester) async {
     // Build the app.
     await tester.pumpWidget(const MyApp());
   ​
     // Change to landscape.
     tester.view.physicalSize = const Size(800, 600);
     tester.view.devicePixelRatio = 1.0;
     addTearDown(() {
       tester.view.resetPhysicalSize();
     });
     await tester.pump();
   ​
     // Verify initial orientation is landscape.
     final orientation = MediaQuery.of(
       tester.element(find.byType(OrientationList)),
     ).orientation;
     expect(orientation, Orientation.landscape);
   ​
     // Verify there are only 3 columns in landscape mode.
     final gridViewFinder = find.byType(GridView);
     final gridView = tester.widget<GridView>(gridViewFinder);
     final delegate =
         gridView.gridDelegate as SliverGridDelegateWithFixedCrossAxisCount;
     expect(delegate.crossAxisCount, 3);
   });
   ```

## 5. Run the tests

[#](#5-run-the-tests)

Run the tests using the following command from the root of the project:

```
flutter test test/widget_test.dart
```

## Complete example

[#](#complete-example)

widget\_test.dart

dart

```
import 'package:flutter_test/flutter_test.dart';
import 'package:material_ui/material_ui.dart';
import 'package:orientation_tests/main.dart';
​
void main() {
  group('Orientation', () {
    // Check if portrait mode displays correctly.
    testWidgets('Displays 2 columns in portrait mode', (tester) async {
      // Build the app.
      await tester.pumpWidget(const MyApp());
​
      // Change to portrait.
      tester.view.physicalSize = const Size(600, 800);
      tester.view.devicePixelRatio = 1.0;
      addTearDown(() {
        tester.view.resetPhysicalSize();
      });
      await tester.pump();
​
      // Verify initial orientation is portrait.
      final orientation = MediaQuery.of(
        tester.element(find.byType(OrientationList)),
      ).orientation;
      expect(orientation, Orientation.portrait);
​
      // Verify there are only 2 columns in portrait mode.
      final gridViewFinder = find.byType(GridView);
      final gridView = tester.widget<GridView>(gridViewFinder);
      final delegate =
          gridView.gridDelegate as SliverGridDelegateWithFixedCrossAxisCount;
      expect(delegate.crossAxisCount, 2);
    });
​
    // Check if landscape mode displays correctly.
    testWidgets('Displays 3 columns in landscape mode', (tester) async {
      // Build the app.
      await tester.pumpWidget(const MyApp());
​
      // Change to landscape.
      tester.view.physicalSize = const Size(800, 600);
      tester.view.devicePixelRatio = 1.0;
      addTearDown(() {
        tester.view.resetPhysicalSize();
      });
      await tester.pump();
​
      // Verify initial orientation is landscape.
      final orientation = MediaQuery.of(
        tester.element(find.byType(OrientationList)),
      ).orientation;
      expect(orientation, Orientation.landscape);
​
      // Verify there are only 3 columns in landscape mode.
      final gridViewFinder = find.byType(GridView);
      final gridView = tester.widget<GridView>(gridViewFinder);
      final delegate =
          gridView.gridDelegate as SliverGridDelegateWithFixedCrossAxisCount;
      expect(delegate.crossAxisCount, 3);
    });
  });
}
```

main.dart

dart

```
import 'package:material_ui/material_ui.dart';
​
void main() {
  runApp(const MyApp());
}
​
class MyApp extends StatelessWidget {
  const MyApp({super.key});
​
  @override
  Widget build(BuildContext context) {
    const appTitle = 'Orientation Demo';
​
    return const MaterialApp(
      title: appTitle,
      home: OrientationList(title: appTitle),
    );
  }
}
​
class OrientationList extends StatelessWidget {
  final String title;
​
  const OrientationList({super.key, required this.title});
​
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text(title)),
      body: OrientationBuilder(
        builder: (context, orientation) {
          return GridView.count(
            // 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.
            children: List.generate(100, (index) {
              return Center(
                child: Text(
                  'Item $index',
                  style: TextTheme.of(context).headlineLarge,
                ),
              );
            }),
          );
        },
      ),
    );
  }
}
```

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-09-29. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/testing/widget/orientation.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/cookbook/testing/widget/orientation\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/testing/widget/orientation.md "Report an issue with this page").

----
url: https://docs.flutter.dev/tools/devtools/network.md
----

\# Use the Network View > How to use the DevTools network view. :::note The network view works with all Flutter and Dart applications. ::: ## What is it? The network view allows you to inspect HTTP, HTTPS, and WebSocket traffic from your Dart or Flutter application. !\[Screenshot of the network screen]\(/assets/images/docs/tools/devtools/network\_screenshot.png){:width="100%"} ## What network traffic is recorded? All network traffic that originates from \`dart:io\` (like the \[\`HttpClient\`]\[HttpClient] class) is logged, including the \[\`dio\`]\[dio] package. Also all network traffic that is logged using the \[\`http\_profile\`]\[http\_profile] package is recorded in the network request table. This includes network traffic from the \[\`cupertino\_http\`]\[cupertino\_http], \[\`cronet\_http\`]\[cronet\_http], and \[\`ok\_http\`]\[ok\_http] packages. For a web app that makes requests using the browser, we recommend using browser tools to inspect network traffic, such as \[Chrome DevTools]\[]. ## How to use it When you open the Network page, DevTools immediately starts recording network traffic. To pause and resume recording, use the \*\*Pause\*\* and \*\*Resume\*\* buttons (upper left). When a network request is sent by your app, it appears in the network request table (left). It's listed as "Pending" until a complete response is received. Select a network request from the table (left) to view details (right). You can inspect general and timing information about the request, as well as the content of response and request headers and bodies. Some data is not available until the response is received. ### Search and filtering You can use the search and filter controls to find a specific request or filter requests out of the request table. !\[Screenshot of the network screen]\(/assets/images/docs/tools/devtools/network\_search\_and\_filter.png) To apply a filter, press the filter button (right of the search bar). You will see a filter dialog pop up: !\[Screenshot of the network screen]\(/assets/images/docs/tools/devtools/network\_filter\_dialog.png) The filter query syntax is described in the dialog. You can filter network requests by the following keys: \* \`method\`, \`m\`: this filter corresponds to the value in the "Method" column \* \`status\`, \`s\`: this filter corresponds to the value in the "Status" column \* \`type\`, \`t\`: this filter corresponds to the value in the "Type" column Any text that is not paired with an available filter key will be queried against all categories (method, URI, status, type). Example filter queries: \`\`\`plaintext my-endpoint m:get t:json s:200 \`\`\` \`\`\`plaintext https s:404 \`\`\` ### Recording network requests on app startup To record network traffic on app startup, you can start your app in a paused state, and then begin recording network traffic in DevTools before resuming your app. 1. Start your app in a paused state: \* \`flutter run --start-paused ...\` \* \`dart run --pause-isolates-on-start --observe ...\` 2. Open DevTools from the IDE where you started your app, or from the link that was printed to the command line if you started your app from the CLI. 3. Navigate to the Network screen and ensure that recording has started. 4. Resume your app. !\[Screenshot of the app resumption experience on the Network screen]\(/assets/images/docs/tools/devtools/network\_startup\_resume.png){:width="100%"} 5. The Network profiler will now record all network traffic from your app, including traffic from app startup. ## Other resources HTTP and HTTPS requests are also surfaced in the \[\`Timeline\`]\[timeline] as asynchronous timeline events. Viewing network activity in the timeline can be useful if you want to see how HTTP traffic aligns with other events happening in your app or in the Flutter framework. To learn how to monitor an app's network traffic and inspect different types of requests using the DevTools, check out a guided \[Network View tutorial]\[network-tutorial]. The tutorial also uses the view to identify network activity that causes poor app performance. \[HttpClient]: https\://api.flutter.dev/dart-io/HttpClient-class.html \[dio]: https\://pub.dev/packages/dio \[http\_profile]: https\://pub.dev/packages/http\_profile \[cupertino\_http]: https\://pub.dev/packages/cupertino\_http \[cronet\_http]: https\://pub.dev/packages/cronet\_http \[ok\_http]: https\://pub.dev/packages/ok\_http \[Chrome DevTools]: https\://developer.chrome.com/docs/devtools/network \[timeline]: /tools/devtools/performance#timeline-events-tab \[network-tutorial]: https\://medium.com/@fluttergems/mastering-dart-flutter-devtools-network-view-part-4-of-8-afce2463687c

----
url: https://docs.flutter.dev/testing/native-debugging.md
----

\# Use a native language debugger > How to connect a native debugger to your running Flutter app.

<!--?code-excerpt path-base="testing/native_debugging"?-->

:::note This guide presumes you understand general debugging, have installed Flutter and git, and have familiarity with the Dart language as well as one of the following languages: Java, Kotlin, Swift, or Objective-C. ::: If you write Flutter apps only with Dart code, you can debug your code using your IDE's debugger. The Flutter team recommends VS Code. If you write a platform-specific plugin or use platform-specific libraries, you can debug that portion of your code with a native debugger. - To debug iOS or macOS code written in Swift or Objective-C, you can use Xcode. - To debug Android code written in Java or Kotlin, you can use Android Studio. - To debug Windows code written in C++, you can use Visual Studio. This guide shows you how you can connect \_two\_ debuggers to your Dart app, one for Dart, and one for the native code. ## Debug Dart code This guide describes how to use VS Code to debug your Flutter app. You can also use your preferred IDE with the Flutter and Dart plugins installed and configured. ## Debug Dart code using VS Code The following procedure explains how to use the Dart debugger with the default sample Flutter app. The featured components in VS Code work and appear when debugging your own Flutter project as well. 1. Create a basic Flutter app. \`\`\`console $ flutter create my\_app \`\`\` \`\`\`console Creating project my\_app... Resolving dependencies in my\_app... Got dependencies in my\_app. Wrote 129 files. All done! You can find general documentation for Flutter at: https\://docs.flutter.dev/ Detailed API documentation is available at: https\://api.flutter.dev/ If you prefer video documentation, consider: https\://www\.youtube.com/c/flutterdev In order to run your application, type: $ cd my\_app $ flutter run Your application code is in my\_app/lib/main.dart. \`\`\` \`\`\`console $ cd my\_app \`\`\` 1. Open the \`lib\main.dart\` file in the Flutter app using VS Code. 1. Click the bug icon (!\[VS Code's bug icon to trigger the debugging mode of a Flutter app]\(/assets/images/docs/testing/debugging/vscode-ui/icons/debug.png)). This opens the following panes in VS Code: - \*\*Debug\*\* - \*\*Debug Console\*\* - \*\*Widget Inspector\*\* The first time you run the debugger takes the longest. 1. Test the debugger. a. In \`main.dart\`, click on this line: \`\`\`dart \_counter++; \`\`\` b. Press `Shift` + `F9`. This adds a breakpoint where the \`\_counter\` variable increments. c. In the app, click the \*\*+\*\* button to increment the counter. The app pauses. d. At this point, VS Code displays: - In the \*\*Editor Groups\*\*: - The highlighted breakpoint in \`main.dart\` - The widget hierarchy for the Flutter app in the \*\*Widget Tree\*\* of the \*\*Widget Inspector\*\* - In the \*\*side bar\*\*: - The state of the app in the \*\*Call Stack\*\* section - The value of the \`this\` local variable in the \*\*Variables\*\* section - In the \*\*panel\*\*: - The log of the Flutter app in the \*\*Debug console\*\* ### VS Code Flutter debugger The Flutter plugin for VS Code adds a number of components to the VS Code user interface. #### Changes to VS Code interface When launched, the Flutter debugger adds debugging tools to the VS Code interface. The following screenshot and table explain the purpose of each tool. !\[VS Code with the Flutter plugin UI additions]\(/assets/images/docs/testing/debugging/vscode-ui/screens/debugger-parts.png) | Highlight Color in Screenshot | Bar, Panel, or Tab | Contents | |-------------------------------|---------------------|-----------------------------------------------------------------------------------| | \*\*Yellow\*\* | Variables | List of current values of variables in the Flutter app | | | Watch | List of items you chose to track in the Flutter app | | | Call Stack | Stack of active subroutines in the Flutter app | | | Breakpoints | List of exceptions and set breakpoints that you set | | \*\*Green\*\* | \`\` | Files that you are editing | | \*\*Pink\*\* | Widget Inspector | Hierarchy of widgets in the running Flutter app | | \*\*Blue\*\* | Layout Explorer | Visual of how Flutter placed the widget you selected in the Widget Inspector | | | Widget Details Tree | List of properties of the widget selected in the Widget Inspector | | \*\*Orange\*\* | Problems | List of issues the Dart analyzer found in the current Dart file | | | Output | Response that the Flutter app returns when building an app | | | Debug Console | Logs or error messages that the Flutter app generates while debugging | | | Terminal | System shell prompt contained in VS Code | {:.table .table-striped} To change where the panel (in \*\*orange\*\*) appears in VS Code, go to \*\*View\*\* > \*\*Appearance\*\* > \*\*Panel Position\*\*. #### VS Code Flutter debugging toolbar The toolbar allows you to debug using any debugger. You can step in, out, and over Dart statements, hot reload, or resume the app. !\[Flutter debugger toolbar in VS Code]\(/assets/images/docs/testing/debugging/vscode-ui/screens/debug-toolbar.png) | Icon | Action | Default keyboard shortcut | |-----------------------------------------------------|-----------------------|-------------------------------------------------------| | !\[Small blue vertical line with a blue triangle that indicates playing or resuming the Flutter app]\(/assets/images/docs/testing/debugging/vscode-ui/icons/play-or-resume.png) | Start or Resume | `F5` | | !\[Small blue double vertical line that indicates pausing the Flutter app]\(/assets/images/docs/testing/debugging/vscode-ui/icons/pause.png) | Pause | `F6` | | !\[Small blue arched arrow over a blue circle that indicates skipping the current block or statement in the Flutter app]\(/assets/images/docs/testing/debugging/vscode-ui/icons/step-over.png) | Step Over | `F10` | | !\[Small blue downward arrow over a blue circle that indicates going into the next function in a Flutter app]\(/assets/images/docs/testing/debugging/vscode-ui/icons/step-into.png) | Step Into | `F11` | | !\[Small blue upward arrow over a blue circle that indicates exiting the current function after one passthrough in a Flutter app]\(/assets/images/docs/testing/debugging/vscode-ui/icons/step-out.png) | Step Out | `Shift` + `F11` | | !\[Small yellow lightning bolt that indicates reloading the UI of a Flutter app without resetting any state values]\(/assets/images/docs/testing/debugging/vscode-ui/icons/hot-reload.png) | Hot Reload | `Ctrl` + `F5` | | !\[Small green almost circular arrow that indicates reloading the UI of a Flutter app and resetting any state values]\(/assets/images/docs/testing/debugging/vscode-ui/icons/hot-restart.png) | Hot Restart | `Shift` + `Special` + `F5` | | !\[Red empty square that indicates you want to stop the running Flutter app]\(/assets/images/docs/testing/debugging/vscode-ui/icons/stop.png) | Stop | `Shift` + `F5` | | !\[Small blue magnifying class with the Flutter logo inside it that opens the Widget inspector]\(/assets/images/docs/testing/debugging/vscode-ui/icons/inspector.png) | Open Widget Inspector | | {:.table .table-striped} ## Update test Flutter app For the remainder of this guide, you need to update the test Flutter app. This update adds native code to debug. 1. Open the \`lib/main.dart\` file using your preferred IDE. 1. Replace the contents of \`main.dart\` with the following code.

Expand to see Flutter code for this example

\`\`\`dart title="lib/main.dart" // Copyright 2023 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter/material.dart'; import 'package:url\_launcher/url\_launcher.dart'; void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( title: 'URL Launcher', theme: ThemeData( colorSchemeSeed: Colors.purple, brightness: Brightness.light, ), home: const MyHomePage(title: 'URL Launcher'), ); } } class MyHomePage extends StatefulWidget { const MyHomePage({super.key, required this.title}); final String title; @override State createState() => \_MyHomePageState(); } class \_MyHomePageState extends State { Future? \_launched; Future \_launchInBrowser(Uri url) async { if (!await launchUrl( url, mode: LaunchMode.externalApplication, )) { throw Exception('Could not launch $url'); } } Future \_launchInWebView(Uri url) async { if (!await launchUrl( url, mode: LaunchMode.inAppWebView, )) { throw Exception('Could not launch $url'); } } Widget \_launchStatus(BuildContext context, AsyncSnapshot snapshot) { if (snapshot.hasError) { return Text('Error: ${snapshot.error}'); } else { return const Text(''); } } @override Widget build(BuildContext context) { final Uri toLaunch = Uri( scheme: 'https', host: 'docs.flutter.dev', path: 'testing/native-debugging'); return Scaffold( appBar: AppBar( title: Text(widget.title), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: \[ Padding( padding: const EdgeInsets.all(16), child: Text(toLaunch.toString()), ), FilledButton( onPressed: () => setState(() { \_launched = \_launchInBrowser(toLaunch); }), child: const Text('Launch in browser'), ), const Padding(padding: EdgeInsets.all(16)), FilledButton( onPressed: () => setState(() { \_launched = \_launchInWebView(toLaunch); }), child: const Text('Launch in app'), ), const Padding(padding: EdgeInsets.all(16.0)), FutureBuilder(future: \_launched, builder: \_launchStatus), ], ), ), ); } } \`\`\` 1. To add the \`url\_launcher\` package as a dependency, run \`flutter pub add\`: \`\`\`console $ flutter pub add url\_launcher \`\`\` \`\`\`console Resolving dependencies... collection 1.17.1 (1.17.2 available) + flutter\_web\_plugins 0.0.0 from sdk flutter matcher 0.12.15 (0.12.16 available) material\_color\_utilities 0.2.0 (0.8.0 available) + plugin\_platform\_interface 2.1.4 source\_span 1.9.1 (1.10.0 available) stream\_channel 2.1.1 (2.1.2 available) test\_api 0.5.1 (0.6.1 available) + url\_launcher 6.1.11 + url\_launcher\_android 6.0.36 + url\_launcher\_ios 6.1.4 + url\_launcher\_linux 3.0.5 + url\_launcher\_macos 3.0.5 + url\_launcher\_platform\_interface 2.1.3 + url\_launcher\_web 2.0.17 + url\_launcher\_windows 3.0.6 Changed 10 dependencies! \`\`\` 1. To check what changed with the codebase: {: type="a"} 1. In Linux or macOS, run this \`find\` command. \`\`\`console $ find ./ -mmin -120 \`\`\` \`\`\`console ./ios/Flutter/Debug.xcconfig ./ios/Flutter/Release.xcconfig ./linux/flutter/generated\_plugin\_registrant.cc ./linux/flutter/generated\_plugins.cmake ./macos/Flutter/Flutter-Debug.xcconfig ./macos/Flutter/Flutter-Release.xcconfig ./macos/Flutter/GeneratedPluginRegistrant.swift ./pubspec.lock ./pubspec.yaml ./windows/flutter/generated\_plugin\_registrant.cc ./windows/flutter/generated\_plugins.cmake \`\`\` 1. In Windows, run this command in the command prompt. \`\`\`ps Get-ChildItem C:\dev\example\ -Rescurse | Where-Object {$\_.LastWriteTime -gt (Get-Date).AddDays(-1)} \`\`\` \`\`\`ps C:\dev\example\ios\Flutter\ Mode LastWriteTime Length Name ---- ------------- ------ ---- 8/1/2025 9:15 AM Debug.xcconfig 8/1/2025 9:15 AM Release.xcconfig C:\dev\example\linux\flutter\ Mode LastWriteTime Length Name ---- ------------- ------ ---- 8/1/2025 9:15 AM generated\_plugin\_registrant.cc 8/1/2025 9:15 AM generated\_plugins.cmake C:\dev\example\macos\Flutter\ Mode LastWriteTime Length Name ---- ------------- ------ ---- 8/1/2025 9:15 AM Flutter-Debug.xcconfig 8/1/2025 9:15 AM Flutter-Release.xcconfig 8/1/2025 9:15 AM GeneratedPluginRegistrant.swift C:\dev\example\ Mode LastWriteTime Length Name ---- ------------- ------ ---- 8/1/2025 9:15 AM pubspec.lock 8/1/2025 9:15 AM pubspec.yaml C:\dev\example\windows\flutter\ Mode LastWriteTime Length Name ---- ------------- ------ ---- 8/1/2025 9:15 AM generated\_plugin\_registrant.cc 8/1/2025 9:15 AM generated\_plugins.cmake \`\`\` Installing \`url\_launcher\` added config files and code files for all target platforms in the Flutter app directory. ## Debug Dart and native language code at the same time This section explains how to debug the Dart code in your Flutter app and any native code with its regular debugger. This capability allows you to leverage Flutter's hot reload when editing native code. ### Debug Dart and Android code using Android Studio To debug native Android code, you need a Flutter app that contains Android code. In this section, you learn how to connect the Dart, Java, and Kotlin debuggers to your app. You don't need VS Code to debug both Dart and Android code. This guide includes the VS Code instructions to be consistent with the Xcode and Visual Studio guides. These section uses the same example Flutter \`url\_launcher\` app created in \[Update test Flutter app]\(#update-test-flutter-app). #### Build the Android version of the Flutter app in the Terminal To generate the needed Android platform dependencies, run the \`flutter build\` command. \`\`\`console flutter build appbundle --debug \`\`\` \`\`\`console Running Gradle task 'bundleDebug'... 27.1s ✓ Built build/app/outputs/bundle/debug/app-debug.aab. \`\`\` #### Start debugging with VS Code first {:#from-vscode-to-android-studio} If you use VS Code to debug most of your code, start with this section. 1. To open the Flutter app directory, go to \*\*File\*\* > \*\*Open Folder...\*\* and choose the \`my\_app\` directory. 1. Open the \`lib/main.dart\` file. 1. If you can build an app for more than one device, you must select the device first. Go to \*\*View\*\* > \*\*Command Palette...\*\* You can also press `Ctrl` / `Cmd` + `Shift` + `P`. 1. Type \`flutter select\`. 1. Click the \*\*Flutter: Select Device\*\* command. 1. Choose your target device. 1. Click the debug icon (!\[VS Code's bug icon to trigger the debugging mode of a Flutter app]\(/assets/images/docs/testing/debugging/vscode-ui/icons/debug.png)). This opens the \*\*Debug\*\* pane and launches the app. Wait for the app to launch on the device and for the debug pane to indicate \*\*Connected\*\*. The debugger takes longer to launch the first time. Subsequent launches start faster. This Flutter app contains two buttons: - \*\*Launch in browser\*\*: This button opens this page in the default browser of your device. - \*\*Launch in app\*\*: This button opens this page within your app. This button only works for iOS or Android. Desktop apps launch a browser. #### Attach to the Flutter process in Android Studio 1. Click the \*\*Attach debugger to Android process\*\* button. (!\[Tiny green bug superimposed with a light grey arrow]\(/assets/images/docs/testing/debugging/native/android-studio/attach-process-button.png)) :::tip If this button doesn't appear in the \*\*Projects\*\* menu bar, verify that you opened Flutter \_application\_ project but \_not a Flutter plugin\_. ::: 1. The \*\*process\*\* dialog displays one entry for each connected device. Select \*\*show all processes\*\* to display available processes for each device. 1. Choose the process to which you want to attach. For this guide, select the \`com.example.my\_app\` process using the \*\*Emulator Pixel\_5\_API\_33\*\*. 1. Locate the tab for \*\*Android Debugger\*\* in the \*\*Debug\*\* pane. 1. In the \*\*Project\*\* pane, expand \*\*my\_app\_android\*\* > \*\*android\*\* > \*\*app\*\* > \*\*src\*\* > \*\*main\*\* > \*\*java\*\* > \*\*io.flutter plugins\*\*. 1. Double click \*\*GeneratedProjectRegistrant\*\* to open the Java code in the \*\*Edit\*\* pane. At the end of this procedure, both the Dart and Android debuggers interact with the same process. Use either, or both, to set breakpoints, examine stack, resume execution and the like. In other words, debug! #### Start debugging with Android Studio first {:#from-android-studio} If you use Android Studio to debug most of your code, start with this section. 1. To open the Flutter app directory, go to \*\*File\*\* > \*\*Open...\*\* and choose the \`my\_app\` directory. 1. Open the \`lib/main.dart\` file. 1. Choose a virtual Android device. Go to the toolbar, open the leftmost dropdown menu, and click on \*\*Open Android Emulator: \\\*\*. You can choose any installed emulator that's doesn't include \`arm64\`. 1. From that same menu, select the virtual Android device. 1. From the toolbar, click \*\*Run 'main.dart'\*\*. You can also press `Ctrl` + `Shift` + `R`. After the app displays in the emulator, continue to the next step. 1. Click the \*\*Attach debugger to Android process\*\* button. (!\[Tiny green bug superimposed with a light grey arrow]\(/assets/images/docs/testing/debugging/native/android-studio/attach-process-button.png)) :::tip If this button doesn't appear in the \*\*Projects\*\* menu bar, verify that you opened Flutter \_application\_ project but \_not a Flutter plugin\_. ::: 1. The \*\*process\*\* dialog displays one entry for each connected device. Select \*\*show all processes\*\* to display available processes for each device. 1. Choose the process to which you want to attach. For this guide, select the \`com.example.my\_app\` process using the \*\*Emulator Pixel\_5\_API\_33\*\*. 1. Locate the tab for \*\*Android Debugger\*\* in the \*\*Debug\*\* pane. 1. In the \*\*Project\*\* pane, expand \*\*my\_app\_android\*\* > \*\*android\*\* > \*\*app\*\* > \*\*src\*\* > \*\*main\*\* > \*\*java\*\* > \*\*io.flutter plugins\*\*. 1. Double click \*\*GeneratedProjectRegistrant\*\* to open the Java code in the \*\*Edit\*\* pane. At the end of this procedure, both the Dart and Android debuggers interact with the same process. Use either, or both, to set breakpoints, examine stack, resume execution and the like. In other words, debug! ### Debug Dart and iOS code using Xcode To debug iOS code, you need a Flutter app that contains iOS code. In this section, you learn to connect two debuggers to your app: Flutter via VS Code and Xcode. You need to run both VS Code and Xcode. These section uses the same example Flutter \`url\_launcher\` app created in \[Update test Flutter app]\(#update-test-flutter-app). #### Build the iOS version of the Flutter app in the Terminal To generate the needed iOS platform dependencies, run the \`flutter build\` command. \`\`\`console $ flutter build ios --config-only --no-codesign --debug \`\`\` \`\`\`console Warning: Building for device with codesigning disabled. You will have to manually codesign before deploying to device. Building com.example.myApp for device (ios)... \`\`\` #### Start debugging with VS Code first {:#vscode-ios} If you use VS Code to debug most of your code, start with this section. ##### Start the Dart debugger in VS Code 1. To open the Flutter app directory, go to \*\*File\*\* > \*\*Open Folder...\*\* and choose the \`my\_app\` directory. 1. Open the \`lib/main.dart\` file. 1. If you can build an app for more than one device, you must select the device first. Go to \*\*View\*\* > \*\*Command Palette...\*\* You can also press `Ctrl` / `Cmd` + `Shift` + `P`. 1. Type \`flutter select\`. 1. Click the \*\*Flutter: Select Device\*\* command. 1. Choose your target device. 1. Click the debug icon (!\[VS Code's bug icon to trigger the debugging mode of a Flutter app]\(/assets/images/docs/testing/debugging/vscode-ui/icons/debug.png)). This opens the \*\*Debug\*\* pane and launches the app. Wait for the app to launch on the device and for the debug pane to indicate \*\*Connected\*\*. The debugger takes longer to launch the first time. Subsequent launches start faster. This Flutter app contains two buttons: - \*\*Launch in browser\*\*: This button opens this page in the default browser of your device. - \*\*Launch in app\*\*: This button opens this page within your app. This button only works for iOS or Android. Desktop apps launch a browser. ##### Attach to the Flutter process in Xcode To attach to the Flutter app in Xcode: 1. Go to \*\*Debug\*\* > \*\*Attach to Process\*\*. 1. Select \*\*Runner\*\*. It should be at the top of the \*\*Attach to Process\*\* menu under the \*\*Likely Targets\*\* heading. #### Start debugging with Xcode first {:#xcode-ios} If you use Xcode to debug most of your code, start with this section. ##### Start the Xcode debugger 1. Open \`ios/Runner.xcworkspace\` from your Flutter app directory. 1. Select the correct device using the \*\*Scheme\*\* menu in the toolbar. If you have no preference, choose \*\*iPhone Pro 14\*\*. 1. Run this Runner as a normal app in Xcode. When the run completes, the \*\*Debug\*\* area at the bottom of Xcode displays a message with the Dart VM service URI. It resembles the following response: \`\`\`console 2023-07-12 14:55:39.966191-0500 Runner\[58361:53017145] flutter: The Dart VM service is listening on http\://127.0.0.1:50642/00wEOvfyff8=/ \`\`\` 1. Copy the Dart VM service URI. ##### Attach to the Dart VM in VS Code 1. To open the command palette, go to \*\*View\*\* > \*\*Command Palette...\*\* You can also press `Cmd` + `Shift` + `P`. 1. Type \`debug\`. 1. Click the \*\*Debug: Attach to Flutter on Device\*\* command. 1. In the \*\*Paste an VM Service URI\*\* box, paste the URI you copied from Xcode and press `Enter`. ### Debug Dart and macOS code using Xcode To debug macOS code, you need a Flutter app that contains macOS code. In this section, you learn to connect two debuggers to your app: Flutter via VS Code and Xcode. You need to run both VS Code and Xcode. These section uses the same example Flutter \`url\_launcher\` app created in \[Update test Flutter app]\(#update-test-flutter-app). #### Build the macOS version of the Flutter app in the Terminal To generate the needed macOS platform dependencies, run the \`flutter build\` command. \`\`\`console flutter build macos --debug \`\`\` \`\`\`console Building macOS application... \`\`\` #### Start debugging with VS Code first {:#vscode-macos} ##### Start the debugger in VS Code 1. To open the Flutter app directory, go to \*\*File\*\* > \*\*Open Folder...\*\* and choose the \`my\_app\` directory. 1. Open the \`lib/main.dart\` file. 1. If you can build an app for more than one device, you must select the device first. Go to \*\*View\*\* > \*\*Command Palette...\*\* You can also press `Ctrl` / `Cmd` + `Shift` + `P`. 1. Type \`flutter select\`. 1. Click the \*\*Flutter: Select Device\*\* command. 1. Choose your target device. 1. Click the debug icon (!\[VS Code's bug icon to trigger the debugging mode of a Flutter app]\(/assets/images/docs/testing/debugging/vscode-ui/icons/debug.png)). This opens the \*\*Debug\*\* pane and launches the app. Wait for the app to launch on the device and for the debug pane to indicate \*\*Connected\*\*. The debugger takes longer to launch the first time. Subsequent launches start faster. This Flutter app contains two buttons: - \*\*Launch in browser\*\*: This button opens this page in the default browser of your device. - \*\*Launch in app\*\*: This button opens this page within your app. This button only works for iOS or Android. Desktop apps launch a browser. ##### Attach to the Flutter process in Xcode 1. To attach to the Flutter app, go to \*\*Debug\*\* > \*\*Attach to Process\*\* > \*\*Runner\*\*. \*\*Runner\*\* should be at the top of the \*\*Attach to Process\*\* menu under the \*\*Likely Targets\*\* heading. #### Start debugging with Xcode first {:#xcode-macos} ##### Start the debugger in Xcode 1. Open \`macos/Runner.xcworkspace\` from your Flutter app directory. 1. Run this Runner as a normal app in Xcode. When the run completes, the \*\*Debug\*\* area at the bottom of Xcode displays a message with the Dart VM service URI. It resembles the following response: \`\`\`console 2023-07-12 14:55:39.966191-0500 Runner\[58361:53017145] flutter: The Dart VM service is listening on http\://127.0.0.1:50642/00wEOvfyff8=/ \`\`\` 1. Copy the Dart VM service URI. ##### Attach to the Dart VM in VS Code 1. To open the command palette, go to \*\*View\*\* > \*\*Command Palette...\*\* You can also press `Cmd` + `Shift` + `P`. 1. Type \`debug\`. 1. Click the \*\*Debug: Attach to Flutter on Device\*\* command. 1. In the \*\*Paste an VM Service URI\*\* box, paste the URI you copied from Xcode and press `Enter`. ### Debug Dart and C++ code using Visual Studio To debug C++ code, you need a Flutter app that contains C++ code. In this section, you learn to connect two debuggers to your app: Flutter via VS Code and Visual Studio. You need to run both VS Code and Visual Studio. These section uses the same example Flutter \`url\_launcher\` app created in \[Update test Flutter app]\(#update-test-flutter-app). #### Build the Windows version of the Flutter app in PowerShell or the Command Prompt To generate the needed Windows platform dependencies, run the \`flutter build\` command. \`\`\`console C:\\> flutter build windows --debug \`\`\` \`\`\`console Building Windows application... 31.4s √ Built build\windows\runner\Debug\my\_app.exe. \`\`\` #### Start debugging with VS Code first {:#vscode-windows} If you use VS Code to debug most of your code, start with this section. ##### Start the debugger in VS Code 1. To open the Flutter app directory, go to \*\*File\*\* > \*\*Open Folder...\*\* and choose the \`my\_app\` directory. 1. Open the \`lib/main.dart\` file. 1. If you can build an app for more than one device, you must select the device first. Go to \*\*View\*\* > \*\*Command Palette...\*\* You can also press `Ctrl` / `Cmd` + `Shift` + `P`. 1. Type \`flutter select\`. 1. Click the \*\*Flutter: Select Device\*\* command. 1. Choose your target device. 1. Click the debug icon (!\[VS Code's bug icon to trigger the debugging mode of a Flutter app]\(/assets/images/docs/testing/debugging/vscode-ui/icons/debug.png)). This opens the \*\*Debug\*\* pane and launches the app. Wait for the app to launch on the device and for the debug pane to indicate \*\*Connected\*\*. The debugger takes longer to launch the first time. Subsequent launches start faster. This Flutter app contains two buttons: - \*\*Launch in browser\*\*: This button opens this page in the default browser of your device. - \*\*Launch in app\*\*: This button opens this page within your app. This button only works for iOS or Android. Desktop apps launch a browser. ##### Attach to the Flutter process in Visual Studio 1. To open the project solution file, go to \*\*File\*\* > \*\*Open\*\* > \*\*Project/Solution…\*\* You can also press `Ctrl` + `Shift` + `O`. 1. Choose the \`build/windows/my\_app.sln\` file in your Flutter app directory. 1. Go to \*\*Debug\*\* > \*\*Attach to Process\*\*. You can also press `Ctrl` + `Alt` + `P`. 1. From the \*\*Attach to Process\*\* dialog box, choose \`my\_app.exe\`. Visual Studio starts monitoring the Flutter app. #### Start debugging with Visual Studio first If you use Visual Studio to debug most of your code, start with this section. ##### Start the local Windows debugger 1. To open the project solution file, go to \*\*File\*\* > \*\*Open\*\* > \*\*Project/Solution…\*\* You can also press `Ctrl` + `Shift` + `O`. 1. Choose the \`build/windows/my\_app.sln\` file in your Flutter app directory. 1. Set \`my\_app\` as the startup project. In the \*\*Solution Explorer\*\*, right-click on \`my\_app\` and select \*\*Set as Startup Project\*\*. 1. Click \*\*Local Windows Debugger\*\* to start debugging. You can also press `F5`. When the Flutter app has started, a console window displays a message with the Dart VM service URI. It resembles the following response: \`\`\`console flutter: The Dart VM service is listening on http\://127.0.0.1:62080/KPHEj2qPD1E=/ \`\`\` 1. Copy the Dart VM service URI. ##### Attach to the Dart VM in VS Code 1. To open the command palette, go to \*\*View\*\* > \*\*Command Palette...\*\* You can also press `Cmd` + `Shift` + `P`. 1. Type \`debug\`. 1. Click the \*\*Debug: Attach to Flutter on Device\*\* command. 1. In the \*\*Paste an VM Service URI\*\* box, paste the URI you copied from Visual Studio and press `Enter`. ## Resources Check out the following resources on debugging Flutter, iOS, Android, macOS and Windows: ### Flutter - \[Debugging Flutter apps]\[] - \[Flutter inspector]\[] and the \[DevTools]\[] docs - \[Performance profiling]\[] \[Debugging Flutter apps]: /testing/debugging \[Performance profiling]: /perf/ui-performance ### Android You can find the following debugging resources on \[developer.android.com]\[]. - \[Debug your app]\[] - \[Android Debug Bridge (adb)]\[] ### iOS and macOS You can find the following debugging resources on \[developer.apple.com]\[]. - \[Debugging]\[] - \[Instruments Help]\[] ### Windows You can find debugging resources on \[Microsoft Learn]\[]. - \[Visual Studio Debugger]\[] - \[Learn to debug C++ code using Visual Studio]\[] \[Android Debug Bridge (adb)]: https\://developer.android.com/studio/command-line/adb \[Debug your app]: https\://developer.android.com/studio/debug \[Debugging]: https\://developer.apple.com/support/debugging/ \[developer.android.com]: https\://developer.android.com \[developer.apple.com]: https\://developer.apple.com \[DevTools]: /tools/devtools \[Flutter inspector]: /tools/devtools/inspector \[Instruments Help]: https\://help.apple.com/instruments/mac/current/ \[Microsoft Learn]: https\://learn.microsoft.com/visualstudio/ \[Visual Studio Debugger]: https\://learn.microsoft.com/visualstudio/debugger/?view=vs-2022 \[Learn to debug C++ code using Visual Studio]: https\://learn.microsoft.com/visualstudio/debugger/getting-started-with-the-debugger-cpp?view=vs-2022

----
url: https://docs.flutter.dev/cookbook/design/drawer.md
----

\# Add a drawer to a screen > How to implement a Material Drawer.

<!--?code-excerpt path-base="cookbook/design/drawer"?-->

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, use the \[\`Drawer\`]\[] widget in combination with a \[\`Scaffold\`]\[] to create a layout with a Material Design drawer. This recipe uses the following steps: 1. Create a \`Scaffold\`. 2. Add a drawer. 3. Populate the drawer with items. 4. Close the drawer programmatically. ## 1. Create a \`Scaffold\` To add a drawer to the app, wrap it in a \[\`Scaffold\`]\[] 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 example, create a \`Scaffold\` with a \`drawer\`:

<!--?code-excerpt "lib/drawer.dart (DrawerStart)" replace="/null, //g"?-->

\`\`\`dart Scaffold( appBar: AppBar(title: const Text('AppBar without hamburger button')), drawer: // Add a Drawer here in the next step. ); \`\`\` ## 2. Add a drawer 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]\[], which adheres to the Material Design spec.

<!--?code-excerpt "lib/drawer.dart (DrawerEmpty)" replace="/null, //g"?-->

\`\`\`dart Scaffold( appBar: AppBar(title: const Text('AppBar with hamburger button')), drawer: Drawer( child: // Populate the Drawer in the next step. ), ); \`\`\` ## 3. Populate the drawer with items Now that you have a \`Drawer\` in place, add content to it. For this example, use a \[\`ListView\`]\[]. 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. Populate the \`ListView\` with a \[\`DrawerHeader\`]\[] and two \[\`ListTile\`]\[] widgets. For more information on working with Lists, see the \[list recipes]\[].

<!--?code-excerpt "lib/drawer.dart (DrawerListView)"?-->

\`\`\`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 // space to fit everything. child: ListView( // Important: Remove any padding from the ListView. padding: EdgeInsets.zero, children: \[ const DrawerHeader( decoration: BoxDecoration(color: Colors.blue), child: Text('Drawer Header'), ), ListTile( title: const Text('Item 1'), onTap: () { // Update the state of the app. // ... }, ), ListTile( title: const Text('Item 2'), onTap: () { // Update the state of the app. // ... }, ), ], ), ); \`\`\` ## 4. Open the drawer programmatically Typically, you don't need to write any code to open a \`drawer\`, Because when the \`leading\` widget is null, the default implementation in \`AppBar\` is \`DrawerButton\`. But if you want to have free control of the \`drawer\`. You can do this by using the \`Builder\` call \`Scaffold.of(context).openDrawer()\`.

<!--?code-excerpt "lib/drawer.dart (DrawerOpen)" replace="/null, //g"?-->

\`\`\`dart Scaffold( appBar: AppBar( title: const Text('AppBar with hamburger button'), leading: Builder( builder: (context) { return IconButton( icon: const Icon(Icons.menu), onPressed: () { Scaffold.of(context).openDrawer(); }, ); }, ), ), drawer: Drawer( child: // Populate the Drawer in the last step. ), ); \`\`\` ## 5. Close the drawer programmatically After a user taps an item, you might want to close the drawer. You can do this by using the \[\`Navigator\`]\[]. When a user opens the drawer, Flutter adds the drawer to the navigation stack. Therefore, to close the drawer, call \`Navigator.pop(context)\`.

<!--?code-excerpt "lib/drawer.dart (CloseDrawer)"?-->

\`\`\`dart ListTile( title: const Text('Item 1'), onTap: () { // Update the state of the app // ... // Then close the drawer Navigator.pop(context); }, ), \`\`\` ## Interactive example This example shows a \[\`Drawer\`]\[] as it is used within a \[\`Scaffold\`]\[] widget. The \[\`Drawer\`]\[] has three \[\`ListTile\`]\[] items. The \`\_onItemTapped\` function changes the selected item's index and displays the corresponding text in the center of the \`Scaffold\`. :::note For more information on implementing navigation, check out the \[Navigation]\[] section of the cookbook. :::

<!--?code-excerpt "lib/main.dart"?-->

\`\`\`dartpad title="Flutter drawer hands-on example in DartPad" run="true" import 'package:flutter/material.dart'; void main() => runApp(const MyApp()); class MyApp extends StatelessWidget { const MyApp({super.key}); static const appTitle = 'Drawer Demo'; @override Widget build(BuildContext context) { return const MaterialApp( title: appTitle, home: MyHomePage(title: appTitle), ); } } class MyHomePage extends StatefulWidget { const MyHomePage({super.key, required this.title}); final String title; @override State createState() => \_MyHomePageState(); } class \_MyHomePageState extends State { int \_selectedIndex = 0; static const TextStyle optionStyle = TextStyle( fontSize: 30, fontWeight: FontWeight.bold, ); static const List \_widgetOptions = \[ Text('Index 0: Home', style: optionStyle), Text('Index 1: Business', style: optionStyle), Text('Index 2: School', style: optionStyle), ]; void \_onItemTapped(int index) { setState(() { \_selectedIndex = index; }); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text(widget.title), leading: Builder( builder: (context) { return IconButton( icon: const Icon(Icons.menu), onPressed: () { Scaffold.of(context).openDrawer(); }, ); }, ), ), body: Center(child: \_widgetOptions\[\_selectedIndex]), 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 // space to fit everything. child: ListView( // Important: Remove any padding from the ListView. padding: EdgeInsets.zero, children: \[ const DrawerHeader( decoration: BoxDecoration(color: Colors.blue), child: Text('Drawer Header'), ), ListTile( title: const Text('Home'), selected: \_selectedIndex == 0, onTap: () { // Update the state of the app \_onItemTapped(0); // Then close the drawer Navigator.pop(context); }, ), ListTile( title: const Text('Business'), selected: \_selectedIndex == 1, onTap: () { // Update the state of the app \_onItemTapped(1); // Then close the drawer Navigator.pop(context); }, ), ListTile( title: const Text('School'), selected: \_selectedIndex == 2, onTap: () { // Update the state of the app \_onItemTapped(2); // Then close the drawer Navigator.pop(context); }, ), ], ), ), ); } } \`\`\` \[\`Drawer\`]: https\://pub.dev/documentation/material\_ui/latest/material\_ui/Drawer-class.html \[\`DrawerHeader\`]: https\://pub.dev/documentation/material\_ui/latest/material\_ui/DrawerHeader-class.html \[list recipes]: /cookbook/lists \[\`ListTile\`]: https\://pub.dev/documentation/material\_ui/latest/material\_ui/ListTile-class.html \[\`ListView\`]: https\://pub.dev/documentation/material\_ui/latest/material\_ui/ListView-class.html \[material library]: https\://pub.dev/documentation/material\_ui/latest/material\_ui \[\`Navigator\`]: https\://pub.dev/documentation/material\_ui/latest/material\_ui/Navigator-class.html \[\`Scaffold\`]: https\://pub.dev/documentation/material\_ui/latest/material\_ui/Scaffold-class.html \[Navigation]: /cookbook/navigation

----
url: https://docs.flutter.dev/platform-integration/android/setup.md
----

\# Set up Android development > Configure your development environment to run, build, and deploy Flutter apps for Android devices. Learn how to set up your development environment to run, build, and deploy Flutter apps for Android devices. :::warning If you haven't set up Flutter already, visit and follow \[Install Flutter]\[] first. Installing the Flutter plugin for Android Studio is \*\*not\*\* enough; you must also install the Flutter SDK and add its \`bin\` directory to your PATH to use the \`flutter\` command. ::: :::note If you've already installed Flutter, ensure that it's \[up to date]\[]. ::: \[Install Flutter]: /install \[up to date]: /install/upgrade ## Choose your development platform {: #dev-platform} The instructions on this page are configured to cover setting up Android development on a \*\*Windows\*\*{:.selected-os-text} device. If you'd like to follow the instructions for a different OS, please select one of the following. ## Set up Android tooling {: #set-up-tooling} With Android Studio, you can run Flutter apps on a physical Android device or an Android Emulator. If you haven't done so already, install and set up the latest stable version of \[Android Studio]\[]. 1.

### Install prerequisites libraries

If you're developing on Linux, first install the \[prerequisite collection of 32-bit libraries]\[64bit-libs] that Android Studio requires. {: .linux-only} 1.

### Install Android Studio

If you haven't done so already, \[install and set up]\[as-install] the latest stable version of \[Android Studio]\[]. If you already have Android Studio installed, ensure that it's \[up to date]\[as-update]. 1.

### Install Android SDK and tools

1\. Launch \*\*Android Studio\*\*. 1. Open the \*\*SDK Manager\*\* settings dialog. 1. If the \*\*Welcome to Android Studio\*\* dialog is open, click the \*\*More Actions\*\* button that follows the \*\*New Project\*\* and \*\*Open\*\* buttons, then click \*\*SDK Manager\*\* from the dropdown menu. 1. If you have a project open, go to \*\*Tools\*\* > \*\*SDK Manager\*\*. {: type="a"} 1. If the \*\*SDK Platforms\*\* tab is not open, switch to it. 1. Verify that the first entry with an \*\*API Level\*\* of \*\*36\*\* has been selected. If the \*\*Status\*\* column displays \*\*Update available\*\* or \*\*Not installed\*\*: 1. Select the checkbox for that entry or row. 1. Click \*\*Apply\*\*. 1. When the \*\*Confirm Change\*\* dialog displays, click \*\*OK\*\*. The \*\*SDK Component Installer\*\* dialog displays with a progress indicator. 1. When the installation finishes, click \*\*Finish\*\*. {: type="a"} 1. Switch to the \*\*SDK Tools\*\* tab. 1. Verify that the following SDK Tools have been selected: - \*\*Android SDK Build-Tools\*\* - \*\*Android SDK Command-line Tools\*\* - \*\*Android Emulator\*\* - \*\*Android SDK Platform-Tools\*\* - \*\*CMake\*\* - \*\*NDK (Side by side)\*\* 1. If the \*\*Status\*\* column for any of the preceding tools displays \*\*Update available\*\* or \*\*Not installed\*\*: 1. Select the checkbox for the necessary tools. 1. Click \*\*Apply\*\*. 1. When the \*\*Confirm Change\*\* dialog displays, click \*\*OK\*\*. The \*\*SDK Component Installer\*\* dialog displays with a progress indicator. 1. When the installation finishes, click \*\*Finish\*\*. {: type="a"} 1.

### Agree to the Android licenses

Before you can use Flutter and after you install all prerequisites, agree to the licenses of the Android SDK platform. 1. Open your preferred terminal. 1. Run the following command to review and sign the SDK licenses. \`\`\`console $ flutter doctor --android-licenses \`\`\` 1. Read and accept any necessary licenses. If you haven't accepted each of the SDK licenses previously, you'll need to review and agree to them before developing for Android. Before agreeing to the terms of each license, read each with care. Once you've accepted all the necessary licenses successfully, you should see output similar to the following: \`\`\`console All SDK package licenses accepted. \`\`\` {: .steps} \[Android Studio]: https\://developer.android.com/studio \[64bit-libs]: https\://developer.android.com/studio/install#64bit-libs \[as-install]: https\://developer.android.com/studio/install \[as-update]: https\://developer.android.com/studio/intro/update ## Set up an Android device {: #set-up-devices} You can debug Flutter apps on physical Android devices or by running them on an Android emulator. To set up your development environment to run a Flutter app on an Android emulator, follow these steps: 1.

### Set up your development device

Enable \[VM acceleration]\[] on your development computer. 1.

### Set up a new emulator

1\. Start \*\*Android Studio\*\*. 1. Open the \*\*Device Manager\*\* settings dialog. 1. If the \*\*Welcome to Android Studio\*\* dialog is open, click the \*\*More Actions\*\* button that follows the \*\*New Project\*\* and \*\*Open\*\* buttons, then select \*\*Virtual Device Manager\*\* from the dropdown menu. 1. If you have a project open, go to \*\*Tools\*\* > \*\*Device Manager\*\*. {: type="a"} 1. Click the \*\*Create Virtual Device\*\* button that appears as a \`+\` icon. The \*\*Virtual Device Configuration\*\* dialog displays. 1. Select either \*\*Phone\*\* or \*\*Tablet\*\* under \*\*Form Factor\*\*. 1. Select a device definition. You can browse or search for the device. 1. Click \*\*Next\*\*. 1. If the option is provided, select either \*\*x86 Images\*\* or \*\*ARM Images\*\* depending on if your development computer is an x64 or Arm64 device. 1. Select one system image for the Android version you want to emulate. 1. If the desired image has a \*\*Download\*\* icon to the left of the system image name, click it. The \*\*SDK Component Installer\*\* dialog displays with a progress indicator. 1. When the download completes, click \*\*Finish\*\*. {: type="a"} 1. Click \*\*Additional settings\*\* in the top tab bar and scroll to \*\*Emulated Performance\*\*. 1. From the \*\*Graphics acceleration\*\* dropdown menu, select an option that mentions \*\*Hardware\*\*. This enables \[hardware acceleration]\[], improving render performance. 1. Verify your virtual device configuration. If it is correct, click \*\*Finish\*\*. To learn more about virtual devices, check out \[Create and manage virtual devices]\[]. 1.

### Try running the emulator

In the \*\*Device Manager\*\* dialog, click the \*\*Run\*\* icon to the right of your desired virtual device. The emulator should start up and display the default canvas for your selected Android OS version and device. {: .steps} \[VM acceleration]: https\://developer.android.com/studio/run/emulator-acceleration#accel-vm \[hardware acceleration]: https\://developer.android.com/studio/run/emulator-acceleration \[Create and manage virtual devices]: https\://developer.android.com/studio/run/managing-avds To set up your development environment to run a Flutter app on a physical Android device, follow these steps: 1.

### Configure your device

Enable \*\*Developer options\*\* and \*\*USB debugging\*\* on your device as described in \[Configure on-device developer options]\[]. 1.

### Enable wireless debugging

To leverage wireless debugging, enable \*\*Wireless debugging\*\* on your device as described in \[Connect to your device using Wi-Fi]\[]. {: .windows-only} 1.

### Install platform prerequisites

If you're developing on Windows, first install the necessary USB driver for your particular device as described in \[Install OEM USB drivers]\[]. 1.

### Connect your device

Plug your device into your computer. If your device prompts you, authorize your computer to access your Android device. 1.

### Verify the device connection

To verify that Flutter recognizes your connected Android device, run \`flutter devices\` in your preferred terminal: \`\`\`console $ flutter devices \`\`\` Your device should be found and show up as a connected device. {: .steps} \[Configure on-device developer options]: https\://developer.android.com/studio/debug/dev-options \[Connect to your device using Wi-Fi]: https\://developer.android.com/studio/run/device#wireless \[Install OEM USB drivers]: https\://developer.android.com/studio/run/oem-usb ## Validate your setup {: #validate-setup} 1.

### Check for toolchain issues

To check for any issues with your Android development setup, run the \`flutter doctor\` command in your preferred terminal: \`\`\`console $ flutter doctor \`\`\` If you see any errors or tasks to complete under the \*\*Android toolchain\*\* or \*\*Android Studio\*\* sections, Complete any mentioned tasks and then run \`flutter doctor\` again to verify any changes. 1.

### Check for Android devices

To ensure you set up your emulator and/or physical Android device correctly, run \`flutter emulators\` and \`flutter devices\` in your preferred terminal: \`\`\`console $ flutter emulators && flutter devices \`\`\` Depending on if you set up an emulator or a device, at least one should output an entry with the platform marked as \*\*android\*\*. 1.

### Troubleshoot setup issues

If you need help resolving any setup issues, check out \[Install and setup troubleshooting]\[]. If you still have issues or questions, reach out on one of the Flutter \[community]\[] channels. {: .steps} \[Install and setup troubleshooting]: /install/troubleshoot#android-setup \[community]: https\://flutter.dev/community ## Start developing for Android {: #start-developing} Congratulations! Now that you've set up Android development for Flutter, you can continue your Flutter learning journey while testing on Android or begin improving integration with Android.

----
url: https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.11.2.md
----

\# DevTools 2.11.2 release notes > Release notes for Dart and Flutter DevTools version 2.11.2. The 2.11.2 release of the Dart and Flutter DevTools includes the following changes among other general improvements. To learn more about DevTools, check out the \[DevTools overview]\(https\://docs.flutter.dev/tools/devtools). ## General updates \* This release included a lot of cleanup and reduction in technical debt. ## CPU profiler updates \* Added the source line number to file uris in CPU profiles - \[#3718]\(https\://github.com/flutter/devtools/pull/3718) !\[cpu stack frame line numbers]\(/assets/images/docs/tools/devtools/release-notes/images-2.11.2/image1.png "cpu stack frame line numbers") ## Debugger updates \* File opener UX improvements, including support for clicking the source file name to open the file search window - \[#3612]\(https\://github.com/flutter/devtools/pull/3612), \[#3758]\(https\://github.com/flutter/devtools/pull/3758) \* Added support for auto-scrolling the File Explorer to the selected file - \[#3786]\(https\://github.com/flutter/devtools/pull/3786), \[#3794]\(https\://github.com/flutter/devtools/pull/3794) !\[debugger file explorer scrolling]\(/assets/images/docs/tools/devtools/release-notes/images-2.11.2/image2.gif "debugger file explorer scrolling") ## Full commit history To find a complete list of changes since the previous release, check out \[the diff on GitHub]\(https\://github.com/flutter/devtools/compare/v2.10.0...v2.11.2).

----
url: https://docs.flutter.dev/platform-integration/android/platform-views.md
----

\# Hosting native Android views in your Flutter app with Platform Views > Learn how to host native Android views in your Flutter app with Platform Views.

<!--?code-excerpt path-base="platform_integration/platform_views"?-->

Platform views allow you to embed native views in a Flutter app, so you can apply transforms, clips, and opacity to the native view from Dart. This allows you, for example, to use the native Google Maps from the Android SDK directly inside your Flutter app. :::note This page discusses how to host your own native Android views within a Flutter app. If you'd like to embed native iOS views in your Flutter app, visit \[Hosting native iOS views]\[]. To embed native macOS views in your Flutter app, visit \[Hosting native macOS views]\[]. ::: \[Hosting native iOS views]: /platform-integration/ios/platform-views \[Hosting native macOS views]: /platform-integration/macos/platform-views Platform Views on Android have several implementations. They come with tradeoffs both in terms of performance and fidelity. ### Choosing an implementation The following matrix summarizes the different implementations and their trade-offs: | Mode | Benefits | Considerations | Enabler | | :--- | :--- | :--- | :--- | | \*\*Texture layer\*\* | • Good Flutter performance\
• Full widget transforms work | • Janky during quick scrolling\
• SurfaceViews lose accessibility and text magnifier breaks\
• Platform View -> Renders to texture -> Uploads to Impeller -> Impeller composites Flutter content and Platform View content | Default behavior or standard \`AndroidView\` | | \*\*Hybrid composition\*\* | • Full native fidelity\
• Correct accessibility and SurfaceView support | • Causes thread merging of raster & platform, which degrades Flutter FPS\
• Platform View -> Renders into the Android view hierarchy as usual, Flutter content renders into \`ImageReader\`-backed \`FlutterImageView\`s (a background layer, plus an overlay layer for content drawn above a platform view), the Android view hierarchy composites them together |• \`PlatformViewLink\` with \`AndroidViewSurface\`\
• \[\`AndroidViewController\` builds either a TLHC or an HC Platform View]\[AC] | | \*\*HCPP\*\* (Experimental) | • Full fidelity and performance\
• Solves original sync overhead | • Requires Android API 34+, Vulkan support, and use of the Impeller rendering engine\
• Platform View -> Renders to native Android Surface, Impeller renders to native Android Surface, SurfaceFlinger composites the two together | • \`\` in \`AndroidManifest.xml\`\
• \`--enable-hcpp\` local flag\
•\[\`AndroidViewController\` builds either a TLHC or an HC Platform View]\[AC] | {:.table .table-striped} \[AC]: https\://github.com/flutter/flutter/blob/main/dev/integration\_tests/hybrid\_android\_views/lib/android\_platform\_view\.dart ## Hybrid composition {: #hybrid-composition } Platform views are rendered as they normally are, directly in the Android view hierarchy. Flutter content is rendered into \`ImageReader\`s and painted by \`FlutterImageView\`s that sit in the same window as the platform views, so the Android view hierarchy composites them together. Flutter uses two kinds of these layers. The main Flutter render surface is swapped for a background \`FlutterImageView\` once a platform view is added. Flutter content that draws on top of a platform view goes into an additional overlay \`FlutterImageView\`, allocated from a pool and recycled between frames. ## Hybrid composition++ (HCPP) {: #hcpp } :::version-note This feature is experimental and is available starting from Flutter 3.44. ::: HCPP is the latest hybrid composition strategy, designed to solve compositing performance and synchronization issues seen in the original Hybrid Composition mode. It is currently available as an opt-in feature. ### Requirements \* \*\*Android API 34 or later\*\*: Required for native transaction synchronization capabilities. \* \*\*Impeller with the Vulkan backend\*\*: The device must be capable of rendering with Vulkan. Impeller also has an OpenGLES backend, and falls back to it on devices without usable Vulkan support. HCPP is not available in that configuration. If these requirements are not met on the end-user device, Flutter will automatically fall back to the existing platform view strategy configured for the app. ### Opt in Because HCPP acts as a global upgrade for how platform views are backed, it's enabled through configuration rather than standard Dart initialization methods (\`initAndroidView\`, and so on). You can enable HCPP using one of the following methods: 1. \*\*Command line flag (run/test)\*\*: Pass the \`--enable-hcpp\` flag to your \`flutter run\` or \`flutter test\` command: \`\`\`console $ flutter run --enable-hcpp \`\`\` :::note This flag is intended for local execution and testing. \*\*It can't be passed to the \`flutter build\` commands.\*\* For release builds, use the manifest configuration as shown in the next step. ::: 2. \*\*AndroidManifest.xml\*\*: Include a \`\` tag inside the \`\` block of your \`AndroidManifest.xml\`: \`\`\`xml \`\`\` ### Limitations and known issues \* \*\*Complex overlay stacking\*\*: Transparent platform views won't display correctly in layout stacks structured as: \*\*Flutter canvas -> Platform View -> Overlay -> Transparent Platform View\*\*, when all four of these layers intersect. ## Texture layer {: #texture-layer } Platform Views are rendered into a texture. Flutter draws the platform views (using the texture). Flutter content is rendered directly into a Surface. This approach provides: \* good performance for Android Views \* good performance for Flutter rendering \* all transformations work correctly However, this approach might cause: \* jankiness on quick scrolling (such as a web view) \* broken accessibility for \`SurfaceView\`s \* broken text magnification unless Flutter is rendered into a \`TextureView\` ## On the Dart side To create a platform view on Android, use the following steps. First, on the Dart side, create a \`Widget\` and add one of the following build implementations depending on your chosen strategy. ### Hybrid composition In your Dart file, for example \`native\_view\_example.dart\`, use the following instructions: 1. Add the following imports:

<!--?code-excerpt "lib/native_view_example_1.dart (import)"?-->

\`\`\`dart import 'package:flutter/foundation.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/services.dart'; import 'package:material\_ui/material\_ui.dart'; \`\`\` 2. Implement a \`build\` method:

<!--?code-excerpt "lib/native_view_example_1.dart (hybrid-composition)"?-->

\`\`\`dart Widget build(BuildContext context) { // This is used in the platform side to register the view. const String viewType = ''; // Pass parameters to the platform side. const Map creationParams = {}; return PlatformViewLink( viewType: viewType, surfaceFactory: (context, controller) { return AndroidViewSurface( controller: controller as AndroidViewController, gestureRecognizers: const >{}, hitTestBehavior: PlatformViewHitTestBehavior.opaque, ); }, onCreatePlatformView: (params) { return PlatformViewsService.initSurfaceAndroidView( id: params.id, viewType: viewType, layoutDirection: TextDirection.ltr, creationParams: creationParams, creationParamsCodec: const StandardMessageCodec(), onFocus: () { params.onFocusChanged(true); }, ) ..addOnPlatformViewCreatedListener(params.onPlatformViewCreated) ..create(); }, ); } \`\`\` For more information, visit the following API docs: \* \[\`PlatformViewLink\`]\[] \* \[\`AndroidViewSurface\`]\[] \* \[\`PlatformViewsService\`]\[] \[\`AndroidViewSurface\`]: https\://api.flutter.dev/flutter/widgets/AndroidViewSurface-class.html \[\`PlatformViewLink\`]: https\://api.flutter.dev/flutter/widgets/PlatformViewLink-class.html \[\`PlatformViewsService\`]: https\://api.flutter.dev/flutter/services/PlatformViewsService-class.html ### TextureLayerHybridComposition In your Dart file, for example \`native\_view\_example.dart\`, use the following instructions: 1. Add the following imports:

<!--?code-excerpt "lib/native_view_example_2.dart (import)"?-->

\`\`\`dart import 'package:flutter/services.dart'; import 'package:material\_ui/material\_ui.dart'; \`\`\` 2. Implement a \`build\` method:

<!--?code-excerpt "lib/native_view_example_2.dart (virtual-display)"?-->

\`\`\`dart Widget build(BuildContext context) { // This is used in the platform side to register the view. const String viewType = ''; // Pass parameters to the platform side. final Map creationParams = {}; return AndroidView( viewType: viewType, layoutDirection: TextDirection.ltr, creationParams: creationParams, creationParamsCodec: const StandardMessageCodec(), ); } \`\`\` For more information, visit the \[\`AndroidView\`]\[] API page. \[\`AndroidView\`]: https\://api.flutter.dev/flutter/widgets/AndroidView-class.html ## On the platform side On the platform side, use the standard \`io.flutter.plugin.platform\` package in either Kotlin or Java: In your native code, implement the following: Extend \`io.flutter.plugin.platform.PlatformView\` to provide a reference to the \`android.view\.View\` (for example, \`NativeView\.kt\`): \`\`\`kotlin package dev.flutter.example import android.content.Context import android.graphics.Color import android.view\.View import android.widget.TextView import io.flutter.plugin.platform.PlatformView internal class NativeView(context: Context, id: Int, creationParams: Map?) : PlatformView { private val textView: TextView override fun getView(): View { return textView } override fun dispose() {} init { textView = TextView(context) textView\.textSize = 72f textView\.setBackgroundColor(Color.rgb(255, 255, 255)) textView\.text = "Rendered on a native Android view (id: $id)" } } \`\`\` Create a factory class that creates an instance of the \`NativeView\` created earlier (for example, \`NativeViewFactory.kt\`): \`\`\`kotlin package dev.flutter.example import android.content.Context import io.flutter.plugin.common.StandardMessageCodec import io.flutter.plugin.platform.PlatformView import io.flutter.plugin.platform.PlatformViewFactory class NativeViewFactory : PlatformViewFactory(StandardMessageCodec.INSTANCE) { override fun create(context: Context, viewId: Int, args: Any?): PlatformView { val creationParams = args as Map? return NativeView(context, viewId, creationParams) } } \`\`\` Finally, register the platform view. You can do this in an app or a plugin. For app registration, modify the app's main activity (for example, \`MainActivity.kt\`): \`\`\`kotlin package dev.flutter.example import io.flutter.embedding.android.FlutterActivity import io.flutter.embedding.engine.FlutterEngine class MainActivity : FlutterActivity() { override fun configureFlutterEngine(flutterEngine: FlutterEngine) { super.configureFlutterEngine(flutterEngine) flutterEngine .platformViewsController .registry .registerViewFactory("", NativeViewFactory()) } } \`\`\` For plugin registration, modify the plugin's main class (for example, \`PlatformViewPlugin.kt\`): \`\`\`kotlin package dev.flutter.plugin.example import io.flutter.embedding.engine.plugins.FlutterPlugin import io.flutter.embedding.engine.plugins.FlutterPlugin.FlutterPluginBinding class PlatformViewPlugin : FlutterPlugin { override fun onAttachedToEngine(binding: FlutterPluginBinding) { binding .platformViewRegistry .registerViewFactory("", NativeViewFactory()) } override fun onDetachedFromEngine(binding: FlutterPluginBinding) {} } \`\`\` In your native code, implement the following: Extend \`io.flutter.plugin.platform.PlatformView\` to provide a reference to the \`android.view\.View\` (for example, \`NativeView\.java\`): \`\`\`java package dev.flutter.example; import android.content.Context; import android.graphics.Color; import android.view\.View; import android.widget.TextView; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import io.flutter.plugin.platform.PlatformView; import java.util.Map; class NativeView implements PlatformView { @NonNull private final TextView textView; NativeView(@NonNull Context context, int id, @Nullable Map creationParams) { textView = new TextView(context); textView\.setTextSize(72); textView\.setBackgroundColor(Color.rgb(255, 255, 255)); textView\.setText("Rendered on a native Android view (id: " + id + ")"); } @NonNull @Override public View getView() { return textView; } @Override public void dispose() {} } \`\`\` Create a factory class that creates an instance of the \`NativeView\` created earlier (for example, \`NativeViewFactory.java\`): \`\`\`java package dev.flutter.example; import android.content.Context; import androidx.annotation.Nullable; import androidx.annotation.NonNull; import io.flutter.plugin.common.StandardMessageCodec; import io.flutter.plugin.platform.PlatformView; import io.flutter.plugin.platform.PlatformViewFactory; import java.util.Map; class NativeViewFactory extends PlatformViewFactory { NativeViewFactory() { super(StandardMessageCodec.INSTANCE); } @NonNull @Override public PlatformView create(@NonNull Context context, int id, @Nullable Object args) { final Map creationParams = (Map) args; return new NativeView(context, id, creationParams); } } \`\`\` Finally, register the platform view. You can do this in an app or a plugin. For app registration, modify the app's main activity (for example, \`MainActivity.java\`): \`\`\`java package dev.flutter.example; import androidx.annotation.NonNull; import io.flutter.embedding.android.FlutterActivity; import io.flutter.embedding.engine.FlutterEngine; public class MainActivity extends FlutterActivity { @Override public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) { flutterEngine .getPlatformViewsController() .getRegistry() .registerViewFactory("", new NativeViewFactory()); } } \`\`\` For plugin registration, modify the plugin's main file (for example, \`PlatformViewPlugin.java\`): \`\`\`java package dev.flutter.plugin.example; import androidx.annotation.NonNull; import io.flutter.embedding.engine.plugins.FlutterPlugin; public class PlatformViewPlugin implements FlutterPlugin { @Override public void onAttachedToEngine(@NonNull FlutterPluginBinding binding) { binding .getPlatformViewRegistry() .registerViewFactory("", new NativeViewFactory()); } @Override public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) {} } \`\`\` For more information, visit the API docs for: \* \[\`FlutterPlugin\`]\[] \* \[\`PlatformViewRegistry\`]\[] \* \[\`PlatformViewFactory\`]\[] \* \[\`PlatformView\`]\[] \[\`FlutterPlugin\`]: https\://api.flutter.dev/javadoc/io/flutter/embedding/engine/plugins/FlutterPlugin.html \[\`PlatformView\`]: https\://api.flutter.dev/javadoc/io/flutter/plugin/platform/PlatformView\.html \[\`PlatformViewFactory\`]: https\://api.flutter.dev/javadoc/io/flutter/plugin/platform/PlatformViewFactory.html \[\`PlatformViewRegistry\`]: https\://api.flutter.dev/javadoc/io/flutter/plugin/platform/PlatformViewRegistry.html ### Manual view invalidation Certain Android Views don't invalidate themselves when their content changes. Some examples include \`SurfaceView\` and \`SurfaceTexture\`. When your Platform View includes these views, you must manually invalidate it after it has been drawn (or, more specifically, after the swap chain is flipped). Invalidate the view by calling \`invalidate\` on it or on one of its parents. \[\`AndroidViewSurface\`]: https\://api.flutter.dev/flutter/widgets/AndroidViewSurface-class.html ### Issues Check out the \[existing Platform View issues]\[] on GitHub. \[existing Platform View issues]: https\://github.com/flutter/flutter/issues?q=is%3Aopen+is%3Aissue+label%3A%22a%3A+platform-views ## Performance Platform views in Flutter come with performance trade-offs. In a typical Flutter app, Flutter rasterizes frames on a dedicated raster thread, while platform code, such as plugins and Android views, runs on the platform thread, which is the Android main thread. This separation keeps Flutter rendering fast and fluid, as the platform thread is not blocked by rasterization work. However, when a platform view is rendered on Android using hybrid composition, Flutter merges the raster thread into the platform thread to ensure correct synchronization between the native Android views and the Flutter canvas. Because of this thread merging, rasterizing complex Flutter widgets alongside a platform view competes with other work on the platform thread, such as OS messages and plugin interactions, potentially causing lower application FPS and frame drops. Also, prior to Android 10, hybrid composition copied each Flutter frame out of the graphic memory into main memory, and then copied it back to a GPU texture. As this copy happens per frame, the performance of the entire Flutter UI might be impacted. In Android 10 or above, the graphics memory is copied only once. Hybrid Composition++ (HCPP) minimizes this overhead by using native transaction synchronization on supported devices (Android API 34+ with Vulkan), allowing superior performance without the heavy costs of original hybrid composition. Virtual display, on the other hand, makes each pixel of the native view flow through additional intermediate graphic buffers, which cost graphic memory and drawing performance. This can cause jank during high-frequency updates like fast scrolling. For complex cases, there are some techniques that can be used to mitigate these issues. For example, you can use a placeholder texture while an animation is happening in Dart. In other words, if an animation is slow while a platform view is rendered, then consider taking a screenshot of the native view and rendering it as a texture. For more information, visit the following API pages: \* \[\`TextureLayer\`]\[] \* \[\`TextureRegistry\`]\[] \* \[\`FlutterTextureRegistry\`]\[] \* \[\`FlutterImageView\`]\[] \[\`FlutterImageView\`]: https\://api.flutter.dev/javadoc/io/flutter/embedding/android/FlutterImageView\.html \[\`FlutterTextureRegistry\`]: https\://api.flutter.dev/ios-embedder/protocol\_flutter\_texture\_registry-p.html \[\`TextureLayer\`]: https\://api.flutter.dev/flutter/rendering/TextureLayer-class.html \[\`TextureRegistry\`]: https\://api.flutter.dev/javadoc/io/flutter/view/TextureRegistry.html

----
url: https://docs.flutter.dev/cookbook/plugins
----

[Play and pause a video](/cookbook/plugins/play-video)

[How to use the video\_player plugin.](/cookbook/plugins/play-video)

[Add ads to your mobile Flutter app or game](/cookbook/plugins/google-mobile-ads)

[How to use the google\_mobile\_ads package to show ads in Flutter.](/cookbook/plugins/google-mobile-ads)

[Take a picture using the camera](/cookbook/plugins/picture-using-camera)

[How to use a camera plugin on mobile.](/cookbook/plugins/picture-using-camera)

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/plugins/index.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/cookbook/plugins\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/plugins/index.md "Report an issue with this page").

----
url: https://docs.flutter.dev/cookbook/effects
----

[Create a staggered menu animation](/cookbook/effects/staggered-menu-animation)

[How to implement a staggered menu animation.](/cookbook/effects/staggered-menu-animation)

[Create a scrolling parallax effect](/cookbook/effects/parallax-scrolling)

[How to implement a scrolling parallax effect.](/cookbook/effects/parallax-scrolling)

[Create an expandable FAB](/cookbook/effects/expandable-fab)

[How to implement a FAB that expands to multiple buttons when tapped.](/cookbook/effects/expandable-fab)

[Create a download button](/cookbook/effects/download-button)

[How to implement a download button.](/cookbook/effects/download-button)

[Create a nested navigation flow](/cookbook/effects/nested-nav)

[How to implement a flow with nested navigation.](/cookbook/effects/nested-nav)

[Drag a UI element](/cookbook/effects/drag-a-widget)

[How to implement a draggable UI element.](/cookbook/effects/drag-a-widget)

[Create a shimmer loading effect](/cookbook/effects/shimmer-loading)

[How to implement a shimmer loading effect.](/cookbook/effects/shimmer-loading)

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/effects/index.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/cookbook/effects\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/effects/index.md "Report an issue with this page").

----
url: https://docs.flutter.dev/perf/web-performance
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Debug performance for web apps](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

The Flutter framework emits timeline events as it works to build frames, draw scenes, and track other activity such as garbage collections. These events are exposed in the [Chrome DevTools performance panel](https://developer.chrome.com/docs/devtools/performance) for debugging.

You can also emit your own timeline events using the `dart:developer` [Timeline](https://api.flutter.dev/flutter/dart-developer/Timeline-class.html) and [TimelineTask](https://api.flutter.dev/flutter/dart-developer/TimelineTask-class.html) APIs for further performance analysis.

## Optional flags to enhance tracing

[#](#optional-flags-to-enhance-tracing)

To configure which timeline events are tracked, set any of the following top-level properties to `true` in your app's `main` method.

* [debugProfileBuildsEnabled](https://api.flutter.dev/flutter/widgets/debugProfileBuildsEnabled.html): Adds `Timeline` events for every `Widget` built.
* [debugProfileBuildsEnabledUserWidgets](https://api.flutter.dev/flutter/widgets/debugProfileBuildsEnabledUserWidgets.html): Adds `Timeline` events for every user-created `Widget` built.
* [debugProfileLayoutsEnabled](https://api.flutter.dev/flutter/rendering/debugProfileLayoutsEnabled.html): Adds `Timeline` events for every `RenderObject` layout.
* [debugProfilePaintsEnabled](https://api.flutter.dev/flutter/rendering/debugProfilePaintsEnabled.html): Adds `Timeline` events for every `RenderObject` painted.

## Instructions

[#](#instructions)

1. *\[Optional]* Set any desired tracing flags to true from your app's main method.
2. Run your Flutter web app in [profile mode](/testing/build-modes#profile).
3. Open up the [Chrome DevTools Performance panel](https://developer.chrome.com/docs/devtools/performance) for your application, and [start recording](https://developer.chrome.com/docs/devtools/performance/#record) to capture timeline events.

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-07-29. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/perf/web-performance.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/perf/web-performance\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/perf/web-performance.md "Report an issue with this page").

----
url: https://docs.flutter.dev/add-to-app/android/project-setup
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Integrate a Flutter module into your Android project](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

Flutter can be embedded into your existing Android application piecemeal, as a source code Gradle subproject or as AARs.

The integration flow can be done using the Android Studio IDE with the [Flutter plugin](https://plugins.jetbrains.com/plugin/9212-flutter) or manually.

## Integrate your Flutter module

[#](#integrate-your-flutter-module)

* [With Android Studio](#67-tab-panel)
* [Without Android Studio](#68-tab-panel)

### Integrate with Android Studio

[#](#integrate-with-android-studio)

The Android Studio IDE can help integrate your Flutter module. Using Android Studio, you can edit both your Android and Flutter code in the same IDE.

You can also use IntelliJ Flutter plugin functionality like Dart code completion, hot reload, and widget inspector.

To build your app, the Android Studio plugin configures your Android project to add your Flutter module as a dependency.

1. Open your Android project in Android Studio.

2. Go to **File** > **New** > **New Project...**. The **New Project** dialog displays.

3. Click **Flutter**.

4. If asked to provide your **Flutter SDK path**, do so and click **Next**.

5. Complete the configuration of your Flutter module.

   * If you have an existing project:

     1. To choose an existing project, click **...** to the right of the **Project location** box.
     2. Navigate to your Flutter project directory.
     3. Click **Open**.

   * If you need to create a new Flutter project:

     1. Complete the configuration dialog.
     2. In the **Project type** menu, select **Module**.

6. Click **Finish**.

### Integrate without Android Studio

[#](#integrate-without-android-studio)

To integrate a Flutter module with an existing Android app manually, without using Flutter's Android Studio plugin, follow these steps:

#### Create a Flutter module

[#](#create-a-flutter-module)

Assuming that you have an existing Android app at `some/path/MyApp`, and that you want your Flutter project as a sibling, do the following:

```
cd some/path/
flutter create -t module --org com.example flutter_module
```

This creates a `some/path/flutter_module/` Flutter module project with some Dart code to get you started and a `.android/` hidden subfolder. The `.android` folder contains an Android project that can both help you run a barebones standalone version of your Flutter module with `flutter run` and it's also a wrapper that helps bootstrap the Flutter module an embeddable Android library.

#### Java version requirement

[#](#java-version-requirement)

Flutter requires your project to declare compatibility with Java 17 or later.

Before attempting to connect your Flutter module project to your host Android app, ensure that your host Android app declares the following source compatibility within your app's `build.gradle` file, under the `android { }` block.

MyApp/app/build.gradle

groovy

```
android {
    // ...
    compileOptions {
        sourceCompatibility = JavaVersion.VERSION_17 // The minimum value
        targetCompatibility = JavaVersion.VERSION_17 // The minimum value
    }
    // ...
}
```

#### Centralize repository settings

[#](#centralize-repository-settings)

Starting with Gradle 7, Android recommends using centralized repository declarations in `settings.gradle` instead of project or module level declarations in `build.gradle` files.

Before attempting to connect your Flutter module project to your host Android app, make the following changes to your host app:

1. Remove the `repositories` block in all of your app's `build.gradle` files.

   groovy

   ```
   // Remove the following block, starting on the next line
       repositories {
           google()
           mavenCentral()
       }
   // ...to the previous line
   ```

2. Add the `dependencyResolutionManagement` displayed in this step to the `settings.gradle` file.

* [Kotlin](#69-tab-panel)
* [Groovy](#70-tab-panel)

settings.gradle.kts

kotlin

```
dependencyResolutionManagement {
    repositoriesMode.set(RepositoriesMode.PREFER_SETTINGS)
    val storageUrl: String = System.getenv("FLUTTER_STORAGE_BASE_URL") ?: "https://storage.googleapis.com"
    repositories {
        google()
        mavenCentral()
        maven("$storageUrl/download.flutter.io")
    }
}
```

settings.gradle

groovy

```
dependencyResolutionManagement {
    repositoriesMode = RepositoriesMode.PREFER_SETTINGS
    String storageUrl = System.env.FLUTTER_STORAGE_BASE_URL ?: "https://storage.googleapis.com"
    repositories {
        google()
        mavenCentral()
        maven {
            url = uri("$storageUrl/download.flutter.io")
        }
    }
}
```

## Add the Flutter module as a dependency

[#](#add-the-flutter-module-as-a-dependency)

Add the Flutter module as a dependency of your existing app in Gradle. You can achieve this in two ways.

1. **Android archive** The AAR mechanism creates generic Android AARs as intermediaries that packages your Flutter module. This is good when your downstream app builders don't want to have the Flutter SDK installed. But, it adds one more build step if you build frequently.

2. **Module source code** The source code subproject mechanism is a convenient one-click build process, but requires the Flutter SDK. This is the mechanism used by the Android Studio IDE plugin.

* [Android Archive](#71-tab-panel)
* [Module source code](#74-tab-panel)

### Depend on the Android Archive (AAR)

[#](#depend-on-the-android-archive-aar)

This option packages your Flutter library as a generic local Maven repository composed of AARs and POMs artifacts. This option allows your team to build the host app without installing the Flutter SDK. You can then distribute the artifacts from a local or remote repository.

Let's assume you built a Flutter module at `some/path/flutter_module`, and then run:

```
cd some/path/flutter_module
flutter build aar
```

Then, follow the on-screen instructions to integrate.

More specifically, this command creates (by default all debug/profile/release modes) a [local repository](https://docs.gradle.org/current/userguide/declaring_repositories.html#sub:maven_local), with the following files:

* build/host/outputs/repo

  * com

    * example

      * flutter\_module

        * flutter\_release

          * 1.0

            * flutter\_release-1.0.aar
            * flutter\_release-1.0.aar.md5
            * flutter\_release-1.0.aar.sha1
            * flutter\_release-1.0.pom
            * flutter\_release-1.0.pom.md5
            * flutter\_release-1.0.pom.sha1

        * maven-metadata.xml

        * maven-metadata.xml.md5

        * maven-metadata.xml.sha1

    * flutter\_profile

      * …

    * flutter\_debug

      * …

To depend on the AAR, the host app must be able to find these files.

To do that, edit `settings.gradle` in your host app so that it includes the local repository and the dependency:

* [Kotlin](#72-tab-panel)
* [Groovy](#73-tab-panel)

settings.gradle.kts

kotlin

```
dependencyResolutionManagement {
    repositoriesMode.set(RepositoriesMode.PREFER_SETTINGS)
    repositories {
        google()
        mavenCentral()
        maven("https://storage.googleapis.com/download.flutter.io")
        maven(url = "<some/path/flutter_module>/build/host/outputs/repo")
    }
}
```

settings.gradle

groovy

```
dependencyResolutionManagement {
    repositoriesMode = RepositoriesMode.PREFER_SETTINGS
    repositories {
        google()
        mavenCentral()
​
        // Add the new repositories starting on the next line...
        maven {
            url = uri("<some/path/flutter_module>/build/host/outputs/repo")
            // This is relative to the location of the build.gradle file
            // if using a relative path.
        }
​
        maven {
            url = uri("https://storage.googleapis.com/download.flutter.io")
        }
        // ...to before this line
    }
}
```



### Kotlin DSL based Android Project

[#](#kotlin-dsl-based-android-project)

After an `aar` build of a Kotlin DSL-based Android project, follow these steps to add the flutter\_module.

Include the flutter module as a dependency in the host app's `app/build.gradle` file.

MyApp/app/build.gradle.kts

kotlin

```
android {
    buildTypes {
        release {
          ...
        }
        debug {
          ...
        }
        create("profile") {
            initWith(getByName("debug"))
        }
}
​
dependencies {
  // ...
  debugImplementation("com.example.flutter_module:flutter_debug:1.0")
  releaseImplementation("com.example.flutter_module:flutter_release:1.0")
  add("profileImplementation", "com.example.flutter_module:flutter_profile:1.0")
}
```

Add the custom `profileImplementation` dependency configuration to the end of the same app-level build.gradle file.

MyApp/app/build.gradle.kts

kotlin

```
configurations {
    getByName("profileImplementation") {
    }
}
```

### Depend on the module's source code

[#](#depend-on-the-modules-source-code)

This option enables a one-step build for both your Android project and Flutter project. This option is convenient when you work on both parts simultaneously and rapidly iterate, but your team must install the Flutter SDK to build the host app.

#### Updating `settings.gradle`

[#](#updating-settings-gradle)

Include the Flutter module as a subproject in the host app's `settings.gradle`. This example assumes `flutter_module` and `MyApp` exist in the same directory

If you are using Kotlin, apply the following changes:

MyApp/settings.gradle.kts

kotlin

```
// Include the host app project. Assumed existing content.
include(":app")
// Replace "flutter_module" with whatever package_name you supplied when you ran:
// `$ flutter create -t module [package_name]
val filePath = settingsDir.parentFile.toString() + "/flutter_module/.android/include_flutter.groovy"
apply(from = File(filePath))
```

If you are using Groovy, apply the following changes:

MyApp/settings.gradle

groovy

```
// Include the host app project.
include(":app")                                   // assumed existing content
setBinding(new Binding([gradle: this]))           // new
def filePath = settingsDir.parentFile.toString() + "/flutter_module/.android/include_flutter.groovy" // new
apply from: filePath                              // new
```

The binding and script evaluation allows the Flutter module to `include` itself (as `:flutter`) and any Flutter plugins used by the module (such as `:package_info` and `:video_player`) in the evaluation context of your `settings.gradle`.

#### Updating `app/build.gradle`

[#](#updating-appbuild-gradle)

Introduce an `implementation` dependency on the Flutter module from your app:

MyApp/app/build.gradle

groovy

```
dependencies {
    implementation(project(":flutter"))
}
```

Your app now includes the Flutter module as a dependency.

Continue to the [Adding a Flutter screen to an Android app](/add-to-app/android/add-flutter-screen) guide.

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/add-to-app/android/project-setup.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/add-to-app/android/project-setup\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/add-to-app/android/project-setup.md "Report an issue with this page").

----
url: https://docs.flutter.dev/cookbook/maintenance/error-reporting
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Report errors to a service](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

While one always tries to create apps that are free of bugs, they're sure to crop up from time to time. Since buggy apps lead to unhappy users and customers, it's important to understand how often your users experience bugs and where those bugs occur. That way, you can prioritize the bugs with the highest impact and work to fix them.

How can you determine how often your users experiences bugs? Whenever an error occurs, create a report containing the error that occurred and the associated stacktrace. You can then send the report to an error tracking service, such as [Bugsnag](https://www.bugsnag.com/platforms/flutter), [Datadog](https://docs.datadoghq.com/real_user_monitoring/flutter/), [Firebase Crashlytics](https://firebase.google.com/docs/crashlytics), [Rollbar](https://rollbar.com/), or Sentry.

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, learn how to report errors to the [Sentry](https://sentry.io/welcome/) crash reporting service using the following steps:

1. Get a DSN from Sentry.
2. Import the Flutter Sentry package
3. Initialize the Sentry SDK
4. Capture errors programmatically

## 1. Get a DSN from Sentry

[#](#1-get-a-dsn-from-sentry)

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:

1. [Create an account with Sentry](https://sentry.io/signup/).
2. Log in to the account.
3. Create a new Flutter project.
4. Copy the code snippet that includes the DSN.

## 2. Import the Sentry package

[#](#2-import-the-sentry-package)

Import the [`sentry_flutter`](https://pub.dev/packages/sentry_flutter) package into the app. The sentry package makes it easier to send error reports to the Sentry error tracking service.

To add the `sentry_flutter` package as a dependency, run `flutter pub add`:

```
$ flutter pub add sentry_flutter
```

## 3. Initialize the Sentry SDK

[#](#3-initialize-the-sentry-sdk)

Initialize the SDK to capture different unhandled errors automatically:

dart

```
import 'package:flutter/widgets.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
​
Future<void> main() async {
  await SentryFlutter.init(
    (options) => options.dsn = 'https://example@sentry.io/example',
    appRunner: () => runApp(const MyApp()),
  );
}
```

Alternatively, you can pass the DSN to Flutter using the `dart-define` tag:

sh

```
--dart-define SENTRY_DSN=https://example@sentry.io/example
```

### What does that give me?

[#](#what-does-that-give-me)

This is all you need for Sentry to capture unhandled errors in Dart and native layers. This includes Swift, Objective-C, C, and C++ on iOS, and Java, Kotlin, C, and C++ on Android.

## 4. Capture errors programmatically

[#](#4-capture-errors-programmatically)

Besides the automatic error reporting that Sentry generates by importing and initializing the SDK, you can use the API to report errors to Sentry:

dart

```
await Sentry.captureException(exception, stackTrace: stackTrace);
```

For more information, see the [Sentry API](https://pub.dev/documentation/sentry_flutter/latest/sentry_flutter/sentry_flutter-library.html) docs on pub.dev.

## Learn more

[#](#learn-more)

Extensive documentation about using the Sentry SDK can be found on [Sentry's site](https://docs.sentry.io/platforms/flutter/).

## Complete example

[#](#complete-example)

To view a working example, see the [Sentry flutter example](https://github.com/getsentry/sentry-dart/tree/main/flutter/example) app.

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-07-31. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/maintenance/error-reporting.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/cookbook/maintenance/error-reporting\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/maintenance/error-reporting.md "Report an issue with this page").

----
url: https://docs.flutter.dev/tools/sdk.md
----

\# Flutter SDK overview > Flutter libraries and command-line tools. The Flutter SDK has the packages and command-line tools that you need to develop Flutter apps across platforms. To get the Flutter SDK, see \[Install]\[]. ## What's in the Flutter SDK The following is available through the Flutter SDK: \* \[Dart SDK]\[] \* Heavily optimized, mobile-first 2D rendering engine with excellent support for text \* Modern react-style framework \* Rich set of widgets implementing Material Design and iOS styles \* APIs for unit and integration tests \* Interop and plugin APIs to connect to the system and 3rd-party SDKs \* Headless test runner for running tests on Windows, Linux, and Mac \* \[Flutter DevTools]\[] for testing, debugging, and profiling your app \* \`flutter\` and \`dart\` command-line tools for creating, building, testing, and compiling your apps Note: For more information about the Flutter SDK, see its \[README file]\[]. ## \`flutter\` command-line tool The \[\`flutter\` CLI tool]\[] (\`flutter/bin/flutter\`) is how developers (or IDEs on behalf of developers) interact with Flutter. ## \`dart\` command-line tool The \[\`dart\` CLI tool]\[] is available with the Flutter SDK at \`flutter/bin/dart\`. \[Flutter DevTools]: /tools/devtools \[Dart SDK]: https\://dart.dev/tools/sdk \[\`dart\` CLI tool]: https\://dart.dev/tools/dart-tool \[\`flutter\` CLI tool]: /reference/flutter-cli \[Install]: /install \[README file]: https\://github.com/flutter/flutter/blob/main/README.md ## SDK support for Flutter developer tools The IDE tooling for Flutter (Android Studio and Intellij plugins, VS Code extensions) supports Flutter SDK versions going back two years. This means that while the tools might still function with SDKs older than two years, they will no longer provide fixes for issues specific to these older versions.

----
url: https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.25.0.md
----

\# DevTools 2.25.0 release notes > Release notes for Dart and Flutter DevTools version 2.25.0. The 2.25.0 release of the Dart and Flutter DevTools includes the following changes among other general improvements. To learn more about DevTools, check out the \[DevTools overview]\(https\://docs.flutter.dev/tools/devtools). ## General updates - Improve DevTools tab bar navigation when the list of tabs is long - \[#5875]\(https\://github.com/flutter/devtools/pull/5875) - Clear registered service methods between app connections - \[#5960]\(https\://github.com/flutter/devtools/pull/5960) ## Memory updates - Add legend for class types - \[#5937]\(https\://github.com/flutter/devtools/pull/5937) - Enable sampling for Memory > Profile - \[#5947]\(https\://github.com/flutter/devtools/pull/5947) !\[memory sampling]\(/assets/images/docs/tools/devtools/release-notes/images-2.25.0/memory.png "memory\_sampling") ## Full commit history To find a complete list of changes since the previous release, check out \[the diff on GitHub]\(https\://github.com/flutter/devtools/compare/v2.24.0...v2.25.0).

----
url: https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.52.0.md
----

\# DevTools 2.52.0 release notes The 2.52.0 release of the Dart and Flutter DevTools includes the following changes among other general improvements. To learn more about DevTools, check out the \[DevTools overview]\(/tools/devtools/overview). ## General updates - Added a horizontal scrollbar to data tables to help with navigation. - \[#9482]\(https\://github.com/flutter/devtools/pull/9482) - Made it possible to resize data table columns by dragging the header separators. - \[#9485]\(https\://github.com/flutter/devtools/pull/9485) ## Network profiler updates - Fixed layout of the "error count" badge in the tab name. - \[#9470]\(https\://github.com/flutter/devtools/pull/9470) - Fixed display of "Response Headers" and "Request Headers" when there are no headers. - \[#9492]\(https\://github.com/flutter/devtools/pull/9492) - Added a banner to clearly indicate when DevTools is not logging network requests. - \[#9495]\(https\://github.com/flutter/devtools/pull/9495) ## VS Code updates - Fixed issue preventing shortcuts like \`Cmd\`+\`C\` and \`Cmd\`+\`V\` from working when DevTools was embedded inside VS Code on macOS. - \[#9472]\(https\://github.com/flutter/devtools/pull/9472) ## Full commit history To find a complete list of changes in this release, check out the \[DevTools git log]\(https\://github.com/flutter/devtools/tree/v2.52.0).

----
url: https://docs.flutter.dev/perf
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Performance](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

[Watch on YouTube in a new tab: "Flutter performance tips | Flutter in Focus"](https://www.youtube.com/watch/PKGguGUwSYE)

What is performance? Why is performance important? How do I improve performance?

Our goal is to answer those three questions (mainly the third one), and anything related to them. This document should serve as the single entry point or the root node of a tree of resources that addresses any questions that you have about performance.

The answers to the first two questions are mostly philosophical, and not as helpful to many developers who visit this page with specific performance issues that need to be solved. Therefore, the answers to those questions are in the [appendix](/perf/appendix).

To improve performance, you first need metrics: some measurable numbers to verify the problems and improvements. In the [metrics](/perf/metrics) page, you'll see which metrics are currently used, and which tools and APIs are available to get the metrics.

There is a list of [Frequently asked questions](/perf/faq), so you can find out if the questions you have or the problems you're having were already answered or encountered, and whether there are existing solutions. (Alternatively, you can check the Flutter GitHub issue database using the [performance](https://github.com/flutter/flutter/issues?q=+label%3A%22severe%3A+performance%22) label.)

Finally, the performance issues are divided into four categories. They correspond to the four labels that are used in the Flutter GitHub issue database: "[perf: speed](https://github.com/flutter/flutter/issues?q=is%3Aopen+label%3A%22perf%3A+speed%22+sort%3Aupdated-asc+)", "[perf: memory](https://github.com/flutter/flutter/issues?q=is%3Aopen+label%3A%22perf%3A+memory%22+sort%3Aupdated-asc+)", "[perf: app size](https://github.com/flutter/flutter/issues?q=is%3Aopen+label%3A%22perf%3A+app+size%22+sort%3Aupdated-asc+)", "[perf: energy](https://github.com/flutter/flutter/issues?q=is%3Aopen+label%3A%22perf%3A+energy%22+sort%3Aupdated-asc+)".

The rest of the content is organized using those four categories.

## Speed

[#](#speed)

Are your animations janky (not smooth)? Learn how to evaluate and fix rendering issues.

[Improving rendering performance](/perf/rendering-performance)

## App size

[#](#app-size)

How to measure your app's size. The smaller the size, the quicker it is to download.

[Measuring your app's size](/perf/app-size)

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/perf/index.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/perf\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/perf/index.md "Report an issue with this page").

----
url: https://docs.flutter.dev/learn/pathway/tutorial/devtools
----

Learn to use the widget inspector and property editor to debug layout issues and experiment with properties in real-time.

[Watch on YouTube in a new tab: "Intro to Flutter and Dart DevTools"](https://www.youtube.com/watch/CIfLE0CShbg)

account\_tree Explore your app's widget tree with the widget inspector

bug\_report Learn to debug layout issues like unbounded constraints

tune Experiment with properties in real-time

***

## Steps

1### Introductionkeyboard\_arrow\_up

As your Flutter app grows in complexity, it becomes more important to understand how each of the widget properties affects the UI. The [Dart and Flutter DevTools](/tools/devtools) provide you with two particularly useful features: the **widget inspector** and the **property editor**.

First, launch DevTools by running the following commands while your app is running in debug mode. Run this command in a **separate terminal window** from where your app is running:

```
$ dart devtools
```

Running this command starts the DevTools server and opens the interface in a browser.

To connect DevTools to your running app:

1. Find the DevTools URL printed in the terminal where your app is running (for example: `Serving DevTools at http://127.0.0.1:9101`).
2. Copy this URL.
3. Paste it into the connect bar in the DevTools browser page.

2### The widget inspectorkeyboard\_arrow\_up

The widget inspector allows you to visualize and explore your widget tree. It helps you understand the layout of your UI and identifies which widgets are responsible for different parts of the screen. Running against the app you've built so far, the inspector looks like this:

Consider the `GamePage` widget you created in this section:

dart

```
class GamePage extends StatelessWidget {
  GamePage({super.key});
​
  final Game _game = Game();
​
  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(8.0),
      child: Column(
        spacing: 5.0,
        children: [
          for (var guess in _game.guesses)
            Row(
              spacing: 5.0,
              children: [
                for (var letter in guess) Tile(letter.char, letter.type)
              ]
            ),
        ],
      ),
    );
  }
}
```

And how it's used in `MainApp`:

dart

```
class MainApp extends StatelessWidget {
  const MainApp({super.key});
​
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: Center(child: GamePage()),
      ),
    );
  }
}
```

In the widget inspector, you should see a tree of exactly the same widgets that are in your code: `MaterialApp` as the root, with `Scaffold` as its `home`, an `AppBar` as its `appBar`, and so on down the entire tree to the `Row` widgets with `Tile` children. You can select any widget in the tree to see its properties and even jump to its source code in your IDE.

3### Debugging layout issueskeyboard\_arrow\_up

The widget inspector is perhaps most useful for debugging layout issues.

In certain situations, a widget's [constraints](/ui/layout/constraints) are unbounded, or infinite. This means that either the maximum width or the maximum height is set to [`double.infinity`](https://api.flutter.dev/flutter/dart-core/double/infinity-constant.html). A widget that tries to be as big as possible won't function usefully when given an unbounded constraint and, in debug mode, throws an exception.

The most common case where a render box ends up with an unbounded constraint is within a flex box widget ([`Row`](https://api.flutter.dev/flutter/widgets/Row-class.html) or [`Column`](https://api.flutter.dev/flutter/widgets/Column-class.html)), and within a scrollable region, such as a [`ListView`](https://api.flutter.dev/flutter/widgets/ListView-class.html) or [`ScrollView`](https://api.flutter.dev/flutter/widgets/ScrollView-class.html) subclasses.

`ListView`, for example, tries to expand to fit the space available in its cross-direction. Such as if it's a vertically scrolling block that tries to be as wide as its parent. If you nest a vertically scrolling `ListView` inside a horizontally scrolling `ListView`, the inner list tries to be as wide as possible, which is infinitely wide, since the outer one is scrollable in that direction.

Perhaps the most common error you'll run into while building a Flutter application is due to incorrectly using layout widgets. This error is referred to as the "unbounded constraints" error.

Watch the following video to get an understanding of how to spot and resolve this issue.

[Watch on YouTube in a new tab: "Decoding Flutter: Unbounded height and width"](https://www.youtube.com/watch/jckqXR5CrPI)

4### The property editorkeyboard\_arrow\_up

When you select a widget in the widget inspector, the property editor displays all the properties of that selected widget. This is a powerful tool for understanding why a widget looks the way it does and for experimenting with property value changes in real-time.

Look at the `Tile` widget's `build` method from earlier:

dart

```
class Tile extends StatelessWidget {
  const Tile(this.letter, this.hitType, {super.key});
​
  final String letter;
  final HitType hitType;
​
  @override
  Widget build(BuildContext context) {
    return Container(
      width: 60,
      height: 60,
      decoration: BoxDecoration(
        border: Border.all(color: Colors.grey.shade300),
        color: switch (hitType) {
          HitType.hit => Colors.green,
          HitType.partial => Colors.yellow,
          HitType.miss => Colors.grey,
          _ => Colors.white,
        },
      ),
    );
  }
}
```

If you select the `Container` widget inside the `Tile` in the widget inspector, the property editor shows its `width` (60), `height` (60), and the `decoration` property. You could then expand the `BoxDecoration` to see the `border` and `color` properties.

For many properties, you can even modify their values directly within the property editor. For example, to quickly test how a different `width` or `height` would look for your `Container` in the `Tile` widget, change the numerical value in the property editor. The tool writes this update directly back to your `.dart` source file on disk, allowing you to instantly view the visual update on your running app upon saving or triggering a hot reload. This allows for rapid iteration on UI design.

5### Reviewkeyboard\_arrow\_up

account\_tree Explored your app's widget tree with the widget inspector keyboard\_arrow\_up

The widget inspector lets you visualize your entire widget tree, select any widget to view its properties, and jump directly to its source code. It's an essential tool for understanding your app's structure.

bug\_report Learned about common layout issues keyboard\_arrow\_up

You learned about **unbounded constraints**, one of the most common errors hit in Flutter development. This happens when widgets like `Row`, `Column`, or `ListView` receive infinite constraints. Now you can recognize and fix these issues when they occur.

tune Experimented with properties in real-time keyboard\_arrow\_up

The property editor shows all properties of a selected widget and lets you modify values directly on disk, allowing you to instantly view updates upon saving or triggering a hot reload. This enables rapid iteration when fine-tuning your UI.

6### Test yourselfkeyboard\_arrow\_up

### DevTools Quiz

1 / 2

**What is a common cause of "unbounded constraints" errors in Flutter?**

1. Using too many StatefulWidgets in the widget tree.

   Not quite.

   StatefulWidget usage doesn't cause unbounded constraints.

2. Placing a widget that tries to expand infinitely inside a scrollable or flex container without proper constraints.

   That's right!

   Widgets like ListView inside a Row, or nested scrollables, can receive infinite constraints and fail.

3. Forgetting to call setState after changing data.

   Not quite.

   Missing setState causes UI not to update, not constraint errors.

4. Using Container without specifying a color.

   Not quite.

   Color is optional and unrelated to layout constraints.

**What can you do with the Widget Inspector in Flutter DevTools?**

1. Automatically generate unit tests for your widgets.

   Not quite.

   The Widget Inspector is for visualization and debugging, not test generation.

2. Visualize your widget tree, select widgets to view their properties, and jump to source code.

   That's right!

   The Widget Inspector lets you explore your app's structure, inspect widget properties, and navigate to the corresponding source code.

3. Deploy your app directly to the app store.

   Not quite.

   Deployment is handled separately; the Widget Inspector is for debugging.

4. Edit your app's theme colors and typography.

   Not quite.

   Theme editing requires code changes; the Widget Inspector is for inspecting the current state.

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/learn/pathway/tutorial/devtools.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/learn/pathway/tutorial/devtools\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/learn/pathway/tutorial/devtools.md "Report an issue with this page").

----
url: https://docs.flutter.dev/platform-integration.md
----

\# Build for and integrate with multiple platforms > Learn how to develop for different platforms and integrate with platform-specific features in Flutter apps. Flutter enables you to build, test, and deploy beautiful, natively compiled, multi-platform applications from a single codebase. ## Overview Flutter and its core packages often automatically support and integrate with Flutter's officially \[supported platforms]\[]. Some platforms require you to \[set up additional tooling]\(#setup), but once your development environment is set up, Flutter apps are usually functional across platforms out of the box. Occasionally you need to integrate with platform-specific functionality. For example, you might want to use a native library that's only available on iOS and iPadOS. For many use cases, you can find and use one of the many \[Flutter plugins]\[] provided by the Flutter team and the amazing Flutter community. If none of them meet your needs, you can \[write platform-specific code]\[] and even \[create your own plugin]\[]. :::tip If you're exploring building your app for multiple platforms, also consider building your UI with \[adaptive and responsive design]\[] in mind. ::: \[supported platforms]: /reference/supported-platforms \[Flutter plugins]: /packages-and-plugins/using-packages \[write platform-specific code]: /platform-integration/platform-channels \[create your own plugin]: /packages-and-plugins/developing-packages \[adaptive and responsive design]: /ui/adaptive-responsive/ ## Set up platform development {:#setup} While Flutter apps can be built for a variety of \[supported platforms]\[] with little to no modifications to your code, your development environment might require additional setup when targeting a new platform. To set up development for an additional platform, select the platform from the following:

\## Integrate with each platform {:#integrate} If the situation you're trying to solve is not covered by an existing \[Flutter plugin]\[], check out the following guides to learn how to integrate with each of the supported platforms. \[Flutter plugin]: /packages-and-plugins/using-packages#searching-for-packages ### Integrate with Android {:#android} Learn how to add custom integrations with Android to your Flutter app.

\### Integrate with iOS {:#ios} Learn how to add custom integrations with iOS to your Flutter app.

\### Integrate with the web {:#web} Learn how to add custom integrations with the web platform to your Flutter app.

[Interop with JavaScript](https://dart.dev/interop/js-interop)

[Learn how to integrate with JavaScript from your Dart code.](https://dart.dev/interop/js-interop)

\### Integrate with Windows {:#windows} Learn how to add custom integrations with Windows to your Flutter app.

[Bind to native code](/platform-integration/windows/building/#integrating-with-windows)

[Learn how to bind to native C code from your app on Windows.](/platform-integration/windows/building/#integrating-with-windows)

[Distribute your app](/platform-integration/windows/building/#distributing-windows-apps)

[Learn about different options for distributing your app on Windows.](/platform-integration/windows/building/#distributing-windows-apps)

[Follow Windows UI conventions](platform-integration/windows/building#supporting-windows-ui-guidelines)

[Learn different techniques to integrate with the Windows look and feel.](platform-integration/windows/building#supporting-windows-ui-guidelines)

\### Integrate with macOS {:#macos} Learn how to add custom integrations with macOS to your Flutter app.

\### Integrate with Linux {:#linux} Learn how to add custom integrations with Linux to your Flutter app.

[Bind to native code](/platform-integration/linux/building#integrate-with-linux)

[Learn how to use and bind to native Linux libraries and code.](/platform-integration/linux/building#integrate-with-linux)

[Prepare for distribution](/platform-integration/linux/building#prepare-linux-apps-for-distribution)

[Prepare your Flutter app for distributing to Linux users.](/platform-integration/linux/building#prepare-linux-apps-for-distribution)

[Deploy to the Snap Store](/deployment/linux/)

[Learn how to deploy your Linux desktop app to the Snap Store.](/deployment/linux/)

----
url: https://docs.flutter.dev/deployment/macos.md
----

\# Build and release a macOS app > How to release a Flutter app to the macOS App Store. This guide provides a step-by-step walkthrough of releasing a Flutter app to the \[App Store]\[appstore]. ## Preliminaries Before beginning the process of releasing your app, ensure that it meets Apple's \[App Review Guidelines]\[appreview]. In order to publish your app to the App Store, you must first enroll in the \[Apple Developer Program]\[devprogram]. You can read more about the various membership options in Apple's \[Choosing a Membership]\[devprogram\_membership] guide. ## Register your app on App Store Connect Manage your app's life cycle on \[App Store Connect]\[appstoreconnect\_login] (formerly iTunes Connect). You define your app name and description, add screenshots, set pricing, and manage releases to the App Store and TestFlight. Registering your app involves two steps: registering a unique Bundle ID, and creating an application record on App Store Connect. For a detailed overview of App Store Connect, see the \[App Store Connect]\[appstoreconnect\_guide] guide. ### Register a Bundle ID Every macOS application is associated with a Bundle ID, a unique identifier registered with Apple. To register a Bundle ID for your app, follow these steps: 1. Open the \[App IDs]\[devportal\_appids] page of your developer account. 1. Click \*\*+\*\* to create a new Bundle ID. 1. Enter an app name, select \*\*Explicit App ID\*\*, and enter an ID. 1. Select the services your app uses, then click \*\*Continue\*\*. 1. On the next page, confirm the details and click \*\*Register\*\* to register your Bundle ID. ### Create an application record on App Store Connect Register your app on App Store Connect: 1. Open \[App Store Connect]\[appstoreconnect\_login] in your browser. 1. On the App Store Connect landing page, click \*\*My Apps\*\*. 1. Click \*\*+\*\* in the top-left corner of the My Apps page, then select \*\*New App\*\*. 1. Fill in your app details in the form that appears. In the Platforms section, ensure that macOS is checked. Since Flutter does not currently support tvOS, leave that checkbox unchecked. Click \*\*Create\*\*. 1. Navigate to the application details for your app and select \*\*App Information\*\* from the sidebar. 1. In the General Information section, select the Bundle ID you registered in the preceding step. For a detailed overview, see \[Add an app to your account]\[appstoreconnect\_guide\_register]. ## Review Xcode project settings This step covers reviewing the most important settings in the Xcode workspace. For detailed procedures and descriptions, see \[Prepare for app distribution]\[distributionguide\_config]. Navigate to your target's settings in Xcode: 1. In Xcode, open \`Runner.xcworkspace\` in your app's \`macos\` folder. 1. To view your app's settings, select the \*\*Runner\*\* project in the Xcode project navigator. Then, in the main view sidebar, select the \*\*Runner\*\* target. 1. Select the \*\*General\*\* tab. Verify the most important settings. In the \*\*Identity\*\* section: \`App Category\` : The app category under which your app will be listed on the Mac App Store. This cannot be none. \`Bundle Identifier\` : The App ID you registered on App Store Connect. In the \*\*Deployment info\*\* section: \`Deployment Target\` : The minimum macOS version that your app supports. To check which versions of macOS that Flutter supports deploying to, check out Flutter's \[Supported deployment platforms]\[]. In the \*\*Signing & Capabilities\*\* section: \`Automatically manage signing\` : Whether Xcode should automatically manage app signing and provisioning. This is set \`true\` by default, which should be sufficient for most apps. For more complex scenarios, see the \[Code Signing Guide]\[codesigning\_guide]. \`Team\` : Select the team associated with your registered Apple Developer account. If required, select \*\*Add Account...\*\*, then update this setting. The \*\*General\*\* tab of your project settings should resemble the following: !\[Xcode Project Settings]\(/assets/images/docs/releaseguide/macos\_xcode\_settings.png){:width="100%"} For a detailed overview of app signing, see \[Create, export, and delete signing certificates]\[appsigning]. \[Supported deployment platforms]: /reference/supported-platforms ## Configuring the app's name, bundle identifier and copyright The configuration for the product identifiers are centralized in \`macos/Runner/Configs/AppInfo.xcconfig\`. For the app's name, set \`PRODUCT\_NAME\`, for the copyright set \`PRODUCT\_COPYRIGHT\`, and finally set \`PRODUCT\_BUNDLE\_IDENTIFIER\` for the app's bundle identifier. ## Updating the app's version number The default version number of the app is \`1.0.0\`. To update it, navigate to the \`pubspec.yaml\` file and update the following line: \`version: 1.0.0+1\` The version number is three numbers separated by dots, such as \`1.0.0\` in the example above, followed by an optional build number such as \`1\` in the example above, separated by a \`+\`. Both the version and the build number can be overridden in Flutter's build by specifying \`--build-name\` and \`--build-number\`, respectively. In macOS, \`build-name\` uses \`CFBundleShortVersionString\` while \`build-number\` uses \`CFBundleVersion\`. Read more about iOS versioning at \[Core Foundation Keys]\[] on the Apple Developer's site. ## Add an app icon When a new Flutter app is created, a placeholder icon set is created. This step covers replacing these placeholder icons with your app's icons: 1. Review the \[macOS App Icon]\[appicon] guidelines. 1. In the Xcode project navigator, select \`Assets.xcassets\` in the \`Runner\` folder. Update the placeholder icons with your own app icons. 1. Verify the icon has been replaced by running your app using \`flutter run -d macos\`. ## Create a build archive with Xcode This step covers creating a build archive and uploading your build to App Store Connect using Xcode. During development, you've been building, debugging, and testing with \_debug\_ builds. When you're ready to ship your app to users on the App Store or TestFlight, you need to prepare a \_release\_ build. At this point, you might consider \[obfuscating your Dart code]\[] to make it more difficult to reverse engineer. Obfuscating your code involves adding a couple flags to your build command. In Xcode, configure the app version and build: 1. Open \`Runner.xcworkspace\` in your app's \`macos\` folder. To do this from the command line, run the following command from the base directory of your application project. \`\`\`console open macos/Runner.xcworkspace \`\`\` 1. Select \*\*Runner\*\* in the Xcode project navigator, then select the \*\*Runner\*\* target in the settings view sidebar. 1. In the Identity section, update the \*\*Version\*\* to the user-facing version number you wish to publish. 1. In the Identity section, update the \*\*Build\*\* identifier to a unique build number used to track this build on App Store Connect. Each upload requires a unique build number. Finally, create a build archive and upload it to App Store Connect: 1. Create a release Archive of your application. From the base directory of your application project, run the following. \`\`\`console flutter build macos \`\`\` 1. Open Xcode and select \*\*Product > Archive\*\* to open the archive created in the previous step. 1. Click the \*\*Validate App\*\* button. If any issues are reported, address them and produce another build. You can reuse the same build ID until you upload an archive. 1. After the archive has been successfully validated, click \*\*Distribute App\*\*. You can follow the status of your build in the Activities tab of your app's details page on \[App Store Connect]\[appstoreconnect\_login]. You should receive an email within 30 minutes notifying you that your build has been validated and is available to release to testers on TestFlight. At this point you can choose whether to release on TestFlight, or go ahead and release your app to the App Store. For more details, see \[Upload an app to App Store Connect]\[distributionguide\_upload]. ## Create a build archive with Codemagic CLI tools This step covers creating a build archive and uploading your build to App Store Connect using Flutter build commands and \[Codemagic CLI Tools]\[codemagic\_cli\_tools] executed in a terminal in the Flutter project directory.

1. Install the Codemagic CLI tools: \`\`\`bash pip3 install codemagic-cli-tools \`\`\`
2. You'll need to generate an \[App Store Connect API Key]\[appstoreconnect\_api\_key] with App Manager access to automate operations with App Store Connect. To make subsequent commands more concise, set the following environment variables from the new key: issuer id, key id, and API key file. \`\`\`bash export APP\_STORE\_CONNECT\_ISSUER\_ID=aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee export APP\_STORE\_CONNECT\_KEY\_IDENTIFIER=ABC1234567 export APP\_STORE\_CONNECT\_PRIVATE\_KEY=\`cat /path/to/api/key/AuthKey\_XXXYYYZZZ.p8\` \`\`\`
3. You need to export or create a Mac App Distribution and a Mac Installer Distribution certificate to perform code signing and package a build archive. If you have existing \[certificates]\[devportal\_certificates], you can export the private keys by executing the following command for each certificate: \`\`\`bash openssl pkcs12 -in .p12 -nodes -nocerts | openssl rsa -out cert\_key \`\`\` Or you can create a new private key by executing the following command: \`\`\`bash ssh-keygen -t rsa -b 2048 -m PEM -f cert\_key -q -N "" \`\`\` Later, you can have CLI tools automatically create a new Mac App Distribution and Mac Installer Distribution certificate. You can use the same private key for each new certificate.
4. Fetch the code signing files from App Store Connect: \`\`\`bash app-store-connect fetch-signing-files YOUR.APP.BUNDLE\_ID \ --platform MAC\_OS \ --type MAC\_APP\_STORE \ --certificate-key=@file:/path/to/cert\_key \ --create \`\`\` Where \`cert\_key\` is either your exported Mac App Distribution certificate private key or a new private key which automatically generates a new certificate.
5. If you do not have a Mac Installer Distribution certificate, you can create a new certificate by executing the following: \`\`\`bash app-store-connect certificates create \ --type MAC\_INSTALLER\_DISTRIBUTION \ --certificate-key=@file:/path/to/cert\_key \ --save \`\`\` Use \`cert\_key\` of the private key you created earlier.
6. Fetch the Mac Installer Distribution certificates: \`\`\`bash app-store-connect certificates list \ --type MAC\_INSTALLER\_DISTRIBUTION \ --certificate-key=@file:/path/to/cert\_key \ --save \`\`\`
7. Set up a new temporary keychain to be used for code signing: \`\`\`bash keychain initialize \`\`\` :::note Restore Login Keychain! After running \`keychain initialize\` you \*\*must\*\* run the following:\
   \`keychain use-login\` This sets your login keychain as the default to avoid potential authentication issues with apps on your machine. :::
8. Now add the fetched certificates to your keychain: \`\`\`bash keychain add-certificates \`\`\`
9. Update the Xcode project settings to use fetched code signing profiles: \`\`\`bash xcode-project use-profiles \`\`\`
10. Install Flutter dependencies: \`\`\`bash flutter packages pub get \`\`\`
11. Install CocoaPods dependencies: \`\`\`bash find . -name "Podfile" -execdir pod install \\; \`\`\`
12. Build the Flutter macOS project: \`\`\`bash flutter build macos --release \`\`\`
13. Package the app: \`\`\`bash APP\_NAME=$(find $(pwd) -name "\*.app") PACKAGE\_NAME=$(basename "$APP\_NAME" .app).pkg xcrun productbuild --component "$APP\_NAME" /Applications/ unsigned.pkg INSTALLER\_CERT\_NAME=$(keychain list-certificates \ | jq '\[.\[] | select(.common\_name | contains("Mac Developer Installer")) | .common\_name]\[0]' \ | xargs) xcrun productsign --sign "$INSTALLER\_CERT\_NAME" unsigned.pkg "$PACKAGE\_NAME" rm -f unsigned.pkg \`\`\`
14. Publish the packaged app to App Store Connect: \`\`\`bash app-store-connect publish \ --path "$PACKAGE\_NAME" \`\`\`
15. As mentioned earlier, don't forget to set your login keychain as the default to avoid authentication issues with apps on your machine: \`\`\`bash keychain use-login \`\`\`

\## Release your app on TestFlight \[TestFlight]\[] allows developers to push their apps to internal and external testers. This optional step covers releasing your build on TestFlight. 1. Navigate to the TestFlight tab of your app's application details page on \[App Store Connect]\[appstoreconnect\_login]. 1. Select \*\*Internal Testing\*\* in the sidebar. 1. Select the build to publish to testers, then click \*\*Save\*\*. 1. Add the email addresses of any internal testers. You can add additional internal users in the \*\*Users and Roles\*\* page of App Store Connect, available from the dropdown menu at the top of the page. ## Distribute to registered devices See \[distribution guide]\[distributionguide\_macos] to prepare an archive for distribution to designated Mac computers. ## Release your app to the App Store When you're ready to release your app to the world, follow these steps to submit your app for review and release to the App Store: 1. Select \*\*Pricing and Availability\*\* from the sidebar of your app's application details page on \[App Store Connect]\[appstoreconnect\_login] and complete the required information. 1. Select the status from the sidebar. If this is the first release of this app, its status is \*\*1.0 Prepare for Submission\*\*. Complete all required fields. 1. Click \*\*Submit for Review\*\*. Apple notifies you when their app review process is complete. Your app is released according to the instructions you specified in the \*\*Version Release\*\* section. For more details, see \[Distribute an app through the App Store]\[distributionguide\_submit]. ## Troubleshooting The \[Distribute your app]\[distributionguide] guide provides a detailed overview of the process of releasing an app to the App Store. ## Additional resources To learn how to package and distribute your Flutter desktop app for macOS the open source way, without using a paid Apple developer account, check out the step-by-step \[macOS packaging guide]\[macos\_packaging\_guide]. \[appicon]: https\://developer.apple.com/design/human-interface-guidelines/macos/icons-and-images/app-icon/ \[appreview]: https\://developer.apple.com/app-store/review/ \[appsigning]: https\://help.apple.com/xcode/mac/current/#/dev154b28f09 \[appstore]: https\://developer.apple.com/app-store/submissions/ \[appstoreconnect]: https\://developer.apple.com/support/app-store-connect/ \[appstoreconnect\_api\_key]: https\://appstoreconnect.apple.com/access/api \[appstoreconnect\_guide]: https\://developer.apple.com/support/app-store-connect/ \[appstoreconnect\_guide\_register]: https\://help.apple.com/app-store-connect/#/dev2cd126805 \[appstoreconnect\_login]: https\://appstoreconnect.apple.com/ \[codemagic\_cli\_tools]: https\://github.com/codemagic-ci-cd/cli-tools \[codesigning\_guide]: https\://developer.apple.com/library/content/documentation/Security/Conceptual/CodeSigningGuide/Introduction/Introduction.html \[Core Foundation Keys]: https\://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html \[devportal\_appids]: https\://developer.apple.com/account/resources/identifiers/list \[devportal\_certificates]: https\://developer.apple.com/account/resources/certificates/list \[devprogram]: https\://developer.apple.com/programs/ \[devprogram\_membership]: https\://developer.apple.com/support/compare-memberships/ \[distributionguide]: https\://help.apple.com/xcode/mac/current/#/dev8b4250b57 \[distributionguide\_config]: https\://help.apple.com/xcode/mac/current/#/dev91fe7130a \[distributionguide\_macos]: https\://help.apple.com/xcode/mac/current/#/dev295cc0fae \[distributionguide\_submit]: https\://help.apple.com/xcode/mac/current/#/dev067853c94 \[distributionguide\_upload]: https\://help.apple.com/xcode/mac/current/#/dev442d7f2ca \[obfuscating your Dart code]: /deployment/obfuscate \[TestFlight]: https\://developer.apple.com/testflight/ \[macos\_packaging\_guide]: https\://medium.com/@fluttergems/packaging-and-distributing-flutter-desktop-apps-the-missing-guide-part-1-macos-b36438269285

----
url: https://docs.flutter.dev/cookbook/design/themes
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Use themes to share colors and font styles](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

To share colors and font styles throughout an app, use themes.

You can define app-wide themes. You can extend a theme to change a theme style for one component. Each theme defines the colors, type style, and other parameters applicable for the type of Material component.

Flutter applies styling in the following order:

1. Styles applied to the specific widget.
2. Themes that override the immediate parent theme.
3. Main theme for the entire app.

After you define a `Theme`, use it within your own widgets. Flutter's Material widgets use your theme to set the background colors and font styles for app bars, buttons, checkboxes, and more.

## Create an app theme

[#](#create-an-app-theme)

To share a `Theme` across your entire app, set the `theme` property to your `MaterialApp` constructor. This property takes a [`ThemeData`](https://pub.dev/documentation/material_ui/latest/material_ui/ThemeData-class.html) instance.

If you don't specify a theme in the constructor, Flutter creates a default theme for you.

dart

```
MaterialApp(
  title: appName,
  theme: ThemeData(
    // Define the default brightness and colors.
    colorScheme: ColorScheme.fromSeed(
      seedColor: Colors.purple,
      // ···
      brightness: Brightness.dark,
    ),
​
    // Define the default `TextTheme`. Use this to specify the default
    // text styling for headlines, titles, bodies of text, and more.
    textTheme: TextTheme(
      displayLarge: const TextStyle(
        fontSize: 72,
        fontWeight: FontWeight.bold,
      ),
      // ···
      titleLarge: GoogleFonts.oswald(
        fontSize: 30,
        fontStyle: FontStyle.italic,
      ),
      bodyMedium: GoogleFonts.merriweather(),
      displaySmall: GoogleFonts.pacifico(),
    ),
  ),
  home: const MyHomePage(title: appName),
);
```

Most instances of `ThemeData` set values for the following two properties. These properties affect the entire app.

1. [`colorScheme`](https://pub.dev/documentation/material_ui/latest/material_ui/ThemeData/colorScheme.html) defines the colors.
2. [`textTheme`](https://pub.dev/documentation/material_ui/latest/material_ui/ThemeData/textTheme.html) defines text styling.

To learn what colors, fonts, and other properties, you can define, check out the [`ThemeData`](https://pub.dev/documentation/material_ui/latest/material_ui/ThemeData-class.html) documentation.

## Apply a theme

[#](#apply-a-theme)

To apply your new theme, use the `Theme.of(context)` method when specifying a widget's styling properties. These can include, but are not limited to, `style` and `color`.

The `Theme.of(context)` method looks up the widget tree and retrieves the nearest `Theme` in the tree. If you have a standalone `Theme`, that's applied. If not, Flutter applies the app's theme.

In the following example, the `Container` constructor uses this technique to set its `color`.

dart

```
Container(
  padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12),
  color: Theme.of(context).colorScheme.primary,
  child: Text(
    'Text with a background color',
    // ···
    style: Theme.of(context).textTheme.bodyMedium!
        .copyWith(color: Theme.of(context).colorScheme.onPrimary),
  ),
),
```

## Override a theme

[#](#override-a-theme)

To override the overall theme in part of an app, wrap that section of the app in a `Theme` widget.

You can override a theme in two ways:

1. Create a unique `ThemeData` instance.
2. Extend the parent theme.

### Set a unique `ThemeData` instance

[#](#set-a-unique-themedata-instance)

If you want a component of your app to ignore the overall theme, create a `ThemeData` instance. Pass that instance to the `Theme` widget.

dart

```
Theme(
  // Create a unique theme with `ThemeData`.
  data: ThemeData(colorScheme: ColorScheme.fromSeed(seedColor: Colors.pink)),
  child: FloatingActionButton(onPressed: () {}, child: const Icon(Icons.add)),
);
```

### Extend the parent theme

[#](#extend-the-parent-theme)

Instead of overriding everything, consider extending the parent theme. To extend a theme, use the [`copyWith()`](https://pub.dev/documentation/material_ui/latest/material_ui/ThemeData/copyWith.html) method.

dart

```
Theme(
  // Find and extend the parent theme using `copyWith`.
  // To learn more, check out the section on `Theme.of`.
  data: Theme.of(context)
      .copyWith(colorScheme: ColorScheme.fromSeed(seedColor: Colors.pink)),
  child: const FloatingActionButton(onPressed: null, child: Icon(Icons.add)),
);
```

## Watch a video on `Theme`

[#](#watch-a-video-on-theme)

To learn more, watch this short Widget of the Week video on the `Theme` widget:

[Watch on YouTube in a new tab: "Theme | Flutter widget of the week"](https://www.youtube.com/watch/oTvQDJOBXmM)

## Try an interactive example

[#](#try-an-interactive-example)

```
import 'package:flutter/material.dart';
// Include the Google Fonts package to provide more text format options
// https://pub.dev/packages/google_fonts
import 'package:google_fonts/google_fonts.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    const appName = 'Custom Themes';

    return MaterialApp(
      title: appName,
      theme: ThemeData(
        // Define the default brightness and colors.
        colorScheme: ColorScheme.fromSeed(
          seedColor: Colors.purple,
          // TRY THIS: Change to "Brightness.light"
          //           and see that all colors change
          //           to better contrast a light background.
          brightness: Brightness.dark,
        ),

        // Define the default `TextTheme`. Use this to specify the default
        // text styling for headlines, titles, bodies of text, and more.
        textTheme: TextTheme(
          displayLarge: const TextStyle(
            fontSize: 72,
            fontWeight: FontWeight.bold,
          ),
          // TRY THIS: Change one of the GoogleFonts
          //           to "lato", "poppins", or "lora".
          //           The title uses "titleLarge"
          //           and the middle text uses "bodyMedium".
          titleLarge: GoogleFonts.oswald(
            fontSize: 30,
            fontStyle: FontStyle.italic,
          ),
          bodyMedium: GoogleFonts.merriweather(),
          displaySmall: GoogleFonts.pacifico(),
        ),
      ),
      home: const MyHomePage(title: appName),
    );
  }
}

class MyHomePage extends StatelessWidget {
  final String title;

  const MyHomePage({super.key, required this.title});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(
          title,
          style: Theme.of(context).textTheme.titleLarge!
              .copyWith(color: Theme.of(context).colorScheme.onSecondary),
        ),
        backgroundColor: Theme.of(context).colorScheme.secondary,
      ),
      body: Center(
        child: Container(
          padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12),
          color: Theme.of(context).colorScheme.primary,
          child: Text(
            'Text with a background color',
            // TRY THIS: Change the Text value
            //           or change the Theme.of(context).textTheme
            //           to "displayLarge" or "displaySmall".
            style: Theme.of(context).textTheme.bodyMedium!
                .copyWith(color: Theme.of(context).colorScheme.onPrimary),
          ),
        ),
      ),
      floatingActionButton: Theme(
        data: Theme.of(context).copyWith(
          // TRY THIS: Change the seedColor to "Colors.red" or
          //           "Colors.blue".
          colorScheme: ColorScheme.fromSeed(
            seedColor: Colors.pink,
            brightness: Brightness.dark,
          ),
        ),
        child: FloatingActionButton(
          onPressed: () {},
          child: const Icon(Icons.add),
        ),
      ),
    );
  }
}
```

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-09-29. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/design/themes.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/cookbook/design/themes\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/design/themes.md "Report an issue with this page").

----
url: https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.13.1.md
----

\# DevTools 2.13.1 release notes > Release notes for Dart and Flutter DevTools version 2.13.1. The 2.13.1 release of the Dart and Flutter DevTools includes the following changes among other general improvements. To learn more about DevTools, check out the \[DevTools overview]\(https\://docs.flutter.dev/tools/devtools). ## General updates \* This release included a lot of cleanup and reduction in technical debt. The most notable is the completion of our migration to sound null safety. \* Show release notes in IDE embedded versions of DevTools - \[#4053]\(https\://github.com/flutter/devtools/pull/4053) \* Polish to the DevTools footer - \[#3989]\(https\://github.com/flutter/devtools/pull/3989), \[#4026]\(https\://github.com/flutter/devtools/pull/4026), \[#4041]\(https\://github.com/flutter/devtools/pull/4041), \[#4076]\(https\://github.com/flutter/devtools/pull/4076) ## Performance updates \* Added a new feature to help you debug raster jank in your Flutter app. This feature allows you to take a snapshot of the current screen shown in your app, and then break down rendering time for that scene by layer. This can help you identify parts of a scene that are expensive to rasterize - \[#4046]\(https\://github.com/flutter/devtools/pull/4046) !\[raster-metrics-feature]\(/assets/images/docs/tools/devtools/release-notes/images-2.13.1/image1.png "raster metrics feature") \* Added a scope setting for "Track Widget Builds", allowing you to specify whether widget builds should be tracked in your code only or in all code - \[#4010]\(https\://github.com/flutter/devtools/pull/4010) !\[track-widget-builds-scope-setting]\(/assets/images/docs/tools/devtools/release-notes/images-2.13.1/image2.png "track widget builds scope setting") ## CPU profiler updates \* Use package uris instead of file uris in the CPU profiler "Source" column - \[#3932]\(https\://github.com/flutter/devtools/pull/3932) ## Debugger updates \* Fix scrolling bug with debugger breakpoints - \[#4074]\(https\://github.com/flutter/devtools/pull/4074) ## Flutter inspector updates \* Add support for displaying flex values larger than 5 in the Layout Explorer - \[#4055]\(https\://github.com/flutter/devtools/pull/4055) ## Full commit history To find a complete list of changes since the previous release, check out \[the diff on GitHub]\(https\://github.com/flutter/devtools/compare/v2.12.2...v2.13.1).

----
url: https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.48.0.md
----

\# DevTools 2.48.0 release notes > Release notes for Dart and Flutter DevTools version 2.48.0. The 2.48.0 release of the Dart and Flutter DevTools includes the following changes among other general improvements. To learn more about DevTools, check out the \[DevTools overview]\(/tools/devtools/overview). ## Network profiler updates \* Fixed network logging after a hot restart. - \[#9271]\(https\://github.com/flutter/devtools/pull/9271). ## Logging updates \* Started displaying events related to timers in the Logging View. - \[#9238]\(https\://github.com/flutter/devtools/pull/9238). ## Advanced developer mode updates \* Added a Queued Microtasks tab to the VM Tools screen, which allows a user to see details about the microtasks scheduled in an isolate's microtask queue. This tab currently only appears when DevTools is connected to a Flutter or Dart app started with \`--profile-microtasks\`. - \[#9239]\(https\://github.com/flutter/devtools/pull/9239). ## Full commit history To find a complete list of changes in this release, check out the \[DevTools git log]\(https\://github.com/flutter/devtools/tree/v2.48.0).

----
url: https://docs.flutter.dev/cookbook/lists/basic-list
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Use lists](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

Displaying lists of data is a fundamental pattern for mobile apps. Flutter includes the [`ListView`](https://pub.dev/documentation/material_ui/latest/material_ui/ListView-class.html) widget to make working with lists a breeze.

## Create a ListView

[#](#create-a-listview)

Using the standard `ListView` constructor is perfect for lists that contain only a few items. The built-in [`ListTile`](https://pub.dev/documentation/material_ui/latest/material_ui/ListTile-class.html) widget is a way to give items a visual structure.

dart

```
ListView(
  children: const <Widget>[
    ListTile(leading: Icon(Icons.map), title: Text('Map')),
    ListTile(leading: Icon(Icons.photo_album), title: Text('Album')),
    ListTile(leading: Icon(Icons.phone), title: Text('Phone')),
  ],
),
```

## Interactive example

[#](#interactive-example)

```
import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    const title = 'Basic List';

    return MaterialApp(
      title: title,
      home: Scaffold(
        appBar: AppBar(title: const Text(title)),
        body: ListView(
          children: const <Widget>[
            ListTile(leading: Icon(Icons.map), title: Text('Map')),
            ListTile(leading: Icon(Icons.photo_album), title: Text('Album')),
            ListTile(leading: Icon(Icons.phone), title: Text('Phone')),
          ],
        ),
      ),
    );
  }
}
```

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-09-29. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/lists/basic-list.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/cookbook/lists/basic-list\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/lists/basic-list.md "Report an issue with this page").

----
url: https://docs.flutter.dev/resources/support
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Flutter support](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

Welcome!

There are many different ways to get support when developing with Flutter. As Flutter is an open source project, your options range from filing issues when you run into bugs in the software, to connecting with your local community, to browsing and identifying a list of commercial agencies and consultants who offer support services.

## I want to report an issue with Flutter

[#](#i-want-to-report-an-issue-with-flutter)

Visit how to [create a useful bug report](/resources/bug-reports). Note that filing bugs is *not* an efficient way to get tech support.

## I want to ask questions about how to use Flutter

[#](#i-want-to-ask-questions-about-how-to-use-flutter)

Your best bet is to consult [Flutter Forum](https://forum.itsallwidgets.com/), [Stack Overflow](https://stackoverflow.com/questions/tagged/flutter), or [Discord](https://discord.com/invite/rflutterdev). While the community is active in these channels, there is no official SLA on responding to new posts or chats in these channels.

## I want professional services support with Flutter

[#](#i-want-professional-services-support-with-flutter)

Check out the [Flutter consultants](https://flutter.dev/consultants) directory.

## I want support from a Flutter expert

[#](#i-want-support-from-a-flutter-expert)

Google has a cadre of [Google Developer Experts](https://developers.google.com/community/experts/directory?specialization=dart%2Cflutter) (GDEs), non-Google employees with expertise in various areas of Google technologies, such as Flutter and Dart. GDEs are *not* free consultants, but they often give lectures, host meetups, run workshops, or make videos. As part of the Dart and Flutter world-wide community, they are a great resource.

## I want to get paid support from Google or the Flutter project

[#](#i-want-to-get-paid-support-from-google-or-the-flutter-project)

As an open source project, we're not set up to offer paid support. We recommend checking out the [consultancy program](https://flutter.dev/consultants).

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/resources/support.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/resources/support\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/resources/support.md "Report an issue with this page").

----
url: https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.19.0
----

The 2.19.0 release of the Dart and Flutter DevTools includes the following changes among other general improvements. To learn more about DevTools, check out the [DevTools overview](https://docs.flutter.dev/tools/devtools).

## Performance updates

[#](#performance-updates)

* Added a button to toggle the visibility of the Flutter Frames chart - [#4577](https://github.com/flutter/devtools/pull/4577)

* Polish the debug mode warning to better describe which data is accurate in debug mode and which data may be misleading - [#3537](https://github.com/flutter/devtools/pull/3537)

* Reorder performance tool tabs and only show the CPU profiler for the "Timeline Events" tab - [#4629](https://github.com/flutter/devtools/pull/4629)

## Memory updates

[#](#memory-updates)

* Improvements to the memory Profile tab - [#4583](https://github.com/flutter/devtools/pull/4583)

## Debugger updates

[#](#debugger-updates)

* Fix an issue with hover cards where they were appearing but never disappearing - [#4627](https://github.com/flutter/devtools/pull/4627)
* Fix a bug with the file search autocomplete dialog - [#4409](https://github.com/flutter/devtools/pull/4409)

## Network profiler updates

[#](#network-profiler-updates)

* Added a "Copy" button in the Network Request view (thanks to @netos23) - [#4509](https://github.com/flutter/devtools/pull/4509)

## Full commit history

[#](#full-commit-history)

To find a complete list of changes since the previous release, check out [the diff on GitHub](https://github.com/flutter/devtools/compare/v2.18.0...v2.19.0).

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/release-notes/release-notes-2.19.0.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.19.0\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/release-notes/release-notes-2.19.0.md "Report an issue with this page").

----
url: https://docs.flutter.dev/llms.txt
----

\# Flutter documentation > Flutter is Google's open-source UI software development kit used for crafting beautiful, natively compiled applications for mobile, web, and desktop from a single codebase. This file provides a curated list of resources to help Large Language Models understand the Flutter framework. The primary language is Dart. The core principle of Flutter is "everything is a widget." ## Getting started - \[Flutter documentation website]\(https\://docs.flutter.dev/): The homepage for the Flutter documentation website. - \[Installation guide]\(https\://docs.flutter.dev/install): The first step for any developer to set up their environment for Flutter development. - \[Introduction to declarative UI]\(https\://docs.flutter.dev/flutter-for/declarative): The conceptual difference between Flutter's declarative style and the imperative style used by many other UI frameworks. - \[Flutter Learning Pathway]\(https\://docs.flutter.dev/learn/pathway): A guide for new developers learning Flutter, covering fundamental topics like Dart, widgets, layout, state management, user input, networking, and local data caching. - \[Dart language tour]\(https\://dart.dev/language): A comprehensive introduction to the Dart programming language, which is required for Flutter development. - \[Flutter Widget of the Week playlist]\(https\://www\.youtube.com/watch?v=D0xwcz2IqAY\&list=PLjxrf2q8roU23XGwz3Km7sQZFTdB996iG): Short, engaging videos introducing individual widgets. ## Latest release - \[What's new in Flutter 3.44]\(https\://flutter.dev/blog/whats-new-in-flutter-3-44): The official announcement for the 3.44 release of Flutter. - \[Announcing Dart 3.12]\(https\://dart.dev/blog/announcing-dart-3-12): The official announcement for the 3.12 release of Dart. ## Learning resources - \[Flutter codelabs]\(https\://codelabs.developers.google.com/?product=flutter): Hands-on tutorials for a variety of Flutter features. - \[Flutter samples]\(https\://github.com/flutter/samples): A repository of sample applications demonstrating Flutter best practices. ## Core concepts - \[Building user interfaces with Flutter]\(https\://docs.flutter.dev/widgets-intro): A foundational explanation of how UIs are built with widgets. - \[Widget catalog]\(https\://docs.flutter.dev/reference/widgets): An A-Z list of all built-in widgets. - \[Layouts in Flutter]\(https\://docs.flutter.dev/ui/layout): A guide to arranging widgets to create a user interface. - \[App architecture and resources]\(https\://docs.flutter.dev/app-architecture/recommendations): Architecture best practices and recommendations for Flutter applications. - \[State management overview]\(https\://docs.flutter.dev/data-and-backend/state-mgmt/options): The different approaches to managing state in a Flutter app. - \[BuildContext class]\(https\://api.flutter.dev/flutter/widgets/BuildContext-class.html): API reference docs for BuildContext - \[Decoding Flutter video: BuildContext?!]\(https\://www\.youtube.com/watch?v=rIaaH87z1-g): Video explaining what BuildContext is and why it shows up in so many widgets and builders. - \[Deep dive into Flutter's build modes]\(https\://docs.flutter.dev/testing/build-modes): Understand the difference between debug, profile, and release modes. ## Building user interfaces - \[Basic widgets]\(https\://docs.flutter.dev/ui/widgets/basics): Learn about essential widgets like \`Text\`, \`Image\`, \`Icon\`, and \`Button\`. - \[Build a Form with validation]\(https\://docs.flutter.dev/cookbook/forms/validation): Create a form and validate user input. - \[Retrieve the value of a text field]\(https\://docs.flutter.dev/cookbook/forms/retrieve-input): Get the text a user has entered. - \[Focus a text field]\(https\://docs.flutter.dev/cookbook/forms/focus): Programmatically focus a text field. - \[Basic list]\(https\://docs.flutter.dev/cookbook/lists/basic-list): Create a simple scrolling list. - \[Grid list]\(https\://docs.flutter.dev/cookbook/lists/grid-lists): Display items in a grid. - \[Horizontal list]\(https\://docs.flutter.dev/cookbook/lists/horizontal-list): Create a list that scrolls horizontally. ## Adding interactivity & navigation - \[Handle tapping and dragging]\(https\://docs.flutter.dev/cookbook/gestures/handling-taps): Respond to user taps and drags on widgets. - \[State management for Beginners]\(https\://docs.flutter.dev/data-and-backend/state-mgmt/simple): A guide to the basics of state management using the recommended \`provider\` package. - \[Navigation and Routing]\(https\://docs.flutter.dev/ui/navigation): An introduction to navigating between different screens in your app. - \[Navigate to a new screen and back]\(https\://docs.flutter.dev/cookbook/navigation/navigation-basics): The most basic navigation pattern. - \[Send data to a new screen]\(https\://docs.flutter.dev/cookbook/navigation/passing-data): Pass data to another screen. ## Animation - \[Implicit animations]\(https\://docs.flutter.dev/ui/animations/implicit-animations): Simple, animated widgets that animate a property change. - \[Explicit animations]\(https\://docs.flutter.dev/ui/animations/tutorial): Use AnimationController for more complex animations. - \[Animate a widget using a physics simulation]\(https\://docs.flutter.dev/cookbook/animation/physics-simulation): How to move a widget from a dragged point back to the center using a spring simulation. ## Advanced UI - \[Slivers, Demystified]\(https\://blog.flutter.dev/slivers-demystified-6ff68ab0296f): An introduction to Slivers, covering SliverList, SliverGrid, SliverAppBar, and SliverPersistentHeader. - \[Creating custom scrolling effects with Slivers]\(https\://docs.flutter.dev/ui/layout/scrolling/slivers): Go beyond ListView to create custom scrollable layouts. - \[Painting and effect widgets]\(https\://docs.flutter.dev/ui/widgets/painting): Widgets that apply visual effects without changing layout, size, or position. - \[Adaptive and responsive design in Flutter]\(https\://docs.flutter.dev/ui/adaptive-responsive): Build UIs that look and feel great on any platform and any screen size. ## Data & backend - \[Choose a database for your Flutter app]\(https\://docs.flutter.dev/data-and-backend/persistence/choose-a-database): Compare database options for Flutter apps, including Cloud Firestore (recommended default for cloud NoSQL and offline sync), Firebase SQL Connect (recommended default for cloud PostgreSQL with generated type-safe Dart SDKs), on-device SQLite (\`sqflite\` and \`drift\`), and key-value storage (\`shared\_preferences\` and \`flutter\_secure\_storage\`). - \[Fetch data from the internet]\(https\://docs.flutter.dev/cookbook/networking/fetch-data): Make a basic HTTP request. - \[Networking with Flutter]\(https\://docs.flutter.dev/data-and-backend/networking): How to make HTTP requests and handle JSON data. - \[Firebase for Flutter]\(https\://docs.flutter.dev/data-and-backend/firebase): A guide to using Firebase as a backend for your Flutter app. - \[Persist data with SQLite]\(https\://docs.flutter.dev/cookbook/persistence/sqlite): Learn the basics of local database storage. - \[Store key-value data on disk]\(https\://docs.flutter.dev/cookbook/persistence/key-value): For simple data storage using \`shared\_preferences\`. ## Building AI-powered apps - \[Firebase AI Logic]\(https\://firebase.google.com/docs/ai-logic): Official documentation for Firebase AI Logic. - \[Firebase AI Logic package]\(https\://pub.dev/packages/firebase\_ai): The pub.dev package for Firebase AI Logic. - \[GenUI SDK for Flutter]\(https\://docs.flutter.dev/ai/genui): Learn how to use the GenUI SDK for Flutter to build more interactive experiences in your apps. - \[Genkit Dart]\(https\://genkit.dev): An open-source, model-agnostic framework for building AI-powered apps in Dart and Flutter with type-safe schemas, flows, and built-in observability. - \[Genkit Dart quickstart]\(https\://genkit.dev/docs/dart/overview): Get started building AI-powered Dart and Flutter apps with Genkit. ## Testing & debugging - \[Testing flutter apps]\(https\://docs.flutter.dev/testing): An overview of testing in Flutter, including unit, widget, and integration tests. - \[Measure performance with an integration test]\(https\://docs.flutter.dev/cookbook/testing/integration/profiling): Profile app performance during integration tests using \`flutter drive --profile\`. - \[An introduction to integration testing]\(https\://docs.flutter.dev/cookbook/testing/integration/introduction): Test the interaction between different parts of your app. - \[Mocking dependencies for widget tests]\(https\://docs.flutter.dev/cookbook/testing/unit/mocking): Isolate your widgets from their dependencies during testing. - \[Finding widgets]\(https\://docs.flutter.dev/cookbook/testing/widget/finders): Find specific widget instances in a test environment. - \[Tapping, dragging, and entering text]\(https\://docs.flutter.dev/cookbook/testing/widget/tap-drag): Perform common user interactions in a widget test. - \[An introduction to widget testing]\(https\://docs.flutter.dev/cookbook/testing/widget/introduction): Use the \`flutter\_test\` package to create, find, and verify widgets. - \[Debugging Flutter apps]\(https\://docs.flutter.dev/testing/debugging): Techniques and tools for finding and fixing issues in your code. - \[Flutter DevTools]\(https\://docs.flutter.dev/tools/devtools): An introduction to the suite of performance and debugging tools for Flutter and Dart. - \[Video: Dive into DevTools]\(https\://youtu.be/\_EYk-E29edo?si=5A2bD829GyXia0pU): Video covering DevTools techniques for debugging and optimizing app performance. ## Tools ### AI-assisted development - \[Get started with AI]\(https\://docs.flutter.dev/ai/get-started): How to set up and use AI agent plugins for Flutter and Dart. - \[How Flutter AI tools work]\(https\://docs.flutter.dev/ai/tools): Technical details on the skills, MCP server, and tools for AI coding assistants. - \[Supercharge your Dart & Flutter development experience with the Dart and Flutter MCP server]\(https\://blog.flutter.dev/supercharge-your-dart-flutter-development-experience-with-the-dart-mcp-server-2edcc8107b49): A blog post explaining the benefits of the MCP server. - \[Gemini in Android Studio now speaks fluent Flutter]\(https\://blog.flutter.dev/gemini-in-android-studio-now-speaks-fluent-flutter-915dfec98274): A blog post about Gemini's Flutter capabilities in Android Studio. ## Performance & optimization - \[Performance best practices]\(https\://docs.flutter.dev/perf): A comprehensive guide to writing performant Flutter code. - \[Flutter performance profiling]\(https\://docs.flutter.dev/perf/ui-performance): Use DevTools to diagnose performance issues. - \[Concurrency and isolates]\(https\://docs.flutter.dev/perf/isolates): Run intensive tasks in the background to avoid blocking the UI. - \[Use the memory view in DevTools]\(https\://docs.flutter.dev/tools/devtools/memory): Inspect memory allocation and detect memory issues. - \[Measuring your app's size]\(https\://docs.flutter.dev/perf/app-size): Learn how to reduce your app's bundle size. - \[Deferred components]\(https\://docs.flutter.dev/perf/deferred-components): Allow parts of your app to be downloaded on demand to improve initial load time. ## Platform integration - \[Writing custom platform-specific code]\(https\://docs.flutter.dev/platform-integration/platform-channels): Communicate with native code using platform channels. - \[Hosting native Android views in your Flutter app with Platform Views]\(https\://docs.flutter.dev/platform-integration/android/platform-views): Embed native Android views in a Flutter app using platform views. - \[Host native iOS views in your Flutter app with platform views]\(https\://docs.flutter.dev/platform-integration/ios/platform-views): Embed native iOS views in a Flutter app using platform views. ## Multiplatform development - \*\*Android:\*\* - \[Android integration]\(https\://docs.flutter.dev/platform-integration/android): The main landing page for Android-specific information. - \[Build and release for Android]\(https\://docs.flutter.dev/deployment/android): A guide to packaging and deploying your Flutter app to the Google Play Store. - \*\*iOS:\*\* - \[iOS integration]\(https\://docs.flutter.dev/platform-integration/ios): The main landing page for iOS-specific information. - \[Build and release for iOS]\(https\://docs.flutter.dev/deployment/ios): A guide to packaging and deploying your Flutter app to the Apple App Store. - \*\*Web:\*\* - \[Building a web application with Flutter]\(https\://docs.flutter.dev/platform-integration/web/building): Configure, run, and build a web application using Flutter. - \[Adding Flutter to any web application]\(https\://docs.flutter.dev/platform-integration/web/embedding-flutter-web): Embed Flutter views in an existing web app or use Flutter for the full page. - \[Support for WebAssembly (Wasm)]\(https\://docs.flutter.dev/platform-integration/web/wasm): Compile Flutter web apps to WebAssembly. - \*\*Desktop:\*\* - \[Build and release for Windows]\(https\://docs.flutter.dev/deployment/windows): A guide to packaging and deploying your Flutter app to Windows. - \[Build and release for macOS]\(https\://docs.flutter.dev/deployment/macos): A guide to packaging and deploying your Flutter app to macOS. - \[Build and release for Linux]\(https\://docs.flutter.dev/deployment/linux): A guide to packaging and deploying your Flutter app to Linux. ## Practical development concerns - \[Using packages]\(https\://docs.flutter.dev/packages-and-plugins/using-packages): How to add new functionality to your app with packages. - \[Monetization with the google\_mobile\_ads package]\(https\://pub.dev/packages/google\_mobile\_ads): The official plugin for displaying Google Mobile Ads. - \[Security best practices]\(https\://docs.flutter.dev/security): An overview of security considerations for Flutter apps. - \[Code obfuscation]\(https\://docs.flutter.dev/deployment/obfuscate): Hide function and class names in compiled Dart code to make it harder to reverse engineer. ## Accessibility & internationalization - \[Accessibility in Flutter]\(https\://docs.flutter.dev/ui/accessibility): A guide to making your Flutter app accessible to all users. - \[Internationalizing Flutter apps]\(https\://docs.flutter.dev/ui/internationalization): How to prepare your app for a global audience. ## Design & UX - \[Organize a theme]\(https\://docs.flutter.dev/cookbook/design/themes): Learn how to use \`ThemeData\` for a consistent design. - \[Cupertino (iOS-style) widgets]\(https\://docs.flutter.dev/ui/widgets/cupertino): Build apps that look and feel native on iOS. - \[Material widgets]\(https\://docs.flutter.dev/ui/widgets/material): A comprehensive list of Material Design widgets. - \[Use a custom font]\(https\://docs.flutter.dev/cookbook/design/fonts): Implement custom typography in your app. ## Advanced topics & internals - \[Architectural overview]\(https\://docs.flutter.dev/resources/architectural-overview): A high-level view of Flutter's architecture. - \[Impeller rendering engine]\(https\://docs.flutter.dev/perf/impeller): Learn about Flutter's new rendering engine. - \[Asynchronous programming in Dart]\(https\://dart.dev/language/async): A deep dive into Futures, Streams, and the event loop. - \[Dart isolates for concurrency]\(https\://dart.dev/language/isolates): Understand how to use isolates for parallel processing. ## Community & contribution - \[Package development best practices]\(https\://docs.flutter.dev/packages-and-plugins/developing-packages): Learn how to create high-quality, publishable packages. - \[Contributing to Flutter]\(https\://github.com/flutter/flutter/blob/master/CONTRIBUTING.md): The official guide to contributing to the Flutter project. - \[Flutter blog]\(https\://blog.flutter.dev): The official blog for the Flutter project. - \[Flutter release notes]\(https\://docs.flutter.dev/release/release-notes): Detailed notes on what's new in each release. - \[Flutter breaking changes and migration guides]\(https\://docs.flutter.dev/release/breaking-changes): Breaking changes and migration guides for each Flutter release. - \[Dart blog]\(https\://dart.dev/blog): The official blog for the Dart language. - \[Dart breaking changes]\(https\://dart.dev/resources/breaking-changes): A list of breaking changes for each Dart release. ## API reference - \[Full Flutter API Reference]\(https\://api.flutter.dev/): The complete, searchable reference for all Flutter libraries, classes, and functions. ## Flutter for developers - \[Flutter for Android developers]\(https\://docs.flutter.dev/flutter-for/android-devs): Apply Android framework knowledge to Flutter development. - \[Flutter for Jetpack Compose developers]\(https\://docs.flutter.dev/flutter-for/compose-devs): Apply Jetpack Compose knowledge to Flutter development. - \[Flutter for SwiftUI Developers]\(https\://docs.flutter.dev/flutter-for/swiftui-devs): Apply SwiftUI knowledge to Flutter development. - \[Flutter for UIKit developers]\(https\://docs.flutter.dev/flutter-for/uikit-devs): Apply UIKit knowledge to Flutter development. - \[Flutter for React Native developers]\(https\://docs.flutter.dev/flutter-for/react-native-devs): Apply React Native knowledge to Flutter development. - \[Flutter for web developers]\(https\://docs.flutter.dev/flutter-for/web-devs): Apply web development knowledge to Flutter development. - \[Flutter for Xamarin.Forms developers]\(https\://docs.flutter.dev/flutter-for/xamarin-forms-devs): Apply Xamarin.Forms knowledge to Flutter development.

----
url: https://docs.flutter.dev/ui/dot-shorthands.md
----

\# Dot shorthands in Flutter > Learn how to use Dart's dot shorthands to write cleaner, concise Flutter code. The \*\*dot shorthands\*\* feature allows you to omit the explicit type when accessing static members, constructors, or enum values, provided the compiler can infer the type from the surrounding context. :::note For a technical overview of this feature, refer to the \[Dot Shorthands guide]\(https\://dart.dev/language/dot-shorthands) in the Dart documentation. ::: ## Why dot shorthands matter Building layouts in Flutter often involves deeply nested widget trees. Historically, this meant repeatedly typing explicit class and enum names for properties like colors, typography, and alignment. Dot shorthands reduces this boilerplate, making your code easier to read and faster to write. Here is a side-by-side comparison of building a simple \`Container\`: ### Without dot shorthands \`\`\`dart Container( alignment: Alignment.center, padding: const EdgeInsets.all(16.0), child: Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.start, children: \[ Text( 'Hello World', style: TextStyle( fontWeight: FontWeight.bold, ), ), ], ), ); \`\`\` ### With dot shorthands \`\`\`dart Container( alignment: .center, // Instead of Alignment.center, padding: const .all(16.0), // Instead of EdgeInsets.all(16.0) child: Column( mainAxisAlignment: .center, // Instead of MainAxisAlignment.center crossAxisAlignment: .start, // Instead of CrossAxisAlignment.start children: \[ Text( 'Hello World', style: TextStyle( fontWeight: .bold, // Instead of FontWeight.bold ), ), ], ), ); \`\`\` ## Where to use dot shorthands Dot shorthands work anywhere the Dart compiler has a clear "context type", meaning it knows exactly what type it expects. In Flutter, this is almost everywhere inside a widget's property list. The most common targets for dot shorthands in Flutter are: \* \*\*Enums\*\*: \`MainAxisAlignment\`, \`CrossAxisAlignment\`, \`BoxFit\`, \`TextDirection\`. \* \*\*Static properties and methods\*\*: \`FontWeight\` (constants like \`.bold\`). \* \*\*Constructors\*\*: \`EdgeInsets.all()\`, \`BorderRadius.circular()\`. ### Example: enums When a property expects an \`enum\`, such as \`mainAxisAlignment\`, you can omit the enum's name and just provide the value preceded by a dot (\`.\`): \`\`\`dart Row( mainAxisAlignment: .spaceEvenly, // Infers MainAxisAlignment.spaceEvenly children: \[ /\* ... \*/ ], ) \`\`\` ### Example: static properties Static properties work when the context type is exactly the class that defines the property. A common example is text styling with \`FontWeight\`: \`\`\`dart Text( 'Feature highlights', style: TextStyle( fontWeight: .bold, // Infers FontWeight.bold ), ) \`\`\` ### Example: constructors You can also use dot shorthands for named constructors. Many Flutter layout properties accept a base class like \`EdgeInsetsGeometry\`. To support dot shorthands, Flutter adds redirecting constructors to these base classes that point to the appropriate subclasses. \`\`\`dart Padding( padding: .symmetric(horizontal: 16.0, vertical: 8.0), // Infers EdgeInsetsGeometry.symmetric child: Text('Spaced out text'), ) \`\`\` You can even use \`.new\` to call an unnamed constructor, though this is less common in standard widget trees: \`\`\`dart class \_MyState extends State { final ScrollController \_scrollController = .new(); // Infers ScrollController() // ... } \`\`\`

----
url: https://docs.flutter.dev/cookbook/design/tabs.md
----

\# Work with tabs > How to implement tabs in a layout.

<!--?code-excerpt path-base="cookbook/design/tabs/"?-->

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]\[]. This recipe creates a tabbed example using the following steps; 1. Create a \`TabController\`. 2. Create the tabs. 3. Create content for each tab. ## 1. Create a \`TabController\` For tabs to work, you need to keep the selected tab and content sections in sync. This is the job of the \[\`TabController\`]\[]. Either create a \`TabController\` manually, or automatically by using a \[\`DefaultTabController\`]\[] widget. Using \`DefaultTabController\` is the simplest option, since it creates a \`TabController\` and makes it available to all descendant widgets.

<!--?code-excerpt "lib/partials.dart (TabController)"?-->

\`\`\`dart return MaterialApp( home: DefaultTabController(length: 3, child: Scaffold()), ); \`\`\` ## 2. Create the tabs When a tab is selected, it needs to display content. You can create tabs using the \[\`TabBar\`]\[] widget. In this example, create a \`TabBar\` with three \[\`Tab\`]\[] widgets and place it within an \[\`AppBar\`]\[].

<!--?code-excerpt "lib/partials.dart (Tabs)"?-->

\`\`\`dart return MaterialApp( home: DefaultTabController( length: 3, child: Scaffold( appBar: AppBar( bottom: const TabBar( tabs: \[ Tab(icon: Icon(Icons.directions\_car)), Tab(icon: Icon(Icons.directions\_transit)), Tab(icon: Icon(Icons.directions\_bike)), ], ), ), ), ), ); \`\`\` 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 you have tabs, display content when a tab is selected. For this purpose, use the \[\`TabBarView\`]\[] widget. :::note Order is important and must correspond to the order of the tabs in the \`TabBar\`. :::

<!--?code-excerpt "lib/main.dart (TabBarView)"?-->

\`\`\`dart body: const TabBarView( children: \[ Icon(Icons.directions\_car), Icon(Icons.directions\_transit), Icon(Icons.directions\_bike), ], ), \`\`\` ## Interactive example

<!--?code-excerpt "lib/main.dart"?-->

\`\`\`dartpad title="Flutter TabBar DartPad hands-on example" run="true" import 'package:flutter/material.dart'; void main() { runApp(const TabBarDemo()); } class TabBarDemo extends StatelessWidget { const TabBarDemo({super.key}); @override Widget build(BuildContext context) { return MaterialApp( home: DefaultTabController( length: 3, child: Scaffold( appBar: AppBar( bottom: const TabBar( tabs: \[ Tab(icon: Icon(Icons.directions\_car)), Tab(icon: Icon(Icons.directions\_transit)), Tab(icon: Icon(Icons.directions\_bike)), ], ), title: const Text('Tabs Demo'), ), body: const TabBarView( children: \[ Icon(Icons.directions\_car), Icon(Icons.directions\_transit), Icon(Icons.directions\_bike), ], ), ), ), ); } } \`\`\` \[\`AppBar\`]: https\://pub.dev/documentation/material\_ui/latest/material\_ui/AppBar-class.html \[\`DefaultTabController\`]: https\://pub.dev/documentation/material\_ui/latest/material\_ui/DefaultTabController-class.html \[material library]: https\://pub.dev/documentation/material\_ui/latest/material\_ui \[\`Tab\`]: https\://pub.dev/documentation/material\_ui/latest/material\_ui/Tab-class.html \[\`TabBar\`]: https\://pub.dev/documentation/material\_ui/latest/material\_ui/TabBar-class.html \[\`TabBarView\`]: https\://pub.dev/documentation/material\_ui/latest/material\_ui/TabBarView-class.html \[\`TabController\`]: https\://pub.dev/documentation/material\_ui/latest/material\_ui/TabController-class.html

----
url: https://docs.flutter.dev/cookbook/testing/widget/finders
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Find widgets](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

To locate widgets in a test environment, use the [`Finder`](https://api.flutter.dev/flutter/flutter_test/Finder-class.html) 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`](https://api.flutter.dev/flutter/flutter_test/flutter_test-library.html) package.

During a `flutter run` session on a widget test, you can also interactively tap parts of the screen for the Flutter tool to print the suggested `Finder`.

This recipe looks at the [`find`](https://api.flutter.dev/flutter/flutter_test/find-constant.html) 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](https://api.flutter.dev/flutter/flutter_test/CommonFinders-class.html).

If you're unfamiliar with widget testing and the role of `Finder` classes, review the [Introduction to widget testing](/cookbook/testing/widget/introduction) recipe.

This recipe uses the following steps:

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 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', (tester) async {
  // Build an App with a Text widget that displays the letter 'H'.
  await tester.pumpWidget(const MaterialApp(home: Scaffold(body: Text('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, 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, 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', (tester) async {
  // Define the test key.
  const testKey = Key('K');
​
  // Build a MaterialApp with the testKey.
  await tester.pumpWidget(MaterialApp(key: testKey, home: Container()));
​
  // 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, 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', (tester) async {
  const childWidget = Padding(padding: EdgeInsets.zero);
​
  // Provide the childWidget to the Container.
  await tester.pumpWidget(Container(child: childWidget));
​
  // Search for the childWidget in the tree and verify it exists.
  expect(find.byWidget(childWidget), findsOneWidget);
});
```

## Summary

[#](#summary)

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, see the [`CommonFinders` documentation](https://api.flutter.dev/flutter/flutter_test/CommonFinders-class.html) to review all available methods.

## Complete example

[#](#complete-example)

dart

```
import 'package:flutter_test/flutter_test.dart';
import 'package:material_ui/material_ui.dart';
​
void main() {
  testWidgets('finds a Text widget', (tester) async {
    // Build an App with a Text widget that displays the letter 'H'.
    await tester.pumpWidget(const MaterialApp(home: Scaffold(body: Text('H'))));
​
    // Find a widget that displays the letter 'H'.
    expect(find.text('H'), findsOneWidget);
  });
​
  testWidgets('finds a widget using a Key', (tester) async {
    // Define the test key.
    const testKey = Key('K');
​
    // Build a MaterialApp with the testKey.
    await tester.pumpWidget(MaterialApp(key: testKey, home: Container()));
​
    // Find the MaterialApp widget using the testKey.
    expect(find.byKey(testKey), findsOneWidget);
  });
​
  testWidgets('finds a specific instance', (tester) async {
    const childWidget = Padding(padding: EdgeInsets.zero);
​
    // Provide the childWidget to the Container.
    await tester.pumpWidget(Container(child: childWidget));
​
    // Search for the childWidget in the tree and verify it exists.
    expect(find.byWidget(childWidget), findsOneWidget);
  });
}
```

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-09-29. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/testing/widget/finders.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/cookbook/testing/widget/finders\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/testing/widget/finders.md "Report an issue with this page").

----
url: https://docs.flutter.dev/cookbook/forms/text-field-changes
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Handle changes to a text field](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

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 where you want to update the results as the user types.

How do you run a callback function every time the text changes? With Flutter, you have two options:

1. Supply an `onChanged()` callback to a `TextField` or a `TextFormField`.
2. Use a `TextEditingController`.

## 1. Supply an `onChanged()` callback to a `TextField` or a `TextFormField`

[#](#1-supply-an-onchanged-callback-to-a-textfield-or-a-textformfield)

The simplest approach is to supply an [`onChanged()`](https://pub.dev/documentation/material_ui/latest/material_ui/TextField/onChanged.html) callback to a [`TextField`](https://pub.dev/documentation/material_ui/latest/material_ui/TextField-class.html) or a [`TextFormField`](https://pub.dev/documentation/material_ui/latest/material_ui/TextFormField-class.html). Whenever the text changes, the callback is invoked.

In this example, print the current value and length of the text field to the console every time the text changes.

It's important to use [`characters`](https://pub.dev/packages/characters) when dealing with user input, as text might contain complex characters. This ensures that every character is counted correctly as it appears to the user.

dart

```
TextField(
  onChanged: (text) {
    print('First text field: $text (${text.characters.length})');
  },
),
```

## 2. Use a `TextEditingController`

[#](#2-use-a-texteditingcontroller)

A more powerful, but more elaborate approach, is to supply a [`TextEditingController`](https://api.flutter.dev/flutter/widgets/TextEditingController-class.html) as the [`controller`](https://pub.dev/documentation/material_ui/latest/material_ui/TextField/controller.html) property of the `TextField` or a `TextFormField`.

To be notified when the text changes, listen to the controller using the [`addListener()`](https://api.flutter.dev/flutter/foundation/ChangeNotifier/addListener.html) method using the following steps:

1. Create a `TextEditingController`.
2. Connect the `TextEditingController` to a text field.
3. Create a function to print the latest value.
4. Listen to the controller for changes.

### Create a `TextEditingController`

[#](#create-a-texteditingcontroller)

Create a `TextEditingController`:

dart

```
// Define a custom Form widget.
class MyCustomForm extends StatefulWidget {
  const MyCustomForm({super.key});
​
  @override
  State<MyCustomForm> createState() => _MyCustomFormState();
}
​
// Define a corresponding State class.
// This class holds data related to the Form.
class _MyCustomFormState extends State<MyCustomForm> {
  // Create a text controller. Later, 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.
    myController.dispose();
    super.dispose();
  }
​
  @override
  Widget build(BuildContext context) {
    // Fill this out in the next step.
  }
}
```

### Connect the `TextEditingController` to a text field

[#](#connect-the-texteditingcontroller-to-a-text-field)

Supply the `TextEditingController` to either a `TextField` or a `TextFormField`. Once you wire these two classes together, you can begin listening for changes to the text field.

dart

```
TextField(controller: myController),
```

### Create a function to print the latest value

[#](#create-a-function-to-print-the-latest-value)

You need a function to run every time the text changes. Create a method in the `_MyCustomFormState` class that prints out the current value of the text field.

dart

```
void _printLatestValue() {
  final text = myController.text;
  print('Second text field: $text (${text.characters.length})');
}
```

### Listen to the controller for changes

[#](#listen-to-the-controller-for-changes)

Finally, listen to the `TextEditingController` and call the `_printLatestValue()` method when the text changes. Use the [`addListener()`](https://api.flutter.dev/flutter/foundation/ChangeNotifier/addListener.html) method for this purpose.

Begin listening for changes when the `_MyCustomFormState` class is initialized, and stop listening when the `_MyCustomFormState` is disposed.

dart

```
@override
void initState() {
  super.initState();
​
  // Start listening to changes.
  myController.addListener(_printLatestValue);
}
```

dart

```
@override
void dispose() {
  // Clean up the controller when the widget is removed from the widget tree.
  // This also removes the _printLatestValue listener.
  myController.dispose();
  super.dispose();
}
```

## Interactive example

[#](#interactive-example)

```
import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      title: 'Handle Text Field Changes',
      home: MyCustomForm(),
    );
  }
}

// Define a custom Form widget.
class MyCustomForm extends StatefulWidget {
  const MyCustomForm({super.key});

  @override
  State<MyCustomForm> createState() => _MyCustomFormState();
}

// Define a corresponding State class.
// This class holds data related to the Form.
class _MyCustomFormState extends State<MyCustomForm> {
  // Create a text controller and use it to retrieve the current value
  // of the TextField.
  final myController = TextEditingController();

  @override
  void initState() {
    super.initState();

    // Start listening to changes.
    myController.addListener(_printLatestValue);
  }

  @override
  void dispose() {
    // Clean up the controller when the widget is removed from the widget tree.
    // This also removes the _printLatestValue listener.
    myController.dispose();
    super.dispose();
  }

  void _printLatestValue() {
    final text = myController.text;
    print('Second text field: $text (${text.characters.length})');
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Handle Text Field Changes')),
      body: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          children: [
            TextField(
              onChanged: (text) {
                print('First text field: $text (${text.characters.length})');
              },
            ),
            TextField(controller: myController),
          ],
        ),
      ),
    );
  }
}
```

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-10-06. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/forms/text-field-changes.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/cookbook/forms/text-field-changes\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/forms/text-field-changes.md "Report an issue with this page").

----
url: https://docs.flutter.dev/tools/devtools/inspector
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Use the Flutter inspector](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

For information on how to locate DevTools screens in different IDEs, check out the [DevTools overview](/tools/devtools).

## What is it?

[#](#what-is-it)

The Flutter widget inspector is a powerful tool for visualizing and exploring Flutter widget trees. The Flutter framework uses widgets as the core building block for anything from controls (such as text, buttons, and toggles), to layout (such as centering, padding, rows, and columns). The inspector helps you visualize and explore Flutter widget trees, and can be used for the following:

* understanding existing layouts
* diagnosing layout issues

## The Flutter inspector

[#](#new)

The Flutter inspector is enabled by default. It can be configured from the [inspector settings dialog](#inspector-settings).

### Debugging layout issues visually

[#](#debugging-layout-issues-visually)

The following is a guide to the features available in the inspector's toolbar. When space is limited, the icon is used as the visual version of the label.

* *
  * **Select widget mode**

  Enable this button in order to select a widget on the device to inspect it. To learn more, check out [Inspecting a widget](#inspecting-a-widget).

* *
  * **Show implementation widgets**

  Enable this button in to show implementation widgets in the widget tree. To learn more, check out [Use the Widget Tree](#use-the-widget-tree).

* **Refresh tree**

  Reload the current widget info.

* **[Slow animations](#slow-animations)**

  Run animations 5 times slower to help fine-tune them.

* **[Show guidelines](#show-guidelines)**

  Overlay guidelines to assist with fixing layout issues.

* **[Show baselines](#show-baselines)**

  Show baselines, which are used for aligning text. Can be useful for checking if text is aligned.

* **[Highlight repaints](#highlight-repaints)**

  Show borders that change color when elements repaint. Useful for finding unnecessary repaints.

* **[Highlight oversized images](#highlight-oversized-images)**

  Highlights images that are using too much memory by inverting colors and flipping them.

## Inspecting a widget

[#](#inspecting-a-widget)

You can browse the interactive widget tree to view nearby widgets and see their field values.

To locate individual UI elements in the widget tree, click the **Select Widget Mode** button in the toolbar. This puts the app on the device into a "widget select" mode. Click any widget in the app's UI; this selects the widget on the app's screen, and scrolls the widget tree to the corresponding node. Toggle the **Select Widget Mode** button again to exit widget select mode.

When debugging layout issues, the key fields to look at are the `size` and `constraints` fields. The constraints flow down the tree, and the sizes flow back up. For more information on how this works, see [Understanding constraints](/ui/layout/constraints).

## Flutter Widget Tree

[#](#flutter-widget-tree)

The Flutter Widget Tree allows you to visualize, understand and navigate your app's Widget tree.

### Use the Widget Tree

[#](#use-the-widget-tree)

#### Viewing widgets created in your project

[#](#viewing-widgets-created-in-your-project)

By default, the Flutter Widget Tree includes all the widgets created in your root project's directory.

The parent-children relationships of the widgets are represented by a single vertical line (if the parent widget only has a single child) or through indentation (if the parent widget has multiple children.)

For example, for the following section of a widget tree:

* `Padding` has a single child `Row`
* `Row` has three children: `Icon`, `SizedBox`, and `Flexible`
* `Flexible` has a single child `Column`
* `Column` has four children: `Text`, `Text`, `SizedBox`, and `Divider`

#### Viewing all widgets

[#](#viewing-all-widgets)

To instead view all the widgets in your widget tree, including those that were created outside of your project, toggle on "Show implementation widgets".

The implementation widgets are shown in a lighter font than the widgets created in your project, thereby visually distinguishing them. They are also hidden behind collapsible groups which can be expanded via the inline expand buttons.

For example, here is the same section of a widget tree as above with implementation widgets shown:

* `Icon` has five implementation widgets collapsed beneath it
* Both `Text` widgets have `RichText` implementation widget children
* `Divider` has nine implementation widgets collapsed beneath it

## Flutter Widget Explorer

[#](#flutter-widget-explorer)

The Flutter Widget Explorer helps you to better understand the inspected widget.

### Use the Widget Explorer

[#](#use-the-widget-explorer)

From the Flutter inspector, select a widget. The Widget Explorer will be shown on the right side of the window.

Depending on the selected widget, the Widget Explorer will include one or more of the following:

* Widget properties tab
* Flex explorer tab
* Render object tab

#### Widget properties tab

[#](#widget-properties-tab)

The properties tab shows you mini-view of your widget layout, including width, height, and padding, along with a list of properties on that widget.

These properties include whether or not the value matches the default value for the property argument.

#### Render object tab

[#](#render-object-tab)

The render object tab displays all the properties set on the render object of the selected Flutter widget.

#### Flex explorer tab

[#](#flex-explorer-tab)

When you select a flex widget (for example, [`Row`](https://api.flutter.dev/flutter/widgets/Row-class.html), [`Column`](https://api.flutter.dev/flutter/widgets/Column-class.html), [`Flex`](https://api.flutter.dev/flutter/widgets/Flex-class.html)) or a direct child of a flex widget, the flex explorer tool will appear in the Widget Explorer.

The flex explorer tool visualizes how [`Flex`](https://api.flutter.dev/flutter/widgets/Flex-class.html) widgets and their children are laid out. The explorer identifies the main axis and cross axis, as well as the current alignment for each (for example, start, end, and spaceBetween). It also shows details like flex factor, flex fit, and layout constraints.

Additionally, the explorer shows layout constraint violations and render overflow errors. Violated layout constraints are colored red, and overflow errors are presented in the standard "yellow-tape" pattern, as you might see on a running device. These visualizations aim to improve understanding of why overflow errors occur as well as how to fix them.

Clicking a widget in the flex explorer mirrors the selection on the on-device inspector. **Select Widget Mode** needs to be enabled for this. To enable it, click on the **Select Widget Mode** button in the inspector.

For some properties, like flex factor, flex fit, and alignment, you can modify the value via dropdown lists in the explorer. When modifying a widget property, you see the new value reflected not only in the flex explorer, but also on the device running your Flutter app. The explorer animates on property changes so that the effect of the change is clear. Widget property changes made from the layout explorer don't modify your source code and are reverted on hot reload.

##### Interactive Properties

[#](#interactive-properties)

The flex explorer supports modifying [`mainAxisAlignment`](https://api.flutter.dev/flutter/widgets/Flex/mainAxisAlignment.html), [`crossAxisAlignment`](https://api.flutter.dev/flutter/widgets/Flex/crossAxisAlignment.html), and [`FlexParentData.flex`](https://api.flutter.dev/flutter/rendering/FlexParentData/flex.html). In the future, we may add support for additional properties such as [`mainAxisSize`](https://api.flutter.dev/flutter/widgets/Flex/mainAxisSize.html), [`textDirection`](https://api.flutter.dev/flutter/widgets/Flex/textDirection.html), and [`FlexParentData.fit`](https://api.flutter.dev/flutter/rendering/FlexParentData/fit.html).

###### mainAxisAlignment

Supported values:

* `MainAxisAlignment.start`
* `MainAxisAlignment.end`
* `MainAxisAlignment.center`
* `MainAxisAlignment.spaceBetween`
* `MainAxisAlignment.spaceAround`
* `MainAxisAlignment.spaceEvenly`

###### crossAxisAlignment

Supported values:

* `CrossAxisAlignment.start`
* `CrossAxisAlignment.center`
* `CrossAxisAlignment.end`
* `CrossAxisAlignment.stretch`

###### FlexParentData.flex

The flex explorer supports 7 flex options in the UI (null, 0, 1, 2, 3, 4, 5), but technically the flex factor of a flex widget's child can be any int.

###### Flexible.fit

The flex explorer supports the two different types of [`FlexFit`](https://api.flutter.dev/flutter/rendering/FlexFit.html): `loose` and `tight`.

## Visual debugging

[#](#visual-debugging)

The Flutter Inspector provides several options for visually debugging your app.

### Slow animations

[#](#slow-animations)

When enabled, this option runs animations 5 times slower for easier visual inspection. This can be useful if you want to carefully observe and tweak an animation that doesn't look quite right.

This can also be set in code:

dart

```
import 'package:flutter/scheduler.dart';
​
void setSlowAnimations() {
  timeDilation = 5.0;
}
```

This slows the animations by 5x.

#### See also

[#](#see-also)

The following links provide more info.

* [Flutter documentation: timeDilation property](https://api.flutter.dev/flutter/scheduler/timeDilation.html)

The following screen recordings show before and after slowing an animation.

### Show guidelines

[#](#show-guidelines)

This feature draws guidelines over your app that display render boxes, alignments, paddings, scroll views, clippings and spacers.

This tool can be used for better understanding your layout. For instance, by finding unwanted padding or understanding widget alignment.

You can also enable this in code:

dart

```
import 'package:flutter/rendering.dart';
​
void showLayoutGuidelines() {
  debugPaintSizeEnabled = true;
}
```

#### Render boxes

[#](#render-boxes)

Widgets that draw to the screen create a [render box](https://api.flutter.dev/flutter/rendering/RenderBox-class.html), the building blocks of Flutter layouts. They're shown with a bright blue border:

#### Alignments

[#](#alignments)

Alignments are shown with yellow arrows. These arrows show the vertical and horizontal offsets of a widget relative to its parent. For example, this button's icon is shown as being centered by the four arrows:

#### Padding

[#](#padding)

Padding is shown with a semi-transparent blue background:

#### Scroll views

[#](#scroll-views)

Widgets with scrolling contents (such as list views) are shown with green arrows:

#### Clipping

[#](#clipping)

Clipping, for example when using the [ClipRect widget](https://api.flutter.dev/flutter/widgets/ClipRect-class.html), are shown with a dashed pink line with a scissors icon:

#### Spacers

[#](#spacers)

Spacer widgets are shown with a grey background, such as this `SizedBox` without a child:

### Show baselines

[#](#show-baselines)

This option makes all baselines visible. Baselines are horizontal lines used to position text.

This can be useful for checking whether text is precisely aligned vertically. For example, the text baselines in the following screenshot are slightly misaligned:

The [Baseline](https://api.flutter.dev/flutter/widgets/Baseline-class.html) widget can be used to adjust baselines.

A line is drawn on any [render box](https://api.flutter.dev/flutter/rendering/RenderBox-class.html) that has a baseline set; alphabetic baselines are shown as green and ideographic as yellow.

You can also enable this in code:

dart

```
import 'package:flutter/rendering.dart';
​
void showBaselines() {
  debugPaintBaselinesEnabled = true;
}
```

### Highlight repaints

[#](#highlight-repaints)

This option draws a border around all [render boxes](https://api.flutter.dev/flutter/rendering/RenderBox-class.html) that changes color every time that box repaints.

This rotating rainbow of colors is useful for finding parts of your app that are repainting too often and potentially harming performance.

For example, one small animation could be causing an entire page to repaint on every frame. Wrapping the animation in a [RepaintBoundary widget](https://api.flutter.dev/flutter/widgets/RepaintBoundary-class.html) limits the repainting to just the animation.

Here the progress indicator causes its container to repaint:

dart

```
class EverythingRepaintsPage extends StatelessWidget {
  const EverythingRepaintsPage({super.key});
​
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Repaint Example')),
      body: const Center(child: CircularProgressIndicator()),
    );
  }
}
```

Wrapping the progress indicator in a `RepaintBoundary` causes only that section of the screen to repaint:

dart

```
class AreaRepaintsPage extends StatelessWidget {
  const AreaRepaintsPage({super.key});
​
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Repaint Example')),
      body: const Center(
        child: RepaintBoundary(child: CircularProgressIndicator()),
      ),
    );
  }
}
```

`RepaintBoundary` widgets have tradeoffs. They can help with performance, but they also have an overhead of creating a new canvas, which uses additional memory.

You can also enable this option in code:

dart

```
import 'package:flutter/rendering.dart';
import 'package:material_ui/material_ui.dart';
​
void highlightRepaints() {
  debugRepaintRainbowEnabled = true;
}
```

### Highlight oversized images

[#](#highlight-oversized-images)

This option highlights images that are too large by both inverting their colors and flipping them vertically:

The highlighted images use more memory than is required; for example, a large 5MB image displayed at 100 by 100 pixels.

Such images can cause poor performance, especially on lower-end devices and when you have many images, as in a list view, this performance hit can add up. Information about each image is printed in the debug console:

```
dash.png has a display size of 213×392 but a decode size of 2130×392, which uses an additional 2542KB.
```

Images are deemed too large if they use at least 128KB more than required.

#### Fixing images

[#](#fixing-images)

Wherever possible, the best way to fix this problem is resizing the image asset file so it's smaller.

If this isn't possible, you can use the `cacheHeight` and `cacheWidth` parameters on the `Image` constructor:

dart

```
class ResizedImage extends StatelessWidget {
  const ResizedImage({super.key});
​
  @override
  Widget build(BuildContext context) {
    return Image.asset('dash.png', cacheHeight: 213, cacheWidth: 392);
  }
}
```

This makes the engine decode this image at the specified size, and reduces memory usage (decoding and storage is still more expensive than if the image asset itself was shrunk). The image is rendered to the constraints of the layout or width and height regardless of these parameters.

This property can also be set in code:

dart

```
void showOversizedImages() {
  debugInvertOversizedImages = true;
}
```

#### More information

[#](#more-information)

You can learn more at the following link:

* [Flutter documentation: debugInvertOversizedImages](https://api.flutter.dev/flutter/rendering/debugInvertOversizedImages.html)

## Track widget creation

[#](#track-widget-creation)

Part of the functionality of the Flutter inspector is based on instrumenting the application code in order to better understand the source locations where widgets are created. The source instrumentation allows the Flutter inspector to present the widget tree in a manner similar to how the UI was defined in your source code. Without it, the tree of nodes in the widget tree are much deeper, and it can be more difficult to understand how the runtime widget hierarchy corresponds to your application's UI.

You can disable this feature by passing `--no-track-widget-creation` to the `flutter run` command.

Here are examples of what your widget tree might look like with and without track widget creation enabled.

Track widget creation enabled (default):

Track widget creation disabled (not recommended):

This feature prevents otherwise-identical `const` Widgets from being considered equal in debug builds. For more details, see the discussion on [common problems when debugging](/testing/debugging).

## Inspector settings

[#](#inspector-settings)

### Enable hover inspection

[#](#enable-hover-inspection)

Hovering over any widget displays its properties and values.

Toggling this value enables or disables the hover inspection functionality.

### Enable widget tree auto-refreshing

[#](#enable-widget-tree-auto-refreshing)

When enabled, the widget tree automatically refreshes after a hot-reload or a navigation event.

### Package directories

[#](#package-directories)

By default, DevTools limits the widgets displayed in the widget tree to those created in the project's root directory. To see all widgets, including those created outside of a project's root directory, toggle on [Show implementation widgets](#debugging-layout-issues-visually)

In order to include other widgets in the default widget tree, a parent directory of theirs must be added to the Package Directories.

For example, consider the following directory structure:

```
project_foo
  pkgs
    project_foo_app
    widgets_A
    widgets_B
```

Running your app from `project_foo_app` displays only widgets from `project_foo/pkgs/project_foo_app` in the widget inspector tree.

To show widgets from `widgets_A` in the widget tree, add `project_foo/pkgs/widgets_A` to the package directories.

To display *all* widgets from your project root in the widget tree, add `project_foo` to the package directories.

Changes to your package directories persist the next time the widget inspector is opened for the app.

## Other resources

[#](#other-resources)

For a demonstration of what's generally possible with the inspector, see the [DartConf 2018 talk](https://www.youtube.com/watch?v=JIcmJNT9DNI) demonstrating the IntelliJ version of the Flutter inspector.

To learn how to visually debug layout issues using DevTools, check out a guided [Flutter Inspector tutorial](https://medium.com/@fluttergems/mastering-dart-flutter-devtools-flutter-inspector-part-2-of-8-bbff40692fc7).

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-09-29. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/inspector.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/tools/devtools/inspector\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/inspector.md "Report an issue with this page").

----
url: https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.28.3
----

The 2.28.3 release of the Dart and Flutter DevTools includes the following changes among other general improvements. To learn more about DevTools, check out the [DevTools overview](https://docs.flutter.dev/tools/devtools).

This was a cherry-pick release on top of DevTools 2.28.2. To learn about the improvements included in DevTools 2.28.2, please read the [release notes](/tools/devtools/release-notes/release-notes-2.28.2).

## General updates

[#](#general-updates)

* Added a link to the new "Dive in to DevTools" YouTube [video](https://www.youtube.com/watch?v=_EYk-E29edo) in the bottom status bar. This video provides a brief tutorial for each DevTools screen. [#6554](https://github.com/flutter/devtools/pull/6554)

* Added a workaround to fix copy button functionality in VSCode. - [#6598](https://github.com/flutter/devtools/pull/6598)

## Performance updates

[#](#performance-updates)

* Disable the Raster Stats tool for the Impeller backend since it is not supported. - [#6616](https://github.com/flutter/devtools/pull/6616)

## VS Code Sidebar updates

[#](#vs-code-sidebar-updates)

* When using VS Code with a light theme, the embedded sidebar provided by DevTools will now also show in the light theme. - [#6581](https://github.com/flutter/devtools/pull/6581)

## Full commit history

[#](#full-commit-history)

To find a complete list of changes in this release, check out the [DevTools git log](https://github.com/flutter/devtools/tree/v2.28.3).

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/release-notes/release-notes-2.28.3.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.28.3\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/release-notes/release-notes-2.28.3.md "Report an issue with this page").

----
url: https://docs.flutter.dev/ui/adaptive-responsive/general.md
----

\# General approach to adaptive apps > General advice on how to approach making your Flutter app adaptive.

<!--?code-excerpt path-base="ui/adaptive_app_demos"?-->

So, just \_how\_ do you approach taking an app designed for conventional mobile devices, and make it beautiful on a wide range of devices? What steps are required? Google engineers, who have experience doing this very thing for large apps, recommend the following 3-step approach. ## Step 1: Abstract !\[Step 1: Abstract info common to any UI widget]\(/assets/images/docs/ui/adaptive-responsive/abstract.png) First, identify the widgets that you plan to make dynamic. Analyze the constructors for those widgets and abstract out the data that you can share. Common widgets that require adaptability are: \* Dialogs, both fullscreen and modal \* Navigation UI, both rail and bottom bar \* Custom layout, such as "is the UI area taller or wider?" For example, in a \`Dialog\` widget, you can share the info that contains the \_content\_ of the dialog. Or, perhaps you want to switch between a \`NavigationBar\` when the app window is small, and a \`NavigationRail\` when the app window is large. These widgets would likely share a list of navigable destinations. In this case, you might create a \`Destination\` widget to hold this info, and specify the \`Destination\` as having both an icon and a text label. Next, you will evaluate your screen size to decide on how to display your UI. ## Step 2: Measure !\[Step 2: How to measure screen size]\(/assets/images/docs/ui/adaptive-responsive/measure.png) You have two ways to determine the size of your display area: \`MediaQuery\` and \`LayoutBuilder\`. ### MediaQuery In the past, you might have used \`MediaQuery.of\` to determine the size of the device's screen. However, devices today feature screens with a wide variety of sizes and shapes, and this test can be misleading. For example, maybe your app currently occupies a small window on a large screen. If you use the \`MediaQuery.of\` method and conclude the screen to be small (when, in fact, the app displays in a tiny window on a large screen), and you've portrait locked your app, it causes the app's window to lock to the center of the screen, surrounded with black. This is hardly an ideal UI on a large screen. :::note The Material Guidelines encourage you to never \_portrait lock\_ your app (by disabling landscape mode). However, if you feel you really must, then at least define the portrait mode to work in top-down mode as well as bottom up. ::: Keep in mind that \`MediaQuery.sizeOf\` returns the current size of the app's entire screen and not just a single widget. You have two ways to measure your screen space. You can use either \`MediaQuery.sizeOf\` or \`LayoutBuilder\`, depending on whether you want the size of the whole app window, or more local sizing. If you want your widget to be fullscreen, even when the app window is small, use \`MediaQuery.sizeOf\` so you can choose the UI based on the size of the app window itself. In the previous section, you want to base the sizing behavior on the entire app's window, so you would use \`MediaQuery.sizeOf\`. :::secondary Why use \`MediaQuery.sizeOf\` instead of \`MediaQuery.of\`? Previous advice recommended that you use the \`of\` method of \`MediaQuery\` to obtain the app window's dimensions. Why has this advice changed? The short answer is \*\*for performance reasons.\*\* \`MediaQuery\` contains a lot of data, but if you're only interested in the size property, it's more efficient to use the \`sizeOf\` method. Both methods return the size of the app window in logical pixels (also known as \_density independent pixels\_). The logical pixel dimensions generally works best as its roughly the same visual size across all devices. The \`MediaQuery\` class has other specialized functions for each of its individual properties for the same reason. ::: Requesting the size of the app window from inside the \`build\` method, as in \`MediaQuery.sizeOf(context)\`, causes the given \`BuildContext\` to rebuild any time the size property changes. ### LayoutBuilder \`LayoutBuilder\` accomplishes a similar goal as \`MediaQuery.sizeOf\`, with some distinctions. Rather than providing the size of the app's window, \`LayoutBuilder\` provides the layout constraints from the parent \`Widget\`. This means that you get sizing information based on the specific spot in the widget tree where you added the \`LayoutBuilder\`. Also, \`LayoutBuilder\` returns a \`BoxConstraints\` object instead of a \`Size\` object, so you are given the valid width and height ranges (minimum and maximum) for the content, rather than just a fixed size. This can be useful for custom widgets. For example, imagine a custom widget, where you want the sizing to be based on the space specifically given to that widget, and not the app window in general. In this scenario, use \`LayoutBuilder\`. ## Step 3: Branch !\[Step 3: Branch the code based on the desired UI]\(/assets/images/docs/ui/adaptive-responsive/branch.png) At this point, you must decide what sizing breakpoints to use when choosing what version of the UI to display. For example, the \[Material layout]\[] guidelines suggest using a bottom nav bar for windows less than 600 logical pixels wide, and a nav rail for those that are 600 pixels wide or greater. Again, your choice shouldn't depend on the \_type\_ of device, but on the device's available window size. \[Material layout]: https\://m3.material.io/foundations/layout/applying-layout/window-size-classes To work through an example that switches between a \`NavigationRail\` and a \`NavigationBar\`, check out the \[Building an animated responsive app layout with Material 3]\[codelab]. \[codelab]: https\://codelabs.developers.google.com/codelabs/flutter-animated-responsive-layout The next page discusses how to ensure that your app looks best on large screens and foldables.

----
url: https://docs.flutter.dev/cookbook/lists/floating-app-bar.md
----

\# Place a floating app bar above a list > How to place a floating app bar or navigation bar above a list.

<!--?code-excerpt path-base="cookbook/lists/floating_app_bar/"?-->

This guide describes how to place a floating app bar or navigation bar above a list in a Flutter app. ## Overview To make it easier for users to view a list of items, you might want to minimize the app bar (navigation bar), as the user scrolls down the list. Moving the app bar into a \[\`CustomScrollView\`]\[] allows you to create an app bar that can be minimized or scroll 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 minimizes as the user scrolls down the list using the following steps: 1. Create a \`CustomScrollView\`. 2. Add a floating app bar to \`CustomScrollView\`. 3. Add a list of items to \`CustomScrollView\`. ## 1. Create a \`CustomScrollView\` 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 provided to the \`CustomScrollView\` are known as \_slivers\_. There are several types of slivers, such as \`SliverList\`, \`SliverGrid\`, and \`SliverAppBar\`. In fact, the \`ListView\` and \`GridView\` widgets use the \`SliverList\` and \`SliverGrid\` widgets to implement scrolling. For this example, create a \`CustomScrollView\` that contains a \`SliverList\`. Also, remove the app bar property from your code if it exists.

<!--?code-excerpt "lib/starter_material.dart (CustomScrollView)" replace="/^return const //g"?-->

\`\`\`dart MaterialApp( title: 'Floating App Bar', home: Scaffold( // No app bar property provided yet. body: CustomScrollView( // Add the app bar and list of items as slivers in the next steps. slivers: \[], ), ), ); \`\`\`

<!--?code-excerpt "lib/starter_cupertino.dart (CustomScrollView)" replace="/^return const //g"?-->

\`\`\`dart CupertinoApp( title: 'Floating Navigation Bar', home: CupertinoPageScaffold( // No navigation bar property provided yet. child: CustomScrollView( // Add the navigation bar and list of items as slivers in the next steps. slivers: \[], ), ), ); \`\`\` ## 2. Add a floating app bar Next, add an app bar to the \[\`CustomScrollView\`]\[]. Flutter provides the \[\`SliverAppBar\`]\[] widget which, much like the normal \`AppBar\` widget, uses the \`SliverAppBar\` to display a title, tabs, images and more. However, the \`SliverAppBar\` also gives you the ability to create a "floating" app bar that shrinks and floats when you're not at the top of the page. To create this effect: 1. Start with an app bar that displays only a title. 2. Set the \`pinned\` property to \`true\`. 3. Add a \`flexibleSpace\` widget that fills the available \`expandedHeight\`.

<!--?code-excerpt "lib/step2_material.dart (SliverAppBar)" replace="/^body: //g;/^\),$/)/g"?-->

\`\`\`dart slivers: \[ // Add the app bar to the CustomScrollView. SliverAppBar( // Provide a standard title. title: Text('Floating App Bar'), // Pin the app bar when scrolling. pinned: true, // Display a placeholder widget to visualize the shrinking size. flexibleSpace: Placeholder(), // Make the initial height of the SliverAppBar larger than normal. expandedHeight: 200, ), ], \`\`\` :::tip Play around with the \[various properties you can pass to the \`SliverAppBar\` widget]\[], 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. ::: Flutter provides the \[\`CupertinoSliverNavigationBar\`]\[] widget, which lets you have a "floating" navigation bar that shrinks when you scroll down and floats when you're not at the top of the page. To create this effect: 1. Add \`CupertinoSliverNavigationBar\` to \`CustomScrollView\`. 2. Start with an app bar that displays only a title.

<!--?code-excerpt "lib/step2_cupertino.dart (SliverAppBar)" replace="/^body: //g;/^\),$/)/g"?-->

\`\`\`dart slivers: \[ // Add the navigation bar to the CustomScrollView. CupertinoSliverNavigationBar( // Provide a standard title. largeTitle: Text('Floating App Bar'), ), ], \`\`\` ## 3. Add a list of items Now that you have the app bar in place, add a list of items to the \`CustomScrollView\`. You have two options: a \[\`SliverList\`]\[] or a \[\`SliverGrid\`]\[]. 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.

<!--?code-excerpt "lib/main_material.dart (SliverList)" replace="/^\),$/)/g"?-->

\`\`\`dart // Next, create a SliverList SliverList.builder( // The builder function returns a ListTile with a title that // displays the index of the current item. itemBuilder: (context, index) => ListTile(title: Text('Item #$index')), // Builds 50 ListTiles itemCount: 50, ) \`\`\`

<!--?code-excerpt "lib/main_cupertino.dart (SliverList)" replace="/^\),$/)/g"?-->

\`\`\`dart // Next, create a SliverList SliverList.builder( // The builder function returns a CupertinoListTile with a title // that displays the index of the current item. itemBuilder: (context, index) => CupertinoListTile(title: Text('Item #$index')), // Builds 50 CupertinoListTile itemCount: 50, ) \`\`\` ## Interactive example

<!--?code-excerpt "lib/main_material.dart"?-->

\`\`\`dartpad title="Flutter floating app bar hands-on example in DartPad" run="false" import 'package:flutter/material.dart'; void main() => runApp(const MyApp()); class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { const title = 'Floating App Bar'; return MaterialApp( title: title, home: Scaffold( // No app bar provided to Scaffold, only a body with a // CustomScrollView. body: CustomScrollView( slivers: \[ // Add the app bar to the CustomScrollView. const SliverAppBar( // Provide a standard title. title: Text(title), // Pin the app bar when scrolling pinned: true, // Display a placeholder widget to visualize the shrinking size. flexibleSpace: Placeholder(), // Make the initial height of the SliverAppBar larger than normal. expandedHeight: 200, ), // Next, create a SliverList SliverList.builder( // The builder function returns a ListTile with a title that // displays the index of the current item. itemBuilder: (context, index) => ListTile(title: Text('Item #$index')), // Builds 50 ListTiles itemCount: 50, ), ], ), ), ); } } \`\`\`

<!--?code-excerpt "lib/main_cupertino.dart"?-->

\`\`\`dartpad title="Flutter floating navigation bar hands-on example in DartPad" run="false" import 'package:flutter/cupertino.dart'; void main() => runApp(const MyApp()); class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { const title = 'Floating Navigation Bar'; return CupertinoApp( title: title, home: CupertinoPageScaffold( // No navigation bar provided to CupertinoPageScaffold, // only a body with a CustomScrollView. child: CustomScrollView( slivers: \[ // Add the navigation bar to the CustomScrollView. const CupertinoSliverNavigationBar( // Provide a standard title. largeTitle: Text(title), ), // Next, create a SliverList SliverList.builder( // The builder function returns a CupertinoListTile with a title // that displays the index of the current item. itemBuilder: (context, index) => CupertinoListTile(title: Text('Item #$index')), // Builds 50 CupertinoListTile itemCount: 50, ), ], ), ), ); } } \`\`\` \[\`CupertinoSliverNavigationBar\`]: https\://pub.dev/documentation/cupertino\_ui/latest/cupertino\_ui/CupertinoSliverNavigationBar-class.html \[\`CustomScrollView\`]: https\://api.flutter.dev/flutter/widgets/CustomScrollView-class.html \[\`SliverAppBar\`]: https\://pub.dev/documentation/material\_ui/latest/material\_ui/SliverAppBar-class.html \[\`SliverChildBuilderDelegate\`]: https\://api.flutter.dev/flutter/widgets/SliverChildBuilderDelegate-class.html \[\`SliverChildDelegate\`]: https\://api.flutter.dev/flutter/widgets/SliverChildDelegate-class.html \[\`SliverGrid\`]: https\://api.flutter.dev/flutter/widgets/SliverGrid-class.html \[\`SliverList\`]: https\://api.flutter.dev/flutter/widgets/SliverList-class.html \[various properties you can pass to the \`SliverAppBar\` widget]: https\://pub.dev/documentation/material\_ui/latest/material\_ui/SliverAppBar/SliverAppBar.html

----
url: https://docs.flutter.dev/cookbook/gestures/handling-taps
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Handle taps](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

You not only want to display information to users, you want users to interact with your app. Use the [`GestureDetector`](https://api.flutter.dev/flutter/widgets/GestureDetector-class.html) widget to respond to fundamental actions, such as tapping and dragging.

This recipe shows how to make a custom button that shows a snackbar when tapped with the following steps:

1. Create the button.
2. Wrap it in a `GestureDetector` and provide an `onTap()` callback.

dart

```
// The GestureDetector wraps the button.
GestureDetector(
  // When the child is tapped, show a snackbar.
  onTap: () {
    const snackBar = SnackBar(content: Text('Tap'));
​
    ScaffoldMessenger.of(context).showSnackBar(snackBar);
  },
  // The custom button
  child: Container(
    padding: const EdgeInsets.all(12),
    decoration: BoxDecoration(
      color: Colors.lightBlue,
      borderRadius: BorderRadius.circular(8),
    ),
    child: const Text('My Button'),
  ),
)
```

## Notes

[#](#notes)

1. For information on adding the Material ripple effect to your button, see the [Add Material touch ripples](/cookbook/gestures/ripples) recipe.
2. Although this example creates a custom button, Flutter includes a handful of button implementations, such as: [`ElevatedButton`](https://pub.dev/documentation/material_ui/latest/material_ui/ElevatedButton-class.html), [`TextButton`](https://pub.dev/documentation/material_ui/latest/material_ui/TextButton-class.html), and [`CupertinoButton`](https://pub.dev/documentation/cupertino_ui/latest/cupertino_ui/CupertinoButton-class.html).

## Interactive example

[#](#interactive-example)

```
import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    const title = 'Gesture Demo';

    return const MaterialApp(
      title: title,
      home: MyHomePage(title: title),
    );
  }
}

class MyHomePage extends StatelessWidget {
  final String title;

  const MyHomePage({super.key, required this.title});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text(title)),
      body: const Center(child: MyButton()),
    );
  }
}

class MyButton extends StatelessWidget {
  const MyButton({super.key});

  @override
  Widget build(BuildContext context) {
    // The GestureDetector wraps the button.
    return GestureDetector(
      // When the child is tapped, show a snackbar.
      onTap: () {
        const snackBar = SnackBar(content: Text('Tap'));

        ScaffoldMessenger.of(context).showSnackBar(snackBar);
      },
      // The custom button
      child: Container(
        padding: const EdgeInsets.all(12),
        decoration: BoxDecoration(
          color: Colors.lightBlue,
          borderRadius: BorderRadius.circular(8),
        ),
        child: const Text('My Button'),
      ),
    );
  }
}
```

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-09-29. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/gestures/handling-taps.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/cookbook/gestures/handling-taps\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/gestures/handling-taps.md "Report an issue with this page").

----
url: https://docs.flutter.dev/testing/code-debugging
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Debug Flutter apps from code](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

This guide describes which debugging features you can enable in your code. For a full list of debugging and profiling tools, check out the [Debugging](/testing/debugging) page.

## Add logging to your application

[#](#add-logging-to-your-application)

The following list contains a few statements that you can use to log the behavior of your application. You can view your logs in DevTools' [Logging view](/tools/devtools/logging) or in your system console.

* [`print()`](https://api.flutter.dev/flutter/dart-core/print.html): Prints a `stdout` (standard output) message. Part of the `dart:io` library.

* [`stderr.method_to_invoke()`](https://api.flutter.dev/flutter/dart-io/stderr.html): Prints a `stderr` (standard error) message. Replace `method_to_invoke()` with a method supported by the `stderr` property, such as `writeln()` or `write()`. Often used in a `try...catch` block. Part of the `dart:io` library.

  dart

  ```
  stderr.writeln('print me');
  ```

* [`log()`](https://api.flutter.dev/flutter/dart-developer/log.html): Includes greater granularity and more information in the logging output. Part of the `dart:developer` library.

* [`debugPrint()`](https://api.flutter.dev/flutter/widgets/debugPrint.html): If too much output results in discarded log lines, use this to keep those lines. Will print messages in release mode unless part of a debug mode check or an assert. Part of the `foundations` library.

### Example 1

[#](#example-1)

dart

```
import 'dart:developer' as developer;
​
void main() {
  developer.log('log me', name: 'my.app.category');
​
  developer.log('log me 1', name: 'my.other.category');
  developer.log('log me 2', name: 'my.other.category');
}
```

You can also pass app data to the log call. The convention for this is to use the `error:` named parameter on the `log()` call, JSON encode the object you want to send, and pass the encoded string to the error parameter.

### Example 2

[#](#example-2)

dart

```
import 'dart:convert';
import 'dart:developer' as developer;
​
void main() {
  var myCustomObject = MyCustomObject();
​
  developer.log(
    'log me',
    name: 'my.app.category',
    error: jsonEncode(myCustomObject),
  );
}
```

DevTool's logging view interprets the JSON encoded error parameter as a data object. DevTool renders in the details view for that log entry.

## Set breakpoints

[#](#set-breakpoints)

You can set breakpoints in DevTools' [Debugger](/tools/devtools/debugger) or in the built-in debugger of your IDE.

To set programmatic breakpoints:

1. Import the `dart:developer` package into the relevant file.

2. Insert programmatic breakpoints using the `debugger()` statement. This statement takes an optional `when` argument. This boolean argument sets a break when the given condition resolves to true.

   **Example 3** illustrates this.

### Example 3

[#](#example-3)

dart

```
import 'dart:developer';
​
void someFunction(double offset) {
  debugger(when: offset > 30);
  // ...
}
```

## Debug app layers using flags

[#](#debug-app-layers-using-flags)

Each layer of the Flutter framework provides a function to dump its current state or events to the console using the `debugPrint` property.

### Print the widget tree

[#](#print-the-widget-tree)

To dump the state of the Widgets library, call the [`debugDumpApp()`](https://api.flutter.dev/flutter/widgets/debugDumpApp.html) function.

1. Open your source file.
2. Import `package:flutter/rendering.dart`.
3. Call the [`debugDumpApp()`](https://api.flutter.dev/flutter/widgets/debugDumpApp.html) function from within the `runApp()` function. You need your app in debug mode. You cannot call this function inside a `build()` method when the app is building.
4. If you haven't started your app, debug it using your IDE.
5. If you have started your app, save your source file. Hot reload re-renders your app.

#### Example 4: Call `debugDumpApp()`

[#](#example-4-call-debugdumpapp)

dart

```
import 'package:material_ui/material_ui.dart';
​
void main() {
  runApp(const MaterialApp(home: AppHome()));
}
​
class AppHome extends StatelessWidget {
  const AppHome({super.key});
​
  @override
  Widget build(BuildContext context) {
    return Material(
      child: Center(
        child: TextButton(
          onPressed: () {
            debugDumpApp();
          },
          child: const Text('Dump Widget Tree'),
        ),
      ),
    );
  }
}
```

This function recursively calls the `toStringDeep()` method starting with the root of the widget tree. It returns a "flattened" tree.

**Example 4** produces the following widget tree. It includes:

* All the widgets projected through their various build functions.

* Many widgets that don't appear in your app's source. The framework's widgets' build functions insert them during the build.

  The following tree, for example, shows [`_InkFeatures`](https://pub.dev/documentation/material_ui/latest/material_ui/InkFeature-class.html). That class implements part of the [`Material`](https://pub.dev/documentation/material_ui/latest/material_ui/Material-class.html) widget. It doesn't appear anywhere in the code in **Example 4**.

**Expand to view the widget tree for Example 4**

```
flutter: WidgetsFlutterBinding - DEBUG MODE
flutter: [root](renderObject: RenderView#06beb)
flutter: └View-[GlobalObjectKey FlutterView#7971c]
flutter:  └_ViewScope
flutter:   └_MediaQueryFromView(state: _MediaQueryFromViewState#d790c)
flutter:    └MediaQuery(MediaQueryData(size: Size(800.0, 600.0), devicePixelRatio: 1.0, textScaleFactor: 1.0, platformBrightness: Brightness.dark, padding: EdgeInsets.zero, viewPadding: EdgeInsets.zero, viewInsets: EdgeInsets.zero, systemGestureInsets: EdgeInsets.zero, alwaysUse24HourFormat: false, accessibleNavigation: false, highContrast: false, disableAnimations: false, invertColors: false, boldText: false, navigationMode: traditional, gestureSettings: DeviceGestureSettings(touchSlop: null), displayFeatures: []))
flutter:     └MaterialApp(state: _MaterialAppState#27fa9)
flutter:      └ScrollConfiguration(behavior: MaterialScrollBehavior)
flutter:       └HeroControllerScope
flutter:        └Focus(state: _FocusState#d7f97)
flutter:         └_FocusInheritedScope
flutter:          └Semantics(container: false, properties: SemanticsProperties, tooltip: null, renderObject: RenderSemanticsAnnotations#a6464)
flutter:           └WidgetsApp-[GlobalObjectKey _MaterialAppState#27fa9](state: _WidgetsAppState#b5b17)
flutter:            └RootRestorationScope(state: _RootRestorationScopeState#6b028)
flutter:             └UnmanagedRestorationScope
flutter:              └RestorationScope(dependencies: [UnmanagedRestorationScope], state: _RestorationScopeState#d1369)
flutter:               └UnmanagedRestorationScope
flutter:                └SharedAppData(state: _SharedAppDataState#95e82)
flutter:                 └_SharedAppModel
flutter:                  └Shortcuts(shortcuts: <Default WidgetsApp Shortcuts>, state: _ShortcutsState#272dc)
flutter:                   └Focus(debugLabel: "Shortcuts", dependencies: [_FocusInheritedScope], state: _FocusState#a3300)
flutter:                    └_FocusInheritedScope
flutter:                     └Semantics(container: false, properties: SemanticsProperties, tooltip: null, renderObject: RenderSemanticsAnnotations#db110)
flutter:                      └DefaultTextEditingShortcuts
flutter:                       └Shortcuts(shortcuts: <Default Text Editing Shortcuts>, state: _ShortcutsState#1d796)
flutter:                        └Focus(debugLabel: "Shortcuts", dependencies: [_FocusInheritedScope], state: _FocusState#0081b)
flutter:                         └_FocusInheritedScope
flutter:                          └Semantics(container: false, properties: SemanticsProperties, tooltip: null, renderObject: RenderSemanticsAnnotations#0d70e)
flutter:                           └Shortcuts(shortcuts: <Web Disabling Text Editing Shortcuts>, state: _ShortcutsState#56bac)
flutter:                            └Focus(debugLabel: "Shortcuts", dependencies: [_FocusInheritedScope], state: _FocusState#3152e)
flutter:                             └_FocusInheritedScope
flutter:                              └Semantics(container: false, properties: SemanticsProperties, tooltip: null, renderObject: RenderSemanticsAnnotations#b7eaf)
flutter:                               └Actions(dispatcher: null, actions: {DoNothingIntent: DoNothingAction#0fda1, DoNothingAndStopPropagationIntent: DoNothingAction#17f30, RequestFocusIntent: RequestFocusAction#10bd0, NextFocusIntent: NextFocusAction#60317, PreviousFocusIntent: PreviousFocusAction#2a933, DirectionalFocusIntent: DirectionalFocusAction#a6922, ScrollIntent: _OverridableContextAction<ScrollIntent>#964fe(defaultAction: ScrollAction#ffb50), PrioritizedIntents: PrioritizedAction#be0e2, VoidCallbackIntent: VoidCallbackAction#805fa}, state: _ActionsState#bbd25)
flutter:                                └_ActionsScope
flutter:                                 └FocusTraversalGroup(policy: ReadingOrderTraversalPolicy#f1e76, state: _FocusTraversalGroupState#0c200)
flutter:                                  └Focus(debugLabel: "FocusTraversalGroup", focusNode: _FocusTraversalGroupNode#ffcad(FocusTraversalGroup [IN FOCUS PATH]), dependencies: [_FocusInheritedScope], state: _FocusState#c7dc2)
flutter:                                   └_FocusInheritedScope
flutter:                                    └TapRegionSurface(renderObject: RenderTapRegionSurface#17aba)
flutter:                                     └ShortcutRegistrar(state: _ShortcutRegistrarState#44954)
flutter:                                      └_ShortcutRegistrarScope
flutter:                                       └Shortcuts(manager: ShortcutManager#eb38c(shortcuts: {}), shortcuts: {}, state: _ShortcutsState#f85ac)
flutter:                                        └Focus(debugLabel: "Shortcuts", dependencies: [_FocusInheritedScope], state: _FocusState#8c1a7)
flutter:                                         └_FocusInheritedScope
flutter:                                          └Semantics(container: false, properties: SemanticsProperties, tooltip: null, renderObject: RenderSemanticsAnnotations#1fc98)
flutter:                                           └Localizations(locale: en_US, delegates: [DefaultMaterialLocalizations.delegate(en_US), DefaultCupertinoLocalizations.delegate(en_US), DefaultWidgetsLocalizations.delegate(en_US)], state: _LocalizationsState#ae3a0)
flutter:                                            └Semantics(container: false, properties: SemanticsProperties, tooltip: null, textDirection: ltr, renderObject: RenderSemanticsAnnotations#8776e)
flutter:                                             └_LocalizationsScope-[GlobalKey#61ca6]
flutter:                                              └Directionality(textDirection: ltr)
flutter:                                               └Title(color: Color(0xff2196f3))
flutter:                                                └CheckedModeBanner("DEBUG")
flutter:                                                 └Banner("DEBUG", textDirection: ltr, location: topEnd, Color(0xa0b71c1c), text inherit: true, text color: Color(0xffffffff), text size: 10.2, text weight: 900, text height: 1.0x, dependencies: [Directionality])
flutter:                                                  └CustomPaint(renderObject: RenderCustomPaint#c014d)
flutter:                                                   └DefaultTextStyle(debugLabel: fallback style; consider putting your text in a Material, inherit: true, color: Color(0xd0ff0000), family: monospace, size: 48.0, weight: 900, decoration: double Color(0xffffff00) TextDecoration.underline, softWrap: wrapping at box width, overflow: clip)
flutter:                                                    └Builder(dependencies: [MediaQuery])
flutter:                                                     └ScaffoldMessenger(dependencies: [MediaQuery], state: ScaffoldMessengerState#5b36e)
flutter:                                                      └_ScaffoldMessengerScope
flutter:                                                       └DefaultSelectionStyle
flutter:                                                        └AnimatedTheme(duration: 200ms, state: _AnimatedThemeState#cd149(ticker inactive, ThemeDataTween(ThemeData#ef3b2 → ThemeData#ef3b2)))
flutter:                                                         └Theme(ThemeData#ef3b2, dependencies: [DefaultSelectionStyle])
flutter:                                                          └_InheritedTheme
flutter:                                                           └CupertinoTheme(brightness: light, primaryColor: MaterialColor(primary value: Color(0xff2196f3)), primaryContrastingColor: Color(0xffffffff), scaffoldBackgroundColor: Color(0xfffafafa), actionTextStyle: TextStyle(inherit: false, color: MaterialColor(primary value: Color(0xff2196f3)), family: .SF Pro Text, size: 17.0, letterSpacing: -0.4, decoration: TextDecoration.none), navActionTextStyle: TextStyle(inherit: false, color: MaterialColor(primary value: Color(0xff2196f3)), family: .SF Pro Text, size: 17.0, letterSpacing: -0.4, decoration: TextDecoration.none))
flutter:                                                            └_InheritedCupertinoTheme
flutter:                                                             └IconTheme(color: MaterialColor(primary value: Color(0xff2196f3)))
flutter:                                                              └IconTheme(color: Color(0xdd000000))
flutter:                                                               └DefaultSelectionStyle
flutter:                                                                └FocusScope(debugLabel: "Navigator Scope", AUTOFOCUS, dependencies: [_FocusInheritedScope], state: _FocusScopeState#acbd8)
flutter:                                                                 └Semantics(container: false, properties: SemanticsProperties, tooltip: null, renderObject: RenderSemanticsAnnotations#ab3f0)
flutter:                                                                  └_FocusInheritedScope
flutter:                                                                   └Navigator-[GlobalObjectKey<NavigatorState> _WidgetsAppState#b5b17](dependencies: [HeroControllerScope, UnmanagedRestorationScope], state: NavigatorState#1395a(tickers: tracking 1 ticker))
flutter:                                                                    └HeroControllerScope
flutter:                                                                     └Listener(listeners: [down, up, cancel], behavior: deferToChild, renderObject: RenderPointerListener#34172)
flutter:                                                                      └AbsorbPointer(absorbing: false, renderObject: RenderAbsorbPointer#f8711)
flutter:                                                                       └FocusTraversalGroup(policy: ReadingOrderTraversalPolicy#f1e76, state: _FocusTraversalGroupState#8d61a)
flutter:                                                                        └Focus(debugLabel: "FocusTraversalGroup", focusNode: _FocusTraversalGroupNode#dd2b1(FocusTraversalGroup [IN FOCUS PATH]), dependencies: [_FocusInheritedScope], state: _FocusState#0bb03)
flutter:                                                                         └_FocusInheritedScope
flutter:                                                                          └Focus(debugLabel: "Navigator", AUTOFOCUS, focusNode: FocusNode#a3309(Navigator [IN FOCUS PATH]), dependencies: [_FocusInheritedScope], state: _FocusState#d3d07)
flutter:                                                                           └_FocusInheritedScope
flutter:                                                                            └UnmanagedRestorationScope
flutter:                                                                             └Overlay-[LabeledGlobalKey<OverlayState>#5485a](state: OverlayState#5bd52(entries: [OverlayEntry#fc947(opaque: true; maintainState: false), OverlayEntry#05a32(opaque: false; maintainState: true)]))
flutter:                                                                              └_Theater(skipCount: 0, dependencies: [Directionality], renderObject: _RenderTheater#e86c3)
flutter:                                                                               ├_OverlayEntryWidget-[LabeledGlobalKey<_OverlayEntryWidgetState>#1b37e](state: _OverlayEntryWidgetState#06ab0)
flutter:                                                                               │└TickerMode(state: _TickerModeState#0b4ac(requested mode: enabled))
flutter:                                                                               │ └_EffectiveTickerMode(effective mode: enabled)
flutter:                                                                               │  └_RenderTheaterMarker
flutter:                                                                               │   └IgnorePointer(ignoring: false, renderObject: RenderIgnorePointer#34c66)
flutter:                                                                               │    └ModalBarrier
flutter:                                                                               │     └BlockSemantics(blocking: true, renderObject: RenderBlockSemantics#97799)
flutter:                                                                               │      └ExcludeSemantics(excluding: true, renderObject: RenderExcludeSemantics#8c4ce)
flutter:                                                                               │       └_ModalBarrierGestureDetector
flutter:                                                                               │        └RawGestureDetector(state: RawGestureDetectorState#556f6(gestures: [any tap], behavior: opaque))
flutter:                                                                               │         └_GestureSemantics(renderObject: RenderSemanticsGestureHandler#616f1)
flutter:                                                                               │          └Listener(listeners: [down, panZoomStart], behavior: opaque, renderObject: RenderPointerListener#c2b89)
flutter:                                                                               │           └Semantics(container: false, properties: SemanticsProperties, tooltip: null, renderObject: RenderSemanticsAnnotations#c3b31)
flutter:                                                                               │            └MouseRegion(listeners: <none>, cursor: SystemMouseCursor(basic), renderObject: RenderMouseRegion#53cdb)
flutter:                                                                               │             └ConstrainedBox(BoxConstraints(biggest), renderObject: RenderConstrainedBox#faa51)
flutter:                                                                               └_OverlayEntryWidget-[LabeledGlobalKey<_OverlayEntryWidgetState>#bc0aa](state: _OverlayEntryWidgetState#cbf35)
flutter:                                                                                └TickerMode(state: _TickerModeState#23e73(requested mode: enabled))
flutter:                                                                                 └_EffectiveTickerMode(effective mode: enabled)
flutter:                                                                                  └_RenderTheaterMarker
flutter:                                                                                   └Semantics(container: false, properties: SemanticsProperties, tooltip: null, sortKey: OrdinalSortKey#135f4(order: 0.0), renderObject: RenderSemanticsAnnotations#5565e)
flutter:                                                                                    └_ModalScope<dynamic>-[LabeledGlobalKey<_ModalScopeState<dynamic>>#4fe82](state: _ModalScopeState<dynamic>#4da7d)
flutter:                                                                                     └AnimatedBuilder(listenable: ValueNotifier<String?>#d87c6(null), state: _AnimatedState#dde81)
flutter:                                                                                      └RestorationScope(dependencies: [UnmanagedRestorationScope], state: _RestorationScopeState#78c51)
flutter:                                                                                       └UnmanagedRestorationScope
flutter:                                                                                        └_ModalScopeStatus(active)
flutter:                                                                                         └Offstage(offstage: false, renderObject: RenderOffstage#5e498)
flutter:                                                                                          └PageStorage
flutter:                                                                                           └Builder
flutter:                                                                                            └Actions(dispatcher: null, actions: {DismissIntent: _DismissModalAction#6279e}, state: _ActionsState#48019)
flutter:                                                                                             └_ActionsScope
flutter:                                                                                              └PrimaryScrollController(ScrollController#6a546(no clients))
flutter:                                                                                               └FocusScope(debugLabel: "_ModalScopeState<dynamic> Focus Scope", focusNode: FocusScopeNode#0e2af(_ModalScopeState<dynamic> Focus Scope [PRIMARY FOCUS]), dependencies: [_FocusInheritedScope], state: _FocusScopeState#0bac4)
flutter:                                                                                                └Semantics(container: false, properties: SemanticsProperties, tooltip: null, renderObject: RenderSemanticsAnnotations#44b4e)
flutter:                                                                                                 └_FocusInheritedScope
flutter:                                                                                                  └RepaintBoundary(renderObject: RenderRepaintBoundary#38f41)
flutter:                                                                                                   └AnimatedBuilder(listenable: Listenable.merge([AnimationController#9d623(⏭ 1.000; paused; for MaterialPageRoute<dynamic>(/))➩ProxyAnimation, kAlwaysDismissedAnimation➩ProxyAnimation➩ProxyAnimation]), dependencies: [_InheritedTheme, _LocalizationsScope-[GlobalKey#61ca6]], state: _AnimatedState#47725)
flutter:                                                                                                    └CupertinoPageTransition(dependencies: [Directionality])
flutter:                                                                                                     └SlideTransition(listenable: kAlwaysDismissedAnimation➩ProxyAnimation➩ProxyAnimation➩Cubic(0.35, 0.91, 0.33, 0.97)ₒₙ/Cubic(0.67, 0.03, 0.65, 0.09)➩Tween<Offset>(Offset(0.0, 0.0) → Offset(-0.3, 0.0))➩Offset(0.0, 0.0), state: _AnimatedState#b6162)
flutter:                                                                                                      └FractionalTranslation(renderObject: RenderFractionalTranslation#fb461)
flutter:                                                                                                       └SlideTransition(listenable: AnimationController#9d623(⏭ 1.000; paused; for MaterialPageRoute<dynamic>(/))➩ProxyAnimation➩ThreePointCubic ₒₙ/FlippedCurve(ThreePointCubic )➩Tween<Offset>(Offset(1.0, 0.0) → Offset(0.0, 0.0))➩Offset(0.0, 0.0), state: _AnimatedState#834bf)
flutter:                                                                                                        └FractionalTranslation(renderObject: RenderFractionalTranslation#73ea4)
flutter:                                                                                                         └DecoratedBoxTransition(listenable: AnimationController#9d623(⏭ 1.000; paused; for MaterialPageRoute<dynamic>(/))➩ProxyAnimation➩Cubic(0.35, 0.91, 0.33, 0.97)➩DecorationTween(_CupertinoEdgeShadowDecoration(colors: null) → _CupertinoEdgeShadowDecoration(colors: [Color(0x04000000), Color(0x00000000)]))➩_CupertinoEdgeShadowDecoration(colors: [Color(0x04000000), Color(0x00000000)]), state: _AnimatedState#a7fca)
flutter:                                                                                                          └DecoratedBox(bg: _CupertinoEdgeShadowDecoration(colors: [Color(0x04000000), Color(0x00000000)]), dependencies: [Directionality, MediaQuery, _LocalizationsScope-[GlobalKey#61ca6]], renderObject: RenderDecoratedBox#9965c)
flutter:                                                                                                           └_CupertinoBackGestureDetector<dynamic>(dependencies: [Directionality, MediaQuery], state: _CupertinoBackGestureDetectorState<dynamic>#ab8cd)
flutter:                                                                                                            └Stack(alignment: AlignmentDirectional.topStart, fit: passthrough, dependencies: [Directionality], renderObject: RenderStack#b2b7c)
flutter:                                                                                                             ├AnimatedBuilder(listenable: ValueNotifier<bool>#1a88e(false), state: _AnimatedState#6e33c)
flutter:                                                                                                             │└IgnorePointer(ignoring: false, renderObject: RenderIgnorePointer#2b763)
flutter:                                                                                                             │ └RepaintBoundary-[GlobalKey#628f4](renderObject: RenderRepaintBoundary#5a53b)
flutter:                                                                                                             │  └Builder
flutter:                                                                                                             │   └Semantics(container: false, properties: SemanticsProperties, tooltip: null, renderObject: RenderSemanticsAnnotations#f8795)
flutter:                                                                                                             │    └AppHome
flutter:                                                                                                             │     └Material(type: canvas, dependencies: [_InheritedTheme, _LocalizationsScope-[GlobalKey#61ca6]], state: _MaterialState#7d183)
flutter:                                                                                                             │      └AnimatedPhysicalModel(duration: 200ms, shape: rectangle, borderRadius: BorderRadius.zero, elevation: 0.0, color: Color(0xfffafafa), animateColor: false, shadowColor: Color(0xff000000), animateShadowColor: true, state: _AnimatedPhysicalModelState#d479e(ticker inactive))
flutter:                                                                                                             │       └PhysicalModel(shape: rectangle, borderRadius: BorderRadius.zero, elevation: 0.0, color: Color(0xfffafafa), shadowColor: Color(0xff000000), renderObject: RenderPhysicalModel#c60b5)
flutter:                                                                                                             │        └NotificationListener<LayoutChangedNotification>
flutter:                                                                                                             │         └_InkFeatures-[GlobalKey#e9da0 ink renderer](renderObject: _RenderInkFeatures#d8e6d)
flutter:                                                                                                             │          └AnimatedDefaultTextStyle(duration: 200ms, debugLabel: (englishLike bodyMedium 2014).merge(blackRedwoodCity bodyMedium), inherit: false, color: Color(0xdd000000), family: .AppleSystemUIFont, size: 14.0, weight: 400, baseline: alphabetic, decoration: TextDecoration.none, softWrap: wrapping at box width, overflow: clip, state: _AnimatedDefaultTextStyleState#12f43(ticker inactive))
flutter:                                                                                                             │           └DefaultTextStyle(debugLabel: (englishLike bodyMedium 2014).merge(blackRedwoodCity bodyMedium), inherit: false, color: Color(0xdd000000), family: .AppleSystemUIFont, size: 14.0, weight: 400, baseline: alphabetic, decoration: TextDecoration.none, softWrap: wrapping at box width, overflow: clip)
flutter:                                                                                                             │            └Center(alignment: Alignment.center, dependencies: [Directionality], renderObject: RenderPositionedBox#b088f)
flutter:                                                                                                             │             └TextButton(dirty, dependencies: [MediaQuery, _InheritedTheme, _LocalizationsScope-[GlobalKey#61ca6]], state: _ButtonStyleState#687c9)
flutter:                                                                                                             │              └Semantics(container: true, properties: SemanticsProperties, tooltip: null, renderObject: RenderSemanticsAnnotations#ca411 relayoutBoundary=up1)
flutter:                                                                                                             │               └_InputPadding(renderObject: _RenderInputPadding#60ede relayoutBoundary=up2)
flutter:                                                                                                             │                └ConstrainedBox(BoxConstraints(56.0<=w<=Infinity, 28.0<=h<=Infinity), renderObject: RenderConstrainedBox#34800 relayoutBoundary=up3)
flutter:                                                                                                             │                 └Material(type: button, color: Color(0x00000000), shadowColor: Color(0xff000000), textStyle.debugLabel: ((englishLike labelLarge 2014).merge(blackRedwoodCity labelLarge)).copyWith, textStyle.inherit: false, textStyle.color: MaterialColor(primary value: Color(0xff2196f3)), textStyle.family: .AppleSystemUIFont, textStyle.size: 14.0, textStyle.weight: 500, textStyle.baseline: alphabetic, textStyle.decoration: TextDecoration.none, shape: RoundedRectangleBorder(BorderSide(width: 0.0, style: none), BorderRadius.circular(4.0)), dependencies: [_InheritedTheme, _LocalizationsScope-[GlobalKey#61ca6]], state: _MaterialState#50a4d(tickers: tracking 5 tickers))
flutter:                                                                                                             │                  └_MaterialInterior(duration: 200ms, shape: RoundedRectangleBorder(BorderSide(width: 0.0, style: none), BorderRadius.circular(4.0)), elevation: 0.0, color: Color(0x00000000), shadowColor: Color(0xff000000), dependencies: [Directionality, _InheritedTheme, _LocalizationsScope-[GlobalKey#61ca6]], state: _MaterialInteriorState#d296d(ticker inactive))
flutter:                                                                                                             │                   └PhysicalShape(clipper: ShapeBorderClipper, elevation: 0.0, color: Color(0x00000000), shadowColor: Color(0xff000000), renderObject: RenderPhysicalShape#43df6 relayoutBoundary=up4)
flutter:                                                                                                             │                    └_ShapeBorderPaint(dependencies: [Directionality])
flutter:                                                                                                             │                     └CustomPaint(renderObject: RenderCustomPaint#c1a3c relayoutBoundary=up5)
flutter:                                                                                                             │                      └NotificationListener<LayoutChangedNotification>
flutter:                                                                                                             │                       └_InkFeatures-[GlobalKey#625bc ink renderer](renderObject: _RenderInkFeatures#54439 relayoutBoundary=up6)
flutter:                                                                                                             │                        └AnimatedDefaultTextStyle(duration: 200ms, debugLabel: ((englishLike labelLarge 2014).merge(blackRedwoodCity labelLarge)).copyWith, inherit: false, color: MaterialColor(primary value: Color(0xff2196f3)), family: .AppleSystemUIFont, size: 14.0, weight: 500, baseline: alphabetic, decoration: TextDecoration.none, softWrap: wrapping at box width, overflow: clip, state: _AnimatedDefaultTextStyleState#2f29d(ticker inactive))
flutter:                                                                                                             │                         └DefaultTextStyle(debugLabel: ((englishLike labelLarge 2014).merge(blackRedwoodCity labelLarge)).copyWith, inherit: false, color: MaterialColor(primary value: Color(0xff2196f3)), family: .AppleSystemUIFont, size: 14.0, weight: 500, baseline: alphabetic, decoration: TextDecoration.none, softWrap: wrapping at box width, overflow: clip)
flutter:                                                                                                             │                          └InkWell
flutter:                                                                                                             │                           └_InkResponseStateWidget(gestures: [tap], mouseCursor: ButtonStyleButton_MouseCursor, clipped to BoxShape.rectangle, dirty, dependencies: [Directionality, MediaQuery, _InheritedTheme, _LocalizationsScope-[GlobalKey#61ca6]], state: _InkResponseState#0b11d)
flutter:                                                                                                             │                            └_ParentInkResponseProvider
flutter:                                                                                                             │                             └Actions(dispatcher: null, actions: {ActivateIntent: CallbackAction<ActivateIntent>#018db, ButtonActivateIntent: CallbackAction<ButtonActivateIntent>#ef87a}, state: _ActionsState#a5eab)
flutter:                                                                                                             │                              └_ActionsScope
flutter:                                                                                                             │                               └Focus(dependencies: [_FocusInheritedScope], state: _FocusState#5a9de)
flutter:                                                                                                             │                                └_FocusInheritedScope
flutter:                                                                                                             │                                 └Semantics(container: false, properties: SemanticsProperties, tooltip: null, renderObject: RenderSemanticsAnnotations#8ac3e relayoutBoundary=up7)
flutter:                                                                                                             │                                  └MouseRegion(listeners: [enter, exit], cursor: SystemMouseCursor(click), renderObject: RenderMouseRegion#13d4e relayoutBoundary=up8)
flutter:                                                                                                             │                                   └Builder(dependencies: [DefaultSelectionStyle])
flutter:                                                                                                             │                                    └DefaultSelectionStyle
flutter:                                                                                                             │                                     └Semantics(container: false, properties: SemanticsProperties, tooltip: null, renderObject: RenderSemanticsAnnotations#d99cc relayoutBoundary=up9)
flutter:                                                                                                             │                                      └GestureDetector(startBehavior: start, dependencies: [MediaQuery])
flutter:                                                                                                             │                                       └RawGestureDetector(state: RawGestureDetectorState#b8d93(gestures: [tap], excludeFromSemantics: true, behavior: opaque))
flutter:                                                                                                             │                                        └Listener(listeners: [down, panZoomStart], behavior: opaque, renderObject: RenderPointerListener#a4c3b relayoutBoundary=up10)
flutter:                                                                                                             │                                         └Builder(dependencies: [IconTheme])
flutter:                                                                                                             │                                          └IconTheme(color: MaterialColor(primary value: Color(0xff2196f3)))
flutter:                                                                                                             │                                           └Padding(padding: EdgeInsets(8.0, 0.0, 8.0, 0.0), dependencies: [Directionality], renderObject: RenderPadding#18a87 relayoutBoundary=up11)
flutter:                                                                                                             │                                            └Align(alignment: Alignment.center, widthFactor: 1.0, heightFactor: 1.0, dependencies: [Directionality], renderObject: RenderPositionedBox#fb8a8 relayoutBoundary=up12)
flutter:                                                                                                             │                                             └Text("Dump Widget Tree", dependencies: [DefaultSelectionStyle, DefaultTextStyle, MediaQuery])
flutter:                                                                                                             │                                              └RichText(softWrap: wrapping at box width, maxLines: unlimited, text: "Dump Widget Tree", dependencies: [Directionality, _LocalizationsScope-[GlobalKey#61ca6]], renderObject: RenderParagraph#d15aa relayoutBoundary=up13)
flutter:                                                                                                             └PositionedDirectional(dependencies: [Directionality])
flutter:                                                                                                              └Positioned(left: 0.0, top: 0.0, bottom: 0.0, width: 20.0)
flutter:                                                                                                               └Listener(listeners: [down], behavior: translucent, renderObject: RenderPointerListener#d884c)
flutter:
flutter:
```

When the button changes from being pressed to being released, this invokes the `debugDumpApp()` function. It also coincides with the [`TextButton`](https://pub.dev/documentation/material_ui/latest/material_ui/TextButton-class.html) object calling [`setState()`](https://api.flutter.dev/flutter/widgets/State/setState.html) and thus marking itself dirty. This explains why a Flutter marks a specific object as "dirty". When you review the widget tree, look for a line that resembles the following:

```
└TextButton(dirty, dependencies: [MediaQuery, _InheritedTheme, _LocalizationsScope-[GlobalKey#5880d]], state: _ButtonStyleState#ab76e)
```

If you write your own widgets, override the [`debugFillProperties()`](https://api.flutter.dev/flutter/widgets/Widget/debugFillProperties.html) method to add information. Add [DiagnosticsProperty](https://api.flutter.dev/flutter/foundation/DiagnosticsProperty-class.html) objects to the method's argument and call the superclass method. The `toString` method uses this function to fill in the widget's description.

### Print the render tree

[#](#print-the-render-tree)

When debugging a layout issue, the Widgets layer's tree might lack detail. The next level of debugging might require a render tree. To dump the render tree:

1. Open your source file.
2. Call the [`debugDumpRenderTree()`](https://api.flutter.dev/flutter/rendering/debugDumpRenderTree.html) function. You can call this any time except during a layout or paint phase. Consider calling it from a [frame callback](https://api.flutter.dev/flutter/scheduler/SchedulerBinding/addPersistentFrameCallback.html) or an event handler.
3. If you haven't started your app, debug it using your IDE.
4. If you have started your app, save your source file. Hot reload re-renders your app.

#### Example 5: Call `debugDumpRenderTree()`

[#](#example-5-call-debugdumprendertree)

dart

```
import 'package:material_ui/material_ui.dart';
​
void main() {
  runApp(const MaterialApp(home: AppHome()));
}
​
class AppHome extends StatelessWidget {
  const AppHome({super.key});
​
  @override
  Widget build(BuildContext context) {
    return Material(
      child: Center(
        child: TextButton(
          onPressed: () {
            debugDumpRenderTree();
          },
          child: const Text('Dump Render Tree'),
        ),
      ),
    );
  }
}
```

When debugging layout issues, look at the `size` and `constraints` fields. The constraints flow down the tree and the sizes flow back up.

**Expand to view the render tree for Example 5**

```
flutter: RenderView#02c80
flutter:  │ debug mode enabled - macos
flutter:  │ view size: Size(800.0, 600.0) (in physical pixels)
flutter:  │ device pixel ratio: 1.0 (physical pixels per logical pixel)
flutter:  │ configuration: Size(800.0, 600.0) at 1.0x (in logical pixels)
flutter:  │
flutter:  └─child: RenderSemanticsAnnotations#fe6b5
flutter:    │ needs compositing
flutter:    │ creator: Semantics ← _FocusInheritedScope ← Focus ←
flutter:    │   HeroControllerScope ← ScrollConfiguration ← MaterialApp ←
flutter:    │   MediaQuery ← _MediaQueryFromView ← _ViewScope ←
flutter:    │   View-[GlobalObjectKey FlutterView#6cffa] ← [root]
flutter:    │ parentData: <none>
flutter:    │ constraints: BoxConstraints(w=800.0, h=600.0)
flutter:    │ size: Size(800.0, 600.0)
flutter:    │
flutter:    └─child: RenderSemanticsAnnotations#6edef
flutter:      │ needs compositing
flutter:      │ creator: Semantics ← _FocusInheritedScope ← Focus ← Shortcuts ←
flutter:      │   _SharedAppModel ← SharedAppData ← UnmanagedRestorationScope ←
flutter:      │   RestorationScope ← UnmanagedRestorationScope ←
flutter:      │   RootRestorationScope ← WidgetsApp-[GlobalObjectKey
flutter:      │   _MaterialAppState#5c303] ← Semantics ← ⋯
flutter:      │ parentData: <none> (can use size)
flutter:      │ constraints: BoxConstraints(w=800.0, h=600.0)
flutter:      │ size: Size(800.0, 600.0)
flutter:      │
flutter:      └─child: RenderSemanticsAnnotations#e8ce8
flutter:        │ needs compositing
flutter:        │ creator: Semantics ← _FocusInheritedScope ← Focus ← Shortcuts ←
flutter:        │   DefaultTextEditingShortcuts ← Semantics ← _FocusInheritedScope
flutter:        │   ← Focus ← Shortcuts ← _SharedAppModel ← SharedAppData ←
flutter:        │   UnmanagedRestorationScope ← ⋯
flutter:        │ parentData: <none> (can use size)
flutter:        │ constraints: BoxConstraints(w=800.0, h=600.0)
flutter:        │ size: Size(800.0, 600.0)
flutter:        │
flutter:        └─child: RenderSemanticsAnnotations#fc545
flutter:          │ needs compositing
flutter:          │ creator: Semantics ← _FocusInheritedScope ← Focus ← Shortcuts ←
flutter:          │   Semantics ← _FocusInheritedScope ← Focus ← Shortcuts ←
flutter:          │   DefaultTextEditingShortcuts ← Semantics ← _FocusInheritedScope
flutter:          │   ← Focus ← ⋯
flutter:          │ parentData: <none> (can use size)
flutter:          │ constraints: BoxConstraints(w=800.0, h=600.0)
flutter:          │ size: Size(800.0, 600.0)
flutter:          │
flutter:          └─child: RenderTapRegionSurface#ff857
flutter:            │ needs compositing
flutter:            │ creator: TapRegionSurface ← _FocusInheritedScope ← Focus ←
flutter:            │   FocusTraversalGroup ← _ActionsScope ← Actions ← Semantics ←
flutter:            │   _FocusInheritedScope ← Focus ← Shortcuts ← Semantics ←
flutter:            │   _FocusInheritedScope ← ⋯
flutter:            │ parentData: <none> (can use size)
flutter:            │ constraints: BoxConstraints(w=800.0, h=600.0)
flutter:            │ size: Size(800.0, 600.0)
flutter:            │ behavior: deferToChild
flutter:            │
flutter:            └─child: RenderSemanticsAnnotations#fe316
flutter:              │ needs compositing
flutter:              │ creator: Semantics ← _FocusInheritedScope ← Focus ← Shortcuts ←
flutter:              │   _ShortcutRegistrarScope ← ShortcutRegistrar ← TapRegionSurface
flutter:              │   ← _FocusInheritedScope ← Focus ← FocusTraversalGroup ←
flutter:              │   _ActionsScope ← Actions ← ⋯
flutter:              │ parentData: <none> (can use size)
flutter:              │ constraints: BoxConstraints(w=800.0, h=600.0)
flutter:              │ size: Size(800.0, 600.0)
flutter:              │
flutter:              └─child: RenderSemanticsAnnotations#fa55c
flutter:                │ needs compositing
flutter:                │ creator: Semantics ← Localizations ← Semantics ←
flutter:                │   _FocusInheritedScope ← Focus ← Shortcuts ←
flutter:                │   _ShortcutRegistrarScope ← ShortcutRegistrar ← TapRegionSurface
flutter:                │   ← _FocusInheritedScope ← Focus ← FocusTraversalGroup ← ⋯
flutter:                │ parentData: <none> (can use size)
flutter:                │ constraints: BoxConstraints(w=800.0, h=600.0)
flutter:                │ size: Size(800.0, 600.0)
flutter:                │
flutter:                └─child: RenderCustomPaint#4b256
flutter:                  │ needs compositing
flutter:                  │ creator: CustomPaint ← Banner ← CheckedModeBanner ← Title ←
flutter:                  │   Directionality ← _LocalizationsScope-[GlobalKey#4a3aa] ←
flutter:                  │   Semantics ← Localizations ← Semantics ← _FocusInheritedScope ←
flutter:                  │   Focus ← Shortcuts ← ⋯
flutter:                  │ parentData: <none> (can use size)
flutter:                  │ constraints: BoxConstraints(w=800.0, h=600.0)
flutter:                  │ size: Size(800.0, 600.0)
flutter:                  │ painter: null
flutter:                  │ foregroundPainter: BannerPainter#1bfd7(Instance of
flutter:                  │   '_SystemFontsNotifier')
flutter:                  │
flutter:                  └─child: RenderSemanticsAnnotations#f470f
flutter:                    │ needs compositing
flutter:                    │ creator: Semantics ← FocusScope ← DefaultSelectionStyle ←
flutter:                    │   IconTheme ← IconTheme ← _InheritedCupertinoTheme ←
flutter:                    │   CupertinoTheme ← _InheritedTheme ← Theme ← AnimatedTheme ←
flutter:                    │   DefaultSelectionStyle ← _ScaffoldMessengerScope ← ⋯
flutter:                    │ parentData: <none> (can use size)
flutter:                    │ constraints: BoxConstraints(w=800.0, h=600.0)
flutter:                    │ size: Size(800.0, 600.0)
flutter:                    │
flutter:                    └─child: RenderPointerListener#f59c8
flutter:                      │ needs compositing
flutter:                      │ creator: Listener ← HeroControllerScope ←
flutter:                      │   Navigator-[GlobalObjectKey<NavigatorState>
flutter:                      │   _WidgetsAppState#0d73a] ← _FocusInheritedScope ← Semantics ←
flutter:                      │   FocusScope ← DefaultSelectionStyle ← IconTheme ← IconTheme ←
flutter:                      │   _InheritedCupertinoTheme ← CupertinoTheme ← _InheritedTheme ← ⋯
flutter:                      │ parentData: <none> (can use size)
flutter:                      │ constraints: BoxConstraints(w=800.0, h=600.0)
flutter:                      │ size: Size(800.0, 600.0)
flutter:                      │ behavior: deferToChild
flutter:                      │ listeners: down, up, cancel
flutter:                      │
flutter:                      └─child: RenderAbsorbPointer#c91bd
flutter:                        │ needs compositing
flutter:                        │ creator: AbsorbPointer ← Listener ← HeroControllerScope ←
flutter:                        │   Navigator-[GlobalObjectKey<NavigatorState>
flutter:                        │   _WidgetsAppState#0d73a] ← _FocusInheritedScope ← Semantics ←
flutter:                        │   FocusScope ← DefaultSelectionStyle ← IconTheme ← IconTheme ←
flutter:                        │   _InheritedCupertinoTheme ← CupertinoTheme ← ⋯
flutter:                        │ parentData: <none> (can use size)
flutter:                        │ constraints: BoxConstraints(w=800.0, h=600.0)
flutter:                        │ size: Size(800.0, 600.0)
flutter:                        │ absorbing: false
flutter:                        │ ignoringSemantics: null
flutter:                        │
flutter:                        └─child: _RenderTheater#07897
flutter:                          │ needs compositing
flutter:                          │ creator: _Theater ←
flutter:                          │   Overlay-[LabeledGlobalKey<OverlayState>#49a93] ←
flutter:                          │   UnmanagedRestorationScope ← _FocusInheritedScope ← Focus ←
flutter:                          │   _FocusInheritedScope ← Focus ← FocusTraversalGroup ←
flutter:                          │   AbsorbPointer ← Listener ← HeroControllerScope ←
flutter:                          │   Navigator-[GlobalObjectKey<NavigatorState>
flutter:                          │   _WidgetsAppState#0d73a] ← ⋯
flutter:                          │ parentData: <none> (can use size)
flutter:                          │ constraints: BoxConstraints(w=800.0, h=600.0)
flutter:                          │ size: Size(800.0, 600.0)
flutter:                          │ skipCount: 0
flutter:                          │ textDirection: ltr
flutter:                          │
flutter:                          ├─onstage 1: RenderIgnorePointer#3b659
flutter:                          │ │ creator: IgnorePointer ← _RenderTheaterMarker ←
flutter:                          │ │   _EffectiveTickerMode ← TickerMode ←
flutter:                          │ │   _OverlayEntryWidget-[LabeledGlobalKey<_OverlayEntryWidgetState>#a47f4]
flutter:                          │ │   ← _Theater ← Overlay-[LabeledGlobalKey<OverlayState>#49a93] ←
flutter:                          │ │   UnmanagedRestorationScope ← _FocusInheritedScope ← Focus ←
flutter:                          │ │   _FocusInheritedScope ← Focus ← ⋯
flutter:                          │ │ parentData: not positioned; offset=Offset(0.0, 0.0) (can use
flutter:                          │ │   size)
flutter:                          │ │ constraints: BoxConstraints(w=800.0, h=600.0)
flutter:                          │ │ size: Size(800.0, 600.0)
flutter:                          │ │ ignoring: false
flutter:                          │ │ ignoringSemantics: null
flutter:                          │ │
flutter:                          │ └─child: RenderBlockSemantics#7586c
flutter:                          │   │ creator: BlockSemantics ← ModalBarrier ← IgnorePointer ←
flutter:                          │   │   _RenderTheaterMarker ← _EffectiveTickerMode ← TickerMode ←
flutter:                          │   │   _OverlayEntryWidget-[LabeledGlobalKey<_OverlayEntryWidgetState>#a47f4]
flutter:                          │   │   ← _Theater ← Overlay-[LabeledGlobalKey<OverlayState>#49a93] ←
flutter:                          │   │   UnmanagedRestorationScope ← _FocusInheritedScope ← Focus ← ⋯
flutter:                          │   │ parentData: <none> (can use size)
flutter:                          │   │ constraints: BoxConstraints(w=800.0, h=600.0)
flutter:                          │   │ blocks semantics of earlier render objects below the common
flutter:                          │   │ boundary
flutter:                          │   │ size: Size(800.0, 600.0)
flutter:                          │   │ blocking: true
flutter:                          │   │
flutter:                          │   └─child: RenderExcludeSemantics#c1d3f
flutter:                          │     │ creator: ExcludeSemantics ← BlockSemantics ← ModalBarrier ←
flutter:                          │     │   IgnorePointer ← _RenderTheaterMarker ← _EffectiveTickerMode ←
flutter:                          │     │   TickerMode ←
flutter:                          │     │   _OverlayEntryWidget-[LabeledGlobalKey<_OverlayEntryWidgetState>#a47f4]
flutter:                          │     │   ← _Theater ← Overlay-[LabeledGlobalKey<OverlayState>#49a93] ←
flutter:                          │     │   UnmanagedRestorationScope ← _FocusInheritedScope ← ⋯
flutter:                          │     │ parentData: <none> (can use size)
flutter:                          │     │ constraints: BoxConstraints(w=800.0, h=600.0)
flutter:                          │     │ size: Size(800.0, 600.0)
flutter:                          │     │ excluding: true
flutter:                          │     │
flutter:                          │     └─child: RenderSemanticsGestureHandler#70b16
flutter:                          │       │ creator: _GestureSemantics ← RawGestureDetector ←
flutter:                          │       │   _ModalBarrierGestureDetector ← ExcludeSemantics ←
flutter:                          │       │   BlockSemantics ← ModalBarrier ← IgnorePointer ←
flutter:                          │       │   _RenderTheaterMarker ← _EffectiveTickerMode ← TickerMode ←
flutter:                          │       │   _OverlayEntryWidget-[LabeledGlobalKey<_OverlayEntryWidgetState>#a47f4]
flutter:                          │       │   ← _Theater ← ⋯
flutter:                          │       │ parentData: <none> (can use size)
flutter:                          │       │ constraints: BoxConstraints(w=800.0, h=600.0)
flutter:                          │       │ size: Size(800.0, 600.0)
flutter:                          │       │ behavior: opaque
flutter:                          │       │ gestures: <none>
flutter:                          │       │
flutter:                          │       └─child: RenderPointerListener#1f34a
flutter:                          │         │ creator: Listener ← _GestureSemantics ← RawGestureDetector ←
flutter:                          │         │   _ModalBarrierGestureDetector ← ExcludeSemantics ←
flutter:                          │         │   BlockSemantics ← ModalBarrier ← IgnorePointer ←
flutter:                          │         │   _RenderTheaterMarker ← _EffectiveTickerMode ← TickerMode ←
flutter:                          │         │   _OverlayEntryWidget-[LabeledGlobalKey<_OverlayEntryWidgetState>#a47f4]
flutter:                          │         │   ← ⋯
flutter:                          │         │ parentData: <none> (can use size)
flutter:                          │         │ constraints: BoxConstraints(w=800.0, h=600.0)
flutter:                          │         │ size: Size(800.0, 600.0)
flutter:                          │         │ behavior: opaque
flutter:                          │         │ listeners: down, panZoomStart
flutter:                          │         │
flutter:                          │         └─child: RenderSemanticsAnnotations#73467
flutter:                          │           │ creator: Semantics ← Listener ← _GestureSemantics ←
flutter:                          │           │   RawGestureDetector ← _ModalBarrierGestureDetector ←
flutter:                          │           │   ExcludeSemantics ← BlockSemantics ← ModalBarrier ←
flutter:                          │           │   IgnorePointer ← _RenderTheaterMarker ← _EffectiveTickerMode ←
flutter:                          │           │   TickerMode ← ⋯
flutter:                          │           │ parentData: <none> (can use size)
flutter:                          │           │ constraints: BoxConstraints(w=800.0, h=600.0)
flutter:                          │           │ size: Size(800.0, 600.0)
flutter:                          │           │
flutter:                          │           └─child: RenderMouseRegion#560dc
flutter:                          │             │ creator: MouseRegion ← Semantics ← Listener ← _GestureSemantics ←
flutter:                          │             │   RawGestureDetector ← _ModalBarrierGestureDetector ←
flutter:                          │             │   ExcludeSemantics ← BlockSemantics ← ModalBarrier ←
flutter:                          │             │   IgnorePointer ← _RenderTheaterMarker ← _EffectiveTickerMode ← ⋯
flutter:                          │             │ parentData: <none> (can use size)
flutter:                          │             │ constraints: BoxConstraints(w=800.0, h=600.0)
flutter:                          │             │ size: Size(800.0, 600.0)
flutter:                          │             │ behavior: opaque
flutter:                          │             │ listeners: <none>
flutter:                          │             │ cursor: SystemMouseCursor(basic)
flutter:                          │             │
flutter:                          │             └─child: RenderConstrainedBox#01e8c
flutter:                          │                 creator: ConstrainedBox ← MouseRegion ← Semantics ← Listener ←
flutter:                          │                   _GestureSemantics ← RawGestureDetector ←
flutter:                          │                   _ModalBarrierGestureDetector ← ExcludeSemantics ←
flutter:                          │                   BlockSemantics ← ModalBarrier ← IgnorePointer ←
flutter:                          │                   _RenderTheaterMarker ← ⋯
flutter:                          │                 parentData: <none> (can use size)
flutter:                          │                 constraints: BoxConstraints(w=800.0, h=600.0)
flutter:                          │                 size: Size(800.0, 600.0)
flutter:                          │                 additionalConstraints: BoxConstraints(biggest)
flutter:                          │
flutter:                          ├─onstage 2: RenderSemanticsAnnotations#8187b
flutter:                          ╎ │ needs compositing
flutter:                          ╎ │ creator: Semantics ← _RenderTheaterMarker ← _EffectiveTickerMode
flutter:                          ╎ │   ← TickerMode ←
flutter:                          ╎ │   _OverlayEntryWidget-[LabeledGlobalKey<_OverlayEntryWidgetState>#8cd54]
flutter:                          ╎ │   ← _Theater ← Overlay-[LabeledGlobalKey<OverlayState>#49a93] ←
flutter:                          ╎ │   UnmanagedRestorationScope ← _FocusInheritedScope ← Focus ←
flutter:                          ╎ │   _FocusInheritedScope ← Focus ← ⋯
flutter:                          ╎ │ parentData: not positioned; offset=Offset(0.0, 0.0) (can use
flutter:                          ╎ │   size)
flutter:                          ╎ │ constraints: BoxConstraints(w=800.0, h=600.0)
flutter:                          ╎ │ size: Size(800.0, 600.0)
flutter:                          ╎ │
flutter:                          ╎ └─child: RenderOffstage#f211d
flutter:                          ╎   │ needs compositing
flutter:                          ╎   │ creator: Offstage ← _ModalScopeStatus ← UnmanagedRestorationScope
flutter:                          ╎   │   ← RestorationScope ← AnimatedBuilder ←
flutter:                          ╎   │   _ModalScope<dynamic>-[LabeledGlobalKey<_ModalScopeState<dynamic>>#db401]
flutter:                          ╎   │   ← Semantics ← _RenderTheaterMarker ← _EffectiveTickerMode ←
flutter:                          ╎   │   TickerMode ←
flutter:                          ╎   │   _OverlayEntryWidget-[LabeledGlobalKey<_OverlayEntryWidgetState>#8cd54]
flutter:                          ╎   │   ← _Theater ← ⋯
flutter:                          ╎   │ parentData: <none> (can use size)
flutter:                          ╎   │ constraints: BoxConstraints(w=800.0, h=600.0)
flutter:                          ╎   │ size: Size(800.0, 600.0)
flutter:                          ╎   │ offstage: false
flutter:                          ╎   │
flutter:                          ╎   └─child: RenderSemanticsAnnotations#9436c
flutter:                          ╎     │ needs compositing
flutter:                          ╎     │ creator: Semantics ← FocusScope ← PrimaryScrollController ←
flutter:                          ╎     │   _ActionsScope ← Actions ← Builder ← PageStorage ← Offstage ←
flutter:                          ╎     │   _ModalScopeStatus ← UnmanagedRestorationScope ←
flutter:                          ╎     │   RestorationScope ← AnimatedBuilder ← ⋯
flutter:                          ╎     │ parentData: <none> (can use size)
flutter:                          ╎     │ constraints: BoxConstraints(w=800.0, h=600.0)
flutter:                          ╎     │ size: Size(800.0, 600.0)
flutter:                          ╎     │
flutter:                          ╎     └─child: RenderRepaintBoundary#f8f28
flutter:                          ╎       │ needs compositing
flutter:                          ╎       │ creator: RepaintBoundary ← _FocusInheritedScope ← Semantics ←
flutter:                          ╎       │   FocusScope ← PrimaryScrollController ← _ActionsScope ← Actions
flutter:                          ╎       │   ← Builder ← PageStorage ← Offstage ← _ModalScopeStatus ←
flutter:                          ╎       │   UnmanagedRestorationScope ← ⋯
flutter:                          ╎       │ parentData: <none> (can use size)
flutter:                          ╎       │ constraints: BoxConstraints(w=800.0, h=600.0)
flutter:                          ╎       │ layer: OffsetLayer#e73b7
flutter:                          ╎       │ size: Size(800.0, 600.0)
flutter:                          ╎       │ metrics: 66.7% useful (1 bad vs 2 good)
flutter:                          ╎       │ diagnosis: insufficient data to draw conclusion (less than five
flutter:                          ╎       │   repaints)
flutter:                          ╎       │
flutter:                          ╎       └─child: RenderFractionalTranslation#c3a54
flutter:                          ╎         │ needs compositing
flutter:                          ╎         │ creator: FractionalTranslation ← SlideTransition ←
flutter:                          ╎         │   CupertinoPageTransition ← AnimatedBuilder ← RepaintBoundary ←
flutter:                          ╎         │   _FocusInheritedScope ← Semantics ← FocusScope ←
flutter:                          ╎         │   PrimaryScrollController ← _ActionsScope ← Actions ← Builder ← ⋯
flutter:                          ╎         │ parentData: <none> (can use size)
flutter:                          ╎         │ constraints: BoxConstraints(w=800.0, h=600.0)
flutter:                          ╎         │ size: Size(800.0, 600.0)
flutter:                          ╎         │ translation: Offset(0.0, 0.0)
flutter:                          ╎         │ transformHitTests: false
flutter:                          ╎         │
flutter:                          ╎         └─child: RenderFractionalTranslation#7fcf2
flutter:                          ╎           │ needs compositing
flutter:                          ╎           │ creator: FractionalTranslation ← SlideTransition ←
flutter:                          ╎           │   FractionalTranslation ← SlideTransition ←
flutter:                          ╎           │   CupertinoPageTransition ← AnimatedBuilder ← RepaintBoundary ←
flutter:                          ╎           │   _FocusInheritedScope ← Semantics ← FocusScope ←
flutter:                          ╎           │   PrimaryScrollController ← _ActionsScope ← ⋯
flutter:                          ╎           │ parentData: <none> (can use size)
flutter:                          ╎           │ constraints: BoxConstraints(w=800.0, h=600.0)
flutter:                          ╎           │ size: Size(800.0, 600.0)
flutter:                          ╎           │ translation: Offset(0.0, 0.0)
flutter:                          ╎           │ transformHitTests: true
flutter:                          ╎           │
flutter:                          ╎           └─child: RenderDecoratedBox#713ec
flutter:                          ╎             │ needs compositing
flutter:                          ╎             │ creator: DecoratedBox ← DecoratedBoxTransition ←
flutter:                          ╎             │   FractionalTranslation ← SlideTransition ← FractionalTranslation
flutter:                          ╎             │   ← SlideTransition ← CupertinoPageTransition ← AnimatedBuilder ←
flutter:                          ╎             │   RepaintBoundary ← _FocusInheritedScope ← Semantics ← FocusScope
flutter:                          ╎             │   ← ⋯
flutter:                          ╎             │ parentData: <none> (can use size)
flutter:                          ╎             │ constraints: BoxConstraints(w=800.0, h=600.0)
flutter:                          ╎             │ size: Size(800.0, 600.0)
flutter:                          ╎             │ ├─decoration: _CupertinoEdgeShadowDecoration
flutter:                          ╎             │     colors: Color(0x04000000), Color(0x00000000)
flutter:                          ╎             │
flutter:                          ╎             │ configuration: ImageConfiguration(bundle:
flutter:                          ╎             │   PlatformAssetBundle#164ca(), devicePixelRatio: 1.0, locale:
flutter:                          ╎             │   en_US, textDirection: TextDirection.ltr, platform: macOS)
flutter:                          ╎             │
flutter:                          ╎             └─child: RenderStack#83b13
flutter:                          ╎               │ needs compositing
flutter:                          ╎               │ creator: Stack ← _CupertinoBackGestureDetector<dynamic> ←
flutter:                          ╎               │   DecoratedBox ← DecoratedBoxTransition ← FractionalTranslation ←
flutter:                          ╎               │   SlideTransition ← FractionalTranslation ← SlideTransition ←
flutter:                          ╎               │   CupertinoPageTransition ← AnimatedBuilder ← RepaintBoundary ←
flutter:                          ╎               │   _FocusInheritedScope ← ⋯
flutter:                          ╎               │ parentData: <none> (can use size)
flutter:                          ╎               │ constraints: BoxConstraints(w=800.0, h=600.0)
flutter:                          ╎               │ size: Size(800.0, 600.0)
flutter:                          ╎               │ alignment: AlignmentDirectional.topStart
flutter:                          ╎               │ textDirection: ltr
flutter:                          ╎               │ fit: passthrough
flutter:                          ╎               │
flutter:                          ╎               ├─child 1: RenderIgnorePointer#ad50f
flutter:                          ╎               │ │ needs compositing
flutter:                          ╎               │ │ creator: IgnorePointer ← AnimatedBuilder ← Stack ←
flutter:                          ╎               │ │   _CupertinoBackGestureDetector<dynamic> ← DecoratedBox ←
flutter:                          ╎               │ │   DecoratedBoxTransition ← FractionalTranslation ←
flutter:                          ╎               │ │   SlideTransition ← FractionalTranslation ← SlideTransition ←
flutter:                          ╎               │ │   CupertinoPageTransition ← AnimatedBuilder ← ⋯
flutter:                          ╎               │ │ parentData: not positioned; offset=Offset(0.0, 0.0) (can use
flutter:                          ╎               │ │   size)
flutter:                          ╎               │ │ constraints: BoxConstraints(w=800.0, h=600.0)
flutter:                          ╎               │ │ size: Size(800.0, 600.0)
flutter:                          ╎               │ │ ignoring: false
flutter:                          ╎               │ │ ignoringSemantics: null
flutter:                          ╎               │ │
flutter:                          ╎               │ └─child: RenderRepaintBoundary#29754
flutter:                          ╎               │   │ needs compositing
flutter:                          ╎               │   │ creator: RepaintBoundary-[GlobalKey#75409] ← IgnorePointer ←
flutter:                          ╎               │   │   AnimatedBuilder ← Stack ←
flutter:                          ╎               │   │   _CupertinoBackGestureDetector<dynamic> ← DecoratedBox ←
flutter:                          ╎               │   │   DecoratedBoxTransition ← FractionalTranslation ←
flutter:                          ╎               │   │   SlideTransition ← FractionalTranslation ← SlideTransition ←
flutter:                          ╎               │   │   CupertinoPageTransition ← ⋯
flutter:                          ╎               │   │ parentData: <none> (can use size)
flutter:                          ╎               │   │ constraints: BoxConstraints(w=800.0, h=600.0)
flutter:                          ╎               │   │ layer: OffsetLayer#fa835
flutter:                          ╎               │   │ size: Size(800.0, 600.0)
flutter:                          ╎               │   │ metrics: 90.9% useful (1 bad vs 10 good)
flutter:                          ╎               │   │ diagnosis: this is an outstandingly useful repaint boundary and
flutter:                          ╎               │   │   should definitely be kept
flutter:                          ╎               │   │
flutter:                          ╎               │   └─child: RenderSemanticsAnnotations#95566
flutter:                          ╎               │     │ creator: Semantics ← Builder ← RepaintBoundary-[GlobalKey#75409]
flutter:                          ╎               │     │   ← IgnorePointer ← AnimatedBuilder ← Stack ←
flutter:                          ╎               │     │   _CupertinoBackGestureDetector<dynamic> ← DecoratedBox ←
flutter:                          ╎               │     │   DecoratedBoxTransition ← FractionalTranslation ←
flutter:                          ╎               │     │   SlideTransition ← FractionalTranslation ← ⋯
flutter:                          ╎               │     │ parentData: <none> (can use size)
flutter:                          ╎               │     │ constraints: BoxConstraints(w=800.0, h=600.0)
flutter:                          ╎               │     │ size: Size(800.0, 600.0)
flutter:                          ╎               │     │
flutter:                          ╎               │     └─child: RenderPhysicalModel#bc9d7
flutter:                          ╎               │       │ creator: PhysicalModel ← AnimatedPhysicalModel ← Material ←
flutter:                          ╎               │       │   AppHome ← Semantics ← Builder ←
flutter:                          ╎               │       │   RepaintBoundary-[GlobalKey#75409] ← IgnorePointer ←
flutter:                          ╎               │       │   AnimatedBuilder ← Stack ←
flutter:                          ╎               │       │   _CupertinoBackGestureDetector<dynamic> ← DecoratedBox ← ⋯
flutter:                          ╎               │       │ parentData: <none> (can use size)
flutter:                          ╎               │       │ constraints: BoxConstraints(w=800.0, h=600.0)
flutter:                          ╎               │       │ size: Size(800.0, 600.0)
flutter:                          ╎               │       │ elevation: 0.0
flutter:                          ╎               │       │ color: Color(0xfffafafa)
flutter:                          ╎               │       │ shadowColor: Color(0xfffafafa)
flutter:                          ╎               │       │ shape: BoxShape.rectangle
flutter:                          ╎               │       │ borderRadius: BorderRadius.zero
flutter:                          ╎               │       │
flutter:                          ╎               │       └─child: _RenderInkFeatures#ac819
flutter:                          ╎               │         │ creator: _InkFeatures-[GlobalKey#d721e ink renderer] ←
flutter:                          ╎               │         │   NotificationListener<LayoutChangedNotification> ← PhysicalModel
flutter:                          ╎               │         │   ← AnimatedPhysicalModel ← Material ← AppHome ← Semantics ←
flutter:                          ╎               │         │   Builder ← RepaintBoundary-[GlobalKey#75409] ← IgnorePointer ←
flutter:                          ╎               │         │   AnimatedBuilder ← Stack ← ⋯
flutter:                          ╎               │         │ parentData: <none> (can use size)
flutter:                          ╎               │         │ constraints: BoxConstraints(w=800.0, h=600.0)
flutter:                          ╎               │         │ size: Size(800.0, 600.0)
flutter:                          ╎               │         │
flutter:                          ╎               │         └─child: RenderPositionedBox#dc1df
flutter:                          ╎               │           │ creator: Center ← DefaultTextStyle ← AnimatedDefaultTextStyle ←
flutter:                          ╎               │           │   _InkFeatures-[GlobalKey#d721e ink renderer] ←
flutter:                          ╎               │           │   NotificationListener<LayoutChangedNotification> ← PhysicalModel
flutter:                          ╎               │           │   ← AnimatedPhysicalModel ← Material ← AppHome ← Semantics ←
flutter:                          ╎               │           │   Builder ← RepaintBoundary-[GlobalKey#75409] ← ⋯
flutter:                          ╎               │           │ parentData: <none> (can use size)
flutter:                          ╎               │           │ constraints: BoxConstraints(w=800.0, h=600.0)
flutter:                          ╎               │           │ size: Size(800.0, 600.0)
flutter:                          ╎               │           │ alignment: Alignment.center
flutter:                          ╎               │           │ textDirection: ltr
flutter:                          ╎               │           │ widthFactor: expand
flutter:                          ╎               │           │ heightFactor: expand
flutter:                          ╎               │           │
flutter:                          ╎               │           └─child: RenderSemanticsAnnotations#a0a4b relayoutBoundary=up1
flutter:                          ╎               │             │ creator: Semantics ← TextButton ← Center ← DefaultTextStyle ←
flutter:                          ╎               │             │   AnimatedDefaultTextStyle ← _InkFeatures-[GlobalKey#d721e ink
flutter:                          ╎               │             │   renderer] ← NotificationListener<LayoutChangedNotification> ←
flutter:                          ╎               │             │   PhysicalModel ← AnimatedPhysicalModel ← Material ← AppHome ←
flutter:                          ╎               │             │   Semantics ← ⋯
flutter:                          ╎               │             │ parentData: offset=Offset(329.0, 286.0) (can use size)
flutter:                          ╎               │             │ constraints: BoxConstraints(0.0<=w<=800.0, 0.0<=h<=600.0)
flutter:                          ╎               │             │ semantic boundary
flutter:                          ╎               │             │ size: Size(142.0, 28.0)
flutter:                          ╎               │             │
flutter:                          ╎               │             └─child: _RenderInputPadding#4672f relayoutBoundary=up2
flutter:                          ╎               │               │ creator: _InputPadding ← Semantics ← TextButton ← Center ←
flutter:                          ╎               │               │   DefaultTextStyle ← AnimatedDefaultTextStyle ←
flutter:                          ╎               │               │   _InkFeatures-[GlobalKey#d721e ink renderer] ←
flutter:                          ╎               │               │   NotificationListener<LayoutChangedNotification> ← PhysicalModel
flutter:                          ╎               │               │   ← AnimatedPhysicalModel ← Material ← AppHome ← ⋯
flutter:                          ╎               │               │ parentData: <none> (can use size)
flutter:                          ╎               │               │ constraints: BoxConstraints(0.0<=w<=800.0, 0.0<=h<=600.0)
flutter:                          ╎               │               │ size: Size(142.0, 28.0)
flutter:                          ╎               │               │
flutter:                          ╎               │               └─child: RenderConstrainedBox#425d6 relayoutBoundary=up3
flutter:                          ╎               │                 │ creator: ConstrainedBox ← _InputPadding ← Semantics ← TextButton
flutter:                          ╎               │                 │   ← Center ← DefaultTextStyle ← AnimatedDefaultTextStyle ←
flutter:                          ╎               │                 │   _InkFeatures-[GlobalKey#d721e ink renderer] ←
flutter:                          ╎               │                 │   NotificationListener<LayoutChangedNotification> ← PhysicalModel
flutter:                          ╎               │                 │   ← AnimatedPhysicalModel ← Material ← ⋯
flutter:                          ╎               │                 │ parentData: offset=Offset(0.0, 0.0) (can use size)
flutter:                          ╎               │                 │ constraints: BoxConstraints(0.0<=w<=800.0, 0.0<=h<=600.0)
flutter:                          ╎               │                 │ size: Size(142.0, 28.0)
flutter:                          ╎               │                 │ additionalConstraints: BoxConstraints(56.0<=w<=Infinity,
flutter:                          ╎               │                 │   28.0<=h<=Infinity)
flutter:                          ╎               │                 │
flutter:                          ╎               │                 └─child: RenderPhysicalShape#8e171 relayoutBoundary=up4
flutter:                          ╎               │                   │ creator: PhysicalShape ← _MaterialInterior ← Material ←
flutter:                          ╎               │                   │   ConstrainedBox ← _InputPadding ← Semantics ← TextButton ←
flutter:                          ╎               │                   │   Center ← DefaultTextStyle ← AnimatedDefaultTextStyle ←
flutter:                          ╎               │                   │   _InkFeatures-[GlobalKey#d721e ink renderer] ←
flutter:                          ╎               │                   │   NotificationListener<LayoutChangedNotification> ← ⋯
flutter:                          ╎               │                   │ parentData: <none> (can use size)
flutter:                          ╎               │                   │ constraints: BoxConstraints(56.0<=w<=800.0, 28.0<=h<=600.0)
flutter:                          ╎               │                   │ size: Size(142.0, 28.0)
flutter:                          ╎               │                   │ elevation: 0.0
flutter:                          ╎               │                   │ color: Color(0x00000000)
flutter:                          ╎               │                   │ shadowColor: Color(0x00000000)
flutter:                          ╎               │                   │ clipper: ShapeBorderClipper
flutter:                          ╎               │                   │
flutter:                          ╎               │                   └─child: RenderCustomPaint#eea46 relayoutBoundary=up5
flutter:                          ╎               │                     │ creator: CustomPaint ← _ShapeBorderPaint ← PhysicalShape ←
flutter:                          ╎               │                     │   _MaterialInterior ← Material ← ConstrainedBox ← _InputPadding ←
flutter:                          ╎               │                     │   Semantics ← TextButton ← Center ← DefaultTextStyle ←
flutter:                          ╎               │                     │   AnimatedDefaultTextStyle ← ⋯
flutter:                          ╎               │                     │ parentData: <none> (can use size)
flutter:                          ╎               │                     │ constraints: BoxConstraints(56.0<=w<=800.0, 28.0<=h<=600.0)
flutter:                          ╎               │                     │ size: Size(142.0, 28.0)
flutter:                          ╎               │                     │ painter: null
flutter:                          ╎               │                     │ foregroundPainter: _ShapeBorderPainter#ac724()
flutter:                          ╎               │                     │
flutter:                          ╎               │                     └─child: _RenderInkFeatures#b19a7 relayoutBoundary=up6
flutter:                          ╎               │                       │ creator: _InkFeatures-[GlobalKey#87971 ink renderer] ←
flutter:                          ╎               │                       │   NotificationListener<LayoutChangedNotification> ← CustomPaint ←
flutter:                          ╎               │                       │   _ShapeBorderPaint ← PhysicalShape ← _MaterialInterior ←
flutter:                          ╎               │                       │   Material ← ConstrainedBox ← _InputPadding ← Semantics ←
flutter:                          ╎               │                       │   TextButton ← Center ← ⋯
flutter:                          ╎               │                       │ parentData: <none> (can use size)
flutter:                          ╎               │                       │ constraints: BoxConstraints(56.0<=w<=800.0, 28.0<=h<=600.0)
flutter:                          ╎               │                       │ size: Size(142.0, 28.0)
flutter:                          ╎               │                       │
flutter:                          ╎               │                       └─child: RenderSemanticsAnnotations#4d1b3 relayoutBoundary=up7
flutter:                          ╎               │                         │ creator: Semantics ← _FocusInheritedScope ← Focus ← _ActionsScope
flutter:                          ╎               │                         │   ← Actions ← _ParentInkResponseProvider ←
flutter:                          ╎               │                         │   _InkResponseStateWidget ← InkWell ← DefaultTextStyle ←
flutter:                          ╎               │                         │   AnimatedDefaultTextStyle ← _InkFeatures-[GlobalKey#87971 ink
flutter:                          ╎               │                         │   renderer] ← NotificationListener<LayoutChangedNotification> ← ⋯
flutter:                          ╎               │                         │ parentData: <none> (can use size)
flutter:                          ╎               │                         │ constraints: BoxConstraints(56.0<=w<=800.0, 28.0<=h<=600.0)
flutter:                          ╎               │                         │ size: Size(142.0, 28.0)
flutter:                          ╎               │                         │
flutter:                          ╎               │                         └─child: RenderMouseRegion#e5b3f relayoutBoundary=up8
flutter:                          ╎               │                           │ creator: MouseRegion ← Semantics ← _FocusInheritedScope ← Focus ←
flutter:                          ╎               │                           │   _ActionsScope ← Actions ← _ParentInkResponseProvider ←
flutter:                          ╎               │                           │   _InkResponseStateWidget ← InkWell ← DefaultTextStyle ←
flutter:                          ╎               │                           │   AnimatedDefaultTextStyle ← _InkFeatures-[GlobalKey#87971 ink
flutter:                          ╎               │                           │   renderer] ← ⋯
flutter:                          ╎               │                           │ parentData: <none> (can use size)
flutter:                          ╎               │                           │ constraints: BoxConstraints(56.0<=w<=800.0, 28.0<=h<=600.0)
flutter:                          ╎               │                           │ size: Size(142.0, 28.0)
flutter:                          ╎               │                           │ behavior: opaque
flutter:                          ╎               │                           │ listeners: enter, exit
flutter:                          ╎               │                           │ cursor: SystemMouseCursor(click)
flutter:                          ╎               │                           │
flutter:                          ╎               │                           └─child: RenderSemanticsAnnotations#deb9b relayoutBoundary=up9
flutter:                          ╎               │                             │ creator: Semantics ← DefaultSelectionStyle ← Builder ←
flutter:                          ╎               │                             │   MouseRegion ← Semantics ← _FocusInheritedScope ← Focus ←
flutter:                          ╎               │                             │   _ActionsScope ← Actions ← _ParentInkResponseProvider ←
flutter:                          ╎               │                             │   _InkResponseStateWidget ← InkWell ← ⋯
flutter:                          ╎               │                             │ parentData: <none> (can use size)
flutter:                          ╎               │                             │ constraints: BoxConstraints(56.0<=w<=800.0, 28.0<=h<=600.0)
flutter:                          ╎               │                             │ size: Size(142.0, 28.0)
flutter:                          ╎               │                             │
flutter:                          ╎               │                             └─child: RenderPointerListener#2017a relayoutBoundary=up10
flutter:                          ╎               │                               │ creator: Listener ← RawGestureDetector ← GestureDetector ←
flutter:                          ╎               │                               │   Semantics ← DefaultSelectionStyle ← Builder ← MouseRegion ←
flutter:                          ╎               │                               │   Semantics ← _FocusInheritedScope ← Focus ← _ActionsScope ←
flutter:                          ╎               │                               │   Actions ← ⋯
flutter:                          ╎               │                               │ parentData: <none> (can use size)
flutter:                          ╎               │                               │ constraints: BoxConstraints(56.0<=w<=800.0, 28.0<=h<=600.0)
flutter:                          ╎               │                               │ size: Size(142.0, 28.0)
flutter:                          ╎               │                               │ behavior: opaque
flutter:                          ╎               │                               │ listeners: down, panZoomStart
flutter:                          ╎               │                               │
flutter:                          ╎               │                               └─child: RenderPadding#8455f relayoutBoundary=up11
flutter:                          ╎               │                                 │ creator: Padding ← IconTheme ← Builder ← Listener ←
flutter:                          ╎               │                                 │   RawGestureDetector ← GestureDetector ← Semantics ←
flutter:                          ╎               │                                 │   DefaultSelectionStyle ← Builder ← MouseRegion ← Semantics ←
flutter:                          ╎               │                                 │   _FocusInheritedScope ← ⋯
flutter:                          ╎               │                                 │ parentData: <none> (can use size)
flutter:                          ╎               │                                 │ constraints: BoxConstraints(56.0<=w<=800.0, 28.0<=h<=600.0)
flutter:                          ╎               │                                 │ size: Size(142.0, 28.0)
flutter:                          ╎               │                                 │ padding: EdgeInsets(8.0, 0.0, 8.0, 0.0)
flutter:                          ╎               │                                 │ textDirection: ltr
flutter:                          ╎               │                                 │
flutter:                          ╎               │                                 └─child: RenderPositionedBox#80b8d relayoutBoundary=up12
flutter:                          ╎               │                                   │ creator: Align ← Padding ← IconTheme ← Builder ← Listener ←
flutter:                          ╎               │                                   │   RawGestureDetector ← GestureDetector ← Semantics ←
flutter:                          ╎               │                                   │   DefaultSelectionStyle ← Builder ← MouseRegion ← Semantics ← ⋯
flutter:                          ╎               │                                   │ parentData: offset=Offset(8.0, 0.0) (can use size)
flutter:                          ╎               │                                   │ constraints: BoxConstraints(40.0<=w<=784.0, 28.0<=h<=600.0)
flutter:                          ╎               │                                   │ size: Size(126.0, 28.0)
flutter:                          ╎               │                                   │ alignment: Alignment.center
flutter:                          ╎               │                                   │ textDirection: ltr
flutter:                          ╎               │                                   │ widthFactor: 1.0
flutter:                          ╎               │                                   │ heightFactor: 1.0
flutter:                          ╎               │                                   │
flutter:                          ╎               │                                   └─child: RenderParagraph#59bc2 relayoutBoundary=up13
flutter:                          ╎               │                                     │ creator: RichText ← Text ← Align ← Padding ← IconTheme ← Builder
flutter:                          ╎               │                                     │   ← Listener ← RawGestureDetector ← GestureDetector ← Semantics ←
flutter:                          ╎               │                                     │   DefaultSelectionStyle ← Builder ← ⋯
flutter:                          ╎               │                                     │ parentData: offset=Offset(0.0, 6.0) (can use size)
flutter:                          ╎               │                                     │ constraints: BoxConstraints(0.0<=w<=784.0, 0.0<=h<=600.0)
flutter:                          ╎               │                                     │ size: Size(126.0, 16.0)
flutter:                          ╎               │                                     │ textAlign: start
flutter:                          ╎               │                                     │ textDirection: ltr
flutter:                          ╎               │                                     │ softWrap: wrapping at box width
flutter:                          ╎               │                                     │ overflow: clip
flutter:                          ╎               │                                     │ locale: en_US
flutter:                          ╎               │                                     │ maxLines: unlimited
flutter:                          ╎               │                                     ╘═╦══ text ═══
flutter:                          ╎               │                                       ║ TextSpan:
flutter:                          ╎               │                                       ║   debugLabel: ((englishLike labelLarge 2014).merge(blackRedwoodCity
flutter:                          ╎               │                                       ║     labelLarge)).copyWith
flutter:                          ╎               │                                       ║   inherit: false
flutter:                          ╎               │                                       ║   color: MaterialColor(primary value: Color(0xff2196f3))
flutter:                          ╎               │                                       ║   family: .AppleSystemUIFont
flutter:                          ╎               │                                       ║   size: 14.0
flutter:                          ╎               │                                       ║   weight: 500
flutter:                          ╎               │                                       ║   baseline: alphabetic
flutter:                          ╎               │                                       ║   decoration: TextDecoration.none
flutter:                          ╎               │                                       ║   "Dump Render Tree"
flutter:                          ╎               │                                       ╚═══════════
flutter:                          ╎               └─child 2: RenderPointerListener#db4b5
flutter:                          ╎                   creator: Listener ← Positioned ← PositionedDirectional ← Stack ←
flutter:                          ╎                     _CupertinoBackGestureDetector<dynamic> ← DecoratedBox ←
flutter:                          ╎                     DecoratedBoxTransition ← FractionalTranslation ←
flutter:                          ╎                     SlideTransition ← FractionalTranslation ← SlideTransition ←
flutter:                          ╎                     CupertinoPageTransition ← ⋯
flutter:                          ╎                   parentData: top=0.0; bottom=0.0; left=0.0; width=20.0;
flutter:                          ╎                     offset=Offset(0.0, 0.0) (can use size)
flutter:                          ╎                   constraints: BoxConstraints(w=20.0, h=600.0)
flutter:                          ╎                   size: Size(20.0, 600.0)
flutter:                          ╎                   behavior: translucent
flutter:                          ╎                   listeners: down
flutter:                          ╎
flutter:                          └╌no offstage children
flutter:
```

In the render tree for **Example 5**:

* The `RenderView`, or window size, limits all render objects up to and including [`RenderPositionedBox`](https://api.flutter.dev/flutter/rendering/RenderPositionedBox-class.html)`#dc1df` render object to the size of the screen. This example sets the size to `Size(800.0, 600.0)`

* The `constraints` property of each render object limits the size of each child. This property takes the [`BoxConstraints`](https://api.flutter.dev/flutter/rendering/BoxConstraints-class.html) render object as a value. Starting with the `RenderSemanticsAnnotations#fe6b5`, the constraint equals `BoxConstraints(w=800.0, h=600.0)`.

* The [`Center`](https://api.flutter.dev/flutter/widgets/Center-class.html) widget created the `RenderPositionedBox#dc1df` render object under the `RenderSemanticsAnnotations#8187b` subtree.

* Each child under this render object has `BoxConstraints` with both minimum and maximum values. For example, `RenderSemanticsAnnotations#a0a4b` uses `BoxConstraints(0.0<=w<=800.0, 0.0<=h<=600.0)`.

* All children of the `RenderPhysicalShape#8e171` render object use `BoxConstraints(BoxConstraints(56.0<=w<=800.0, 28.0<=h<=600.0))`.

* The child `RenderPadding#8455f` sets a `padding` value of `EdgeInsets(8.0, 0.0, 8.0, 0.0)`. This sets a left and right padding of 8 to all subsequent children of this render object. They now have new constraints: `BoxConstraints(40.0<=w<=784.0, 28.0<=h<=600.0)`.

This object, which the `creator` field tells us is probably part of the [`TextButton`](https://pub.dev/documentation/material_ui/latest/material_ui/TextButton-class.html)'s definition, sets a minimum width of 88 pixels on its contents and a specific height of 36.0. This is the `TextButton` class implementing the Material Design guidelines regarding button dimensions.

`RenderPositionedBox#80b8d` render object loosens the constraints again to center the text within the button. The [`RenderParagraph`](https://api.flutter.dev/flutter/rendering/RenderParagraph-class.html)#59bc2 render object picks its size based on its contents. If you follow the sizes back up the tree, you see how the size of the text influences the width of all the boxes that form the button. All parents take their child's dimensions to size themselves.

Another way to notice this is by looking at the `relayoutBoundary` attribute of in the descriptions of each box. This tells you how many ancestors depend on this element's size.

For example, the innermost `RenderPositionedBox` line has a `relayoutBoundary=up13`. This means that when Flutter marks the `RenderConstrainedBox` as dirty, it also marks box's 13 ancestors as dirty because the new dimensions might affect those ancestors.

To add information to the dump if you write your own render objects, override [`debugFillProperties()`](https://api.flutter.dev/flutter/rendering/Layer/debugFillProperties.html). Add [DiagnosticsProperty](https://api.flutter.dev/flutter/foundation/DiagnosticsProperty-class.html) objects to the method's argument then call the superclass method.

### Print the layer tree

[#](#print-the-layer-tree)

To debug a compositing issue, use [`debugDumpLayerTree()`](https://api.flutter.dev/flutter/rendering/debugDumpLayerTree.html).

#### Example 6: Call `debugDumpLayerTree()`

[#](#example-6-call-debugdumplayertree)

dart

```
import 'package:material_ui/material_ui.dart';
​
void main() {
  runApp(const MaterialApp(home: AppHome()));
}
​
class AppHome extends StatelessWidget {
  const AppHome({super.key});
​
  @override
  Widget build(BuildContext context) {
    return Material(
      child: Center(
        child: TextButton(
          onPressed: () {
            debugDumpLayerTree();
          },
          child: const Text('Dump Layer Tree'),
        ),
      ),
    );
  }
}
```

**Expand to view the output of layer tree for Example 6**

```
flutter: TransformLayer#214da
flutter:  │ owner: RenderView#ebaaf
flutter:  │ creator: [root]
flutter:  │ engine layer: TransformEngineLayer#535de
flutter:  │ handles: 1
flutter:  │ offset: Offset(0.0, 0.0)
flutter:  │ transform:
flutter:  │   [0] 1.0,0.0,0.0,0.0
flutter:  │   [1] 0.0,1.0,0.0,0.0
flutter:  │   [2] 0.0,0.0,1.0,0.0
flutter:  │   [3] 0.0,0.0,0.0,1.0
flutter:  │
flutter:  ├─child 1: OffsetLayer#0f766
flutter:  │ │ creator: RepaintBoundary ← _FocusInheritedScope ← Semantics ←
flutter:  │ │   FocusScope ← PrimaryScrollController ← _ActionsScope ← Actions
flutter:  │ │   ← Builder ← PageStorage ← Offstage ← _ModalScopeStatus ←
flutter:  │ │   UnmanagedRestorationScope ← ⋯
flutter:  │ │ engine layer: OffsetEngineLayer#1768d
flutter:  │ │ handles: 2
flutter:  │ │ offset: Offset(0.0, 0.0)
flutter:  │ │
flutter:  │ ├─child 1: PictureLayer#dd023
flutter:  │ │   handles: 1
flutter:  │ │   paint bounds: Rect.fromLTRB(0.0, 0.0, 800.0, 600.0)
flutter:  │ │   picture: _NativePicture#36f94
flutter:  │ │   raster cache hints: isComplex = false, willChange = false
flutter:  │ │
flutter:  │ └─child 2: OffsetLayer#4cfc8
flutter:  │   │ creator: RepaintBoundary-[GlobalKey#bd5d9] ← IgnorePointer ←
flutter:  │   │   AnimatedBuilder ← Stack ←
flutter:  │   │   _CupertinoBackGestureDetector<dynamic> ← DecoratedBox ←
flutter:  │   │   DecoratedBoxTransition ← FractionalTranslation ←
flutter:  │   │   SlideTransition ← FractionalTranslation ← SlideTransition ←
flutter:  │   │   CupertinoPageTransition ← ⋯
flutter:  │   │ engine layer: OffsetEngineLayer#a1676
flutter:  │   │ handles: 2
flutter:  │   │ offset: Offset(0.0, 0.0)
flutter:  │   │
flutter:  │   └─child 1: PictureLayer#aee55
flutter:  │       handles: 1
flutter:  │       paint bounds: Rect.fromLTRB(0.0, 0.0, 800.0, 600.0)
flutter:  │       picture: _NativePicture#e732d
flutter:  │       raster cache hints: isComplex = false, willChange = false
flutter:  │
flutter:  └─child 2: PictureLayer#b16e5
flutter:      handles: 1
flutter:      paint bounds: Rect.fromLTRB(0.0, 0.0, 800.0, 600.0)
flutter:      picture: _NativePicture#eef0a
flutter:      raster cache hints: isComplex = false, willChange = false
flutter:
```

The `RepaintBoundary` widget creates:

1. A `RenderRepaintBoundary` RenderObject in the render tree as shown in the **Example 5** results.

   ```
   ╎     └─child: RenderRepaintBoundary#f8f28
   ╎       │ needs compositing
   ╎       │ creator: RepaintBoundary ← _FocusInheritedScope ← Semantics ←
   ╎       │   FocusScope ← PrimaryScrollController ← _ActionsScope ← Actions
   ╎       │   ← Builder ← PageStorage ← Offstage ← _ModalScopeStatus ←
   ╎       │   UnmanagedRestorationScope ← ⋯
   ╎       │ parentData: <none> (can use size)
   ╎       │ constraints: BoxConstraints(w=800.0, h=600.0)
   ╎       │ layer: OffsetLayer#e73b7
   ╎       │ size: Size(800.0, 600.0)
   ╎       │ metrics: 66.7% useful (1 bad vs 2 good)
   ╎       │ diagnosis: insufficient data to draw conclusion (less than five
   ╎       │   repaints)
   ```

2. A new layer in the layer tree as shown in the **Example 6** results.

   ```
   ├─child 1: OffsetLayer#0f766
   │ │ creator: RepaintBoundary ← _FocusInheritedScope ← Semantics ←
   │ │   FocusScope ← PrimaryScrollController ← _ActionsScope ← Actions
   │ │   ← Builder ← PageStorage ← Offstage ← _ModalScopeStatus ←
   │ │   UnmanagedRestorationScope ← ⋯
   │ │ engine layer: OffsetEngineLayer#1768d
   │ │ handles: 2
   │ │ offset: Offset(0.0, 0.0)
   ```

This reduces how much needs to be repainted.

### Print the focus tree

[#](#print-the-focus-tree)

To debug a focus or shortcut issue, dump the focus tree using the [`debugDumpFocusTree()`](https://api.flutter.dev/flutter/widgets/debugDumpFocusTree.html) function.

The `debugDumpFocusTree()` method returns the focus tree for the app.

The focus tree labels nodes in the following way:

* The focused node is labeled `PRIMARY FOCUS`.
* Ancestors of the focus nodes are labeled `IN FOCUS PATH`.

If your app uses the [`Focus`](https://api.flutter.dev/flutter/widgets/Focus-class.html) widget, use the [`debugLabel`](https://api.flutter.dev/flutter/widgets/Focus/debugLabel.html) property to simplify finding its focus node in the tree.

You can also use the [`debugFocusChanges`](https://api.flutter.dev/flutter/widgets/debugFocusChanges.html) boolean property to enable extensive logging when the focus changes.

#### Example 7: Call `debugDumpFocusTree()`

[#](#example-7-call-debugdumpfocustree)

dart

```
import 'package:material_ui/material_ui.dart';
​
void main() {
  runApp(const MaterialApp(home: AppHome()));
}
​
class AppHome extends StatelessWidget {
  const AppHome({super.key});
​
  @override
  Widget build(BuildContext context) {
    return Material(
      child: Center(
        child: TextButton(
          onPressed: () {
            debugDumpFocusTree();
          },
          child: const Text('Dump Focus Tree'),
        ),
      ),
    );
  }
}
```

**Expand to view the focus tree for Example 7**

```
flutter: FocusManager#9d096
flutter:  │ primaryFocus: FocusScopeNode#926dc(_ModalScopeState<dynamic>
flutter:  │   Focus Scope [PRIMARY FOCUS])
flutter:  │ primaryFocusCreator: FocusScope ← PrimaryScrollController ←
flutter:  │   _ActionsScope ← Actions ← Builder ← PageStorage ← Offstage ←
flutter:  │   _ModalScopeStatus ← UnmanagedRestorationScope ←
flutter:  │   RestorationScope ← AnimatedBuilder ←
flutter:  │   _ModalScope<dynamic>-[LabeledGlobalKey<_ModalScopeState<dynamic>>#bd53e]
flutter:  │   ← Semantics ← _RenderTheaterMarker ← _EffectiveTickerMode ←
flutter:  │   TickerMode ←
flutter:  │   _OverlayEntryWidget-[LabeledGlobalKey<_OverlayEntryWidgetState>#89dd7]
flutter:  │   ← _Theater ← Overlay-[LabeledGlobalKey<OverlayState>#52f82] ←
flutter:  │   UnmanagedRestorationScope ← ⋯
flutter:  │
flutter:  └─rootScope: FocusScopeNode#f4205(Root Focus Scope [IN FOCUS PATH])
flutter:    │ IN FOCUS PATH
flutter:    │ focusedChildren: FocusScopeNode#a0d10(Navigator Scope [IN FOCUS
flutter:    │   PATH])
flutter:    │
flutter:    └─Child 1: FocusNode#088ec([IN FOCUS PATH])
flutter:      │ context: Focus
flutter:      │ NOT FOCUSABLE
flutter:      │ IN FOCUS PATH
flutter:      │
flutter:      └─Child 1: FocusNode#85f70(Shortcuts [IN FOCUS PATH])
flutter:        │ context: Focus
flutter:        │ NOT FOCUSABLE
flutter:        │ IN FOCUS PATH
flutter:        │
flutter:        └─Child 1: FocusNode#f0c18(Shortcuts [IN FOCUS PATH])
flutter:          │ context: Focus
flutter:          │ NOT FOCUSABLE
flutter:          │ IN FOCUS PATH
flutter:          │
flutter:          └─Child 1: FocusNode#0749f(Shortcuts [IN FOCUS PATH])
flutter:            │ context: Focus
flutter:            │ NOT FOCUSABLE
flutter:            │ IN FOCUS PATH
flutter:            │
flutter:            └─Child 1: _FocusTraversalGroupNode#28990(FocusTraversalGroup [IN FOCUS PATH])
flutter:              │ context: Focus
flutter:              │ NOT FOCUSABLE
flutter:              │ IN FOCUS PATH
flutter:              │
flutter:              └─Child 1: FocusNode#5b515(Shortcuts [IN FOCUS PATH])
flutter:                │ context: Focus
flutter:                │ NOT FOCUSABLE
flutter:                │ IN FOCUS PATH
flutter:                │
flutter:                └─Child 1: FocusScopeNode#a0d10(Navigator Scope [IN FOCUS PATH])
flutter:                  │ context: FocusScope
flutter:                  │ IN FOCUS PATH
flutter:                  │ focusedChildren: FocusScopeNode#926dc(_ModalScopeState<dynamic>
flutter:                  │   Focus Scope [PRIMARY FOCUS])
flutter:                  │
flutter:                  └─Child 1: _FocusTraversalGroupNode#72c8a(FocusTraversalGroup [IN FOCUS PATH])
flutter:                    │ context: Focus
flutter:                    │ NOT FOCUSABLE
flutter:                    │ IN FOCUS PATH
flutter:                    │
flutter:                    └─Child 1: FocusNode#eb709(Navigator [IN FOCUS PATH])
flutter:                      │ context: Focus
flutter:                      │ IN FOCUS PATH
flutter:                      │
flutter:                      └─Child 1: FocusScopeNode#926dc(_ModalScopeState<dynamic> Focus Scope [PRIMARY FOCUS])
flutter:                        │ context: FocusScope
flutter:                        │ PRIMARY FOCUS
flutter:                        │
flutter:                        └─Child 1: FocusNode#a6b74
flutter:                            context: Focus
flutter:
```

### Print the semantics tree

[#](#print-the-semantics-tree)

The `debugDumpSemanticsTree()` function prints the semantic tree for the app.

The Semantics tree is presented to the system accessibility APIs. To obtain a dump of the Semantics tree:

1. Enable accessibility using a system accessibility tool or the `SemanticsDebugger`
2. Use the [`debugDumpSemanticsTree()`](https://api.flutter.dev/flutter/rendering/debugDumpSemanticsTree.html) function.

#### Example 8: Call `debugDumpSemanticsTree()`

[#](#example-8-call-debugdumpsemanticstree)

dart

```
import 'package:flutter/foundation.dart';
import 'package:flutter/rendering.dart';
import 'package:material_ui/material_ui.dart';
​
void main() {
  runApp(const MaterialApp(home: AppHome()));
}
​
class AppHome extends StatelessWidget {
  const AppHome({super.key});
​
  @override
  Widget build(BuildContext context) {
    return Material(
      child: Center(
        child: Semantics(
          button: true,
          enabled: true,
          label: 'Clickable text here!',
          child: GestureDetector(
            onTap: () {
              debugDumpSemanticsTree();
              if (kDebugMode) {
                print('Clicked!');
              }
            },
            child: const Text('Click Me!', style: TextStyle(fontSize: 56)),
          ),
        ),
      ),
    );
  }
}
```

**Expand to view the semantic tree for Example 8**

```
flutter: SemanticsNode#0
flutter:  │ Rect.fromLTRB(0.0, 0.0, 800.0, 600.0)
flutter:  │
flutter:  └─SemanticsNode#1
flutter:    │ Rect.fromLTRB(0.0, 0.0, 800.0, 600.0)
flutter:    │ textDirection: ltr
flutter:    │
flutter:    └─SemanticsNode#2
flutter:      │ Rect.fromLTRB(0.0, 0.0, 800.0, 600.0)
flutter:      │ sortKey: OrdinalSortKey#824a2(order: 0.0)
flutter:      │
flutter:      └─SemanticsNode#3
flutter:        │ Rect.fromLTRB(0.0, 0.0, 800.0, 600.0)
flutter:        │ flags: scopesRoute
flutter:        │
flutter:        └─SemanticsNode#4
flutter:            Rect.fromLTRB(278.0, 267.0, 522.0, 333.0)
flutter:            actions: tap
flutter:            flags: isButton, hasEnabledState, isEnabled
flutter:            label:
flutter:              "Clickable text here!
flutter:              Click Me!"
flutter:            textDirection: ltr
flutter:
flutter: Clicked!
```

### Print event timings

[#](#print-event-timings)

If you want to find out where your events happen relative to the frame's begin and end, you can set prints to log these events. To print the beginning and end of the frames to the console, toggle the [`debugPrintBeginFrameBanner`](https://api.flutter.dev/flutter/scheduler/debugPrintBeginFrameBanner.html) and the [`debugPrintEndFrameBanner`](https://api.flutter.dev/flutter/scheduler/debugPrintEndFrameBanner.html).

**The print frame banner log for Example 1**

```
I/flutter : ▄▄▄▄▄▄▄▄ Frame 12         30s 437.086ms ▄▄▄▄▄▄▄▄
I/flutter : Debug print: Am I performing this work more than once per frame?
I/flutter : Debug print: Am I performing this work more than once per frame?
I/flutter : ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
```

To print the call stack causing the current frame to be scheduled, use the [`debugPrintScheduleFrameStacks`](https://api.flutter.dev/flutter/scheduler/debugPrintScheduleFrameStacks.html) flag.

## Debug layout issues

[#](#debug-layout-issues)

To debug a layout problem using a GUI, set [`debugPaintSizeEnabled`](https://api.flutter.dev/flutter/rendering/debugPaintSizeEnabled.html) to `true`. This flag can be found in the `rendering` library. You can enable it at any time and affects all painting while `true`. Consider adding it to the top of your `void main()` entry point.

#### Example 9

[#](#example-9)

See an example in the following code:

dart

```
// Add import to the Flutter rendering library.
import 'package:flutter/rendering.dart';
import 'package:material_ui/material_ui.dart';
​
void main() {
  debugPaintSizeEnabled = true;
  runApp(const MyApp());
}
```

When enabled, Flutter displays the following changes to your app:

* Displays all boxes in a bright teal border.
* Displays all padding as a box with a faded blue fill and blue border around the child widget.
* Displays all alignment positioning with yellow arrows.
* Displays all spacers in gray, when they have no child.

The [`debugPaintBaselinesEnabled`](https://api.flutter.dev/flutter/rendering/debugPaintBaselinesEnabled.html) flag does something similar but for objects with baselines. The app displays the baseline for alphabetic characters in bright green and the baseline for ideographic characters in orange. Alphabetic characters "sit" on the alphabetic baseline, but that baseline "cuts" through the bottom of [CJK characters](https://en.wikipedia.org/wiki/CJK_characters). Flutter positions the ideographic baseline at the very bottom of the text line.

The [`debugPaintPointersEnabled`](https://api.flutter.dev/flutter/rendering/debugPaintPointersEnabled.html) flag turns on a special mode that highlights any objects that you tap in teal. This can help you determine if an object fails to hit test. This might happen if the object falls outside the bounds of its parent and thus not considered for hit testing in the first place.

If you're trying to debug compositor layers, consider using the following flags.

* Use the [`debugPaintLayerBordersEnabled`](https://api.flutter.dev/flutter/rendering/debugPaintLayerBordersEnabled.html) flag to find the boundaries of each layer. This flag results in outlining each layer's bounds in orange.

* Use the [`debugRepaintRainbowEnabled`](https://api.flutter.dev/flutter/rendering/debugRepaintRainbowEnabled.html) flag to display a repainted layer. Whenever a layer repaints, it overlays with a rotating set of colors.

Any function or method in the Flutter framework that starts with `debug...` only works in [debug mode](/testing/build-modes#debug).

## Debug animation issues

[#](#debug-animation-issues)

Set the [`timeDilation`](https://api.flutter.dev/flutter/scheduler/timeDilation.html) variable (from the `scheduler` library) to a number greater than 1.0, for instance, 50.0. It's best to only set this once on app startup. If you change it on the fly, especially if you reduce it while animations are running, it's possible that the framework will observe time going backwards, which will probably result in asserts and generally interfere with your efforts.

## Debug performance issues

[#](#debug-performance-issues)

Flutter provides a wide variety of top-level properties and functions to help you debug your app at various points along the development cycle. To use these features, compile your app in debug mode.

The following list highlights some flags and one function from the [rendering library](https://api.flutter.dev/flutter/rendering/rendering-library.html) for debugging performance issues.

* [`debugDumpRenderTree()`](https://api.flutter.dev/flutter/rendering/debugDumpRenderTree.html)

  To dump the rendering tree to the console, call this function when not in a layout or repaint phase.

  To set these flags either:

  * Edit the framework code.
  * Import the module, set the value in your `main()` function, then hot restart.

* [`debugPaintLayerBordersEnabled`](https://api.flutter.dev/flutter/rendering/debugPaintLayerBordersEnabled.html)

  To display the boundaries of each layer, set this property to `true`. When set, each layer paints a box around its boundary.

* [`debugRepaintRainbowEnabled`](https://api.flutter.dev/flutter/rendering/debugRepaintRainbowEnabled.html)

  To display a colored border around each widget, set this property to `true`. These borders change color as the app user scrolls in the app. To set this flag, add `debugRepaintRainbowEnabled = true;` as a top-level property in your app. If any static widgets rotate through colors after setting this flag, consider adding repaint boundaries to those areas.

* [`debugPrintMarkNeedsLayoutStacks`](https://api.flutter.dev/flutter/rendering/debugPrintMarkNeedsLayoutStacks.html)

  To determine if your app creates more layouts than expected, set this property to `true`. This layout issue could happen on the timeline, on a profile, or from a `print` statement inside a layout method. When set, the framework outputs stack traces to the console to explain why your app marks each render object to be laid out.

* [`debugPrintMarkNeedsPaintStacks`](https://api.flutter.dev/flutter/rendering/debugPrintMarkNeedsPaintStacks.html)

  To determine if your app paints more layouts than expected, set this property to `true`.

You can generate stack traces on demand as well. To print your own stack traces, add the `debugPrintStack()` function to your app.

### Trace Dart code performance

[#](#trace-dart-code-performance)

To perform custom performance traces and measure wall or CPU time of arbitrary segments of Dart code, use `dart:developer` [Timeline](https://api.dart.dev/dart-developer/Timeline-class.html) utilities.

1. Open your source code.

2. Wrap the code you want to measure in `Timeline` methods.

   dart

   ```
   import 'dart:developer';
   ​
   void main() {
     Timeline.startSync('interesting function');
     // iWonderHowLongThisTakes();
     Timeline.finishSync();
   }
   ```

3. While connected to your app, open DevTools' [Timeline events tab](/tools/devtools/performance#timeline-events-tab).

4. Select the **Dart** recording option in the **Performance settings**.

5. Perform the function you want to measure.

To ensure that the runtime performance characteristics closely match that of your final product, run your app in [profile mode](/testing/build-modes#profile).

### Add performance overlay

[#](#add-performance-overlay)

To enable the `PerformanceOverlay` widget in your code, set the `showPerformanceOverlay` property to `true` on the [`MaterialApp`](https://pub.dev/documentation/material_ui/latest/material_ui/MaterialApp/MaterialApp.html), [`CupertinoApp`](https://pub.dev/documentation/cupertino_ui/latest/cupertino_ui/CupertinoApp-class.html), or [`WidgetsApp`](https://api.flutter.dev/flutter/widgets/WidgetsApp-class.html) constructor:

#### Example 10

[#](#example-10)

dart

```
import 'package:material_ui/material_ui.dart';
​
class MyApp extends StatelessWidget {
  const MyApp({super.key});
​
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      showPerformanceOverlay: true,
      title: 'My Awesome App',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
      ),
      home: const MyHomePage(title: 'My Awesome App'),
    );
  }
}
```

(If you're not using `MaterialApp`, `CupertinoApp`, or `WidgetsApp`, you can get the same effect by wrapping your application in a stack and putting a widget on your stack that was created by calling [`PerformanceOverlay.allEnabled()`](https://api.flutter.dev/flutter/widgets/PerformanceOverlay/PerformanceOverlay.allEnabled.html).)

To learn how to interpret the graphs in the overlay, check out [The performance overlay](/perf/ui-performance#the-performance-overlay) in [Profiling Flutter performance](/perf/ui-performance).

## Add widget alignment grid

[#](#add-widget-alignment-grid)

To add an overlay to a [Material Design baseline grid](https://m3.material.io/foundations/layout/understanding-layout/spacing) on your app to help verify alignments, add the `debugShowMaterialGrid` argument in the [`MaterialApp` constructor](https://pub.dev/documentation/material_ui/latest/material_ui/MaterialApp/MaterialApp.html).

To add an overlay to non-Material applications, add a [`GridPaper`](https://api.flutter.dev/flutter/widgets/GridPaper-class.html) widget.

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-09-29. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/testing/code-debugging.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/testing/code-debugging\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/testing/code-debugging.md "Report an issue with this page").

----
url: https://docs.flutter.dev/ai/genui
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top GenUI SDK for Flutter](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

## What is GenUI?

[#](#what-is-genui)

At its core, the GenUI SDK for Flutter is an orchestration layer. This suite of packages coordinates the flow of information between your user, your Flutter widgets, and an AI agent, transforming text-based conversations into rich, interactive experiences.

Imagine that, instead of presenting your user with a wall of text, they are presented with a graphical UI consisting of (for example), a row of labeled buttons and a date picker.

The GenUI SDK for Flutter uses a JSON-based format to compose a UI from your existing widget catalog. As a user interacts with the UI, state changes are fed back to the agent, creating a high-bandwidth loop and turning an agent interaction into a rich, intuitive experience.

The GenUI SDK for Flutter is designed to easily integrate into your Flutter application.

## When would you use it?

[#](#when-would-you-use-it)

Use GenUI SDK for Flutter to incorporate graphical UI into your app. For example:

* Instead of describing a list of products in text, use it to render a clickable carousel of product widgets.
* When a user asks to plan a trip, use it to generate a complete form with sliders, date pickers, and text fields.

For more context about GenUI SDK for Flutter, check out the [Getting started with GenUI video](https://www.youtube.com/watch?v=nWr6eZKM6no):

[Watch on YouTube in a new tab: "Getting started with GenUI"](https://www.youtube.com/watch/nWr6eZKM6no)

Also, check out the Flutter + A2UI = GenUI video from Google I/O 2026!

[Watch on YouTube in a new tab: "Flutter + A2UI = GenUI"](https://www.youtube.com/watch/tXeyaV1gVJk)

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-08-19. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/ai/genui/index.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/ai/genui\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/ai/genui/index.md "Report an issue with this page").

----
url: https://docs.flutter.dev/tools/devtools/app-size
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Use the app size tool](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

## What is it?

[#](#what-is-it)

The app size tool allows you to analyze the total size of your app. You can view a single snapshot of "size information" using the [Analysis tab](#analysis-tab), or compare two different snapshots of "size information" using the [Diff tab](#diff-tab).

### What is "size information"?

[#](#what-is-size-information)

"Size information" contains size data for Dart code, native code, and non-code elements of your app, like the application package, assets and fonts. A "size information" file contains data for the total picture of your application size.

### Dart size information

[#](#dart-size-information)

The Dart AOT compiler performs tree-shaking on your code when compiling your application (profile or release mode only—the AOT compiler is not used for debug builds, which are JIT compiled). This means that the compiler attempts to optimize your app's size by removing pieces of code that are unused or unreachable.

After the compiler optimizes your code as much as it can, the end result can be summarized as the collection of packages, libraries, classes, and functions that exist in the binary output, along with their size in bytes. This is the Dart portion of "size information" we can analyze in the app size tool to further optimize Dart code and track down size issues.

## How to use it

[#](#how-to-use-it)

If DevTools is already connected to a running application, navigate to the "App Size" tab.

If DevTools is not connected to a running application, you can access the tool from the landing page that appears once you have launched DevTools (see [launch instructions](/tools/devtools#start)).

## Analysis tab

[#](#analysis-tab)

The analysis tab allows you to inspect a single snapshot of size information. You can view the hierarchical structure of the size data using the treemap and table, and you can view code attribution data (for example, why a piece of code is included in your compiled application) using the dominator tree and call graph.

### Loading a size file

[#](#loading-a-size-file)

When you open the Analysis tab, you'll see instructions to load an app size file. Drag and drop an app size file into the dialog, and click "Analyze Size".

See [Generating size files](#generating-size-files) below for information on generating size files.

### Treemap and table

[#](#treemap-and-table)

The treemap and table show the hierarchical data for your app's size.

#### Use the treemap

[#](#use-the-treemap)

A treemap is a visualization for hierarchical data. The space is broken up into rectangles, where each rectangle is sized and ordered by some quantitative variable (in this case, size in bytes). The area of each rectangle is proportional to the size the node occupies in the compiled application. Inside of each rectangle (call one A), there are additional rectangles that exist one level deeper in the data hierarchy (children of A).

To drill into a cell in the treemap, select the cell. This re-roots the tree so that the selected cell becomes the visual root of the treemap.

To navigate back, or up a level, use the breadcrumb navigator at the top of the treemap.

### Dominator tree and call graph

[#](#dominator-tree-and-call-graph)

This section of the page shows code size attribution data (for example, why a piece of code is included in your compiled application). This data is visible in the form of a dominator tree as well as a call graph.

#### Use the dominator tree

[#](#use-the-dominator-tree)

A [dominator tree](https://en.wikipedia.org/wiki/Dominator_\(graph_theory\)) is a tree where each node's children are those nodes it immediately dominates. A node `a` is said to "dominate" a node `b` if every path to `b` must go through `a`.

To put it in context of app size analysis, imagine `package:a` imports both `package:b` and `package:c`, and both `package:b` and `package:c` import `package:d`.

```
package:a
|__ package:b
|   |__ package:d
|__ package:c
    |__ package:d
```

In this example, `package:a` dominates `package:d`, so the dominator tree for this data would look like:

```
package:a
|__ package:b
|__ package:c
|__ package:d
```

This information is helpful for understanding why certain pieces of code are present in your compiled application. For example, if you are analyzing your app size and find an unexpected package included in your compiled app, you can use the dominator tree to trace the package to its root source.

#### Use the call graph

[#](#use-the-call-graph)

A call graph provides similar information to the dominator tree in regards to helping you understand why code exists in a compiled application. However, instead of showing the one-to-many dominant relationships between nodes of code size data like the dominator tree, the call graph shows the many-to-many relationships that existing between nodes of code size data.

Again, using the following example:

```
package:a
|__ package:b
|   |__ package:d
|__ package:c
    |__ package:d
```

The call graph for this data would link `package:d` to its direct callers, `package:b` and `package:c`, instead of its "dominator", `package:a`.

```
package:a --> package:b -->
                              package:d
package:a --> package:c -->
```

This information is useful for understanding the fine-grained dependencies of between pieces of your code (packages, libraries, classes, functions).

#### Should I use the dominator tree or the call graph?

[#](#should-i-use-the-dominator-tree-or-the-call-graph)

Use the dominator tree if you want to understand the *root* cause for why a piece of code is included in your application. Use the call graph if you want to understand all the call paths to and from a piece of code.

A dominator tree is an analysis or slice of call graph data, where nodes are connected by "dominance" instead of parent-child hierarchy. In the case where a parent node dominates a child, the relationship in the call graph and the dominator tree would be identical, but this is not always the case.

In the scenario where the call graph is complete (an edge exists between every pair of nodes), the dominator tree would show the that `root` is the dominator for every node in the graph. This is an example where the call graph would give you a better understanding around why a piece of code is included in your application.

## Diff tab

[#](#diff-tab)

The diff tab allows you to compare two snapshots of size information. The two size information files you are comparing should be generated from two different versions of the same app; for example, the size file generated before and after changes to your code. You can visualize the difference between the two data sets using the treemap and table.

### Loading size files

[#](#loading-size-files)

When you open the **Diff** tab, you'll see instructions to load "old" and "new" size files. Again, these files need to be generated from the same application. Drag and drop these files into their respective dialogs, and click **Analyze Diff**.

See [Generating size files](#generating-size-files) below for information on generating these files.

### Treemap and table

[#](#treemap-and-table-1)

In the diff view, the treemap and tree table show only data that differs between the two imported size files.

For questions about using the treemap, see [Use the treemap](#use-the-treemap) above.

## Generating size files

[#](#generating-size-files)

To use the app size tool, you'll need to generate a Flutter size analysis file. This file contains size information for your entire application (native code, Dart code, assets, fonts, etc.), and you can generate it using the `--analyze-size` flag:

```
flutter build <your target platform> --analyze-size
```

This builds your application, prints a size summary to the command line, and prints a line telling you where to find the size analysis file.

```
flutter build apk --analyze-size --target-platform=android-arm64
...
▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
app-release.apk (total compressed)                               6 MB
...
▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
A summary of your APK analysis can be found at: build/apk-code-size-analysis_01.json
```

In this example, import the `build/apk-code-size-analysis_01.json` file into the app size tool to analyze further. For more information, see [App Size Documentation](/perf/app-size#breaking-down-the-size).

## Other resources

[#](#other-resources)

To learn how to perform a step-by-step size analysis of the Wonderous App using DevTools, check out the [App Size Tool tutorial](https://medium.com/@fluttergems/mastering-dart-flutter-devtools-app-size-tool-part-3-of-8-9be6e9ec42a2). Various strategies to reduce an app's size are also discussed.

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-07-31. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/app-size.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/tools/devtools/app-size\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/app-size.md "Report an issue with this page").

----
url: https://docs.flutter.dev/perf/app-size.md
----

\# Measuring your app's size > How to measure app size for iOS and Android. Many developers are concerned with the size of their compiled app. As the APK, app bundle, or IPA version of a Flutter app is self-contained and holds all the code and assets needed to run the app, its size can be a concern. The larger an app, the more space it requires on a device, the longer it takes to download, and it might break the limit of useful features like Android instant apps. ## Debug builds are not representative By default, launching your app with \`flutter run\`, or by clicking the \*\*Play\*\* button in your IDE, generates a \_debug\_ build of the Flutter app. The app size of a debug build is large due to the debugging overhead that allows for hot reload and source-level debugging. As such, it is not representative of a production app end users download. ## Checking the total size A default release build, such as one created by \`flutter build apk\` or \`flutter build ios\`, is built to conveniently assemble your upload package to the Play Store and App Store. As such, they're also not representative of your end-users' download size. The stores generally reprocess and split your upload package to target the specific downloader and the downloader's hardware, such as filtering for assets targeting the phone's DPI, filtering native libraries targeting the phone's CPU architecture. ### Estimating total size To get the closest approximate size on each platform, use the following instructions. #### Android Follow the Google \[Play Console's instructions]\[] for checking app download and install sizes. Produce an upload package for your application: \`\`\`console flutter build appbundle \`\`\` Log into your \[Google Play Console]\[]. Upload your application binary by drag dropping the .aab file. View the application's download and install size in the \*\*Android vitals\*\* -> \*\*App size\*\* tab. The download size is calculated based on an XXXHDPI (\~640dpi) device on an arm64-v8a architecture. Your end users' download sizes might vary depending on their hardware. The top tab has a toggle for download size and install size. The page also contains optimization tips further below. #### iOS Create an \[Xcode App Size Report]\[]. First, by configuring the app version and build as described in the \[iOS create build archive instructions]\[]. Then: 1. Run \`flutter build ipa --export-method development\`. 1. Run \`open build/ios/archive/\*.xcarchive\` to open the archive in Xcode. 1. Click \*\*Distribute App\*\*. 1. Select a method of distribution. \*\*Development\*\* is the simplest if you don't intend to distribute the application. 1. In \*\*App Thinning\*\*, select 'all compatible device variants'. 1. Select \*\*Strip Swift symbols\*\*. Sign and export the IPA. The exported directory contains \`App Thinning Size Report.txt\` with details about your projected application size on different devices and versions of iOS. The App Size Report for the default demo app in Flutter 1.17 shows: \`\`\`plaintext Variant: Runner-7433FC8E-1DF4-4299-A7E8-E00768671BEB.ipa Supported variant descriptors: \[device: iPhone12,1, os-version: 13.0] and \[device: iPhone11,8, os-version: 13.0] App + On Demand Resources size: 5.4 MB compressed, 13.7 MB uncompressed App size: 5.4 MB compressed, 13.7 MB uncompressed On Demand Resources size: Zero KB compressed, Zero KB uncompressed \`\`\` In this example, the app has an approximate download size of 5.4 MB and an approximate installation size of 13.7 MB on an iPhone12,1 (\[Model ID / Hardware number]\[] for iPhone 11) and iPhone11,8 (iPhone XR) running iOS 13.0. To measure an iOS app exactly, you have to upload a release IPA to Apple's App Store Connect (\[instructions]\[]) and obtain the size report from there. IPAs are commonly larger than APKs as explained in \[How big is the Flutter engine?]\[], a section in the Flutter \[FAQ]\[]. ## Breaking down the size Starting in Flutter version 1.22 and DevTools version 0.9.1, a size analysis tool is included to help developers understand the breakdown of the release build of their application. :::warning As stated in the \[checking total size]\(#checking-the-total-size) section above, an upload package is not representative of your end users' download size. Be aware that redundant native library architectures and asset densities seen in the breakdown tool can be filtered by the Play Store and App Store. ::: The size analysis tool is invoked by passing the \`--analyze-size\` flag when building: - \`flutter build apk --analyze-size\` - \`flutter build appbundle --analyze-size\` - \`flutter build ios --analyze-size\` - \`flutter build linux --analyze-size\` - \`flutter build macos --analyze-size\` - \`flutter build windows --analyze-size\` This build is different from a standard release build in two ways. 1. The tool compiles Dart in a way that records code size usage of Dart packages. 2. The tool displays a high level summary of the size breakdown in the terminal, and leaves a \`\*-code-size-analysis\_\*.json\` file for more detailed analysis in DevTools. In addition to analyzing a single build, two builds can also be diffed by loading two \`\*-code-size-analysis\_\*.json\` files into DevTools. Check out the \[DevTools documentation]\[] for details. Through the summary, you can get a quick idea of the size usage per category (such as asset, native code, Flutter libraries, etc). The compiled Dart native library is further broken down by package for quick analysis. :::warning This tool on iOS creates a .app rather than an IPA. Use this tool to evaluate the relative size of the .app's content. To get a closer estimate of the download size, reference the \[Estimating total size]\(#estimating-total-size) section above. ::: ### Deeper analysis in DevTools The \`\*-code-size-analysis\_\*.json\` file produced above can be further analyzed in deeper detail in DevTools where a tree or a treemap view can break down the contents of the application into the individual file level and up to function level for the Dart AOT artifact. This can be done by \`dart devtools\`, selecting \`Open app size tool\` and uploading the JSON file. For further information on using the DevTools app size tool, check out the \[DevTools documentation]\[]. ## Reducing app size When building a release version of your app, consider using the \`--split-debug-info\` tag. This tag can dramatically reduce code size. For an example of using this tag, see \[Obfuscating Dart code]\[]. Some other things you can do to make your app smaller are: \* Remove unused resources \* Minimize resource imported from libraries \* Compress PNG and JPEG files Another way to reduce app size is by using platform-specific code. The Dart compiler removes code that is unreachable on the target platform. For example, if you have code that is specific to Windows, you can wrap it in a check using the \`Platform\` class from \`dart:io\`, like \`if (Platform.isWindows)\`. When you build the app for Android, the compiler sees that this check is always false and removes the Windows-specific code from the release build. \[FAQ]: /resources/faq \[How big is the Flutter engine?]: /resources/faq#how-big-is-the-flutter-engine \[instructions]: /deployment/ios \[Xcode App Size Report]: https\://developer.apple.com/documentation/xcode/reducing\_your\_app\_s\_size#3458589 \[iOS create build archive instructions]: /deployment/ios#update-the-apps-build-and-version-numbers \[Model ID / Hardware number]: https\://en.wikipedia.org/wiki/List\_of\_iOS\_devices#Models \[Obfuscating Dart code]: /deployment/obfuscate \[Play Console's instructions]: https\://support.google.com/googleplay/android-developer/answer/9302563?hl=en \[Google Play Console]: https\://play.google.com/apps/publish/ \[DevTools documentation]: /tools/devtools/app-size

----
url: https://docs.flutter.dev/cookbook/effects/expandable-fab.md
----

\# Create an expandable FAB > How to implement a FAB that expands to multiple buttons when tapped.

<!--?code-excerpt path-base="cookbook/effects/expandable_fab"?-->

A Floating Action Button (FAB) is a round button that floats near the bottom right of a content area. This button represents the primary action for the corresponding content, but sometimes, there is no primary action. Instead, there are a few critical actions that the user might take. In this case, you could create an expandable FAB like the one shown in the following figure. When pressed, this expandable FAB spawns multiple, other action buttons. Each button corresponds to one of those critical actions. The following animation shows the app's behavior: !\[Expanding and collapsing the FAB]\(/assets/images/docs/cookbook/effects/ExpandingFAB.webp){:.site-mobile-screenshot} ## Create an ExpandableFab widget Start by creating a new stateful widget called \`ExpandableFab\`. This widget displays the primary FAB and coordinates the expansion and collapse of the other action buttons. The widget takes in parameters for whether or not the \`ExpandedFab\` begins in the expanded position, what the maximum distance of each action button is, and a list of children. You'll use the list later to provide the other action buttons.

<!--?code-excerpt "lib/excerpt1.dart (ExpandableFab)"?-->

\`\`\`dart @immutable class ExpandableFab extends StatefulWidget { const ExpandableFab({ super.key, this.initialOpen, required this.distance, required this.children, }); final bool? initialOpen; final double distance; final List children; @override State createState() => \_ExpandableFabState(); } class \_ExpandableFabState extends State { @override Widget build(BuildContext context) { return const SizedBox(); } } \`\`\` ## FAB cross-fade The \`ExpandableFab\` displays a blue edit button when collapsed and a white close button when expanded. When expanding and collapsing, these two buttons scale and fade between one another. Implement the expand and collapse cross-fade between the two different FABs.

<!--?code-excerpt "lib/excerpt2.dart (ExpandableFabState)"?-->

\`\`\`dart class \_ExpandableFabState extends State { bool \_open = false; @override void initState() { super.initState(); \_open = widget.initialOpen ?? false; } void \_toggle() { setState(() { \_open = !\_open; }); } @override Widget build(BuildContext context) { return SizedBox.expand( child: Stack( alignment: Alignment.bottomRight, clipBehavior: Clip.none, children: \[\_buildTapToCloseFab(), \_buildTapToOpenFab()], ), ); } Widget \_buildTapToCloseFab() { return SizedBox( width: 56, height: 56, child: Center( child: Material( shape: const CircleBorder(), clipBehavior: Clip.antiAlias, elevation: 4, child: InkWell( onTap: \_toggle, child: Padding( padding: const EdgeInsets.all(8), child: Icon(Icons.close, color: Theme.of(context).primaryColor), ), ), ), ), ); } Widget \_buildTapToOpenFab() { return IgnorePointer( ignoring: \_open, child: AnimatedContainer( transformAlignment: Alignment.center, transform: Matrix4.diagonal3Values( \_open ? 0.7 : 1.0, \_open ? 0.7 : 1.0, 1.0, ), duration: const Duration(milliseconds: 250), curve: const Interval(0.0, 0.5, curve: Curves.easeOut), child: AnimatedOpacity( opacity: \_open ? 0.0 : 1.0, curve: const Interval(0.25, 1.0, curve: Curves.easeInOut), duration: const Duration(milliseconds: 250), child: FloatingActionButton( onPressed: \_toggle, child: const Icon(Icons.create), ), ), ), ); } } \`\`\` The open button sits on top of the close button within a \`Stack\`, allowing for the visual appearance of a cross-fade as the top button appears and disappears. To achieve the cross-fade animation, the open button uses an \`AnimatedContainer\` with a scale transform and an \`AnimatedOpacity\`. The open button scales down and fades out when the \`ExpandableFab\` goes from collapsed to expanded. Then, the open button scales up and fades in when the \`ExpandableFab\` goes from expanded to collapsed. You'll notice that the open button is wrapped with an \`IgnorePointer\` widget. This is because the open button always exists, even when it's transparent. Without the \`IgnorePointer\`, the open button always receives the tap event, even when the close button is visible. ## Create an ActionButton widget Each of the buttons that expand from the \`ExpandableFab\` have the same design. They're blue circles with white icons. More precisely, the button background color is the \`ColorScheme.secondary\` color, and the icon color is \`ColorScheme.onSecondary\`. Define a new stateless widget called \`ActionButton\` to display these round buttons.

<!--?code-excerpt "lib/main.dart (ActionButton)"?-->

\`\`\`dart @immutable class ActionButton extends StatelessWidget { const ActionButton({super.key, this.onPressed, required this.icon}); final VoidCallback? onPressed; final Widget icon; @override Widget build(BuildContext context) { final theme = Theme.of(context); return Material( shape: const CircleBorder(), clipBehavior: Clip.antiAlias, color: theme.colorScheme.secondary, elevation: 4, child: IconButton( onPressed: onPressed, icon: icon, color: theme.colorScheme.onSecondary, ), ); } } \`\`\` Pass a few instances of this new \`ActionButton\` widget into your \`ExpandableFab\`.

<!--?code-excerpt "lib/main.dart (FloatingActionButton)"?-->

\`\`\`dart floatingActionButton: ExpandableFab( distance: 112, children: \[ ActionButton( onPressed: () => \_showAction(context, 0), icon: const Icon(Icons.format\_size), ), ActionButton( onPressed: () => \_showAction(context, 1), icon: const Icon(Icons.insert\_photo), ), ActionButton( onPressed: () => \_showAction(context, 2), icon: const Icon(Icons.videocam), ), ], ), \`\`\` ## Expand and collapse the action buttons The child \`ActionButton\`s should fly out from under the open FAB when expanded. Then, the child \`ActionButton\`s should fly back under the open FAB when collapsed. This motion requires explicit (x,y) positioning of each \`ActionButton\` and an \`Animation\` to choreograph changes to those (x,y) positions over time. Introduce an \`AnimationController\` and an \`Animation\` to control the rate at which the various \`ActionButton\`s expand and collapse.

<!--?code-excerpt "lib/excerpt3.dart (ExpandableFabState3)" replace="/\/\/ code-excerpt-closing-bracket/}/g"?-->

\`\`\`dart class \_ExpandableFabState extends State with SingleTickerProviderStateMixin { late final AnimationController \_controller; late final Animation \_expandAnimation; bool \_open = false; @override void initState() { super.initState(); \_open = widget.initialOpen ?? false; \_controller = AnimationController( value: \_open ? 1.0 : 0.0, duration: const Duration(milliseconds: 250), vsync: this, ); \_expandAnimation = CurvedAnimation( curve: Curves.fastOutSlowIn, reverseCurve: Curves.easeOutQuad, parent: \_controller, ); } @override void dispose() { \_controller.dispose(); super.dispose(); } void \_toggle() { setState(() { \_open = !\_open; if (\_open) { \_controller.forward(); } else { \_controller.reverse(); } }); } } \`\`\` Next, introduce a new stateless widget called \`\_ExpandingActionButton\`, and configure this widget to animate and position an individual \`ActionButton\`. The \`ActionButton\` is provided as a generic \`Widget\` called \`child\`.

<!--?code-excerpt "lib/excerpt3.dart (ExpandingActionButton)"?-->

\`\`\`dart @immutable class \_ExpandingActionButton extends StatelessWidget { const \_ExpandingActionButton({ required this.directionInDegrees, required this.maxDistance, required this.progress, required this.child, }); final double directionInDegrees; final double maxDistance; final Animation progress; final Widget child; @override Widget build(BuildContext context) { return AnimatedBuilder( animation: progress, builder: (context, child) { final offset = Offset.fromDirection( directionInDegrees \* (math.pi / 180.0), progress.value \* maxDistance, ); return Positioned( right: 4.0 + offset.dx, bottom: 4.0 + offset.dy, child: Transform.rotate( angle: (1.0 - progress.value) \* math.pi / 2, child: child!, ), ); }, child: FadeTransition(opacity: progress, child: child), ); } } \`\`\` The most important part of \`\_ExpandingActionButton\` is the \`Positioned\` widget, which positions the \`child\` at a specific (x,y) coordinate within the surrounding \`Stack\`. The \`AnimatedBuilder\` causes the \`Positioned\` widget to rebuild every time the animation changes. The \`FadeTransition\` widget orchestrates the appearance and disappearance of each \`ActionButton\` as they expand and collapse, respectively. :::note The use of a \`Positioned\` widget within \`\_ExpandingActionButton\` implies that \`\_ExpandingActionButton\` can only be used as a direct child of a \`Stack\`. This is due to the explicit relationship between \`Positioned\` and \`Stack\`. ::: Finally, use the new \`\_ExpandingActionButton\` widget within the \`ExpandableFab\` to complete the exercise.

<!--?code-excerpt "lib/excerpt4.dart (ExpandableFabState4)" replace="/\/\/ code-excerpt-closing-bracket/}/g"?-->

\`\`\`dart class \_ExpandableFabState extends State with SingleTickerProviderStateMixin { @override Widget build(BuildContext context) { return SizedBox.expand( child: Stack( alignment: Alignment.bottomRight, clipBehavior: Clip.none, children: \[ \_buildTapToCloseFab(), ...\_buildExpandingActionButtons(), \_buildTapToOpenFab(), ], ), ); } List \_buildExpandingActionButtons() { final children = \[]; final count = widget.children.length; final step = 90.0 / (count - 1); for ( var i = 0, angleInDegrees = 0.0; i < count; i++, angleInDegrees += step ) { children.add( \_ExpandingActionButton( directionInDegrees: angleInDegrees, maxDistance: widget.distance, progress: \_expandAnimation, child: widget.children\[i], ), ); } return children; } } \`\`\` Congratulations! You now have an expandable FAB. ## Interactive example Run the app: \* Click the FAB in the lower-right corner, represented with an Edit icon. It fans out to 3 buttons and is itself replaced by a close button, represented by an \*\*X\*\*. \* Click the close button to see the expanded buttons fly back to the original FAB and the \*\*X\*\* is replaced by the Edit icon. \* Expand the FAB again, and click on any of the 3 satellite buttons to see a dialog representing that button's action.

<!--?code-excerpt "lib/main.dart"?-->

\`\`\`dartpad title="Flutter expandable floating action button hands-on example in DartPad" run="true" import 'dart:math' as math; import 'package:flutter/material.dart'; void main() { runApp( const MaterialApp( home: ExampleExpandableFab(), debugShowCheckedModeBanner: false, ), ); } @immutable class ExampleExpandableFab extends StatelessWidget { static const \_actionTitles = \['Create Post', 'Upload Photo', 'Upload Video']; const ExampleExpandableFab({super.key}); void \_showAction(BuildContext context, int index) { showDialog( context: context, builder: (context) { return AlertDialog( content: Text(\_actionTitles\[index]), actions: \[ TextButton( onPressed: () => Navigator.of(context).pop(), child: const Text('CLOSE'), ), ], ); }, ); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('Expandable Fab')), body: ListView\.builder( padding: const EdgeInsets.symmetric(vertical: 8), itemCount: 25, itemBuilder: (context, index) { return FakeItem(isBig: index.isOdd); }, ), floatingActionButton: ExpandableFab( distance: 112, children: \[ ActionButton( onPressed: () => \_showAction(context, 0), icon: const Icon(Icons.format\_size), ), ActionButton( onPressed: () => \_showAction(context, 1), icon: const Icon(Icons.insert\_photo), ), ActionButton( onPressed: () => \_showAction(context, 2), icon: const Icon(Icons.videocam), ), ], ), ); } } @immutable class ExpandableFab extends StatefulWidget { const ExpandableFab({ super.key, this.initialOpen, required this.distance, required this.children, }); final bool? initialOpen; final double distance; final List children; @override State createState() => \_ExpandableFabState(); } class \_ExpandableFabState extends State with SingleTickerProviderStateMixin { late final AnimationController \_controller; late final Animation \_expandAnimation; bool \_open = false; @override void initState() { super.initState(); \_open = widget.initialOpen ?? false; \_controller = AnimationController( value: \_open ? 1.0 : 0.0, duration: const Duration(milliseconds: 250), vsync: this, ); \_expandAnimation = CurvedAnimation( curve: Curves.fastOutSlowIn, reverseCurve: Curves.easeOutQuad, parent: \_controller, ); } @override void dispose() { \_controller.dispose(); super.dispose(); } void \_toggle() { setState(() { \_open = !\_open; if (\_open) { \_controller.forward(); } else { \_controller.reverse(); } }); } @override Widget build(BuildContext context) { return SizedBox.expand( child: Stack( alignment: Alignment.bottomRight, clipBehavior: Clip.none, children: \[ \_buildTapToCloseFab(), ...\_buildExpandingActionButtons(), \_buildTapToOpenFab(), ], ), ); } Widget \_buildTapToCloseFab() { return SizedBox( width: 56, height: 56, child: Center( child: Material( shape: const CircleBorder(), clipBehavior: Clip.antiAlias, elevation: 4, child: InkWell( onTap: \_toggle, child: Padding( padding: const EdgeInsets.all(8), child: Icon(Icons.close, color: Theme.of(context).primaryColor), ), ), ), ), ); } List \_buildExpandingActionButtons() { final children = \[]; final count = widget.children.length; final step = 90.0 / (count - 1); for ( var i = 0, angleInDegrees = 0.0; i < count; i++, angleInDegrees += step ) { children.add( \_ExpandingActionButton( directionInDegrees: angleInDegrees, maxDistance: widget.distance, progress: \_expandAnimation, child: widget.children\[i], ), ); } return children; } Widget \_buildTapToOpenFab() { return IgnorePointer( ignoring: \_open, child: AnimatedContainer( transformAlignment: Alignment.center, transform: Matrix4.diagonal3Values( \_open ? 0.7 : 1.0, \_open ? 0.7 : 1.0, 1.0, ), duration: const Duration(milliseconds: 250), curve: const Interval(0.0, 0.5, curve: Curves.easeOut), child: AnimatedOpacity( opacity: \_open ? 0.0 : 1.0, curve: const Interval(0.25, 1.0, curve: Curves.easeInOut), duration: const Duration(milliseconds: 250), child: FloatingActionButton( onPressed: \_toggle, child: const Icon(Icons.create), ), ), ), ); } } @immutable class \_ExpandingActionButton extends StatelessWidget { const \_ExpandingActionButton({ required this.directionInDegrees, required this.maxDistance, required this.progress, required this.child, }); final double directionInDegrees; final double maxDistance; final Animation progress; final Widget child; @override Widget build(BuildContext context) { return AnimatedBuilder( animation: progress, builder: (context, child) { final offset = Offset.fromDirection( directionInDegrees \* (math.pi / 180.0), progress.value \* maxDistance, ); return Positioned( right: 4.0 + offset.dx, bottom: 4.0 + offset.dy, child: Transform.rotate( angle: (1.0 - progress.value) \* math.pi / 2, child: child!, ), ); }, child: FadeTransition(opacity: progress, child: child), ); } } @immutable class ActionButton extends StatelessWidget { const ActionButton({super.key, this.onPressed, required this.icon}); final VoidCallback? onPressed; final Widget icon; @override Widget build(BuildContext context) { final theme = Theme.of(context); return Material( shape: const CircleBorder(), clipBehavior: Clip.antiAlias, color: theme.colorScheme.secondary, elevation: 4, child: IconButton( onPressed: onPressed, icon: icon, color: theme.colorScheme.onSecondary, ), ); } } @immutable class FakeItem extends StatelessWidget { const FakeItem({super.key, required this.isBig}); final bool isBig; @override Widget build(BuildContext context) { return Container( margin: const EdgeInsets.symmetric(vertical: 8, horizontal: 24), height: isBig ? 128 : 36, decoration: BoxDecoration( borderRadius: const BorderRadius.all(Radius.circular(8)), color: Colors.grey.shade300, ), ); } } \`\`\`

----
url: https://docs.flutter.dev/cookbook/design/orientation.md
----

\# Update the UI based on orientation > Respond to a change in the screen's orientation.

<!--?code-excerpt path-base="cookbook/design/orientation"?-->

In some situations, you want to update the display of an app when the shape of the available space changes like when a user rotates the 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. Expanded docs covering this and more can be found in the \[adaptive ui documentation]\[]. In Flutter, you can build different layouts depending on a given \[\`Orientation\`]\[]. In this example, build a list that displays two columns in portrait mode and three columns in landscape mode using the following steps: 1. Build a \`GridView\` with two columns. 2. Use an \`OrientationBuilder\` to change the number of columns. ## 1. Build a \`GridView\` with two 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.

<!--?code-excerpt "lib/partials.dart (GridViewCount)"?-->

\`\`\`dart return GridView\.count( // A list with 2 columns crossAxisCount: 2, // ... ); \`\`\` To learn more about working with \`GridViews\`, see the \[Creating a grid list]\[] recipe. ## 2. Use an \`OrientationBuilder\` to change the number of columns To determine the app's current \`Orientation\`, use the \[\`OrientationBuilder\`]\[] 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\`, build a list that displays two columns in portrait mode, or three columns in landscape mode.

<!--?code-excerpt "lib/partials.dart (OrientationBuilder)"?-->

\`\`\`dart body: OrientationBuilder( builder: (context, orientation) { return GridView\.count( // 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, use \`MediaQuery.orientationOf(context)\` instead of an \`OrientationBuilder\` widget. Using \`MediaQuery.orientationOf\` as a way to organize UI is \[discouraged]\[]. Instead use \`MediaQuery.sizeOf(context)\` ::: ## Interactive example

<!--?code-excerpt "lib/main.dart"?-->

\`\`\`dartpad title="Flutter app orientation hands-on example in DartPad" run="true" import 'package:flutter/material.dart'; void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { const appTitle = 'Orientation Demo'; return const MaterialApp( title: appTitle, home: OrientationList(title: appTitle), ); } } class OrientationList extends StatelessWidget { final String title; const OrientationList({super.key, required this.title}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text(title)), body: OrientationBuilder( builder: (context, orientation) { return GridView\.count( // 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. children: List.generate(100, (index) { return Center( child: Text( 'Item $index', style: TextTheme.of(context).headlineLarge, ), ); }), ); }, ), ); } } \`\`\` ## Locking device orientation In the previous section, you learned how to adapt the app UI to device orientation changes. Flutter also allows you to specify the orientations your app supports using the values of \[\`DeviceOrientation\`]. You can either: \* Lock the app to a single orientation, like only the \`portraitUp\` position, or... \* Allow multiple orientations, like both \`portraitUp\` and \`portraitDown\`, but not landscape. In the application \`main()\` method, call \[\`SystemChrome.setPreferredOrientations()\`] with the list of preferred orientations that your app supports. To lock the device to a single orientation, you can pass a list with a single item. For a list of all the possible values, check out \[\`DeviceOrientation\`].

<!--?code-excerpt "lib/orientation.dart (PreferredOrientations)"?-->

\`\`\`dart void main() { WidgetsFlutterBinding.ensureInitialized(); SystemChrome.setPreferredOrientations(\[DeviceOrientation.portraitUp]); runApp(const MyApp()); } \`\`\` \[Creating a grid list]: /cookbook/lists/grid-lists \[\`DeviceOrientation\`]: https\://api.flutter.dev/flutter/services/DeviceOrientation.html \[\`OrientationBuilder\`]: https\://api.flutter.dev/flutter/widgets/OrientationBuilder-class.html \[\`Orientation\`]: https\://api.flutter.dev/flutter/widgets/Orientation.html \[\`SystemChrome.setPreferredOrientations()\`]: https\://api.flutter.dev/flutter/services/SystemChrome/setPreferredOrientations.html \[adaptive ui documentation]: https\://api.flutter.dev/ui/adaptive-responsive \[discouraged]: https\://api.flutter.dev/ui/adaptive-responsive/best-practices

----
url: https://docs.flutter.dev/cookbook/lists/mixed-list
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Create lists with different types of items](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

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.

Here's how you can create such a structure with Flutter:

1. Create a data source with different types of items.
2. Convert the data source into a list of widgets.

## 1. Create a data source with different types of items

[#](#1-create-a-data-source-with-different-types-of-items)

### Types of items

[#](#types-of-items)

To represent different types of items in a list, define a class for each type of item.

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.
abstract class ListItem {
  /// The title line to show in a list item.
  Widget buildTitle(BuildContext context);
​
  /// The subtitle line, if any, to show in a list item.
  Widget buildSubtitle(BuildContext context);
}
​
/// A ListItem that contains data to display a heading.
class HeadingItem implements ListItem {
  final String heading;
​
  HeadingItem(this.heading);
​
  @override
  Widget buildTitle(BuildContext context) {
    return Text(heading, style: Theme.of(context).textTheme.headlineSmall);
  }
​
  @override
  Widget buildSubtitle(BuildContext context) => const SizedBox.shrink();
}
​
/// A ListItem that contains data to display a message.
class MessageItem implements ListItem {
  final String sender;
  final String body;
​
  MessageItem(this.sender, this.body);
​
  @override
  Widget buildTitle(BuildContext context) => Text(sender);
​
  @override
  Widget buildSubtitle(BuildContext context) => Text(body);
}
```

### Create a list of items

[#](#create-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, generate a list of items to work with. The list contains a header followed by five messages. Each message has one of 3 types: `ListItem`, `HeadingItem`, or `MessageItem`.

dart

```
final items = List<ListItem>.generate(
  1000,
  (i) => i % 6 == 0
      ? HeadingItem('Heading $i')
      : MessageItem('Sender $i', 'Message body $i'),
);
```

## 2. Convert the data source into a list of widgets

[#](#2-convert-the-data-source-into-a-list-of-widgets)

To convert each item into a widget, use the [`ListView.builder()`](https://api.flutter.dev/flutter/widgets/ListView/ListView.builder.html) constructor.

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.

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.
  // Convert each item into a widget based on the type of item it is.
  itemBuilder: (context, index) {
    final item = items[index];
​
    return ListTile(
      title: item.buildTitle(context),
      subtitle: item.buildSubtitle(context),
    );
  },
)
```

## Interactive example

[#](#interactive-example)

```
import 'package:flutter/material.dart';

void main() {
  runApp(
    MyApp(
      items: List<ListItem>.generate(
        1000,
        (i) => i % 6 == 0
            ? HeadingItem('Heading $i')
            : MessageItem('Sender $i', 'Message body $i'),
      ),
    ),
  );
}

class MyApp extends StatelessWidget {
  final List<ListItem> items;

  const MyApp({super.key, required this.items});

  @override
  Widget build(BuildContext context) {
    const title = 'Mixed List';

    return MaterialApp(
      title: title,
      home: Scaffold(
        appBar: AppBar(title: const Text(title)),
        body: 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.
          // Convert each item into a widget based on the type of item it is.
          itemBuilder: (context, index) {
            final item = items[index];

            return ListTile(
              title: item.buildTitle(context),
              subtitle: item.buildSubtitle(context),
            );
          },
        ),
      ),
    );
  }
}

/// The base class for the different types of items the list can contain.
abstract class ListItem {
  /// The title line to show in a list item.
  Widget buildTitle(BuildContext context);

  /// The subtitle line, if any, to show in a list item.
  Widget buildSubtitle(BuildContext context);
}

/// A ListItem that contains data to display a heading.
class HeadingItem implements ListItem {
  final String heading;

  HeadingItem(this.heading);

  @override
  Widget buildTitle(BuildContext context) {
    return Text(heading, style: Theme.of(context).textTheme.headlineSmall);
  }

  @override
  Widget buildSubtitle(BuildContext context) => const SizedBox.shrink();
}

/// A ListItem that contains data to display a message.
class MessageItem implements ListItem {
  final String sender;
  final String body;

  MessageItem(this.sender, this.body);

  @override
  Widget buildTitle(BuildContext context) => Text(sender);

  @override
  Widget buildSubtitle(BuildContext context) => Text(body);
}
```

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/lists/mixed-list.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/cookbook/lists/mixed-list\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/lists/mixed-list.md "Report an issue with this page").

----
url: https://docs.flutter.dev/platform-integration/desktop
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Desktop support for Flutter](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

Flutter provides support for compiling a native Windows, macOS, or Linux desktop app. Flutter's desktop support also extends to plugins—you can install existing plugins that support the Windows, macOS, or Linux platforms, or you can create your own.

## Create a new project

[#](#create-a-new-project)

You can use the following steps to create a new project with desktop support.

### Set up desktop devtools

[#](#set-up-desktop-devtools)

Consult the guide for your target desktop environment:

* [Install Linux desktop devtools](/platform-integration/linux/setup#set-up-tooling)
* [Install macOS desktop devtools](/platform-integration/macos/setup#set-up-tooling)
* [Install Windows desktop devtools](/platform-integration/windows/setup#set-up-tooling)

If `flutter doctor` finds problems or missing components for a platform that you don't want to develop for, you can ignore those warnings. Or you can disable the platform altogether using the `flutter config` command, for example:

```
$ flutter config --no-enable-ios
```

Other available flags:

* `--no-enable-windows-desktop`
* `--no-enable-linux-desktop`
* `--no-enable-macos-desktop`
* `--no-enable-web`
* `--no-enable-android`
* `--no-enable-ios`

After enabling desktop support, restart your IDE so that it can detect the new device.

### Create and run

[#](#create-and-run)

Creating a new project with desktop support is no different from [creating a new Flutter project](/reference/create-new-app) for other platforms.

Once you've configured your environment for desktop support, you can create and run a desktop application either in the IDE or from the command line.

#### Using an IDE

[#](#using-an-ide)

After you've configured your environment to support desktop, make sure you restart the IDE if it was already running.

Create a new application in your IDE and it automatically creates iOS, Android, web, and desktop versions of your app. From the device pulldown, select **windows (desktop)**, **macOS (desktop)**, or **linux (desktop)** and run your application to see it launch on the desktop.

#### From the command line

[#](#from-the-command-line)

To create a new application that includes desktop support (in addition to mobile and web support), run the following commands, substituting `my_app` with the name of your project:

```
$ flutter create my_app
$ cd my_app
```

To launch your application from the command line, enter one of the following commands from the top of the package:

```
C:\> flutter run -d windows
$ flutter run -d macos
$ flutter run -d linux
```

## Build a release app

[#](#build-a-release-app)

To generate a release build, run one of the following commands:

```
PS C:\> flutter build windows
$ flutter build macos
$ flutter build linux
```

## Add desktop support to an existing Flutter app

[#](#add-desktop-support-to-an-existing-flutter-app)

To add desktop support to an existing Flutter project, run the following command in a terminal from the root project directory:

```
$ flutter create --platforms=windows,macos,linux .
```

This adds the necessary desktop files and directories to your existing Flutter project. To add only specific desktop platforms, change the `platforms` list to include only the platform(s) you want to add.

## Plugin support

[#](#plugin-support)

Flutter on the desktop supports using and creating plugins. To use a plugin that supports desktop, follow the steps for plugins in [using packages](/packages-and-plugins/using-packages). Flutter automatically adds the necessary native code to your project, as with any other platform.

### Writing a plugin

[#](#writing-a-plugin)

When you start building your own plugins, you'll want to keep federation in mind. Federation is the ability to define several different packages, each targeted at a different set of platforms, brought together into a single plugin for ease of use by developers. For example, the Windows implementation of the `url_launcher` is really `url_launcher_windows`, but a Flutter developer can simply add the `url_launcher` package to their `pubspec.yaml` as a dependency and the build process pulls in the correct implementation based on the target platform. Federation is handy because different teams with different expertise can build plugin implementations for different platforms. You can add a new platform implementation to any endorsed federated plugin on pub.dev, so long as you coordinate this effort with the original plugin author.

For more information, including information about endorsed plugins, see the following resources:

* [Developing packages and plugins](/packages-and-plugins/developing-packages), particularly the [Federated plugins](/packages-and-plugins/developing-packages#federated-plugins) section.
* [How to write a Flutter web plugin, part 2](https://blog.flutter.dev/how-to-write-a-flutter-web-plugin-part-2-afdddb69ece6), covers the structure of federated plugins and contains information applicable to desktop plugins.
* [Modern Flutter Plugin Development](https://blog.flutter.dev/modern-flutter-plugin-development-4c3ee015cf5a) covers recent enhancements to Flutter's plugin support.

[]()

## Samples

[#](#samples)

You can run the following samples as desktop apps, as well as download and inspect the source code to learn more about Flutter desktop support.

* Wonderous app [running app](https://wonderous.app//web) , [repo](https://github.com/gskinnerTeam/flutter-wonderous-app)

  A showcase app that uses Flutter to create a highly expressive user interface. Wonderous focuses on delivering an accessible and high-quality user experience while including engaging interactions and novel animations. To run Wonderous as a desktop app, clone the project and follow the instructions provided in the [README](https://github.com/gskinnerTeam/flutter-wonderous-app#wonderous).

* Flokk [announcement blogpost](https://blog.gskinner.com/archives/2020/09/flokk-how-we-built-a-desktop-app-using-flutter.html) , [repo](https://github.com/gskinnerTeam/flokk)

  A Google contacts manager that integrates with GitHub and Twitter. It syncs with your Google account, imports your contacts, and allows you to manage them.

* [Photo Search app](https://github.com/flutter/samples/tree/main/desktop_photo_search)

  A sample application built as a desktop application that uses desktop-supported plugins.

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-07-31. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/platform-integration/desktop.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/platform-integration/desktop\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/platform-integration/desktop.md "Report an issue with this page").

----
url: https://docs.flutter.dev/platform-integration/windows
----

[External windows in Flutter Windows apps](/platform-integration/windows/extern_win)

[Special considerations for adding external windows to Flutter apps](/platform-integration/windows/extern_win)

[Set up Windows development](/platform-integration/windows/setup)

[Configure your development environment to run, build, and deploy Flutter apps for Windows.](/platform-integration/windows/setup)

[Building Windows apps with Flutter](/platform-integration/windows/building)

[Platform-specific considerations for building for Windows with Flutter.](/platform-integration/windows/building)

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/platform-integration/windows/index.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/platform-integration/windows\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/platform-integration/windows/index.md "Report an issue with this page").

----
url: https://docs.flutter.dev/perf/isolates.md
----

\# Concurrency and isolates > Multithreading in Flutter using Dart isolates.

<!--?code-excerpt path-base="perf/concurrency/isolates/"?-->

All Dart code runs in \[isolates]\(https\://dart.dev/language/concurrency), which are similar to threads, but differ in that isolates have their own isolated memory. They do not share state in any way, and can only communicate by messaging. By default, Flutter apps do all of their work on a single isolate – the main isolate. In most cases, this model allows for simpler programming and is fast enough that the application's UI doesn't become unresponsive. Sometimes though, applications need to perform exceptionally large computations that can cause "UI jank" (jerky motion). If your app is experiencing jank for this reason, you can move these computations to a helper isolate. This allows the underlying runtime environment to run the computation concurrently with the main UI isolate's work and takes advantage of multi-core devices. Each isolate has its own memory and its own event loop. The event loop processes events in the order that they're added to an event queue. On the main isolate, these events can be anything from handling a user tapping in the UI, to executing a function, to painting a frame on the screen. The following figure shows an example event queue with 3 events waiting to be processed. !\[The main isolate diagram]\(/assets/images/docs/development/concurrency/basics-main-isolate.png){:width="50%" .diagram-wrap} For smooth rendering, Flutter adds a "paint frame" event to the event queue 60 times per second(for a 60Hz device). If these events aren't processed on time, the application experiences UI jank, or worse, become unresponsive altogether. !\[Event jank diagram]\(/assets/images/docs/development/concurrency/event-jank.png){:width="60%" .diagram-wrap} Whenever a process can't be completed in a frame gap, the time between two frames, it's a good idea to offload the work to another isolate to ensure that the main isolate can produce 60 frames per second. When you spawn an isolate in Dart, it can process the work concurrently with the main isolate, without blocking it. You can read more about how isolates and the event loop work in Dart on the \[concurrency page]\[] of the Dart documentation. \[concurrency page]: https\://dart.dev/language/concurrency ## Common use cases for isolates There is only one hard rule for when you should use isolates, and that's when large computations are causing your Flutter application to experience UI jank. This jank happens when there is any computation that takes longer than Flutter's frame gap. !\[Event jank diagram]\(/assets/images/docs/development/concurrency/event-jank.png){:width="60%" .diagram-wrap} Any process \_could\_ take longer to complete, depending on the implementation and the input data, making it impossible to create an exhaustive list of when you need to consider using isolates. That said, isolates are commonly used for the following: - Reading data from a local database - Sending push notifications - Parsing and decoding large data files - Processing or compressing photos, audio files, and video files - Converting audio and video files - When you need asynchronous support while using FFI - Applying filtering to complex lists or filesystems ## Message passing between isolates Dart's isolates are an implementation of the \[Actor model]\[]. They can only communicate with each other by message passing, which is done with \[\`Port\` objects]\[]. When messages are "passed" between each other, they are generally copied from the sending isolate to the receiving isolate. This means that any value passed to an isolate, even if mutated on that isolate, doesn't change the value on the original isolate. The only \[objects that aren't copied when passed]\[] to an isolate are immutable objects that can't be changed anyway, such a String or an unmodifiable byte. When you pass an immutable object between isolates, a reference to that object is sent across the port, rather than the object being copied, for better performance. Because immutable objects can't be updated, this effectively retains the actor model behavior. \[\`Port\` objects]: https\://api.dart.dev/dart-isolate/ReceivePort-class.html \[objects that aren't copied when passed]: https\://api.dart.dev/dart-isolate/SendPort/send.html An exception to this rule is when an isolate exits when it sends a message using the \`Isolate.exit\` method. Because the sending isolate won't exist after sending the message, it can pass ownership of the message from one isolate to the other, ensuring that only one isolate can access the message. The two lowest-level primitives that send messages are \`SendPort.send\`, which makes a copy of a mutable message as it sends, and \`Isolate.exit\`, which sends the reference to the message. Both \`Isolate.run\` and \`compute\` use \`Isolate.exit\` under the hood. ## Short-lived isolates The easiest way to move a process to an isolate in Flutter is with the \`Isolate.run\` method. This method spawns an isolate, passes a callback to the spawned isolate to start some computation, returns a value from the computation, and then shuts the isolate down when the computation is complete. This all happens concurrently with the main isolate, and doesn't block it. !\[Isolate diagram]\(/assets/images/docs/development/concurrency/isolate-bg-worker.png){:width="70%" .diagram-wrap} The \`Isolate.run\` method requires a single argument, a callback function, that is run on the new isolate. This callback's function signature must have exactly one required, unnamed argument. When the computation completes, it returns the callback's value back to the main isolate, and exits the spawned isolate. For example, consider this code that loads a large JSON blob from a file, and converts that JSON into custom Dart objects. If the json decoding process wasn't off loaded to a new isolate, this method would cause the UI to become unresponsive for several seconds.

<!--?code-excerpt "lib/main.dart (isolate-run)"?-->

\`\`\`dart // Produces a list of 211,640 photo objects. // (The JSON file is \~20MB.) Future> getPhotos() async { final String jsonString = await rootBundle.loadString('assets/photos.json'); final List photos = await Isolate.run>(() { final List photoData = jsonDecode(jsonString) as List; return photoData.cast>().map(Photo.fromJson).toList(); }); return photos; } \`\`\` For a complete walkthrough of using Isolates to parse JSON in the background, see \[this cookbook recipe]\[]. \[this cookbook recipe]: /cookbook/networking/background-parsing ## Stateful, longer-lived isolates Short-live isolates are convenient to use, but there is performance overhead required to spawn new isolates, and to copy objects from one isolate to another. If you're doing the same computation using \`Isolate.run\` repeatedly, you might have better performance by creating isolates that don't exit immediately. To do this, you can use a handful of lower-level isolate-related APIs that \`Isolate.run\` abstracts: - \[\`Isolate.spawn()\`]\[] and \[\`Isolate.exit()\`]\[] - \[\`ReceivePort\`]\[] and \[\`SendPort\`]\[] - \[\`send()\`]\[] method When you use the \`Isolate.run\` method, the new isolate immediately shuts down after it returns a single message to the main isolate. Sometimes, you'll need isolates that are long lived, and can pass multiple messages to each other over time. In Dart, you can accomplish this with the Isolate API and Ports. These long-lived isolates are colloquially known as \_background workers\_. Long-lived isolates are useful when you have a specific process that either needs to be run repeatedly throughout the lifetime of your application, or if you have a process that runs over a period of time and needs to yield multiple return values to the main isolate. Or, you might use \[worker\_manager]\[] to manage long-lived isolates. \[worker\_manager]: https\://pub.dev/packages/worker\_manager ### ReceivePorts and SendPorts Set up long-lived communication between isolates with two classes (in addition to Isolate): \[\`ReceivePort\`]\[] and \[\`SendPort\`]\[]. These ports are the only way isolates can communicate with each other. \`Ports\` behave similarly to \`Streams\`, in which the \`StreamController\` or \`Sink\` is created in one isolate, and the listener is set up in the other isolate. In this analogy, the \`StreamController\` is called a \`SendPort\`, and you can "add" messages with the \`send()\` method. \`ReceivePort\`s are the listeners, and when these listeners receive a new message, they call a provided callback with the message as an argument. For an in-depth explanation on setting up two-way communication between the main isolate and a worker isolate, follow the examples in the \[Dart documentation]\[]. \[Dart documentation]: https\://dart.dev/language/concurrency ## Using platform plugins in isolates You can use platform plugins in background isolates. This enables plugins to offload heavy, platform-dependent computations to an isolate that won't block your UI. For example, imagine you're encrypting data using a native host API (such as an Android API on Android, an iOS API on iOS, and so on). Previously, \[marshaling data]\[] to the host platform could waste UI thread time, and can now be done in a background isolate. Platform channel isolates use the \[\`BackgroundIsolateBinaryMessenger\`]\[] API. The following snippet shows an example of using the \`shared\_preferences\` package in a background isolate.

<!--?code-excerpt "lib/isolate_binary_messenger.dart"?-->

\`\`\`dart import 'dart:isolate'; import 'package:flutter/services.dart'; import 'package:shared\_preferences/shared\_preferences.dart'; void main() { // Identify the root isolate to pass to the background isolate. RootIsolateToken rootIsolateToken = RootIsolateToken.instance!; Isolate.spawn(\_isolateMain, rootIsolateToken); } Future \_isolateMain(RootIsolateToken rootIsolateToken) async { // Register the background isolate with the root isolate. BackgroundIsolateBinaryMessenger.ensureInitialized(rootIsolateToken); // You can now use the shared\_preferences plugin. SharedPreferences sharedPreferences = await SharedPreferences.getInstance(); print(sharedPreferences.getBool('isDebug')); } \`\`\` ## Limitations of Isolates If you're coming to Dart from a language with multithreading, it's reasonable to expect isolates to behave like threads, but that isn't the case. Isolates have their own global fields, and can only communicate with message passing, ensuring that mutable objects in an isolate are only ever accessible in a single isolate. Therefore, isolates are limited by their access to their own memory. For example, if you have an application with a global mutable variable called \`configuration\`, it is copied as a new global field in a spawned isolate. If you mutate that variable in the spawned isolate, it remains untouched in the main isolate. This is true even if you pass the \`configuration\` object as a message to the new isolate. This is how isolates are meant to function, and it's important to keep in mind when you consider using isolates. ### Web platforms and compute Dart web platforms, including Flutter web, don't support isolates. If you're targeting the web with your Flutter app, you can use the \`compute\` method to ensure your code compiles. The \[\`compute()\`]\[] method runs the computation on the main thread on the web, but spawns a new thread on mobile devices. On mobile and desktop platforms \`await compute(fun, message)\` is equivalent to \`await Isolate.run(() => fun(message))\`. For more information on concurrency on the web, check out the \[concurrency documentation]\[] on dart.dev. \[concurrency documentation]: https\://dart.dev/language/concurrency ### No \`rootBundle\` access or \`dart:ui\` methods All UI tasks and Flutter itself are coupled to the main isolate. Therefore, you can't access assets using \`rootBundle\` in spawned isolates, nor can you perform any widget or UI work in spawned isolates. ### Limited plugin messages from host platform to Flutter With background isolate platform channels, you can use platform channels in isolates to send messages to the host platform (for example Android or iOS), and receive responses to those messages. However, you can't receive unsolicited messages from the host platform. As an example, you can't set up a long-lived Firestore listener in a background isolate, because Firestore uses platform channels to push updates to Flutter, which are unsolicited. You can, however, query Firestore for a response in the background. ## More information For more information on isolates, check out the following resources: - If you're using many isolates, consider the \[IsolateNameServer]\[] class in Flutter, or the pub package that clones the functionality for Dart applications not using Flutter. - Dart's Isolates are an implementation of the \[Actor model]\[]. - \[isolate\_agents]\[] is a package that abstracts Ports and make it easier to create long-lived isolates. - Read more about the \`BackgroundIsolateBinaryMessenger\` API \[announcement]\[]. \[announcement]: https\://blog.flutter.dev/introducing-background-isolate-channels-7a299609cad8 \[Actor model]: https\://en.wikipedia.org/wiki/Actor\_model \[isolate\_agents]: https\://medium.com/@gaaclarke/isolate-agents-easy-isolates-for-flutter-6d75bf69a2e7 \[marshaling data]: https\://en.wikipedia.org/wiki/Marshalling\_(computer\_science) \[\`compute()\`]: https\://api.flutter.dev/flutter/foundation/compute.html \[\`Isolate.spawn()\`]: https\://api.dart.dev/dart-isolate/Isolate/spawn.html \[\`Isolate.exit()\`]: https\://api.dart.dev/dart-isolate/Isolate/exit.html \[\`ReceivePort\`]: https\://api.dart.dev/dart-isolate/ReceivePort-class.html \[\`SendPort\`]: https\://api.dart.dev/dart-isolate/SendPort-class.html \[\`send()\`]: https\://api.dart.dev/dart-isolate/SendPort/send.html \[\`BackgroundIsolateBinaryMessenger\`]: https\://api.flutter.dev/flutter/services/BackgroundIsolateBinaryMessenger-class.html \[IsolateNameServer]: https\://api.flutter.dev/flutter/dart-ui/IsolateNameServer-class.html

----
url: https://docs.flutter.dev/ai/best-practices/tool-calls.md
----

\# Tool calls (aka function calls) > Learn how to implement tool calling, manage agentic loops, and incorporate human-in-the-loop interactions using the Firebase AI Logic SDK. While it's true that LLMs are trained essentially on the entire internet, they don't know everything. They know what was on the public internet the day they were trained, but they don't know anything more recent than that. They don't know anything that's private to you or your organization. And even things they do know can easily get tangled up with other things they know. For those scenarios, and many others, we often provide an LLM with one or more tools. ### Tool defined A tool is a name, a description and a JSON schema for the format of the input data when the LLM "calls" the tool. For example, if we prompt the LLM to "Reduce the carbs in Grandma's All America Breakfast recipe", it won't know what grandma's recipe is unless we give it a "lookupRecipe" tool that takes a query string we can use to look up the recipe. Conceptually, a tool is something we give the LLM to call when it needs that data or service. The way an LLM calls a tool is by responding to the app's request with a specially formatted message that means "tool call". A tool call message includes the name and JSON arguments for the tool. The app handles the tool call and bundles the result in another LLM request, to which the LLM then responds. This can go on for a while. An app can configure a model instance with any number of tools (although the LLM does better with a smaller set of targeted tools that don't overlap in functionality). The LLM can bundle up any number of tool calls in its response and can take any number of tool results in a request. The LLM consolidates multiple round-trips for prompts and tool call results via a stack of messages that form a history of request/response pairs. When it's done with the tool calls, the LLM returns its final response, for example "Here's a version of Grandma's All American Breakfast recipe that's high on protein and low on carbs…". ### Gemini functions In the Firebase AI Logic SDK, a tool is called a "function", but it's the same thing. In the sample, the clue solver model is configured with a function to look up word details. If the LLM wants details about a word to help with the solving process, calling the function provides it with data from \[the Free Dictionary API]\[dictionary-api]: \`\`\`json \[ { "word": "tool", "phonetic": "/tuːl/", "phonetics": \[ { "text": "/tuːl/", "audio": "https\://api.dictionaryapi.dev/media/pronunciations/en/tool-uk.mp3", "sourceUrl": "https\://commons.wikimedia.org/w/index.php?curid=94709459", "license": { "name": "BY-SA 4.0", "url": "https\://creativecommons.org/licenses/by-sa/4.0" } } ], "meanings": \[ { "partOfSpeech": "noun", "definitions": \[ { "definition": "A mechanical device intended to make a task easier.", "synonyms": \[], "antonyms": \[], "example": "Hand me that tool, would you? I don't have the right tools to start fiddling around with the engine." }, ... \`\`\` The app has a Dart function that does the look up: \`\`\`dart // Look up the metadata for a word in the dictionary API. Future> \_getWordMetadataFromApi(String word) async { final url = Uri.parse( 'https\://api.dictionaryapi.dev/api/v2/entries/en/${Uri.encodeComponent(word)}', ); final response = await http.get(url); return response.statusCode == 200 ? {'result': jsonDecode(response.body)} : {'error': 'Could not find a definition for "$word".'}; } \`\`\` The model is configured with the look up function as part of initialization: \`\`\`dart // The model for solving clues. \_clueSolverModel = FirebaseAI.googleAI().generativeModel( model: 'gemini-2.5-flash', systemInstruction: Content.text(clueSolverSystemInstruction), tools: \[ Tool.functionDeclarations(\[ FunctionDeclaration( 'getWordMetadata', 'Gets grammatical metadata for a word, like its part of speech. ' 'Best used to verify a candidate answer against a clue that implies a ' 'grammatical constraint.', parameters: { 'word': Schema(SchemaType.string, description: 'The word to look up.'), }, ), ]), ], ); \`\`\` For reliability, it's also a good idea to list the tools in the system instruction: \`\`\`\`dart static String get clueSolverSystemInstruction => ''' You are an expert crossword puzzle solver. ... ### Tool: \`getWordMetadata\` You have a tool to get grammatical information about a word. \*\*When to use:\*\* - This tool is most helpful as a verification step after you have a likely answer. - Consider using this tool when a clue contains a grammatical hint that could be ambiguous. - \*\*Good candidates for verification:\*\* - Clues that seem to be verbs (e.g., "To run," "Waving"). - Clues that are adverbs (e.g., "Happily," "Quickly"). - Clues that specify a plural form. - \*\*Try to avoid using the tool for:\*\* - Simple definitions (e.g., "A small dog"). - Fill-in-the-blank clues (e.g., "\_\_\_ and flow"). - Proper nouns (e.g., "Capital of France"). \*\*Function signature:\*\* \`\`\`json ${jsonEncode(\_getWordMetadataFunction.toJson())} \`\`\` '''; \`\`\`\` When the app makes a request, the model now has a tool to use when it decides that it will be helpful. To support tool calls, we need to implement an agentic loop. ## The Agentic Loop An LLM is functionally stateless, which means that you have to give it all of the data it needs with every request. For a request that's just the prompt and any files you want to send along, the Firebase AI Logic SDK exposes the \`generateContent\` method on your model instance. However, tool calling requires a history of messages that form the initial prompt, as well as the response/request pairs that make up tool calls and tool results. To support this Firebase Logic AI provides a "chat" object to gather the history. We use it to build the agentic loop: - Start a chat to hold the message history across multiple request/response pairs - Gather the tool results for any tool calls it provides - Bundle the tool results into a new request - Loop until the model provides a response without tool calls - Return the text accumulated across all responses Here's that algorithm expressed as an extension method on the \`GenerativeModel\` class so we can call it just like we call \`generateContent\`: \`\`\`dart extension on GenerativeModel { Future generateContentWithFunctions({ required String prompt, required Future> Function(FunctionCall) onFunctionCall, }) async { // Use a chat session to support multiple request/response pairs, which is // needed to support function calls. final chat = startChat(); final buffer = StringBuffer(); var response = await chat.sendMessage(Content.text(prompt)); while (true) { // Append the response text to the buffer. buffer.write(response.text ?? ''); // If no function calls were collected, we're done if (response.functionCalls.isEmpty) break; // Append a newline to separate responses. buffer.write('\n'); // Execute all function calls final functionResponses = \[]; for (final functionCall in response.functionCalls) { try { functionResponses.add( FunctionResponse( functionCall.name, await onFunctionCall(functionCall), ), ); } catch (ex) { functionResponses.add( FunctionResponse(functionCall.name, {'error': ex.toString()}), ); } } // Get the next response stream with function results response = await chat.sendMessage( Content.functionResponses(functionResponses), ); } return buffer.toString(); } } \`\`\` This method takes a prompt and a callback for handling the specific tool calls, which the sample calls to handle the word lookup function: \`\`\`dart await \_clueSolverModel.generateContentWithFunctions( prompt: getSolverPrompt(clue, length, pattern), onFunctionCall: (functionCall) async => switch (functionCall.name) { 'getWordMetadata' => await \_getWordMetadataFromApi( functionCall.args\['word'] as String, ), \_ => throw Exception('Unknown function call: ${functionCall.name}'), }, ); \`\`\` Structured output makes an LLM useful to program against but it's the tools that turn an LLM into an "agent" (more on this in the Mode of interaction section). ### Structured output and tool calls Combining structured output and tool calls produce a powerful combination. In the sample, the clue solver has a tool to look up word details. It's also asked to return JSON that bundles the solution with a confidence score, both of which are shown in the app's task list: Unfortunately, as of this writing, combining structured output and functions when using the Firebase AI Logic SDK produces an exception: \`\`\`plaintext Function calling with a response mime type: 'application/json' is unsupported \`\`\` As a (hopefully temporary) work-around to this issue, the sample removes the structured output configuration, instead using a tool called \`returnResult\` to simulate structured output: \`\`\`dart // The model for solving clues. \_clueSolverModel = FirebaseAI.googleAI().generativeModel( model: 'gemini-2.5-flash', systemInstruction: Content.text(clueSolverSystemInstruction), tools: \[ Tool.functionDeclarations(\[ ..., FunctionDeclaration( 'returnResult', 'Returns the final result of the clue solving process.', parameters: { 'answer': Schema( SchemaType.string, description: 'The answer to the clue.', ), 'confidence': Schema( SchemaType.number, description: 'The confidence score in the answer from 0.0 to 1.0.', ), }, ), ]), ], ); \`\`\` The \`returnResult\` method is also mentioned in the system instruction: \`\`\`\`dart static String get clueSolverSystemInstruction => ''' You are an expert crossword puzzle solver. ... ### Tool: \`returnResult\` You have a tool to return the final result of the clue solving process. \*\*When to use:\*\* - Use this tool when you have a final answer and confidence score to return. You must use this tool exactly once, and only once, to return the final result. \*\*Function signature:\*\* \`\`\`json ${jsonEncode(\_returnResultFunction.toJson())} \`\`\` '''; \`\`\`\` When the model calls \`returnResult\`, the sample caches the result, which the \`solveClue\` looks up after calling \`generateContentWithFunctions\`: \`\`\`dart // Buffer for the result of the clue solving process. final \_returnResult = {}; // Cache the return result of the clue solving process via a function call. // This is how we get JSON responses from the model with functions, since the // model cannot return JSON directly when tools are used. Map \_cacheReturnResult(Map returnResult) { assert(\_returnResult.isEmpty); \_returnResult.addAll(returnResult); return {'status': 'success'}; } Future solveClue(Clue clue, int length, String pattern) async { // Clear the return result cache; this is where the result will be stored. \_returnResult.clear(); // Generate JSON response with functions and schema. await \_clueSolverModel.generateContentWithFunctions( prompt: getSolverPrompt(clue, length, pattern), onFunctionCall: (functionCall) async => switch (functionCall.name) { 'getWordMetadata' => ..., 'returnResult' => \_cacheReturnResult(functionCall.args), \_ => throw Exception('Unknown function call: ${functionCall.name}'), }, ); // Use the structured output that the LLM has called function with assert(\_returnResult.isNotEmpty); return ClueAnswer( answer: \_returnResult\['answer'] as String, confidence: (\_returnResult\['confidence'] as num).toDouble(), ); } \`\`\` We have to work a little harder to get the combo of structured output and tool calls using Firebase AI Logic, but the results are worth it! ### Human in the loop So far, we've seen tools used for gathering data and formatting output. We can also use them to get a human involved. As an example, sometimes when the sample will pass in a pattern the solution should take – like "\_R\_Y" – the model wants to suggest an answer that doesn't fit this pattern – like "RENT". A conflict like this is a good time to ask for help from the user: This is called putting the "human in the loop" and it's yet another way for humans and LLMs to collaborate. Flutter and the Firebase AI Logic SDK make this easy to do. First, the sample defines a function and configures the model: \`\`\`\`dart // The new function to let the LLM resolve solution conflicts static final \_resolveConflictFunction = FunctionDeclaration( 'resolveConflict', 'Asks the user to resolve a conflict between the letter pattern and the ' 'proposed answer. Use this BEFORE calling returnResult if the answer you ' 'want to propose does not match the letter pattern.', parameters: { 'proposedAnswer': Schema( SchemaType.string, description: 'The answer the LLM wants to suggest.', ), 'pattern': Schema( SchemaType.string, description: 'The current letter pattern from the grid.', ), 'clue': Schema(SchemaType.string, description: 'The clue text.'), }, ); // Pass the new tool to the model for solving clues. final \_clueSolverModel = FirebaseAI.googleAI().generativeModel( model: 'gemini-2.5-flash', systemInstruction: Content.text(clueSolverSystemInstruction), tools: \[ Tool.functionDeclarations(\[ ... \_resolveConflictFunction, ]), ], ); // Let the LLM know that it has a new tool. static String get clueSolverSystemInstruction => ''' You are an expert crossword puzzle solver. ... ### Tool: \`resolveConflict\` You have a tool to ask the user to resolve a conflict. \*\*When to use:\*\* - Use this tool \*\*BEFORE\*\* \`returnResult\` if your proposed answer conflicts with the provided letter pattern. - For example, if the pattern is \`\_ R \_ Y\` and you want to suggest \`RENT\` (which fits the clue), there is a conflict at the second letter (\`R\` vs \`E\`). You should call \`resolveConflict(proposedAnswer: "RENT", pattern: "\_ R \_ Y", clue: "...")\`. - The tool will return the user's decision (either your proposed answer or a new one). You should then use that result to call \`returnResult\`. \*\*Function signature:\*\* \`\`\`json ${jsonEncode(\_resolveConflictFunction.toJson())} \`\`\` '''; \`\`\`\` Now when the model sees a conflict, it will call the tool: \`\`\`dart // handle the LLM's request to resolve the conflict await \_clueSolverModel.generateContentWithFunctions( prompt: getSolverPrompt(clue, length, pattern), onFunctionCall: (functionCall) async => switch (functionCall.name) { ... 'resolveConflict' => await \_handleResolveConflict( functionCall.args, onConflict, ), }, ); // Show the dialog to gather the user's input Future> \_handleResolveConflict( Map args, Future Function(String clue, String proposedAnswer, String pattern)? onConflict, ) async { final proposedAnswer = args\['proposedAnswer'] as String; final pattern = args\['pattern'] as String; final clue = args\['clue'] as String; if (onConflict != null) { final result = await onConflict(clue, proposedAnswer, pattern); return {'result': result}; } return {'result': proposedAnswer}; } \`\`\` The sample handles the tool with an implementation of the \`onConflict\` method that calls \`showDialog\` to gather data from the user. This all happens in the middle of the agentic loop, but that's OK – the model is not waiting; it's already sent back its response to the app's initial request. The user can take their time with the UI while the sample waits on the \`Future\` returned \`showDialog\`. When they're done, the model continues where it left off using the message history and the most recent request, which in this case happens to be data gathered interactively from the user. A modal dialog box is a simple way to put the human in the loop but is not the only way in Flutter to do so. If you'd prefer, an instance of \[a \`Completer\`]\[completer] lets you set some state in your app that puts it into "gathering data from the user" mode. When the app has the data, it can call \`complete\` on the \`Completer\` and resume the agentic loop. Or, since you own the agentic loop, you can check for a call to a "special" function that indicates that you need to gather data from the user. This kind of special function is sometimes called an "interrupt" and you "resume" the conversation with the model when you have the data from the user. Remember that the LLM is stateless. It's not waiting on you, so you can handle the agentic loop in whatever way makes the most sense for your app. You can come back to the LLM with an updated message history and a new prompt at any time, whether it's been a minute or in a month. \[dictionary-api]: https\://dictionaryapi.dev/ \[completer]: https\://api.flutter.dev/flutter/dart-async/Completer-class.html

----
url: https://docs.flutter.dev/install/archive.md
----

\# Flutter SDK archive > All current Flutter SDK releases: stable, beta, and main. :::note Developing in China If you want to use Flutter in China, check out \[using Flutter in China]\[]. If you're not developing in China, ignore this notice and follow the other instructions on this page. 如果你正在中国的网络环境下配置 Flutter， 请参考 \[在中国网络环境下使用 Flutter]\[] 文档. ::: \[using Flutter in China]: /community/china \[在中国网络环境下使用 Flutter]: https\://docs.flutter.cn/community/china/ ## Overview The Flutter SDK archive is a collection of all previous versions of the Flutter SDK. This archive is useful for developers who need to use an older version of Flutter for compatibility reasons or to investigate bugs. After you download a release, to set up Flutter, follow the \[manual installation instructions]\[manual-install]. The archive includes Flutter SDKs for Windows, macOS, and Linux on the following \[channels]\[]: \* \*\*Stable channel\*\*: This channel contains the most stable Flutter builds. Roughly every third beta version is promoted to the stable version. The stable channel is the recommended channel for new users and for production app releases. \* \*\*Beta channel\*\*: This channel is the most recent version of Flutter that is available, but it is not yet stable. The beta branch is usually released on the first Wednesday of the month. A fix will typically end up in the beta channel about two weeks after it lands in the main channel. Releases are distributed as \[installation bundles]\[]. \* \*\*Main channel\*\*: This channel has the newest features, but it hasn't been fully tested and might have some bugs. We don't recommend using it unless you're contributing to Flutter itself. The following information is available for each Flutter release in the SDK archive: \* \*\*Flutter version\*\*: The version number of the Flutter SDK (for example, 3.35.0, 2.10.5) follows a modified \[calendar versioning]\[] scheme called \_CalVer\_. For more information, visit the \[Flutter SDK versioning]\[] page. \* \*\*Architecture\*\*: The processor architecture the SDK is built for (for example, x64, arm64). This specifies the type of processor the SDK is compatible with. \* \*\*Ref\*\*: The git commit hash that uniquely identifies the specific codebase used for that release. \* \*\*Release Date\*\*: The date when that particular Flutter version was officially released. \* \*\*Dart version\*\*: The corresponding version of the Dart SDK included in the Flutter SDK release. \* \*\*Provenance\*\*: Provides details about the build process and origin of the SDK, potentially including information about security attestations or build systems used. Results are returned as JSON. \[calendar versioning]: https\://calver.org/ \[Flutter SDK versioning]: https\://github.com/flutter/flutter/blob/main/docs/releases/Release-versioning.md ## Public release windows Predictability is key to landing complex features safely. We use public release windows to provide the community with the visibility needed to plan ahead. By explicitly calling out branch cutoff dates and release targets, we enable everyone to align their development cycles and coordinate feature landing effectively. ### What is a branch cutoff date? This date is the deadline for pull requests to land in the default branches (\`main\` for Dart and \`master\` for Flutter) to guarantee inclusion in the next stable release. \* \*\*Before cutoff:\*\* Your PR will ship in the next stable version. \* \*\*After cutoff:\*\* Your PR will wait for the following cycle. ### 2026 schedule | Flutter version | Release target | Branch cutoff date | |-----------------|----------------|--------------------| | Flutter 3.41 | February, 2026 | 2026-01-06 | | Flutter 3.44 | May, 2026 | 2026-04-07 | | Flutter 3.47 | August, 2026 | 2026-07-07 | | Flutter 3.50 | November, 2026 | 2026-10-06 | {:.table} --- ## Stable channel ## Beta channel :::tip To see what's changed in a beta release, compare the version tags on GitHub. 1. Find the version number (tag) you want to see (for example, \`3.38.0-0.2.pre\`). 2. Find the previous version number (for example, \`3.38.0-0.1.pre\`). 3. Go to the \[GitHub compare page]\(https\://github.com/flutter/flutter/compare). 4. Select the older tag for the \`base\` field and the newer tag for the \`compare\` field. For example: \[\`flutter/flutter\@3.38.0-0.1.pre...3.38.0-0.2.pre\`]\(https\://github.com/flutter/flutter/compare/3.38.0-0.1.pre...3.38.0-0.2.pre) ::: []()## Main channel \[Installation bundles]\[] are not available for the \`main\` channel (which was previously known as the \`master\` channel). However, you can get the SDK directly from \[GitHub repo]\[] by cloning the \`main\` channel and then triggering a download of the SDK dependencies: \`\`\`console $ git clone -b main https\://github.com/flutter/flutter.git $ ./flutter/bin/flutter --version \`\`\` []()## More information - For instructions on installing Flutter from a downloaded SDK, see \[Install Flutter manually]\[manual-install]. - To learn what's new in the major Flutter builds, check out the \[release notes]\[] page. - For details on how our installation bundles are structured, see \[Installation bundles]\[]. \[channels]: https\://github.com/flutter/flutter/blob/main/docs/releases/Flutter-build-release-channels.md \[manual-install]: /install/manual \[release notes]: /release/release-notes \[GitHub repo]: https\://github.com/flutter/flutter \[Installation bundles]: https\://github.com/flutter/flutter/blob/main/docs/infra/Flutter-Installation-Bundles.md

----
url: https://docs.flutter.dev/ui/widgets/styling
----

Manage the theme of your app, make your app responsive to screen sizes, or add padding.

[Media​Query](https://api.flutter.dev/flutter/widgets/MediaQuery-class.html)

[Establishes a subtree in which media queries resolve to the given data.](https://api.flutter.dev/flutter/widgets/MediaQuery-class.html)

[Padding](https://api.flutter.dev/flutter/widgets/Padding-class.html)

[A widget that insets its child by the given padding.](https://api.flutter.dev/flutter/widgets/Padding-class.html)

[Theme](https://pub.dev/documentation/material_ui/latest/material_ui/Theme-class.html)

[Applies a theme to descendant widgets. A theme describes the colors and typographic choices of an application.](https://pub.dev/documentation/material_ui/latest/material_ui/Theme-class.html)

Find more widgets in the [widget catalog](/ui/widgets).

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-09-29. [Report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/ui/widgets/styling "Report an issue with this page").

----
url: https://docs.flutter.dev/ui/interactivity/gestures
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Taps, drags, and other gestures](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

This document explains how to listen for, and respond to, *gestures* in Flutter. Examples of gestures include taps, drags, and scaling.

The gesture system in Flutter has two separate layers. The first layer has raw pointer events that describe the location and movement of pointers (for example, touches, mice, and styli) across the screen. The second layer has *gestures* that describe semantic actions that consist of one or more pointer movements.

## Pointers

[#](#pointers)

Pointers represent raw data about the user's interaction with the device's screen. There are four types of pointer events:

* [`PointerDownEvent`](https://api.flutter.dev/flutter/gestures/PointerDownEvent-class.html)

  The pointer has contacted the screen at a particular location.

* [`PointerMoveEvent`](https://api.flutter.dev/flutter/gestures/PointerMoveEvent-class.html)

  The pointer has moved from one location on the screen to another.

* [`PointerUpEvent`](https://api.flutter.dev/flutter/gestures/PointerUpEvent-class.html)

  The pointer has stopped contacting the screen.

* [`PointerCancelEvent`](https://api.flutter.dev/flutter/gestures/PointerCancelEvent-class.html)

  Input from this pointer is no longer directed towards this app.

On pointer down, the framework does a *hit test* on your app to determine which widget exists at the location where the pointer contacted the screen. The pointer down event (and subsequent events for that pointer) are then dispatched to the innermost widget found by the hit test. From there, the events bubble up the tree and are dispatched to all the widgets on the path from the innermost widget to the root of the tree. There is no mechanism for canceling or stopping pointer events from being dispatched further.

To listen to pointer events directly from the widgets layer, use a [`Listener`](https://api.flutter.dev/flutter/widgets/Listener-class.html) widget. However, generally, consider using gestures (as discussed below) instead.

## Gestures

[#](#gestures)

Gestures represent semantic actions (for example, tap, drag, and scale) that are recognized from multiple individual pointer events, potentially even multiple individual pointers. Gestures can dispatch multiple events, corresponding to the lifecycle of the gesture (for example, drag start, drag update, and drag end):

**Tap**

* `onTapDown`

  A pointer that might cause a tap has contacted the screen at a particular location.

* `onTapUp`

  A pointer that triggers a tap has stopped contacting the screen at a particular location.

* `onTap`

  The pointer that previously triggered the `onTapDown` has also triggered `onTapUp` which ends up causing a tap.

* `onTapCancel`

  The pointer that previously triggered the `onTapDown` won't end up causing a tap.

**Double tap**

* `onDoubleTap`

  The user has tapped the screen at the same location twice in quick succession.

**Long press**

* `onLongPress`

  A pointer has remained in contact with the screen at the same location for a long period of time.

**Vertical drag**

* `onVerticalDragStart`

  A pointer has contacted the screen and might begin to move vertically.

* `onVerticalDragUpdate`

  A pointer that is in contact with the screen and moving vertically has moved in the vertical direction.

* `onVerticalDragEnd`

  A pointer that was previously in contact with the screen and moving vertically is no longer in contact with the screen and was moving at a specific velocity when it stopped contacting the screen.

**Horizontal drag**

* `onHorizontalDragStart`

  A pointer has contacted the screen and might begin to move horizontally.

* `onHorizontalDragUpdate`

  A pointer that is in contact with the screen and moving horizontally has moved in the horizontal direction.

* `onHorizontalDragEnd`

  A pointer that was previously in contact with the screen and moving horizontally is no longer in contact with the screen and was moving at a specific velocity when it stopped contacting the screen.

**Pan**

* `onPanStart`

  A pointer has contacted the screen and might begin to move horizontally or vertically. This callback crashes if `onHorizontalDragStart` or `onVerticalDragStart` is set.

* `onPanUpdate`

  A pointer that is in contact with the screen and is moving in the vertical or horizontal direction. This callback crashes if `onHorizontalDragUpdate` or `onVerticalDragUpdate` is set.

* `onPanEnd`

  A pointer that was previously in contact with screen is no longer in contact with the screen and is moving at a specific velocity when it stopped contacting the screen. This callback crashes if `onHorizontalDragEnd` or `onVerticalDragEnd` is set.

### Adding gesture detection to widgets

[#](#adding-gesture-detection-to-widgets)

To listen to gestures from the widgets layer, use a [`GestureDetector`](https://api.flutter.dev/flutter/widgets/GestureDetector-class.html).

If you're using Material Components, many of those widgets already respond to taps or gestures. For example, [`IconButton`](https://pub.dev/documentation/material_ui/latest/material_ui/IconButton-class.html) and [`TextButton`](https://pub.dev/documentation/material_ui/latest/material_ui/TextButton-class.html) respond to presses (taps), and [`ListView`](https://api.flutter.dev/flutter/widgets/ListView-class.html) responds to swipes to trigger scrolling. If you aren't using those widgets, but you want the "ink splash" effect on a tap, you can use [`InkWell`](https://pub.dev/documentation/material_ui/latest/material_ui/InkWell-class.html).

### Gesture disambiguation

[#](#gesture-disambiguation)

At a given location on screen, there might be multiple gesture detectors. For example:

* A `ListTile` has a tap recognizer that responds to the entire `ListTile`, and a nested one around a trailing icon button. The screen rect of the trailing icon is now covered by two gesture recognizers that need to negotiate for who handles the gesture if it turns out to be a tap.
* A single `GestureDetector` covers a screen area configured to handle multiple gestures, such as a long press and a tap. The `tap` recognizer must now negotiate with the `long press` recognizer when the user touches that part of the screen. Depending on what happens next with that pointer, one of the two recognizers receives the gesture, or neither receives the gesture if the user performs something that's neither a tap nor a long press.

All of these gesture detectors listen to the stream of pointer events as they flow past and attempt to recognize specific gestures. The [`GestureDetector`](https://api.flutter.dev/flutter/widgets/GestureDetector-class.html) widget decides which gestures to attempt to recognize based on which of its callbacks are non-null.

When there is more than one gesture recognizer for a given pointer on the screen, the framework disambiguates which gesture the user intends by having each recognizer join the *gesture arena*. The gesture arena determines which gesture wins using the following rules:

* At any time, a recognizer can eliminate itself and leave the arena. If there's only one recognizer left in the arena, that recognizer wins.

* At any time, a recognizer can declare itself the winner, causing all of the remaining recognizers to lose.

For example, when disambiguating horizontal and vertical dragging, both recognizers enter the arena when they receive the pointer down event. The recognizers observe the pointer move events. If the user moves the pointer more than a certain number of logical pixels horizontally, the horizontal recognizer declares the win and the gesture is interpreted as a horizontal drag. Similarly, if the user moves more than a certain number of logical pixels vertically, the vertical recognizer declares itself the winner.

The gesture arena is beneficial when there is only a horizontal (or vertical) drag recognizer. In that case, there is only one recognizer in the arena and the horizontal drag is recognized immediately, which means the first pixel of horizontal movement can be treated as a drag and the user won't need to wait for further gesture disambiguation.

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-09-29. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/ui/interactivity/gestures/index.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/ui/interactivity/gestures\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/ui/interactivity/gestures/index.md "Report an issue with this page").

----
url: https://docs.flutter.dev/app-architecture/case-study/dependency-injection
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Communicating between layers](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

Along with defining clear responsibilities for each component of the architecture, it's important to consider how the components communicate. This refers to both the rules that dictate communication, and the technical implementation of how components communicate. An app's architecture should answer the following questions:

* Which components are allowed to communicate with which other components (including components of the same type)?
* What do these components expose as output to each other?
* How is any given layer 'wired up' to another layer?

Using this diagram as a guide, the rules of engagement are as follows:

| Component  | Rules of engagement                                                                                                                                                                                                                              |
| ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| View       | 1. A view is only aware of exactly one view model, and is never aware of any other layer or component. When created, Flutter passes the view model to the view as an argument, exposing the view model's data and command callbacks to the view. |
| ViewModel  | 1) A ViewModel belongs to exactly one view, which can see its data, but the model never needs to know that a view exists.
2) A view model is aware of one or more repositories, which are passed into the view model's constructor.              |
| Repository | 1. A repository can be aware of many services, which are passed as arguments into the repository constructor.
2. A repository can be used by many view models, but it never needs to be aware of them.                                           |
| Service    | 1) A service can be used by many repositories, but it never needs to be aware of a repository (or any other object).                                                                                                                             |

## Dependency injection

[#](#dependency-injection)

This guide has shown how these different components communicate with each other by using inputs and outputs. In every case, communication between two layers is facilitated by passing a component into the constructor methods (of the components that consume its data), such as a `Service` into a `Repository.`

dart

```
class MyRepository {
  MyRepository({required MyService myService})
          : _myService = myService;
​
  late final MyService _myService;
}
```

One thing that's missing, however, is object creation. Where, in an application, is the `MyService` instance created so that it can be passed into `MyRepository`? This answer to this question involves a pattern known as [dependency injection](https://en.wikipedia.org/wiki/Dependency_injection).

In the Compass app, *dependency injection* is handled using [`package:provider`](https://pub.dev/packages/provider). Based on their experience building Flutter apps, teams at Google recommend using `package:provider` to implement dependency injection.

Services and repositories are exposed to the top level of the widget tree of the Flutter application as `Provider` objects.

dependencies.dart

dart

```
runApp(
  MultiProvider(
    providers: [
      Provider(create: (context) => AuthApiClient()),
      Provider(create: (context) => ApiClient()),
      Provider(create: (context) => SharedPreferencesService()),
      ChangeNotifierProvider(
        create: (context) => AuthRepositoryRemote(
          authApiClient: context.read(),
          apiClient: context.read(),
          sharedPreferencesService: context.read(),
        ) as AuthRepository,
      ),
      Provider(create: (context) =>
        DestinationRepositoryRemote(
          apiClient: context.read(),
        ) as DestinationRepository,
      ),
      Provider(create: (context) =>
        ContinentRepositoryRemote(
          apiClient: context.read(),
        ) as ContinentRepository,
      ),
      // In the Compass app, additional service and repository providers live here.
    ],
    child: const MainApp(),
  ),
);
```

Services are exposed only so they can immediately be injected into repositories via the `BuildContext.read` method from `provider`, as shown in the preceding snippet. Repositories are then exposed so that they can be injected into view models as needed.

Slightly lower in the widget tree, view models that correspond to a full screen are created in the [`package:go_router`](https://pub.dev/packages/go_router) configuration, where provider is again used to inject the necessary repositories.

router.dart

dart

```
// This code was modified for demo purposes.
GoRouter router(
  AuthRepository authRepository,
) =>
    GoRouter(
      initialLocation: Routes.home,
      debugLogDiagnostics: true,
      redirect: _redirect,
      refreshListenable: authRepository,
      routes: [
        GoRoute(
          path: Routes.login,
          builder: (context, state) {
            return LoginScreen(
              viewModel: LoginViewModel(
                authRepository: context.read(),
              ),
            );
          },
        ),
        GoRoute(
          path: Routes.home,
          builder: (context, state) {
            final viewModel = HomeViewModel(
              bookingRepository: context.read(),
            );
            return HomeScreen(viewModel: viewModel);
          },
          routes: [
            // ...
          ],
        ),
      ],
    );
```

Within the view model or repository, the injected component should be private. For example, the `HomeViewModel` class looks like this:

home\_viewmodel.dart

dart

```
class HomeViewModel extends ChangeNotifier {
  HomeViewModel({
    required BookingRepository bookingRepository,
    required UserRepository userRepository,
  })  : _bookingRepository = bookingRepository,
        _userRepository = userRepository;
​
  final BookingRepository _bookingRepository;
  final UserRepository _userRepository;
​
  // ...
}
```

Private methods prevent the view, which has access to the view model, from calling methods on the repository directly.

This concludes the code walkthrough of the Compass app. This page only walked through the architecture-related code, but it doesn't tell the whole story. Most utility code, widget code, and UI styling was ignored. Browse the code in the [Compass app repository](https://github.com/flutter/samples/tree/main/compass_app) for a complete example of a robust Flutter application built following these principles.

## Feedback

[#](#feedback)

As this section of the website is evolving, we [welcome your feedback](https://google.qualtrics.com/jfe/form/SV_4T0XuR9Ts29acw6?page=%22case-study/dependency-injection%22)!

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-07-23. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/app-architecture/case-study/dependency-injection.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/app-architecture/case-study/dependency-injection\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/app-architecture/case-study/dependency-injection.md "Report an issue with this page").

----
url: https://docs.flutter.dev/cookbook/effects/parallax-scrolling.md
----

\# Create a scrolling parallax effect > How to implement a scrolling parallax effect.

<!--?code-excerpt path-base="cookbook/effects/parallax_scrolling"?-->

When you scroll a list of cards (containing images, for example) in an app, you might notice that those images appear to scroll more slowly than the rest of the screen. It almost looks as if the cards in the list are in the foreground, but the images themselves sit far off in the distant background. This effect is known as parallax. In this recipe, you create the parallax effect by building a list of cards (with rounded corners containing some text). Each card also contains an image. As the cards slide up the screen, the images within each card slide down. The following animation shows the app's behavior: !\[Parallax scrolling]\(/assets/images/docs/cookbook/effects/ParallaxScrolling.webp){:.site-mobile-screenshot} ## Create a list to hold the parallax items To display a list of parallax scrolling images, you must first display a list. Create a new stateless widget called \`ParallaxRecipe\`. Within \`ParallaxRecipe\`, build a widget tree with a \`SingleChildScrollView\` and a \`Column\`, which forms a list.

<!--?code-excerpt "lib/excerpt1.dart (ParallaxRecipe)"?-->

\`\`\`dart class ParallaxRecipe extends StatelessWidget { const ParallaxRecipe({super.key}); @override Widget build(BuildContext context) { return const SingleChildScrollView(child: Column(children: \[])); } } \`\`\` ## Display items with text and a static image Each list item displays a rounded-rectangle background image, representing one of seven locations in the world. Stacked on top of that background image is the name of the location and its country, positioned in the lower left. Between the background image and the text is a dark gradient, which improves the legibility of the text against the background. Implement a stateless widget called \`LocationListItem\` that consists of the previously mentioned visuals. For now, use a static \`Image\` widget for the background. Later, you'll replace that widget with a parallax version.

<!--?code-excerpt "lib/excerpt2.dart (LocationListItem)"?-->

\`\`\`dart @immutable class LocationListItem extends StatelessWidget { const LocationListItem({ super.key, required this.imageUrl, required this.name, required this.country, }); final String imageUrl; final String name; final String country; @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16), child: AspectRatio( aspectRatio: 16 / 9, child: ClipRRect( borderRadius: BorderRadius.circular(16), child: Stack( children: \[ \_buildParallaxBackground(context), \_buildGradient(), \_buildTitleAndSubtitle(), ], ), ), ), ); } Widget \_buildParallaxBackground(BuildContext context) { return Positioned.fill(child: Image.network(imageUrl, fit: BoxFit.cover)); } Widget \_buildGradient() { return Positioned.fill( child: DecoratedBox( decoration: BoxDecoration( gradient: LinearGradient( colors: \[Colors.transparent, Colors.black.withValues(alpha: 0.7)], begin: Alignment.topCenter, end: Alignment.bottomCenter, stops: const \[0.6, 0.95], ), ), ), ); } Widget \_buildTitleAndSubtitle() { return Positioned( left: 20, bottom: 20, child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: \[ Text( name, style: const TextStyle( color: Colors.white, fontSize: 20, fontWeight: FontWeight.bold, ), ), Text( country, style: const TextStyle(color: Colors.white, fontSize: 14), ), ], ), ); } } \`\`\` Next, add the list items to your \`ParallaxRecipe\` widget.

<!--?code-excerpt "lib/excerpt3.dart (ParallaxRecipeItems)"?-->

\`\`\`dart class ParallaxRecipe extends StatelessWidget { const ParallaxRecipe({super.key}); @override Widget build(BuildContext context) { return SingleChildScrollView( child: Column( children: \[ for (final location in locations) LocationListItem( imageUrl: location.imageUrl, name: location.name, country: location.place, ), ], ), ); } } \`\`\` You now have a typical, scrollable list of cards that displays seven unique locations in the world. In the next step, you add a parallax effect to the background image. ## Implement the parallax effect A parallax scrolling effect is achieved by slightly pushing the background image in the opposite direction of the rest of the list. As the list items slide up the screen, each background image slides slightly downward. Conversely, as the list items slide down the screen, each background image slides slightly upward. Visually, this results in parallax. The parallax effect depends on the list item's current position within its ancestor \`Scrollable\`. As the list item's scroll position changes, the position of the list item's background image must also change. This is an interesting problem to solve. The position of a list item within the \`Scrollable\` isn't available until Flutter's layout phase is complete. This means that the position of the background image must be determined in the paint phase, which comes after the layout phase. Fortunately, Flutter provides a widget called \`Flow\`, which is specifically designed to give you control over the transform of a child widget immediately before the widget is painted. In other words, you can intercept the painting phase and take control to reposition your child widgets however you want. :::note To learn more, check out this short Widget of the Week video on the \`Flow\` widget: ::: :::note In cases where you need control over what a child paints, rather than where a child is painted, consider using a \[\`CustomPaint\`]\[] widget. In cases where you need control over the layout, painting, and hit testing, consider defining a custom \[\`RenderBox\`]\[]. ::: Wrap your background \`Image\` widget with a \[\`Flow\`]\[] widget.

<!--?code-excerpt "lib/excerpt4.dart (BuildParallaxBackground)" replace="/\n    delegate: ParallaxFlowDelegate\(\),//g"?-->

\`\`\`dart Widget \_buildParallaxBackground(BuildContext context) { return Flow( children: \[Image.network(imageUrl, fit: BoxFit.cover)], ); } \`\`\` Introduce a new \`FlowDelegate\` called \`ParallaxFlowDelegate\`.

<!--?code-excerpt "lib/excerpt4.dart (BuildParallaxBackground)"?-->

\`\`\`dart Widget \_buildParallaxBackground(BuildContext context) { return Flow( delegate: ParallaxFlowDelegate(), children: \[Image.network(imageUrl, fit: BoxFit.cover)], ); } \`\`\`

<!--?code-excerpt "lib/excerpt4.dart (parallax-flow-delegate)" replace="/\n    return constraints;//g"?-->

\`\`\`dart class ParallaxFlowDelegate extends FlowDelegate { ParallaxFlowDelegate(); @override BoxConstraints getConstraintsForChild(int i, BoxConstraints constraints) { // TODO: We'll add more to this later. } @override void paintChildren(FlowPaintingContext context) { // TODO: We'll add more to this later. } @override bool shouldRepaint(covariant FlowDelegate oldDelegate) { // TODO: We'll add more to this later. return true; } } \`\`\` A \`FlowDelegate\` controls how its children are sized and where those children are painted. In this case, your \`Flow\` widget has only one child: the background image. That image must be exactly as wide as the \`Flow\` widget. Return tight width constraints for your background image child.

<!--?code-excerpt "lib/main.dart (TightWidth)"?-->

\`\`\`dart @override BoxConstraints getConstraintsForChild(int i, BoxConstraints constraints) { return BoxConstraints.tightFor(width: constraints.maxWidth); } \`\`\` Your background images are now sized appropriately, but you still need to calculate the vertical position of each background image based on its scroll position, and then paint it. There are three critical pieces of information that you need to compute the desired position of a background image: \* The bounds of the ancestor \`Scrollable\` \* The bounds of the individual list item \* The size of the image after it's scaled down to fit in the list item To look up the bounds of the \`Scrollable\`, you pass a \`ScrollableState\` into your \`FlowDelegate\`. To look up the bounds of your individual list item, pass your list item's \`BuildContext\` into your \`FlowDelegate\`. To look up the final size of your background image, assign a \`GlobalKey\` to your \`Image\` widget, and then you pass that \`GlobalKey\` into your \`FlowDelegate\`. Make this information available to \`ParallaxFlowDelegate\`.

<!--?code-excerpt "lib/excerpt5.dart (global-key)" plaster="none"?-->

\`\`\`dart @immutable class LocationListItem extends StatelessWidget { final GlobalKey \_backgroundImageKey = GlobalKey(); Widget \_buildParallaxBackground(BuildContext context) { return Flow( delegate: ParallaxFlowDelegate( scrollable: Scrollable.of(context), listItemContext: context, backgroundImageKey: \_backgroundImageKey, ), children: \[ Image.network(imageUrl, key: \_backgroundImageKey, fit: BoxFit.cover), ], ); } } \`\`\`

<!--?code-excerpt "lib/excerpt5.dart (parallax-flow-delegate-gk)" plaster="none"?-->

\`\`\`dart class ParallaxFlowDelegate extends FlowDelegate { ParallaxFlowDelegate({ required this.scrollable, required this.listItemContext, required this.backgroundImageKey, }); final ScrollableState scrollable; final BuildContext listItemContext; final GlobalKey backgroundImageKey; } \`\`\` Having all the information needed to implement parallax scrolling, implement the \`shouldRepaint()\` method.

<!--?code-excerpt "lib/main.dart (ShouldRepaint)"?-->

\`\`\`dart @override bool shouldRepaint(ParallaxFlowDelegate oldDelegate) { return scrollable != oldDelegate.scrollable || listItemContext != oldDelegate.listItemContext || backgroundImageKey != oldDelegate.backgroundImageKey; } \`\`\` Now, implement the layout calculations for the parallax effect. First, calculate the pixel position of a list item within its ancestor \`Scrollable\`.

<!--?code-excerpt "lib/excerpt5.dart (paint-children)" plaster="none"?-->

\`\`\`dart @override void paintChildren(FlowPaintingContext context) { // Calculate the position of this list item within the viewport. final scrollableBox = scrollable.context.findRenderObject() as RenderBox; final listItemBox = listItemContext.findRenderObject() as RenderBox; final listItemOffset = listItemBox.localToGlobal( listItemBox.size.centerLeft(Offset.zero), ancestor: scrollableBox, ); } \`\`\` Use the pixel position of the list item to calculate its percentage from the top of the \`Scrollable\`. A list item at the top of the scrollable area should produce 0%, and a list item at the bottom of the scrollable area should produce 100%.

<!--?code-excerpt "lib/excerpt5.dart (paint-children-2)"?-->

\`\`\`dart @override void paintChildren(FlowPaintingContext context) { // Calculate the position of this list item within the viewport. final scrollableBox = scrollable.context.findRenderObject() as RenderBox; final listItemBox = listItemContext.findRenderObject() as RenderBox; final listItemOffset = listItemBox.localToGlobal( listItemBox.size.centerLeft(Offset.zero), ancestor: scrollableBox, ); // Determine the percent position of this list item within the // scrollable area. final viewportDimension = scrollable.position.viewportDimension; final scrollFraction = (listItemOffset.dy / viewportDimension).clamp( 0.0, 1.0, ); // ··· } \`\`\` Use the scroll percentage to calculate an \`Alignment\`. At 0%, you want \`Alignment(0.0, -1.0)\`, and at 100%, you want \`Alignment(0.0, 1.0)\`. These coordinates correspond to top and bottom alignment, respectively.

<!--?code-excerpt "lib/excerpt5.dart (paint-children-3)" plaster="none"?-->

\`\`\`dart @override void paintChildren(FlowPaintingContext context) { // Calculate the position of this list item within the viewport. final scrollableBox = scrollable.context.findRenderObject() as RenderBox; final listItemBox = listItemContext.findRenderObject() as RenderBox; final listItemOffset = listItemBox.localToGlobal( listItemBox.size.centerLeft(Offset.zero), ancestor: scrollableBox, ); // Determine the percent position of this list item within the // scrollable area. final viewportDimension = scrollable.position.viewportDimension; final scrollFraction = (listItemOffset.dy / viewportDimension).clamp( 0.0, 1.0, ); // Calculate the vertical alignment of the background // based on the scroll percent. final verticalAlignment = Alignment(0.0, scrollFraction \* 2 - 1); } \`\`\` Use \`verticalAlignment\`, along with the size of the list item and the size of the background image, to produce a \`Rect\` that determines where the background image should be positioned.

<!--?code-excerpt "lib/excerpt5.dart (paint-children-4)" plaster="none"?-->

\`\`\`dart @override void paintChildren(FlowPaintingContext context) { // Calculate the position of this list item within the viewport. final scrollableBox = scrollable.context.findRenderObject() as RenderBox; final listItemBox = listItemContext.findRenderObject() as RenderBox; final listItemOffset = listItemBox.localToGlobal( listItemBox.size.centerLeft(Offset.zero), ancestor: scrollableBox, ); // Determine the percent position of this list item within the // scrollable area. final viewportDimension = scrollable.position.viewportDimension; final scrollFraction = (listItemOffset.dy / viewportDimension).clamp( 0.0, 1.0, ); // Calculate the vertical alignment of the background // based on the scroll percent. final verticalAlignment = Alignment(0.0, scrollFraction \* 2 - 1); // Convert the background alignment into a pixel offset for // painting purposes. final backgroundSize = (backgroundImageKey.currentContext!.findRenderObject() as RenderBox) .size; final listItemSize = context.size; final childRect = verticalAlignment.inscribe( backgroundSize, Offset.zero & listItemSize, ); } \`\`\` Using \`childRect\`, paint the background image with the desired translation transformation. It's this transformation over time that gives you the parallax effect.

<!--?code-excerpt "lib/excerpt5.dart (paint-children-5)" plaster="none" ?-->

\`\`\`dart @override void paintChildren(FlowPaintingContext context) { // Calculate the position of this list item within the viewport. final scrollableBox = scrollable.context.findRenderObject() as RenderBox; final listItemBox = listItemContext.findRenderObject() as RenderBox; final listItemOffset = listItemBox.localToGlobal( listItemBox.size.centerLeft(Offset.zero), ancestor: scrollableBox, ); // Determine the percent position of this list item within the // scrollable area. final viewportDimension = scrollable.position.viewportDimension; final scrollFraction = (listItemOffset.dy / viewportDimension).clamp( 0.0, 1.0, ); // Calculate the vertical alignment of the background // based on the scroll percent. final verticalAlignment = Alignment(0.0, scrollFraction \* 2 - 1); // Convert the background alignment into a pixel offset for // painting purposes. final backgroundSize = (backgroundImageKey.currentContext!.findRenderObject() as RenderBox) .size; final listItemSize = context.size; final childRect = verticalAlignment.inscribe( backgroundSize, Offset.zero & listItemSize, ); // Paint the background. context.paintChild( 0, transform: Transform.translate(offset: Offset(0.0, childRect.top)) .transform, ); } \`\`\` You need one final detail to achieve the parallax effect. The \`ParallaxFlowDelegate\` repaints when the inputs change, but the \`ParallaxFlowDelegate\` doesn't repaint every time the scroll position changes. Pass the \`ScrollableState\`'s \`ScrollPosition\` to the \`FlowDelegate\` superclass so that the \`FlowDelegate\` repaints every time the \`ScrollPosition\` changes.

<!--?code-excerpt "lib/main.dart (SuperScrollPosition)" replace="/;\n/;\n}/g"?-->

\`\`\`dart class ParallaxFlowDelegate extends FlowDelegate { ParallaxFlowDelegate({ required this.scrollable, required this.listItemContext, required this.backgroundImageKey, }) : super(repaint: scrollable.position); \`\`\` Congratulations! You now have a list of cards with parallax, scrolling background images. ## Interactive example Run the app: \* Scroll up and down to observe the parallax effect.

<!--?code-excerpt "lib/main.dart"?-->

\`\`\`dartpad title="Flutter parallax scrolling hands-on example in DartPad" run="true" import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; const Color darkBlue = Color.fromARGB(255, 18, 32, 47); void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( theme: ThemeData.dark().copyWith(scaffoldBackgroundColor: darkBlue), debugShowCheckedModeBanner: false, home: const Scaffold(body: Center(child: ExampleParallax())), ); } } class ExampleParallax extends StatelessWidget { const ExampleParallax({super.key}); @override Widget build(BuildContext context) { return SingleChildScrollView( child: Column( children: \[ for (final location in locations) LocationListItem( imageUrl: location.imageUrl, name: location.name, country: location.place, ), ], ), ); } } class LocationListItem extends StatelessWidget { LocationListItem({ super.key, required this.imageUrl, required this.name, required this.country, }); final String imageUrl; final String name; final String country; final GlobalKey \_backgroundImageKey = GlobalKey(); @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16), child: AspectRatio( aspectRatio: 16 / 9, child: ClipRRect( borderRadius: BorderRadius.circular(16), child: Stack( children: \[ \_buildParallaxBackground(context), \_buildGradient(), \_buildTitleAndSubtitle(), ], ), ), ), ); } Widget \_buildParallaxBackground(BuildContext context) { return Flow( delegate: ParallaxFlowDelegate( scrollable: Scrollable.of(context), listItemContext: context, backgroundImageKey: \_backgroundImageKey, ), children: \[ Image.network(imageUrl, key: \_backgroundImageKey, fit: BoxFit.cover), ], ); } Widget \_buildGradient() { return Positioned.fill( child: DecoratedBox( decoration: BoxDecoration( gradient: LinearGradient( colors: \[Colors.transparent, Colors.black.withValues(alpha: 0.7)], begin: Alignment.topCenter, end: Alignment.bottomCenter, stops: const \[0.6, 0.95], ), ), ), ); } Widget \_buildTitleAndSubtitle() { return Positioned( left: 20, bottom: 20, child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: \[ Text( name, style: const TextStyle( color: Colors.white, fontSize: 20, fontWeight: FontWeight.bold, ), ), Text( country, style: const TextStyle(color: Colors.white, fontSize: 14), ), ], ), ); } } class ParallaxFlowDelegate extends FlowDelegate { ParallaxFlowDelegate({ required this.scrollable, required this.listItemContext, required this.backgroundImageKey, }) : super(repaint: scrollable.position); final ScrollableState scrollable; final BuildContext listItemContext; final GlobalKey backgroundImageKey; @override BoxConstraints getConstraintsForChild(int i, BoxConstraints constraints) { return BoxConstraints.tightFor(width: constraints.maxWidth); } @override void paintChildren(FlowPaintingContext context) { // Calculate the position of this list item within the viewport. final scrollableBox = scrollable.context.findRenderObject() as RenderBox; final listItemBox = listItemContext.findRenderObject() as RenderBox; final listItemOffset = listItemBox.localToGlobal( listItemBox.size.centerLeft(Offset.zero), ancestor: scrollableBox, ); // Determine the percent position of this list item within the // scrollable area. final viewportDimension = scrollable.position.viewportDimension; final scrollFraction = (listItemOffset.dy / viewportDimension).clamp( 0.0, 1.0, ); // Calculate the vertical alignment of the background // based on the scroll percent. final verticalAlignment = Alignment(0.0, scrollFraction \* 2 - 1); // Convert the background alignment into a pixel offset for // painting purposes. final backgroundSize = (backgroundImageKey.currentContext!.findRenderObject() as RenderBox) .size; final listItemSize = context.size; final childRect = verticalAlignment.inscribe( backgroundSize, Offset.zero & listItemSize, ); // Paint the background. context.paintChild( 0, transform: Transform.translate(offset: Offset(0.0, childRect.top)) .transform, ); } @override bool shouldRepaint(ParallaxFlowDelegate oldDelegate) { return scrollable != oldDelegate.scrollable || listItemContext != oldDelegate.listItemContext || backgroundImageKey != oldDelegate.backgroundImageKey; } } class Parallax extends SingleChildRenderObjectWidget { const Parallax({super.key, required Widget background}) : super(child: background); @override RenderObject createRenderObject(BuildContext context) { return RenderParallax(scrollable: Scrollable.of(context)); } @override void updateRenderObject( BuildContext context, covariant RenderParallax renderObject, ) { renderObject.scrollable = Scrollable.of(context); } } class ParallaxParentData extends ContainerBoxParentData {} class RenderParallax extends RenderBox with RenderObjectWithChildMixin, RenderProxyBoxMixin { RenderParallax({required this.\_scrollable}); ScrollableState \_scrollable; ScrollableState get scrollable => \_scrollable; set scrollable(ScrollableState value) { if (value != \_scrollable) { if (attached) { \_scrollable.position.removeListener(markNeedsLayout); } \_scrollable = value; if (attached) { \_scrollable.position.addListener(markNeedsLayout); } } } @override void attach(covariant PipelineOwner owner) { super.attach(owner); \_scrollable.position.addListener(markNeedsLayout); } @override void detach() { \_scrollable.position.removeListener(markNeedsLayout); super.detach(); } @override void setupParentData(covariant RenderObject child) { if (child.parentData is! ParallaxParentData) { child.parentData = ParallaxParentData(); } } @override void performLayout() { size = constraints.biggest; // Force the background to take up all available width // and then scale its height based on the image's aspect ratio. final background = child!; final backgroundImageConstraints = BoxConstraints.tightFor( width: size.width, ); background.layout(backgroundImageConstraints, parentUsesSize: true); // Set the background's local offset, which is zero. (background.parentData as ParallaxParentData).offset = Offset.zero; } @override void paint(PaintingContext context, Offset offset) { // Get the size of the scrollable area. final viewportDimension = scrollable.position.viewportDimension; // Calculate the global position of this list item. final scrollableBox = scrollable.context.findRenderObject() as RenderBox; final backgroundOffset = localToGlobal( size.centerLeft(Offset.zero), ancestor: scrollableBox, ); // Determine the percent position of this list item within the // scrollable area. final scrollFraction = (backgroundOffset.dy / viewportDimension).clamp( 0.0, 1.0, ); // Calculate the vertical alignment of the background // based on the scroll percent. final verticalAlignment = Alignment(0.0, scrollFraction \* 2 - 1); // Convert the background alignment into a pixel offset for // painting purposes. final background = child!; final backgroundSize = background.size; final listItemSize = size; final childRect = verticalAlignment.inscribe( backgroundSize, Offset.zero & listItemSize, ); // Paint the background. context.paintChild( background, (background.parentData as ParallaxParentData).offset + offset + Offset(0.0, childRect.top), ); } } class Location { const Location({ required this.name, required this.place, required this.imageUrl, }); final String name; final String place; final String imageUrl; } const urlPrefix = 'https\://docs.flutter.dev/assets/images/exercise/effects/parallax'; const locations = \[ Location( name: 'Mount Rushmore', place: 'U.S.A', imageUrl: '$urlPrefix/01-mount-rushmore.jpg', ), Location( name: 'Gardens By The Bay', place: 'Singapore', imageUrl: '$urlPrefix/02-singapore.jpg', ), Location( name: 'Machu Picchu', place: 'Peru', imageUrl: '$urlPrefix/03-machu-picchu.jpg', ), Location( name: 'Vitznau', place: 'Switzerland', imageUrl: '$urlPrefix/04-vitznau.jpg', ), Location( name: 'Bali', place: 'Indonesia', imageUrl: '$urlPrefix/05-bali.jpg', ), Location( name: 'Mexico City', place: 'Mexico', imageUrl: '$urlPrefix/06-mexico-city.jpg', ), Location(name: 'Cairo', place: 'Egypt', imageUrl: '$urlPrefix/07-cairo.jpg'), ]; \`\`\` \[\`CustomPaint\`]: https\://api.flutter.dev/flutter/widgets/CustomPaint-class.html \[\`Flow\`]: https\://api.flutter.dev/flutter/widgets/Flow-class.html \[\`RenderBox\`]: https\://api.flutter.dev/flutter/rendering/RenderBox-class.html

----
url: https://docs.flutter.dev/tools/devtools/app-size.md
----

\# Use the app size tool > Learn how to use the DevTools app size tool. ## What is it? The app size tool allows you to analyze the total size of your app. You can view a single snapshot of "size information" using the \[Analysis tab]\[], or compare two different snapshots of "size information" using the \[Diff tab]\[]. ### What is "size information"? "Size information" contains size data for Dart code, native code, and non-code elements of your app, like the application package, assets and fonts. A "size information" file contains data for the total picture of your application size. ### Dart size information The Dart AOT compiler performs tree-shaking on your code when compiling your application (profile or release mode only—the AOT compiler is not used for debug builds, which are JIT compiled). This means that the compiler attempts to optimize your app's size by removing pieces of code that are unused or unreachable. After the compiler optimizes your code as much as it can, the end result can be summarized as the collection of packages, libraries, classes, and functions that exist in the binary output, along with their size in bytes. This is the Dart portion of "size information" we can analyze in the app size tool to further optimize Dart code and track down size issues. ## How to use it If DevTools is already connected to a running application, navigate to the "App Size" tab. !\[Screenshot of app size tab]\(/assets/images/docs/tools/devtools/app\_size\_tab.png) If DevTools is not connected to a running application, you can access the tool from the landing page that appears once you have launched DevTools (see \[launch instructions]\[]). !\[Screenshot of app size access on landing page]\(/assets/images/docs/tools/devtools/app\_size\_access\_landing\_page.png){:width="100%"} ## Analysis tab The analysis tab allows you to inspect a single snapshot of size information. You can view the hierarchical structure of the size data using the treemap and table, and you can view code attribution data (for example, why a piece of code is included in your compiled application) using the dominator tree and call graph. !\[Screenshot of app size analysis]\(/assets/images/docs/tools/devtools/app\_size\_analysis.png){:width="100%"} ### Loading a size file When you open the Analysis tab, you'll see instructions to load an app size file. Drag and drop an app size file into the dialog, and click "Analyze Size". !\[Screenshot of app size analysis loading screen]\(/assets/images/docs/tools/devtools/app\_size\_load\_analysis.png){:width="100%"} See \[Generating size files]\[] below for information on generating size files. ### Treemap and table The treemap and table show the hierarchical data for your app's size. #### Use the treemap A treemap is a visualization for hierarchical data. The space is broken up into rectangles, where each rectangle is sized and ordered by some quantitative variable (in this case, size in bytes). The area of each rectangle is proportional to the size the node occupies in the compiled application. Inside of each rectangle (call one A), there are additional rectangles that exist one level deeper in the data hierarchy (children of A). To drill into a cell in the treemap, select the cell. This re-roots the tree so that the selected cell becomes the visual root of the treemap. To navigate back, or up a level, use the breadcrumb navigator at the top of the treemap. !\[Screenshot of treemap breadcrumb navigator]\(/assets/images/docs/tools/devtools/treemap\_breadcrumbs.png){:width="100%"} ### Dominator tree and call graph This section of the page shows code size attribution data (for example, why a piece of code is included in your compiled application). This data is visible in the form of a dominator tree as well as a call graph. #### Use the dominator tree A \[dominator tree]\[] is a tree where each node's children are those nodes it immediately dominates. A node \`a\` is said to "dominate" a node \`b\` if every path to \`b\` must go through \`a\`. \[dominator tree]: https\://en.wikipedia.org/wiki/Dominator\_(graph\_theory) To put it in context of app size analysis, imagine \`package:a\` imports both \`package:b\` and \`package:c\`, and both \`package:b\` and \`package:c\` import \`package:d\`. \`\`\`plaintext package:a |\_\_ package:b | |\_\_ package:d |\_\_ package:c |\_\_ package:d \`\`\` In this example, \`package:a\` dominates \`package:d\`, so the dominator tree for this data would look like: \`\`\`plaintext package:a |\_\_ package:b |\_\_ package:c |\_\_ package:d \`\`\` This information is helpful for understanding why certain pieces of code are present in your compiled application. For example, if you are analyzing your app size and find an unexpected package included in your compiled app, you can use the dominator tree to trace the package to its root source. !\[Screenshot of code size dominator tree]\(/assets/images/docs/tools/devtools/app\_size\_dominator\_tree.png){:width="100%"} #### Use the call graph A call graph provides similar information to the dominator tree in regards to helping you understand why code exists in a compiled application. However, instead of showing the one-to-many dominant relationships between nodes of code size data like the dominator tree, the call graph shows the many-to-many relationships that existing between nodes of code size data. Again, using the following example: \`\`\`plaintext package:a |\_\_ package:b | |\_\_ package:d |\_\_ package:c |\_\_ package:d \`\`\` The call graph for this data would link \`package:d\` to its direct callers, \`package:b\` and \`package:c\`, instead of its "dominator", \`package:a\`. \`\`\`plaintext package:a --> package:b --> package:d package:a --> package:c --> \`\`\` This information is useful for understanding the fine-grained dependencies of between pieces of your code (packages, libraries, classes, functions). !\[Screenshot of code size call graph]\(/assets/images/docs/tools/devtools/app\_size\_call\_graph.png){:width="100%"} #### Should I use the dominator tree or the call graph? Use the dominator tree if you want to understand the \*root\* cause for why a piece of code is included in your application. Use the call graph if you want to understand all the call paths to and from a piece of code. A dominator tree is an analysis or slice of call graph data, where nodes are connected by "dominance" instead of parent-child hierarchy. In the case where a parent node dominates a child, the relationship in the call graph and the dominator tree would be identical, but this is not always the case. In the scenario where the call graph is complete (an edge exists between every pair of nodes), the dominator tree would show the that \`root\` is the dominator for every node in the graph. This is an example where the call graph would give you a better understanding around why a piece of code is included in your application. ## Diff tab The diff tab allows you to compare two snapshots of size information. The two size information files you are comparing should be generated from two different versions of the same app; for example, the size file generated before and after changes to your code. You can visualize the difference between the two data sets using the treemap and table. !\[Screenshot of app size diff]\(/assets/images/docs/tools/devtools/app\_size\_diff.png){:width="100%"} ### Loading size files When you open the \*\*Diff\*\* tab, you'll see instructions to load "old" and "new" size files. Again, these files need to be generated from the same application. Drag and drop these files into their respective dialogs, and click \*\*Analyze Diff\*\*. !\[Screenshot of app size diff loading screen]\(/assets/images/docs/tools/devtools/app\_size\_load\_diff.png){:width="100%"} See \[Generating size files]\[] below for information on generating these files. ### Treemap and table In the diff view, the treemap and tree table show only data that differs between the two imported size files. For questions about using the treemap, see \[Use the treemap]\[] above. ## Generating size files To use the app size tool, you'll need to generate a Flutter size analysis file. This file contains size information for your entire application (native code, Dart code, assets, fonts, etc.), and you can generate it using the \`--analyze-size\` flag: \`\`\`console flutter build --analyze-size \`\`\` This builds your application, prints a size summary to the command line, and prints a line telling you where to find the size analysis file. \`\`\`console flutter build apk --analyze-size --target-platform=android-arm64 ... ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ app-release.apk (total compressed) 6 MB ... ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ A summary of your APK analysis can be found at: build/apk-code-size-analysis\_01.json \`\`\` In this example, import the \`build/apk-code-size-analysis\_01.json\` file into the app size tool to analyze further. For more information, see \[App Size Documentation]\[]. ## Other resources To learn how to perform a step-by-step size analysis of the Wonderous App using DevTools, check out the \[App Size Tool tutorial]\[app-size-tutorial]. Various strategies to reduce an app's size are also discussed. \[Use the treemap]: #use-the-treemap \[Generating size files]: #generating-size-files \[Analysis tab]: #analysis-tab \[Diff tab]: #diff-tab \[launch instructions]: /tools/devtools#start \[App Size Documentation]: /perf/app-size#breaking-down-the-size \[app-size-tutorial]: https\://medium.com/@fluttergems/mastering-dart-flutter-devtools-app-size-tool-part-3-of-8-9be6e9ec42a2

----
url: https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.16.0
----

The 2.16.0 release of the Dart and Flutter DevTools includes the following changes among other general improvements. To learn more about DevTools, check out the [DevTools overview](https://docs.flutter.dev/tools/devtools).

## General updates

[#](#general-updates)

This release includes several bug fixes and improvements, as well as work towards some new features that are coming soon!

## Full commit history

[#](#full-commit-history)

To find a complete list of changes since the previous release, check out [the diff on GitHub](https://github.com/flutter/devtools/compare/v2.15.0...v2.16.0).

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/release-notes/release-notes-2.16.0.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.16.0\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/release-notes/release-notes-2.16.0.md "Report an issue with this page").

----
url: https://docs.flutter.dev/cookbook/navigation/navigation-basics.md
----

\# Navigate to a new screen and back > How to navigate between routes.

<!--?code-excerpt path-base="cookbook/navigation/navigation_basics"?-->

Most apps contain several screens for displaying different types of information. For example, an app might have a screen that displays products. When the user taps the image of a product, a new screen displays details about the product. :::note Terminology In Flutter, \_screens\_ and \_pages\_ are called \_routes\_. The remainder of this recipe refers to routes. ::: In Android, a route is equivalent to an \`Activity\`. In iOS, a route is equivalent to a \`ViewController\`. In Flutter, a route is just a widget. This recipe uses the \[\`Navigator\`]\[] to navigate to a new route. The next few sections show how to navigate between two routes, using these steps: 1. Create two routes. 2. Navigate to the second route using \`Navigator.push()\`. 3. Return to the first route using \`Navigator.pop()\`. ## 1. Create two routes First, create two routes to work with. Since this is a basic example, each route contains only a single button. Tapping the button on the first route navigates to the second route. Tapping the button on the second route returns to the first route. First, set up the visual structure:

<!--?code-excerpt "lib/main_step1.dart (first-second-routes)"?-->

\`\`\`dart class FirstRoute extends StatelessWidget { const FirstRoute({super.key}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('First Route')), body: Center( child: ElevatedButton( child: const Text('Open route'), onPressed: () { // Navigate to second route when tapped. }, ), ), ); } } class SecondRoute extends StatelessWidget { const SecondRoute({super.key}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('Second Route')), body: Center( child: ElevatedButton( onPressed: () { // Navigate back to first route when tapped. }, child: const Text('Go back!'), ), ), ); } } \`\`\`

<!--?code-excerpt "lib/main_step1_cupertino.dart (first-second-routes)"?-->

\`\`\`dart class FirstRoute extends StatelessWidget { const FirstRoute({super.key}); @override Widget build(BuildContext context) { return CupertinoPageScaffold( navigationBar: const CupertinoNavigationBar(middle: Text('First Route')), child: Center( child: CupertinoButton( child: const Text('Open route'), onPressed: () { // Navigate to second route when tapped. }, ), ), ); } } class SecondRoute extends StatelessWidget { const SecondRoute({super.key}); @override Widget build(BuildContext context) { return CupertinoPageScaffold( navigationBar: const CupertinoNavigationBar(middle: Text('Second Route')), child: Center( child: CupertinoButton( onPressed: () { // Navigate back to first route when tapped. }, child: const Text('Go back!'), ), ), ); } } \`\`\` ## 2. Navigate to the second route using Navigator.push() To switch to a new route, use the \[\`Navigator.push()\`]\[] 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 platform-specific route such as \[\`MaterialPageRoute\`]\[] or \[\`CupertinoPageRoute\`]\[]. A platform-specific route 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:

<!--?code-excerpt "lib/main_step2.dart (first-route-on-pressed)" replace="/^\},$/}/g"?-->

\`\`\`dart // Within the \`FirstRoute\` widget: onPressed: () { Navigator.push( context, MaterialPageRoute( builder: (context) => const SecondRoute(), ), ); } \`\`\`

<!--?code-excerpt "lib/main_step2_cupertino.dart (first-route-on-pressed)" replace="/^\},$/}/g"?-->

\`\`\`dart // Within the \`FirstRoute\` widget: onPressed: () { Navigator.push( context, CupertinoPageRoute( builder: (context) => const SecondRoute(), ), ); } \`\`\` ## 3. Return to the first route using Navigator.pop() How do you close the second route and return to the first? By using the \[\`Navigator.pop()\`]\[] method. The \`pop()\` method removes the current \`Route\` from the stack of routes managed by the \`Navigator\`. To implement a return to the original route, update the \`onPressed()\` callback in the \`SecondRoute\` widget:

<!--?code-excerpt "lib/main_step2.dart (second-route-on-pressed)" replace="/^\},$/}/g"?-->

\`\`\`dart // Within the SecondRoute widget onPressed: () { Navigator.pop(context); } \`\`\` ## Interactive example

<!--?code-excerpt "lib/main.dart"?-->

\`\`\`dartpad title="Flutter navigation hands-on example in DartPad" run="true" import 'package:flutter/material.dart'; void main() { runApp(const MaterialApp(title: 'Navigation Basics', home: FirstRoute())); } class FirstRoute extends StatelessWidget { const FirstRoute({super.key}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('First Route')), body: Center( child: ElevatedButton( child: const Text('Open route'), onPressed: () { Navigator.push( context, MaterialPageRoute( builder: (context) => const SecondRoute(), ), ); }, ), ), ); } } class SecondRoute extends StatelessWidget { const SecondRoute({super.key}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('Second Route')), body: Center( child: ElevatedButton( onPressed: () { Navigator.pop(context); }, child: const Text('Go back!'), ), ), ); } } \`\`\`

<!--?code-excerpt "lib/main_cupertino.dart"?-->

\`\`\`dartpad title="Flutter Cupertino theme hands-on example in DartPad" run="true" import 'package:flutter/cupertino.dart'; void main() { runApp(const CupertinoApp(title: 'Navigation Basics', home: FirstRoute())); } class FirstRoute extends StatelessWidget { const FirstRoute({super.key}); @override Widget build(BuildContext context) { return CupertinoPageScaffold( navigationBar: const CupertinoNavigationBar(middle: Text('First Route')), child: Center( child: CupertinoButton( child: const Text('Open route'), onPressed: () { Navigator.push( context, CupertinoPageRoute( builder: (context) => const SecondRoute(), ), ); }, ), ), ); } } class SecondRoute extends StatelessWidget { const SecondRoute({super.key}); @override Widget build(BuildContext context) { return CupertinoPageScaffold( navigationBar: const CupertinoNavigationBar(middle: Text('Second Route')), child: Center( child: CupertinoButton( onPressed: () { Navigator.pop(context); }, child: const Text('Go back!'), ), ), ); } } \`\`\` ## Additional navigation methods The recipe in this topic shows you one way to navigate to a new screen and back to the previous scene, using the \[\`push\`] and \[\`pop\`] methods in the \[\`Navigator\`] class, but there are several other \`Navigator\` static methods that you can use. Here are a few of them: \* \[\`pushAndRemoveUntil\`]: Adds a navigation route to the stack and then removes the most recent routes from the stack until a condition is met. \* \[\`pushReplacement\`]: Replaces the current route on the top of the stack with a new one. \* \[\`replace\`]: Replace a route on the stack with another route. \* \[\`replaceRouteBelow\`]: Replace the route below a specific route on the stack. \* \[\`popUntil\`]: Removes the most recent routes that were added to the stack of navigation routes until a condition is met. \* \[\`removeRoute\`]: Remove a specific route from the stack. \* \[\`removeRouteBelow\`]: Remove the route below a specific route on the stack. \* \[\`restorablePush\`]: Restore a route that was removed from the stack. \[Cupertino]: /ui/widgets/cupertino \[Material Components]: /ui/widgets/material \[\`CupertinoApp\`]: https\://pub.dev/documentation/cupertino\_ui/latest/cupertino\_ui/CupertinoApp-class.html \[\`CupertinoButton\`]: https\://pub.dev/documentation/cupertino\_ui/latest/cupertino\_ui/CupertinoButton-class.html \[\`CupertinoPageRoute\`]: https\://pub.dev/documentation/cupertino\_ui/latest/cupertino\_ui/CupertinoPageRoute-class.html \[\`CupertinoPageScaffold\`]: https\://pub.dev/documentation/cupertino\_ui/latest/cupertino\_ui/CupertinoPageScaffold-class.html \[\`ElevatedButton\`]: https\://pub.dev/documentation/material\_ui/latest/material\_ui/ElevatedButton-class.html \[\`MaterialApp\`]: https\://pub.dev/documentation/material\_ui/latest/material\_ui/MaterialApp-class.html \[\`MaterialPageRoute\`]: https\://pub.dev/documentation/material\_ui/latest/material\_ui/MaterialPageRoute-class.html \[\`Navigator.pop()\`]: https\://api.flutter.dev/flutter/widgets/Navigator/pop.html \[\`Navigator.push()\`]: https\://api.flutter.dev/flutter/widgets/Navigator/push.html \[\`Navigator\`]: https\://api.flutter.dev/flutter/widgets/Navigator-class.html \[\`pop\`]: https\://api.flutter.dev/flutter/widgets/Navigator/pop.html \[\`popUntil\`]: https\://api.flutter.dev/flutter/widgets/Navigator/popUntil.html \[\`push\`]: https\://api.flutter.dev/flutter/widgets/Navigator/push.html \[\`pushAndRemoveUntil\`]: https\://api.flutter.dev/flutter/widgets/Navigator/pushAndRemoveUntil.html \[\`pushReplacement\`]: https\://api.flutter.dev/flutter/widgets/Navigator/pushReplacement.html \[\`removeRoute\`]: https\://api.flutter.dev/flutter/widgets/Navigator/removeRoute.html \[\`removeRouteBelow\`]: https\://api.flutter.dev/flutter/widgets/Navigator/removeRouteBelow\.html \[\`replace\`]: https\://api.flutter.dev/flutter/widgets/Navigator/replace.html \[\`replaceRouteBelow\`]: https\://api.flutter.dev/flutter/widgets/Navigator/replaceRouteBelow\.html \[\`restorablePush\`]: https\://api.flutter.dev/flutter/widgets/Navigator/restorablePush.html \[\`Scaffold\`]: https\://pub.dev/documentation/material\_ui/latest/material\_ui/Scaffold-class.html

----
url: https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.46.0.md
----

\# DevTools 2.46.0 release notes > Release notes for Dart and Flutter DevTools version 2.46.0. The 2.46.0 release of the Dart and Flutter DevTools includes the following changes among other general improvements. To learn more about DevTools, check out the \[DevTools overview]\(/tools/devtools/overview). ## General updates - Fixed a bug which caused web apps to remain paused after triggering a hot-restart from DevTools. - \[#9125]\(https\://github.com/flutter/devtools/pull/9125) - Landed a change to dismiss stale banner messages when the connected app state changes. - \[#9148]\(https\://github.com/flutter/devtools/pull/9148) - Fixed a focus traversal issue with search fields. \[#9166]\(https\://github.com/flutter/devtools/pull/9166) ## Performance updates - Fixed a bug where the Performance page would hang when connected to a paused Flutter app. - \[#9162]\(https\://github.com/flutter/devtools/pull/9162) ## Full commit history To find a complete list of changes in this release, check out the \[DevTools git log]\(https\://github.com/flutter/devtools/tree/v2.46.0).

----
url: https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.9.2.md
----

\# DevTools 2.9.2 release notes > Release notes for Dart and Flutter DevTools version 2.9.2. The 2.9.2 release of the Dart and Flutter DevTools includes the following changes among other general improvements. To learn more about DevTools, check out the \[DevTools overview]\(https\://docs.flutter.dev/tools/devtools). ## General updates \* Take our 2022 DevTools survey! Provide your feedback and help us improve your development experience. This survey prompt will show up directly in DevTools sometime in mid-February. !\[survey prompt]\(/assets/images/docs/tools/devtools/release-notes/images-2.9.2/image1.png "survey\_prompt") \*Note\*: If you are having issues launching the survey, please make sure you have upgraded to the latest Flutter stable branch 2.10. There was a bug in DevTools (fixed in \[#3574]\(https\://github.com/flutter/devtools/pull/3574)) that prevented the survey from being able to be opened, and unless you are on Flutter 2.10, this bug will still be present.\_ \* General bug fixes and improvements - \[#3528]\(https\://github.com/flutter/devtools/pull/3528), \[#3531]\(https\://github.com/flutter/devtools/pull/3531), \[#3532]\(https\://github.com/flutter/devtools/pull/3532), \[#3539]\(https\://github.com/flutter/devtools/pull/3539) ## Performance updates \* Added frame numbers to x-axis the Flutter frames chart - \[#3526]\(https\://github.com/flutter/devtools/pull/3526) !\[frame numbers]\(/assets/images/docs/tools/devtools/release-notes/images-2.9.2/image2.png "frame\_numbers") ## Debugger updates \* Fix a bug where the File Explorer in the Debugger did not show contents after a hot restart - \[#3527]\(https\://github.com/flutter/devtools/pull/3527) ## Full commit history To find a complete list of changes since the previous release, check out \[the diff on GitHub]\(https\://github.com/flutter/devtools/compare/v2.9.1...v2.9.2).

----
url: https://docs.flutter.dev/platform-integration/bind-native-code
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Bind to native code using FFI](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

Flutter apps can use the [dart:ffi](https://api.dart.dev/dart-ffi/dart-ffi-library.html) library to call native APIs. *FFI* stands for [*foreign function interface*](https://en.wikipedia.org/wiki/Foreign_function_interface). Other terms for similar functionality include *native interface* and *language bindings.*

Since Flutter 3.38, the recommended way to bind to native code is to use the `flutter create --template=package_ffi` command. This template uses [build hooks](https://dart.dev/tools/hooks) to configure the native build in a `build.dart` script, and no longer requires OS-specific build files. This approach works for both Flutter and Dart standalone projects.

If you need to use the Flutter Plugin API, or if you need to configure a Google Play services runtime on Android, use the standard plugin template (`flutter create --template=plugin`).

## Create an FFI package

[#](#create-an-ffi-package)

To create an FFI package, run the following command:

```
$ flutter create --template=package_ffi native_add
$ cd native_add
```

This creates a package with the following specialized content:

* **`lib/native_add.dart`**: The Dart code that defines the API of the package.
* **`lib/native_add_bindings_generated.dart`**: The generated Dart bindings for the native code.
* **`src/native_add.c`**: The native C source code.
* **`src/native_add.h`**: The C header file for the native code.
* **`hook/build.dart`**: A script that is run by the Flutter SDK to compile the native code.
* **`ffigen.yaml`**: The configuration file for [`package:ffigen`](https://pub.dev/packages/ffigen) to generate the Dart bindings.
* **`pubspec.yaml`**: The package definition, which enables the `build.dart` hook.

## The native code

[#](#the-native-code)

The native code is located in `src/native_add.c` and `src/native_add.h`. The C function `sum` is defined in the `.c` file and its signature is in the header file. The function is marked to be exported so that it can be called from Dart.

## The build hook

[#](#the-build-hook)

The native code is compiled and bundled with your app automatically. This is done by the `hook/build.dart` script, which is a [build hook](https://dart.dev/tools/hooks).

This means you no longer need to write OS-specific build files (like `CMakeLists.txt` for Linux/Windows, `.podspec` for iOS/macOS, or `build.gradle` for Android) to compile your native code.

The build hook uses `package:native_toolchain_c` to compile the C code into a dynamic library. You can customize this file to build other native languages or to download pre-compiled binaries.

## The Dart code

[#](#the-dart-code)

The Dart code defines the public API of the package.

### Generating the bindings

[#](#generating-the-bindings)

To bind to the native code, the template uses [`package:ffigen`](https://pub.dev/packages/ffigen) to generate bindings from the header file (`src/native_add.h`). The generation is configured in `ffigen.yaml`.

This generates `lib/native_add_bindings_generated.dart`.

### Calling the native function

[#](#calling-the-native-function)

The generated bindings in `lib/native_add_bindings_generated.dart` contain `@Native() external` functions. These functions are automatically resolved at runtime against the code asset outputted by the build hook (which runs at build time). This means there is no OS-specific logic required for `dlopen`-ing the dynamic libraries, making the Dart code truly cross-platform.

The main library file `lib/native_add.dart` exposes these functions. Your app can then call these functions by importing `package:native_add/native_add.dart`.

## Testing

[#](#testing)

The generated package includes a unit test in `test/native_add_test.dart` that shows how to test the native function.

## Other use cases

[#](#other-use-cases)

### System libraries

[#](#system-libraries)

To link against a system library, you modify the `build.dart` hook to specify the linking mode. Instead of compiling source code, you create a `CodeAsset` and set its `linkMode`.

For many system libraries on Android, iOS, Linux, and macOS, you can use `LookupInProcess()` to find symbols in the main process.

For Windows, you often use `DynamicLoadingSystem()` and provide the name of the DLL.

Here is an example `build.dart` that links against system libraries to get the host name:

dart

```
// hook/build.dart
import 'package:hooks/hooks.dart';
import 'package:code_assets/code_assets.dart';
​
void main(List<String> args) async {
  await build(args, (input, output) async {
    final targetOS = input.target.os;
    switch (targetOS) {
      case OS.android || OS.iOS || OS.linux || OS.macOS:
        output.assets.code.add(
          CodeAsset(
            package: 'host_name',
            name: 'src/third_party/unix.dart',
            linkMode: LookupInProcess(),
          ),
        );
      case OS.windows:
        output.assets.code.add(
          CodeAsset(
            package: 'host_name',
            name: 'src/third_party/windows.dart',
            linkMode: DynamicLoadingSystem(Uri.file('ws2_32.dll')),
          ),
        );
      default:
        throw Exception('Unsupported target os: $targetOS');
    }
  });
}
```

The Dart files (`unix.dart`, `windows.dart`) would then contain the `external` functions that use the symbols from these system libraries.

#### Bundling `libc++_shared.so` on Android

[#](#bundling-libc-shared-so-on-android)

Although `libc++_shared.so` ships with the Android NDK, it isn't a system library. If your app or package uses the [C++ standard library](https://developer.android.com/ndk/guides/cpp-support#cs), or includes [multiple shared libraries](https://developer.android.com/ndk/guides/cpp-support#shared_runtimes) that depend on it, your app needs to bundle `libc++_shared.so`.

To bundle the library in your app, add a dependency on [`package:android_libcpp_shared`](https://pub.dev/packages/android_libcpp_shared), which uses its own build hook to bundle `libc++_shared.so` from the locally installed NDK for each target architecture.

### Closed-source libraries

[#](#closed-source-libraries)

You can also use build hooks to link against pre-compiled, closed-source libraries. The recommended approach is to download the pre-compiled binaries at build time and verify their integrity with a file hash.

In your `build.dart` hook, you would:

1. Download the library from a URL.
2. Verify the hash of the downloaded file.
3. Place the library in the build output directory.
4. Create a `CodeAsset` with `DynamicLoading` pointing to the library.

Here is a simplified example of the `CodeAsset` creation:

dart

```
// hook/build.dart
import 'package:hooks/hooks.dart';
import 'package:code_assets/code_assets.dart';
​
void main(List<String> args) async {
  await build(args, (input, output) async {
    // 1. Download the library from a URL.
    // 2. Verify the hash of the downloaded file.
    // 3. Place the library in the build output directory.
​
    output.assets.code.add(
      CodeAsset(
        package: input.packageName,
        name: 'src/my_lib.dart', // Dart file with bindings
        linkMode: DynamicLoadingBundled(),
        file: input.outputDirectory.resolve('my_lib.so'),
      ),
    );
  });
}
```

You would need to handle different architectures and platforms by having different versions of your pre-compiled library.

For more examples, see the [code\_assets package examples](https://pub.dev/packages/code_assets/example).

## Dynamic library naming guidelines

[#](#dynamic-library-naming-guidelines)

When implementing `build.dart` hooks for packages that bundle code assets, it is critical to ensure consistent naming of your dynamic libraries across all target architectures and SDKs.

On Apple platforms (iOS and macOS), dynamic libraries are bundled into frameworks. Flutter's build system relies on these names to generate metadata and package distributable formats like XCFrameworks.

### Consistency across architectures

[#](#consistency-across-architectures)

For a given asset ID, your hook will be invoked multiple times, once per architecture. Your hook must produce the same filename regardless of the target architecture (for example, `arm64` vs. `x64`).

* **Why?** Within a single SDK build, Flutter combines architecture-specific binaries into a single universal (fat) binary using `lipo`. If architectures have different filenames, the tool will pick one non-deterministically and issue a warning. Furthermore, error messages at runtime will be confusing for your users if dynamic libraries are renamed.
* **Recommended action**: Avoid adding architecture suffixes to your filenames (for example, use `libsqlite3.dylib` instead of `libsqlite3_arm64.dylib`). Instead, write the file to `input.outputDirectory` (which is unique per architecture) or to an architecture-specific subdirectory of `input.outputDirectoryShared` (for example, `input.outputDirectoryShared.resolve('$architecture/')`).

### Consistency across SDKs (iOS)

[#](#consistency-across-sdks-ios)

When building for iOS, your hook will be invoked multiple times with different values for the SDK and architecture. Both physical device (`iphoneos`) and simulator (`iphonesimulator`) invocations must produce the same framework name for the same asset ID.

* **Why?** Flutter uses `xcodebuild -create-xcframework` to combine these outputs. Xcode requires that all platform slices within an XCFramework share the same framework name to allow seamless linking. If filenames differ, the Flutter tool cannot create a correct XCFramework, and commands like `flutter build ios-framework` will fail.
* **Recommended action**: Do not use suffixes like `_sim` or `_simulator` for the simulator build. The XCFramework structure already handles the platform separation internally (for example, `MyLib.xcframework/ios-arm64_x86_64-simulator/MyLib.framework`). Instead, write the file to `input.outputDirectory` (which is unique per SDK) or to an SDK-specific subdirectory of `input.outputDirectoryShared`.

### Consistency in the set of assets

[#](#consistency-in-the-set-of-assets)

Your hook must produce the same set of Asset IDs across all SDKs for a given target platform.

* **Why?** Apple's build system and App Store validation require that all frameworks included in an application are compatible with the target device. If you produce an asset for the simulator (`iphonesimulator`) but not for the physical device (`iphoneos`), the resulting XCFramework will contain a slice that has no counterpart for the device. This can lead to build failures or Apple rejecting the application for including simulator-only binaries in a device build.
* **Recommended action**: Ensure that your `build.dart` hook logic handles all supported SDKs consistently. If you produce an asset for one SDK, you must produce a corresponding asset for all other SDKs for that platform. For SDK-specific code, you can use stub implementations for other SDKs.

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-06-08. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/platform-integration/bind-native-code.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/platform-integration/bind-native-code\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/platform-integration/bind-native-code.md "Report an issue with this page").

----
url: https://docs.flutter.dev/app-architecture/case-study/ui-layer
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top UI layer case study](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

The [UI layer](/app-architecture/guide#ui-layer) of each feature in your Flutter application should be made up of two components: a **[`View`](/app-architecture/guide#views)** and a **[`ViewModel`](/app-architecture/guide#view-models).**

In the most general sense, view models manage UI state, and views display UI state. Views and view models have a one-to-one relationship; for each view, there's exactly one corresponding view model that manages that view's state. Each pair of view and view model make up the UI for a single feature. For example, an app might have classes called `LogOutView` and a `LogOutViewModel`.

## Define a view model

[#](#define-a-view-model)

A view model is a Dart class responsible for handling UI logic. View models take domain data models as input and expose that data as UI state to their corresponding views. They encapsulate logic that the view can attach to event handlers, like button presses, and manage sending these events to the data layer of the app, where data changes happen.

The following code snippet is a class declaration for a view model class called the `HomeViewModel`. Its inputs are the [repositories](/app-architecture/guide#repositories) that provide its data. In this case, the view model is dependent on the `BookingRepository` and `UserRepository` as arguments.

home\_viewmodel.dart

dart

```
class HomeViewModel {
  HomeViewModel({
    required BookingRepository bookingRepository,
    required UserRepository userRepository,
  }) :
    // Repositories are manually assigned because they're private members.
    _bookingRepository = bookingRepository,
    _userRepository = userRepository;
​
  final BookingRepository _bookingRepository;
  final UserRepository _userRepository;
  // ...
}
```

View models are always dependent on data repositories, which are provided as arguments to the view model's constructor. View models and repositories have a many-to-many relationship, and most view models will depend on multiple repositories.

As in the earlier `HomeViewModel` example declaration, repositories should be private members on the view model, otherwise views would have direct access to the data layer of the application.

### UI state

[#](#ui-state)

The output of a view model is data that a view needs to render, generally referred to as **UI State**, or just state. UI state is an immutable snapshot of data that is required to fully render a view.

The view model exposes state as public members. On the view model in the following code example, the exposed data is a `User` object, as well as the user's saved itineraries which are exposed as an object of type `List<BookingSummary>`.

home\_viewmodel.dart

dart

```
class HomeViewModel {
  HomeViewModel({
   required BookingRepository bookingRepository,
   required UserRepository userRepository,
  }) : _bookingRepository = bookingRepository,
      _userRepository = userRepository;
​
  final BookingRepository _bookingRepository;
  final UserRepository _userRepository;
​
  User? _user;
  User? get user => _user;
​
  List<BookingSummary> _bookings = [];
​
  /// Items in an [UnmodifiableListView] can't be directly modified,
  /// but changes in the source list can be modified. Since _bookings
  /// is private and bookings is not, the view has no way to modify the
  /// list directly.
  UnmodifiableListView<BookingSummary> get bookings => UnmodifiableListView(_bookings);
​
  // ...
}
```

As mentioned, the UI state should be immutable. This is a crucial part of bug-free software.

The compass app uses the [`package:freezed`](https://pub.dev/packages/freezed) to enforce immutability on data classes. For example, the following code shows the `User` class definition. `freezed` provides deep immutability, and generates the implementation for useful methods like `copyWith` and `toJson`.

user.dart

dart

```
@freezed
class User with _$User {
  const factory User({
    /// The user's name.
    required String name,
​
    /// The user's picture URL.
    required String picture,
  }) = _User;
​
  factory User.fromJson(Map<String, Object?> json) => _$UserFromJson(json);
}
```

### Updating UI state

[#](#updating-ui-state)

In addition to storing state, view models need to tell Flutter to re-render views when the data layer provides a new state. In the Compass app, view models extend [`ChangeNotifier`](https://api.flutter.dev/flutter/foundation/ChangeNotifier-class.html) to achieve this.

home\_viewmodel.dart

dart

```
class HomeViewModel extends ChangeNotifier {
  HomeViewModel({
   required BookingRepository bookingRepository,
   required UserRepository userRepository,
  }) : _bookingRepository = bookingRepository,
      _userRepository = userRepository;
  final BookingRepository _bookingRepository;
  final UserRepository _userRepository;
​
  User? _user;
  User? get user => _user;
​
  List<BookingSummary> _bookings = [];
  List<BookingSummary> get bookings => _bookings;
​
  // ...
}
```

`HomeViewModel.user` is a public member that the view depends on. When new data flows from the data layer and new state needs to be emitted, [`notifyListeners`](https://api.flutter.dev/flutter/foundation/ChangeNotifier/notifyListeners.html) is called.

This figure shows from a high-level how new data in the repository propagates up to the UI layer and triggers a re-build of your Flutter widgets.

1. New state is provided to the view model from a Repository.
2. The view model updates its UI state to reflect the new data.
3. `ViewModel.notifyListeners` is called, alerting the View of new UI State.
4. The view (widget) re-renders.

For example, when the user navigates to the Home screen and the view model is created, the `_load` method is called. Until this method completes, the UI state is empty, the view displays a loading indicator. When the `_load` method completes, if it's successful, there's new data in the view model, and it must notify the view that new data is available.

home\_viewmodel.dart

dart

```
class HomeViewModel extends ChangeNotifier {
  // ...
​
 Future<Result> _load() async {
    try {
      final userResult = await _userRepository.getUser();
      switch (userResult) {
        case Ok<User>():
          _user = userResult.value;
          _log.fine('Loaded user');
        case Error<User>():
          _log.warning('Failed to load user', userResult.error);
      }
​
      // ...
​
      return userResult;
    } finally {
      notifyListeners();
    }
  }
}
```

## Define a view

[#](#define-a-view)

A view is a widget within your app. Often, a view represents one screen in your app that has its own route and includes a [`Scaffold`](https://pub.dev/documentation/material_ui/latest/material_ui/Scaffold-class.html) at the top of the widget subtree, such as the `HomeScreen`, but this isn't always the case.

Sometimes a view is a single UI element that encapsulates functionality that needs to be re-used throughout the app. For example, the Compass app has a view called `LogoutButton`, which can be dropped anywhere in the widget tree that a user might expect to find a logout button. The `LogoutButton` view has its own view model called `LogoutViewModel`. And on larger screens, there might be multiple views on screen that would take up the full screen on mobile.

The widgets within a view have three responsibilities:

* They display the data properties from the view model.
* They listen for updates from the view model and re-render when new data is available.
* They attach callbacks from the view model to event handlers, if applicable.

Continuing the Home feature example, the following code shows the definition of the `HomeScreen` view.

home\_screen.dart

dart

```
class HomeScreen extends StatelessWidget {
  const HomeScreen({super.key, required this.viewModel});
​
  final HomeViewModel viewModel;
​
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      // ...
    );
  }
}
```

Most of the time, a view's only inputs should be a `key`, which all Flutter widgets take as an optional argument, and the view's corresponding view model.

### Display UI data in a view

[#](#display-ui-data-in-a-view)

A view depends on a view model for its state. In the Compass app, the view model is passed in as an argument in the view's constructor. The following example code snippet is from the `HomeScreen` widget.

home\_screen.dart

dart

```
class HomeScreen extends StatelessWidget {
  const HomeScreen({super.key, required this.viewModel});
​
  final HomeViewModel viewModel;
​
  @override
  Widget build(BuildContext context) {
    // ...
  }
}
```

Within the widget, you can access the passed-in bookings from the `viewModel`. In the following code, the `booking` property is being provided to a sub-widget.

home\_screen.dart

dart

```
@override
  Widget build(BuildContext context) {
    return Scaffold(
      // Some code was removed for brevity.
      body: SafeArea(
        child: ListenableBuilder(
          listenable: viewModel,
          builder: (context, _) {
            return CustomScrollView(
              slivers: [
                SliverToBoxAdapter(...),
                SliverList.builder(
                   itemCount: viewModel.bookings.length,
                    itemBuilder: (_, index) => _Booking(
                      key: ValueKey(viewModel.bookings[index].id),
                      booking:viewModel.bookings[index],
                      onTap: () => context.push(Routes.bookingWithId(
                         viewModel.bookings[index].id)),
                      onDismissed: (_) => viewModel.deleteBooking.execute(
                           viewModel.bookings[index].id,
                         ),
                    ),
                ),
              ],
            );
          },
        ),
      ),
```

### Update the UI

[#](#update-the-ui)

The `HomeScreen` widget listens for updates from the view model with the [`ListenableBuilder`](https://api.flutter.dev/flutter/widgets/ListenableBuilder-class.html) widget. Everything in the widget subtree under the `ListenableBuilder` widget re-renders when the provided [`Listenable`](https://api.flutter.dev/flutter/foundation/Listenable-class.html) changes. In this case, the provided `Listenable` is the view model. Recall that the view model is of type [`ChangeNotifier`](https://api.flutter.dev/flutter/foundation/ChangeNotifier-class.html) which is a subtype of the `Listenable` type.

home\_screen.dart

dart

```
@override
Widget build(BuildContext context) {
  return Scaffold(
    // Some code was removed for brevity.
      body: SafeArea(
        child: ListenableBuilder(
          listenable: viewModel,
          builder: (context, _) {
            return CustomScrollView(
              slivers: [
                SliverToBoxAdapter(),
                SliverList.builder(
                  itemCount: viewModel.bookings.length,
                  itemBuilder: (_, index) =>
                      _Booking(
                        key: ValueKey(viewModel.bookings[index].id),
                        booking: viewModel.bookings[index],
                        onTap: () =>
                            context.push(Routes.bookingWithId(
                                viewModel.bookings[index].id)
                            ),
                        onDismissed: (_) =>
                            viewModel.deleteBooking.execute(
                              viewModel.bookings[index].id,
                            ),
                      ),
                ),
              ],
            );
          }
        )
      )
  );
}
```

### Handling user events

[#](#handling-user-events)

Finally, a view needs to listen for *events* from users, so the view model can handle those events. This is achieved by exposing a callback method on the view model class which encapsulates all the logic.

On the `HomeScreen`, users can delete previously booked events by swiping a [`Dismissible`](https://api.flutter.dev/flutter/widgets/Dismissible-class.html) widget.

Recall this code from the previous snippet:

home\_screen.dart

dart

```
SliverList.builder(
  itemCount: widget.viewModel.bookings.length,
  itemBuilder: (_, index) => _Booking(
    key: ValueKey(viewModel.bookings[index].id),
    booking: viewModel.bookings[index],
    onTap: () => context.push(
      Routes.bookingWithId(viewModel.bookings[index].id)
    ),
    onDismissed: (_) =>
      viewModel.deleteBooking.execute(widget.viewModel.bookings[index].id),
  ),
),
```

On the `HomeScreen`, a user's saved trip is represented by the `_Booking` widget. When a `_Booking` is dismissed, the `viewModel.deleteBooking` method is executed.

A saved booking is application state that persists beyond a session or the lifetime of a view, and only repositories should modify such application state. So, the `HomeViewModel.deleteBooking` method turns around and calls a method exposed by a repository in the data layer, as shown in the following code snippet.

home\_viewmodel.dart

dart

```
Future<Result<void>> _deleteBooking(int id) async {
  try {
    final resultDelete = await _bookingRepository.delete(id);
    switch (resultDelete) {
      case Ok<void>():
        _log.fine('Deleted booking $id');
      case Error<void>():
        _log.warning('Failed to delete booking $id', resultDelete.error);
        return resultDelete;
    }
​
    // Some code was omitted for brevity.
    // final  resultLoadBookings = ...;
​
    return resultLoadBookings;
  } finally {
    notifyListeners();
  }
}
```

In the Compass app, these methods that handle user events are called **commands**.

### Command objects

[#](#command-objects)

Commands are responsible for the interaction that starts in the UI layer and flows back to the data layer. In this app specifically, a `Command` is also a type that helps update the UI safely, regardless of the response time or contents.

The `Command` class wraps a method and helps handle the different states of that method, such as `running`, `complete`, and `error`. These states make it easy to display different UI, like loading indicators when `Command.running` is true.

The following is code from the `Command` class. Some code has been omitted for demo purposes.

command.dart

dart

```
abstract class Command<T> extends ChangeNotifier {
  Command();
  bool running = false;
  Result<T>? _result;
​
  /// true if action completed with error
  bool get error => _result is Error;
​
  /// true if action completed successfully
  bool get completed => _result is Ok;
​
  /// Internal execute implementation
  Future<void> _execute(action) async {
    if (_running) return;
​
    // Emit running state - e.g. button shows loading state
    _running = true;
    _result = null;
    notifyListeners();
​
    try {
      _result = await action();
    } finally {
      _running = false;
      notifyListeners();
    }
  }
}
```

The `Command` class itself extends `ChangeNotifier`, and within the method `Command.execute`, `notifyListeners` is called multiple times. This allows the view to handle different states with very little logic, which you'll see an example of later on this page.

You may have also noticed that `Command` is an abstract class. It's implemented by concrete classes such as `Command0` `Command1`. The integer in the class name refers to the number of arguments that the underlying method expects. You can see examples of these implementation classes in the Compass app's [`utils` directory](https://github.com/flutter/samples/blob/main/compass_app/app/lib/utils/command.dart).

### Ensuring views can render before data exists

[#](#ensuring-views-can-render-before-data-exists)

In view model classes, commands are created in the constructor.

home\_viewmodel.dart

dart

```
class HomeViewModel extends ChangeNotifier {
  HomeViewModel({
   required BookingRepository bookingRepository,
   required UserRepository userRepository,
  }) : _bookingRepository = bookingRepository,
      _userRepository = userRepository {
    // Load required data when this screen is built.
    load = Command0(_load)..execute();
    deleteBooking = Command1(_deleteBooking);
  }
​
  final BookingRepository _bookingRepository;
  final UserRepository _userRepository;
​
  late Command0 load;
  late Command1<void, int> deleteBooking;
​
  User? _user;
  User? get user => _user;
​
  List<BookingSummary> _bookings = [];
  List<BookingSummary> get bookings => _bookings;
​
  Future<Result> _load() async {
    // ...
  }
​
  Future<Result<void>> _deleteBooking(int id) async {
    // ...
  }
​
  // ...
}
```

The `Command.execute` method is asynchronous, so it can't guarantee that the data will be available when the view wants to render. This gets at *why* the Compass app uses `Commands`. In the view's `Widget.build` method, the command is used to conditionally render different widgets.

home\_screen.dart

dart

```
// ...
child: ListenableBuilder(
  listenable: viewModel.load,
  builder: (context, child) {
    if (viewModel.load.running) {
      return const Center(child: CircularProgressIndicator());
    }
​
    if (viewModel.load.error) {
      return ErrorIndicator(
        title: AppLocalization.of(context).errorWhileLoadingHome,
        label: AppLocalization.of(context).tryAgain,
          onPressed: viewModel.load.execute,
        );
     }
​
    // The command has completed without error.
    // Return the main view widget.
    return child!;
  },
),
​
// ...
```

Because the `load` command is a property that exists on the view model rather than something ephemeral, it doesn't matter when the `load` method is called or when it resolves. For example, if the load command resolves before the `HomeScreen` widget was even created, it isn't a problem because the `Command` object still exists, and exposes the correct state.

This pattern standardizes how common UI problems are solved in the app, making your codebase less error-prone and more scalable, but it's not a pattern that every app will want to implement. Whether you want to use it is highly dependent on other architectural choices you make. Many libraries that help you manage state have their own tools to solve these problems. For example, if you were to use [streams](https://api.flutter.dev/flutter/dart-async/Stream-class.html) and [`StreamBuilders`](https://api.flutter.dev/flutter/widgets/StreamBuilder-class.html) in your app, the [`AsyncSnapshot`](https://api.flutter.dev/flutter/widgets/AsyncSnapshot-class.html) classes provided by Flutter have this functionality built in.

## Feedback

[#](#feedback)

As this section of the website is evolving, we [welcome your feedback](https://google.qualtrics.com/jfe/form/SV_4T0XuR9Ts29acw6?page=%22case-study/ui-layer%22)!

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-09-29. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/app-architecture/case-study/ui-layer.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/app-architecture/case-study/ui-layer\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/app-architecture/case-study/ui-layer.md "Report an issue with this page").

----
url: https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.32.0
----

The 2.32.0 release of the Dart and Flutter DevTools includes the following changes among other general improvements. To learn more about DevTools, check out the [DevTools overview](https://docs.flutter.dev/tools/devtools).

## General updates

[#](#general-updates)

* Improved overall usability by making the DevTools UI more dense. This significantly improves the user experience when using DevTools embedded in an IDE. - [#7030](https://github.com/flutter/devtools/pull/7030)
* Removed the "Dense mode" setting. - [#7086](https://github.com/flutter/devtools/pull/7086)
* Added support for filtering with regular expressions in the Logging, Network, and CPU profiler pages - [#7027](https://github.com/flutter/devtools/pull/7027)
* Add a DevTools server interaction for getting the DTD uri. - [#7054](https://github.com/flutter/devtools/pull/7054)

## Memory updates

[#](#memory-updates)

* Supported allocation tracing for Flutter profile builds and Dart AOT compiled applications. - [#7058](https://github.com/flutter/devtools/pull/7058)
* Supported import of memory snapshots. - [#6974](https://github.com/flutter/devtools/pull/6974)

## Debugger updates

[#](#debugger-updates)

* Highlighted `extension type` as a declaration keyword, highlight the `$` in identifier interpolation as part of the interpolation, and properly highlight comments within type arguments. - [#6837](https://github.com/flutter/devtools/pull/6837)

## Logging updates

[#](#logging-updates)

* Added toggle filters to filter out noisy Flutter and Dart logs - [#7026](https://github.com/flutter/devtools/pull/7026)

* Added a scrollbar to the details pane. - [#6917](https://github.com/flutter/devtools/pull/6917)

## DevTools extension updates

[#](#devtools-extension-updates)

* Added a description and documentation link to the `devtools_options.yaml` file that is created in a user's project. - [#7052](https://github.com/flutter/devtools/pull/7052)
* Updated the Simulated DevTools Environment Panel to be collapsible (thanks to @victoreronmosele!) - [#7062](https://github.com/flutter/devtools/pull/7062)
* Integrated DevTools extensions with the new Dart Tooling Daemon. This will allow DevTools extensions to access public methods registered by other DTD clients, such as an IDE, as well as access a minimal file system API for interacting with the development project. - [#7108](https://github.com/flutter/devtools/pull/7108)

## VS Code sidebar updates

[#](#vs-code-sidebar-updates)

* Fixed an issue that prevented the VS code sidebar from loading in recent `beta` and `main` builds. - [#6984](https://github.com/flutter/devtools/pull/6984)
* Showed DevTools extensions as an option from the debug sessions DevTools dropdown, when available. [#6709](https://github.com/flutter/devtools/pull/6709)

## Full commit history

[#](#full-commit-history)

To find a complete list of changes in this release, check out the [DevTools git log](https://github.com/flutter/devtools/tree/v2.32.0).

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/release-notes/release-notes-2.32.0.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.32.0\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/release-notes/release-notes-2.32.0.md "Report an issue with this page").

----
url: https://docs.flutter.dev/cookbook/effects/shimmer-loading
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Create a shimmer loading effect](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

Loading times are unavoidable in application development. From a user experience (UX) perspective, the most important thing is to show your users that loading is taking place. One popular approach to communicate to users that data is loading is to display a chrome color with a shimmer animation over the shapes that approximate the type of content that is loading.

The following animation shows the app's behavior:

This recipe begins with the content widgets defined and positioned. There is also a Floating Action Button (FAB) in the bottom-right corner that toggles between a loading mode and a loaded mode so that you can easily validate your implementation.

## Draw the shimmer shapes

[#](#draw-the-shimmer-shapes)

The shapes that shimmer in this effect are independent from the actual content that eventually loads.

Therefore, the goal is to display shapes that represent the eventual content as accurately as possible.

Displaying accurate shapes is easy in situations where the content has a clear boundary. For example, in this recipe, there are some circular images and some rounded rectangle images. You can draw shapes that precisely match the outlines of those images.

On the other hand, consider the text that appears beneath the rounded rectangle images. You won't know how many lines of text exist until the text loads. Therefore, there is no point in trying to draw a rectangle for every line of text. Instead, while the data is loading, you draw a couple of very thin rounded rectangles that represent the text that will appear. The shape and size doesn't quite match, but that is OK.

Start with the circular list items at the top of the screen. Ensure that each `CircleListItem` widget displays a circle with a color while the image is loading.

dart

```
class CircleListItem extends StatelessWidget {
  const CircleListItem({super.key});
​
  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 8),
      child: Container(
        width: 54,
        height: 54,
        decoration: const BoxDecoration(
          color: Colors.black,
          shape: BoxShape.circle,
        ),
        child: ClipOval(
          child: Image.network(
            'https://docs.flutter.dev/assets/images/'
            'exercise/effects/split-check/Avatar1.jpg',
            fit: BoxFit.cover,
          ),
        ),
      ),
    );
  }
}
```

As long as your widgets display some kind of shape, you can apply the shimmer effect in this recipe.

Similar to the `CircleListItem` widgets, ensure that the `CardListItem` widgets display a color where the image will appear. Also, in the `CardListItem` widget, switch between the display of the text and the rectangles based on the current loading status.

dart

```
class CardListItem extends StatelessWidget {
  const CardListItem({super.key, required this.isLoading});
​
  final bool isLoading;
​
  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [_buildImage(), const SizedBox(height: 16), _buildText()],
      ),
    );
  }
​
  Widget _buildImage() {
    return AspectRatio(
      aspectRatio: 16 / 9,
      child: Container(
        width: double.infinity,
        decoration: BoxDecoration(
          color: Colors.black,
          borderRadius: BorderRadius.circular(16),
        ),
        child: ClipRRect(
          borderRadius: BorderRadius.circular(16),
          child: Image.network(
            'https://docs.flutter.dev/assets/images/'
            'exercise/effects/split-check/Food1.jpg',
            fit: BoxFit.cover,
          ),
        ),
      ),
    );
  }
​
  Widget _buildText() {
    if (isLoading) {
      return Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Container(
            width: double.infinity,
            height: 24,
            decoration: BoxDecoration(
              color: Colors.black,
              borderRadius: BorderRadius.circular(16),
            ),
          ),
          const SizedBox(height: 16),
          Container(
            width: 250,
            height: 24,
            decoration: BoxDecoration(
              color: Colors.black,
              borderRadius: BorderRadius.circular(16),
            ),
          ),
        ],
      );
    } else {
      return const Padding(
        padding: EdgeInsets.symmetric(horizontal: 8),
        child: Text(
          'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do '
          'eiusmod tempor incididunt ut labore et dolore magna aliqua.',
        ),
      );
    }
  }
}
```

Your UI now renders itself differently depending on whether it's loading or loaded. By temporarily commenting out the image URLs, you can see the two ways your UI renders.

The next goal is to paint all of the colored areas with a single gradient that looks like a shimmer.

## Paint the shimmer gradient

[#](#paint-the-shimmer-gradient)

The key to the effect achieved in this recipe is to use a widget called [`ShaderMask`](https://api.flutter.dev/flutter/widgets/ShaderMask-class.html). The `ShaderMask` widget, as the name suggests, applies a shader to its child, but only in the areas where the child already painted something. For example, you'll apply a shader to only the black shapes that you configured earlier.

Define a chrome-colored, linear gradient that gets applied to the shimmer shapes.

dart

```
const _shimmerGradient = LinearGradient(
  colors: [Color(0xFFEBEBF4), Color(0xFFF4F4F4), Color(0xFFEBEBF4)],
  stops: [0.1, 0.3, 0.4],
  begin: Alignment(-1.0, -0.3),
  end: Alignment(1.0, 0.3),
  tileMode: TileMode.clamp,
);
```

Define a new stateful widget called `ShimmerLoading` that wraps a given `child` widget with a `ShaderMask`. Configure the `ShaderMask` widget to apply the shimmer gradient as a shader with a `blendMode` of `srcATop`. The `srcATop` blend mode replaces any color that your `child` widget painted with the shader color.

dart

```
class ShimmerLoading extends StatefulWidget {
  const ShimmerLoading({
    super.key,
    required this.isLoading,
    required this.child,
  });
​
  final bool isLoading;
  final Widget child;
​
  @override
  State<ShimmerLoading> createState() => _ShimmerLoadingState();
}
​
class _ShimmerLoadingState extends State<ShimmerLoading> {
  @override
  Widget build(BuildContext context) {
    if (!widget.isLoading) {
      return widget.child;
    }
​
    return ShaderMask(
      blendMode: BlendMode.srcATop,
      shaderCallback: (bounds) {
        return _shimmerGradient.createShader(bounds);
      },
      child: widget.child,
    );
  }
}
```

Wrap your `CircleListItem` widgets with a `ShimmerLoading` widget.

dart

```
Widget _buildTopRowItem() {
  return ShimmerLoading(isLoading: _isLoading, child: const CircleListItem());
}
```

Wrap your `CardListItem` widgets with a `ShimmerLoading` widget.

dart

```
Widget _buildListItem() {
  return ShimmerLoading(
    isLoading: _isLoading,
    child: CardListItem(isLoading: _isLoading),
  );
}
```

When your shapes are loading, they now display the shimmer gradient that is returned from the `shaderCallback`.

This is a big step in the right direction, but there's a problem with this gradient display. Each `CircleListItem` widget and each `CardListItem` widget displays a new version of the gradient. For this recipe, the entire screen should look like one, big shimmering surface. You solve this problem in the next step.

## Paint one big shimmer

[#](#paint-one-big-shimmer)

To paint one big shimmer across the screen, each `ShimmerLoading` widget needs to paint the same full-screen gradient based on the position of that `ShimmerLoading` widget on the screen.

To be more precise, rather than assume that the shimmer should take up the entire screen, there should be some area that shares the shimmer. Maybe that area takes up the entire screen, or maybe it doesn't. The way to solve this kind of problem in Flutter is to define another widget that sits above all of the `ShimmerLoading` widgets in the widget tree, and call it `Shimmer`. Then, each `ShimmerLoading` widget gets a reference to the `Shimmer` ancestor and requests the desired size and gradient to display.

Define a new stateful widget called `Shimmer` that takes in a [`LinearGradient`](https://api.flutter.dev/flutter/painting/LinearGradient-class.html) and provides descendants with access to its `State` object.

dart

```
class Shimmer extends StatefulWidget {
  static ShimmerState? of(BuildContext context) {
    return context.findAncestorStateOfType<ShimmerState>();
  }
​
  const Shimmer({super.key, required this.linearGradient, this.child});
​
  final LinearGradient linearGradient;
  final Widget? child;
​
  @override
  ShimmerState createState() => ShimmerState();
}
​
class ShimmerState extends State<Shimmer> {
  @override
  Widget build(BuildContext context) {
    return widget.child ?? const SizedBox();
  }
}
```

Add methods to the `ShimmerState` class in order to provide access to the `linearGradient`, the size of the `ShimmerState`'s `RenderBox`, and look up the position of a descendant within the `ShimmerState`'s `RenderBox`.

dart

```
class ShimmerState extends State<Shimmer> {
  Gradient get gradient => LinearGradient(
    colors: widget.linearGradient.colors,
    stops: widget.linearGradient.stops,
    begin: widget.linearGradient.begin,
    end: widget.linearGradient.end,
  );
​
  bool get isSized =>
      (context.findRenderObject() as RenderBox?)?.hasSize ?? false;
​
  Size get size => (context.findRenderObject() as RenderBox).size;
​
  Offset getDescendantOffset({
    required RenderBox descendant,
    Offset offset = Offset.zero,
  }) {
    final shimmerBox = context.findRenderObject() as RenderBox;
    return descendant.localToGlobal(offset, ancestor: shimmerBox);
  }
​
  @override
  Widget build(BuildContext context) {
    return widget.child ?? const SizedBox();
  }
}
```

Wrap all of your screen's content with the `Shimmer` widget.

dart

```
class _ExampleUiLoadingAnimationState extends State<ExampleUiLoadingAnimation> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Shimmer(
        linearGradient: _shimmerGradient,
        child: ListView(
          // ListView Contents
        ),
      ),
    );
  }
}
```

Use the `Shimmer` widget within your `ShimmerLoading` widget to paint the shared gradient.

dart

```
class _ShimmerLoadingState extends State<ShimmerLoading> {
  @override
  Widget build(BuildContext context) {
    if (!widget.isLoading) {
      return widget.child;
    }
​
    // Collect ancestor shimmer information.
    final shimmer = Shimmer.of(context)!;
    if (!shimmer.isSized) {
      // The ancestor Shimmer widget isn't laid
      // out yet. Return an empty box.
      return const SizedBox();
    }
    final shimmerSize = shimmer.size;
    final gradient = shimmer.gradient;
    final offsetWithinShimmer = shimmer.getDescendantOffset(
      descendant: context.findRenderObject() as RenderBox,
    );
​
    return ShaderMask(
      blendMode: BlendMode.srcATop,
      shaderCallback: (bounds) {
        return gradient.createShader(
          Rect.fromLTWH(
            -offsetWithinShimmer.dx,
            -offsetWithinShimmer.dy,
            shimmerSize.width,
            shimmerSize.height,
          ),
        );
      },
      child: widget.child,
    );
  }
}
```

Your `ShimmerLoading` widgets now display a shared gradient that takes up all of the space within the `Shimmer` widget.

## Animate the shimmer

[#](#animate-the-shimmer)

The shimmer gradient needs to move in order to give the appearance of a shimmering shine.

The `LinearGradient` has a property called `transform` that can be used to transform the appearance of the gradient, for example, to move it horizontally. The `transform` property accepts a `GradientTransform` instance.

Define a class called `_SlidingGradientTransform` that implements `GradientTransform` to achieve the appearance of horizontal sliding.

dart

```
class _SlidingGradientTransform extends GradientTransform {
  const _SlidingGradientTransform({required this.slidePercent});
​
  final double slidePercent;
​
  @override
  Matrix4? transform(Rect bounds, {TextDirection? textDirection}) {
    return Matrix4.translationValues(bounds.width * slidePercent, 0.0, 0.0);
  }
}
```

The gradient slide percentage changes over time in order to create the appearance of motion. To change the percentage, configure an [`AnimationController`](https://api.flutter.dev/flutter/animation/AnimationController-class.html) in the `ShimmerState` class.

dart

```
class ShimmerState extends State<Shimmer> with SingleTickerProviderStateMixin {
  late AnimationController _shimmerController;
​
  @override
  void initState() {
    super.initState();
​
    _shimmerController = AnimationController.unbounded(vsync: this)
      ..repeat(min: -0.5, max: 1.5, period: const Duration(milliseconds: 1000));
  }
​
  @override
  void dispose() {
    _shimmerController.dispose();
    super.dispose();
  }
  }
```

Apply the `_SlidingGradientTransform` to the `gradient` by using the `_shimmerController`'s `value` as the `slidePercent`.

dart

```
LinearGradient get gradient => LinearGradient(
  colors: widget.linearGradient.colors,
  stops: widget.linearGradient.stops,
  begin: widget.linearGradient.begin,
  end: widget.linearGradient.end,
  transform: _SlidingGradientTransform(
    slidePercent: _shimmerController.value,
  ),
);
```

The gradient now animates, but your individual `ShimmerLoading` widgets don't repaint themselves as the gradient changes. Therefore, it looks like nothing is happening.

Expose the `_shimmerController` from `ShimmerState` as a [`Listenable`](https://api.flutter.dev/flutter/foundation/Listenable-class.html).

dart

```
Listenable get shimmerChanges => _shimmerController;
```

In `ShimmerLoading`, listen for changes to the ancestor `ShimmerState`'s `shimmerChanges` property, and repaint the shimmer gradient.

dart

```
class _ShimmerLoadingState extends State<ShimmerLoading> {
  Listenable? _shimmerChanges;
​
  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    if (_shimmerChanges != null) {
      _shimmerChanges!.removeListener(_onShimmerChange);
    }
    _shimmerChanges = Shimmer.of(context)?.shimmerChanges;
    if (_shimmerChanges != null) {
      _shimmerChanges!.addListener(_onShimmerChange);
    }
  }
​
  @override
  void dispose() {
    _shimmerChanges?.removeListener(_onShimmerChange);
    super.dispose();
  }
​
  void _onShimmerChange() {
    if (widget.isLoading) {
      setState(() {
        // Update the shimmer painting.
      });
    }
  }
  }
```

Congratulations! You now have a full-screen, animated shimmer effect that turns on and off as the content loads.

## Interactive example

[#](#interactive-example)

```
import 'package:flutter/material.dart';

void main() {
  runApp(
    const MaterialApp(
      home: ExampleUiLoadingAnimation(),
      debugShowCheckedModeBanner: false,
    ),
  );
}

const _shimmerGradient = LinearGradient(
  colors: [Color(0xFFEBEBF4), Color(0xFFF4F4F4), Color(0xFFEBEBF4)],
  stops: [0.1, 0.3, 0.4],
  begin: Alignment(-1.0, -0.3),
  end: Alignment(1.0, 0.3),
  tileMode: TileMode.clamp,
);

class ExampleUiLoadingAnimation extends StatefulWidget {
  const ExampleUiLoadingAnimation({super.key});

  @override
  State<ExampleUiLoadingAnimation> createState() =>
      _ExampleUiLoadingAnimationState();
}

class _ExampleUiLoadingAnimationState extends State<ExampleUiLoadingAnimation> {
  bool _isLoading = true;

  void _toggleLoading() {
    setState(() {
      _isLoading = !_isLoading;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Shimmer(
        linearGradient: _shimmerGradient,
        child: ListView(
          physics: _isLoading ? const NeverScrollableScrollPhysics() : null,
          children: [
            const SizedBox(height: 16),
            _buildTopRowList(),
            const SizedBox(height: 16),
            _buildListItem(),
            _buildListItem(),
            _buildListItem(),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _toggleLoading,
        child: Icon(_isLoading ? Icons.hourglass_full : Icons.hourglass_bottom),
      ),
    );
  }

  Widget _buildTopRowList() {
    return SizedBox(
      height: 72,
      child: ListView(
        physics: _isLoading ? const NeverScrollableScrollPhysics() : null,
        scrollDirection: Axis.horizontal,
        shrinkWrap: true,
        children: [
          const SizedBox(width: 16),
          _buildTopRowItem(),
          _buildTopRowItem(),
          _buildTopRowItem(),
          _buildTopRowItem(),
          _buildTopRowItem(),
          _buildTopRowItem(),
        ],
      ),
    );
  }

  Widget _buildTopRowItem() {
    return ShimmerLoading(isLoading: _isLoading, child: const CircleListItem());
  }

  Widget _buildListItem() {
    return ShimmerLoading(
      isLoading: _isLoading,
      child: CardListItem(isLoading: _isLoading),
    );
  }
}

class Shimmer extends StatefulWidget {
  static ShimmerState? of(BuildContext context) {
    return context.findAncestorStateOfType<ShimmerState>();
  }

  const Shimmer({super.key, required this.linearGradient, this.child});

  final LinearGradient linearGradient;
  final Widget? child;

  @override
  ShimmerState createState() => ShimmerState();
}

class ShimmerState extends State<Shimmer> with SingleTickerProviderStateMixin {
  late AnimationController _shimmerController;

  @override
  void initState() {
    super.initState();

    _shimmerController = AnimationController.unbounded(vsync: this)
      ..repeat(min: -0.5, max: 1.5, period: const Duration(milliseconds: 1000));
  }

  @override
  void dispose() {
    _shimmerController.dispose();
    super.dispose();
  }

  LinearGradient get gradient => LinearGradient(
    colors: widget.linearGradient.colors,
    stops: widget.linearGradient.stops,
    begin: widget.linearGradient.begin,
    end: widget.linearGradient.end,
    transform: _SlidingGradientTransform(
      slidePercent: _shimmerController.value,
    ),
  );

  bool get isSized =>
      (context.findRenderObject() as RenderBox?)?.hasSize ?? false;

  Size get size => (context.findRenderObject() as RenderBox).size;

  Offset getDescendantOffset({
    required RenderBox descendant,
    Offset offset = Offset.zero,
  }) {
    final shimmerBox = context.findRenderObject() as RenderBox?;
    return descendant.localToGlobal(offset, ancestor: shimmerBox);
  }

  Listenable get shimmerChanges => _shimmerController;

  @override
  Widget build(BuildContext context) {
    return widget.child ?? const SizedBox();
  }
}

class _SlidingGradientTransform extends GradientTransform {
  const _SlidingGradientTransform({required this.slidePercent});

  final double slidePercent;

  @override
  Matrix4? transform(Rect bounds, {TextDirection? textDirection}) {
    return Matrix4.translationValues(bounds.width * slidePercent, 0.0, 0.0);
  }
}

class ShimmerLoading extends StatefulWidget {
  const ShimmerLoading({
    super.key,
    required this.isLoading,
    required this.child,
  });

  final bool isLoading;
  final Widget child;

  @override
  State<ShimmerLoading> createState() => _ShimmerLoadingState();
}

class _ShimmerLoadingState extends State<ShimmerLoading> {
  Listenable? _shimmerChanges;

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    if (_shimmerChanges != null) {
      _shimmerChanges!.removeListener(_onShimmerChange);
    }
    _shimmerChanges = Shimmer.of(context)?.shimmerChanges;
    if (_shimmerChanges != null) {
      _shimmerChanges!.addListener(_onShimmerChange);
    }
  }

  @override
  void dispose() {
    _shimmerChanges?.removeListener(_onShimmerChange);
    super.dispose();
  }

  void _onShimmerChange() {
    if (widget.isLoading) {
      setState(() {
        // Update the shimmer painting.
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    if (!widget.isLoading) {
      return widget.child;
    }

    // Collect ancestor shimmer info.
    final shimmer = Shimmer.of(context)!;
    if (!shimmer.isSized) {
      // The ancestor Shimmer widget has not laid
      // itself out yet. Return an empty box.
      return const SizedBox();
    }
    final shimmerSize = shimmer.size;
    final gradient = shimmer.gradient;
    final offsetWithinShimmer = shimmer.getDescendantOffset(
      descendant: context.findRenderObject() as RenderBox,
    );

    return ShaderMask(
      blendMode: BlendMode.srcATop,
      shaderCallback: (bounds) {
        return gradient.createShader(
          Rect.fromLTWH(
            -offsetWithinShimmer.dx,
            -offsetWithinShimmer.dy,
            shimmerSize.width,
            shimmerSize.height,
          ),
        );
      },
      child: widget.child,
    );
  }
}

//----------- List Items ---------
class CircleListItem extends StatelessWidget {
  const CircleListItem({super.key});
  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 8),
      child: Container(
        width: 54,
        height: 54,
        decoration: const BoxDecoration(
          color: Colors.black,
          shape: BoxShape.circle,
        ),
        child: ClipOval(
          child: Image.network(
            'https://docs.flutter.dev/assets/images/'
            'exercise/effects/split-check/Avatar1.jpg',
            fit: BoxFit.cover,
          ),
        ),
      ),
    );
  }
}

class CardListItem extends StatelessWidget {
  const CardListItem({super.key, required this.isLoading});

  final bool isLoading;

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [_buildImage(), const SizedBox(height: 16), _buildText()],
      ),
    );
  }

  Widget _buildImage() {
    return AspectRatio(
      aspectRatio: 16 / 9,
      child: Container(
        width: double.infinity,
        decoration: BoxDecoration(
          color: Colors.black,
          borderRadius: BorderRadius.circular(16),
        ),
        child: ClipRRect(
          borderRadius: BorderRadius.circular(16),
          child: Image.network(
            'https://docs.flutter.dev/assets/images/'
            'exercise/effects/split-check/Food1.jpg',
            fit: BoxFit.cover,
          ),
        ),
      ),
    );
  }

  Widget _buildText() {
    if (isLoading) {
      return Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Container(
            width: double.infinity,
            height: 24,
            decoration: BoxDecoration(
              color: Colors.black,
              borderRadius: BorderRadius.circular(16),
            ),
          ),
          const SizedBox(height: 16),
          Container(
            width: 250,
            height: 24,
            decoration: BoxDecoration(
              color: Colors.black,
              borderRadius: BorderRadius.circular(16),
            ),
          ),
        ],
      );
    } else {
      return const Padding(
        padding: EdgeInsets.symmetric(horizontal: 8),
        child: Text(
          'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do '
          'eiusmod tempor incididunt ut labore et dolore magna aliqua.',
        ),
      );
    }
  }
}
```

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-09-28. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/effects/shimmer-loading.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/cookbook/effects/shimmer-loading\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/effects/shimmer-loading.md "Report an issue with this page").

----
url: https://docs.flutter.dev/platform-integration/android/predictive-back.md
----

\# Add the predictive back gesture > Learn how to enable and handle Android predictive back gestures in Flutter. The Android predictive back gesture lets users preview where a back gesture will navigate to, whether that's the previous screen or the home screen, before they commit to or cancel it. ## Overview Starting with Android 14 (API level 34), predictive back animations are enabled by default for system gestures when supported by the application. Flutter provides built-in support for predictive back animations across default page route transitions and custom pop handling. ## Enable predictive back in Android To support predictive back gestures in your Flutter app on Android 13 or later: 1. Open \`android/app/src/main/AndroidManifest.xml\`. 1. Add \`android:enableOnBackInvokedCallback="true"\` to the \`\` tag: \`\`\`xml \`\`\` ## Handle back gestures with PopScope To customize back navigation or prevent users from accidentally leaving a screen, use the \[\`PopScope\`]\[] widget. \`PopScope\` replaces the deprecated \`WillPopScope\` widget and supports predictive back gestures. ### Callback parameters \`PopScope\` uses the \`onPopInvokedWithResult\` callback: \* \`didPop\`: A boolean indicating whether the pop operation succeeded. If \`canPop\` is \`false\`, \`didPop\` is \`false\`. \* \`result\`: An optional return payload passed when popping the route (for example, with \`Navigator.pop(context, result)\`). ### Example: Intercepting back navigation \`\`\`dart PopScope( canPop: false, onPopInvokedWithResult: (bool didPop, Object? result) async { if (didPop) { return; } final shouldLeave = await \_showExitConfirmationDialog(context); if (shouldLeave && context.mounted) { Navigator.of(context).pop(result); } }, child: Scaffold( appBar: AppBar(title: const Text('Form Screen')), body: const Center(child: Text('Complete the form before leaving.')), ), ) \`\`\` ## More information For more details on API migrations, see the \[Android predictive back migration guide]\[]. \[\`PopScope\`]: https\://api.flutter.dev/flutter/widgets/PopScope-class.html \[Android predictive back migration guide]: /release/breaking-changes/android-predictive-back

----
url: https://docs.flutter.dev/platform-integration/web/setup.md
----

\# Set up web development > Configure your development environment to run, build, and deploy Flutter apps for the web platform. Learn how to set up your development environment to run, build, and deploy Flutter apps for the web platform. :::note If you haven't set up Flutter already, \[install Flutter]\[] first. If you've already installed Flutter, ensure that it's \[up to date]\[]. ::: \[install Flutter]: /install \[up to date]: /install/upgrade ## Install a web browser {: #install} To run and debug your Flutter app on the web, \[download and install Google Chrome]\[chrome-install] or \[install and use Microsoft Edge]\[edge-install].

Expand for instructions for other browsers

If you want to debug your app in other web browsers, you can use the \`flutter run -d web-server\` command, and manually navigate to the specified URL in your preferred browser. Note that debugging support in the \`web-server\` mode is limited. \[chrome-install]: https\://www\.google.com/chrome/ \[edge-install]: https\://www\.microsoft.com/edge ## Validate your setup {: #validate-setup} To ensure that you installed the browser successfully, and that Flutter can find it, run \`flutter devices\` in your preferred terminal. You should at least see one connected device labeled \*\*Chrome (web)\*\* or \*\*Edge (web)\*\*, similar to the following: \`\`\`console highlightLines=4 $ flutter devices Found 1 connected devices: Chrome (web) • chrome • web-javascript • Google Chrome \`\`\` If the command isn't found, or you don't see Chrome listed, check out \[Set up troubleshooting]\[troubleshoot]. \[troubleshoot]: /install/troubleshoot ## Start developing for the web {: #start-developing} Now that you've set up web development for Flutter, you can continue your Flutter learning journey while testing on the web or begin expanding integration with the web.

----
url: https://docs.flutter.dev/cookbook/design/package-fonts
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Export fonts from a package](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

Rather than declaring a font as part of an app, you can declare a font as part of a separate package. This is a convenient way to share the same font across several different projects, or for coders publishing their packages to [pub.dev](https://pub.dev). This recipe uses the following steps:

1. Add a font to a package.
2. Add the package and font to the app.
3. Use the font.

## 1. Add a font to a package

[#](#1-add-a-font-to-a-package)

To export a font from a package, you need to import the font files into the `lib` folder of the package project. You can place font files directly in the `lib` folder or in a subdirectory, such as `lib/fonts`.

In this example, assume you've got a Flutter library called `awesome_package` with fonts living in a `lib/fonts` folder.

```
awesome_package/
  lib/
    awesome_package.dart
    fonts/
      Raleway-Regular.ttf
      Raleway-Italic.ttf
```

## 2. Add the package and fonts to the app

[#](#2-add-the-package-and-fonts-to-the-app)

Now you can use the fonts in the package by updating the `pubspec.yaml` in the *app's* root directory.

### Add the package to the app

[#](#add-the-package-to-the-app)

To add the `awesome_package` package as a dependency, run `flutter pub add`:

```
$ flutter pub add awesome_package
```

### Declare the font assets

[#](#declare-the-font-assets)

Now that you've imported the package, tell Flutter where to find the fonts from the `awesome_package`.

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

```
flutter:
  fonts:
    - family: Raleway
      fonts:
        - asset: packages/awesome_package/fonts/Raleway-Regular.ttf
        - asset: packages/awesome_package/fonts/Raleway-Italic.ttf
          style: italic
```

[]()

## 3. Use the font

[#](#3-use-the-font)

Use a [`TextStyle`](https://api.flutter.dev/flutter/painting/TextStyle-class.html) to change the appearance of text. To use package fonts, declare which font you'd like to use and which package the font belongs to.

dart

```
child: Text(
  'Using the Raleway font from the awesome_package',
  style: TextStyle(fontFamily: 'Raleway'),
),
```

## Complete example

[#](#complete-example)

### Fonts

[#](#fonts)

The Raleway and RobotoMono fonts were downloaded from [Google Fonts](https://fonts.google.com).

### `pubspec.yaml`

[#](#pubspec-yaml)

yaml

```
name: package_fonts
description: An example of how to use package fonts with Flutter
​
dependencies:
  awesome_package:
  flutter:
    sdk: flutter
​
dev_dependencies:
  flutter_test:
    sdk: flutter
​
flutter:
  fonts:
    - family: Raleway
      fonts:
        - asset: packages/awesome_package/fonts/Raleway-Regular.ttf
        - asset: packages/awesome_package/fonts/Raleway-Italic.ttf
          style: italic
  uses-material-design: true
```

### `main.dart`

[#](#main-dart)

dart

```
import 'package:material_ui/material_ui.dart';
​
void main() => runApp(const MyApp());
​
class MyApp extends StatelessWidget {
  const MyApp({super.key});
​
  @override
  Widget build(BuildContext context) {
    return const MaterialApp(title: 'Package Fonts', home: MyHomePage());
  }
}
​
class MyHomePage extends StatelessWidget {
  const MyHomePage({super.key});
​
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      // The AppBar uses the app-default font.
      appBar: AppBar(title: const Text('Package Fonts')),
      body: const Center(
        // This Text widget uses the Raleway font.
        child: Text(
          'Using the Raleway font from the awesome_package',
          style: TextStyle(fontFamily: 'Raleway'),
        ),
      ),
    );
  }
}
```

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-09-29. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/design/package-fonts.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/cookbook/design/package-fonts\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/design/package-fonts.md "Report an issue with this page").

----
url: https://docs.flutter.dev/ui/adaptive-responsive/large-screens.md
----

\# Large screen devices > Things to keep in mind when adapting apps to large screens.

<!--?code-excerpt path-base="ui/adaptive_app_demos"?-->

This page provides guidance on optimizing your app to improve its behavior on large screens. Flutter, like Android, defines \[large screens]\[] as tablets, foldables, and ChromeOS devices running Android. Flutter \_also\_ defines large screen devices as web, desktop, and iPads. :::secondary Why do large screens matter, in particular? Demand for large screens continues to increase. As of January 2024, more than \[270 million active large screen]\[large screens] and foldable devices run on Android and more than \[14.9 million iPad users]\[]. When your app supports large screens, it also receives other benefits. Optimizing your app to fill the screen. For example, it: \* Improves your app's user engagement metrics. \* Increases your app's visibility in the Play Store. Recent \[Play Store updates]\[] show ratings by device type and indicates when an app lacks large screen support. \* Ensures that your app meets iPadOS submission guidelines and is \[accepted in the App Store]\[]. ::: \[14.9 million iPad users]: https\://www\.statista.com/statistics/299632/tablet-shipments-apple/ \[accepted in the App Store]: https\://developer.apple.com/ipados/submit/ \[large screens]: https\://developer.android.com/guide/topics/large-screens/get-started-with-large-screens \[Play Store updates]: https\://developer.android.com/2022/03/helping-users-discover-quality-apps-on.html ## Layout with GridView Consider the following screenshots of an app. The app displays its UI in a \`ListView\`. The image on the left shows the app running on a mobile device. The image on the right shows the app running on a large screen device \_before the advice on this page was applied\_. !\[Sample of large screen]\(/assets/images/docs/ui/adaptive-responsive/large-screen.png){:width="90%"} This is not optimal. The \[Android Large Screen App Quality Guidelines]\[guidelines] and the \[iOS equivalent]\[] say that neither text nor boxes should take up the full screen width. How to solve this in an adaptive way? \[guidelines]: https\://developer.android.com/docs/quality-guidelines/large-screen-app-quality \[iOS equivalent]: https\://developer.apple.com/design/human-interface-guidelines/designing-for-ipados A common solution uses \`GridView\`, as shown in the next section. ### GridView You can use the \`GridView\` widget to transform your existing \`ListView\` into more reasonably-sized items. \`GridView\` is similar to the \[\`ListView\`]\[] widget, but instead of handling only a list of widgets arranged linearly, \`GridView\` can arrange widgets in a two-dimensional array. \`GridView\` also has constructors that are similar to \`ListView\`. The \`ListView\` default constructor maps to \`GridView\.count\`, and \`ListView\.builder\` is similar to \`GridView\.builder\`. \`GridView\` has some additional constructors for more custom layouts. To learn more, visit the \[\`GridView\`]\[] API page. \[\`GridView\`]: https\://api.flutter.dev/flutter/widgets/GridView-class.html \[\`ListView\`]: https\://api.flutter.dev/flutter/widgets/ListView-class.html For example, if your original app used a \`ListView\.builder\`, swap that out for a \`GridView\.builder\`. If your app has a large number of items, it's recommended to use this builder constructor to only build the item widgets that are actually visible. Most of the parameters in the constructor are the same between the two widgets, so it's a straightforward swap. However, you need to figure out what to set for the \`gridDelegate\`. Flutter provides powerful premade \`gridDelegates\` that you can use, namely: \[\`SliverGridDelegateWithFixedCrossAxisCount\`]\[] : Lets you assign a specific number of columns to your grid. \[\`SliverGridDelegateWithMaxCrossAxisExtent\`]\[] : Lets you define a max item width. \[\`SliverGridDelegateWithFixedCrossAxisCount\`]: https\://api.flutter.dev/flutter/rendering/SliverGridDelegateWithFixedCrossAxisCount-class.html \[\`SliverGridDelegateWithMaxCrossAxisExtent\`]: https\://api.flutter.dev/flutter/rendering/SliverGridDelegateWithMaxCrossAxisExtent-class.html :::secondary Don't use the grid delegate for these classes that lets you set the column count directly and then hardcode the number of columns based on whether the device is a tablet, or whatever. The number of columns should be based on the size of the window and not the size of the physical device. This distinction is important because many mobile devices support multi-window mode, which can cause your app to be rendered in a space smaller than the physical size of the display. Also, Flutter apps can run on web and desktop, which might be sized in many ways. \*\*For this reason, use \`MediaQuery\` to get the app window size rather than the physical device size.\*\* ::: ### Other solutions Another way to approach these situations is to use the \`maxWidth\` property of \`BoxConstraints\`. This involves the following: \* Wrap the \`GridView\`in a \`ConstrainedBox\` and give it a \`BoxConstraints\` with a maximum width set. \* Use a \`Container\` instead of a \`ConstrainedBox\` if you want other functionality like setting the background color. For choosing the maximum width value, consider using the values recommended by Material 3 in the \[Applying layout]\[] guide. \[Applying layout]: https\://m3.material.io/foundations/layout/applying-layout/window-size-classes ## Foldables As mentioned previously, Android and Flutter both recommend in their design guidance \*\*not\*\* to lock screen orientation, but some apps lock screen orientation anyway. Be aware that this can cause problems when running your app on a foldable device. When running on a foldable, the app might look ok when the device is folded. But when unfolding, you might find the app letterboxed. As described in the \[SafeArea & MediaQuery]\[sa-mq] page, letterboxing means that the app's window is locked to the center of the screen while the window is surrounded with black. \[sa-mq]: /ui/adaptive-responsive/safearea-mediaquery Why can this happen? This can happen when using \`MediaQuery\` to figure out the window size for your app. When the device is folded, orientation is restricted to portrait mode. Under the hood, \`setPreferredOrientations\` causes Android to use a portrait compatibility mode and the app is displayed in a letterboxed state. In the letterboxed state, \`MediaQuery\` never receives the larger window size that allows the UI to expand. You can solve this in one of two ways: \* Support all orientations. \* Use the dimensions of the \_physical display\_. In fact, this is one of the \_few\_ situations where you would use the physical display dimensions and \_not\_ the window dimensions. How to obtain the physical screen dimensions? You can use the \[\`Display\`]\[] API, which contains the size, pixel ratio, and refresh rate of the physical device. \[\`Display\`]: https\://api.flutter.dev/flutter/dart-ui/Display-class.html The following sample code retrieves a \`Display\` object: \`\`\`dart /// AppState object. ui.FlutterView? \_view; @override void didChangeDependencies() { super.didChangeDependencies(); \_view = View\.maybeOf(context); } void didChangeMetrics() { final ui.Display? display = \_view?.display; } \`\`\` The important thing is to find the display of the view that you care about. This creates a forward-looking API that should handle current \_and\_ future multi-display and multi-view devices. ## Adaptive input Adding support for more screens, also means expanding input controls. Android guidelines describe three tiers of large format device support. !\[3 tiers of large format device support]\(/assets/images/docs/ui/adaptive-responsive/large-screen-guidelines.png){:width="90%"} Tier 3, the lowest level of support, includes support for mouse and stylus input (\[Material 3 guidelines]\[m3-guide], \[Apple guidelines]\[]). If your app uses Material 3 and its buttons and selectors, then your app already has built-in support for various additional input states. But what if you have a custom widget? Check out the \[User input]\[] page for guidance on adding \[input support for widgets]\[]. \[Apple guidelines]: https\://developer.apple.com/design/human-interface-guidelines/designing-for-ipados#Best-practices \[input support for widgets]: /ui/adaptive-responsive/input#custom-widgets \[m3-guide]: https\://developer.android.com/docs/quality-guidelines/large-screen-app-quality \[User input]: /ui/adaptive-responsive/input ### Navigation Navigation can create unique challenges when working with a variety of differently-sized devices. Generally, you want to switch between a \[\`BottomNavigationBar\`]\[] and a \[\`NavigationRail\`] depending on available screen space. For more information (and corresponding example code), check out \[Problem: Navigation rail]\[], a section in the \[Developing Flutter apps for Large screens]\[article] article. \[article]: https\://blog.flutter.dev/developing-flutter-apps-for-large-screens-53b7b0e17f10 \[\`BottomNavigationBar\`]: https\://pub.dev/documentation/material\_ui/latest/material\_ui/BottomNavigationBar-class.html \[\`NavigationRail\`]: https\://pub.dev/documentation/material\_ui/latest/material\_ui/NavigationRail-class.html \[Problem: Navigation rail]: https\://blog.flutter.dev/developing-flutter-apps-for-large-screens-53b7b0e17f10#:\~:text=Problem%3A%20Navigation%20rail1

----
url: https://docs.flutter.dev/data-and-backend/state-mgmt/declarative.md
----

\# Start thinking declaratively > How to think about declarative programming. If you're coming to Flutter from an imperative framework (such as Android SDK or iOS UIKit), you need to start thinking about app development from a new perspective. Many assumptions that you might have don't apply to Flutter. For example, in Flutter it's okay to rebuild parts of your UI from scratch instead of modifying it. Flutter is fast enough to do that, even on every frame if needed. Flutter is \_declarative\_. This means that Flutter builds its user interface to reflect the current state of your app: When the state of your app changes (for example, the user flips a switch in the settings screen), you change the state, and that triggers a redraw of the user interface. There is no imperative changing of the UI itself (like \`widget.setText\`)—you change the state, and the UI rebuilds from scratch. Read more about the declarative approach to UI programming in the \[Introduction to declarative UI]\[]. 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 might not seem as intuitive as the imperative style. This is why this section is here. Read on. \[Introduction to declarative UI]: /flutter-for/declarative

----
url: https://docs.flutter.dev/perf/best-practices
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Performance best practices](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

Generally, Flutter applications are performant by default, so you only need to avoid common pitfalls to get excellent performance. These best practice recommendations will help you write the most performant Flutter app possible.

How do you design a Flutter app to most efficiently render your scenes? In particular, how do you ensure that the painting code generated by the framework is as efficient as possible? Some rendering and layout operations are known to be slow, but can't always be avoided. They should be used thoughtfully, following the guidance below.

## Minimize expensive operations

[#](#minimize-expensive-operations)

Some operations are more expensive than others, meaning that they consume more resources. Obviously, you want to only use these operations when necessary. How you design and implement your app's UI can have a big impact on how efficiently it runs.

### Control build() cost

[#](#control-build-cost)

Here are some things to keep in mind when designing your UI:

* Avoid repetitive and costly work in `build()` methods since `build()` can be invoked frequently when ancestor widgets rebuild.

* Avoid overly large single widgets with a large `build()` function. Split them into different widgets based on encapsulation but also on how they change:

  * When `setState()` is called on a `State` object, all descendent widgets rebuild. Therefore, localize the `setState()` call to the part of the subtree whose UI actually needs to change. Avoid calling `setState()` high up in the tree if the change is contained to a small part of the tree.
  * The traversal to rebuild all descendents stops when the same instance of the child widget as the previous frame is re-encountered. This technique is heavily used inside the framework for optimizing animations where the animation doesn't affect the child subtree. See the [`TransitionBuilder`](https://api.flutter.dev/flutter/widgets/TransitionBuilder.html) pattern and the [source code for `SlideTransition`](https://github.com/flutter/flutter/blob/main/packages/flutter/lib/src/widgets/transitions.dart#L168), which uses this principle to avoid rebuilding its descendents when animating. ("Same instance" is evaluated using `operator ==`, but see the pitfalls section at the end of this page for advice on when to avoid overriding `operator ==`.)
  * To create reusable pieces of UIs, prefer using a [`StatelessWidget`](https://api.flutter.dev/flutter/widgets/StatelessWidget-class.html) rather than a function.

For more information, check out:

* [Performance considerations](https://api.flutter.dev/flutter/widgets/StatefulWidget-class.html#performance-considerations), part of the [`StatefulWidget`](https://api.flutter.dev/flutter/widgets/StatefulWidget-class.html) API doc
* [Widgets vs helper methods](https://www.youtube.com/watch?v=IOyq-eTRhvo), a video from the official Flutter YouTube channel that explains why widgets are more performant than functions.

***

### Use StringBuffer for efficient string building

[#](#use-stringbuffer-for-efficient-string-building)

When you need to build a string from multiple parts, especially inside a loop, using the `+` operator can be inefficient because it creates a new `String` object on each concatenation. A better approach is to use `StringBuffer`, which collects all the strings and concatenates them only once, when you call `toString()`.

[Watch on YouTube in a new tab: "StringBuffer (Technique of the Week)"](https://www.youtube.com/watch/xSsFtDY-nOw)

***

### Use saveLayer() thoughtfully

[#](#use-savelayer-thoughtfully)

Some Flutter code uses `saveLayer()`, an expensive operation, to implement various visual effects in the UI. Even if your code doesn't explicitly call `saveLayer()`, other widgets or packages that you use might call it behind the scenes. Perhaps your app is calling `saveLayer()` more than necessary; excessive calls to `saveLayer()` can cause jank.

#### Why is saveLayer expensive?

[#](#why-is-savelayer-expensive)

Calling `saveLayer()` allocates an offscreen buffer and drawing content into the offscreen buffer might trigger a render target switch. The GPU wants to run like a firehose, and a render target switch forces the GPU to redirect that stream temporarily and then direct it back again. On mobile GPUs this is particularly disruptive to rendering throughput.

#### When is saveLayer required?

[#](#when-is-savelayer-required)

At runtime, if you need to dynamically display various shapes coming from a server (for example), each with some transparency, that might (or might not) overlap, then you pretty much have to use `saveLayer()`.

#### Debugging calls to saveLayer

[#](#debugging-calls-to-savelayer)

How can you tell how often your app calls `saveLayer()`, either directly or indirectly? The `saveLayer()` method triggers an event on the [DevTools timeline](/tools/devtools/performance#timeline-events-tab); learn when your scene uses `saveLayer` by checking the `PerformanceOverlayLayer.checkerboardOffscreenLayers` switch in the [DevTools Performance view](/tools/devtools/performance).

#### Minimizing calls to saveLayer

[#](#minimizing-calls-to-savelayer)

Can you avoid calls to `saveLayer`? It might require rethinking of how you create your visual effects:

* If the calls are coming from *your* code, can you reduce or eliminate them? For example, perhaps your UI overlaps two shapes, each having non-zero transparency:

  * If they always overlap in the same amount, in the same way, with the same transparency, you can precalculate what this overlapped, semi-transparent object looks like, cache it, and use that instead of calling `saveLayer()`. This works with any static shape you can precalculate.
  * Can you refactor your painting logic to avoid overlaps altogether?

* If the calls are coming from a package that you don't own, contact the package owner and ask why these calls are necessary. Can they be reduced or eliminated? If not, you might need to find another package, or write your own.

Other widgets that might trigger `saveLayer()` and are potentially costly:

* [`ShaderMask`](https://api.flutter.dev/flutter/widgets/ShaderMask-class.html)
* [`ColorFilter`](https://api.flutter.dev/flutter/dart-ui/ColorFilter-class.html)
* [`Chip`](https://pub.dev/documentation/material_ui/latest/material_ui/Chip-class.html)—might trigger a call to `saveLayer()` if `disabledColorAlpha != 0xff`
* [`Text`](https://api.flutter.dev/flutter/widgets/Text-class.html)—might trigger a call to `saveLayer()` if there's an `overflowShader`

***

### Minimize use of opacity and clipping

[#](#minimize-use-of-opacity-and-clipping)

Opacity is another expensive operation, as is clipping. Here are some tips you might find to be useful:

* Use the [`Opacity`](https://api.flutter.dev/flutter/widgets/Opacity-class.html) widget only when necessary. See the [Transparent image](https://api.flutter.dev/flutter/widgets/Opacity-class.html#transparent-image) section in the `Opacity` API page for an example of applying opacity directly to an image, which is faster than using the `Opacity` widget.
* Instead of wrapping simple shapes or text in an `Opacity` widget, it's usually faster to just draw them with a semitransparent color. (Though this only works if there are no overlapping bits in the to-be-drawn shape.)
* To implement fading in an image, consider using the [`FadeInImage`](https://api.flutter.dev/flutter/widgets/FadeInImage-class.html) widget, which applies a gradual opacity using the GPU's fragment shader. For more information, check out the [`Opacity`](https://api.flutter.dev/flutter/widgets/Opacity-class.html) docs.
* **Clipping** doesn't call `saveLayer()` (unless explicitly requested with `Clip.antiAliasWithSaveLayer`), so these operations aren't as expensive as `Opacity`, but clipping is still costly, so use with caution. By default, clipping is disabled (`Clip.none`), so you must explicitly enable it when needed.
* To create a rectangle with rounded corners, instead of applying a clipping rectangle, consider using the `borderRadius` property offered by many of the widget classes.

***

### Implement grids and lists thoughtfully

[#](#implement-grids-and-lists-thoughtfully)

How your grids and lists are implemented might be causing performance problems for your app. This section describes an important best practice when creating grids and lists, and how to determine whether your app uses excessive layout passes.

#### Be lazy!

[#](#be-lazy)

When building a large grid or list, use the lazy builder methods, with callbacks. That ensures that only the visible portion of the screen is built at startup time.

For more information and examples, check out:

* [Working with long lists](/cookbook/lists/long-lists)
* [Creating a `ListView` that loads one page at a time](https://medium.com/saugo360/flutter-creating-a-listview-that-loads-one-page-at-a-time-c5c91b6fabd3) a community article by AbdulRahman AlHamali
* [`ListView.builder`](https://api.flutter.dev/flutter/widgets/ListView/ListView.builder.html) API

#### Avoid intrinsics

[#](#avoid-intrinsics)

For information on how intrinsic passes might be causing problems with your grids and lists, see the next section.

***

### Minimize layout passes caused by intrinsic operations

[#](#minimize-layout-passes-caused-by-intrinsic-operations)

If you've done much Flutter programming, you are probably familiar with [how layout and constraints work](/ui/layout/constraints) when creating your UI. You might even have memorized Flutter's basic layout rule: **Constraints go down. Sizes go up. Parent sets position.**

For some widgets, particularly grids and lists, the layout process can be expensive. Flutter strives to perform just one layout pass over the widgets but, sometimes, a second pass (called an *intrinsic pass*) is needed, and that can slow performance.

#### What is an intrinsic pass?

[#](#what-is-an-intrinsic-pass)

An intrinsic pass happens when, for example, you want all cells to have the size of the biggest or smallest cell (or some similar calculation that requires polling all cells).

For example, consider a large grid of `Card`s. A grid should have uniformly sized cells, so the layout code performs a pass, starting from the root of the grid (in the widget tree), asking **each** card in the grid (not just the visible cards) to return its *intrinsic* size—the size that the widget prefers, assuming no constraints. With this information, the framework determines a uniform cell size, and re-visits all grid cells a second time, telling each card what size to use.

#### Debugging intrinsic passes

[#](#debugging-intrinsic-passes)

To determine whether you have excessive intrinsic passes, enable the **[Track layouts option](/tools/devtools/performance#track-layouts)** in DevTools (disabled by default), and look at the app's [stack trace](/tools/devtools/cpu-profiler#flame-chart) to learn how many layout passes were performed. Once you enable tracking, intrinsic timeline events are labeled as '$runtimeType intrinsics'.

#### Avoiding intrinsic passes

[#](#avoiding-intrinsic-passes)

You have a couple options for avoiding the intrinsic pass:

* Set the cells to a fixed size up front.
* Choose a particular cell to be the "anchor" cell—all cells will be sized relative to this cell. Write a custom [`RenderObject`](https://api.flutter.dev/flutter/rendering/RenderObject-class.html) that positions the child anchor first and then lays out the other children around it.

To dive even deeper into how layout works, check out the [layout and rendering](/resources/architectural-overview#layout-and-rendering) section in the [Flutter architectural overview](/resources/architectural-overview).

***

### Build and display frames in 16ms

[#](#build-and-display-frames-in-16ms)

Since there are two separate threads for building and rendering, you have 16ms for building, and 16ms for rendering on a 60Hz display. If latency is a concern, build and display a frame in 16ms *or less*. Note that means built in 8ms or less, and rendered in 8ms or less, for a total of 16ms or less.

If your frames are rendering in well under 16ms total in [profile mode](/testing/build-modes#profile), you likely don't have to worry about performance even if some performance pitfalls apply, but you should still aim to build and render a frame as fast as possible. Why?

* Lowering the frame render time below 16ms might not make a visual difference, but it **improves battery life** and thermal issues.
* It might run fine on your device, but consider performance for the lowest device you are targeting.
* As 120fps devices become more widely available, you'll want to render frames in under 8ms (total) in order to provide the smoothest experience.

If you are wondering why 60fps leads to a smooth visual experience, check out the video [Why 60fps?](https://www.youtube.com/watch?v=CaMTIgxCSqU)

## Pitfalls

[#](#pitfalls)

If you need to tune your app's performance, or perhaps the UI isn't as smooth as you expect, the [DevTools Performance view](/tools/devtools/performance) can help!

Also, the Flutter plugin for your IDE might be useful. In the Flutter Performance window, enable the **Show widget rebuild information** check box. This feature helps you detect when frames are being rendered and displayed in more than 16ms. Where possible, the plugin provides a link to a relevant tip.

The following behaviors might negatively impact your app's performance.

* Avoid using the `Opacity` widget, and particularly avoid it in an animation. Use `AnimatedOpacity` or `FadeInImage` instead. For more information, check out [Performance considerations for opacity animation](https://api.flutter.dev/flutter/widgets/Opacity-class.html#performance-considerations-for-opacity-animation).

* When using an `AnimatedBuilder`, avoid putting a subtree in the builder function that builds widgets that don't depend on the animation. This subtree is rebuilt for every tick of the animation. Instead, build that part of the subtree once and pass it as a child to the `AnimatedBuilder`. For more information, check out [Performance optimizations](https://api.flutter.dev/flutter/widgets/AnimatedBuilder-class.html#performance-optimizations).

* Avoid clipping in an animation. If possible, pre-clip the image before animating it.

* Avoid using constructors with a concrete `List` of children (such as `Column()` or `ListView()`) if most of the children are not visible on screen to avoid the build cost.

* Avoid overriding `operator ==` on `Widget` objects. While it might seem like it would help by avoiding unnecessary rebuilds, in practice it hurts performance because it results in O(N²) behavior. The only exception to this rule is leaf widgets (widgets with no children), in the specific case where comparing the properties of the widget is likely to be significantly more efficient than rebuilding the widget and where the widget will rarely change configuration. Even in such cases, it is generally preferable to rely on caching the widgets, because even one override of `operator ==` can result in across-the-board performance degradation as the compiler can no longer assume that the call is always static.

## Resources

[#](#resources)

For more performance info, check out the following resources:

* [Performance optimizations](https://api.flutter.dev/flutter/widgets/AnimatedBuilder-class.html#performance-optimizations) in the AnimatedBuilder API page
* [Performance considerations for opacity animation](https://api.flutter.dev/flutter/widgets/Opacity-class.html#performance-considerations-for-opacity-animation) in the Opacity API page
* [Child elements' lifecycle](https://api.flutter.dev/flutter/widgets/ListView-class.html#child-elements-lifecycle) and how to load them efficiently, in the ListView API page
* [Performance considerations](https://api.flutter.dev/flutter/widgets/StatefulWidget-class.html#performance-considerations) of a `StatefulWidget`
* [Best practices for optimizing Flutter web loading speed](https://blog.flutter.dev/best-practices-for-optimizing-flutter-web-loading-speed-7cc0df14ce5c)

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-10-14. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/perf/best-practices.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/perf/best-practices\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/perf/best-practices.md "Report an issue with this page").

----
url: https://docs.flutter.dev/cookbook/animation/animated-container.md
----

\# Animate the properties of a container > How to animate properties of a container using implicit animations.

<!--?code-excerpt path-base="cookbook/animation/animated_container/"?-->

The \[\`Container\`]\[] 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 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\`]\[] 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 animations are known as "implicit animations." This recipe describes how to use an \`AnimatedContainer\` to animate the size, background color, and border radius when the user taps a button using the following steps: 1. Create a \`StatefulWidget\` with default properties. 1. Build an \`AnimatedContainer\` using the properties. 1. Start the animation by rebuilding with new properties. ## 1. Create a \`StatefulWidget\` with default properties To start, create \[\`StatefulWidget\`]\[] and \[\`State\`]\[] classes. 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. You can also define the default value of each property. These properties belong to a custom \`State\` class so they can be updated when the user taps a button.

<!--?code-excerpt "lib/starter.dart (Starter)" remove="return Container();"?-->

\`\`\`dart class AnimatedContainerApp extends StatefulWidget { const AnimatedContainerApp({super.key}); @override State createState() => \_AnimatedContainerAppState(); } class \_AnimatedContainerAppState extends State { // Define the various properties with default values. Update these properties // when the user taps a FloatingActionButton. double \_width = 50; double \_height = 50; Color \_color = Colors.green; BorderRadiusGeometry \_borderRadius = BorderRadius.circular(8); @override Widget build(BuildContext context) { // Fill this out in the next steps. } } \`\`\` ## 2. Build an \`AnimatedContainer\` using the properties Next, build the \`AnimatedContainer\` using the properties defined in the previous step. Furthermore, provide a \`duration\` that defines how long the animation should run.

<!--?code-excerpt "lib/main.dart (AnimatedContainer)" replace="/^child: //g;/^\),$/)/g"?-->

\`\`\`dart AnimatedContainer( // Use the properties stored in the State class. width: \_width, height: \_height, decoration: BoxDecoration( color: \_color, borderRadius: \_borderRadius, ), // Define how long the animation should take. duration: const Duration(seconds: 1), // Provide an optional curve to make the animation feel smoother. curve: Curves.fastOutSlowIn, ) \`\`\` ## 3. Start the animation by rebuilding with new properties Finally, start the animation by rebuilding the \`AnimatedContainer\` with the new properties. How to trigger a rebuild? Use the \[\`setState()\`]\[] method. 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()\`. 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.

<!--?code-excerpt "lib/main.dart (FAB)" replace="/^floatingActionButton: //g;/^\),$/)/g"?-->

\`\`\`dart FloatingActionButton( // When the user taps the button onPressed: () { // Use setState to rebuild the widget with new values. setState(() { // Create a random number generator. final random = Random(); // Generate a random width and height. \_width = random.nextInt(300).toDouble(); \_height = random.nextInt(300).toDouble(); // Generate a random color. \_color = Color.fromRGBO( random.nextInt(256), random.nextInt(256), random.nextInt(256), 1, ); // Generate a random border radius. \_borderRadius = BorderRadius.circular( random.nextInt(100).toDouble(), ); }); }, tooltip: 'Animate', child: const Icon(Icons.play\_arrow), ) \`\`\` ## Interactive example

<!--?code-excerpt "lib/main.dart"?-->

\`\`\`dartpad title="Flutter animated container hands-on example in DartPad" run="true" import 'dart:math'; import 'package:flutter/material.dart'; void main() => runApp(const AnimatedContainerApp()); class AnimatedContainerApp extends StatefulWidget { const AnimatedContainerApp({super.key}); @override State createState() => \_AnimatedContainerAppState(); } class \_AnimatedContainerAppState extends State { // Define the various properties with default values. Update these properties // when the user taps a FloatingActionButton. double \_width = 50; double \_height = 50; Color \_color = Colors.green; BorderRadiusGeometry \_borderRadius = BorderRadius.circular(8); @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( appBar: AppBar(title: const Text('AnimatedContainer Demo')), body: Center( child: AnimatedContainer( // Use the properties stored in the State class. width: \_width, height: \_height, decoration: BoxDecoration( color: \_color, borderRadius: \_borderRadius, ), // Define how long the animation should take. duration: const Duration(seconds: 1), // Provide an optional curve to make the animation feel smoother. curve: Curves.fastOutSlowIn, ), ), floatingActionButton: FloatingActionButton( // When the user taps the button onPressed: () { // Use setState to rebuild the widget with new values. setState(() { // Create a random number generator. final random = Random(); // Generate a random width and height. \_width = random.nextInt(300).toDouble(); \_height = random.nextInt(300).toDouble(); // Generate a random color. \_color = Color.fromRGBO( random.nextInt(256), random.nextInt(256), random.nextInt(256), 1, ); // Generate a random border radius. \_borderRadius = BorderRadius.circular( random.nextInt(100).toDouble(), ); }); }, tooltip: 'Animate', child: const Icon(Icons.play\_arrow), ), ), ); } } \`\`\` \[\`AnimatedContainer\`]: https\://api.flutter.dev/flutter/widgets/AnimatedContainer-class.html \[\`Container\`]: https\://api.flutter.dev/flutter/widgets/Container-class.html \[\`setState()\`]: https\://api.flutter.dev/flutter/widgets/State/setState.html \[\`State\`]: https\://api.flutter.dev/flutter/widgets/State-class.html \[\`StatefulWidget\`]: https\://api.flutter.dev/flutter/widgets/StatefulWidget-class.html

----
url: https://docs.flutter.dev/learn/pathway/tutorial/set-up-state-project.md
----

\# Set up your project > Instructions on how to create a new Flutter app. Preview the Wikipedia reader app you'll build and set up the initial project with required packages. title: What you'll accomplish items: - title: Preview the Wikipedia reader app you'll build icon: preview - title: Add packages for handling HTTP requests and Wikipedia data icon: inventory\_2 - title: Set up the initial project structure icon: code --- ### Introduction In the next few lessons, you'll learn how to work with data in a Flutter app. You'll build an app that fetches and displays article summaries from the \[Wikipedia API]\[]. These lessons explore: - Making HTTP requests in Flutter. - Managing application state with \`ChangeNotifier\`. - Using the MVVM architecture pattern. - Creating responsive user interfaces that update automatically when data changes. This tutorial assumes you've completed the \[Getting started with Dart]\[] and the \[Introduction to Flutter UI]\[] tutorials, and therefore doesn't explain concepts like HTTP, JSON, or widget basics. :::recommend Support Wikipedia \[Wikipedia]\[] is a valuable resource, providing free access to human knowledge through millions of articles written collaboratively by volunteers worldwide. Consider \[donating to Wikipedia]\[] to help keep this incredible resource free and accessible to everyone. ::: \[Wikipedia API]: https\://en.wikipedia.org/api/rest\_v1/ \[Getting started with Dart]: https\://dart.dev/learn/tutorial \[Introduction to Flutter UI]: /learn/pathway/tutorial/create-an-app \[Wikipedia]: https\://wikipedia.org/ \[donating to Wikipedia]: https\://donate.wikimedia.org/ ### Create a new Flutter project Create a new Flutter project using the \[Flutter CLI]\[]. In your preferred terminal, run the following command to create a minimal Flutter app: \`\`\`console $ flutter create wikipedia\_reader --empty \`\`\` \[Flutter CLI]: /reference/flutter-cli ### Add required dependencies Your app needs the \[\`http\` package]\[] to make HTTP requests, and the \[\`material\_ui\` package]\[] for some UI components. Add them to your project: \`\`\`console $ cd wikipedia\_reader && flutter pub add http material\_ui && flutter pub get \`\`\` \[\`http\` package]: https\://pub.dev/packages/http \[\`material\_ui\` package]: https\://pub.dev/packages/material\_ui ### Examine the starter code First, create a new file \`lib/summary.dart\` to define the data model for Wikipedia article summaries. This file has no special logic, and is simply a collection of classes that represent the data returned by the Wikipedia API. It's sufficient to copy the code below into the file and then ignore it. If you aren't comfortable with basic Dart classes, you should read the \[Dart Getting Started]\[] tutorial first.

<!--?code-excerpt "fwe/wikipedia_reader/lib/summary.dart (All)"?-->

\`\`\`dart title="lib/summary.dart" collapsed /// Representation of the JSON data returned by the Wikipedia API. class Summary { /// Returns a new \[Summary] instance. Summary({ required this.titles, required this.pageId, required this.extract, required this.extractHtml, required this.lang, required this.dir, required this.url, this.description, this.thumbnail, this.originalImage, }); /// The title information of this article. final TitlesSet titles; /// The page ID of this article. final int pageId; /// The first few sentences of the article in plain text. final String extract; /// The first few sentences of the article in HTML format. final String extractHtml; /// The language code of the article's content, such as "en" for English. final String lang; /// The text directionality of the article's content, such as "ltr" or "rtl". final String dir; /// The URL of the page. final String url; /// A description of the article, if available. final String? description; /// A thumbnail-sized version of the article's primary image, if available. final ImageFile? thumbnail; /// The original full-sized article's primary image, if available. final ImageFile? originalImage; /// Whether this article has an image. bool get hasImage => originalImage != null && thumbnail != null; /// Returns a new \[Summary] instance and imports its values from a JSON map static Summary fromJson(Map json) { return switch (json) { { 'titles': final Map titles, 'pageid': final int pageId, 'extract': final String extract, 'extract\_html': final String extractHtml, 'thumbnail': final Map thumbnail, 'originalimage': final Map originalImage, 'lang': final String lang, 'dir': final String dir, 'description': final String description, 'content\_urls': { 'desktop': {'page': final String url}, 'mobile': {'page': String \_}, }, } => Summary( titles: TitlesSet.fromJson(titles), pageId: pageId, extract: extract, extractHtml: extractHtml, thumbnail: ImageFile.fromJson(thumbnail), originalImage: ImageFile.fromJson(originalImage), lang: lang, dir: dir, description: description, url: url, ), { 'titles': final Map titles, 'pageid': final int pageId, 'extract': final String extract, 'extract\_html': final String extractHtml, 'lang': final String lang, 'dir': final String dir, 'description': final String description, 'content\_urls': { 'desktop': {'page': final String url}, 'mobile': {'page': String \_}, }, } => Summary( titles: TitlesSet.fromJson(titles), pageId: pageId, extract: extract, extractHtml: extractHtml, lang: lang, dir: dir, description: description, url: url, ), { 'titles': final Map titles, 'pageid': final int pageId, 'extract': final String extract, 'extract\_html': final String extractHtml, 'lang': final String lang, 'dir': final String dir, 'content\_urls': { 'desktop': {'page': final String url}, 'mobile': {'page': String \_}, }, } => Summary( titles: TitlesSet.fromJson(titles), pageId: pageId, extract: extract, extractHtml: extractHtml, lang: lang, dir: dir, url: url, ), \_ => throw FormatException('Could not deserialize Summary, json=$json'), }; } @override String toString() => 'Summary\[' 'titles=$titles, ' 'pageId=$pageId, ' 'extract=$extract, ' 'extractHtml=$extractHtml, ' 'thumbnail=${thumbnail ?? 'null'}, ' 'originalImage=${originalImage ?? 'null'}, ' 'lang=$lang, ' 'dir=$dir, ' 'description=$description' ']'; } // Image path and size, but doesn't contain any Wikipedia descriptions. class ImageFile { /// Returns a new \[ImageFile] instance. ImageFile({required this.source, required this.width, required this.height}); /// The URI of the original image. final String source; /// The width of the original image. final int width; /// The height of the original image. final int height; /// The file extension of the image, or 'err' if one can't be determined. String get extension { final extension = getFileExtension(source); // By default, return a non-viable image extension. return extension ?? 'err'; } /// Returns a JSON map representation of this \[ImageFile]. Map toJson() { return { 'source': source, 'width': width, 'height': height, }; } /// Returns a new \[ImageFile] instance with its values populated from \[json]. static ImageFile fromJson(Map json) { if (json case { 'source': final String source, 'height': final int height, 'width': final int width, }) { return ImageFile(source: source, width: width, height: height); } throw FormatException('Could not deserialize OriginalImage, json=$json'); } @override String toString() => 'OriginalImage\[source\_=$source, width=$width, height=$height]'; } class TitlesSet { /// Returns a new \[TitlesSet] instance. TitlesSet({ required this.canonical, required this.normalized, required this.display, }); /// The non-prefixed DB key for the article. /// /// Might contain changes such as underscores instead of spaces. /// Best suited for making request URIs, but still requires percent-encoding. final String canonical; /// The \[normalized title]\(https\://www\.mediawiki.org/wiki/API:Query#Example\_2:\_Title\_normalization) /// of the article. final String normalized; /// The title as it should be displayed to the user. final String display; /// Returns a new \[TitlesSet] instance with its values populated from \[json]. static TitlesSet fromJson(Map json) { if (json case { 'canonical': final String canonical, 'normalized': final String normalized, 'display': final String display, }) { return TitlesSet( canonical: canonical, normalized: normalized, display: display, ); } throw FormatException('Could not deserialize TitleSet, json=$json'); } @override String toString() => 'TitlesSet\[' 'canonical=$canonical, ' 'normalized=$normalized, ' 'display=$display' ']'; } String? getFileExtension(String file) { final segments = file.split('.'); if (segments.isNotEmpty) return segments.last; return null; } const acceptableImageFormats = \['png', 'jpg', 'jpeg']; \`\`\` Then, open \`lib/main.dart\` and replace the existing code with this basic structure, which adds required imports that the app uses:

<!--?code-excerpt "fwe/wikipedia_reader/lib/step1_main.dart (All)"?-->

\`\`\`dart title="lib/main.dart" import 'dart:convert'; import 'dart:io'; import 'package:http/http.dart'; import 'package:material\_ui/material\_ui.dart'; import 'summary.dart'; void main() { runApp(const MainApp()); } class MainApp extends StatelessWidget { const MainApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( appBar: AppBar(title: const Text('Wikipedia Flutter')), body: const Center(child: Text('Loading...')), ), ); } } \`\`\` This code provides a basic app structure with a title bar and placeholder content. The imports at the top include everything you need for HTTP requests, JSON parsing, and the Wikipedia data model. \[Dart Getting Started]: https\://dart.dev/tutorial ### Run your app Test that everything works by running your app: \`\`\`console $ flutter run -d chrome \`\`\` You should see a simple app with "Wikipedia Flutter" in the app bar and "Loading..." in the center of the screen. ### Review title: What you accomplished subtitle: Here's a summary of what you built and learned in this lesson. completed: true items: - title: Previewed the Wikipedia reader app icon: preview details: >- You're starting a new tutorial section focused on working with data. You'll learn HTTP requests, state management with \`ChangeNotifier\`, and the MVVM architectural pattern. - title: Added the http package and created a data model icon: inventory\_2 details: >- You used \`flutter pub add\` to install the http package for making HTTP requests and created the \`Summary\` class for Wikipedia data. Packages let you leverage existing code built by the community instead of building everything from scratch. - title: Set up the initial project structure icon: code details: >- Your app has the basic structure with all necessary imports for HTTP requests, JSON parsing, and Wikipedia data. You're ready to start fetching real data from the Wikipedia API! ### Test yourself - question: "What does the \`--empty\` flag do when running \`flutter create\`?" options: - text: Creates a project with no files at all. correct: false explanation: The project still has essential files; it just uses a minimal template. - text: Creates a minimal Flutter project with less boilerplate code. correct: true explanation: "The \`--empty\` flag generates a minimal starter template without the default counter app." - text: Creates a project without any dependencies. correct: false explanation: The project still includes core Flutter dependencies. - text: Creates a project that can only run on web. correct: false explanation: The flag doesn't restrict platforms; it only affects the starter template. - question: What command is used to add a package dependency to a Flutter project? options: - text: "\`flutter install \[package\_name]\`" correct: false explanation: "The correct command uses \`pub add\`, not \`install\`." - text: "\`flutter pub add \[package\_name]\`" correct: true explanation: "Running \`flutter pub add\` adds the package to pubspec.yaml and downloads it." - text: "\`dart get \[package\_name]\`" correct: false explanation: "The command for adding packages is \`flutter pub add\` or editing pubspec.yaml." - text: "\`flutter package install \[package\_name]\`" correct: false explanation: "There is no \`flutter package\` command; use \`flutter pub add\`."

----
url: https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.24.0
----

The 2.24.0 release of the Dart and Flutter DevTools includes the following changes among other general improvements. To learn more about DevTools, check out the [DevTools overview](https://docs.flutter.dev/tools/devtools).

## General updates

[#](#general-updates)

* Improve the overall performance of DevTools tables - [#5664](https://github.com/flutter/devtools/pull/5664), [#5696](https://github.com/flutter/devtools/pull/5696)

## CPU profiler updates

[#](#cpu-profiler-updates)

* Fix bug with CPU flame chart selection and tooltips - [#5676](https://github.com/flutter/devtools/pull/5676)

## Debugger updates

[#](#debugger-updates)

* Improve support for inspecting `UserTag` and `MirrorReferent` instances - [#5490](https://github.com/flutter/devtools/pull/5490)
* Fix expression evaluation bug where selecting an autocomplete result for a field would clear the current input - [#5717](https://github.com/flutter/devtools/pull/5717)
* Make selection of a stack frame scroll to the frame location in the source code - [#5722](https://github.com/flutter/devtools/pull/5722)
* Improve performance of searching for a file and searching in a file - [#5733](https://github.com/flutter/devtools/pull/5733)
* Disable syntax highlighting for files with more than 100,000 characters due to performance constraints - [#5743](https://github.com/flutter/devtools/pull/5743)
* Fix bug where source code wasn't visible if syntax highlighting for a file was disabled - [#5743](https://github.com/flutter/devtools/pull/5743)
* Prevent file names and source code from getting out of sync - [#5827](https://github.com/flutter/devtools/pull/5827)

## Full commit history

[#](#full-commit-history)

To find a complete list of changes since the previous release, check out [the diff on GitHub](https://github.com/flutter/devtools/compare/v2.23.1...v2.24.0).

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/release-notes/release-notes-2.24.0.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.24.0\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/release-notes/release-notes-2.24.0.md "Report an issue with this page").

----
url: https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.27.0.md
----

\# DevTools 2.27.0 release notes > Release notes for Dart and Flutter DevTools version 2.27.0. The 2.27.0 release of the Dart and Flutter DevTools includes the following changes among other general improvements. To learn more about DevTools, check out the \[DevTools overview]\(https\://docs.flutter.dev/tools/devtools). ## Network profiler updates - Fix a crash with large text in network requests and responses - \[#6254]\(https\://github.com/flutter/devtools/pull/6254) !\[Example truncation of text in the network view]\(/assets/images/docs/tools/devtools/release-notes/images-2.27.0/truncation.png "Example truncation of text in the network view") ## Full commit history To find a complete list of changes since the previous release, check out \[the diff on GitHub]\(https\://github.com/flutter/devtools/compare/v2.26.1...v2.27.0).

----
url: https://docs.flutter.dev/platform-integration/macos
----

[Set up macOS development](/platform-integration/macos/setup)

[Configure your development environment to run, build, and deploy Flutter apps for macOS devices.](/platform-integration/macos/setup)

[Building macOS apps with Flutter](/platform-integration/macos/building)

[Platform-specific considerations for building for macOS with Flutter.](/platform-integration/macos/building)

[Hosting native macOS views in your Flutter app with Platform Views](/platform-integration/macos/platform-views)

[Learn how to host native macOS views in your Flutter app with Platform Views.](/platform-integration/macos/platform-views)

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/platform-integration/macos/index.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/platform-integration/macos\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/platform-integration/macos/index.md "Report an issue with this page").

----
url: https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.24.0.md
----

\# DevTools 2.24.0 release notes > Release notes for Dart and Flutter DevTools version 2.24.0. The 2.24.0 release of the Dart and Flutter DevTools includes the following changes among other general improvements. To learn more about DevTools, check out the \[DevTools overview]\(https\://docs.flutter.dev/tools/devtools). ## General updates \* Improve the overall performance of DevTools tables - \[#5664]\(https\://github.com/flutter/devtools/pull/5664), \[#5696]\(https\://github.com/flutter/devtools/pull/5696) ## CPU profiler updates \* Fix bug with CPU flame chart selection and tooltips - \[#5676]\(https\://github.com/flutter/devtools/pull/5676) ## Debugger updates \* Improve support for inspecting \`UserTag\` and \`MirrorReferent\` instances - \[#5490]\(https\://github.com/flutter/devtools/pull/5490) \* Fix expression evaluation bug where selecting an autocomplete result for a field would clear the current input - \[#5717]\(https\://github.com/flutter/devtools/pull/5717) \* Make selection of a stack frame scroll to the frame location in the source code - \[#5722]\(https\://github.com/flutter/devtools/pull/5722) \* Improve performance of searching for a file and searching in a file - \[#5733]\(https\://github.com/flutter/devtools/pull/5733) \* Disable syntax highlighting for files with more than 100,000 characters due to performance constraints - \[#5743]\(https\://github.com/flutter/devtools/pull/5743) \* Fix bug where source code wasn't visible if syntax highlighting for a file was disabled - \[#5743]\(https\://github.com/flutter/devtools/pull/5743) \* Prevent file names and source code from getting out of sync - \[#5827]\(https\://github.com/flutter/devtools/pull/5827) ## Full commit history To find a complete list of changes since the previous release, check out \[the diff on GitHub]\(https\://github.com/flutter/devtools/compare/v2.23.1...v2.24.0).

----
url: https://docs.flutter.dev/platform-integration/macos/building
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Building macOS apps with Flutter](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

This page discusses considerations unique to building macOS apps with Flutter, including shell integration and distribution of macOS apps through the Apple Store.

## Integrating with macOS look and feel

[#](#integrating-with-macos-look-and-feel)

While you can use any visual style or theme you choose to build a macOS app, you might want to adapt your app to more fully align with the macOS look and feel. Flutter includes the [Cupertino](/ui/widgets/cupertino) widget set, which provides a set of widgets for the current iOS design language. Many of these widgets, including sliders, switches and segmented controls, are also appropriate for use on macOS.

Alternatively, you might find the [macos\_ui](https://pub.dev/packages/macos_ui) package a good fit for your needs. This package provides widgets and themes that implement the macOS design language, including a `MacosWindow` frame and scaffold, toolbars, pulldown and pop-up buttons, and modal dialogs.

## Building macOS apps

[#](#building-macos-apps)

To distribute your macOS application, you can either [distribute it through the macOS App Store](https://developer.apple.com/macos/submit/), or you can distribute the `.app` itself, perhaps from your own website. You need to notarize your macOS application before distributing it outside the macOS App Store.

The first step in both of the above processes involves working with your application inside of Xcode. To be able to compile your application from inside of Xcode you first need to build the application for release using the `flutter build` command, then open the Flutter macOS Runner application.

bash

```
flutter build macos
open macos/Runner.xcworkspace
```

Once inside of Xcode, follow either Apple's [documentation on notarizing macOS Applications](https://developer.apple.com/documentation/xcode/notarizing_macos_software_before_distribution), or [on distributing an application through the App Store](https://help.apple.com/xcode/mac/current/#/dev067853c94). You should also read through the [macOS-specific support](#entitlements-and-the-app-sandbox) section below to understand how entitlements, the App Sandbox, and the Hardened Runtime impact your distributable application.

[Build and release a macOS app](/deployment/macos) provides a more detailed step-by-step walkthrough of releasing a Flutter app to the App Store.

## Entitlements and the App Sandbox

[#](#entitlements-and-the-app-sandbox)

macOS builds are configured by default to be signed, and sandboxed with App Sandbox. This means that if you want to confer specific capabilities or services on your macOS app, such as the following:

* Accessing the internet
* Capturing movies and images from the built-in camera
* Accessing files

Then you must set up specific *entitlements* in Xcode. The following section tells you how to do this.

### Setting up entitlements

[#](#setting-up-entitlements)

Managing sandbox settings is done in the `macos/Runner/*.entitlements` files. When editing these files, you shouldn't remove the original `Runner-DebugProfile.entitlements` exceptions (that support incoming network connections and JIT), as they're necessary for the `debug` and `profile` modes to function correctly.

If you're used to managing entitlement files through the **Xcode capabilities UI**, be aware that the capabilities editor updates only one of the two files or, in some cases, it creates a whole new entitlements file and switches the project to use it for all configurations. Either scenario causes issues. We recommend that you edit the files directly. Unless you have a very specific reason, you should always make identical changes to both files.

If you keep the App Sandbox enabled (which is required if you plan to distribute your application in the [App Store](https://developer.apple.com/app-store/submissions/)), you need to manage entitlements for your application when you add certain plugins or other native functionality. For instance, using the [`file_selector`](https://pub.dev/packages/file_selector) plugin requires adding either the `com.apple.security.files.user-selected.read-only` or `com.apple.security.files.user-selected.read-write` entitlement. Another common entitlement is `com.apple.security.network.client`, which you must add if you make any network requests.

Without the `com.apple.security.network.client` entitlement, for example, network requests fail with a message such as:

```
flutter: SocketException: Connection failed
(OS Error: Operation not permitted, errno = 1),
address = example.com, port = 443
```

For more information on these topics, see [App Sandbox](https://developer.apple.com/documentation/security/app_sandbox) and [Entitlements](https://developer.apple.com/documentation/bundleresources/entitlements) on the Apple Developer site.

## Hardened Runtime

[#](#hardened-runtime)

If you choose to distribute your application outside of the App Store, you need to notarize your application for compatibility with macOS. This requires enabling the Hardened Runtime option. Once you have enabled it, you need a valid signing certificate in order to build.

By default, the entitlements file allows JIT for debug builds but, as with App Sandbox, you might need to manage other entitlements. If you have both App Sandbox and Hardened Runtime enabled, you might need to add multiple entitlements for the same resource. For instance, microphone access would require both `com.apple.security.device.audio-input` (for Hardened Runtime) and `com.apple.security.device.microphone` (for App Sandbox).

For more information on this topic, see [Hardened Runtime](https://developer.apple.com/documentation/security/hardened_runtime) on the Apple Developer site.

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-09-14. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/platform-integration/macos/building.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/platform-integration/macos/building\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/platform-integration/macos/building.md "Report an issue with this page").

----
url: https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.25.0
----

The 2.25.0 release of the Dart and Flutter DevTools includes the following changes among other general improvements. To learn more about DevTools, check out the [DevTools overview](https://docs.flutter.dev/tools/devtools).

## General updates

[#](#general-updates)

* Improve DevTools tab bar navigation when the list of tabs is long - [#5875](https://github.com/flutter/devtools/pull/5875)
* Clear registered service methods between app connections - [#5960](https://github.com/flutter/devtools/pull/5960)

## Memory updates

[#](#memory-updates)

* Add legend for class types - [#5937](https://github.com/flutter/devtools/pull/5937)

* Enable sampling for Memory > Profile - [#5947](https://github.com/flutter/devtools/pull/5947)

## Full commit history

[#](#full-commit-history)

To find a complete list of changes since the previous release, check out [the diff on GitHub](https://github.com/flutter/devtools/compare/v2.24.0...v2.25.0).

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/release-notes/release-notes-2.25.0.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.25.0\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/release-notes/release-notes-2.25.0.md "Report an issue with this page").

----
url: https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.36.0.md
----

\# DevTools 2.36.0 release notes > Release notes for Dart and Flutter DevTools version 2.36.0. The 2.36.0 release of the Dart and Flutter DevTools includes the following changes among other general improvements. To learn more about DevTools, check out the \[DevTools overview]\(/tools/devtools). ## Performance updates \* Added a feature for showing widget build counts. Enable this setting to see widget build counts for each Flutter frame in the "Frame Analysis" tool, or to see an aggregate summary of these counts in the new "Rebuild Stats" tool. - \[#7838]\(https\://github.com/flutter/devtools/pull/7838), \[#7847]\(https\://github.com/flutter/devtools/pull/7847) !\[Track widget build counts setting]\(/assets/images/docs/tools/devtools/release-notes/images-2.36.0/track\_build\_counts\_setting.png "Track widget build counts setting") !\[Widget rebuild counts in the Frame Analysis view]\(/assets/images/docs/tools/devtools/release-notes/images-2.36.0/rebuild\_counts\_frame\_analysis.png "Widget rebuilds counts for a flutter frame") !\[Widget rebuild counts in the Rebuild Stats view]\(/assets/images/docs/tools/devtools/release-notes/images-2.36.0/rebuild\_stats.png "Widget rebuilds counts aggregate stats") ## Network profiler updates \* Added better support for narrow viewing windows, like when this screen is embedded in an IDE. - \[#7726]\(https\://github.com/flutter/devtools/pull/7726) ## Deep links tool updates \* Adds an error page to explain the issue when the tool fails to parse the project. - \[#7767]\(https\://github.com/flutter/devtools/pull/7767) ## DevTools Extension updates \* Fixed an issue with detecting extensions for Dart or Flutter tests. - \[#7717]\(https\://github.com/flutter/devtools/pull/7717) \* Fixed an issue with detecting extensions for nested Dart or Flutter projects. - \[#7742]\(https\://github.com/flutter/devtools/pull/7742) \* Added an example to \`package:devtools\_extensions\` that shows how to interact with the Dart Tooling Daemon from a DevTools extension. - \[#7752]\(https\://github.com/flutter/devtools/pull/7752) \* Fixed a DevTools routing bug related to disabling an extension. - \[#7791]\(https\://github.com/flutter/devtools/pull/7791) \* Fixed a bug causing a "Page Not Found" error when refreshing DevTools from an extension screen. - \[#7822]\(https\://github.com/flutter/devtools/pull/7822) \* Fixed a theming issue when extensions are embedded in an IDE - \[#7824]\(https\://github.com/flutter/devtools/pull/7824) ## Full commit history To find a complete list of changes in this release, check out the \[DevTools git log]\(https\://github.com/flutter/devtools/tree/v2.36.0).

----
url: https://docs.flutter.dev/reference/flutter-cli
----

The `flutter` command-line tool is how developers (or IDEs on behalf of developers) interact with Flutter. For Dart related commands, you can use the [`dart`](https://dart.dev/tools/dart-tool) command-line tool.

Here's how you might use the `flutter` tool to create, analyze, test, and run an app:

```
$ flutter create my_app
$ cd my_app
$ flutter analyze
$ flutter test
$ flutter run lib/main.dart
```

To run [`pub`](https://dart.dev/tools/dart-pub) commands using the `flutter` tool:

```
$ flutter pub get
$ flutter pub outdated
$ flutter pub upgrade
```

To view all commands that `flutter` supports:

```
$ flutter --help --verbose
```

To get the current version of the Flutter SDK, including its framework, engine, and tools:

```
$ flutter --version
```

## `flutter` commands

[#](#flutter-commands)

The following table shows which commands you can use with the `flutter` tool:

| Command         | Example of use                                 | More information                                                                                                |
| --------------- | ---------------------------------------------- | --------------------------------------------------------------------------------------------------------------- |
| analyze         | `flutter analyze -d <DEVICE_ID>`               | Analyzes the project's Dart source code. Use instead of [`dart analyze`](https://dart.dev/tools/dart-analyze) . |
| assemble        | `flutter assemble -o <DIRECTORY>`              | Assemble and build flutter resources.                                                                           |
| attach          | `flutter attach -d <DEVICE_ID>`                | Attach to a running application.                                                                                |
| bash-completion | `flutter bash-completion`                      | Output command line shell completion setup scripts.                                                             |
| build           | `flutter build <BUILD_TARGET>`                 | Build a target-specific bundle..                                                                                |
| channel         | `flutter channel <CHANNEL_NAME>`               | List or switch flutter channels.                                                                                |
| clean           | `flutter clean`                                | Delete the `build/` and `.dart_tool/` directories.                                                              |
| config          | `flutter config --build-dir=<DIRECTORY>`       | Configure Flutter settings. To remove a setting, configure it to an empty string.                               |
| create          | `flutter create <DIRECTORY>`                   | Creates a new project.                                                                                          |
| custom-devices  | `flutter custom-devices list`                  | Add, delete, list, and reset custom devices.                                                                    |
| devices         | `flutter devices -d <DEVICE_ID>`               | List all connected devices.                                                                                     |
| doctor          | `flutter doctor`                               | Show information about the installed tooling.                                                                   |
| downgrade       | `flutter downgrade`                            | Downgrade Flutter to the last active version for the current channel.                                           |
| drive           | `flutter drive`                                | Runs Flutter Driver tests for the current project.                                                              |
| emulators       | `flutter emulators`                            | List, launch and create emulators.                                                                              |
| gen-l10n        | `flutter gen-l10n <DIRECTORY>`                 | Generate localizations for the Flutter project.                                                                 |
| install         | `flutter install -d <DEVICE_ID>`               | Install a Flutter app on an attached device.                                                                    |
| logs            | `flutter logs`                                 | Show log output for running Flutter apps.                                                                       |
| precache        | `flutter precache <ARGUMENTS>`                 | Populates the Flutter tool's cache of binary artifacts.                                                         |
| pub             | `flutter pub <PUB_COMMAND>`                    | Works with packages. Use instead of [`dart pub`](https://dart.dev/tools/dart-pub) .                             |
| run             | `flutter run <DART_FILE>`                      | Runs a Flutter program.                                                                                         |
| screenshot      | `flutter screenshot`                           | Take a screenshot of a Flutter app from a connected device.                                                     |
| symbolize       | `flutter symbolize --input=<STACK_TRACK_FILE>` | Symbolize a stack trace from the AOT compiled flutter application.                                              |
| test            | `flutter test [<DIRECTORYDART_FILE>]`          | Runs tests in this package. Use instead of [`dart test`](https://dart.dev/tools/dart-test) .                    |
| upgrade         | `flutter upgrade`                              | Upgrade your copy of Flutter.                                                                                   |

For additional help on any of the commands, enter `flutter help <command>` or follow the links in the **More information** column. You can also get details on `pub` commands — for example, `flutter help pub outdated`.

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/reference/flutter-cli.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/reference/flutter-cli\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/reference/flutter-cli.md "Report an issue with this page").

----
url: https://docs.flutter.dev/security
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Security](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

The Flutter team takes the security of Flutter and the applications created with it seriously. This page describes how to report any vulnerabilities you might find, and lists best practices to minimize the risk of introducing a vulnerability.

## Security philosophy

[#](#security-philosophy)

Flutter security strategy is based on five key pillars:

* **Identify**: Track and prioritize key security risks by identifying core assets, key threats, and vulnerabilities.
* **Detect**: Detect and identify vulnerabilities using techniques and tools like vulnerability scanning, static application security testing, and fuzzing.
* **Protect**: Eliminate risks by mitigating known vulnerabilities and protect critical assets against source threats.
* **Respond**: Define processes to report, triage, and respond to vulnerabilities or attacks.
* **Recover**: Build capabilities to contain and recover from an incident with minimal impact.

## Reporting vulnerabilities

[#](#reporting-vulnerabilities)

Before reporting a security vulnerability found by a static analysis tool, consider checking our list of [known false positives](/reference/security-false-positives).

Report security vulnerabilities to <https://g.co/vulnz> and include a description of the issue, the steps you took to create the issue, affected versions, and if known, mitigations for the issue. We use g.co/vulnz for our intake, and do coordination and disclosure on GitHub (including using GitHub Security Advisory). The Google Security Team will respond within 5 working days of your report on g.co/vulnz.

You may also reach out to the team via our public Discord chat channels; however, please also make sure to make vulnerability reports to g.co/vulnz, and avoid revealing information about vulnerabilities in public if that could put users at risk.

You should expect a close collaboration as we work to resolve the security vulnerability you have reported. Please reach out to security\@flutter.dev only if you do not receive a response to a g.co/vulnz report within the above mentioned 5 working days.

For more details on how we handle security vulnerabilities, see our [security policy](https://github.com/flutter/flutter/security/policy).

## Flagging existing issues as security-related

[#](#flagging-existing-issues-as-security-related)

If you believe that an existing GitHub issue is security-related, we ask that you both report the issue to g.co/vulnz and send an email to security\@flutter.dev. The email should include the GitHub issue ID and a short description of why it should be handled according to this security policy.

Security reports are not tracked explicitly in the GitHub issue database. We use GitHub's security advisory feature to track open security reports.

## Supported versions

[#](#supported-versions)

We commit to publishing security updates for the version of Flutter currently on the `stable` branch.

## Expectations

[#](#expectations)

We treat security reports equivalent to a P0 priority level. This means that we attempt to fix them as quickly as possible. Depending on our release schedule, we will release either a new beta or a stable hotfix for any major security report found in the most recent stable version of our SDK, whichever is most expedient.

Any vulnerability reported for flutter websites like docs.flutter.dev doesn't require a release and will be fixed in the website itself.

## Bug Bounty programs

[#](#bug-bounty-programs)

Non-Google teams that use or contribute to Flutter are also welcome to include Flutter within the scope of their bug bounty programs. To have your program listed, please contact `security@flutter.dev`.

Google considers Flutter to be in scope for the [Google Open Source Software Vulnerability Reward Program](https://bughunters.google.com/open-source-security).

## Receiving security updates

[#](#receiving-security-updates)

The best way to receive security updates is to subscribe to the [flutter-announce](https://groups.google.com/forum/#!forum/flutter-announce) mailing list or watch updates to the [Discord channel](https://discord.gg/BS8KZyg). We also announce security updates in the technical release blog post.

## Best practices

[#](#best-practices)

* **Keep current with the latest Flutter SDK releases.** We regularly update Flutter, and these updates might fix security defects discovered in previous versions.

* **Keep your application's dependencies up to date.** Make sure you [upgrade your package dependencies](/install/upgrade) to keep the dependencies up to date. Avoid pinning to specific versions for your dependencies and, if you do, make sure you check periodically to see if your dependencies have had security updates, and update the pin accordingly.

* **Keep your copy of Flutter up to date.** Private, customized versions of Flutter tend to fall behind the current version and might not include important security fixes and enhancements. Instead, routinely update your copy of Flutter. If you're making changes to improve Flutter, be sure to update your fork and consider sharing your changes with the community.

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-07-31. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/security/index.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/security\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/security/index.md "Report an issue with this page").

----
url: https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.7.0
----

The 2.7.0 release of the Dart and Flutter DevTools includes the following changes among other general improvements. To learn more about DevTools, check out the [DevTools overview](/tools/devtools).

## General updates

[#](#general-updates)

* Improvements for initial page load time - [#3309](https://github.com/flutter/devtools/pull/3309)
* Fix a couple scrollbar-related issues - [#3393](https://github.com/flutter/devtools/pull/3393), [#3401](https://github.com/flutter/devtools/pull/3401)

## Debugger updates

[#](#debugger-updates)

* Add an open file dialog (ctrl / cmd + p) - [#3342](https://github.com/flutter/devtools/pull/3342), [#3354](https://github.com/flutter/devtools/pull/3354), [#3371](https://github.com/flutter/devtools/pull/3371), [#3384](https://github.com/flutter/devtools/pull/3384)

* Add a copy button to the call stack view - [#3334](https://github.com/flutter/devtools/pull/3334)

## CPU profiler updates

[#](#cpu-profiler-updates)

* Added functionality to load an app startup profile for Flutter apps. This profile will contain CPU samples from the initialization of the Dart VM up until the first Flutter frame has been rendered - [#3357](https://github.com/flutter/devtools/pull/3357)

  When the app startup profile has been loaded, you will see that the "AppStartUp" user tag is selected for the profile. You can also load the app startup profile by selecting this user tag filter, when present, in the list of available user tags.

* Added multi-isolate support. Select which isolate you want to profile from the isolate selector at the bottom of the page - [#3362](https://github.com/flutter/devtools/pull/3362)

* Add class names to CPU stack frames in the profiler - [#3385](https://github.com/flutter/devtools/pull/3385)

## Full commit history

[#](#full-commit-history)

To find a complete list of changes since the previous release, check out [the diff on GitHub](https://github.com/flutter/devtools/compare/v2.6.0...v2.7.0).

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/release-notes/release-notes-2.7.0.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.7.0\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/release-notes/release-notes-2.7.0.md "Report an issue with this page").

----
url: https://docs.flutter.dev/learn/pathway/tutorial
----

Welcome to the Flutter tutorial! This tutorial teaches you how to build applications from scratch that run on mobile, desktop, and web.

You'll start from the very beginning: creating a blank Flutter application. By the end, you'll have built a handful of small apps that demonstrate the critical features of Flutter development (and more!)

### Before you begin

[#](#before-you-begin)

This tutorial is the third step in the Flutter learning pathway, and therefor assumes that you have:

1. A Flutter environment set up
2. Basic understanding of the Dart programming language

If either of those aren't true, please start at the [Learning pathway page](/learn/pathway).

## Contents

[#](#contents)

1. Quick install
   1. [Install Flutter](/learn/pathway/quick-install)

2. Introduction to Flutter UI

   1. [Tutorial introduction](/learn/pathway/tutorial)
   2. [Create a Flutter app](/learn/pathway/tutorial/create-an-app)
   3. [Widget fundamentals](/learn/pathway/tutorial/widget-fundamentals)
   4. [Layout widgets on a screen](/learn/pathway/tutorial/layout)
   5. [DevTools](/learn/pathway/tutorial/devtools)
   6. [Handle user input](/learn/pathway/tutorial/user-input)
   7. [Learn about stateful widgets](/learn/pathway/tutorial/stateful-widget)
   8. [Add implicit animations](/learn/pathway/tutorial/implicit-animations)

3. State in Flutter apps

   1. [The state management project](/learn/pathway/tutorial/set-up-state-project)
   2. [Make Http Requests](/learn/pathway/tutorial/http-requests)
   3. [Use `ChangeNotifier` to update app state](/learn/pathway/tutorial/change-notifier)
   4. [Use `ListenableBuilder` to update app UI](/learn/pathway/tutorial/listenable-builder)

4. Flutter UI 102

   1. [Advanced UI features](/learn/pathway/tutorial/advanced-ui)
   2. [Adaptive layouts](/learn/pathway/tutorial/adaptive-layout)
   3. [Scrolling and slivers](/learn/pathway/tutorial/slivers)
   4. [Stack based navigation](/learn/pathway/tutorial/navigation)

5. How Flutter works
   1. [How Flutter works](/learn/pathway/how-flutter-works)

[Start learning](/learn/pathway/tutorial/create-an-app)

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/learn/pathway/tutorial/index.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/learn/pathway/tutorial\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/learn/pathway/tutorial/index.md "Report an issue with this page").

----
url: https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.18.0.md
----

\# DevTools 2.18.0 release notes > Release notes for Dart and Flutter DevTools version 2.18.0. The 2.18.0 release of the Dart and Flutter DevTools includes the following changes among other general improvements. To learn more about DevTools, check out the \[DevTools overview]\(https\://docs.flutter.dev/tools/devtools). ## Inspector updates - Auto scrolling behavior improved when snapping a widget into focus - \[#4283]\(https\://github.com/flutter/devtools/pull/4283) - Fix issue where widget inspector wouldn't load when connecting to a paused app - \[#4527]\(https\://github.com/flutter/devtools/pull/4527) - Improve widget inspector hover cards to show progress while waiting for data - \[#4488]\(https\://github.com/flutter/devtools/pull/4488) ## Performance updates - Fix issue where scrollbar would go out of sync with the frame content - \[#4503]\(https\://github.com/flutter/devtools/pull/4503) - Add offline support for raster stats - \[#4491]\(https\://github.com/flutter/devtools/pull/4491) - Add 'Rendering time' column to Raster Metrics tab - \[#4474]\(https\://github.com/flutter/devtools/pull/4474) !\[render-time-column]\(/assets/images/docs/tools/devtools/release-notes/images-2.18.0/render-time-column.png "Rendering time column in the Raster Metrics tab") ## CPU profiler updates - Fix crash when an empty frame is filtered - \[#4502]\(https\://github.com/flutter/devtools/pull/4502) - Fix bugs in CPU profile trees - \[#4413]\(https\://github.com/flutter/devtools/pull/4413) - UI Cleanup - \[#4404]\(https\://github.com/flutter/devtools/pull/4404) ## Memory updates - Add Profile and Allocation Tracing sub-tabs - \[#4523]\(https\://github.com/flutter/devtools/pull/4523) !\[profile]\(/assets/images/docs/tools/devtools/release-notes/images-2.18.0/profile.png "Profile in Memory tab") !\[allocation-tracing]\(/assets/images/docs/tools/devtools/release-notes/images-2.18.0/allocation-tracing.png "Allocation Tracing in Memory tab") - Implement snapshot visualization - \[#4473]\(https\://github.com/flutter/devtools/pull/4473) ## Debugger updates - Fix bug for file opener and search - \[#4525]\(https\://github.com/flutter/devtools/pull/4525) - Fix the code view's scrollable area - \[#4448]\(https\://github.com/flutter/devtools/pull/4448) - Allow syntax highlighting on nested captures in parser - \[#4427]\(https\://github.com/flutter/devtools/pull/4427) ## Network profiler updates - When on the Network tab, network recordings now continue working after the app hot restarts - \[#4438]\(https\://github.com/flutter/devtools/pull/4438) ## Logging updates - Log messages from non-stdout sources are now shown - \[#4487]\(https\://github.com/flutter/devtools/pull/4487) ## Full commit history To find a complete list of changes since the previous release, check out \[the diff on GitHub]\(https\://github.com/flutter/devtools/compare/v2.17.0...v2.18.0).

----
url: https://docs.flutter.dev/cookbook/plugins/google-mobile-ads
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Add ads to your mobile Flutter app or game](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

Many developers use advertising to monetize their mobile apps and games. This allows their app to be downloaded free of charge, which improves the app's popularity.

To add ads to your Flutter project, use [AdMob](https://admob.google.com/home/), Google's mobile advertising platform. This recipe demonstrates how to use the [`google_mobile_ads`](https://pub.dev/packages/google_mobile_ads) package to add a banner ad to your app or game.

## 1. Get AdMob App IDs

[#](#1-get-admob-app-ids)

1. Go to [AdMob](https://admob.google.com/) and set up an account. This could take some time because you need to provide banking information, sign contracts, and so on.

2. With the AdMob account ready, create two *Apps* in AdMob: one for Android and one for iOS.

3. Open the **App settings** section.

4. Get the AdMob *App IDs* for both the Android app and the iOS app. They resemble `ca-app-pub-1234567890123456~1234567890`. Note the tilde (`~`) between the two numbers.

## 2. Platform-specific setup

[#](#2-platform-specific-setup)

Update your Android and iOS configurations to include your App IDs.

### Android

[#](#android)

Add your AdMob app ID to your Android app.

1. Open the app's `android/app/src/main/AndroidManifest.xml` file.

2. Add a new `<meta-data>` tag.

3. Set the `android:name` element with a value of `com.google.android.gms.ads.APPLICATION_ID`.

4. Set the `android:value` element with the value to your own AdMob app ID that you got in the previous step. Include them in quotes as shown:

   xml

   ```
   <manifest>
       <application>
           ...
   ​
           <!-- Sample AdMob app ID: ca-app-pub-3940256099942544~3347511713 -->
           <meta-data
               android:name="com.google.android.gms.ads.APPLICATION_ID"
               android:value="ca-app-pub-xxxxxxxxxxxxxxxx~yyyyyyyyyy"/>
       </application>
   </manifest>
   ```

### iOS

[#](#ios)

Add your AdMob app ID to your iOS app.

1. Open your app's `ios/Runner/Info.plist` file.

2. Enclose `GADApplicationIdentifier` with a `key` tag.

3. Enclose your AdMob app ID with a `string` tag. You created this AdMob App ID in [step 1](#1-get-admob-app-ids).

   xml

   ```
   <key>GADApplicationIdentifier</key>
   <string>ca-app-pub-################~##########</string>
   ```

## 3. Add the `google_mobile_ads` plugin

[#](#3-add-the-google-mobile-ads-plugin)

To add the `google_mobile_ads` plugin as a dependency, run `flutter pub add`:

```
$ flutter pub add google_mobile_ads
```

## 4. Initialize the Mobile Ads SDK

[#](#4-initialize-the-mobile-ads-sdk)

You need to initialize the Mobile Ads SDK before loading ads.

1. Call `MobileAds.instance.initialize()` to initialize the Mobile Ads SDK.

   dart

   ```
   void main() {
     WidgetsFlutterBinding.ensureInitialized();
     unawaited(MobileAds.instance.initialize());
   ​
     runApp(const MyApp());
   }
   ```

Run the initialization step at startup, as shown above, so that the AdMob SDK has enough time to initialize before it is needed.

## 5. Load a banner ad

[#](#5-load-a-banner-ad)

To show an ad, you need to request it from AdMob.

To load a banner ad, construct a `BannerAd` instance, and call `load()` on it.

dart

```
/// Loads a banner ad.
void _loadAd() {
  final bannerAd = BannerAd(
    size: widget.adSize,
    adUnitId: widget.adUnitId,
    request: const AdRequest(),
    listener: BannerAdListener(
      // Called when an ad is successfully received.
      onAdLoaded: (ad) {
        if (!mounted) {
          ad.dispose();
          return;
        }
        setState(() {
          _bannerAd = ad as BannerAd;
        });
      },
      // Called when an ad request failed.
      onAdFailedToLoad: (ad, error) {
        debugPrint('BannerAd failed to load: $error');
        ad.dispose();
      },
    ),
  );
​
  // Start loading.
  bannerAd.load();
}
```

To view a complete example, check out the last step of this recipe.

## 6. Show banner ad

[#](#6-show-banner-ad)

Once you have a loaded instance of `BannerAd`, use `AdWidget` to show it.

dart

```
AdWidget(ad: _bannerAd)
```

It's a good idea to wrap the widget in a `SafeArea` (so that no part of the ad is obstructed by device notches) and a `SizedBox` (so that it has its specified, constant size before and after loading).

dart

```
@override
Widget build(BuildContext context) {
  return SafeArea(
    child: SizedBox(
      width: widget.adSize.width.toDouble(),
      height: widget.adSize.height.toDouble(),
      child: _bannerAd == null
          // Nothing to render yet.
          ? const SizedBox()
          // The actual ad.
          : AdWidget(ad: _bannerAd!),
    ),
  );
}
```

You must dispose of an ad when you no longer need to access it. The best practice for when to call `dispose()` is either after the `AdWidget` is removed from the widget tree or in the `BannerAdListener.onAdFailedToLoad()` callback.

dart

```
_bannerAd?.dispose();
```

## 7. Configure ads

[#](#7-configure-ads)

To show anything beyond test ads, you have to register ad units.

1. Open [AdMob](https://admob.google.com/).

2. Create an *Ad unit* for each of the AdMob apps.

   This asks for the Ad unit's format. AdMob provides many formats beyond banner ads --- interstitials, rewarded ads, app open ads, and so on. The API for those is similar, and documented in the [AdMob documentation](https://developers.google.com/admob/flutter/quick-start) and through [official samples](https://github.com/googleads/googleads-mobile-flutter/tree/main/samples/admob).

3. Choose banner ads.

4. Get the *Ad unit IDs* for both the Android app and the iOS app. You can find these in the **Ad units** section. They look something like `ca-app-pub-1234567890123456/1234567890`. The format resembles the *App ID* but with a slash (`/`) between the two numbers. This distinguishes an *Ad unit ID* from an *App ID*.

5. Add these *Ad unit IDs* to the constructor of `BannerAd`, depending on the target app platform.

   dart

   ```
   final String adUnitId = Platform.isAndroid
       // Use this ad unit on Android...
       ? 'ca-app-pub-3940256099942544/6300978111'
       // ... or this one on iOS.
       : 'ca-app-pub-3940256099942544/2934735716';
   ```

## 8. Final touches

[#](#8-final-touches)

To display the ads in a published app or game (as opposed to debug or testing scenarios), your app must meet additional requirements:

1. Your app must be reviewed and approved before it can fully serve ads. Follow AdMob's [app readiness guidelines](https://support.google.com/admob/answer/10564477). For example, your app must be listed on at least one of the supported stores such as Google Play Store or Apple App Store.

2. You must [create an `app-ads.txt`](https://support.google.com/admob/answer/9363762) file and publish it on your developer website.

To learn more about app and game monetization, visit the official sites of [AdMob](https://admob.google.com/) and [Ad Manager](https://admanager.google.com/).

## 9. Complete example

[#](#9-complete-example)

The following code implements a simple stateful widget that loads a banner ad and shows it.

dart

```
import 'dart:io';
​
import 'package:flutter/widgets.dart';
import 'package:google_mobile_ads/google_mobile_ads.dart';
​
class MyBannerAdWidget extends StatefulWidget {
  /// The requested size of the banner. Defaults to [AdSize.banner].
  final AdSize adSize;
​
  /// The AdMob ad unit to show.
  ///
  /// TODO: replace this test ad unit with your own ad unit
  final String adUnitId = Platform.isAndroid
      // Use this ad unit on Android...
      ? 'ca-app-pub-3940256099942544/6300978111'
      // ... or this one on iOS.
      : 'ca-app-pub-3940256099942544/2934735716';
​
  MyBannerAdWidget({super.key, this.adSize = AdSize.banner});
​
  @override
  State<MyBannerAdWidget> createState() => _MyBannerAdWidgetState();
}
​
class _MyBannerAdWidgetState extends State<MyBannerAdWidget> {
  /// The banner ad to show. This is `null` until the ad is actually loaded.
  BannerAd? _bannerAd;
​
  @override
  Widget build(BuildContext context) {
    return SafeArea(
      child: SizedBox(
        width: widget.adSize.width.toDouble(),
        height: widget.adSize.height.toDouble(),
        child: _bannerAd == null
            // Nothing to render yet.
            ? const SizedBox()
            // The actual ad.
            : AdWidget(ad: _bannerAd!),
      ),
    );
  }
​
  @override
  void initState() {
    super.initState();
    _loadAd();
  }
​
  @override
  void dispose() {
    _bannerAd?.dispose();
    super.dispose();
  }
​
  /// Loads a banner ad.
  void _loadAd() {
    final bannerAd = BannerAd(
      size: widget.adSize,
      adUnitId: widget.adUnitId,
      request: const AdRequest(),
      listener: BannerAdListener(
        // Called when an ad is successfully received.
        onAdLoaded: (ad) {
          if (!mounted) {
            ad.dispose();
            return;
          }
          setState(() {
            _bannerAd = ad as BannerAd;
          });
        },
        // Called when an ad request failed.
        onAdFailedToLoad: (ad, error) {
          debugPrint('BannerAd failed to load: $error');
          ad.dispose();
        },
      ),
    );
​
    // Start loading.
    bannerAd.load();
  }
}
```

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-09-02. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/plugins/google-mobile-ads.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/cookbook/plugins/google-mobile-ads\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/plugins/google-mobile-ads.md "Report an issue with this page").

----
url: https://docs.flutter.dev/ui/design/graphics
----

[Writing and using fragment shaders](/ui/design/graphics/fragment-shaders)

[How to author and use fragment shaders to create custom visual effects in your Flutter app.](/ui/design/graphics/fragment-shaders)

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/ui/design/graphics/index.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/ui/design/graphics\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/ui/design/graphics/index.md "Report an issue with this page").

----
url: https://docs.flutter.dev/resources/games-toolkit
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Casual Games Toolkit](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

The Flutter Casual Games Toolkit pulls together new and existing resources so you can accelerate development of games on mobile platforms.

This page outlines where you can find these available resources.

## Why Flutter for games?

[#](#why-flutter-for-games)

The Flutter framework can create performant apps for six target platforms from the desktop to mobile devices to the web.

With Flutter's benefits of cross-platform development, performance, and open source licensing, it makes a great choice for games.

Casual games fall into two categories: turn-based games and real-time games. You might be familiar with both types of games, though perhaps you didn't think about them in quite this way.

*Turn-based games* cover games meant for a mass market with simple rules and gameplay. This includes board games, card games, puzzles, and strategy games. These games respond to simple user input, like tapping on a card or entering a number or letter. These games are well suited for Flutter.

*Real-time games* cover games with a series of actions that require real-time responses. These include endless runner games, racing games, and so on. You might want to create a game with advanced features like collision detection, camera views, game loops, and the like. These types of games could use an open source game engine like the [Flame game engine](https://flame-engine.org/) built using Flutter.

## What's included in the toolkit

[#](#whats-included-in-the-toolkit)

The Casual Games Toolkit provides the following free resources.

* A repository that includes three game templates that provide a starting point for building a casual game.

  1. A [base game template](https://github.com/flutter/games/tree/main/templates/basic) that includes the basics for:

     * Main menu
     * Navigation
     * Settings
     * Level selection
     * Player progress
     * Play session management
     * Sound
     * Themes

  2. A [card game template](https://github.com/flutter/games/tree/main/templates/card) that includes everything in the base template plus:

     * Drag and drop
     * Game state management
     * Multiplayer integration hooks

  3. An [endless runner template](https://github.com/flutter/games/tree/main/templates/endless_runner) created in partnership with the open source game engine, Flame. It implements:

     * A FlameGame base template
     * Player steering
     * Collision detection
     * Parallax effects
     * Spawning
     * Different visual effects

  4. A sample game built on top of the endless runner template, called SuperDash. You can play the game on iOS, Android, or [web](https://superdash.flutter.dev/), [view the open source code repo](https://github.com/flutter/super_dash), or [read how the game was created in 6 weeks](https://blog.flutter.dev/how-we-built-the-new-super-dash-demo-in-flutter-and-flame-in-just-six-weeks-9c7aa2a5ad31).

* Developer guides for integrating needed services.

* A link to a [Flame Discord](https://discord.gg/qUyQFVbV45) channel. If you have a Discord account, use this [direct link](https://discord.com/login?redirect_to=%2Fchannels%2F509714518008528896%2F788415774938103829).

The included game templates and cookbook recipes make certain choices to accelerate development. They include specific packages, like `provider`, `google_mobile_ads`, `in_app_purchase`, `audioplayers`, `crashlytics`, and `games_services`. If you prefer other packages, you can change the code to use them.

The Flutter team understands that monetization might be a future consideration. Cookbook recipes for advertising and in-app purchases have been added.

As explained on the [Games](https://flutter.dev/games) page, you can leverage up to $900 in offers when you integrate Google services, such as [Cloud, Firebase](https://cloud.google.com/free), and [Ads](https://ads.google.com/intl/en_us/home/flutter/#!/), into your game.

## Get started

[#](#get-started)

Are you ready? To get started:

1. If you haven't done so, [install Flutter](/install).

2. [Clone the games repo](https://github.com/flutter/games).

3. Review the `README` file for the first type of game you want to create.

   * [basic game](https://github.com/flutter/games/blob/main/templates/basic/README.md)
   * [card game](https://github.com/flutter/games/blob/main/templates/card/README.md)
   * [runner game](https://github.com/flutter/games/blob/main/templates/endless_runner/README.md)

4. [Join the Flame community on Discord](https://discord.gg/qUyQFVbV45) (use the [direct link](https://discord.com/login?redirect_to=%2Fchannels%2F509714518008528896%2F788415774938103829) if you already have a Discord account).

5. Review the codelabs and cookbook recipes.

   * Build a [multiplayer game](/cookbook/games/firestore-multiplayer) with Cloud Firestore.
   * Build a [word puzzle](https://codelabs.developers.google.com/codelabs/flutter-word-puzzle) with Flutter.
   * Make your games more engaging with [leaderboards and achievements](/cookbook/games/achievements-leaderboard).
   * Monetize your games with [in-game ads](/cookbook/plugins/google-mobile-ads) and [in-app purchases](https://codelabs.developers.google.com/codelabs/flutter-in-app-purchases#0).
   * Add user authentication flow to your game with [Firebase Authentication](https://firebase.google.com/codelabs/firebase-auth-in-flutter-apps#0).
   * Collect analytics about crashes and errors inside your game with [Firebase Crashlytics](https://firebase.google.com/docs/crashlytics/get-started?platform=flutter).

6. Set up accounts on AdMob, Firebase, and Cloud, as needed.

7. Write your game!

8. Deploy to both the Google Play and Apple stores.

## Example games

[#](#example-games)

For Google I/O 2022, both the Flutter team and Very Good Ventures created new games.

* VGV created the [I/O Pinball game](https://pinball.flutter.dev/#/) using the Flame engine. To learn about this game, read [I/O Pinball Powered by Flutter and Firebase](https://flutter.dev/blog/i-o-pinball-powered-by-flutter-and-firebase) on the Flutter blog and [play the game](https://pinball.flutter.dev/#/) in your browser.

* The Flutter team created [I/O Flip](https://flip.withgoogle.com/#/), a virtual [CCG](https://en.wikipedia.org/wiki/Collectible_card_game). To learn more about I/O Flip, check out [How It's Made: I/O FLIP adds a twist to a classic card game with generative AI](https://developers.googleblog.com/2023/05/how-its-made-io-flip-adds-twist-to.html) on the Google Developers blog and [play the game](https://flip.withgoogle.com/#/) in your browser.

## Other resources

[#](#other-resources)

Once you feel ready to go beyond these games templates, investigate other resources that our community recommended.

| Feature                     | Resources                                                                                                                                                                                                                                                                                                                                                                                                                            |
| --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Animation and sprites       | book\_5 [Special effects](/cookbook/effects) handyman [Spriter Pro](https://store.steampowered.com/app/332360/Spriter_Pro/) package\_2 [rive](https://pub.dev/packages/rive)                                                                                                                                                                                                                                                         |
| App review                  | package\_2 [app\_review](https://pub.dev/packages/app_review)                                                                                                                                                                                                                                                                                                                                                                        |
| Audio                       | package\_2 [audioplayers](https://pub.dev/packages/audioplayers) package\_2 [flutter\_soloud](https://pub.dev/packages/flutter_soloud)                                                                                                                                                                                                                                                                                               |
| Authentication              | science [User Authentication using Firebase](https://firebase.google.com/codelabs/firebase-auth-in-flutter-apps#0)                                                                                                                                                                                                                                                                                                                   |
| Cloud services              | science [Add Firebase to your Flutter game](https://firebase.google.com/docs/flutter/setup)                                                                                                                                                                                                                                                                                                                                          |
| Debugging                   | quick\_reference\_all [Firebase Crashlytics overview](https://firebase.google.com/docs/crashlytics/get-started?platform=flutter) package\_2 [firebase\_crashlytics](https://pub.dev/packages/firebase_crashlytics)                                                                                                                                                                                                                   |
| Drivers                     | package\_2 [win32\_gamepad](https://pub.dev/packages/win32_gamepad)                                                                                                                                                                                                                                                                                                                                                                  |
| Game assets and asset tools | photo\_album [CraftPix](https://craftpix.net) photo\_album [Game Developer Studio](https://www.gamedeveloperstudio.com) handyman [GIMP](https://www.gimp.org)                                                                                                                                                                                                                                                                        |
| Game engines                | package\_2 [Flame](https://pub.dev/packages/flame) package\_2 [Bonfire](https://pub.dev/packages/bonfire) package\_2 [forge2d](https://pub.dev/packages/forge2d)                                                                                                                                                                                                                                                                     |
| Game features               | book\_5 [Add achievements and leaderboards to your game](/cookbook/games/achievements-leaderboard) book\_5 [Add multiplayer support to your game](/cookbook/games/firestore-multiplayer)                                                                                                                                                                                                                                             |
| Game services integration   | package\_2 [games\_services](https://pub.dev/packages/games_services)                                                                                                                                                                                                                                                                                                                                                                |
| Level editor                | handyman [Tiled](https://www.mapeditor.org/)                                                                                                                                                                                                                                                                                                                                                                                         |
| Monetization                | book\_5 [Add advertising to your Flutter game](/cookbook/plugins/google-mobile-ads) science [Add AdMob ads to a Flutter app](https://codelabs.developers.google.com/codelabs/admob-ads-in-flutter) science [Add in-app purchases to your Flutter app](https://codelabs.developers.google.com/codelabs/flutter-in-app-purchases#0) science [Gaming UX and Revenue Optimizations for Apps](https://flutter.dev/go/games-revenue) (PDF) |
| Persistence                 | package\_2 [shared\_preferences](https://pub.dev/packages/shared_preferences) package\_2 [sqflite](https://pub.dev/packages/sqflite) package\_2 [cbl\_flutter](https://pub.dev/packages/cbl_flutter) (Couchbase Lite)                                                                                                                                                                                                                |
| Special effects             | api [Paint API](https://api.flutter.dev/flutter/dart-ui/Paint-class.html) book\_5 [Special effects](/cookbook/effects)                                                                                                                                                                                                                                                                                                               |
| User Experience             | quick\_reference\_all [Best practices for optimizing Flutter web loading speed](https://blog.flutter.dev/best-practices-for-optimizing-flutter-web-loading-speed-7cc0df14ce5c)                                                                                                                                                                                                                                                       |

## Other resources

[#](#other-resources-1)

Check out the following videos:

* [Building multiplatform games with Flutter](https://www.youtube.com/watch?v=7mG_sW40tsw), a talk given at the [Game Developer Conference (GDC)](https://gdconf.com/) 2024.
* [How to build a physics-based game with Flutter and Flame's Forge2D](https://www.youtube.com/watch?v=nsnQJrYHHNQ), from Google I/O 2024.

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-07-31. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/resources/games-toolkit.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/resources/games-toolkit\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/resources/games-toolkit.md "Report an issue with this page").

----
url: https://docs.flutter.dev/resources/inside-flutter
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Inside Flutter](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

This document describes the inner workings of the Flutter toolkit that make Flutter's API possible. Because Flutter widgets are built using aggressive composition, user interfaces built with Flutter have a large number of widgets. To support this workload, Flutter uses sublinear algorithms for layout and building widgets as well as data structures that make tree surgery efficient and that have a number of constant-factor optimizations. With some additional details, this design also makes it easy for developers to create infinite scrolling lists using callbacks that build exactly those widgets that are visible to the user.

## Aggressive composability

[#](#aggressive-composability)

One of the most distinctive aspects of Flutter is its *aggressive composability*. Widgets are built by composing other widgets, which are themselves built out of progressively more basic widgets. For example, `Padding` is a widget rather than a property of other widgets. As a result, user interfaces built with Flutter consist of many, many widgets.

The widget building recursion bottoms out in `RenderObjectWidgets`, which are widgets that create nodes in the underlying *render* tree. The render tree is a data structure that stores the geometry of the user interface, which is computed during *layout* and used during *painting* and *hit testing*. Most Flutter developers do not author render objects directly but instead manipulate the render tree using widgets.

In order to support aggressive composability at the widget layer, Flutter uses a number of efficient algorithms and optimizations at both the widget and render tree layers, which are described in the following subsections.

### Sublinear layout

[#](#sublinear-layout)

With a large number of widgets and render objects, the key to good performance is efficient algorithms. Of paramount importance is the performance of *layout*, which is the algorithm that determines the geometry (for example, the size and position) of the render objects. Some other toolkits use layout algorithms that are O(N²) or worse (for example, fixed-point iteration in some constraint domain). Flutter aims for linear performance for initial layout, and *sublinear layout performance* in the common case of subsequently updating an existing layout. Typically, the amount of time spent in layout should scale more slowly than the number of render objects.

Flutter performs one layout per frame, and the layout algorithm works in a single pass. *Constraints* are passed down the tree by parent objects calling the layout method on each of their children. The children recursively perform their own layout and then return *geometry* up the tree by returning from their layout method. Importantly, once a render object has returned from its layout method, that render object will not be visited again[1](#fn-1) until the layout for the next frame. This approach combines what might otherwise be separate measure and layout passes into a single pass and, as a result, each render object is visited *at most twice*[2](#fn-2) during layout: once on the way down the tree, and once on the way up the tree.

Flutter has several specializations of this general protocol. The most common specialization is `RenderBox`, which operates in two-dimensional, cartesian coordinates. In box layout, the constraints are a min and max width and a min and max height. During layout, the child determines its geometry by choosing a size within these bounds. After the child returns from layout, the parent decides the child's position in the parent's coordinate system[3](#fn-3). Note that the child's layout cannot depend on its position, as the position is not determined until after the child returns from the layout. As a result, the parent is free to reposition the child without needing to recompute its layout.

More generally, during layout, the *only* information that flows from parent to child are the constraints and the *only* information that flows from child to parent is the geometry. These invariants can reduce the amount of work required during layout:

* If the child has not marked its own layout as dirty, the child can return immediately from layout, cutting off the walk, as long as the parent gives the child the same constraints as the child received during the previous layout.

* Whenever a parent calls a child's layout method, the parent indicates whether it uses the size information returned from the child. If, as often happens, the parent does not use the size information, then the parent need not recompute its layout if the child selects a new size because the parent is guaranteed that the new size will conform to the existing constraints.

* *Tight* constraints are those that can be satisfied by exactly one valid geometry. For example, if the min and max widths are equal to each other and the min and max heights are equal to each other, the only size that satisfies those constraints is one with that width and height. If the parent provides tight constraints, then the parent need not recompute its layout whenever the child recomputes its layout, even if the parent uses the child's size in its layout, because the child cannot change size without new constraints from its parent.

* A render object can declare that it uses the constraints provided by the parent only to determine its geometry. Such a declaration informs the framework that the parent of that render object does not need to recompute its layout when the child recomputes its layout *even if the constraints are not tight* and *even if the parent's layout depends on the child's size*, because the child cannot change size without new constraints from its parent.

As a result of these optimizations, when the render object tree contains dirty nodes, only those nodes and a limited part of the subtree around them are visited during layout.

### Sublinear widget building

[#](#sublinear-widget-building)

Similar to the layout algorithm, Flutter's widget building algorithm is sublinear. After being built, the widgets are held by the *element tree*, which retains the logical structure of the user interface. The element tree is necessary because the widgets themselves are *immutable*, which means (among other things), they cannot remember their parent or child relationships with other widgets. The element tree also holds the *state* objects associated with stateful widgets.

In response to user input (or other stimuli), an element can become dirty, for example if the developer calls `setState()` on the associated state object. The framework keeps a list of dirty elements and jumps directly to them during the *build* phase, skipping over clean elements. During the build phase, information flows *unidirectionally* down the element tree, which means each element is visited at most once during the build phase. Once cleaned, an element cannot become dirty again because, by induction, all its ancestor elements are also clean[4](#fn-4).

Because widgets are *immutable*, if an element has not marked itself as dirty, the element can return immediately from build, cutting off the walk, if the parent rebuilds the element with an identical widget. Moreover, the element need only compare the object identity of the two widget references in order to establish that the new widget is the same as the old widget. Developers exploit this optimization to implement the *reprojection* pattern, in which a widget includes a prebuilt child widget stored as a member variable in its build.

During build, Flutter also avoids walking the parent chain using `InheritedWidgets`. If widgets commonly walked their parent chain, for example to determine the current theme color, the build phase would become O(N²) in the depth of the tree, which can be quite large due to aggressive composition. To avoid these parent walks, the framework pushes information down the element tree by maintaining a hash table of `InheritedWidget`s at each element. Typically, many elements will reference the same hash table, which changes only at elements that introduce a new `InheritedWidget`.

### Linear reconciliation

[#](#linear-reconciliation)

Contrary to popular belief, Flutter does not employ a tree-diffing algorithm. Instead, the framework decides whether to reuse elements by examining the child list for each element independently using an O(N) algorithm. The child list reconciliation algorithm optimizes for the following cases:

* The old child list is empty.
* The two lists are identical.
* There is an insertion or removal of one or more widgets in exactly one place in the list.
* If each list contains a widget with the same key[5](#fn-5), the two widgets are matched.

The general approach is to match up the beginning and end of both child lists by comparing the runtime type and key of each widget, potentially finding a non-empty range in the middle of each list that contains all the unmatched children. The framework then places the children in the range in the old child list into a hash table based on their keys. Next, the framework walks the range in the new child list and queries the hash table by key for matches. Unmatched children are discarded and rebuilt from scratch whereas matched children are rebuilt with their new widgets.

### Tree surgery

[#](#tree-surgery)

Reusing elements is important for performance because elements own two critical pieces of data: the state for stateful widgets and the underlying render objects. When the framework is able to reuse an element, the state for that logical part of the user interface is preserved and the layout information computed previously can be reused, often avoiding entire subtree walks. In fact, reusing elements is so valuable that Flutter supports *non-local* tree mutations that preserve state and layout information.

Developers can perform a non-local tree mutation by associating a `GlobalKey` with one of their widgets. Each global key is unique throughout the entire application and is registered with a thread-specific hash table. During the build phase, the developer can move a widget with a global key to an arbitrary location in the element tree. Rather than building a fresh element at that location, the framework will check the hash table and reparent the existing element from its previous location to its new location, preserving the entire subtree.

The render objects in the reparented subtree are able to preserve their layout information because the layout constraints are the only information that flows from parent to child in the render tree. The new parent is marked dirty for layout because its child list has changed, but if the new parent passes the child the same layout constraints the child received from its old parent, the child can return immediately from layout, cutting off the walk.

Global keys and non-local tree mutations are used extensively by developers to achieve effects such as hero transitions and navigation.

### Constant-factor optimizations

[#](#constant-factor-optimizations)

In addition to these algorithmic optimizations, achieving aggressive composability also relies on several important constant-factor optimizations. These optimizations are most important at the leaves of the major algorithms discussed above.

* **Child-model agnostic.** Unlike most toolkits, which use child lists, Flutter's render tree does not commit to a specific child model. For example, the `RenderBox` class has an abstract `visitChildren()` method rather than a concrete `firstChild` and `nextSibling` interface. Many subclasses support only a single child, held directly as a member variable, rather than a list of children. For example, `RenderPadding` supports only a single child and, as a result, has a simpler layout method that takes less time to execute.

* **Visual render tree, logical widget tree.** In Flutter, the render tree operates in a device-independent, visual coordinate system, which means smaller values in the x coordinate are always towards the left, even if the current reading direction is right-to-left. The widget tree typically operates in logical coordinates, meaning with *start* and *end* values whose visual interpretation depends on the reading direction. The transformation from logical to visual coordinates is done in the handoff between the widget tree and the render tree. This approach is more efficient because layout and painting calculations in the render tree happen more often than the widget-to-render tree handoff and can avoid repeated coordinate conversions.

* **Text handled by a specialized render object.** The vast majority of render objects are ignorant of the complexities of text. Instead, text is handled by a specialized render object, `RenderParagraph`, which is a leaf in the render tree. Rather than subclassing a text-aware render object, developers incorporate text into their user interface using composition. This pattern means `RenderParagraph` can avoid recomputing its text layout as long as its parent supplies the same layout constraints, which is common, even during tree surgery.

* **Observable objects.** Flutter uses both the model-observation and the reactive paradigms. Obviously, the reactive paradigm is dominant, but Flutter uses observable model objects for some leaf data structures. For example, `Animation`s notify an observer list when their value changes. Flutter hands off these observable objects from the widget tree to the render tree, which observes them directly and invalidates only the appropriate stage of the pipeline when they change. For example, a change to an `Animation<Color>` might trigger only the paint phase rather than both the build and paint phases.

Taken together and summed over the large trees created by aggressive composition, these optimizations have a substantial effect on performance.

### Separation of the Element and RenderObject trees

[#](#separation-of-the-element-and-renderobject-trees)

The `RenderObject` and `Element` (Widget) trees in Flutter are isomorphic (strictly speaking, the `RenderObject` tree is a subset of the `Element` tree). An obvious simplification would be to combine these trees into one tree. However, in practice there are a number of benefits to having these trees be separate:

* **Performance.** When the layout changes, only the relevant parts of the layout tree need to be walked. Due to composition, the element tree frequently has many additional nodes that would have to be skipped.

* **Clarity.** The clearer separation of concerns allows the widget protocol and the render object protocol to each be specialized to their specific needs, simplifying the API surface and thus lowering the risk of bugs and the testing burden.

* **Type safety.** The render object tree can be more type safe since it can guarantee at runtime that children will be of the appropriate type (each coordinate system, e.g. has its own type of render object). Composition widgets can be agnostic about the coordinate system used during layout (for example, the same widget exposing a part of the app model could be used in both a box layout and a sliver layout), and thus in the element tree, verifying the type of render objects would require a tree walk.

## Infinite scrolling

[#](#infinite-scrolling)

Infinite scrolling lists are notoriously difficult for toolkits. Flutter supports infinite scrolling lists with a simple interface based on the *builder* pattern, in which a `ListView` uses a callback to build widgets on demand as they become visible to the user during scrolling. Supporting this feature requires *viewport-aware layout* and *building widgets on demand*.

### Viewport-aware layout

[#](#viewport-aware-layout)

Like most things in Flutter, scrollable widgets are built using composition. The outside of a scrollable widget is a `Viewport`, which is a box that is "bigger on the inside," meaning its children can extend beyond the bounds of the viewport and can be scrolled into view. However, rather than having `RenderBox` children, a viewport has `RenderSliver` children, known as *slivers*, which have a viewport-aware layout protocol.

The sliver layout protocol matches the structure of the box layout protocol in that parents pass constraints down to their children and receive geometry in return. However, the constraint and geometry data differs between the two protocols. In the sliver protocol, children are given information about the viewport, including the amount of visible space remaining. The geometry data they return enables a variety of scroll-linked effects, including collapsible headers and parallax.

Different slivers fill the space available in the viewport in different ways. For example, a sliver that produces a linear list of children lays out each child in order until the sliver either runs out of children or runs out of space. Similarly, a sliver that produces a two-dimensional grid of children fills only the portion of its grid that is visible. Because they are aware of how much space is visible, slivers can produce a finite number of children even if they have the potential to produce an unbounded number of children.

Slivers can be composed to create bespoke scrollable layouts and effects. For example, a single viewport can have a collapsible header followed by a linear list and then a grid. All three slivers will cooperate through the sliver layout protocol to produce only those children that are actually visible through the viewport, regardless of whether those children belong to the header, the list, or the grid[6](#fn-6).

### Building widgets on demand

[#](#building-widgets-on-demand)

If Flutter had a strict *build-then-layout-then-paint* pipeline, the foregoing would be insufficient to implement an infinite scrolling list because the information about how much space is visible through the viewport is available only during the layout phase. Without additional machinery, the layout phase is too late to build the widgets necessary to fill the space. Flutter solves this problem by interleaving the build and layout phases of the pipeline. At any point in the layout phase, the framework can start building new widgets on demand *as long as those widgets are descendants of the render object currently performing layout*.

Interleaving build and layout is possible only because of the strict controls on information propagation in the build and layout algorithms. Specifically, during the build phase, information can propagate only down the tree. When a render object is performing layout, the layout walk has not visited the subtree below that render object, which means writes generated by building in that subtree cannot invalidate any information that has entered the layout calculation thus far. Similarly, once layout has returned from a render object, that render object will never be visited again during this layout, which means any writes generated by subsequent layout calculations cannot invalidate the information used to build the render object's subtree.

Additionally, linear reconciliation and tree surgery are essential for efficiently updating elements during scrolling and for modifying the render tree when elements are scrolled into and out of view at the edge of the viewport.

## API Ergonomics

[#](#api-ergonomics)

Being fast only matters if the framework can actually be used effectively. To guide Flutter's API design towards greater usability, Flutter has been repeatedly tested in extensive UX studies with developers. These studies sometimes confirmed pre-existing design decisions, sometimes helped guide the prioritization of features, and sometimes changed the direction of the API design. For instance, Flutter's APIs are heavily documented; UX studies confirmed the value of such documentation, but also highlighted the need specifically for sample code and illustrative diagrams.

This section discusses some of the decisions made in Flutter's API design in aid of usability.

### Specializing APIs to match the developer's mindset

[#](#specializing-apis-to-match-the-developers-mindset)

The base class for nodes in Flutter's `Widget`, `Element`, and `RenderObject` trees does not define a child model. This allows each node to be specialized for the child model that is applicable to that node.

Most `Widget` objects have a single child `Widget`, and therefore only expose a single `child` parameter. Some widgets support an arbitrary number of children, and expose a `children` parameter that takes a list. Some widgets don't have any children at all and reserve no memory, and have no parameters for them. Similarly, `RenderObjects` expose APIs specific to their child model. `RenderImage` is a leaf node, and has no concept of children. `RenderPadding` takes a single child, so it has storage for a single pointer to a single child. `RenderFlex` takes an arbitrary number of children and manages it as a linked list.

In some rare cases, more complicated child models are used. The `RenderTable` render object's constructor takes an array of arrays of children, the class exposes getters and setters that control the number of rows and columns, and there are specific methods to replace individual children by x,y coordinate, to add a row, to provide a new array of arrays of children, and to replace the entire child list with a single array and a column count. In the implementation, the object does not use a linked list like most render objects but instead uses an indexable array.

The `Chip` widgets and `InputDecoration` objects have fields that match the slots that exist on the relevant controls. Where a one-size-fits-all child model would force semantics to be layered on top of a list of children, for example, defining the first child to be the prefix value and the second to be the suffix, the dedicated child model allows for dedicated named properties to be used instead.

This flexibility allows each node in these trees to be manipulated in the way most idiomatic for its role. It's rare to want to insert a cell in a table, causing all the other cells to wrap around; similarly, it's rare to want to remove a child from a flex row by index instead of by reference.

The `RenderParagraph` object is the most extreme case: it has a child of an entirely different type, `TextSpan`. At the `RenderParagraph` boundary, the `RenderObject` tree transitions into being a `TextSpan` tree.

The overall approach of specializing APIs to meet the developer's expectations is applied to more than just child models.

Some rather trivial widgets exist specifically so that developers will find them when looking for a solution to a problem. Adding a space to a row or column is easily done once one knows how, using the `Expanded` widget and a zero-sized `SizedBox` child, but discovering that pattern is unnecessary because searching for `space` uncovers the `Spacer` widget, which uses `Expanded` and `SizedBox` directly to achieve the effect.

Similarly, hiding a widget subtree is easily done by not including the widget subtree in the build at all. However, developers typically expect there to be a widget to do this, and so the `Visibility` widget exists to wrap this pattern in a trivial reusable widget.

### Explicit arguments

[#](#explicit-arguments)

UI frameworks tend to have many properties, such that a developer is rarely able to remember the semantic meaning of each constructor argument of each class. As Flutter uses the reactive paradigm, it is common for build methods in Flutter to have many calls to constructors. By leveraging Dart's support for named arguments, Flutter's API is able to keep such build methods clear and understandable.

This pattern is extended to any method with multiple arguments, and in particular is extended to any boolean argument, so that isolated `true` or `false` literals in method calls are always self-documenting. Furthermore, to avoid confusion commonly caused by double negatives in APIs, boolean arguments and properties are always named in the positive form (for example, `enabled: true` rather than `disabled: false`).

### Paving over pitfalls

[#](#paving-over-pitfalls)

A technique used in a number of places in the Flutter framework is to define the API such that error conditions don't exist. This removes entire classes of errors from consideration.

For example, interpolation functions allow one or both ends of the interpolation to be null, instead of defining that as an error case: interpolating between two null values is always null, and interpolating from a null value or to a null value is the equivalent of interpolating to the zero analog for the given type. This means that developers who accidentally pass null to an interpolation function will not hit an error case, but will instead get a reasonable result.

A more subtle example is in the `Flex` layout algorithm. The concept of this layout is that the space given to the flex render object is divided among its children, so the size of the flex should be the entirety of the available space. In the original design, providing infinite space would fail: it would imply that the flex should be infinitely sized, a useless layout configuration. Instead, the API was adjusted so that when infinite space is allocated to the flex render object, the render object sizes itself to fit the desired size of the children, reducing the possible number of error cases.

The approach is also used to avoid having constructors that allow inconsistent data to be created. For instance, the `PointerDownEvent` constructor does not allow the `down` property of `PointerEvent` to be set to `false` (a situation that would be self-contradictory); instead, the constructor does not have a parameter for the `down` field and always sets it to `true`.

In general, the approach is to define valid interpretations for all values in the input domain. The simplest example is the `Color` constructor. Instead of taking four integers, one for red, one for green, one for blue, and one for alpha, each of which could be out of range, the default constructor takes a single integer value, and defines the meaning of each bit (for example, the bottom eight bits define the red component), so that any input value is a valid color value.

A more elaborate example is the `paintImage()` function. This function takes eleven arguments, some with quite wide input domains, but they have been carefully designed to be mostly orthogonal to each other, such that there are very few invalid combinations.

### Reporting error cases aggressively

[#](#reporting-error-cases-aggressively)

Not all error conditions can be designed out. For those that remain, in debug builds, Flutter generally attempts to catch the errors very early and immediately reports them. Asserts are widely used. Constructor arguments are spot checked in detail. Lifecycles are monitored and when inconsistencies are detected they immediately cause an exception to be thrown.

In some cases, this is taken to extremes: for example, when running unit tests, regardless of what else the test is doing, every `RenderBox` subclass that is laid out aggressively inspects whether its intrinsic sizing methods fulfill the intrinsic sizing contract. This helps catch errors in APIs that might otherwise not be exercised.

When exceptions are thrown, they include as much information as is available. Some of Flutter's error messages proactively probe the associated stack trace to determine the most likely location of the actual bug. Others walk the relevant trees to determine the source of bad data. The most common errors include detailed instructions including in some cases sample code for avoiding the error, or links to further documentation.

### Reactive paradigm

[#](#reactive-paradigm)

Mutable tree-based APIs suffer from a dichotomous access pattern: creating the tree's original state typically uses a very different set of operations than subsequent updates. Flutter's rendering layer uses this paradigm, as it is an effective way to maintain a persistent tree, which is key for efficient layout and painting. However, it means that direct interaction with the rendering layer is awkward at best and bug-prone at worst.

Flutter's widget layer introduces a composition mechanism using the reactive paradigm[7](#fn-7) to manipulate the underlying rendering tree. This API abstracts out the tree manipulation by combining the tree creation and tree mutation steps into a single tree description (build) step, where, after each change to the system state, the new configuration of the user interface is described by the developer and the framework computes the series of tree mutations necessary to reflect this new configuration.

### Interpolation

[#](#interpolation)

Since Flutter's framework encourages developers to describe the interface configuration matching the current application state, a mechanism exists to implicitly animate between these configurations.

For example, suppose that in state S1 the interface consists of a circle, but in state S2 it consists of a square. Without an animation mechanism, the state change would have a jarring interface change. An implicit animation allows the circle to be smoothly squared over several frames.

Each feature that can be implicitly animated has a stateful widget that keeps a record of the current value of the input, and begins an animation sequence whenever the input value changes, transitioning from the current value to the new value over a specified duration.

This is implemented using `lerp` (linear interpolation) functions using immutable objects. Each state (circle and square, in this case) is represented as an immutable object that is configured with appropriate settings (color, stroke width, etc) and knows how to paint itself. When it is time to draw the intermediate steps during the animation, the start and end values are passed to the appropriate `lerp` function along with a *t* value representing the point along the animation, where 0.0 represents the `start` and 1.0 represents the `end`[8](#fn-8), and the function returns a third immutable object representing the intermediate stage.

For the circle-to-square transition, the `lerp` function would return an object representing a "rounded square" with a radius described as a fraction derived from the *t* value, a color interpolated using the `lerp` function for colors, and a stroke width interpolated using the `lerp` function for doubles. That object, which implements the same interface as circles and squares, would then be able to paint itself when requested to.

This technique allows the state machinery, the mapping of states to configurations, the animation machinery, the interpolation machinery, and the specific logic relating to how to paint each frame to be entirely separated from each other.

This approach is broadly applicable. In Flutter, basic types like `Color` and `Shape` can be interpolated, but so can much more elaborate types such as `Decoration`, `TextStyle`, or `Theme`. These are typically constructed from components that can themselves be interpolated, and interpolating the more complicated objects is often as simple as recursively interpolating all the values that describe the complicated objects.

Some interpolatable objects are defined by class hierarchies. For example, shapes are represented by the `ShapeBorder` interface, and there exists a variety of shapes, including `BeveledRectangleBorder`, `BoxBorder`, `CircleBorder`, `RoundedRectangleBorder`, and `StadiumBorder`. A single `lerp` function can't anticipate all possible types, and therefore the interface instead defines `lerpFrom` and `lerpTo` methods, which the static `lerp` method defers to. When told to interpolate from a shape A to a shape B, first B is asked if it can `lerpFrom` A, then, if it cannot, A is instead asked if it can `lerpTo` B. (If neither is possible, then the function returns A from values of `t` less than 0.5, and returns B otherwise.)

This allows the class hierarchy to be arbitrarily extended, with later additions being able to interpolate between previously-known values and themselves.

In some cases, the interpolation itself cannot be described by any of the available classes, and a private class is defined to describe the intermediate stage. This is the case, for instance, when interpolating between a `CircleBorder` and a `RoundedRectangleBorder`.

This mechanism has one further added advantage: it can handle interpolation from intermediate stages to new values. For example, half-way through a circle-to-square transition, the shape could be changed once more, causing the animation to need to interpolate to a triangle. So long as the triangle class can `lerpFrom` the rounded-square intermediate class, the transition can be seamlessly performed.

## Conclusion

[#](#conclusion)

Flutter's slogan, "everything is a widget," revolves around building user interfaces by composing widgets that are, in turn, composed of progressively more basic widgets. The result of this aggressive composition is a large number of widgets that require carefully designed algorithms and data structures to process efficiently. With some additional design, these data structures also make it easy for developers to create infinite scrolling lists that build widgets on demand when they become visible.

***

**Footnotes:**

1. For layout, at least. It might be revisited for painting, for building the accessibility tree if necessary, and for hit testing if necessary. [↩](#fnref-1)

2. Reality, of course, is a bit more complicated. Some layouts involve intrinsic dimensions or baseline measurements, which do involve an additional walk of the relevant subtree (aggressive caching is used to mitigate the potential for quadratic performance in the worst case). These cases, however, are surprisingly rare. In particular, intrinsic dimensions are not required for the common case of shrink-wrapping. [↩](#fnref-2)

3. Technically, the child's position is not part of its RenderBox geometry and therefore need not actually be calculated during layout. Many render objects implicitly position their single child at 0,0 relative to their own origin, which requires no computation or storage at all. Some render objects avoid computing the position of their children until the last possible moment (for example, during the paint phase), to avoid the computation entirely if they are not subsequently painted. [↩](#fnref-3)

4. There exists one exception to this rule. As discussed in the [Building widgets on demand](#building-widgets-on-demand) section, some widgets can be rebuilt as a result of a change in layout constraints. If a widget marked itself dirty for unrelated reasons in the same frame that it also is affected by a change in layout constraints, it will be updated twice. This redundant build is limited to the widget itself and does not impact its descendants. [↩](#fnref-4)

5. A key is an opaque object optionally associated with a widget whose equality operator is used to influence the reconciliation algorithm. [↩](#fnref-5)

6. For accessibility, and to give applications a few extra milliseconds between when a widget is built and when it appears on the screen, the viewport creates (but does not paint) widgets for a few hundred pixels before and after the visible widgets. [↩](#fnref-6)

7. This approach was first made popular by Facebook's React library. [↩](#fnref-7)

8. In practice, the *t* value is allowed to extend past the 0.0-1.0 range, and does so for some curves. For example, the "elastic" curves overshoot briefly in order to represent a bouncing effect. The interpolation logic typically can extrapolate past the start or end as appropriate. For some types, for example, when interpolating colors, the *t* value is effectively clamped to the 0.0-1.0 range. [↩](#fnref-8)

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/resources/inside-flutter.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/resources/inside-flutter\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/resources/inside-flutter.md "Report an issue with this page").

----
url: https://docs.flutter.dev/learn/pathway/tutorial/advanced-ui
----

Preview the Rolodex app you'll build and set up a Cupertino-based project with data models.

preview Preview the Rolodex app you'll build

phone\_iphone Set up a project with Cupertino widgets

data\_object Create data models for contacts and groups

***

## Steps

1### Introductionkeyboard\_arrow\_up

In this third installment of the Flutter tutorial series, you'll use Flutter's Cupertino library to build a partial clone of the iOS Contacts app.

By the end of this tutorial, you'll have learned how to create adaptive layouts, implement comprehensive theming, build navigation patterns, and use advanced scrolling techniques.

#### What you'll learn

[#](#what-youll-learn)

This tutorial explores the following topics:

* Building responsive layouts with `LayoutBuilder`.
* Using advanced scrolling with slivers and search.
* Implementing stack-based navigation patterns.
* Creating comprehensive themes with `CupertinoThemeData`.
* Supporting both light and dark themes.
* Creating an iOS-style UI using Cupertino widgets.

This tutorial assumes that you've completed the previous Flutter tutorials and are comfortable with basic widget composition, state management, and the Flutter project structure.

2### Create a new Flutter projectkeyboard\_arrow\_up

To build a Flutter app, you first need a Flutter project. You can create a new app with the [Flutter CLI tool](/reference/flutter-cli), which is installed as part of the Flutter SDK.

Open your preferred terminal and run the following command to create a new Flutter project:

```
$ flutter create rolodex --empty
$ cd rolodex
```

This command creates a new Flutter project that uses the minimal "empty" template.

3### Add the Cupertino dependencieskeyboard\_arrow\_up

This project uses the [`cupertino_ui` package](https://pub.dev/packages/cupertino_ui) for Cupertino widgets and the [`cupertino_icons` package](https://pub.dev/packages/cupertino_icons) for Cupertino icon assets. Add both official Flutter packages as dependencies by running the following command:

```
$ flutter pub add cupertino_ui cupertino_icons
```

4### Set up the project structurekeyboard\_arrow\_up

First, create the basic directory structure for your app. In your project's `lib` directory, create the following folders:

```
$ mkdir lib/data lib/screens lib/theme
```

This command creates folders to organize your code into logical sections: data models, screen widgets, and theme configuration.

5### Replace the starter codekeyboard\_arrow\_up

In your IDE, open the `lib/main.dart` file, and replace its entire contents with the following starter code:

dart

```
import 'package:cupertino_ui/cupertino_ui.dart';
​
void main() {
  runApp(const RolodexApp());
}
​
class RolodexApp extends StatelessWidget {
  const RolodexApp({super.key});
​
  @override
  Widget build(BuildContext context) {
    return const CupertinoApp(
      title: 'Rolodex',
      theme: CupertinoThemeData(
        barBackgroundColor: CupertinoDynamicColor.withBrightness(
          color: Color(0xFFF9F9F9),
          darkColor: Color(0xFF1D1D1D),
        ),
      ),
      home: CupertinoPageScaffold(child: Center(child: Text('Hello Rolodex!'))),
    );
  }
}
```

Unlike the previous two tutorials, this app uses `CupertinoApp` instead of `MaterialApp`. The Cupertino design system provides iOS-style widgets and styling, which is perfect for building apps that feel native on Apple devices.

6### Run your appkeyboard\_arrow\_up

In your terminal at the root of your Flutter app, run the following command:

```
$ flutter run -d chrome
```

The app builds and launches in a new instance of Chrome. It displays "Hello Rolodex!" in the center of the screen.

7### Create the data modelskeyboard\_arrow\_up

Before building the UI, create the data structures and sample data that the app will use. This section is lightly explained because it's not the focus of this tutorial.

#### Contact data

[#](#contact-data)

Create a new file, `lib/data/contact.dart`, and add the basic `Contact` class:

dart

```
class Contact {
  Contact({
    required this.id,
    required this.firstName,
    this.middleName,
    required this.lastName,
    this.suffix,
  });
​
  final int id;
  final String firstName;
  final String lastName;
  final String? middleName;
  final String? suffix;
}
​
keyboard_arrow_rightfinal johnAppleseed = Contact(id: 0, firstName: 'John', lastName: 'Appleseed');
final kateBell = Contact(id: 1, firstName: 'Kate', lastName: 'Bell');
final annaHaro = Contact(id: 2, firstName: 'Anna', lastName: 'Haro');
final danielHiggins = Contact(
  id: 3,
  firstName: 'Daniel',
  lastName: 'Higgins',
  suffix: 'Jr.',
);
final davidTaylor = Contact(id: 4, firstName: 'David', lastName: 'Taylor');
final hankZakroff = Contact(
  id: 5,
  firstName: 'Hank',
  middleName: 'M.',
  lastName: 'Zakroff',
);
​
final alexAnderson = Contact(id: 6, firstName: 'Alex', lastName: 'Anderson');
final benBrown = Contact(id: 7, firstName: 'Ben', lastName: 'Brown');
final carolCarter = Contact(id: 8, firstName: 'Carol', lastName: 'Carter');
final dianaDevito = Contact(id: 9, firstName: 'Diana', lastName: 'Devito');
final emilyEvans = Contact(id: 10, firstName: 'Emily', lastName: 'Evans');
final frankFisher = Contact(id: 11, firstName: 'Frank', lastName: 'Fisher');
final graceGreen = Contact(id: 12, firstName: 'Grace', lastName: 'Green');
final henryHall = Contact(id: 13, firstName: 'Henry', lastName: 'Hall');
final isaacIngram = Contact(id: 14, firstName: 'Isaac', lastName: 'Ingram');
final juliaJackson = Contact(id: 15, firstName: 'Julia', lastName: 'Jackson');
final kevinKelly = Contact(id: 16, firstName: 'Kevin', lastName: 'Kelly');
final lindaLewis = Contact(id: 17, firstName: 'Linda', lastName: 'Lewis');
final michaelMiller = Contact(id: 18, firstName: 'Michael', lastName: 'Miller');
final nancyNewman = Contact(id: 19, firstName: 'Nancy', lastName: 'Newman');
final oliverOwens = Contact(id: 20, firstName: 'Oliver', lastName: 'Owens');
final penelopeParker = Contact(
  id: 21,
  firstName: 'Penelope',
  lastName: 'Parker',
);
final quentinQuinn = Contact(id: 22, firstName: 'Quentin', lastName: 'Quinn');
final rachelReed = Contact(id: 23, firstName: 'Rachel', lastName: 'Reed');
final samuelSmith = Contact(id: 24, firstName: 'Samuel', lastName: 'Smith');
final tessaTurner = Contact(id: 25, firstName: 'Tessa', lastName: 'Turner');
final umbertoUpton = Contact(id: 26, firstName: 'Umberto', lastName: 'Upton');
final victoriaVance = Contact(id: 27, firstName: 'Victoria', lastName: 'Vance');
final williamWilson = Contact(id: 28, firstName: 'William', lastName: 'Wilson');
final xavierXu = Contact(id: 29, firstName: 'Xavier', lastName: 'Xu');
final yasmineYoung = Contact(id: 30, firstName: 'Yasmine', lastName: 'Young');
final zacharyZimmerman = Contact(
  id: 31,
  firstName: 'Zachary',
  lastName: 'Zimmerman',
);
final elizabethMJohnson = Contact(
  id: 32,
  firstName: 'Elizabeth',
  middleName: 'M.',
  lastName: 'Johnson',
);
final robertLWilliamsSr = Contact(
  id: 33,
  firstName: 'Robert',
  middleName: 'L.',
  lastName: 'Williams',
  suffix: 'Sr.',
);
final margaretAnneDavis = Contact(
  id: 34,
  firstName: 'Margaret',
  middleName: 'Anne',
  lastName: 'Davis',
);
final williamJamesBrownIII = Contact(
  id: 35,
  firstName: 'William',
  middleName: 'James',
  lastName: 'Brown',
  suffix: 'III',
);
final maryElizabethClark = Contact(
  id: 36,
  firstName: 'Mary',
  middleName: 'Elizabeth',
  lastName: 'Clark',
);
final drSarahWatson = Contact(
  id: 37,
  firstName: 'Dr. Sarah',
  lastName: 'Watson',
);
final jamesRSmithEsq = Contact(
  id: 38,
  firstName: 'James',
  middleName: 'R.',
  lastName: 'Smith',
  suffix: 'Esq.',
);
final mariaCruz = Contact(id: 39, firstName: 'Maria', lastName: 'Cruz');
final pierreMartin = Contact(id: 40, firstName: 'Pierre', lastName: 'Martin');
final yukiTanaka = Contact(id: 41, firstName: 'Yuki', lastName: 'Tanaka');
final hansSchmidt = Contact(id: 42, firstName: 'Hans', lastName: 'Schmidt');
final priyaPatel = Contact(id: 43, firstName: 'Priya', lastName: 'Patel');
final carlosGarcia = Contact(id: 44, firstName: 'Carlos', lastName: 'Garcia');
final ninaVolkova = Contact(id: 45, firstName: 'Nina', lastName: 'Volkova');
final jenniferAdams = Contact(id: 46, firstName: 'Jennifer', lastName: 'Adams');
final michaelBaker = Contact(id: 47, firstName: 'Michael', lastName: 'Baker');
final sarahCooper = Contact(id: 48, firstName: 'Sarah', lastName: 'Cooper');
final christopherDaniel = Contact(
  id: 49,
  firstName: 'Christopher',
  lastName: 'Daniel',
);
final jessicaEdwards = Contact(
  id: 50,
  firstName: 'Jessica',
  lastName: 'Edwards',
);
​
keyboard_arrow_rightfinal Set<Contact> allContacts = {
  johnAppleseed,
  kateBell,
  annaHaro,
  danielHiggins,
  davidTaylor,
  hankZakroff,
  alexAnderson,
  benBrown,
  carolCarter,
  dianaDevito,
  emilyEvans,
  frankFisher,
  graceGreen,
  henryHall,
  isaacIngram,
  juliaJackson,
  kevinKelly,
  lindaLewis,
  michaelMiller,
  nancyNewman,
  oliverOwens,
  penelopeParker,
  quentinQuinn,
  rachelReed,
  samuelSmith,
  tessaTurner,
  umbertoUpton,
  victoriaVance,
  williamWilson,
  xavierXu,
  yasmineYoung,
  zacharyZimmerman,
  elizabethMJohnson,
  robertLWilliamsSr,
  margaretAnneDavis,
  williamJamesBrownIII,
  maryElizabethClark,
  drSarahWatson,
  jamesRSmithEsq,
  mariaCruz,
  pierreMartin,
  yukiTanaka,
  hansSchmidt,
  priyaPatel,
  carlosGarcia,
  ninaVolkova,
  jenniferAdams,
  michaelBaker,
  sarahCooper,
  christopherDaniel,
  jessicaEdwards,
};
```

This sample data includes contacts with and without middle names and suffixes. This gives you a variety of data to work with as you build the UI.

#### ContactGroup data

[#](#contactgroup-data)

Now, create the contact groups that organize your contacts into lists. Create a new `lib/data/contact_group.dart` file and add the `ContactGroup` class:

dart

```
import 'dart:collection';
​
import 'package:cupertino_ui/cupertino_ui.dart';
​
import 'contact.dart';
​
class ContactGroup {
  factory ContactGroup({
    required int id,
    required String label,
    bool permanent = false,
    String? title,
    List<Contact>? contacts,
  }) {
    final contactsCopy = contacts ?? <Contact>[];
    _sortContacts(contactsCopy);
    return ContactGroup._internal(
      id: id,
      label: label,
      permanent: permanent,
      title: title,
      contacts: contactsCopy,
    );
  }
​
  ContactGroup._internal({
    required this.id,
    required this.label,
    this.permanent = false,
    String? title,
    List<Contact>? contacts,
  }) : title = title ?? label,
       _contacts = contacts ?? const <Contact>[];
​
  final int id;
  final bool permanent;
  final String label;
  final String title;
  final List<Contact> _contacts;
​
  List<Contact> get contacts => _contacts;
​
  AlphabetizedContactMap get alphabetizedContacts {
    final contactsMap = AlphabetizedContactMap();
    for (final contact in _contacts) {
      final lastInitial = contact.lastName[0].toUpperCase();
      if (contactsMap.containsKey(lastInitial)) {
        contactsMap[lastInitial]!.add(contact);
      } else {
        contactsMap[lastInitial] = [contact];
      }
    }
    return contactsMap;
  }
}
```

A `ContactGroup` represents a collection of contacts, such as "All Contacts" or "Favorites".

Add the following helper code and sample data to `lib/data/contact_group.dart`:

dart

```
typedef AlphabetizedContactMap = SplayTreeMap<String, List<Contact>>;
​
/// Sorts a list of [contacts] alphabetically by
/// last name, then first name, then middle name.
/// If names are identical, sorts by contact ID to ensure consistent ordering.
void _sortContacts(List<Contact> contacts) {
  contacts.sort((a, b) {
    final checkLastName = a.lastName.compareTo(b.lastName);
    if (checkLastName != 0) {
      return checkLastName;
    }
    final checkFirstName = a.firstName.compareTo(b.firstName);
    if (checkFirstName != 0) {
      return checkFirstName;
    }
    if (a.middleName != null && b.middleName != null) {
      final checkMiddleName = a.middleName!.compareTo(b.middleName!);
      if (checkMiddleName != 0) {
        return checkMiddleName;
      }
    } else if (a.middleName != null || b.middleName != null) {
      return a.middleName != null ? 1 : -1;
    }
​
    // If both contacts have the exact same name, order by first created.
    return a.id.compareTo(b.id);
  });
}
​
final allPhone = ContactGroup(
  id: 0,
  permanent: true,
  label: 'All iPhone',
  title: 'iPhone',
  contacts: allContacts.toList(),
);
​
final friends = ContactGroup(
  id: 1,
  label: 'Friends',
  contacts: [allContacts.elementAt(3)],
);
​
final work = ContactGroup(id: 2, label: 'Work');
​
List<ContactGroup> generateSeedData() {
  return [allPhone, friends, work];
}
```

This code creates three sample groups and a function to generate the initial data for the app.

Finally, add a class that manages state changes to `lib/data/contact_group.dart`:

dart

```
class ContactGroupsModel {
  ContactGroupsModel() : _listsNotifier = ValueNotifier(generateSeedData());
​
  final ValueNotifier<List<ContactGroup>> _listsNotifier;
​
  ValueNotifier<List<ContactGroup>> get listsNotifier => _listsNotifier;
​
  List<ContactGroup> get lists => _listsNotifier.value;
​
  ContactGroup findContactList(int id) {
    return lists[id];
  }
​
  void dispose() {
    _listsNotifier.dispose();
  }
}
```

If you aren't familiar with `ValueNotifier`, you should complete the [previous tutorial covering state](/learn/pathway/tutorial/set-up-state-project) before continuing, which covers state management.

8### Connect the data to your appkeyboard\_arrow\_up

Update your `main.dart` to include the global state and import the new data file:

dart

```
import 'package:cupertino_ui/cupertino_ui.dart';
​
import 'data/contact_group.dart';
​
final contactGroupsModel = ContactGroupsModel();
​
void main() {
  runApp(const RolodexApp());
}
​
class RolodexApp extends StatelessWidget {
  const RolodexApp({super.key});
​
  @override
  Widget build(BuildContext context) {
    return const CupertinoApp(
      title: 'Rolodex',
      theme: CupertinoThemeData(
        barBackgroundColor: CupertinoDynamicColor.withBrightness(
          color: Color(0xFFF9F9F9),
          darkColor: Color(0xFF1D1D1D),
        ),
      ),
      home: CupertinoPageScaffold(child: Center(child: Text('Hello Rolodex!'))),
    );
  }
}
```

With all the extraneous code out of the way, in the next lesson, you'll start building the app in earnest.

9### Reviewkeyboard\_arrow\_up

preview Previewed the Rolodex app keyboard\_arrow\_up

You're starting a new tutorial section focused on advanced UI features. To make your app feel polished and native on any device, you'll learn adaptive layouts, slivers, navigation, and theming.

phone\_iphone Set up a project with Cupertino widgets keyboard\_arrow\_up

Unlike the previous lessons, this app uses `CupertinoApp` instead of `MaterialApp`. The Cupertino design system provides iOS-style widgets that feel native on Apple devices.

data\_object Created data models for contacts and groups keyboard\_arrow\_up

You created `Contact` and `ContactGroup` classes with sample data, plus a `ContactGroupsModel` for state management. This foundation supports the UI you'll build in the coming lessons.

10### Test yourselfkeyboard\_arrow\_up

### Advanced UI Setup Quiz

1 / 2

**What is the main difference between CupertinoApp and MaterialApp?**

1. CupertinoApp only works on iOS devices.

   Not quite.

   CupertinoApp can run on any platform; it just provides iOS-style widgets.

2. CupertinoApp provides iOS-style widgets and styling, while MaterialApp provides Material Design widgets.

   That's right!

   CupertinoApp uses Cupertino design system widgets that match the iOS look and feel.

3. CupertinoApp is lighter and has better performance.

   Not quite.

   Both have similar performance; they differ in visual style, not speed.

4. MaterialApp requires more configuration to set up.

   Not quite.

   Both have similar setup requirements; they just use different design systems.

**What is the purpose of a ValueNotifier in state management?**

1. To validate user input values.

   Not quite.

   ValueNotifier holds and notifies about value changes, not validation.

2. To hold a single value and notify listeners when that value changes.

   That's right!

   ValueNotifier is a simple ChangeNotifier that wraps a single value and notifies listeners on change.

3. To convert values between different data types.

   Not quite.

   Type conversion is not the purpose of ValueNotifier.

4. To store values permanently in local storage.

   Not quite.

   ValueNotifier holds values in memory; persistence requires separate implementation.

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-10-15. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/learn/pathway/tutorial/advanced-ui.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/learn/pathway/tutorial/advanced-ui\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/learn/pathway/tutorial/advanced-ui.md "Report an issue with this page").

----
url: https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.28.2
----

The 2.28.2 release of the Dart and Flutter DevTools includes the following changes among other general improvements. To learn more about DevTools, check out the [DevTools overview](https://docs.flutter.dev/tools/devtools).

This was a cherry-pick release on top of DevTools 2.28.1. To learn about the improvements included in DevTools 2.28.1, please read the [release notes](/tools/devtools/release-notes/release-notes-2.28.1).

## DevTools Extension updates

[#](#devtools-extension-updates)

* Enabled DevTools extensions when debugging a Dart entry point that is not under `lib` (e.g. a unit test or integration test). Thanks to [@bartekpacia](https://github.com/bartekpacia) for this change! - [#6644](https://github.com/flutter/devtools/pull/6644)

## Full commit history

[#](#full-commit-history)

To find a complete list of changes in this release, check out the [DevTools git log](https://github.com/flutter/devtools/tree/v2.28.2).

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/release-notes/release-notes-2.28.2.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.28.2\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/release-notes/release-notes-2.28.2.md "Report an issue with this page").

----
url: https://docs.flutter.dev/install.md
----

\# Install Flutter > Learn how to install the Flutter SDK. Ready to build beautiful, multiplatform apps from a single codebase? Choose from the following two options to get started setting up your Flutter development environment.

[Quick start Recommended](/install/quick)

[Use VS Code or another Code OSS-based editor to quickly install, set up, and try out Flutter development!](/install/quick)

[Custom setup](/install/custom)

[Install the Flutter SDK, set up any initial target platform, and get started learning and developing with Flutter!](/install/custom)

\## Try Flutter {:#try} You can quickly try Flutter online without any local setup.

[DartPad](https://dartpad.dev)

[Quickly build and run simple single-file Flutter apps on the web.](https://dartpad.dev)

[]()## Update Flutter {: #update} If you already have Flutter installed and would like to upgrade your Flutter SDK installation or change to a different release channel, check out \[Upgrading Flutter]\[]. When upgrading, also remember to check out the published list of \[breaking changes and migration guides]\[]. \[Upgrading Flutter]: /install/upgrade \[breaking changes and migration guides]: /release/breaking-changes ## Download previous releases {: #previous-releases} If you're looking to download and install previous releases of Flutter, check out the \[SDK archive]\[]. :::note We recommend keeping your apps and development environments up to date with the \*\*latest\*\* \`stable\` or \`beta\` releases. ::: \[SDK archive]: /install/archive ## Troubleshoot and uninstall Flutter {: #uninstall} Guides to help you resolve common issues with your Flutter development environment and uninstall Flutter.

[Add Flutter to path](/install/add-to-path)

[Using Flutter on the command line requires that the Flutter SDK is added to your system's \`PATH\` environment variable.](/install/add-to-path)

[Troubleshoot SDK](/install/troubleshoot)

[Use this guide to resolve common issues with your Flutter development environment.](/install/troubleshoot)

[Uninstall SDK](/install/uninstall)

[Uninstall Flutter from your system.](/install/uninstall)

----
url: https://docs.flutter.dev/cookbook/networking/authenticated-requests
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Make authenticated requests](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

To fetch data from most web services, you need to provide authorization. There are many ways to do this, but perhaps the most common uses the `Authorization` HTTP header.

## Add authorization headers

[#](#add-authorization-headers)

The [`http`](https://pub.dev/packages/http) package provides a convenient way to add headers to your requests. Alternatively, use the [`HttpHeaders`](https://api.dart.dev/dart-io/HttpHeaders-class.html) class from the `dart:io` library.

dart

```
final response = await http.get(
  Uri.parse('https://jsonplaceholder.typicode.com/albums/1'),
  // Send authorization headers to the backend.
  headers: {HttpHeaders.authorizationHeader: 'Basic your_api_token_here'},
);
```

## Complete example

[#](#complete-example)

This example builds upon the [Fetching data from the internet](/cookbook/networking/fetch-data) recipe.

dart

```
import 'dart:async';
import 'dart:convert';
import 'dart:io';
​
import 'package:http/http.dart' as http;
​
Future<Album> fetchAlbum() async {
  final response = await http.get(
    Uri.parse('https://jsonplaceholder.typicode.com/albums/1'),
    // Send authorization headers to the backend.
    headers: {HttpHeaders.authorizationHeader: 'Basic your_api_token_here'},
  );
  final responseJson = jsonDecode(response.body) as Map<String, dynamic>;
​
  return Album.fromJson(responseJson);
}
​
class Album {
  final int userId;
  final int id;
  final String title;
​
  const Album({required this.userId, required this.id, required this.title});
​
  factory Album.fromJson(Map<String, dynamic> json) {
    return switch (json) {
      {'userId': int userId, 'id': int id, 'title': String title} => Album(
        userId: userId,
        id: id,
        title: title,
      ),
      _ => throw const FormatException('Failed to load album.'),
    };
  }
}
```

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/networking/authenticated-requests.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/cookbook/networking/authenticated-requests\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/networking/authenticated-requests.md "Report an issue with this page").

----
url: https://docs.flutter.dev/packages-and-plugins/dependency-management
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Package dependency management](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

When you build a Flutter app, you often use external packages to add functionality. Each package can depend on other packages, creating a tree of dependencies. Understanding how Dart and Flutter manage these dependencies helps you resolve version conflicts and keep your app stable.

## How dependency resolution works

[#](#how-dependency-resolution-works)

Your Flutter project specifies dependencies in the `pubspec.yaml` file. When you run `flutter pub get`, the package manager resolves the dependencies and records the exact versions in the `pubspec.lock` file.

### Direct and transitive dependencies

[#](#direct-and-transitive-dependencies)

A dependency can be direct or transitive:

* **Direct dependencies**

  The packages you explicitly list under the `dependencies` or `dev_dependencies` section in your `pubspec.yaml` file.

* **Transitive dependencies**

  The packages that your direct dependencies depend on. You don't list these packages in your `pubspec.yaml` file, but your project requires them to compile.

### The pubspec.lock file

[#](#the-pubspec-lock-file)

The `pubspec.lock` file contains the exact version of every direct and transitive dependency used in your project. This file ensures that every developer on your team, and your build servers, use the exact same package versions. Commit the `pubspec.lock` file to version control for application projects, but not for package projects.

## The role of the version solver

[#](#the-role-of-the-version-solver)

Unlike some package managers, the Dart package manager (`pub`) allows only a single version of any package in your app's compilation tree.

This constraint exists for several reasons:

1. **Type safety:** If your app uses two versions of the same package, you can get type mismatch errors. For example, if `v1` and `v2` of a package both define a `User` class, the Dart compiler treats them as two distinct types. You can't pass a `v1.User` to a method expecting a `v2.User`.
2. **Binary size:** Including multiple versions of the same package increases the size of your application.
3. **Global state:** Many packages use global variables or singletons. Having multiple copies of the package can lead to inconsistent state or bugs.

The version solver's job is to look at all version constraints for your direct and transitive dependencies and find a single concrete version for each package that satisfies all constraints.

## Understand version constraints

[#](#understand-version-constraints)

When you declare a dependency in `pubspec.yaml`, you specify a version constraint. Dart packages use [Semantic Versioning](https://semver.org/spec/v2.0.0-rc.1.html) (SemVer). A version number has three parts: `major.minor.patch` (for example, `2.1.4`).

You can define version constraints in your `pubspec.yaml` file using different syntaxes:

### Caret syntax

[#](#caret-syntax)

The caret syntax (`^`) is the most common way to define a version constraint. It tells the solver to use any version that is compatible with the specified version.

* For stable versions (1.0.0 and higher), the caret allows updates that don't change the major version:
  * `^1.2.3` is equivalent to `>=1.2.3 <2.0.0`

* For pre-release/pre-1.0.0 versions, any change in the minor or patch version can introduce breaking changes. Therefore, the caret constraint is more restrictive:

  * `^0.8.0` is equivalent to `>=0.8.0 <0.9.0`
  * `^0.0.3` is equivalent to `>=0.0.3 <0.0.4`

### Traditional ranges

[#](#traditional-ranges)

You can define explicit version ranges using comparison operators:

yaml

```
dependencies:
  url_launcher: '>=5.4.0 <6.0.0'
```

### Any version

[#](#any-version)

If you don't specify a version constraint or use `any`, the solver can choose any version:

yaml

```
dependencies:
  url_launcher: any
```

## Understand dependency conflicts

[#](#understand-dependency-conflicts)

A dependency conflict occurs when two packages in your dependency tree require incompatible versions of the same transitive dependency.

Consider this example scenario:

1. Your app depends on two direct dependencies: `package_a` and `package_b`.
2. `package_a` depends on `foo: ^1.0.0`.
3. `package_b` depends on `foo: ^2.0.0`.

Because `foo` can't be both `^1.0.0` (which is `<2.0.0`) and `^2.0.0` (which is `>=2.0.0`), the version solver fails.

When you run `flutter pub get` or try to build the app, the solver prints an error message:

```
Because every version of package_a depends on foo ^1.0.0 and
every version of package_b depends on foo ^2.0.0,
package_a is incompatible with package_b.
So, because my_app depends on both package_a ^1.0.0
and package_b ^1.0.0, version solving failed.
```

To read this error message, trace the conflict from the bottom up in the dependency graph:

1. Identify the conflicting package (`foo`).
2. Find which packages require conflicting versions. For example, `package_a` requires `foo: ^1.0.0` and `package_b` requires `foo: ^2.0.0`.
3. Check which direct dependencies pull in those conflicting packages. For example, `my_app` depends on both `package_a: ^1.0.0` and `package_b: ^1.0.0`.

## Resolve dependency conflicts

[#](#resolve-dependency-conflicts)

You can use the following steps to resolve dependency conflicts.

### Upgrade package versions

[#](#upgrade-package-versions)

Before making manual changes, check if newer, compatible versions of your dependencies exist. Run the following command to upgrade your packages to the latest versions allowed by your `pubspec.yaml` constraints:

bash

```
flutter pub upgrade
```

To see which packages have newer versions available beyond your current constraints, run:

bash

```
flutter pub outdated
```

To upgrade to newer major versions, run `flutter pub upgrade --major-versions`. This command upgrades the constraints in your `pubspec.yaml` file to allow the latest resolvable versions, and then runs `flutter pub get` to retrieve those upgraded dependencies.

### Use dependency overrides

[#](#use-dependency-overrides)

If no compatible versions exist because one of the packages hasn't been updated, you can force the version solver to use a specific version. Add a `dependency_overrides` section to your `pubspec.yaml` file:

yaml

```
dependencies:
  package_a: ^1.0.0
  package_b: ^1.0.0
​
dependency_overrides:
  foo: ^2.0.0
```

This overrides all constraints for `foo` and forces the version solver to use `^2.0.0`.

### Support the ecosystem

[#](#support-the-ecosystem)

If a package is unmaintained or slow to update, you can help resolve the issue:

1. **File an issue:** Search the package's repository for existing issues or file a new issue to notify the maintainer.

2. **Submit a pull request:** If you can fix the conflict, fork the package repository, update the constraints, and submit a pull request.

3. **Use a git or path dependency:** While waiting for the maintainer to merge your pull request, you can point your `pubspec.yaml` to your fork or a local copy:

   yaml

   ```
   dependencies:
     package_a:
       git:
         url: https://github.com/your-username/package_a.git
         ref: update-foo-dependency
   ```

## Learn more

[#](#learn-more)

For more information on how Dart manages dependencies, see the following resources:

* [Package versioning](https://dart.dev/tools/pub/versioning) on dart.dev
* [Pub dependencies](https://dart.dev/tools/pub/dependencies) on dart.dev
* [Dependency overrides](https://dart.dev/tools/pub/dependencies#dependency-overrides) on dart.dev

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-07-28. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/packages-and-plugins/dependency-management.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/packages-and-plugins/dependency-management\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/packages-and-plugins/dependency-management.md "Report an issue with this page").

----
url: https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.48.0
----

The 2.48.0 release of the Dart and Flutter DevTools includes the following changes among other general improvements. To learn more about DevTools, check out the [DevTools overview](/tools/devtools/overview).

## Network profiler updates

[#](#network-profiler-updates)

* Fixed network logging after a hot restart. - [#9271](https://github.com/flutter/devtools/pull/9271).

## Logging updates

[#](#logging-updates)

* Started displaying events related to timers in the Logging View. - [#9238](https://github.com/flutter/devtools/pull/9238).

## Advanced developer mode updates

[#](#advanced-developer-mode-updates)

* Added a Queued Microtasks tab to the VM Tools screen, which allows a user to see details about the microtasks scheduled in an isolate's microtask queue. This tab currently only appears when DevTools is connected to a Flutter or Dart app started with `--profile-microtasks`. - [#9239](https://github.com/flutter/devtools/pull/9239).

## Full commit history

[#](#full-commit-history)

To find a complete list of changes in this release, check out the [DevTools git log](https://github.com/flutter/devtools/tree/v2.48.0).

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/release-notes/release-notes-2.48.0.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.48.0\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/release-notes/release-notes-2.48.0.md "Report an issue with this page").

----
url: https://docs.flutter.dev/learn/pathway/tutorial/advanced-ui.md
----

\# Advanced UI features > A gentle introduction into advanced UI features: adaptive layouts, slivers, scrolling, navigation. Preview the Rolodex app you'll build and set up a Cupertino-based project with data models. title: What you'll accomplish items: - title: Preview the Rolodex app you'll build icon: preview - title: Set up a project with Cupertino widgets icon: phone\_iphone - title: Create data models for contacts and groups icon: data\_object --- ### Introduction In this third installment of the Flutter tutorial series, you'll use Flutter's Cupertino library to build a partial clone of the iOS Contacts app. By the end of this tutorial, you'll have learned how to create adaptive layouts, implement comprehensive theming, build navigation patterns, and use advanced scrolling techniques. #### What you'll learn This tutorial explores the following topics: \* Building responsive layouts with \`LayoutBuilder\`. \* Using advanced scrolling with slivers and search. \* Implementing stack-based navigation patterns. \* Creating comprehensive themes with \`CupertinoThemeData\`. \* Supporting both light and dark themes. \* Creating an iOS-style UI using Cupertino widgets. This tutorial assumes that you've completed the previous Flutter tutorials and are comfortable with basic widget composition, state management, and the Flutter project structure. ### Create a new Flutter project To build a Flutter app, you first need a Flutter project. You can create a new app with the \[Flutter CLI tool]\[], which is installed as part of the Flutter SDK. Open your preferred terminal and run the following command to create a new Flutter project: \`\`\`console $ flutter create rolodex --empty $ cd rolodex \`\`\` This command creates a new Flutter project that uses the minimal "empty" template. \[Flutter CLI tool]: /reference/flutter-cli ### Add the Cupertino dependencies This project uses the \[\`cupertino\_ui\` package]\[] for Cupertino widgets and the \[\`cupertino\_icons\` package]\[] for Cupertino icon assets. Add both official Flutter packages as dependencies by running the following command: \`\`\`console $ flutter pub add cupertino\_ui cupertino\_icons \`\`\` ### Set up the project structure First, create the basic directory structure for your app. In your project's \`lib\` directory, create the following folders: \`\`\`console $ mkdir lib/data lib/screens lib/theme \`\`\` This command creates folders to organize your code into logical sections: data models, screen widgets, and theme configuration. ### Replace the starter code In your IDE, open the \`lib/main.dart\` file, and replace its entire contents with the following starter code:

<!--?code-excerpt "fwe/rolodex/lib/step1_advanced_ui/main_starter.dart"?-->

\`\`\`dart import 'package:cupertino\_ui/cupertino\_ui.dart'; void main() { runApp(const RolodexApp()); } class RolodexApp extends StatelessWidget { const RolodexApp({super.key}); @override Widget build(BuildContext context) { return const CupertinoApp( title: 'Rolodex', theme: CupertinoThemeData( barBackgroundColor: CupertinoDynamicColor.withBrightness( color: Color(0xFFF9F9F9), darkColor: Color(0xFF1D1D1D), ), ), home: CupertinoPageScaffold(child: Center(child: Text('Hello Rolodex!'))), ); } } \`\`\` Unlike the previous two tutorials, this app uses \`CupertinoApp\` instead of \`MaterialApp\`. The Cupertino design system provides iOS-style widgets and styling, which is perfect for building apps that feel native on Apple devices. ### Run your app In your terminal at the root of your Flutter app, run the following command: \`\`\`console $ flutter run -d chrome \`\`\` The app builds and launches in a new instance of Chrome. It displays "Hello Rolodex!" in the center of the screen. ### Create the data models Before building the UI, create the data structures and sample data that the app will use. This section is lightly explained because it's not the focus of this tutorial. #### Contact data Create a new file, \`lib/data/contact.dart\`, and add the basic \`Contact\` class:

<!--?code-excerpt "fwe/rolodex/lib/step1_advanced_ui/data/contact.dart" replace="/\/\/ openFold/[* -/g; /\/\/ closeFold/*]/g;"?-->

\`\`\`dart foldable class Contact { Contact({ required this.id, required this.firstName, this.middleName, required this.lastName, this.suffix, }); final int id; final String firstName; final String lastName; final String? middleName; final String? suffix; } \[\* - final johnAppleseed = Contact(id: 0, firstName: 'John', lastName: 'Appleseed'); final kateBell = Contact(id: 1, firstName: 'Kate', lastName: 'Bell'); final annaHaro = Contact(id: 2, firstName: 'Anna', lastName: 'Haro'); final danielHiggins = Contact( id: 3, firstName: 'Daniel', lastName: 'Higgins', suffix: 'Jr.', ); final davidTaylor = Contact(id: 4, firstName: 'David', lastName: 'Taylor'); final hankZakroff = Contact( id: 5, firstName: 'Hank', middleName: 'M.', lastName: 'Zakroff', ); final alexAnderson = Contact(id: 6, firstName: 'Alex', lastName: 'Anderson'); final benBrown = Contact(id: 7, firstName: 'Ben', lastName: 'Brown'); final carolCarter = Contact(id: 8, firstName: 'Carol', lastName: 'Carter'); final dianaDevito = Contact(id: 9, firstName: 'Diana', lastName: 'Devito'); final emilyEvans = Contact(id: 10, firstName: 'Emily', lastName: 'Evans'); final frankFisher = Contact(id: 11, firstName: 'Frank', lastName: 'Fisher'); final graceGreen = Contact(id: 12, firstName: 'Grace', lastName: 'Green'); final henryHall = Contact(id: 13, firstName: 'Henry', lastName: 'Hall'); final isaacIngram = Contact(id: 14, firstName: 'Isaac', lastName: 'Ingram'); final juliaJackson = Contact(id: 15, firstName: 'Julia', lastName: 'Jackson'); final kevinKelly = Contact(id: 16, firstName: 'Kevin', lastName: 'Kelly'); final lindaLewis = Contact(id: 17, firstName: 'Linda', lastName: 'Lewis'); final michaelMiller = Contact(id: 18, firstName: 'Michael', lastName: 'Miller'); final nancyNewman = Contact(id: 19, firstName: 'Nancy', lastName: 'Newman'); final oliverOwens = Contact(id: 20, firstName: 'Oliver', lastName: 'Owens'); final penelopeParker = Contact( id: 21, firstName: 'Penelope', lastName: 'Parker', ); final quentinQuinn = Contact(id: 22, firstName: 'Quentin', lastName: 'Quinn'); final rachelReed = Contact(id: 23, firstName: 'Rachel', lastName: 'Reed'); final samuelSmith = Contact(id: 24, firstName: 'Samuel', lastName: 'Smith'); final tessaTurner = Contact(id: 25, firstName: 'Tessa', lastName: 'Turner'); final umbertoUpton = Contact(id: 26, firstName: 'Umberto', lastName: 'Upton'); final victoriaVance = Contact(id: 27, firstName: 'Victoria', lastName: 'Vance'); final williamWilson = Contact(id: 28, firstName: 'William', lastName: 'Wilson'); final xavierXu = Contact(id: 29, firstName: 'Xavier', lastName: 'Xu'); final yasmineYoung = Contact(id: 30, firstName: 'Yasmine', lastName: 'Young'); final zacharyZimmerman = Contact( id: 31, firstName: 'Zachary', lastName: 'Zimmerman', ); final elizabethMJohnson = Contact( id: 32, firstName: 'Elizabeth', middleName: 'M.', lastName: 'Johnson', ); final robertLWilliamsSr = Contact( id: 33, firstName: 'Robert', middleName: 'L.', lastName: 'Williams', suffix: 'Sr.', ); final margaretAnneDavis = Contact( id: 34, firstName: 'Margaret', middleName: 'Anne', lastName: 'Davis', ); final williamJamesBrownIII = Contact( id: 35, firstName: 'William', middleName: 'James', lastName: 'Brown', suffix: 'III', ); final maryElizabethClark = Contact( id: 36, firstName: 'Mary', middleName: 'Elizabeth', lastName: 'Clark', ); final drSarahWatson = Contact( id: 37, firstName: 'Dr. Sarah', lastName: 'Watson', ); final jamesRSmithEsq = Contact( id: 38, firstName: 'James', middleName: 'R.', lastName: 'Smith', suffix: 'Esq.', ); final mariaCruz = Contact(id: 39, firstName: 'Maria', lastName: 'Cruz'); final pierreMartin = Contact(id: 40, firstName: 'Pierre', lastName: 'Martin'); final yukiTanaka = Contact(id: 41, firstName: 'Yuki', lastName: 'Tanaka'); final hansSchmidt = Contact(id: 42, firstName: 'Hans', lastName: 'Schmidt'); final priyaPatel = Contact(id: 43, firstName: 'Priya', lastName: 'Patel'); final carlosGarcia = Contact(id: 44, firstName: 'Carlos', lastName: 'Garcia'); final ninaVolkova = Contact(id: 45, firstName: 'Nina', lastName: 'Volkova'); final jenniferAdams = Contact(id: 46, firstName: 'Jennifer', lastName: 'Adams'); final michaelBaker = Contact(id: 47, firstName: 'Michael', lastName: 'Baker'); final sarahCooper = Contact(id: 48, firstName: 'Sarah', lastName: 'Cooper'); final christopherDaniel = Contact( id: 49, firstName: 'Christopher', lastName: 'Daniel', ); final jessicaEdwards = Contact( id: 50, firstName: 'Jessica', lastName: 'Edwards', ); \*] \[\* - final Set allContacts = { johnAppleseed, kateBell, annaHaro, danielHiggins, davidTaylor, hankZakroff, alexAnderson, benBrown, carolCarter, dianaDevito, emilyEvans, frankFisher, graceGreen, henryHall, isaacIngram, juliaJackson, kevinKelly, lindaLewis, michaelMiller, nancyNewman, oliverOwens, penelopeParker, quentinQuinn, rachelReed, samuelSmith, tessaTurner, umbertoUpton, victoriaVance, williamWilson, xavierXu, yasmineYoung, zacharyZimmerman, elizabethMJohnson, robertLWilliamsSr, margaretAnneDavis, williamJamesBrownIII, maryElizabethClark, drSarahWatson, jamesRSmithEsq, mariaCruz, pierreMartin, yukiTanaka, hansSchmidt, priyaPatel, carlosGarcia, ninaVolkova, jenniferAdams, michaelBaker, sarahCooper, christopherDaniel, jessicaEdwards, }; \*] \`\`\` This sample data includes contacts with and without middle names and suffixes. This gives you a variety of data to work with as you build the UI. #### ContactGroup data Now, create the contact groups that organize your contacts into lists. Create a new \`lib/data/contact\_group.dart\` file and add the \`ContactGroup\` class:

<!--?code-excerpt "fwe/rolodex/lib/step1_advanced_ui/data/contact_group.dart (contact_group_class)"?-->

\`\`\`dart import 'dart:collection'; import 'package:cupertino\_ui/cupertino\_ui.dart'; import 'contact.dart'; class ContactGroup { factory ContactGroup({ required int id, required String label, bool permanent = false, String? title, List? contacts, }) { final contactsCopy = contacts ?? \[]; \_sortContacts(contactsCopy); return ContactGroup.\_internal( id: id, label: label, permanent: permanent, title: title, contacts: contactsCopy, ); } ContactGroup.\_internal({ required this.id, required this.label, this.permanent = false, String? title, List? contacts, }) : title = title ?? label, \_contacts = contacts ?? const \[]; final int id; final bool permanent; final String label; final String title; final List \_contacts; List get contacts => \_contacts; AlphabetizedContactMap get alphabetizedContacts { final contactsMap = AlphabetizedContactMap(); for (final contact in \_contacts) { final lastInitial = contact.lastName\[0].toUpperCase(); if (contactsMap.containsKey(lastInitial)) { contactsMap\[lastInitial]!.add(contact); } else { contactsMap\[lastInitial] = \[contact]; } } return contactsMap; } } \`\`\` A \`ContactGroup\` represents a collection of contacts, such as "All Contacts" or "Favorites". Add the following helper code and sample data to \`lib/data/contact\_group.dart\`:

<!--?code-excerpt "fwe/rolodex/lib/step1_advanced_ui/data/contact_group.dart (helper_code)"?-->

\`\`\`dart typedef AlphabetizedContactMap = SplayTreeMap>; /// Sorts a list of \[contacts] alphabetically by /// last name, then first name, then middle name. /// If names are identical, sorts by contact ID to ensure consistent ordering. void \_sortContacts(List contacts) { contacts.sort((a, b) { final checkLastName = a.lastName.compareTo(b.lastName); if (checkLastName != 0) { return checkLastName; } final checkFirstName = a.firstName.compareTo(b.firstName); if (checkFirstName != 0) { return checkFirstName; } if (a.middleName != null && b.middleName != null) { final checkMiddleName = a.middleName!.compareTo(b.middleName!); if (checkMiddleName != 0) { return checkMiddleName; } } else if (a.middleName != null || b.middleName != null) { return a.middleName != null ? 1 : -1; } // If both contacts have the exact same name, order by first created. return a.id.compareTo(b.id); }); } final allPhone = ContactGroup( id: 0, permanent: true, label: 'All iPhone', title: 'iPhone', contacts: allContacts.toList(), ); final friends = ContactGroup( id: 1, label: 'Friends', contacts: \[allContacts.elementAt(3)], ); final work = ContactGroup(id: 2, label: 'Work'); List generateSeedData() { return \[allPhone, friends, work]; } \`\`\` This code creates three sample groups and a function to generate the initial data for the app. Finally, add a class that manages state changes to \`lib/data/contact\_group.dart\`:

<!--?code-excerpt "fwe/rolodex/lib/step1_advanced_ui/data/contact_group.dart (model_class)"?-->

\`\`\`dart class ContactGroupsModel { ContactGroupsModel() : \_listsNotifier = ValueNotifier(generateSeedData()); final ValueNotifier> \_listsNotifier; ValueNotifier> get listsNotifier => \_listsNotifier; List get lists => \_listsNotifier.value; ContactGroup findContactList(int id) { return lists\[id]; } void dispose() { \_listsNotifier.dispose(); } } \`\`\` If you aren't familiar with \`ValueNotifier\`, you should complete the \[previous tutorial covering state]\[] before continuing, which covers state management. \[previous tutorial covering state]: /learn/pathway/tutorial/set-up-state-project ### Connect the data to your app Update your \`main.dart\` to include the global state and import the new data file:

<!--?code-excerpt "fwe/rolodex/lib/step1_advanced_ui/main.dart"?-->

\`\`\`dart import 'package:cupertino\_ui/cupertino\_ui.dart'; import 'data/contact\_group.dart'; final contactGroupsModel = ContactGroupsModel(); void main() { runApp(const RolodexApp()); } class RolodexApp extends StatelessWidget { const RolodexApp({super.key}); @override Widget build(BuildContext context) { return const CupertinoApp( title: 'Rolodex', theme: CupertinoThemeData( barBackgroundColor: CupertinoDynamicColor.withBrightness( color: Color(0xFFF9F9F9), darkColor: Color(0xFF1D1D1D), ), ), home: CupertinoPageScaffold(child: Center(child: Text('Hello Rolodex!'))), ); } } \`\`\` With all the extraneous code out of the way, in the next lesson, you'll start building the app in earnest. \[\`cupertino\_ui\` package]: https\://pub.dev/packages/cupertino\_ui \[\`cupertino\_icons\` package]: https\://pub.dev/packages/cupertino\_icons ### Review title: What you accomplished subtitle: Here's a summary of what you built and learned in this lesson. completed: true items: - title: Previewed the Rolodex app icon: preview details: >- You're starting a new tutorial section focused on advanced UI features. To make your app feel polished and native on any device, you'll learn adaptive layouts, slivers, navigation, and theming. - title: Set up a project with Cupertino widgets icon: phone\_iphone details: >- Unlike the previous lessons, this app uses \`CupertinoApp\` instead of \`MaterialApp\`. The Cupertino design system provides iOS-style widgets that feel native on Apple devices. - title: Created data models for contacts and groups icon: data\_object details: >- You created \`Contact\` and \`ContactGroup\` classes with sample data, plus a \`ContactGroupsModel\` for state management. This foundation supports the UI you'll build in the coming lessons. ### Test yourself - question: What is the main difference between CupertinoApp and MaterialApp? options: - text: CupertinoApp only works on iOS devices. correct: false explanation: CupertinoApp can run on any platform; it just provides iOS-style widgets. - text: CupertinoApp provides iOS-style widgets and styling, while MaterialApp provides Material Design widgets. correct: true explanation: CupertinoApp uses Cupertino design system widgets that match the iOS look and feel. - text: CupertinoApp is lighter and has better performance. correct: false explanation: Both have similar performance; they differ in visual style, not speed. - text: MaterialApp requires more configuration to set up. correct: false explanation: Both have similar setup requirements; they just use different design systems. - question: What is the purpose of a ValueNotifier in state management? options: - text: To validate user input values. correct: false explanation: ValueNotifier holds and notifies about value changes, not validation. - text: To hold a single value and notify listeners when that value changes. correct: true explanation: ValueNotifier is a simple ChangeNotifier that wraps a single value and notifies listeners on change. - text: To convert values between different data types. correct: false explanation: Type conversion is not the purpose of ValueNotifier. - text: To store values permanently in local storage. correct: false explanation: ValueNotifier holds values in memory; persistence requires separate implementation.

----
url: https://docs.flutter.dev/cookbook/persistence/sqlite
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Persist data with SQLite](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

If you are writing an app that needs to persist and query large 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 your relational data needs to sync across devices in the cloud, check out [Choose a database for your Flutter app](/data-and-backend/persistence/choose-a-database).

Flutter apps can make use of the SQLite databases via the [`sqflite`](https://pub.dev/packages/sqflite) plugin available on pub.dev. This recipe demonstrates the basics of using `sqflite` to insert, read, update, and remove data about various Dogs.

If you are new to SQLite and SQL statements, review the [SQLite Tutorial](https://www.sqlitetutorial.net/) to learn the basics before completing this recipe.

This recipe uses the following steps:

1. Add the dependencies.
2. Define the `Dog` data model.
3. Open the database.
4. Create the `dogs` table.
5. Insert a `Dog` into the database.
6. Retrieve the list of dogs.
7. Update a `Dog` in the database.
8. Delete a `Dog` from the database.

## 1. Add the dependencies

[#](#1-add-the-dependencies)

To work with SQLite databases, import the `sqflite` and `path` packages.

* 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.

To add the packages as a dependency, run `flutter pub add`:

```
$ flutter pub add sqflite path
```

Make sure to import the packages in the file you'll be working in.

dart

```
import 'dart:async';
​
import 'package:flutter/widgets.dart';
import 'package:path/path.dart';
import 'package:sqflite/sqflite.dart';
```

## 2. Define the Dog data model

[#](#2-define-the-dog-data-model)

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.

dart

```
class Dog {
  final int id;
  final String name;
  final int age;
​
  const Dog({required this.id, required this.name, required this.age});
}
```

## 3. Open the database

[#](#3-open-the-database)

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 `getDatabasesPath()` from the `sqflite` package, combined with the `join` function from the `path` package.
2. Open the database with the `openDatabase()` function from `sqflite`.

dart

```
// Avoid errors caused by flutter upgrade.
// Importing 'package:flutter/widgets.dart' is required.
WidgetsFlutterBinding.ensureInitialized();
// Open the database and store the reference.
final 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
  // constructed for each platform.
  join(await getDatabasesPath(), 'doggie_database.db'),
);
```

## 4. Create the `dogs` table

[#](#4-create-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 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, see the [official SQLite Datatypes documentation](https://www.sqlite.org/datatype3.html).

dart

```
final 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
  // constructed for each platform.
  join(await getDatabasesPath(), 'doggie_database.db'),
  // When the database is first created, create a table to store dogs.
  onCreate: (db, version) {
    // Run the CREATE TABLE statement on the database.
    return db.execute(
      'CREATE TABLE dogs(id INTEGER PRIMARY KEY, name TEXT, age INTEGER)',
    );
  },
  // Set the version. This executes the onCreate function and provides a
  // path to perform database upgrades and downgrades.
  version: 1,
);
```

## 5. Insert a Dog into the database

[#](#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.

First, insert a `Dog` into the `dogs` table. This involves two steps:

1. Convert the `Dog` into a `Map`
2. Use the [`insert()`](https://pub.dev/documentation/sqflite_common/latest/sqlite_api/DatabaseExecutor/insert.html) method to store the `Map` in the `dogs` table.

dart

```
class Dog {
  final int id;
  final String name;
  final int age;
​
  Dog({required this.id, required this.name, required this.age});
​
  // Convert a Dog into a Map. The keys must correspond to the names of the
  // columns in the database.
  Map<String, Object?> toMap() {
    return {'id': id, 'name': name, 'age': age};
  }
​
  // 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}';
  }
}
```

dart

```
// Define a function that inserts dogs into the database
Future<void> insertDog(Dog dog) async {
  // Get a reference to the database.
  final db = await database;
​
  // 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.
  await db.insert(
    'dogs',
    dog.toMap(),
    conflictAlgorithm: ConflictAlgorithm.replace,
  );
}
```

dart

```
// Create a Dog and add it to the dogs table
var fido = Dog(id: 0, name: 'Fido', age: 35);
​
await insertDog(fido);
```

## 6. Retrieve the list of Dogs

[#](#6-retrieve-the-list-of-dogs)

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 returns a `List<Map>`.
2. Convert the `List<Map>` into a `List<Dog>`.

dart

```
// A method that retrieves all the dogs from the dogs table.
Future<List<Dog>> dogs() async {
  // Get a reference to the database.
  final db = await database;
​
  // Query the table for all the dogs.
  final List<Map<String, Object?>> dogMaps = await db.query('dogs');
​
  // Convert the list of each dog's fields into a list of `Dog` objects.
  return [
    for (final {'id': id as int, 'name': name as String, 'age': age as int}
        in dogMaps)
      Dog(id: id, name: name, age: age),
  ];
}
```

dart

```
// 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

[#](#7-update-a-dog-in-the-database)

After inserting information into the database, you might want to update that information at a later time. You can do this by using the [`update()`](https://pub.dev/documentation/sqflite_common/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.

dart

```
Future<void> updateDog(Dog dog) async {
  // Get a reference to the database.
  final db = await database;
​
  // Update the given Dog.
  await db.update(
    'dogs',
    dog.toMap(),
    // Ensure that the Dog has a matching id.
    where: 'id = ?',
    // Pass the Dog's id as a whereArg to prevent SQL injection.
    whereArgs: [dog.id],
  );
}
```

dart

```
// Update Fido's age and save it to the database.
fido = Dog(id: fido.id, name: fido.name, age: fido.age + 7);
await updateDog(fido);
​
// Print the updated results.
print(await dogs()); // Prints Fido with age 42.
```

## 8. Delete a `Dog` from the database

[#](#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()`](https://pub.dev/documentation/sqflite_common/latest/sqlite_api/DatabaseExecutor/delete.html) method from the `sqflite` library.

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<void> deleteDog(int id) async {
  // Get a reference to the database.
  final db = await database;
​
  // Remove the Dog from the database.
  await db.delete(
    'dogs',
    // Use a `where` clause to delete a specific dog.
    where: 'id = ?',
    // Pass the Dog's id as a whereArg to prevent SQL injection.
    whereArgs: [id],
  );
}
```

## Example

[#](#example)

To run the example:

1. Create a new Flutter project.
2. Add the `sqflite` 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';
​
import 'package:flutter/widgets.dart';
import 'package:path/path.dart';
import 'package:sqflite/sqflite.dart';
​
void main() async {
  // Avoid errors caused by flutter upgrade.
  // Importing 'package:flutter/widgets.dart' is required.
  WidgetsFlutterBinding.ensureInitialized();
  // Open the database and store the reference.
  final 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
    // constructed for each platform.
    join(await getDatabasesPath(), 'doggie_database.db'),
    // When the database is first created, create a table to store dogs.
    onCreate: (db, version) {
      // Run the CREATE TABLE statement on the database.
      return db.execute(
        'CREATE TABLE dogs(id INTEGER PRIMARY KEY, name TEXT, age INTEGER)',
      );
    },
    // Set the version. This executes the onCreate function and provides a
    // path to perform database upgrades and downgrades.
    version: 1,
  );
​
  // Define a function that inserts dogs into the database
  Future<void> insertDog(Dog dog) async {
    // Get a reference to the database.
    final db = await database;
​
    // 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.
    await db.insert(
      'dogs',
      dog.toMap(),
      conflictAlgorithm: ConflictAlgorithm.replace,
    );
  }
​
  // A method that retrieves all the dogs from the dogs table.
  Future<List<Dog>> dogs() async {
    // Get a reference to the database.
    final db = await database;
​
    // Query the table for all the dogs.
    final List<Map<String, Object?>> dogMaps = await db.query('dogs');
​
    // Convert the list of each dog's fields into a list of `Dog` objects.
    return [
      for (final {'id': id as int, 'name': name as String, 'age': age as int}
          in dogMaps)
        Dog(id: id, name: name, age: age),
    ];
  }
​
  Future<void> updateDog(Dog dog) async {
    // Get a reference to the database.
    final db = await database;
​
    // Update the given Dog.
    await db.update(
      'dogs',
      dog.toMap(),
      // Ensure that the Dog has a matching id.
      where: 'id = ?',
      // Pass the Dog's id as a whereArg to prevent SQL injection.
      whereArgs: [dog.id],
    );
  }
​
  Future<void> deleteDog(int id) async {
    // Get a reference to the database.
    final db = await database;
​
    // Remove the Dog from the database.
    await db.delete(
      'dogs',
      // Use a `where` clause to delete a specific dog.
      where: 'id = ?',
      // Pass the Dog's id as a whereArg to prevent SQL injection.
      whereArgs: [id],
    );
  }
​
  // Create a Dog and add it to the dogs table
  var fido = Dog(id: 0, name: 'Fido', age: 35);
​
  await insertDog(fido);
​
  // Now, use the method above to retrieve all the dogs.
  print(await dogs()); // Prints a list that include Fido.
​
  // Update Fido's age and save it to the database.
  fido = Dog(id: fido.id, name: fido.name, age: fido.age + 7);
  await updateDog(fido);
​
  // Print the updated results.
  print(await dogs()); // Prints Fido with age 42.
​
  // Delete Fido from the database.
  await deleteDog(fido.id);
​
  // Print the list of dogs (empty).
  print(await dogs());
}
​
class Dog {
  final int id;
  final String name;
  final int age;
​
  Dog({required this.id, required this.name, required this.age});
​
  // Convert a Dog into a Map. The keys must correspond to the names of the
  // columns in the database.
  Map<String, Object?> toMap() {
    return {'id': id, 'name': name, 'age': age};
  }
​
  // 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}';
  }
}
```

## Use relationships between tables

[#](#use-relationships-between-tables)

In real-world applications, you often need to model relationships between tables.

For example, instead of storing all data in a single table, you can separate related data into multiple tables and connect them using foreign keys.

sql

```
CREATE TABLE breeds(
  id INTEGER PRIMARY KEY,
  name TEXT
);

You can execute these statements using the `db.execute()` method from the `sqflite` package.

CREATE TABLE dogs(
  id INTEGER PRIMARY KEY,
  name TEXT,
  age INTEGER,
  breed_id INTEGER,
  FOREIGN KEY (breed_id) REFERENCES breeds(id)
);
```

In this example, each dog is associated with a breed using the `breed_id` field. This allows you to organize data more efficiently and avoid duplication.

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-10-13. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/persistence/sqlite.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/cookbook/persistence/sqlite\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/persistence/sqlite.md "Report an issue with this page").

----
url: https://docs.flutter.dev/add-to-app/performance
----

This page describes the breakdown of the steps involved to show a Flutter UI. Knowing this, you can make better, more informed decisions about when to pre-warm the Flutter engine, which operations are possible at which stage, and the latency and memory costs of those operations.

## Loading Flutter

[#](#loading-flutter)

Android and iOS apps (the two supported platforms for integrating into existing apps), full Flutter apps, and add-to-app patterns have a similar sequence of conceptual loading steps when displaying the Flutter UI.

### Finding the Flutter resources

[#](#finding-the-flutter-resources)

Flutter's engine runtime and your application's compiled Dart code are both bundled as shared libraries on Android and iOS. The first step of loading Flutter is to find those resources in your .apk/.ipa/.app (along with other Flutter assets such as images, fonts, and JIT code, if applicable).

This happens when you construct a `FlutterEngine` for the first time on both **[Android](https://api.flutter.dev/javadoc/io/flutter/embedding/engine/FlutterEngine.html)** and **[iOS](https://api.flutter.dev/ios-embedder/interface_flutter_engine.html)** APIs.

### Loading the Flutter library

[#](#loading-the-flutter-library)

After it's found, the engine's shared libraries are memory loaded once per process.

On **Android**, this also happens when the [`FlutterEngine`](https://api.flutter.dev/javadoc/io/flutter/embedding/engine/FlutterEngine.html) is constructed because the JNI connectors need to reference the Flutter C++ library. On **iOS**, this happens when the [`FlutterEngine`](https://api.flutter.dev/ios-embedder/interface_flutter_engine.html) is first run, such as by running [`runWithEntrypoint:`](https://api.flutter.dev/ios-embedder/interface_flutter_engine.html#a019d6b3037eff6cfd584fb2eb8e9035e).

### Starting the Dart VM

[#](#starting-the-dart-vm)

The Dart runtime is responsible for managing Dart memory and concurrency for your Dart code. In JIT mode, it's additionally responsible for compiling the Dart source code into machine code during runtime.

A single Dart runtime exists per application session on Android and iOS.

A one-time Dart VM start is done when constructing the [`FlutterEngine`](https://api.flutter.dev/javadoc/io/flutter/embedding/engine/FlutterEngine.html) for the first time on **Android** and when [running a Dart entrypoint](https://api.flutter.dev/ios-embedder/interface_flutter_engine.html) for the first time on **iOS**.

At this point, your Dart code's [snapshot](https://github.com/dart-lang/sdk/wiki/Snapshots) is also loaded into memory from your application's files.

This is a generic process that also occurs if you used the [Dart SDK](https://dart.dev/tools/sdk) directly, without the Flutter engine.

The Dart VM never shuts down after it's started.

### Creating and running a Dart Isolate

[#](#creating-and-running-a-dart-isolate)

After the Dart runtime is initialized, the Flutter engine's usage of the Dart runtime is the next step.

This is done by starting a [Dart `Isolate`](https://api.dart.dev/dart-isolate/Isolate-class.html) in the Dart runtime. The isolate is Dart's container for memory and threads. A number of [auxiliary threads](https://github.com/flutter/flutter/blob/main/docs/about/The-Engine-architecture.md#threading) on the host platform are also created at this point to support the isolate, such as a thread for offloading GPU handling and another for image decoding.

One isolate exists per `FlutterEngine` instance, and multiple isolates can be hosted by the same Dart VM.

On **Android**, this happens when you call [`DartExecutor.executeDartEntrypoint()`](https://api.flutter.dev/javadoc/io/flutter/embedding/engine/dart/DartExecutor.html#executeDartEntrypoint-io.flutter.embedding.engine.dart.DartExecutor.DartEntrypoint-) on a `FlutterEngine` instance.

On **iOS**, this happens when you call [`runWithEntrypoint:`](https://api.flutter.dev/ios-embedder/interface_flutter_engine.html#a019d6b3037eff6cfd584fb2eb8e9035e) on a `FlutterEngine`.

At this point, your Dart code's selected entrypoint (the `main()` function of your Dart library's `main.dart` file, by default) is executed. If you called the Flutter function [`runApp()`](https://api.flutter.dev/flutter/widgets/runApp.html) in your `main()` function, then your Flutter app or your library's widget tree is also created and built. If you need to prevent certain functionalities from executing in your Flutter code, then the `AppLifecycleState.detached` enum value indicates that the `FlutterEngine` isn't attached to any UI components such as a `FlutterViewController` on iOS or a `FlutterActivity` on Android.

### Attaching a UI to the Flutter engine

[#](#attaching-a-ui-to-the-flutter-engine)

A standard, full Flutter app moves to reach this state as soon as the app is launched.

In an add-to-app scenario, this happens when you attach a `FlutterEngine` to a UI component such as by calling [`startActivity()`](https://developer.android.com/reference/android/content/Context#startActivity\(android.content.Intent\)) with an [`Intent`](https://developer.android.com/reference/android/content/Intent.html) built using [`FlutterActivity.withCachedEngine()`](https://api.flutter.dev/javadoc/io/flutter/embedding/android/FlutterActivity.html#withCachedEngine-java.lang.String-) on **Android**. Or, by presenting a [`FlutterViewController`](https://api.flutter.dev/ios-embedder/interface_flutter_view_controller.html) initialized by using [`initWithEngine: nibName: bundle:`](https://api.flutter.dev/ios-embedder/interface_flutter_view_controller.html#a0aeea9525c569d5efbd359e2d95a7b31) on **iOS**.

This is also the case if a Flutter UI component was launched without pre-warming a `FlutterEngine` such as with [`FlutterActivity.createDefaultIntent()`](https://api.flutter.dev/javadoc/io/flutter/embedding/android/FlutterActivity.html#createDefaultIntent-android.content.Context-) on **Android**, or with [`FlutterViewController initWithProject: nibName: bundle:`](https://api.flutter.dev/ios-embedder/interface_flutter_view_controller.html#aa3aabfb89e958602ce6a6690c919f655) on **iOS**. An implicit `FlutterEngine` is created in these cases.

Behind the scene, both platform's UI components provide the `FlutterEngine` with a rendering surface such as a [`Surface`](https://developer.android.com/reference/android/view/Surface) on **Android** or a [CAEAGLLayer](https://developer.apple.com/documentation/quartzcore/caeagllayer) or [CAMetalLayer](https://developer.apple.com/documentation/quartzcore/cametallayer) on **iOS**.

At this point, the [`Layer`](https://api.flutter.dev/flutter/rendering/Layer-class.html) tree generated by your Flutter program, per frame, is converted into OpenGL (or Vulkan or Metal) GPU instructions.

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-07-31. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/add-to-app/performance.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/add-to-app/performance\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/add-to-app/performance.md "Report an issue with this page").

----
url: https://docs.flutter.dev/tools/devtools/cli.md
----

\# Run DevTools from the command line > Learn how to launch and use DevTools from the command line. To run DevTools from the CLI, you must have \`dart\` on your path. Then to launch DevTools, run the \`dart devtools\` command. To upgrade DevTools, upgrade Flutter. If a newer Dart SDK (which is included in the Flutter SDK) has a newer version of DevTools, running \`dart devtools\` automatically launches this version. If \`which dart\` points to a Dart SDK \_not\_ included in your Flutter SDK, updating that Dart SDK won't update the Flutter version. When you run DevTools from the command line, you should see output that looks something like: \`\`\`plaintext Serving DevTools at http\://127.0.0.1:9100 \`\`\` ## Start an application to debug Next, start an app to connect to. This can be either a Flutter application or a Dart command-line application. The command below specifies a Flutter app: \`\`\`console cd path/to/flutter/app flutter run \`\`\` You need to have a device connected, or a simulator open, for \`flutter run\` to work. Once the app starts, you'll see a message in your terminal that looks like the following: \`\`\`console A Dart VM Service on macOS is available at: http\://127.0.0.1:51830/u37pq71Re0k=/ The Flutter DevTools debugger and profiler on macOS is available at: http\://127.0.0.1:9100?uri=http\://127.0.0.1:51830/u37pq71Re0k=/ \`\`\` Open the DevTools instance connected to your app by opening the second link in Chrome. This URL contains a security token, so it's different for each run of your app. This means that if you stop your application and re-run it, you need to connect to DevTools again with the new URL. ## Connect to a new app instance If your app stops running or you opened DevTools manually, you should see a \*\*Connect\*\* dialog: !\[Screenshot of the DevTools connect dialog]\(/assets/images/docs/tools/devtools/connect\_dialog.png){:width="100%"} You can manually connect DevTools to a new app instance by copying the link you got from running your app, such as \` http\://127.0.0.1:51830/u37pq71Re0k=/\`

----
url: https://docs.flutter.dev/cookbook/networking/delete-data
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Delete data on the internet](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

This recipe covers how to delete data over the internet using the `http` package.

This recipe uses the following steps:

1. Add the `http` package.
2. Delete data on the server.
3. Update the screen.

## 1. Add the `http` package

[#](#1-add-the-http-package)

To add the `http` package as a dependency, run `flutter pub add`:

```
$ flutter pub add http
```

Import the `http` package.

dart

```
import 'package:http/http.dart' as http;
import 'package:material_ui/material_ui.dart';
```

If you are deploying to Android, edit your `AndroidManifest.xml` file to add the Internet permission.

xml

```
<!-- Required to fetch data from the internet. -->
<uses-permission android:name="android.permission.INTERNET" />
```

Likewise, if you are deploying to macOS, edit your `macos/Runner/DebugProfile.entitlements` and `macos/Runner/Release.entitlements` files to include the network client entitlement.

xml

```
<!-- Required to fetch data from the internet. -->
<key>com.apple.security.network.client</key>
<true/>
```

## 2. Delete data on the server

[#](#2-delete-data-on-the-server)

This recipe covers how to delete an album from the [JSONPlaceholder](https://jsonplaceholder.typicode.com/) using the `http.delete()` method. Note that this requires the `id` of the album that you want to delete. For this example, use something you already know, for example `id = 1`.

dart

```
Future<http.Response> deleteAlbum(String id) async {
  final http.Response response = await http.delete(
    Uri.parse('https://jsonplaceholder.typicode.com/albums/$id'),
    headers: <String, String>{
      'Content-Type': 'application/json; charset=UTF-8',
    },
  );
​
  return response;
}
```

The `http.delete()` method returns a `Future` that contains a `Response`.

* [`Future`](https://api.flutter.dev/flutter/dart-async/Future-class.html) is a core Dart class for working with async operations. A Future object represents a potential value or error that will be available at some time in the future.
* The `http.Response` class contains the data received from a successful http call.
* The `deleteAlbum()` method takes an `id` argument that is needed to identify the data to be deleted from the server.

## 3. Update the screen

[#](#3-update-the-screen)

In order to check whether the data has been deleted or not, first fetch the data from the [JSONPlaceholder](https://jsonplaceholder.typicode.com/) using the `http.get()` method, and display it in the screen. (See the [Fetch Data](/cookbook/networking/fetch-data) recipe for a complete example.) You should now have a **Delete Data** button that, when pressed, calls the `deleteAlbum()` method.

dart

```
Column(
  mainAxisAlignment: MainAxisAlignment.center,
  children: <Widget>[
    Text(snapshot.data?.title ?? 'Deleted'),
    ElevatedButton(
      child: const Text('Delete Data'),
      onPressed: () {
        setState(() {
          _futureAlbum = deleteAlbum(
            snapshot.data!.id.toString(),
          );
        });
      },
    ),
  ],
);
```

Now, when you click on the ***Delete Data*** button, the `deleteAlbum()` method is called and the id you are passing is the id of the data that you retrieved from the internet. This means you are going to delete the same data that you fetched from the internet.

### Returning a response from the deleteAlbum() method

[#](#returning-a-response-from-the-deletealbum-method)

Once the delete request has been made, you can return a response from the `deleteAlbum()` method to notify our screen that the data has been deleted.

dart

```
Future<Album> deleteAlbum(String id) async {
  final http.Response response = await http.delete(
    Uri.parse('https://jsonplaceholder.typicode.com/albums/$id'),
    headers: <String, String>{
      'Content-Type': 'application/json; charset=UTF-8',
    },
  );
​
  if (response.statusCode == 200) {
    // If the server did return a 200 OK response,
    // then return an empty Album. After deleting,
    // you'll get an empty JSON `{}` response.
    // Don't return `null`, otherwise `snapshot.hasData`
    // will always return false on `FutureBuilder`.
    return Album.empty();
  } else {
    // If the server did not return a "200 OK response",
    // then throw an exception.
    throw Exception('Failed to delete album.');
  }
}
```

`FutureBuilder()` now rebuilds when it receives a response. Since the response won't have any data in its body if the request was successful, the `Album.fromJson()` method creates an instance of the `Album` object with a default value (`null` in our case). This behavior can be altered in any way you wish.

That's all! Now you've got a function that deletes the data from the internet.

## Complete example

[#](#complete-example)

dart

```
import 'dart:async';
import 'dart:convert';
​
import 'package:http/http.dart' as http;
import 'package:material_ui/material_ui.dart';
​
Future<Album> fetchAlbum() async {
  final response = await http.get(
    Uri.parse('https://jsonplaceholder.typicode.com/albums/1'),
  );
​
  if (response.statusCode == 200) {
    // If the server did return a 200 OK response, then parse the JSON.
    return Album.fromJson(jsonDecode(response.body) as Map<String, dynamic>);
  } else {
    // If the server did not return a 200 OK response, then throw an exception.
    throw Exception('Failed to load album');
  }
}
​
Future<Album> deleteAlbum(String id) async {
  final http.Response response = await http.delete(
    Uri.parse('https://jsonplaceholder.typicode.com/albums/$id'),
    headers: <String, String>{
      'Content-Type': 'application/json; charset=UTF-8',
    },
  );
​
  if (response.statusCode == 200) {
    // If the server did return a 200 OK response,
    // then return an empty Album. After deleting,
    // you'll get an empty JSON `{}` response.
    // Don't return `null`, otherwise `snapshot.hasData`
    // will always return false on `FutureBuilder`.
    return Album.empty();
  } else {
    // If the server did not return a "200 OK response",
    // then throw an exception.
    throw Exception('Failed to delete album.');
  }
}
​
class Album {
  int? id;
  String? title;
​
  Album({this.id, this.title});
​
  Album.empty();
​
  factory Album.fromJson(Map<String, dynamic> json) {
    return switch (json) {
      {'id': int id, 'title': String title} => Album(id: id, title: title),
      _ => throw const FormatException('Failed to load album.'),
    };
  }
}
​
void main() {
  runApp(const MyApp());
}
​
class MyApp extends StatefulWidget {
  const MyApp({super.key});
​
  @override
  State<MyApp> createState() {
    return _MyAppState();
  }
}
​
class _MyAppState extends State<MyApp> {
  late Future<Album> _futureAlbum;
​
  @override
  void initState() {
    super.initState();
    _futureAlbum = fetchAlbum();
  }
​
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Delete Data Example',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
      ),
      home: Scaffold(
        appBar: AppBar(title: const Text('Delete Data Example')),
        body: Center(
          child: FutureBuilder<Album>(
            future: _futureAlbum,
            builder: (context, snapshot) {
              // If the connection is done,
              // check for response data or an error.
              if (snapshot.connectionState == ConnectionState.done) {
                if (snapshot.hasData) {
                  return Column(
                    mainAxisAlignment: MainAxisAlignment.center,
                    children: <Widget>[
                      Text(snapshot.data?.title ?? 'Deleted'),
                      ElevatedButton(
                        child: const Text('Delete Data'),
                        onPressed: () {
                          setState(() {
                            _futureAlbum = deleteAlbum(
                              snapshot.data!.id.toString(),
                            );
                          });
                        },
                      ),
                    ],
                  );
                } else if (snapshot.hasError) {
                  return Text('${snapshot.error}');
                }
              }
​
              // By default, show a loading spinner.
              return const CircularProgressIndicator();
            },
          ),
        ),
      ),
    );
  }
}
```

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-09-29. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/networking/delete-data.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/cookbook/networking/delete-data\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/networking/delete-data.md "Report an issue with this page").

----
url: https://docs.flutter.dev/cookbook/effects/expandable-fab
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Create an expandable FAB](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

A Floating Action Button (FAB) is a round button that floats near the bottom right of a content area. This button represents the primary action for the corresponding content, but sometimes, there is no primary action. Instead, there are a few critical actions that the user might take. In this case, you could create an expandable FAB like the one shown in the following figure. When pressed, this expandable FAB spawns multiple, other action buttons. Each button corresponds to one of those critical actions.

The following animation shows the app's behavior:

## Create an ExpandableFab widget

[#](#create-an-expandablefab-widget)

Start by creating a new stateful widget called `ExpandableFab`. This widget displays the primary FAB and coordinates the expansion and collapse of the other action buttons. The widget takes in parameters for whether or not the `ExpandedFab` begins in the expanded position, what the maximum distance of each action button is, and a list of children. You'll use the list later to provide the other action buttons.

dart

```
@immutable
class ExpandableFab extends StatefulWidget {
  const ExpandableFab({
    super.key,
    this.initialOpen,
    required this.distance,
    required this.children,
  });
​
  final bool? initialOpen;
  final double distance;
  final List<Widget> children;
​
  @override
  State<ExpandableFab> createState() => _ExpandableFabState();
}
​
class _ExpandableFabState extends State<ExpandableFab> {
  @override
  Widget build(BuildContext context) {
    return const SizedBox();
  }
}
```

## FAB cross-fade

[#](#fab-cross-fade)

The `ExpandableFab` displays a blue edit button when collapsed and a white close button when expanded. When expanding and collapsing, these two buttons scale and fade between one another.

Implement the expand and collapse cross-fade between the two different FABs.

dart

```
class _ExpandableFabState extends State<ExpandableFab> {
  bool _open = false;
​
  @override
  void initState() {
    super.initState();
    _open = widget.initialOpen ?? false;
  }
​
  void _toggle() {
    setState(() {
      _open = !_open;
    });
  }
​
  @override
  Widget build(BuildContext context) {
    return SizedBox.expand(
      child: Stack(
        alignment: Alignment.bottomRight,
        clipBehavior: Clip.none,
        children: [_buildTapToCloseFab(), _buildTapToOpenFab()],
      ),
    );
  }
​
  Widget _buildTapToCloseFab() {
    return SizedBox(
      width: 56,
      height: 56,
      child: Center(
        child: Material(
          shape: const CircleBorder(),
          clipBehavior: Clip.antiAlias,
          elevation: 4,
          child: InkWell(
            onTap: _toggle,
            child: Padding(
              padding: const EdgeInsets.all(8),
              child: Icon(Icons.close, color: Theme.of(context).primaryColor),
            ),
          ),
        ),
      ),
    );
  }
​
  Widget _buildTapToOpenFab() {
    return IgnorePointer(
      ignoring: _open,
      child: AnimatedContainer(
        transformAlignment: Alignment.center,
        transform: Matrix4.diagonal3Values(
          _open ? 0.7 : 1.0,
          _open ? 0.7 : 1.0,
          1.0,
        ),
        duration: const Duration(milliseconds: 250),
        curve: const Interval(0.0, 0.5, curve: Curves.easeOut),
        child: AnimatedOpacity(
          opacity: _open ? 0.0 : 1.0,
          curve: const Interval(0.25, 1.0, curve: Curves.easeInOut),
          duration: const Duration(milliseconds: 250),
          child: FloatingActionButton(
            onPressed: _toggle,
            child: const Icon(Icons.create),
          ),
        ),
      ),
    );
  }
}
```

The open button sits on top of the close button within a `Stack`, allowing for the visual appearance of a cross-fade as the top button appears and disappears.

To achieve the cross-fade animation, the open button uses an `AnimatedContainer` with a scale transform and an `AnimatedOpacity`. The open button scales down and fades out when the `ExpandableFab` goes from collapsed to expanded. Then, the open button scales up and fades in when the `ExpandableFab` goes from expanded to collapsed.

You'll notice that the open button is wrapped with an `IgnorePointer` widget. This is because the open button always exists, even when it's transparent. Without the `IgnorePointer`, the open button always receives the tap event, even when the close button is visible.

## Create an ActionButton widget

[#](#create-an-actionbutton-widget)

Each of the buttons that expand from the `ExpandableFab` have the same design. They're blue circles with white icons. More precisely, the button background color is the `ColorScheme.secondary` color, and the icon color is `ColorScheme.onSecondary`.

Define a new stateless widget called `ActionButton` to display these round buttons.

dart

```
@immutable
class ActionButton extends StatelessWidget {
  const ActionButton({super.key, this.onPressed, required this.icon});
​
  final VoidCallback? onPressed;
  final Widget icon;
​
  @override
  Widget build(BuildContext context) {
    final theme = Theme.of(context);
    return Material(
      shape: const CircleBorder(),
      clipBehavior: Clip.antiAlias,
      color: theme.colorScheme.secondary,
      elevation: 4,
      child: IconButton(
        onPressed: onPressed,
        icon: icon,
        color: theme.colorScheme.onSecondary,
      ),
    );
  }
}
```

Pass a few instances of this new `ActionButton` widget into your `ExpandableFab`.

dart

```
floatingActionButton: ExpandableFab(
  distance: 112,
  children: [
    ActionButton(
      onPressed: () => _showAction(context, 0),
      icon: const Icon(Icons.format_size),
    ),
    ActionButton(
      onPressed: () => _showAction(context, 1),
      icon: const Icon(Icons.insert_photo),
    ),
    ActionButton(
      onPressed: () => _showAction(context, 2),
      icon: const Icon(Icons.videocam),
    ),
  ],
),
```

## Expand and collapse the action buttons

[#](#expand-and-collapse-the-action-buttons)

The child `ActionButton`s should fly out from under the open FAB when expanded. Then, the child `ActionButton`s should fly back under the open FAB when collapsed. This motion requires explicit (x,y) positioning of each `ActionButton` and an `Animation` to choreograph changes to those (x,y) positions over time.

Introduce an `AnimationController` and an `Animation` to control the rate at which the various `ActionButton`s expand and collapse.

dart

```
class _ExpandableFabState extends State<ExpandableFab>
    with SingleTickerProviderStateMixin {
  late final AnimationController _controller;
  late final Animation<double> _expandAnimation;
  bool _open = false;
​
  @override
  void initState() {
    super.initState();
    _open = widget.initialOpen ?? false;
    _controller = AnimationController(
      value: _open ? 1.0 : 0.0,
      duration: const Duration(milliseconds: 250),
      vsync: this,
    );
    _expandAnimation = CurvedAnimation(
      curve: Curves.fastOutSlowIn,
      reverseCurve: Curves.easeOutQuad,
      parent: _controller,
    );
  }
​
  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }
​
  void _toggle() {
    setState(() {
      _open = !_open;
      if (_open) {
        _controller.forward();
      } else {
        _controller.reverse();
      }
    });
  }
  }
```

Next, introduce a new stateless widget called `_ExpandingActionButton`, and configure this widget to animate and position an individual `ActionButton`. The `ActionButton` is provided as a generic `Widget` called `child`.

dart

```
@immutable
class _ExpandingActionButton extends StatelessWidget {
  const _ExpandingActionButton({
    required this.directionInDegrees,
    required this.maxDistance,
    required this.progress,
    required this.child,
  });
​
  final double directionInDegrees;
  final double maxDistance;
  final Animation<double> progress;
  final Widget child;
​
  @override
  Widget build(BuildContext context) {
    return AnimatedBuilder(
      animation: progress,
      builder: (context, child) {
        final offset = Offset.fromDirection(
          directionInDegrees * (math.pi / 180.0),
          progress.value * maxDistance,
        );
        return Positioned(
          right: 4.0 + offset.dx,
          bottom: 4.0 + offset.dy,
          child: Transform.rotate(
            angle: (1.0 - progress.value) * math.pi / 2,
            child: child!,
          ),
        );
      },
      child: FadeTransition(opacity: progress, child: child),
    );
  }
}
```

The most important part of `_ExpandingActionButton` is the `Positioned` widget, which positions the `child` at a specific (x,y) coordinate within the surrounding `Stack`. The `AnimatedBuilder` causes the `Positioned` widget to rebuild every time the animation changes. The `FadeTransition` widget orchestrates the appearance and disappearance of each `ActionButton` as they expand and collapse, respectively.

Finally, use the new `_ExpandingActionButton` widget within the `ExpandableFab` to complete the exercise.

dart

```
class _ExpandableFabState extends State<ExpandableFab>
    with SingleTickerProviderStateMixin {
  @override
  Widget build(BuildContext context) {
    return SizedBox.expand(
      child: Stack(
        alignment: Alignment.bottomRight,
        clipBehavior: Clip.none,
        children: [
          _buildTapToCloseFab(),
          ..._buildExpandingActionButtons(),
          _buildTapToOpenFab(),
        ],
      ),
    );
  }
​
  List<Widget> _buildExpandingActionButtons() {
    final children = <Widget>[];
    final count = widget.children.length;
    final step = 90.0 / (count - 1);
    for (
      var i = 0, angleInDegrees = 0.0;
      i < count;
      i++, angleInDegrees += step
    ) {
      children.add(
        _ExpandingActionButton(
          directionInDegrees: angleInDegrees,
          maxDistance: widget.distance,
          progress: _expandAnimation,
          child: widget.children[i],
        ),
      );
    }
    return children;
  }
  }
```

Congratulations! You now have an expandable FAB.

## Interactive example

[#](#interactive-example)

Run the app:

* Click the FAB in the lower-right corner, represented with an Edit icon. It fans out to 3 buttons and is itself replaced by a close button, represented by an **X**.
* Click the close button to see the expanded buttons fly back to the original FAB and the **X** is replaced by the Edit icon.
* Expand the FAB again, and click on any of the 3 satellite buttons to see a dialog representing that button's action.

```
import 'dart:math' as math;

import 'package:flutter/material.dart';

void main() {
  runApp(
    const MaterialApp(
      home: ExampleExpandableFab(),
      debugShowCheckedModeBanner: false,
    ),
  );
}

@immutable
class ExampleExpandableFab extends StatelessWidget {
  static const _actionTitles = ['Create Post', 'Upload Photo', 'Upload Video'];

  const ExampleExpandableFab({super.key});

  void _showAction(BuildContext context, int index) {
    showDialog<void>(
      context: context,
      builder: (context) {
        return AlertDialog(
          content: Text(_actionTitles[index]),
          actions: [
            TextButton(
              onPressed: () => Navigator.of(context).pop(),
              child: const Text('CLOSE'),
            ),
          ],
        );
      },
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Expandable Fab')),
      body: ListView.builder(
        padding: const EdgeInsets.symmetric(vertical: 8),
        itemCount: 25,
        itemBuilder: (context, index) {
          return FakeItem(isBig: index.isOdd);
        },
      ),
      floatingActionButton: ExpandableFab(
        distance: 112,
        children: [
          ActionButton(
            onPressed: () => _showAction(context, 0),
            icon: const Icon(Icons.format_size),
          ),
          ActionButton(
            onPressed: () => _showAction(context, 1),
            icon: const Icon(Icons.insert_photo),
          ),
          ActionButton(
            onPressed: () => _showAction(context, 2),
            icon: const Icon(Icons.videocam),
          ),
        ],
      ),
    );
  }
}

@immutable
class ExpandableFab extends StatefulWidget {
  const ExpandableFab({
    super.key,
    this.initialOpen,
    required this.distance,
    required this.children,
  });

  final bool? initialOpen;
  final double distance;
  final List<Widget> children;

  @override
  State<ExpandableFab> createState() => _ExpandableFabState();
}

class _ExpandableFabState extends State<ExpandableFab>
    with SingleTickerProviderStateMixin {
  late final AnimationController _controller;
  late final Animation<double> _expandAnimation;
  bool _open = false;

  @override
  void initState() {
    super.initState();
    _open = widget.initialOpen ?? false;
    _controller = AnimationController(
      value: _open ? 1.0 : 0.0,
      duration: const Duration(milliseconds: 250),
      vsync: this,
    );
    _expandAnimation = CurvedAnimation(
      curve: Curves.fastOutSlowIn,
      reverseCurve: Curves.easeOutQuad,
      parent: _controller,
    );
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  void _toggle() {
    setState(() {
      _open = !_open;
      if (_open) {
        _controller.forward();
      } else {
        _controller.reverse();
      }
    });
  }

  @override
  Widget build(BuildContext context) {
    return SizedBox.expand(
      child: Stack(
        alignment: Alignment.bottomRight,
        clipBehavior: Clip.none,
        children: [
          _buildTapToCloseFab(),
          ..._buildExpandingActionButtons(),
          _buildTapToOpenFab(),
        ],
      ),
    );
  }

  Widget _buildTapToCloseFab() {
    return SizedBox(
      width: 56,
      height: 56,
      child: Center(
        child: Material(
          shape: const CircleBorder(),
          clipBehavior: Clip.antiAlias,
          elevation: 4,
          child: InkWell(
            onTap: _toggle,
            child: Padding(
              padding: const EdgeInsets.all(8),
              child: Icon(Icons.close, color: Theme.of(context).primaryColor),
            ),
          ),
        ),
      ),
    );
  }

  List<Widget> _buildExpandingActionButtons() {
    final children = <Widget>[];
    final count = widget.children.length;
    final step = 90.0 / (count - 1);
    for (
      var i = 0, angleInDegrees = 0.0;
      i < count;
      i++, angleInDegrees += step
    ) {
      children.add(
        _ExpandingActionButton(
          directionInDegrees: angleInDegrees,
          maxDistance: widget.distance,
          progress: _expandAnimation,
          child: widget.children[i],
        ),
      );
    }
    return children;
  }

  Widget _buildTapToOpenFab() {
    return IgnorePointer(
      ignoring: _open,
      child: AnimatedContainer(
        transformAlignment: Alignment.center,
        transform: Matrix4.diagonal3Values(
          _open ? 0.7 : 1.0,
          _open ? 0.7 : 1.0,
          1.0,
        ),
        duration: const Duration(milliseconds: 250),
        curve: const Interval(0.0, 0.5, curve: Curves.easeOut),
        child: AnimatedOpacity(
          opacity: _open ? 0.0 : 1.0,
          curve: const Interval(0.25, 1.0, curve: Curves.easeInOut),
          duration: const Duration(milliseconds: 250),
          child: FloatingActionButton(
            onPressed: _toggle,
            child: const Icon(Icons.create),
          ),
        ),
      ),
    );
  }
}

@immutable
class _ExpandingActionButton extends StatelessWidget {
  const _ExpandingActionButton({
    required this.directionInDegrees,
    required this.maxDistance,
    required this.progress,
    required this.child,
  });

  final double directionInDegrees;
  final double maxDistance;
  final Animation<double> progress;
  final Widget child;

  @override
  Widget build(BuildContext context) {
    return AnimatedBuilder(
      animation: progress,
      builder: (context, child) {
        final offset = Offset.fromDirection(
          directionInDegrees * (math.pi / 180.0),
          progress.value * maxDistance,
        );
        return Positioned(
          right: 4.0 + offset.dx,
          bottom: 4.0 + offset.dy,
          child: Transform.rotate(
            angle: (1.0 - progress.value) * math.pi / 2,
            child: child!,
          ),
        );
      },
      child: FadeTransition(opacity: progress, child: child),
    );
  }
}

@immutable
class ActionButton extends StatelessWidget {
  const ActionButton({super.key, this.onPressed, required this.icon});

  final VoidCallback? onPressed;
  final Widget icon;

  @override
  Widget build(BuildContext context) {
    final theme = Theme.of(context);
    return Material(
      shape: const CircleBorder(),
      clipBehavior: Clip.antiAlias,
      color: theme.colorScheme.secondary,
      elevation: 4,
      child: IconButton(
        onPressed: onPressed,
        icon: icon,
        color: theme.colorScheme.onSecondary,
      ),
    );
  }
}

@immutable
class FakeItem extends StatelessWidget {
  const FakeItem({super.key, required this.isBig});

  final bool isBig;

  @override
  Widget build(BuildContext context) {
    return Container(
      margin: const EdgeInsets.symmetric(vertical: 8, horizontal: 24),
      height: isBig ? 128 : 36,
      decoration: BoxDecoration(
        borderRadius: const BorderRadius.all(Radius.circular(8)),
        color: Colors.grey.shade300,
      ),
    );
  }
}
```

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/effects/expandable-fab.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/cookbook/effects/expandable-fab\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/effects/expandable-fab.md "Report an issue with this page").

----
url: https://docs.flutter.dev/cookbook/lists/grid-lists.md
----

\# Create a grid list > How to implement a grid list.

<!--?code-excerpt path-base="cookbook/lists/grid_lists"?-->

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\`]\[] widget. The simplest way to get started using grids is by using the \[\`GridView\.count()\`]\[] constructor, because it allows you to specify how many rows or columns you'd like. To visualize how \`GridView\` works, generate a list of 100 widgets that display their index in the list.

<!--?code-excerpt "lib/main.dart (GridView)" replace="/^body\: //g"?-->

\`\`\`dart GridView\.count( // Create a grid with 2 columns. // If you change the scrollDirection to horizontal, // this produces 2 rows. crossAxisCount: 2, // Generate 100 widgets that display their index in the list. children: List.generate(100, (index) { return Center( child: Text( 'Item $index', style: TextTheme.of(context).headlineSmall, ), ); }), ), \`\`\` ## Interactive example

<!--?code-excerpt "lib/main.dart"?-->

\`\`\`dartpad title="Flutter GridView hands-on example in DartPad" run="true" import 'package:flutter/material.dart'; void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { const title = 'Grid List'; return MaterialApp( title: title, home: Scaffold( appBar: AppBar(title: const Text(title)), body: GridView\.count( // Create a grid with 2 columns. // If you change the scrollDirection to horizontal, // this produces 2 rows. crossAxisCount: 2, // Generate 100 widgets that display their index in the list. children: List.generate(100, (index) { return Center( child: Text( 'Item $index', style: TextTheme.of(context).headlineSmall, ), ); }), ), ), ); } } \`\`\` \[\`GridView\`]: https\://api.flutter.dev/flutter/widgets/GridView-class.html \[\`GridView\.count()\`]: https\://api.flutter.dev/flutter/widgets/GridView/GridView\.count.html

----
url: https://docs.flutter.dev/ui/interactivity/actions-and-shortcuts.md
----

\# Using Actions and Shortcuts > How to use Actions and Shortcuts in your Flutter app. This page describes how to bind physical keyboard events to actions in the user interface. For instance, to define keyboard shortcuts in your application, this page is for you. ## Overview For a GUI application to do anything, it has to have actions: users want to tell the application to \_do\_ something. Actions are often simple functions that directly perform the action (such as set a value or save a file). In a larger application, however, things are more complex: the code for invoking the action, and the code for the action itself might need to be in different places. Shortcuts (key bindings) might need definition at a level that knows nothing about the actions they invoke. That's where Flutter's actions and shortcuts system comes in. It allows developers to define actions that fulfill intents bound to them. In this context, an intent is a generic action that the user wishes to perform, and an \[\`Intent\`]\[] class instance represents these user intents in Flutter. An \`Intent\` can be general purpose, fulfilled by different actions in different contexts. An \[\`Action\`]\[] can be a simple callback (as in the case of the \[\`CallbackAction\`]\[]) or something more complex that integrates with entire undo/redo architectures (for example) or other logic. !\[Using Shortcuts Diagram]\[]{:width="100%" .diagram-wrap} \[\`Shortcuts\`]\[] are key bindings that activate by pressing a key or combination of keys. The key combinations reside in a table with their bound intent. When the \`Shortcuts\` widget invokes them, it sends their matching intent to the actions subsystem for fulfillment. To illustrate the concepts in actions and shortcuts, this article creates a simple app that allows a user to select and copy text in a text field using both buttons and shortcuts. ### Why separate Actions from Intents? You might wonder: why not just map a key combination directly to an action? Why have intents at all? This is because it is useful to have a separation of concerns between where the key mapping definitions are (often at a high level), and where the action definitions are (often at a low level), and because it is important to be able to have a single key combination map to an intended operation in an app, and have it adapt automatically to whichever action fulfills that intended operation for the focused context. For instance, Flutter has an \`ActivateIntent\` widget that maps each type of control to its corresponding version of an \`ActivateAction\` (and that executes the code that activates the control). This code often needs fairly private access to do its work. If the extra layer of indirection that \`Intent\`s provide didn't exist, it would be necessary to elevate the definition of the actions to where the defining instance of the \`Shortcuts\` widget could see them, causing the shortcuts to have more knowledge than necessary about which action to invoke, and to have access to or provide state that it wouldn't necessarily have or need otherwise. This allows your code to separate the two concerns to be more independent. Intents configure an action so that the same action can serve multiple uses. An example of this is \`DirectionalFocusIntent\`, which takes a direction to move the focus in, allowing the \`DirectionalFocusAction\` to know which direction to move the focus. Just be careful: don't pass state in the \`Intent\` that applies to all invocations of an \`Action\`: that kind of state should be passed to the constructor of the \`Action\` itself, to keep the \`Intent\` from needing to know too much. ### Why not use callbacks? You also might wonder: why not just use a callback instead of an \`Action\` object? The main reason is that it's useful for actions to decide whether they are enabled by implementing \`isEnabled\`. Also, it is often helpful if the key bindings, and the implementation of those bindings, are in different places. If all you need are callbacks without the flexibility of \`Actions\` and \`Shortcuts\`, you can use the \[\`CallbackShortcuts\`]\[] widget:

<!--?code-excerpt "ui/actions_and_shortcuts/lib/samples.dart (callback-shortcuts)"?-->

\`\`\`dart @override Widget build(BuildContext context) { return CallbackShortcuts( bindings: { const SingleActivator(LogicalKeyboardKey.arrowUp): () { setState(() => count = count + 1); }, const SingleActivator(LogicalKeyboardKey.arrowDown): () { setState(() => count = count - 1); }, }, child: Focus( autofocus: true, child: Column( children: \[ const Text('Press the up arrow key to add to the counter'), const Text('Press the down arrow key to subtract from the counter'), Text('count: $count'), ], ), ), ); } \`\`\` ## Shortcuts As you'll see below, actions are useful on their own, but the most common use case involves binding them to a keyboard shortcut. This is what the \`Shortcuts\` widget is for. It is inserted into the widget hierarchy to define key combinations that represent the user's intent when that key combination is pressed. To convert that intended purpose for the key combination into a concrete action, the \`Actions\` widget used to map the \`Intent\` to an \`Action\`. For instance, you can define a \`SelectAllIntent\`, and bind it to your own \`SelectAllAction\` or to your \`CanvasSelectAllAction\`, and from that one key binding, the system invokes either one, depending on which part of your application has focus. Let's see how the key binding part works:

<!--?code-excerpt "ui/actions_and_shortcuts/lib/samples.dart (shortcuts)"?-->

\`\`\`dart @override Widget build(BuildContext context) { return Shortcuts( shortcuts: { LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.keyA): const SelectAllIntent(), }, child: Actions( dispatcher: LoggingActionDispatcher(), actions: >{ SelectAllIntent: SelectAllAction(model), }, child: Builder( builder: (context) => TextButton( onPressed: Actions.handler( context, const SelectAllIntent(), ), child: const Text('SELECT ALL'), ), ), ), ); } \`\`\` The map given to a \`Shortcuts\` widget maps a \`LogicalKeySet\` (or a \`ShortcutActivator\`, see note below) to an \`Intent\` instance. The logical key set defines a set of one or more keys, and the intent indicates the intended purpose of the keypress. The \`Shortcuts\` widget looks up key presses in the map, to find an \`Intent\` instance, which it gives to the action's \`invoke()\` method. :::note \`ShortcutActivator\` is a replacement for \`LogicalKeySet\`. It allows for more flexible and correct activation of shortcuts. \`LogicalKeySet\` is a \`ShortcutActivator\`, of course, but there is also \`SingleActivator\`, which takes a single key and the optional modifiers to be pressed before the key. Then there is \`CharacterActivator\`, which activates a shortcut based on the character produced by a key sequence, instead of the logical keys themselves. \`ShortcutActivator\` is also meant to be subclassed to allow for custom ways of activating shortcuts from key events. ::: ### The ShortcutManager The shortcut manager, a longer-lived object than the \`Shortcuts\` widget, passes on key events when it receives them. It contains the logic for deciding how to handle the keys, the logic for walking up the tree to find other shortcut mappings, and maintains a map of key combinations to intents. While the default behavior of the \`ShortcutManager\` is usually desirable, the \`Shortcuts\` widget takes a \`ShortcutManager\` that you can subclass to customize its functionality. For example, if you wanted to log each key that a \`Shortcuts\` widget handled, you could make a \`LoggingShortcutManager\`:

<!--?code-excerpt "ui/actions_and_shortcuts/lib/samples.dart (logging-shortcut-manager)"?-->

\`\`\`dart class LoggingShortcutManager extends ShortcutManager { @override KeyEventResult handleKeypress(BuildContext context, KeyEvent event) { final KeyEventResult result = super.handleKeypress(context, event); if (result == KeyEventResult.handled) { print('Handled shortcut $event in $context'); } return result; } } \`\`\` Now, every time the \`Shortcuts\` widget handles a shortcut, it prints out the key event and relevant context. ## Actions \`Actions\` allow for the definition of operations that the application can perform by invoking them with an \`Intent\`. Actions can be enabled or disabled, and receive the intent instance that invoked them as an argument to allow configuration by the intent. ### Defining actions Actions, in their simplest form, are just subclasses of \`Action\` with an \`invoke()\` method. Here's a simple action that simply invokes a function on the provided model:

<!--?code-excerpt "ui/actions_and_shortcuts/lib/samples.dart (select-all-action)"?-->

\`\`\`dart class SelectAllAction extends Action { SelectAllAction(this.model); final Model model; @override void invoke(covariant SelectAllIntent intent) => model.selectAll(); } \`\`\` Or, if it's too much of a bother to create a new class, use a \`CallbackAction\`:

<!--?code-excerpt "ui/actions_and_shortcuts/lib/samples.dart (callback-action)"?-->

\`\`\`dart CallbackAction(onInvoke: (intent) => model.selectAll()); \`\`\` Once you have an action, you add it to your application using the \[\`Actions\`]\[] widget, which takes a map of \`Intent\` types to \`Action\`s:

<!--?code-excerpt "ui/actions_and_shortcuts/lib/samples.dart (select-all-usage)"?-->

\`\`\`dart @override Widget build(BuildContext context) { return Actions( actions: >{SelectAllIntent: SelectAllAction(model)}, child: child, ); } \`\`\` The \`Shortcuts\` widget uses the \`Focus\` widget's context and \`Actions.invoke\` to find which action to invoke. If the \`Shortcuts\` widget doesn't find a matching intent type in the first \`Actions\` widget encountered, it considers the next ancestor \`Actions\` widget, and so on, until it reaches the root of the widget tree, or finds a matching intent type and invokes the corresponding action. ### Invoking Actions The actions system has several ways to invoke actions. By far the most common way is through the use of a \`Shortcuts\` widget covered in the previous section, but there are other ways to interrogate the actions subsystem and invoke an action. It's possible to invoke actions that are not bound to keys. For instance, to find an action associated with an intent, you can use:

<!--?code-excerpt "ui/actions_and_shortcuts/lib/samples.dart (maybe-find)"?-->

\`\`\`dart Action? selectAll = Actions.maybeFind( context, ); \`\`\` This returns an \`Action\` associated with the \`SelectAllIntent\` type if one is available in the given \`context\`. If one isn't available, it returns null. If an associated \`Action\` should always be available, then use \`find\` instead of \`maybeFind\`, which throws an exception when it doesn't find a matching \`Intent\` type. To invoke the action (if it exists), call:

<!--?code-excerpt "ui/actions_and_shortcuts/lib/samples.dart (invoke-action)"?-->

\`\`\`dart Object? result; if (selectAll != null) { result = Actions.of(context) .invokeAction(selectAll, const SelectAllIntent()); } \`\`\` Combine that into one call with the following:

<!--?code-excerpt "ui/actions_and_shortcuts/lib/samples.dart (maybe-invoke)"?-->

\`\`\`dart Object? result = Actions.maybeInvoke( context, const SelectAllIntent(), ); \`\`\` Sometimes you want to invoke an action as a result of pressing a button or another control. You can do this with the \`Actions.handler\` function. If the intent has a mapping to an enabled action, the \`Actions.handler\` function creates a handler closure. However, if it doesn't have a mapping, it returns \`null\`. This allows the button to be disabled if there is no enabled action that matches in the context.

<!--?code-excerpt "ui/actions_and_shortcuts/lib/samples.dart (handler)"?-->

\`\`\`dart @override Widget build(BuildContext context) { return Actions( actions: >{SelectAllIntent: SelectAllAction(model)}, child: Builder( builder: (context) => TextButton( onPressed: Actions.handler( context, SelectAllIntent(controller: controller), ), child: const Text('SELECT ALL'), ), ), ); } \`\`\` The \`Actions\` widget only invokes actions when \`isEnabled(Intent intent)\` returns true, allowing the action to decide if the dispatcher should consider it for invocation. If the action isn't enabled, the action is not invoked and the framework stops searching for matching actions at that point. The previous example uses a \`Builder\` because \`Actions.handler\` and \`Actions.invoke\` (for example) only finds actions in the provided \`context\`, and if the example passes the \`context\` given to the \`build\` function, the framework starts looking \_above\_ the current widget. Using a \`Builder\` allows the framework to find the actions defined in the same \`build\` function. You can invoke an action without needing a \`BuildContext\`, but since the \`Actions\` widget requires a context to find an enabled action to invoke, you need to provide one, either by creating your own \`Action\` instance, or by finding one in an appropriate context with \`Actions.find\`. To invoke the action, pass the action to the \`invoke\` method on an \`ActionDispatcher\`, either one you created yourself, or one retrieved from an existing \`Actions\` widget using the \`Actions.of(context)\` method. Check whether the action is enabled before calling \`invoke\`. Of course, you can also just call \`invoke\` on the action itself, passing an \`Intent\`, but then you opt out of any services that an action dispatcher might provide (like logging, undo/redo, and so on). ### Action dispatchers Most of the time, you just want to invoke an action, have it do its thing, and forget about it. Sometimes, however, you might want to log the executed actions. This is where replacing the default \`ActionDispatcher\` with a custom dispatcher comes in. You pass your \`ActionDispatcher\` to the \`Actions\` widget, and it invokes actions from any \`Actions\` widgets below that one that doesn't set a dispatcher of its own. The first thing \`Actions\` does when invoking an action is look up the \`ActionDispatcher\` and pass the action to it for invocation. If there is none, it creates a default \`ActionDispatcher\` that simply invokes the action. If you want a log of all the actions invoked, however, you can create your own \`LoggingActionDispatcher\` to do the job:

<!--?code-excerpt "ui/actions_and_shortcuts/lib/samples.dart (logging-action-dispatcher)"?-->

\`\`\`dart class LoggingActionDispatcher extends ActionDispatcher { @override Object? invokeAction( covariant Action action, covariant Intent intent, \[ BuildContext? context, ]) { print('Action invoked: $action($intent) from $context'); super.invokeAction(action, intent, context); return null; } @override (bool, Object?) invokeActionIfEnabled( covariant Action action, covariant Intent intent, \[ BuildContext? context, ]) { print('Action invoked: $action($intent) from $context'); return super.invokeActionIfEnabled(action, intent, context); } } \`\`\` Then you pass that to your top-level \`Actions\` widget:

<!--?code-excerpt "ui/actions_and_shortcuts/lib/samples.dart (logging-action-dispatcher-usage)"?-->

\`\`\`dart @override Widget build(BuildContext context) { return Actions( dispatcher: LoggingActionDispatcher(), actions: >{SelectAllIntent: SelectAllAction(model)}, child: Builder( builder: (context) => TextButton( onPressed: Actions.handler( context, const SelectAllIntent(), ), child: const Text('SELECT ALL'), ), ), ); } \`\`\` This logs every action as it executes, like so: \`\`\`console flutter: Action invoked: SelectAllAction#906fc(SelectAllIntent#a98e3) from Builder(dependencies: \_\[ActionsMarker]) \`\`\` ## Putting it together The combination of \`Actions\` and \`Shortcuts\` is powerful: you can define generic intents that map to specific actions at the widget level. Here's a simple app that illustrates the concepts described above. The app creates a text field that also has "select all" and "copy to clipboard" buttons next to it. The buttons invoke actions to accomplish their work. All the invoked actions and shortcuts are logged.

<!--?code-excerpt "ui/actions_and_shortcuts/lib/copyable_text.dart"?-->

\`\`\`dartpad title="Copyable text DartPad hands-on example" run="true" import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; /// A text field that also has buttons to select all the text and copy the /// selected text to the clipboard. class CopyableTextField extends StatefulWidget { const CopyableTextField({super.key, required this.title}); final String title; @override State createState() => \_CopyableTextFieldState(); } class \_CopyableTextFieldState extends State { late final TextEditingController controller = TextEditingController(); late final FocusNode focusNode = FocusNode(); @override void dispose() { controller.dispose(); focusNode.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Actions( dispatcher: LoggingActionDispatcher(), actions: >{ ClearIntent: ClearAction(controller), CopyIntent: CopyAction(controller), SelectAllIntent: SelectAllAction(controller, focusNode), }, child: Builder( builder: (context) { return Scaffold( body: Center( child: Row( children: \[ const Spacer(), Expanded( child: TextField( controller: controller, focusNode: focusNode, ), ), IconButton( icon: const Icon(Icons.copy), onPressed: Actions.handler( context, const CopyIntent(), ), ), IconButton( icon: const Icon(Icons.select\_all), onPressed: Actions.handler( context, const SelectAllIntent(), ), ), const Spacer(), ], ), ), ); }, ), ); } } /// A ShortcutManager that logs all keys that it handles. class LoggingShortcutManager extends ShortcutManager { @override KeyEventResult handleKeypress(BuildContext context, KeyEvent event) { final KeyEventResult result = super.handleKeypress(context, event); if (result == KeyEventResult.handled) { print('Handled shortcut $event in $context'); } return result; } } /// An ActionDispatcher that logs all the actions that it invokes. class LoggingActionDispatcher extends ActionDispatcher { @override Object? invokeAction( covariant Action action, covariant Intent intent, \[ BuildContext? context, ]) { print('Action invoked: $action($intent) from $context'); super.invokeAction(action, intent, context); return null; } } /// An intent that is bound to ClearAction in order to clear its /// TextEditingController. class ClearIntent extends Intent { const ClearIntent(); } /// An action that is bound to ClearIntent that clears its /// TextEditingController. class ClearAction extends Action { ClearAction(this.controller); final TextEditingController controller; @override Object? invoke(covariant ClearIntent intent) { controller.clear(); return null; } } /// An intent that is bound to CopyAction to copy from its /// TextEditingController. class CopyIntent extends Intent { const CopyIntent(); } /// An action that is bound to CopyIntent that copies the text in its /// TextEditingController to the clipboard. class CopyAction extends Action { CopyAction(this.controller); final TextEditingController controller; @override Object? invoke(covariant CopyIntent intent) { final String selectedString = controller.text.substring( controller.selection.baseOffset, controller.selection.extentOffset, ); Clipboard.setData(ClipboardData(text: selectedString)); return null; } } /// An intent that is bound to SelectAllAction to select all the text in its /// controller. class SelectAllIntent extends Intent { const SelectAllIntent(); } /// An action that is bound to SelectAllAction that selects all text in its /// TextEditingController. class SelectAllAction extends Action { SelectAllAction(this.controller, this.focusNode); final TextEditingController controller; final FocusNode focusNode; @override Object? invoke(covariant SelectAllIntent intent) { controller.selection = controller.selection.copyWith( baseOffset: 0, extentOffset: controller.text.length, affinity: controller.selection.affinity, ); focusNode.requestFocus(); return null; } } /// The top level application class. /// /// Shortcuts defined here are in effect for the whole app, /// although different widgets may fulfill them differently. class MyApp extends StatelessWidget { const MyApp({super.key}); static const String title = 'Shortcuts and Actions Demo'; @override Widget build(BuildContext context) { return MaterialApp( title: title, theme: ThemeData( colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), ), home: Shortcuts( shortcuts: { LogicalKeySet(LogicalKeyboardKey.escape): const ClearIntent(), LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.keyC): const CopyIntent(), LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.keyA): const SelectAllIntent(), }, child: const CopyableTextField(title: title), ), ); } } void main() => runApp(const MyApp()); \`\`\` \[\`Action\`]: https\://api.flutter.dev/flutter/widgets/Action-class.html \[\`Actions\`]: https\://api.flutter.dev/flutter/widgets/Actions-class.html \[\`CallbackAction\`]: https\://api.flutter.dev/flutter/widgets/CallbackAction-class.html \[\`CallbackShortcuts\`]: https\://api.flutter.dev/flutter/widgets/CallbackShortcuts-class.html \[\`Intent\`]: https\://api.flutter.dev/flutter/widgets/Intent-class.html \[\`Shortcuts\`]: https\://api.flutter.dev/flutter/widgets/Shortcuts-class.html \[Using Shortcuts Diagram]: /assets/images/docs/using\_shortcuts.png

----
url: https://docs.flutter.dev/tools/vs-code.md
----

\# Visual Studio Code > How to develop Flutter apps in Visual Studio Code.

* [Android Studio and IntelliJ](/tools/android-studio)
* [Visual Studio Code]()

[]()## Installation and setup {: #setup} \[VS Code]\[] is a code editor to build and debug apps. With the Flutter extension installed, you can compile, deploy, and debug Flutter apps. To install the latest version of VS Code, follow Microsoft's instructions for the relevant platform: - \[Install on macOS]\[] - \[Install on Windows]\[] - \[Install on Linux]\[] \[VS Code]: https\://code.visualstudio.com/ \[Install on macOS]: https\://code.visualstudio.com/docs/setup/mac \[Install on Windows]: https\://code.visualstudio.com/docs/setup/windows \[Install on Linux]: https\://code.visualstudio.com/docs/setup/linux ### Install the Flutter extension {: #install-extension} 1. Start \*\*VS Code\*\*. 1. Open a browser and go to the \[Flutter extension]\[] page on the Visual Studio Marketplace. 1. Click \*\*Install\*\*. Installing the Flutter extension also installs the Dart extension. \[Flutter extension]: https\://marketplace.visualstudio.com/items?itemName=Dart-Code.flutter ### Validate your VS Code setup {: #validate-setup} 1. Go to \*\*View\*\* > \*\*Command Palette...\*\*. You can also press `Ctrl` / `Cmd` + `Shift` + `P`. 1. Type \`doctor\`. 1. Select \*\*Flutter: Run Flutter Doctor\*\*. Once you select this command, VS Code does the following: - Opens the \*\*Output\*\* panel. - Displays \*\*flutter (flutter)\*\* in the dropdown on the upper right of this panel. - Displays the output of \`flutter doctor\` command. ### Updating the extension {:#updating} Updates to the extensions are shipped on a regular basis. By default, VS Code automatically updates extensions when updates are available. To install updates yourself: 1. Click \*\*Extensions\*\* in the Side Bar. 1. If the Flutter extension has an available update, click \*\*Update\*\* and then \*\*Reload\*\*. 1. Restart VS Code. ## Creating projects There are a couple ways to create a new project. ### Creating a new project To create a new Flutter project from the Flutter starter app template: 1. Go to \*\*View\*\* > \*\*Command Palette...\*\*. You can also press `Ctrl` / `Cmd` + `Shift` + `P`. 1. Type \`flutter\`. 1. Select the \*\*Flutter: New Project\*\*. 1. Press `Enter`. 1. Select \*\*Application\*\*. 1. Press `Enter`. 1. Select a \*\*Project location\*\*. 1. Enter your desired \*\*Project name\*\*. ### Opening a project from existing source code To open an existing Flutter project: 1. Go to \*\*File\*\* > \*\*Open\*\*. You can also press `Ctrl` / `Cmd` + `O` 1. Browse to the directory holding your existing Flutter source code files. 1. Click \*\*Open\*\*. ## Editing code and viewing issues The Flutter extension performs code analysis. The code analysis can: - Highlight language syntax - Complete code based on rich type analysis - Navigate to type declarations - Go to \*\*Go\*\* > \*\*Go to Definition\*\*. - You can also press `F12`. - Find type usages. - Press `Shift` + `F12`. - View all current source code problems. - Go to \*\*View\*\* > \*\*Problems\*\*. - You can also press `Ctrl` / `Cmd` + `Shift` + `M`. - The Problems pane displays any analysis issues:\
!\[Problems pane]\(/assets/images/docs/tools/vs-code/problems.png) ## Running and debugging :::note You can debug your app in a couple of ways. - Using \[DevTools]\[], a suite of debugging and profiling tools that run in a browser. - Using VS Code's built-in debugging features, such as setting breakpoints. The instructions below describe features available in VS Code. For information on launching and using DevTools, see \[Running DevTools from VS Code]\[] in the \[DevTools]\[] docs. ::: Start debugging by clicking \*\*Run > Start Debugging\*\* from the main IDE window, or press `F5`. ### Selecting a target device When a Flutter project is open in VS Code, you should see a set of Flutter specific entries in the status bar, including a Flutter SDK version and a device name (or the message \*\*No Devices\*\*):\
!\[VS Code status bar]\[] :::note - If you do not see a Flutter version number or device info, your project might not have been detected as a Flutter project. Ensure that 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 or emulator, to proceed. ::: The Flutter extension automatically selects the last device connected. However, if you have multiple devices/simulators connected, click \*\*device\*\* in the status bar to see a pick-list at the top of the screen. Select the device you want to use for running or debugging. :::secondary \*\*Are you developing for macOS or iOS remotely using Visual Studio Code Remote?\*\* If so, you might need to manually unlock the keychain. For more information, see this \[question on StackExchange]\[]. ::: \[question on StackExchange]: https\://superuser.com/questions/270095/when-i-ssh-into-os-x-i-dont-have-my-keychain-when-i-use-terminal-i-do/363840#363840 ### Run app without breakpoints Go to \*\*Run\*\* > \*\*Start Without Debugging\*\*. You can also press `Ctrl` + `F5`. ### Run app with breakpoints 1. If desired, set breakpoints in your source code. 1. Click \*\*Run\*\* > \*\*Start Debugging\*\*. You can also press `F5`. The status bar turns orange to show you are in a debug session.\
!\[Debug console]\(/assets/images/docs/tools/vs-code/debug\_console.png) - The left \*\*Debug Sidebar\*\* shows stack frames and variables. - The bottom \*\*Debug Console\*\* pane shows detailed logging output. - Debugging is based on a default launch configuration. To customize, click the cog at the top of the \*\*Debug Sidebar\*\* to create a \`launch.json\` file. You can then modify the values. ### Run app in debug, profile, or release mode Flutter offers many different build modes to run your app in. You can read more about them in \[Flutter's build modes]\[]. 1. Open the \`launch.json\` file in VS Code. If you don't have a \`launch.json\` file: {: type="a"} 1. Go to \*\*View\*\* > \*\*Run\*\*. You can also press `Ctrl` / `Cmd` + `Shift` + `D` The \*\*Run and Debug\*\* panel displays. 1. Click \*\*create a launch.json file\*\*. 1. In the \`configurations\` section, change the \`flutterMode\` property to the build mode you want to target. For example, if you want to run in debug mode, your \`launch.json\` might look like this: \`\`\`json "configurations": \[ { "name": "Flutter", "request": "launch", "type": "dart", "flutterMode": "debug" } ] \`\`\` 1. Run the app through the \*\*Run\*\* panel. ## Fast edit and refresh development cycle Flutter offers a best-in-class developer cycle enabling you to see the effect of your changes almost instantly with the \_Stateful Hot Reload\_ feature. To learn more, check out \[Hot reload]\[]. ## Advanced debugging You might find the following advanced debugging tips useful: ### Debugging visual layout issues During a debug session, several additional debugging commands are added to the \[Command Palette]\[] and to the \[Flutter inspector]\[]. When space is limited, the icon is used as the visual version of the label. \*\*Toggle Baseline Painting\*\* !\[Baseline painting icon]\(/assets/images/docs/tools/devtools/paint-baselines-icon.png){:.theme-icon width="20px"} : Causes each RenderBox to paint a line at each of its baselines. \*\*Toggle Repaint Rainbow\*\* !\[Repaint rainbow icon]\(/assets/images/docs/tools/devtools/repaint-rainbow-icon.png){:.theme-icon width="20px"} : Shows rotating colors on layers when repainting. \*\*Toggle Slow Animations\*\* !\[Slow animations icon]\(/assets/images/docs/tools/devtools/slow-animations-icon.png){:.theme-icon width="20px"} : Slows down animations to enable visual inspection. \*\*Toggle Debug Mode Banner\*\* !\[Debug mode banner icon]\(/assets/images/docs/tools/devtools/debug-mode-banner-icon.png){:width="20px"} : Hides the debug mode banner even when running a debug build. ### Debugging external libraries By default, debugging an external library is disabled in the Flutter extension. To enable: 1. Select \*\*Settings > Extensions > Dart Configuration\*\*. 2. Check the \`Debug External Libraries\` option. ## Editing tips for Flutter code 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. To invoke the assist, click the lightbulb as shown in the following screenshot: !\[Code assists]\(/assets/images/docs/tools/vs-code/assists.png){:width="467px"} You can also press `Ctrl` / `Cmd` + `.` Quick fixes are similar, only they are shown with a piece of code has an error and they can assist in correcting it. \*\*Wrap with new widget assist\*\* : This can be used when you have a widget that you want to wrap in a surrounding widget, for example if you want to wrap a widget in a \`Row\` or \`Column\`. \*\*Wrap widget list with new widget assist\*\* : Similar to the assist above, but for wrapping an existing list of widgets rather than an individual widget. \*\*Convert child to children assist\*\* : Changes a child argument to a children argument, and wraps the argument value in a list. \*\*Convert StatelessWidget to StatefulWidget assist\*\* : Changes the implementation of a \`StatelessWidget\` to that of a \`StatefulWidget\`, by creating the \`State\` class and moving the code there. ### Snippets Snippets can be used to speed up entering typical code structures. They are invoked by typing their prefix, and then selecting from the code completion window: !\[Snippets]\(/assets/images/docs/tools/vs-code/snippets.png){:width="100%"} The Flutter extension includes the following snippets: - Prefix \`stless\`: Create a new subclass of -StatelessWidget\`. - Prefix \`stful\`: Create a new subclass of \`StatefulWidget\` and its associated State subclass. - Prefix \`stanim\`: Create a new subclass of \`StatefulWidget\`, and its associated State subclass including a field initialized with an \`AnimationController\`. The Dart extension includes the following snippets: | Prefix | Description | Code Example | |---|---|---| | \`main\` | Insert a main function, used as an entry point. | \`void main(List args) { }\` | | \`try\` | Insert a try/catch block. | \`try { } catch (e) { }\` | | \`if\` | Insert an if statement. | \`if (condition) { }\` | | \`ife\` | Insert an if statement with an else block. | \`if (condition) { } else { }\` | | \`switch\` | Insert a switch statement. | \`switch (variable) { case value1: break; case value2: break; default: }\` | | \`for\` | Insert a for loop. | \`for (var i = 0; i < 10; i++) { }\` | | \`fori\` | Insert a for-in loop. | \`for (var item in list) { }\` | | \`while\` | Insert a while loop. | \`while (condition) { }\` | | \`do\` | Insert a do-while loop. | \`do { } while (condition);\` | | \`fun\` | Insert a function definition. | \`void myFunction(String name) { }\` | | \`class\` | Insert a class definition. | \`class MyClass { }\` | | \`typedef\` | Insert a typedef. | \`typedef MyFunction = void Function(String);\` | | \`test\` | Insert a test block. | \`test('My test description', () { });\` | | \`group\` | Insert a test group block. | \`group('My test group', () { });\` | You can also define custom snippets by executing \*\*Configure User Snippets\*\* from the \[Command Palette]\[]. ### Keyboard shortcuts \*\*Hot reload\*\* : To perform a hot reload during a debug session, click \*\*Hot Reload\*\* on the \*\*Debug Toolbar\*\*. You can also press `Ctrl` + `F5` (`Cmd` + `F5` on macOS). Keyboard mappings can be changed by executing the \*\*Open Keyboard Shortcuts\*\* command from the \[Command Palette]\[]. ### Hot reload vs. hot restart Hot reload works by injecting updated source code files into the running Dart VM (Virtual Machine). This includes not only adding new classes, but also adding methods and fields to existing classes, and changing existing functions. A few types of code changes cannot be hot reloaded though: - Global variable initializers - Static field initializers - The \`main()\` method of the app For these changes, restart your app without ending your debugging session. To perform a hot restart, run the \*\*Flutter: Hot Restart\*\* command from the \[Command Palette]\[]. You can also press `Ctrl` + `Shift` + `F5` or `Cmd` + `Shift` + `F5` on macOS. ### Customize closing labels {: #closing-labels} The Dart extension displays labels after the closing delimiters of multi-line constructs, such as constructor invocations, method calls, and list literals. Closing labels are editor annotations and don't change your source code, even though the default \` // \` prefix makes them look similar to comments. To change the prefix: 1. Open the \[Command Palette]\[]. 1. Select \*\*Preferences: Open Settings (UI)\*\*. 1. Search for \`dart.closingLabels\`. 1. In \*\*Dart: Closing Labels Prefix\*\*, enter your preferred prefix. For example, to remove the comment marker while keeping space between the code and label, set the prefix to a single space (\` \`): \`\`\`json { "dart.closingLabelsPrefix": " " } \`\`\` To hide closing labels, turn off \*\*Dart: Closing Labels\*\*. For the complete list of closing label options, see the \[extension's settings reference]\[closing-label-options]. \[closing-label-options]: https\://dartcode.org/docs/settings/#dartclosinglabels ## Flutter Property Editor {: #property-editor} The Flutter Property Editor is a powerful tool provided by the \[Flutter extension]\[] that lets you view and modify widget properties directly from its visual interface. ### How to open the Flutter Property Editor in VS Code 1. Click on the Flutter Property Editor \*\*icon\*\* !\[Flutter Property Editor VS Code icon]\(/assets/images/docs/tools/devtools/property-editor-icon-vscode.png){:width="20px"} in the VS Code sidebar. 2. The Flutter Property Editor will load in the side panel. 3. Please refer to the Flutter Property Editor \[documentation]\[] for a detailed usage guide. !\[Flutter Property Editor side panel in VS Code]\(/assets/images/docs/tools/devtools/property-editor-vscode.png){:width="600px"} \[Flutter extension]: https\://marketplace.visualstudio.com/items?itemName=Dart-Code.flutter \[documentation]: /tools/property-editor ## Troubleshooting ### Known issues and feedback All known bugs are tracked in the issue tracker: \[Dart and Flutter extensions GitHub issue tracker]\[issue tracker]. We welcome feedback, both on bugs/issues and feature requests. Prior to filing new issues: - Do a quick search in the issue trackers to see if the issue is already tracked. - Make sure you are \[up to date]\(#updating) with the most recent version of the plugin. When filing new issues, include \[flutter doctor]\[] output. \[Command Palette]: https\://code.visualstudio.com/docs/getstarted/userinterface#\_command-palette \[DevTools]: /tools/devtools \[flutter doctor]: /resources/bug-reports/#provide-some-flutter-diagnostics \[Flutter inspector]: /tools/devtools/inspector \[Flutter's build modes]: /testing/build-modes \[Hot reload]: /tools/hot-reload \[let us know]: https\://github.com/flutter/website/issues/new \[issue tracker]: https\://github.com/Dart-Code/Dart-Code/issues \[Running DevTools from VS Code]: /tools/devtools/vscode \[VS Code status bar]: /assets/images/docs/tools/vs-code/device\_status\_bar.png

----
url: https://docs.flutter.dev/tools/devtools/extensions.md
----

\# DevTools extensions > Learn how to use and build DevTools extensions. ## What are DevTools extensions? \[DevTools extensions]\[] are developer tools provided by third-party packages that are tightly integrated into the DevTools tooling suite. Extensions are distributed as part of a pub package, and they are dynamically loaded into DevTools when a user is debugging their app. \[DevTools extensions]: https\://pub.dev/packages/devtools\_extensions ## Use a DevTools extension If your app depends on a package that provides a DevTools extension, the extension automatically shows up in a new tab when you open DevTools. ### Configure extension enablement states You need to manually enable the extension before it loads for the first time. Make sure the extension is provided by a source you trust before enabling it. When you open the extension for the first time, you'll see a prompt to enable the extension: !\[Screenshot of extension enablement prompt]\(/assets/images/docs/tools/devtools/extension\_enable\_prompt.png) You can modify the setting at any time from the DevTools Extensions dialog: !\[Screenshot of DevTools Extensions dialog button]\(/assets/images/docs/tools/devtools/extension\_dialog\_button.png) !\[Screenshot of extension enablement dialog]\(/assets/images/docs/tools/devtools/extension\_dialog.png) :::note If the extension requires connecting to a running application, you won't see the enablement prompt or enablement settings until DevTools is connected to a running app. ::: Extension enablement states are stored in a \`devtools\_options.yaml\` file in the root of the user's project (similar to \`analysis\_options.yaml\`). \`\`\`yaml description: This file stores settings for Dart & Flutter DevTools. documentation: https\://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states extensions: - provider: true - shared\_preferences: true - foo: false \`\`\` This file stores per-project (or optionally, per user) settings for DevTools. If this file is \*\*checked into source control\*\*, the specified options are configured for the project. This means that anyone who pulls a project's source code and works on the project uses the same settings. If this file is \*\*omitted from source control\*\*, for example by adding \`devtools\_options.yaml\` as an entry in the \`.gitignore\` file, then the specified options are configured separately for each user. Since each user or contributor to the project uses a local copy of the \`devtools\_options.yaml\` file in this case, the specified options might differ between project contributors. ## Build a DevTools extension For an in-depth guide on how to build a DevTools extension, visit \[Build custom tooling in Flutter and Dart DevTools]\[build-extensions] You might also check out the following video: \[build-extensions]: /tools/devtools/custom-tool

----
url: https://docs.flutter.dev/platform-integration
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Build for and integrate with multiple platforms](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

Flutter enables you to build, test, and deploy beautiful, natively compiled, multi-platform applications from a single codebase.

## Overview

[#](#overview)

Flutter and its core packages often automatically support and integrate with Flutter's officially [supported platforms](/reference/supported-platforms). Some platforms require you to [set up additional tooling](#setup), but once your development environment is set up, Flutter apps are usually functional across platforms out of the box.

Occasionally you need to integrate with platform-specific functionality. For example, you might want to use a native library that's only available on iOS and iPadOS. For many use cases, you can find and use one of the many [Flutter plugins](/packages-and-plugins/using-packages) provided by the Flutter team and the amazing Flutter community. If none of them meet your needs, you can [write platform-specific code](/platform-integration/platform-channels) and even [create your own plugin](/packages-and-plugins/developing-packages).

## Set up platform development

[#](#setup)

While Flutter apps can be built for a variety of [supported platforms](/reference/supported-platforms) with little to no modifications to your code, your development environment might require additional setup when targeting a new platform.

To set up development for an additional platform, select the platform from the following:

## Integrate with each platform

[#](#integrate)

If the situation you're trying to solve is not covered by an existing [Flutter plugin](/packages-and-plugins/using-packages#searching-for-packages), check out the following guides to learn how to integrate with each of the supported platforms.

### Integrate with Android

[#](#android)

Learn how to add custom integrations with Android to your Flutter app.

### Integrate with iOS

[#](#ios)

Learn how to add custom integrations with iOS to your Flutter app.

### Integrate with the web

[#](#web)

Learn how to add custom integrations with the web platform to your Flutter app.

[Interop with JavaScript open\_in\_new](https://dart.dev/interop/js-interop)

[Learn how to integrate with JavaScript from your Dart code.](https://dart.dev/interop/js-interop)

### Integrate with Windows

[#](#windows)

Learn how to add custom integrations with Windows to your Flutter app.

[Bind to native code](/platform-integration/windows/building/#integrating-with-windows)

[Learn how to bind to native C code from your app on Windows.](/platform-integration/windows/building/#integrating-with-windows)

[Distribute your app](/platform-integration/windows/building/#distributing-windows-apps)

[Learn about different options for distributing your app on Windows.](/platform-integration/windows/building/#distributing-windows-apps)

[Follow Windows UI conventions](platform-integration/windows/building#supporting-windows-ui-guidelines)

[Learn different techniques to integrate with the Windows look and feel.](platform-integration/windows/building#supporting-windows-ui-guidelines)

### Integrate with macOS

[#](#macos)

Learn how to add custom integrations with macOS to your Flutter app.

### Integrate with Linux

[#](#linux)

Learn how to add custom integrations with Linux to your Flutter app.

[Bind to native code](/platform-integration/linux/building#integrate-with-linux)

[Learn how to use and bind to native Linux libraries and code.](/platform-integration/linux/building#integrate-with-linux)

[Prepare for distribution](/platform-integration/linux/building#prepare-linux-apps-for-distribution)

[Prepare your Flutter app for distributing to Linux users.](/platform-integration/linux/building#prepare-linux-apps-for-distribution)

[Deploy to the Snap Store](/deployment/linux/)

[Learn how to deploy your Linux desktop app to the Snap Store.](/deployment/linux/)

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-09-14. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/platform-integration/index.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/platform-integration\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/platform-integration/index.md "Report an issue with this page").

----
url: https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.29.0
----

The 2.29.0 release of the Dart and Flutter DevTools includes the following changes among other general improvements. To learn more about DevTools, check out the [DevTools overview](https://docs.flutter.dev/tools/devtools).

## General updates

[#](#general-updates)

* Fix a bug with service extension states not being cleared on app disconnect. - [#6547](https://github.com/flutter/devtools/pull/6547)

* Improved styling of bottom status bar when connected to an app. - [#6525](https://github.com/flutter/devtools/pull/6525)

* Added a workaround to fix copy button functionality in VSCode. - [#6598](https://github.com/flutter/devtools/pull/6598)

## Performance updates

[#](#performance-updates)

* Added an option in the "Enhance Tracing" menu for tracking platform channel activity. This is useful for apps with plugins. - [#6515](https://github.com/flutter/devtools/pull/6515)

* Made the Performance screen available when there is no connected app. Performance data that was previously saved from DevTools can be reloaded for viewing from this screen. - [#6567](https://github.com/flutter/devtools/pull/6567)

* Added an "Open" button to the Performance controls for loading data that was previously saved from DevTools. - [#6567](https://github.com/flutter/devtools/pull/6567)

## CPU profiler updates

[#](#cpu-profiler-updates)

* Tree guidelines are now always enabled for the "Bottom Up" and "Call Tree" tabs. - [#6534](https://github.com/flutter/devtools/pull/6534)

* Made the CPU profiler screen available when there is no connected app. CPU profiles that were previously saved from DevTools can be reloaded for viewing from this screen. - [#6567](https://github.com/flutter/devtools/pull/6567)

* Added an "Open" button to the CPU profiler controls for loading data that was previously saved from DevTools. - [#6567](https://github.com/flutter/devtools/pull/6567)

## Network profiler updates

[#](#network-profiler-updates)

* Network statuses now show with an error color when the request failed. - [#6527](https://github.com/flutter/devtools/pull/6527)

## Full commit history

[#](#full-commit-history)

To find a complete list of changes in this release, check out the [DevTools git log](https://github.com/flutter/devtools/tree/v2.29.0).

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/release-notes/release-notes-2.29.0.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.29.0\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/release-notes/release-notes-2.29.0.md "Report an issue with this page").

----
url: https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.47.0
----

The 2.47.0 release of the Dart and Flutter DevTools includes the following changes among other general improvements. To learn more about DevTools, check out the [DevTools overview](/tools/devtools/overview).

## General updates

[#](#general-updates)

* Fixed an issue where copying all logs in the console would show `null` for any inspected widgets. - [#9204](https://github.com/flutter/devtools/pull/9204)

## Full commit history

[#](#full-commit-history)

To find a complete list of changes in this release, check out the [DevTools git log](https://github.com/flutter/devtools/tree/v2.47.0).

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/release-notes/release-notes-2.47.0.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.47.0\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/release-notes/release-notes-2.47.0.md "Report an issue with this page").

----
url: https://docs.flutter.dev/learn/pathway/tutorial/create-an-app.md
----

\# Create an app > Instructions on how to create a new Flutter app. Learn the first steps to building a Flutter app, from creating a project to understanding widgets and hot reload. title: What you'll accomplish items: - title: Create a new Flutter project using the CLI icon: terminal - title: Understand widgets and the widget tree icon: account\_tree - title: Run your app and use hot reload icon: bolt --- ### What you'll build In this first section of the Flutter tutorial, you'll build the core UI of an app called 'Birdle', a game similar to \[Wordle, the popular New York Times game]\[]. By the end of this tutorial, you'll have learned the fundamentals of building Flutter UIs, and your app will look like the following screenshot (and it'll even mostly work 😀). \[Wordle, the popular New York Times game]: https\://www\.nytimes.com/games/wordle/index.html ### Create a new Flutter project The first step to building Flutter apps is to create a new project. You create new apps with the \[Flutter CLI tool]\[], installed as part of the Flutter SDK. Open your terminal or command prompt and run the following command to create a new Flutter project: \`\`\`console $ flutter create birdle --empty \`\`\` This creates a new Flutter project using the minimal "empty" template. \[Flutter CLI tool]: /reference/flutter-cli ### Examine the code In your IDE, open the file at \`lib/main.dart\`. Starting from the top, you'll see this code.

<!--?code-excerpt "fwe/birdle/lib/step1_main.dart (main)"?-->

\`\`\`dart title="lib/main.dart" void main() { runApp(const MainApp()); } \`\`\` The \`main\` function is the entry point to any Dart program, and a Flutter app is just a \*\*Dart\*\* program. The \`runApp\` method is part of the Flutter SDK, and it takes a \*\*widget\*\* as an argument. In this case, an instance of the \`MainApp\` widget is being passed in. Just below the \`main\` function, you'll find the \`MainApp\` class declaration.

<!--?code-excerpt "fwe/birdle/lib/step1_main.dart (MainApp)"?-->

\`\`\`dart class MainApp extends StatelessWidget { const MainApp({super.key}); @override Widget build(BuildContext context) { return const MaterialApp( home: Scaffold( body: Center( child: Text('Hello World!'), ), ), ); } } \`\`\` \`MainApp\` is the \*\*root widget\*\*, as it's the widget that's passed into \`runApp\`. Within this widget, there's a \`build\` method that returns another widget called \`MaterialApp\`. Essentially, this is what a Flutter app is: a composition of widgets that make up a tree structure called the \*\*widget tree.\*\* Your job as a Flutter developer is to compose widgets from the SDK into larger, custom widgets that display a UI. At the moment, the widget tree is quite simple: ### Run your app 1. In your terminal, navigate to the root directory of your created Flutter app: \`\`\`console $ cd birdle \`\`\` 1. Run the app using the Flutter CLI tool. \`\`\`console $ flutter run -d chrome \`\`\` The app will build and launch in a new instance of Chrome. ### Use hot reload \*\*Stateful hot reload\*\*, if you haven't heard of it, allows a running Flutter app to re-render updated business logic or UI code in less than a second – all without losing your place in the app. In your IDE, open the \`main.dart\` file and navigate to line \~15 and find this code:

<!--?code-excerpt "fwe/birdle/lib/step1_main.dart (Text)"?-->

\`\`\`dart child: Text('Hello World!'), \`\`\` Change the text inside the string to anything you want. Then, hot-reload your app by pressing \`r\` in the terminal where the app is running. The running app should instantly show your updated text. ### Review title: What you accomplished subtitle: Here's a summary of what you built and learned in this lesson. completed: true items: - title: Created your first Flutter project icon: terminal details: >- You used \`flutter create\` with the \`--empty\` flag to scaffold a minimal Flutter project. The CLI generates the project structure and boilerplate code needed to get started. - title: Explored the widget tree icon: account\_tree details: >- Flutter UIs are built by composing \*\*widgets\*\* into a tree structure. The \`runApp\` function takes a root widget, and that widget's \`build\` method returns other widgets, forming the \*\*widget tree\*\*. Your job as a Flutter developer is to compose these widgets into custom UIs. - title: Ran your app with hot reload icon: bolt details: >- You ran your app with \`flutter run\` and experienced \*\*stateful hot reload\*\*, which lets you see code changes reflected in under a second without losing app state. Press \`r\` in the terminal to trigger a hot reload. ### Test yourself - question: "What is the purpose of the \`runApp\` function in a Flutter application?" options: - text: It compiles the Dart code into native machine code. correct: false explanation: "Compilation happens before the app runs; \`runApp\` starts the Flutter framework with a root widget." - text: It takes a widget as an argument and makes it the root of the widget tree. correct: true explanation: "The \`runApp\` function inflates the given widget and attaches it to the screen, making it the root of the widget tree." - text: "It creates the \`main.dart\` file for the project." correct: false explanation: "The file is created by \`flutter create\`; \`runApp\` is called at runtime." - text: It downloads Flutter dependencies from the internet. correct: false explanation: "Dependencies are managed by \`flutter pub get\`, not \`runApp\`." - question: How do you trigger a hot reload while a Flutter app is running in the terminal? options: - text: "Press \`h\` in the terminal." correct: false explanation: "Pressing \`h\` shows help options; \`r\` triggers hot reload." - text: "Press \`r\` in the terminal." correct: true explanation: "Pressing \`r\` in the terminal where the app is running triggers a hot reload." - text: "Stop and restart the app with \`flutter run\`." correct: false explanation: A full restart is not needed; hot reload is faster. - text: Save the file and wait for automatic reload. correct: false explanation: "By default, you need to press \`r\` to trigger hot reload in the terminal."

----
url: https://docs.flutter.dev/cookbook/gestures/dismissible
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Implement swipe to dismiss](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

The "swipe to dismiss" pattern is common in many mobile apps. For example, when writing an email app, you might want to allow a user to swipe away email messages to delete them from a list.

Flutter makes this task easy by providing the [`Dismissible`](https://api.flutter.dev/flutter/widgets/Dismissible-class.html) widget. Learn how to implement swipe to dismiss with the following steps:

1. Create a list of items.
2. Wrap each item in a `Dismissible` widget.
3. Provide "leave behind" indicators.

## 1. Create a list of items

[#](#1-create-a-list-of-items)

First, create a list of items. For detailed instructions on how to create a list, follow the [Working with long lists](/cookbook/lists/long-lists) recipe.

### Create a data source

[#](#create-a-data-source)

In this example, you want 20 sample items to work with. To keep it simple, generate a list of strings.

dart

```
final items = List<String>.generate(20, (i) => 'Item ${i + 1}');
```

### Convert the data source into a list

[#](#convert-the-data-source-into-a-list)

Display each item in the list on screen. Users won't be able to swipe these items away just yet.

dart

```
ListView.builder(
  itemCount: items.length,
  itemBuilder: (context, index) {
    return ListTile(title: Text(items[index]));
  },
)
```

## 2. Wrap each item in a Dismissible widget

[#](#2-wrap-each-item-in-a-dismissible-widget)

In this step, give users the ability to swipe an item off the list by using the [`Dismissible`](https://api.flutter.dev/flutter/widgets/Dismissible-class.html) widget.

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.

Update the `itemBuilder()` function to return a `Dismissible` widget:

dart

```
itemBuilder: (context, index) {
  final item = items[index];
  return Dismissible(
    // Each Dismissible must contain a Key. Keys allow Flutter to
    // uniquely identify widgets.
    key: Key(item),
    // Provide a function that tells the app
    // what to do after an item has been swiped away.
    onDismissed: (direction) {
      // Remove the item from the data source.
      setState(() {
        items.removeAt(index);
      });
​
      // Then show a snackbar.
      ScaffoldMessenger.of(context)
          .showSnackBar(SnackBar(content: Text('$item dismissed')));
    },
    child: ListTile(title: Text(item)),
  );
},
```

## 3. Provide "leave behind" indicators

[#](#3-provide-leave-behind-indicators)

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, the indicator is a red background.

To add the indicator, provide a `background` parameter to the `Dismissible`.

dart

```
  ScaffoldMessenger.of(context)
      .showSnackBar(SnackBar(content: Text('$item dismissed')));
},
// Show a red background as the item is swiped away.
background: Container(color: Colors.red),
child: ListTile(
  title: Text(item),
),
```

## Interactive example

[#](#interactive-example)

```
import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

// MyApp is a StatefulWidget. This allows updating the state of the
// widget when an item is removed.
class MyApp extends StatefulWidget {
  const MyApp({super.key});

  @override
  MyAppState createState() {
    return MyAppState();
  }
}

class MyAppState extends State<MyApp> {
  final items = List<String>.generate(20, (i) => 'Item ${i + 1}');

  @override
  Widget build(BuildContext context) {
    const title = 'Dismissing Items';

    return MaterialApp(
      title: title,
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
      ),
      home: Scaffold(
        appBar: AppBar(title: const Text(title)),
        body: ListView.builder(
          itemCount: items.length,
          itemBuilder: (context, index) {
            final item = items[index];
            return Dismissible(
              // Each Dismissible must contain a Key. Keys allow Flutter to
              // uniquely identify widgets.
              key: Key(item),
              // Provide a function that tells the app
              // what to do after an item has been swiped away.
              onDismissed: (direction) {
                // Remove the item from the data source.
                setState(() {
                  items.removeAt(index);
                });

                // Then show a snackbar.
                ScaffoldMessenger.of(context)
                    .showSnackBar(SnackBar(content: Text('$item dismissed')));
              },
              // Show a red background as the item is swiped away.
              background: Container(color: Colors.red),
              child: ListTile(title: Text(item)),
            );
          },
        ),
      ),
    );
  }
}
```

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-08-24. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/gestures/dismissible.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/cookbook/gestures/dismissible\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/gestures/dismissible.md "Report an issue with this page").

----
url: https://docs.flutter.dev/resources/payments-overview
----

Seamlessly accept payments with multiple providers in your Flutter app

The following resource can help to get you started:

* [Add ads to your mobile Flutter app or game](/cookbook/plugins/google-mobile-ads) (cookbook recipe)

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/resources/payments-overview.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/resources/payments-overview\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/resources/payments-overview.md "Report an issue with this page").

----
url: https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.36.0
----

The 2.36.0 release of the Dart and Flutter DevTools includes the following changes among other general improvements. To learn more about DevTools, check out the [DevTools overview](/tools/devtools).

## Performance updates

[#](#performance-updates)

* Added a feature for showing widget build counts. Enable this setting to see widget build counts for each Flutter frame in the "Frame Analysis" tool, or to see an aggregate summary of these counts in the new "Rebuild Stats" tool. - [#7838](https://github.com/flutter/devtools/pull/7838), [#7847](https://github.com/flutter/devtools/pull/7847)

## Network profiler updates

[#](#network-profiler-updates)

* Added better support for narrow viewing windows, like when this screen is embedded in an IDE. - [#7726](https://github.com/flutter/devtools/pull/7726)

## Deep links tool updates

[#](#deep-links-tool-updates)

* Adds an error page to explain the issue when the tool fails to parse the project. - [#7767](https://github.com/flutter/devtools/pull/7767)

## DevTools Extension updates

[#](#devtools-extension-updates)

* Fixed an issue with detecting extensions for Dart or Flutter tests. - [#7717](https://github.com/flutter/devtools/pull/7717)
* Fixed an issue with detecting extensions for nested Dart or Flutter projects. - [#7742](https://github.com/flutter/devtools/pull/7742)
* Added an example to `package:devtools_extensions` that shows how to interact with the Dart Tooling Daemon from a DevTools extension. - [#7752](https://github.com/flutter/devtools/pull/7752)
* Fixed a DevTools routing bug related to disabling an extension. - [#7791](https://github.com/flutter/devtools/pull/7791)
* Fixed a bug causing a "Page Not Found" error when refreshing DevTools from an extension screen. - [#7822](https://github.com/flutter/devtools/pull/7822)
* Fixed a theming issue when extensions are embedded in an IDE - [#7824](https://github.com/flutter/devtools/pull/7824)

## Full commit history

[#](#full-commit-history)

To find a complete list of changes in this release, check out the [DevTools git log](https://github.com/flutter/devtools/tree/v2.36.0).

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/release-notes/release-notes-2.36.0.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.36.0\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/release-notes/release-notes-2.36.0.md "Report an issue with this page").

----
url: https://docs.flutter.dev/app-architecture/case-study/dependency-injection.md
----

\# Communicating between layers > How to implement dependency injection to communicate between MVVM layers. Along with defining clear responsibilities for each component of the architecture, it's important to consider how the components communicate. This refers to both the rules that dictate communication, and the technical implementation of how components communicate. An app's architecture should answer the following questions: \* Which components are allowed to communicate with which other components (including components of the same type)? \* What do these components expose as output to each other? \* How is any given layer 'wired up' to another layer? !\[A diagram showing the components of app architecture.]\(/assets/images/docs/app-architecture/guide/feature-architecture-simplified.png) Using this diagram as a guide, the rules of engagement are as follows: | Component | Rules of engagement | |------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | View |

1. A view is only aware of exactly one view model, and is never aware of any other layer or component. When created, Flutter passes the view model to the view as an argument, exposing the view model's data and command callbacks to the view.

\| | ViewModel |

1. A ViewModel belongs to exactly one view, which can see its data, but the model never needs to know that a view exists.
2. A view model is aware of one or more repositories, which are passed into the view model's constructor.

\| | Repository |

1. A repository can be aware of many services, which are passed as arguments into the repository constructor.
2. A repository can be used by many view models, but it never needs to be aware of them.

\| | Service |

1. A service can be used by many repositories, but it never needs to be aware of a repository (or any other object).

\| {:.table .table-striped} ## Dependency injection This guide has shown how these different components communicate with each other by using inputs and outputs. In every case, communication between two layers is facilitated by passing a component into the constructor methods (of the components that consume its data), such as a \`Service\` into a \`Repository.\` \`\`\`dart class MyRepository { MyRepository({required MyService myService}) : \_myService = myService; late final MyService \_myService; } \`\`\` One thing that's missing, however, is object creation. Where, in an application, is the \`MyService\` instance created so that it can be passed into \`MyRepository\`? This answer to this question involves a pattern known as \[dependency injection]\[]. In the Compass app, \*dependency injection\* is handled using \[\`package:provider\`]\[]. Based on their experience building Flutter apps, teams at Google recommend using \`package:provider\` to implement dependency injection. Services and repositories are exposed to the top level of the widget tree of the Flutter application as \`Provider\` objects. \`\`\`dart title=dependencies.dart runApp( MultiProvider( providers: \[ Provider(create: (context) => AuthApiClient()), Provider(create: (context) => ApiClient()), Provider(create: (context) => SharedPreferencesService()), ChangeNotifierProvider( create: (context) => AuthRepositoryRemote( authApiClient: context.read(), apiClient: context.read(), sharedPreferencesService: context.read(), ) as AuthRepository, ), Provider(create: (context) => DestinationRepositoryRemote( apiClient: context.read(), ) as DestinationRepository, ), Provider(create: (context) => ContinentRepositoryRemote( apiClient: context.read(), ) as ContinentRepository, ), // In the Compass app, additional service and repository providers live here. ], child: const MainApp(), ), ); \`\`\` Services are exposed only so they can immediately be injected into repositories via the \`BuildContext.read\` method from \`provider\`, as shown in the preceding snippet. Repositories are then exposed so that they can be injected into view models as needed. Slightly lower in the widget tree, view models that correspond to a full screen are created in the \[\`package:go\_router\`]\[] configuration, where provider is again used to inject the necessary repositories. \`\`\`dart title=router.dart // This code was modified for demo purposes. GoRouter router( AuthRepository authRepository, ) => GoRouter( initialLocation: Routes.home, debugLogDiagnostics: true, redirect: \_redirect, refreshListenable: authRepository, routes: \[ GoRoute( path: Routes.login, builder: (context, state) { return LoginScreen( viewModel: LoginViewModel( authRepository: context.read(), ), ); }, ), GoRoute( path: Routes.home, builder: (context, state) { final viewModel = HomeViewModel( bookingRepository: context.read(), ); return HomeScreen(viewModel: viewModel); }, routes: \[ // ... ], ), ], ); \`\`\` Within the view model or repository, the injected component should be private. For example, the \`HomeViewModel\` class looks like this: \`\`\`dart title=home\_viewmodel.dart class HomeViewModel extends ChangeNotifier { HomeViewModel({ required BookingRepository bookingRepository, required UserRepository userRepository, }) : \_bookingRepository = bookingRepository, \_userRepository = userRepository; final BookingRepository \_bookingRepository; final UserRepository \_userRepository; // ... } \`\`\` Private methods prevent the view, which has access to the view model, from calling methods on the repository directly. This concludes the code walkthrough of the Compass app. This page only walked through the architecture-related code, but it doesn't tell the whole story. Most utility code, widget code, and UI styling was ignored. Browse the code in the \[Compass app repository]\[] for a complete example of a robust Flutter application built following these principles. \[\`package:provider\`]: https\://pub.dev/packages/provider \[\`package:go\_router\`]: https\://pub.dev/packages/go\_router \[Compass app repository]: https\://github.com/flutter/samples/tree/main/compass\_app \[dependency injection]: https\://en.wikipedia.org/wiki/Dependency\_injection ## Feedback As this section of the website is evolving, we \[welcome your feedback]\[]! \[welcome your feedback]: https\://google.qualtrics.com/jfe/form/SV\_4T0XuR9Ts29acw6?page="case-study/dependency-injection"

----
url: https://docs.flutter.dev/platform-integration/web/web-images
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Display images on the web](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

The web supports the standard [`Image`](https://api.flutter.dev/flutter/widgets/Image-class.html) widget and the more advanced [`dart:ui/Image`](https://api.flutter.dev/flutter/dart-ui/Image-class.html) class (where more fine-grained control is needed to display images). However, because web browsers are built to run untrusted code safely, there are certain limitations in what you can do with images compared to mobile and desktop platforms. This page explains these limitations and offers ways to work around them.

## Background

[#](#background)

The web offers several methods for displaying images:

* The built-in [`<img>`](https://developer.mozilla.org/docs/Web/HTML/Element/img) and [`<picture>`](https://developer.mozilla.org/docs/Web/HTML/Element/picture) HTML elements
* The [`drawImage`](https://developer.mozilla.org/docs/Web/API/CanvasRenderingContext2D/drawImage) method on the [`<canvas>`](https://developer.mozilla.org/docs/Web/HTML/Element/canvas) element
* Custom image codec that renders to a WebGL canvas

Each option has its own benefits and drawbacks. For example, the built-in elements fit nicely among other HTML elements, and they automatically take advantage of browser caching, and built-in image optimization and memory management. They allow you to safely display images from arbitrary sources (more on than in the CORS section below). `drawImage` is great when the image must fit within other content rendered using the `<canvas>` element. You also gain control over image sizing and, when the CORS policy allows it, read the pixels of the image back for further processing. Finally, WebGL gives you the highest degree of control over the image. Not only can you read the pixels and apply custom image algorithms, but you can also use GLSL for hardware-acceleration.

## Cross-Origin Resource Sharing (CORS)

[#](#cross-origin-resource-sharing-cors)

[CORS](https://developer.mozilla.org/docs/Web/HTTP/CORS) is a mechanism that browsers use to control how one site accesses the resources of another site. It is designed such that, by default, one web-site is not allowed to make HTTP requests to another site using [XHR](https://developer.mozilla.org/docs/Web/API/XMLHttpRequest) or [`fetch`](https://developer.mozilla.org/docs/Web/API/Fetch_API/Using_Fetch). This prevents scripts on another site from acting on behalf of the user and from gaining access to another site's resources without permission.

On the web, Flutter renders graphics using WebGL or WebGPU. WebGL requires access to raw image bytes to draw images onto a canvas, which requires servers to be configured with CORS headers. If CORS is not enabled on the image server, Flutter automatically falls back to rendering cross-origin images using HTML `<img>` elements.

## Solutions

[#](#solutions)

There are multiple solutions to workaround CORS restrictions in Flutter.

### In-memory, asset, and same-origin network images

[#](#in-memory-asset-and-same-origin-network-images)

If the app has the bytes of the encoded image in memory, provided as an [asset](/ui/assets/assets-and-images), or stored on the same server that serves the application (also known as *same-origin*), no extra effort is necessary. The image can be displayed using [`Image.memory`](https://api.flutter.dev/flutter/widgets/Image/Image.memory.html), [`Image.asset`](https://api.flutter.dev/flutter/widgets/Image/Image.asset.html), or [`Image.network`](https://api.flutter.dev/flutter/widgets/Image/Image.network.html).

### Host images in a CORS-enabled CDN

[#](#host-images-in-a-cors-enabled-cdn)

Typically, content delivery networks (CDN) can be configured to customize what domains are allowed to access your content. For example, Firebase site hosting allows [specifying a custom](https://firebase.google.com/docs/hosting/full-config#headers) `Access-Control-Allow-Origin` header in the `firebase.json` file.

### Use a CORS proxy if you have no control over the origin server

[#](#use-a-cors-proxy-if-you-have-no-control-over-the-origin-server)

If the image server cannot be configured to allow CORS requests from your application, you might still be able to load images by proxying the requests through another server. This requires that the intermediate server has sufficient access to load the images.

This method can be used in situations when the original image server serves images publicly, but is not configured with the correct CORS headers.

Examples:

* Using [CloudFlare Workers](https://developers.cloudflare.com/workers/examples/cors-header-proxy).
* Using [Firebase Functions](https://github.com/7kfpun/cors-proxy).

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-07-31. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/platform-integration/web/web-images.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/platform-integration/web/web-images\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/platform-integration/web/web-images.md "Report an issue with this page").

----
url: https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.13.1
----

The 2.13.1 release of the Dart and Flutter DevTools includes the following changes among other general improvements. To learn more about DevTools, check out the [DevTools overview](https://docs.flutter.dev/tools/devtools).

## General updates

[#](#general-updates)

* This release included a lot of cleanup and reduction in technical debt. The most notable is the completion of our migration to sound null safety.
* Show release notes in IDE embedded versions of DevTools - [#4053](https://github.com/flutter/devtools/pull/4053)
* Polish to the DevTools footer - [#3989](https://github.com/flutter/devtools/pull/3989), [#4026](https://github.com/flutter/devtools/pull/4026), [#4041](https://github.com/flutter/devtools/pull/4041), [#4076](https://github.com/flutter/devtools/pull/4076)

## Performance updates

[#](#performance-updates)

* Added a new feature to help you debug raster jank in your Flutter app. This feature allows you to take a snapshot of the current screen shown in your app, and then break down rendering time for that scene by layer. This can help you identify parts of a scene that are expensive to rasterize - [#4046](https://github.com/flutter/devtools/pull/4046)

* Added a scope setting for "Track Widget Builds", allowing you to specify whether widget builds should be tracked in your code only or in all code - [#4010](https://github.com/flutter/devtools/pull/4010)

## CPU profiler updates

[#](#cpu-profiler-updates)

* Use package uris instead of file uris in the CPU profiler "Source" column - [#3932](https://github.com/flutter/devtools/pull/3932)

## Debugger updates

[#](#debugger-updates)

* Fix scrolling bug with debugger breakpoints - [#4074](https://github.com/flutter/devtools/pull/4074)

## Flutter inspector updates

[#](#flutter-inspector-updates)

* Add support for displaying flex values larger than 5 in the Layout Explorer - [#4055](https://github.com/flutter/devtools/pull/4055)

## Full commit history

[#](#full-commit-history)

To find a complete list of changes since the previous release, check out [the diff on GitHub](https://github.com/flutter/devtools/compare/v2.12.2...v2.13.1).

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/release-notes/release-notes-2.13.1.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.13.1\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/release-notes/release-notes-2.13.1.md "Report an issue with this page").

----
url: https://docs.flutter.dev/learn/pathway/how-flutter-works.md
----

\# How Flutter works > Dive deeper into how Flutter works through a six-part video series. ## Flutter's architecture {:#architecture}Welcome to the first episode of "How Flutter Works," a six-part series designed to explore what happens to your Dart code after you hand it off to the Flutter framework. This episode takes a high-level look at Flutter's architecture, covering declarative code, multi-platform frameworks, and the role of Dart. This video is perfect for anyone beginning to research Flutter who wants to understand the big picture. ## Widgets and the three trees {:#three-trees}Dive into Flutter's architecture by exploring its three primary trees: \`Widget\`, \`Element\`, and \`RenderObject\`. Discover how widgets provide the declarative API for Flutter developers and see how elements glue widgets to the rendering layer. You'll also learn about the role of render objects in translating widget values into painting calls. ## State objects and their lifecycle {:#state}Episode 3 of "How Flutter Works" dives deep into the \`State\` class, a critical piece behind every \`StatefulWidget\`. Follow the full lifecycle of a \`State\` object—from \`initState\`, where you initialize resources, to \`dispose\`, where you clean up. Along the way, explore important methods including \`didChangeDependencies\`, \`didUpdateWidget\`, and the all-important \`build\` method. By the end of the episode, you'll understand how \`State\` objects track, respond to, and manage changes in your Flutter apps—and how the \`State\` lifecycle enables efficient UI updates. This episode also peels back the curtain on how Flutter recurses down the widget tree after a \`setState\` call, building out only the parts of your app that need to change. You'll learn why \`const\` constructors matter for performance, why \`setState\` closures must be synchronous, and how elements (not widgets themselves) manage the actual rebuild process. If you're curious how Flutter keeps apps fast and responsive, or you just want to really understand what happens behind the scenes, this episode is packed with the essential foundations. ## The widgets that actually render {:#render-object-widgets}Ever wonder how your Flutter app actually renders to the screen? This video dives into \`RenderObjectWidget\`—the only type of widget in Flutter that creates something visual. While stateless and stateful widgets help structure your app, it's render object widgets that turn your UI code into real pixels. You'll learn how Flutter builds the \`Widget\`, \`Element\`, and \`RenderObject\` trees, why many common widgets don't directly render anything, and how Flutter uses \`RenderObjectWidget\` to create and update render objects that power your UI. ## A day in the life of a render object {:#render-objects}In Episode 5 of "How Flutter Works," Craig walks through a full day in the life of a \`RenderObject\`. Building on the concepts from Episode 4, this video explains the core responsibilities of render objects: layout, painting, hit testing, and accessibility. Craig demystifies how constraints flow down the render tree, how sizes come back up, and how parent render objects set their children's positions. He also breaks down key methods like \`layout\`, \`paint\`, and \`describeSemanticsConfiguration\`, showing how they fit together to keep your UI responsive and accurate. ## The Flutter engine and embedders {:#engine}In Episode 6 of "How Flutter Works," Craig takes us beneath the Dart code to explore the Flutter engine and embedders. This episode explains how Flutter mobile apps rely on native Android and iOS code to launch and operate, how the Flutter engine connects your Dart code to the host platform, and how embedders facilitate communication between the two. Craig also highlights the structure of a newly generated Flutter project, dives into how threads are managed in a Flutter app, and explains the roles of platform channels and the \[Pigeon]\[] package. You'll also learn why the Flutter engine is written in C++ rather than Dart, how it evolved from a fork of Chrome, and how it uses Skia or Impeller to render each frame. The episode wraps up by looking ahead to Flutter's future architecture improvements, which aim to simplify native interop even further. If you want a clear mental model of how Flutter apps work under the hood, this is the perfect way to connect all the layers together. \[Pigeon]: https\://pub.dev/packages/pigeon

----
url: https://docs.flutter.dev/perf/web-performance.md
----

\# Debug performance for web apps > Learn how to use Chrome DevTools to debug web performance issues. :::note Profiling Flutter web apps requires Flutter version 3.14 or later. ::: The Flutter framework emits timeline events as it works to build frames, draw scenes, and track other activity such as garbage collections. These events are exposed in the \[Chrome DevTools performance panel]\[] for debugging. :::note For information on how to optimize web loading speed, read the Flutter blog post \[Best practices for optimizing Flutter web loading speed]\[article]. \[article]: https\://blog.flutter.dev/best-practices-for-optimizing-flutter-web-loading-speed-7cc0df14ce5c ::: You can also emit your own timeline events using the \`dart:developer\` \[Timeline]\[] and \[TimelineTask]\[] APIs for further performance analysis. \[Chrome DevTools performance panel]: https\://developer.chrome.com/docs/devtools/performance \[Timeline]: https\://api.flutter.dev/flutter/dart-developer/Timeline-class.html \[TimelineTask]: https\://api.flutter.dev/flutter/dart-developer/TimelineTask-class.html !\[Screenshot of the Chrome DevTools performance panel]\(/assets/images/docs/tools/devtools/chrome-devtools-performance-panel.png) ## Optional flags to enhance tracing To configure which timeline events are tracked, set any of the following top-level properties to \`true\` in your app's \`main\` method. - \[debugProfileBuildsEnabled]\[]: Adds \`Timeline\` events for every \`Widget\` built. - \[debugProfileBuildsEnabledUserWidgets]\[]: Adds \`Timeline\` events for every user-created \`Widget\` built. - \[debugProfileLayoutsEnabled]\[]: Adds \`Timeline\` events for every \`RenderObject\` layout. - \[debugProfilePaintsEnabled]\[]: Adds \`Timeline\` events for every \`RenderObject\` painted. \[debugProfileBuildsEnabled]: https\://api.flutter.dev/flutter/widgets/debugProfileBuildsEnabled.html \[debugProfileBuildsEnabledUserWidgets]: https\://api.flutter.dev/flutter/widgets/debugProfileBuildsEnabledUserWidgets.html \[debugProfileLayoutsEnabled]: https\://api.flutter.dev/flutter/rendering/debugProfileLayoutsEnabled.html \[debugProfilePaintsEnabled]: https\://api.flutter.dev/flutter/rendering/debugProfilePaintsEnabled.html ## Instructions 1. \_\[Optional]\_ Set any desired tracing flags to true from your app's main method. 2. Run your Flutter web app in \[profile mode]\[]. 3. Open up the \[Chrome DevTools Performance panel]\[] for your application, and \[start recording]\[] to capture timeline events. \[start recording]: https\://developer.chrome.com/docs/devtools/performance/#record \[profile mode]: /testing/build-modes#profile \[Chrome DevTools performance panel]: https\://developer.chrome.com/docs/devtools/performance

----
url: https://docs.flutter.dev/add-to-app/android/add-flutter-fragment.md
----

\# Add a Flutter Fragment to an Android app > Learn how to add a Flutter Fragment to your existing Android app. This guide describes how to add a Flutter \`Fragment\` to an existing Android app. In Android, a \[\`Fragment\`]\[] represents a modular piece of a larger UI. A \`Fragment\` might be used to present a sliding drawer, tabbed content, a page in a \`ViewPager\`, or it might simply represent a normal screen in a single-\`Activity\` app. Flutter provides a \[\`FlutterFragment\`]\[] so that developers can present a Flutter experience any place that they can use a regular \`Fragment\`. If an \`Activity\` is equally applicable for your application needs, consider \[using a \`FlutterActivity\`]\[] instead of a \`FlutterFragment\`, which is quicker and easier to use. \`FlutterFragment\` allows developers to control the following details of the Flutter experience within the \`Fragment\`: \* Initial Flutter route \* Dart entrypoint to execute \* Opaque vs translucent background \* Whether \`FlutterFragment\` should control its surrounding \`Activity\` \* Whether a new \[\`FlutterEngine\`]\[] or a cached \`FlutterEngine\` should be used \`FlutterFragment\` also comes with a number of calls that must be forwarded from its surrounding \`Activity\`. These calls allow Flutter to react appropriately to OS events. All varieties of \`FlutterFragment\`, and its requirements, are described in this guide. ## Add a \`FlutterFragment\` to an \`Activity\` with a new \`FlutterEngine\` The first thing to do to use a \`FlutterFragment\` is to add it to a host \`Activity\`. To add a \`FlutterFragment\` to a host \`Activity\`, instantiate and attach an instance of \`FlutterFragment\` in \`onCreate()\` within the \`Activity\`, or at another time that works for your app: \`\`\`kotlin title="MyActivity.kt" class MyActivity : FragmentActivity() { companion object { // Define a tag String to represent the FlutterFragment within this // Activity's FragmentManager. This value can be whatever you'd like. private const val TAG\_FLUTTER\_FRAGMENT = "flutter\_fragment" } // Declare a local variable to reference the FlutterFragment so that you // can forward calls to it later. private var flutterFragment: FlutterFragment? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // Inflate a layout that has a container for your FlutterFragment. For // this example, assume that a FrameLayout exists with an ID of // R.id.fragment\_container. setContentView(R.layout.my\_activity\_layout) // Get a reference to the Activity's FragmentManager to add a new // FlutterFragment, or find an existing one. val fragmentManager: FragmentManager = supportFragmentManager // Attempt to find an existing FlutterFragment, in case this is not the // first time that onCreate() was run. flutterFragment = fragmentManager .findFragmentByTag(TAG\_FLUTTER\_FRAGMENT) as FlutterFragment? // Create and attach a FlutterFragment if one does not exist. if (flutterFragment == null) { var newFlutterFragment = FlutterFragment.createDefault() flutterFragment = newFlutterFragment fragmentManager .beginTransaction() .add( R.id.fragment\_container, newFlutterFragment, TAG\_FLUTTER\_FRAGMENT ) .commit() } } } \`\`\` \`\`\`java title="MyActivity.java" public class MyActivity extends FragmentActivity { // Define a tag String to represent the FlutterFragment within this // Activity's FragmentManager. This value can be whatever you'd like. private static final String TAG\_FLUTTER\_FRAGMENT = "flutter\_fragment"; // Declare a local variable to reference the FlutterFragment so that you // can forward calls to it later. private FlutterFragment flutterFragment; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Inflate a layout that has a container for your FlutterFragment. // For this example, assume that a FrameLayout exists with an ID of // R.id.fragment\_container. setContentView(R.layout.my\_activity\_layout); // Get a reference to the Activity's FragmentManager to add a new // FlutterFragment, or find an existing one. FragmentManager fragmentManager = getSupportFragmentManager(); // Attempt to find an existing FlutterFragment, // in case this is not the first time that onCreate() was run. flutterFragment = (FlutterFragment) fragmentManager .findFragmentByTag(TAG\_FLUTTER\_FRAGMENT); // Create and attach a FlutterFragment if one does not exist. if (flutterFragment == null) { flutterFragment = FlutterFragment.createDefault(); fragmentManager .beginTransaction() .add( R.id.fragment\_container, flutterFragment, TAG\_FLUTTER\_FRAGMENT ) .commit(); } } } \`\`\` The previous code is sufficient to render a Flutter UI that begins with a call to your \`main()\` Dart entrypoint, an initial Flutter route of \`/\`, and a new \`FlutterEngine\`. However, this code is not sufficient to achieve all expected Flutter behavior. Flutter depends on various OS signals that must be forwarded from your host \`Activity\` to \`FlutterFragment\`. These calls are shown in the following example: \`\`\`kotlin title="MyActivity.kt" class MyActivity : FragmentActivity() { override fun onPostResume() { super.onPostResume() flutterFragment!!.onPostResume() } override fun onNewIntent(@NonNull intent: Intent) { flutterFragment!!.onNewIntent(intent) } override fun onBackPressed() { flutterFragment!!.onBackPressed() } override fun onRequestPermissionsResult( requestCode: Int, permissions: Array, grantResults: IntArray ) { flutterFragment!!.onRequestPermissionsResult( requestCode, permissions, grantResults ) } override fun onActivityResult( requestCode: Int, resultCode: Int, data: Intent? ) { super.onActivityResult(requestCode, resultCode, data) flutterFragment!!.onActivityResult( requestCode, resultCode, data ) } override fun onUserLeaveHint() { flutterFragment!!.onUserLeaveHint() } override fun onTrimMemory(level: Int) { super.onTrimMemory(level) flutterFragment!!.onTrimMemory(level) } } \`\`\` \`\`\`java title="MyActivity.java" public class MyActivity extends FragmentActivity { @Override public void onPostResume() { super.onPostResume(); flutterFragment.onPostResume(); } @Override protected void onNewIntent(@NonNull Intent intent) { flutterFragment.onNewIntent(intent); } @Override public void onBackPressed() { flutterFragment.onBackPressed(); } @Override public void onRequestPermissionsResult( int requestCode, @NonNull String\[] permissions, @NonNull int\[] grantResults ) { flutterFragment.onRequestPermissionsResult( requestCode, permissions, grantResults ); } @Override public void onActivityResult( int requestCode, int resultCode, @Nullable Intent data ) { super.onActivityResult(requestCode, resultCode, data); flutterFragment.onActivityResult( requestCode, resultCode, data ); } @Override public void onUserLeaveHint() { flutterFragment.onUserLeaveHint(); } @Override public void onTrimMemory(int level) { super.onTrimMemory(level); flutterFragment.onTrimMemory(level); } } \`\`\` With the OS signals forwarded to Flutter, your \`FlutterFragment\` works as expected. You have now added a \`FlutterFragment\` to your existing Android app. The simplest integration path uses a new \`FlutterEngine\`, which comes with a non-trivial initialization time, leading to a blank UI until Flutter is initialized and rendered the first time. Most of this time overhead can be avoided by using a cached, pre-warmed \`FlutterEngine\`, which is discussed in \[Using a pre-warmed \`FlutterEngine\`]\(#using-a-pre-warmed-flutterengine). ### Automatic back button and predictive back handling When embedding a \`FlutterFragment\` into a native Android app on Android 13 or higher (API level 33+), you can configure the fragment to automatically handle system back button presses and predictive back gestures without manually forwarding \`onBackPressed()\`. Instead of overriding \`onBackPressed\` as shown above, set \`shouldAutomaticallyHandleOnBackPressed(true)\` when building your fragment: \`\`\`kotlin val flutterFragment = FlutterFragment.withNewEngine() .shouldAutomaticallyHandleOnBackPressed(true) .build() \`\`\` \`\`\`java FlutterFragment flutterFragment = FlutterFragment.withNewEngine() .shouldAutomaticallyHandleOnBackPressed(true) .build(); \`\`\` ## Using a pre-warmed \`FlutterEngine\` By default, a \`FlutterFragment\` creates its own instance of a \`FlutterEngine\`, which requires non-trivial warm-up time. This means your user sees a blank \`Fragment\` for a brief moment. You can mitigate most of this warm-up time by using an existing, pre-warmed instance of \`FlutterEngine\`. To use a pre-warmed \`FlutterEngine\` in a \`FlutterFragment\`, instantiate a \`FlutterFragment\` with the \`withCachedEngine()\` factory method. \`\`\`kotlin title="MyApplication.kt" // Somewhere in your app, before your FlutterFragment is needed, // like in the Application class ... // Instantiate a FlutterEngine. val flutterEngine = FlutterEngine(context) // Start executing Dart code in the FlutterEngine. flutterEngine.getDartExecutor().executeDartEntrypoint( DartEntrypoint.createDefault() ) // Cache the pre-warmed FlutterEngine to be used later by FlutterFragment. FlutterEngineCache .getInstance() .put("my\_engine\_id", flutterEngine) \`\`\` \`\`\`kotlin title="MyActivity.java" FlutterFragment.withCachedEngine("my\_engine\_id").build() \`\`\` \`\`\`java title="MyApplication.java" // Somewhere in your app, before your FlutterFragment is needed, // like in the Application class ... // Instantiate a FlutterEngine. FlutterEngine flutterEngine = new FlutterEngine(context); // Start executing Dart code in the FlutterEngine. flutterEngine.getDartExecutor().executeDartEntrypoint( DartEntrypoint.createDefault() ); // Cache the pre-warmed FlutterEngine to be used later by FlutterFragment. FlutterEngineCache .getInstance() .put("my\_engine\_id", flutterEngine); \`\`\` \`\`\`java title="MyActivity.java" FlutterFragment.withCachedEngine("my\_engine\_id").build(); \`\`\` \`FlutterFragment\` internally knows about \[\`FlutterEngineCache\`]\[] and retrieves the pre-warmed \`FlutterEngine\` based on the ID given to \`withCachedEngine()\`. By providing a pre-warmed \`FlutterEngine\`, as previously shown, your app renders the first Flutter frame as quickly as possible. #### Initial route with a cached engine The concept of an initial route is available when configuring a \`FlutterActivity\` or a \`FlutterFragment\` with a new \`FlutterEngine\`. However, \`FlutterActivity\` and \`FlutterFragment\` don't offer the concept of an initial route when using a cached engine. This is because a cached engine is expected to already be running Dart code, which means it's too late to configure the initial route. Developers that would like their cached engine to begin with a custom initial route can configure their cached \`FlutterEngine\` to use a custom initial route just before executing the Dart entrypoint. The following example demonstrates the use of an initial route with a cached engine: \`\`\`kotlin title="MyApplication.kt" class MyApplication : Application() { lateinit var flutterEngine : FlutterEngine override fun onCreate() { super.onCreate() // Instantiate a FlutterEngine. flutterEngine = FlutterEngine(this) // Configure an initial route. flutterEngine.navigationChannel.setInitialRoute("your/route/here"); // Start executing Dart code to pre-warm the FlutterEngine. flutterEngine.dartExecutor.executeDartEntrypoint( DartExecutor.DartEntrypoint.createDefault() ) // Cache the FlutterEngine to be used by FlutterActivity or FlutterFragment. FlutterEngineCache .getInstance() .put("my\_engine\_id", flutterEngine) } } \`\`\` \`\`\`java title="MyApplication.java" public class MyApplication extends Application { @Override public void onCreate() { super.onCreate(); // Instantiate a FlutterEngine. flutterEngine = new FlutterEngine(this); // Configure an initial route. flutterEngine.getNavigationChannel().setInitialRoute("your/route/here"); // Start executing Dart code to pre-warm the FlutterEngine. flutterEngine.getDartExecutor().executeDartEntrypoint( DartEntrypoint.createDefault() ); // Cache the FlutterEngine to be used by FlutterActivity or FlutterFragment. FlutterEngineCache .getInstance() .put("my\_engine\_id", flutterEngine); } } \`\`\` By setting the initial route of the navigation channel, the associated \`FlutterEngine\` displays the desired route upon initial execution of the \`runApp()\` Dart function. Changing the initial route property of the navigation channel after the initial execution of \`runApp()\` has no effect. Developers who would like to use the same \`FlutterEngine\` between different \`Activity\`s and \`Fragment\`s and switch the route between those displays need to set up a method channel and explicitly instruct their Dart code to change \`Navigator\` routes. ## Display a splash screen The initial display of Flutter content requires some wait time, even if a pre-warmed \`FlutterEngine\` is used. To help improve the user experience around this brief waiting period, Flutter supports the display of a splash screen (also known as "launch screen") until Flutter renders its first frame. For instructions about how to show a launch screen, see the \[splash screen guide]\[]. ## Run Flutter with a specified initial route An Android app might contain many independent Flutter experiences, running in different \`FlutterFragment\`s, with different \`FlutterEngine\`s. In these scenarios, it's common for each Flutter experience to begin with different initial routes (routes other than \`/\`). To facilitate this, \`FlutterFragment\`'s \`Builder\` allows you to specify a desired initial route, as shown: \`\`\`kotlin title="MyActivity.kt" // With a new FlutterEngine. val flutterFragment = FlutterFragment.withNewEngine() .initialRoute("myInitialRoute/") .build() \`\`\` \`\`\`java title="MyActivity.java" // With a new FlutterEngine. FlutterFragment flutterFragment = FlutterFragment.withNewEngine() .initialRoute("myInitialRoute/") .build(); \`\`\` :::note \`FlutterFragment\`'s initial route property has no effect when a pre-warmed \`FlutterEngine\` is used because the pre-warmed \`FlutterEngine\` already chose an initial route. The initial route can be chosen explicitly when pre-warming a \`FlutterEngine\`. ::: ## Run Flutter from a specified entrypoint Similar to varying initial routes, different \`FlutterFragment\`s might want to execute different Dart entrypoints. In a typical Flutter app, there is only one Dart entrypoint: \`main()\`, but you can define other entrypoints. \`FlutterFragment\` supports specification of the desired Dart entrypoint to execute for the given Flutter experience. To specify an entrypoint, build \`FlutterFragment\`, as shown: \`\`\`kotlin title="MyActivity.kt" val flutterFragment = FlutterFragment.withNewEngine() .dartEntrypoint("mySpecialEntrypoint") .build() \`\`\` \`\`\`java title="MyActivity.java" FlutterFragment flutterFragment = FlutterFragment.withNewEngine() .dartEntrypoint("mySpecialEntrypoint") .build(); \`\`\` The \`FlutterFragment\` configuration results in the execution of a Dart entrypoint called \`mySpecialEntrypoint()\`. Notice that the parentheses \`()\` are not included in the \`dartEntrypoint\` \`String\` name. :::note \`FlutterFragment\`'s Dart entrypoint property has no effect when a pre-warmed \`FlutterEngine\` is used because the pre-warmed \`FlutterEngine\` already executed a Dart entrypoint. The Dart entrypoint can be chosen explicitly when pre-warming a \`FlutterEngine\`. ::: ## Control \`FlutterFragment\`'s render mode \`FlutterFragment\` can either use a \`SurfaceView\` to render its Flutter content, or it can use a \`TextureView\`. The default is \`SurfaceView\`, which is significantly better for performance than \`TextureView\`. However, \`SurfaceView\` can't be interleaved in the middle of an Android \`View\` hierarchy. A \`SurfaceView\` must either be the bottommost \`View\` in the hierarchy, or the topmost \`View\` in the hierarchy. Additionally, on Android versions before Android N, \`SurfaceView\`s can't be animated because their layout and rendering aren't synchronized with the rest of the \`View\` hierarchy. If either of these use cases are requirements for your app, then you need to use \`TextureView\` instead of \`SurfaceView\`. Select a \`TextureView\` by building a \`FlutterFragment\` with a \`texture\` \`RenderMode\`: \`\`\`kotlin title="MyActivity.kt" // With a new FlutterEngine. val flutterFragment = FlutterFragment.withNewEngine() .renderMode(FlutterView\.RenderMode.texture) .build() // With a cached FlutterEngine. val flutterFragment = FlutterFragment.withCachedEngine("my\_engine\_id") .renderMode(FlutterView\.RenderMode.texture) .build() \`\`\` \`\`\`java title="MyActivity.java" // With a new FlutterEngine. FlutterFragment flutterFragment = FlutterFragment.withNewEngine() .renderMode(FlutterView\.RenderMode.texture) .build(); // With a cached FlutterEngine. FlutterFragment flutterFragment = FlutterFragment.withCachedEngine("my\_engine\_id") .renderMode(FlutterView\.RenderMode.texture) .build(); \`\`\` Using the configuration shown, the resulting \`FlutterFragment\` renders its UI to a \`TextureView\`. ## Display a \`FlutterFragment\` with transparency By default, \`FlutterFragment\` renders with an opaque background, using a \`SurfaceView\`. (See "Control \`FlutterFragment\`'s render mode.") That background is black for any pixels that aren't painted by Flutter. Rendering with an opaque background is the preferred rendering mode for performance reasons. Flutter rendering with transparency on Android negatively affects performance. However, there are many designs that require transparent pixels in the Flutter experience that show through to the underlying Android UI. For this reason, Flutter supports translucency in a \`FlutterFragment\`. :::note Both \`SurfaceView\` and \`TextureView\` support transparency. However, when a \`SurfaceView\` is instructed to render with transparency, it positions itself at a higher z-index than all other Android \`View\`s, which means it appears above all other \`View\`s. This is a limitation of \`SurfaceView\`. If it's acceptable to render your Flutter experience on top of all other content, then \`FlutterFragment\`'s default \`RenderMode\` of \`surface\` is the \`RenderMode\` that you should use. However, if you need to display Android \`View\`s both above and below your Flutter experience, then you must specify a \`RenderMode\` of \`texture\`. See "Control \`FlutterFragment\`'s render mode" for information about controlling the \`RenderMode\`. ::: To enable transparency for a \`FlutterFragment\`, build it with the following configuration: \`\`\`kotlin title="MyActivity.kt" // Using a new FlutterEngine. val flutterFragment = FlutterFragment.withNewEngine() .transparencyMode(FlutterView\.TransparencyMode.transparent) .build() // Using a cached FlutterEngine. val flutterFragment = FlutterFragment.withCachedEngine("my\_engine\_id") .transparencyMode(FlutterView\.TransparencyMode.transparent) .build() \`\`\` \`\`\`java title="MyActivity.java" // Using a new FlutterEngine. FlutterFragment flutterFragment = FlutterFragment.withNewEngine() .transparencyMode(FlutterView\.TransparencyMode.transparent) .build(); // Using a cached FlutterEngine. FlutterFragment flutterFragment = FlutterFragment.withCachedEngine("my\_engine\_id") .transparencyMode(FlutterView\.TransparencyMode.transparent) .build(); \`\`\` ## The relationship between \`FlutterFragment\` and its \`Activity\` Some apps choose to use \`Fragment\`s as entire Android screens. In these apps, it would be reasonable for a \`Fragment\` to control system chrome like Android's status bar, navigation bar, and orientation. In other apps, \`Fragment\`s are used to represent only a portion of a UI. A \`FlutterFragment\` might be used to implement the inside of a drawer, a video player, or a single card. In these situations, it would be inappropriate for the \`FlutterFragment\` to affect Android's system chrome because there are other UI pieces within the same \`Window\`. \`FlutterFragment\` comes with a concept that helps differentiate between the case when a \`FlutterFragment\` should be able to control its host \`Activity\`, and the cases when a \`FlutterFragment\` should only affect its own behavior. To prevent a \`FlutterFragment\` from exposing its \`Activity\` to Flutter plugins, and to prevent Flutter from controlling the \`Activity\`'s system UI, use the \`shouldAttachEngineToActivity()\` method in \`FlutterFragment\`'s \`Builder\`, as shown: \`\`\`kotlin title="MyActivity.kt" // Using a new FlutterEngine. val flutterFragment = FlutterFragment.withNewEngine() .shouldAttachEngineToActivity(false) .build() // Using a cached FlutterEngine. val flutterFragment = FlutterFragment.withCachedEngine("my\_engine\_id") .shouldAttachEngineToActivity(false) .build() \`\`\` \`\`\`java title="MyActivity.java" // Using a new FlutterEngine. FlutterFragment flutterFragment = FlutterFragment.withNewEngine() .shouldAttachEngineToActivity(false) .build(); // Using a cached FlutterEngine. FlutterFragment flutterFragment = FlutterFragment.withCachedEngine("my\_engine\_id") .shouldAttachEngineToActivity(false) .build(); \`\`\` Passing \`false\` to the \`shouldAttachEngineToActivity()\` \`Builder\` method prevents Flutter from interacting with the surrounding \`Activity\`. The default value is \`true\`, which allows Flutter and Flutter plugins to interact with the surrounding \`Activity\`. :::note Some plugins might expect or require an \`Activity\` reference. Ensure that none of your plugins require an \`Activity\` before you disable access. ::: \[\`Fragment\`]: https\://developer.android.com/guide/components/fragments \[\`FlutterFragment\`]: https\://api.flutter.dev/javadoc/io/flutter/embedding/android/FlutterFragment.html \[using a \`FlutterActivity\`]: /add-to-app/android/add-flutter-screen \[\`FlutterEngine\`]: https\://api.flutter.dev/javadoc/io/flutter/embedding/engine/FlutterEngine.html \[\`FlutterEngineCache\`]: https\://api.flutter.dev/javadoc/io/flutter/embedding/engine/FlutterEngineCache.html \[splash screen guide]: /platform-integration/android/splash-screen

----
url: https://docs.flutter.dev/ai/best-practices/mode-of-interaction
----

It's a mistake to think of a request to an LLM in the same way as calling a function. Given the same set of inputs in the same order, a function acts predictably. We can write tests and inject faults and harden a function for a wide variety of inputs.

An LLM is not like that. A better way to think about it is as if the LLM were a user and to treat the data we get from them as such. Like a user, an LLM is nondeterministic, often wrong (partially or wholly) and sometimes plain random. To guard our apps under these conditions, we need to build the same guardrails around LLM input as we do around user input.

If we can do that successfully, then we can bring extraordinary abilities to apps in the form of problem solving and creativity that can rival that of a human.

### Separation of concerns

[#](#separation-of-concerns)

LLMs are good at some things and bad at others; the key is to bring them into your apps for the good while mitigating the bad. As an example, let's consider the task list in the Crossword Companion:

The task list is the set of clues that need solving. The goal is to use colors and solutions in the task list to show progress during the solving process. The initial implementation provided the model with a tool for managing the task list, asking it to provide updates on progress as it went. Flash could not solve the puzzle this way, but Pro could. Unfortunately, it solved it in big chunks, only remembering to update the task list once or twice with a big delay in between. No amount of prompting could convince it to update the tasks as it went. You'll see the same behavior with modern AI agents managing their own task lists; that's just where we are in the evolution of LLMs at the moment.

So how do we get consistent, deterministic updates of the task list? Take task management out of the LLM's hands and handle it in the code.

To generalize, before applying an LLM solution to a problem you're facing, ask yourself whether an LLM is the best tool for the job. Is human-like problem solving and creativity worth the tradeoff in unpredictability?

The answer to that question comes with experimentation. Here are some examples from the sample:

| Task                                              | LLM Suitability                                              | Code Suitability                                                                  |
| ------------------------------------------------- | ------------------------------------------------------------ | --------------------------------------------------------------------------------- |
| **Parsing the grid for size, contents and clues** | Great for an LLM by using vision and language understanding  | Difficult to write the code to do this                                            |
| **Validating grid contents**                      | Possible to do with another LLM checking the work            | Easier for a human to glance at and adjust                                        |
| **Handling the task list**                        | An LLM is unlikely to do this consistently                   | Easy to write the code to loop through a task list, updating as it goes           |
| **Solving each clue**                             | Great for an LLM using language understanding and generation | Difficult to do given real world clues that depend on word play, names, and slang |
| **Resolving conflicts**                           | An LLM is inconsistent on this kind of looping               | Easy for a human to glance at and adjust                                          |

It's a judgement call for sure, but if you can reasonably write the code to do it, your results will be predictable. However, if writing the code would be unreasonably difficult, then consider an LLM, knowing you'll have to build the guardrails like we did in the sample.

### Ask vs agent

[#](#ask-vs-agent)

There's more than just one pivot to consider besides code vs. LLM. Models operate in roughly two modes: "ask" and "agent".

A LLM is in "ask" mode when we prompt it without giving it tools to affect change in the world, for example, no tools at all or tools just for looking up data. Both the crossword interference model and clue solver models run in ask mode, using tools only for additional data.

On the other hand, when we give an LLM a set of tools that allow it to operate on our behalf in the world – like reading and writing files, executing bash commands, loading web pages, calling web APIs, and so on – that LLM is in "agent" mode.

### Guardrails

[#](#guardrails)

The difference between ask and agent mode is not the model you choose or the prompts you give it, but the tools you supply. The combination of the tools and the agentic loop described in the Tool calls section allow an LLM to call any number of those tools as often as it decides. Giving it that power puts the responsibility on you to make sure you treat it as unpredictable; more like a person than a program.

You do that the same way that you validate user input, by building up a suite of tests to see how your app works against LLM responses. Give real LLMs a wide variety of prompts and mock the tools to evaluate how the LLM is using them. Like your first user testing experience, your first LLM testing results might surprise you. Use that data to build the guardrails you need to harden your app.

In the sample, we didn't have to guard against harm, but we did have to guard against imperfect results. It was extensive testing against real-world data that led to the institution of human-in-the-loop guards against attempting to solve an invalid puzzle or conflicting solutions. In this way, Flutter and Firebase AI Logic make the perfect combination to harness the power of an LLM and bring unique capabilities to your apps.

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-08-19. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/ai/best-practices/mode-of-interaction.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/ai/best-practices/mode-of-interaction\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/ai/best-practices/mode-of-interaction.md "Report an issue with this page").

----
url: https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.28.4.md
----

\# DevTools 2.28.4 release notes > Release notes for Dart and Flutter DevTools version 2.28.4. The 2.28.4 release of the Dart and Flutter DevTools includes the following changes among other general improvements. To learn more about DevTools, check out the \[DevTools overview]\(https\://docs.flutter.dev/tools/devtools). This was a cherry-pick release on top of DevTools 2.28.3. To learn about the improvements included in DevTools 2.28.3, please read the \[release notes]\(/tools/devtools/release-notes/release-notes-2.28.3). ## Inspector updates \* Added link to package directory documentation, from the inspect settings dialog - \[#6825]\(https\://github.com/flutter/devtools/pull/6825) \* Fixed a bug where widgets owned by the Flutter framework were showing up in the widget tree view - \[#6857]\(https\://github.com/flutter/devtools/pull/6857) ## Full commit history To find a complete list of changes in this release, check out the \[DevTools git log]\(https\://github.com/flutter/devtools/tree/v2.28.4).

----
url: https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.15.0
----

The 2.15.0 release of the Dart and Flutter DevTools includes the following changes among other general improvements. To learn more about DevTools, check out the [DevTools overview](https://docs.flutter.dev/tools/devtools).

## General updates

[#](#general-updates)

* The DevTools 2.15 release includes improvements to all tables in DevTools (logging view, network profiler, CPU profiler, and so on) - [#4175](https://github.com/flutter/devtools/pull/4175)

## Performance updates

[#](#performance-updates)

* Added outlines to each layer displayed in the Raster Metrics tool - [#4192](https://github.com/flutter/devtools/pull/4192)

* Fix a bug with loading offline data - [#4189](https://github.com/flutter/devtools/pull/4189)

## Network updates

[#](#network-updates)

* Added a Json viewer with syntax highlighting for network responses - [#4167](https://github.com/flutter/devtools/pull/4167)

* Added the ability to copy network responses - [#4190](https://github.com/flutter/devtools/pull/4190)

## Memory updates

[#](#memory-updates)

* Added the ability to select a different isolate from the DevTools footer - [#4173](https://github.com/flutter/devtools/pull/4173)
* Made the automatic snapshotting feature a configurable setting - [#4200](https://github.com/flutter/devtools/pull/4200)

## CPU profiler

[#](#cpu-profiler)

* Stop manually truncating source URIs in the profiler tables - [#4166](https://github.com/flutter/devtools/pull/4166)

## Full commit history

[#](#full-commit-history)

To find a complete list of changes since the previous release, check out [the diff on GitHub](https://github.com/flutter/devtools/compare/v2.14.0...v2.15.0).

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/release-notes/release-notes-2.15.0.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.15.0\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/release-notes/release-notes-2.15.0.md "Report an issue with this page").

----
url: https://docs.flutter.dev/learn/pathway/tutorial/listenable-builder.md
----

\# Rebuild UI when state changes > Instructions on how to manage state with ChangeNotifiers.

<!--?code-excerpt replace="/ *\/\/\s+ignore_for_file:[^\n]+\n//g;"?-->

Learn to use ListenableBuilder to automatically rebuild UI and handle all possible states with switch expressions. title: What you'll accomplish items: - title: Use ListenableBuilder to rebuild UI automatically icon: sync - title: Handle all possible states with switch expressions icon: alt\_route - title: Build the complete View layer with proper styling icon: article --- ### Introduction The view layer is your UI, and in Flutter, that refers to your app's widgets. As it pertains to this tutorial, the important part is wiring up your UI to respond to data changes from the ViewModel. \[\`ListenableBuilder\`]\[] is a widget that can "listen" to a \[\`ChangeNotifier\`]\[], and automatically rebuilds when it's provided \`ChangeNotifier\` calls \`notifyListeners()\`. \[\`ListenableBuilder\`]: https\://api.flutter.dev/flutter/widgets/ListenableBuilder-class.html \[\`ChangeNotifier\`]: https\://api.flutter.dev/flutter/foundation/ChangeNotifier-class.html ### Create the article view widget Create the \`ArticleView\` widget that manages your page's layout and ViewModel lifecycle. Because it must explicitly initialize data fetching before rendering, implement it as a \`StatefulWidget\`. Start by creating the basic stateful structure:

<!--?code-excerpt "fwe/wikipedia_reader/lib/step4a_main.dart"?-->

\`\`\`dart import 'package:material\_ui/material\_ui.dart'; class ArticleView extends StatefulWidget { const ArticleView({super.key}); @override State createState() => \_ArticleViewState(); } class \_ArticleViewState extends State { // The view model will be instantiated here next. @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('Wikipedia Flutter')), body: const Center(child: Text('Loading...')), ); } } \`\`\` ### Instantiate the article view model Next, initialize your \`ArticleViewModel\` mapping it to the state's lifecycle. Provide the ViewModel and execute \`fetchArticle()\` within \`initState()\`:

<!--?code-excerpt "fwe/wikipedia_reader/lib/step4b_main.dart (view-model)"?-->

\`\`\`dart highlightLines=2-8 class ArticleView extends StatefulWidget { const ArticleView({super.key}); @override State createState() => \_ArticleViewState(); } class \_ArticleViewState extends State { final ArticleViewModel viewModel = ArticleViewModel(ArticleModel()); @override void initState() { super.initState(); viewModel.fetchArticle(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('Wikipedia Flutter')), body: const Center(child: Text('Loading...')), ); } } \`\`\` ### Update your app to include the article view Connect everything together by updating your \`MainApp\` to include your completed \`ArticleView\`. Replace your existing \`MainApp\` with this updated version:

<!--?code-excerpt "fwe/wikipedia_reader/lib/step4_main.dart (main-app)"?-->

\`\`\`dart highlightLines=6 class MainApp extends StatelessWidget { const MainApp({super.key}); @override Widget build(BuildContext context) { return const MaterialApp(home: ArticleView()); } } \`\`\` This change switches from the console-based test to the full UI experience with proper state management. ### Listen for state changes Wrap your UI in a \[\`ListenableBuilder\`]\[] to listen for state changes, and pass it a \`ChangeNotifier\` object. In this case, the \`ArticleViewModel\` extends \`ChangeNotifier\`.

<!--?code-excerpt "fwe/wikipedia_reader/lib/step4c_main.dart (view-model)"?-->

\`\`\`dart highlightLines=21-26 class ArticleView extends StatefulWidget { const ArticleView({super.key}); @override State createState() => \_ArticleViewState(); } class \_ArticleViewState extends State { final ArticleViewModel viewModel = ArticleViewModel(ArticleModel()); @override void initState() { super.initState(); viewModel.fetchArticle(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('Wikipedia Flutter')), body: ListenableBuilder( listenable: viewModel, builder: (context, child) { return const Center(child: Text('Loading...')); }, ), ); } } \`\`\` \`ListenableBuilder\` uses the \*builder\* pattern, which requires a callback rather than a \`child\` widget to build the widget tree below it. These widgets are flexible because you can perform operations within the callback, building different widgets based on the state. \[\`ListenableBuilder\`]: https\://api.flutter.dev/flutter/widgets/ListenableBuilder-class.html ### Handle possible view model states Recall the \`ArticleViewModel\`, which has three properties that the UI is interested in: - \`Summary? summary\` - \`bool isLoading\` - \`Exception? error\` Depending on the combined state of these properties, the UI can display different widgets. Use Dart's support for \[switch expressions]\[] to handle all possible combinations in a clean, readable way:

<!--?code-excerpt "fwe/wikipedia_reader/lib/step4_main.dart (view-model)"?-->

\`\`\`dart class ArticleView extends StatefulWidget { const ArticleView({super.key}); @override State createState() => \_ArticleViewState(); } class \_ArticleViewState extends State { final ArticleViewModel viewModel = ArticleViewModel(ArticleModel()); @override void initState() { super.initState(); viewModel.fetchArticle(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('Wikipedia Flutter')), body: Center( child: ListenableBuilder( listenable: viewModel, builder: (context, \_) { return switch (( viewModel.isLoading, viewModel.summary, viewModel.error, )) { (true, \_, \_) => const CircularProgressIndicator(), (\_, \_, final Exception e) => Text('Error: $e'), (\_, final summary?, \_) => ArticlePage( summary: summary, nextArticleCallback: viewModel.fetchArticle, ), \_ => const Text('Something went wrong!'), }; }, ), ), ); } } \`\`\` This is an excellent example of how a declarative, reactive framework like Flutter and a pattern like MVVM work together: The UI is rendered based on the state and updates when a state changes demands it, but it doesn't manage any state or the process of updating itself. The business logic and rendering are completely separate from each other. \[switch expressions]: https\://dart.dev/language/branches#switch-expressions ### Complete the UI The only thing remaining is to use the properties and methods provided by the view model to build the UI. Now create a \`ArticlePage\` widget that displays the actual article content. This reusable widget takes summary data and a callback function:

<!--?code-excerpt "fwe/wikipedia_reader/lib/step4d_main.dart (page)"?-->

\`\`\`dart class ArticlePage extends StatelessWidget { const ArticlePage({ super.key, required this.summary, required this.nextArticleCallback, }); final Summary summary; final VoidCallback nextArticleCallback; @override Widget build(BuildContext context) { return const Center( child: Text('Article content will be displayed here...'), ); } } \`\`\` ### Add a scrollable layout Replace the placeholder with a scrollable column layout:

<!--?code-excerpt "fwe/wikipedia_reader/lib/step4e_main.dart (page)"?-->

\`\`\`dart highlightLines=13-16 class ArticlePage extends StatelessWidget { const ArticlePage({ super.key, required this.summary, required this.nextArticleCallback, }); final Summary summary; final VoidCallback nextArticleCallback; @override Widget build(BuildContext context) { return const SingleChildScrollView( child: Column( children: \[Text('Article content will be displayed here...')], ), ); } } \`\`\` ### Add article content and button Complete the layout with an article widget and navigation button:

<!--?code-excerpt "fwe/wikipedia_reader/lib/step4_main.dart (page)"?-->

\`\`\`dart highlightLines=16-20 class ArticlePage extends StatelessWidget { const ArticlePage({ super.key, required this.summary, required this.nextArticleCallback, }); final Summary summary; final VoidCallback nextArticleCallback; @override Widget build(BuildContext context) { return SingleChildScrollView( child: Column( children: \[ ArticleWidget(summary: summary), ElevatedButton( onPressed: nextArticleCallback, child: const Text('Next random article'), ), ], ), ); } } \`\`\` ### Create the \`ArticleWidget\` The \`ArticleWidget\` handles the display of the actual article content with proper styling and conditional rendering. #### Set up the basic article structure Start with the widget that accepts a \`summary\` parameter:

<!--?code-excerpt "fwe/wikipedia_reader/lib/step4f_main.dart (article)"?-->

\`\`\`dart class ArticleWidget extends StatelessWidget { const ArticleWidget({super.key, required this.summary}); final Summary summary; @override Widget build(BuildContext context) { return const Text('Article content will be displayed here...'); } } \`\`\` #### Add padding and column layout Wrap the content in proper padding and layout:

<!--?code-excerpt "fwe/wikipedia_reader/lib/step4g_main.dart (article)"?-->

\`\`\`dart highlightLines=8-14 class ArticleWidget extends StatelessWidget { const ArticleWidget({super.key, required this.summary}); final Summary summary; @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.all(8), child: Column( spacing: 10, children: \[const Text('Article content will be displayed here...')], ), ); } } \`\`\` #### Add conditional image display Add the article image that only shows when available:

<!--?code-excerpt "fwe/wikipedia_reader/lib/step4h_main.dart (article)"?-->

\`\`\`dart highlightLines=13 class ArticleWidget extends StatelessWidget { const ArticleWidget({super.key, required this.summary}); final Summary summary; @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.all(8), child: Column( spacing: 10, children: \[ if (summary.hasImage) Image.network(summary.originalImage!.source), const Text('Article content will be displayed here...'), ], ), ); } } \`\`\` #### Complete with styled text content Replace the placeholder text with a properly styled title, description, and extract:

<!--?code-excerpt "fwe/wikipedia_reader/lib/step4_main.dart (article)"?-->

\`\`\`dart highlightLines=14-25 class ArticleWidget extends StatelessWidget { const ArticleWidget({super.key, required this.summary}); final Summary summary; @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.all(8), child: Column( spacing: 10, children: \[ if (summary.hasImage) Image.network(summary.originalImage!.source), Text( summary.titles.normalized, overflow: TextOverflow\.ellipsis, style: Theme.of(context).textTheme.displaySmall, ), if (summary.description != null) Text( summary.description!, overflow: TextOverflow\.ellipsis, style: Theme.of(context).textTheme.bodySmall, ), Text(summary.extract), ], ), ); } } \`\`\` This widget demonstrates a few important UI concepts: - \*\*Conditional rendering\*\*: The \`if\` statements show content only when available. - \*\*Text styling\*\*: Different text styles create visual hierarchy using Flutter's theme system. - \*\*Proper spacing\*\*: The \`spacing\` parameter provides consistent vertical spacing. - \*\*Overflow handling\*\*: \`TextOverflow\.ellipsis\` prevents text from breaking the layout. ### Run the complete app Hot reload your app one final time. You should now see: 1. A loading spinner while the initial article loads. 1. The article's title, description, and summary extract. 1. An image (if the article has one). 1. A button to load another random article. To see the reactive UI in action, click the \*\*Next random article\*\* button. The app shows a loading state, fetches new data, and updates the display automatically. ### Review title: What you accomplished subtitle: Here's a summary of what you built and learned in this lesson. completed: true items: - title: Used ListenableBuilder to rebuild UI automatically icon: sync details: >- \`ListenableBuilder\` listens to your ViewModel and automatically rebuilds its children whenever \`notifyListeners()\` is called. In the MVVM pattern, this is the key connection between your ViewModel and View. - title: Handled all possible states with switch expressions icon: alt\_route details: >- Using a switch expression, you accounted for the possible state combinations with an appropriate user interface, Conditionally displaying a loading spinner, an error message, or the actual article content. With this handling, the UI is now more robust and complete. - title: Built the complete View layer with proper styling icon: article details: >- You created \`ArticleView\`, \`ArticlePage\`, and \`ArticleWidget\` with conditional rendering, text styling, proper spacing, and overflow handling. These are core UI patterns you'll use in every Flutter app. - title: Completed the MVVM architecture icon: celebration details: >- You've built a complete app with Model (data operations), ViewModel (state management), and View (reactive UI) layers. This separation of concerns helps your code be more testable, maintainable, and scalable. ### Test yourself - question: What is the purpose of ListenableBuilder in Flutter? options: - text: To create animations based on a ChangeNotifier. correct: false explanation: ListenableBuilder rebuilds UI on state changes, not specifically for animations. - text: "To listen to a ChangeNotifier and automatically rebuild its child widgets when \`notifyListeners()\` is called." correct: true explanation: ListenableBuilder listens to a Listenable and rebuilds its builder function when notified. - text: To manually control when widgets should be rebuilt. correct: false explanation: The rebuild is automatic when notifyListeners() is called; you don't control it manually. - text: To cache widget builds for better performance. correct: false explanation: ListenableBuilder is about reactive updates, not caching. - question: When does ListenableBuilder rebuild its child widgets? options: - text: Every time the app's frame refreshes. correct: false explanation: ListenableBuilder only rebuilds when notified, not on every frame. - text: When the Listenable it's listening to calls notifyListeners(). correct: true explanation: "ListenableBuilder subscribes to the Listenable and rebuilds its builder function whenever \`notifyListeners()\` is called." - text: Only when the widget is first mounted. correct: false explanation: "It rebuilds whenever \`notifyListeners()\` is called, not just on mount." - text: When the parent widget rebuilds. correct: false explanation: "ListenableBuilder rebuilds based on the Listenable, not parent rebuilds."

----
url: https://docs.flutter.dev/cookbook/plugins/picture-using-camera
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Take a picture using the camera](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

Many apps require working with the device's cameras to take photos and videos. Flutter provides the [`camera`](https://pub.dev/packages/camera) plugin for this purpose. The `camera` plugin provides tools to get a list of the available cameras, display a preview coming from a specific camera, and take photos or videos.

This recipe demonstrates how to use the `camera` plugin to display a preview, take a photo, and display it using the following steps:

1. Add the required dependencies.
2. Get a list of the available cameras.
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.

## 1. Add the required dependencies

[#](#1-add-the-required-dependencies)

To complete this recipe, you need to add three dependencies to your app:

* [`camera`](https://pub.dev/packages/camera)

  Provides tools to work with the cameras on the device.

* [`path_provider`](https://pub.dev/packages/path_provider)

  Finds the correct paths to store images.

* [`path`](https://pub.dev/packages/path)

  Creates paths that work on any platform.

To add the packages as dependencies, run `flutter pub add`:

```
$ flutter pub add camera path_provider path
```

## 2. Get a list of the available cameras

[#](#2-get-a-list-of-the-available-cameras)

Next, get a list of available cameras using the `camera` plugin.

dart

```
// Ensure that plugin services are initialized so that `availableCameras()`
// can be called before `runApp()`
WidgetsFlutterBinding.ensureInitialized();
​
// Obtain a list of the available cameras on the device.
final cameras = await availableCameras();
​
// Get a specific camera from the list of available cameras.
final firstCamera = cameras.first;
```

## 3. Create and initialize the `CameraController`

[#](#3-create-and-initialize-the-cameracontroller)

Once you have a camera, use the following steps 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.

1. Create a `StatefulWidget` with a companion `State` class.
2. Add a variable to the `State` class to store the `CameraController`.
3. Add a variable to the `State` class to store the `Future` returned from `CameraController.initialize()`.
4. Create and initialize the controller in the `initState()` method.
5. Dispose of the controller in the `dispose()` method.

dart

```
// A screen that allows users to take a picture using a given camera.
class TakePictureScreen extends StatefulWidget {
  const TakePictureScreen({super.key, required this.camera});
​
  final CameraDescription camera;
​
  @override
  TakePictureScreenState createState() => TakePictureScreenState();
}
​
class TakePictureScreenState extends State<TakePictureScreen> {
  late CameraController _controller;
  late Future<void> _initializeControllerFuture;
​
  @override
  void initState() {
    super.initState();
    // To display the current output from the Camera,
    // create a CameraController.
    _controller = CameraController(
      // Get a specific camera from the list of available cameras.
      widget.camera,
      // Define the resolution to use.
      ResolutionPreset.medium,
    );
​
    // Next, initialize the controller. This returns a Future.
    _initializeControllerFuture = _controller.initialize();
  }
​
  @override
  void dispose() {
    // 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.
    return Container();
  }
}
```

## 4. Use a `CameraPreview` to display the camera's feed

[#](#4-use-a-camerapreview-to-display-the-cameras-feed)

Next, use the `CameraPreview` widget from the `camera` package to display a preview of the camera's feed.

Use a [`FutureBuilder`](https://api.flutter.dev/flutter/widgets/FutureBuilder-class.html) 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.
FutureBuilder<void>(
  future: _initializeControllerFuture,
  builder: (context, snapshot) {
    if (snapshot.connectionState == ConnectionState.done) {
      // If the Future is complete, display the preview.
      return CameraPreview(_controller);
    } else {
      // Otherwise, display a loading indicator.
      return const Center(child: CircularProgressIndicator());
    }
  },
)
```

## 5. Take a picture with the `CameraController`

[#](#5-take-a-picture-with-the-cameracontroller)

You can use the `CameraController` to take pictures using the [`takePicture()`](https://pub.dev/documentation/camera/latest/camera/CameraController/takePicture.html) method, which returns an [`XFile`](https://pub.dev/documentation/cross_file/latest/cross_file/XFile-class.html), a cross-platform, simplified `File` abstraction. On both Android and IOS, the new image is stored in their respective cache directories, and the `path` to that location is returned in the `XFile`.

In this example, create a `FloatingActionButton` that takes a picture using the `CameraController` when a user taps on the button.

Taking a picture requires 2 steps:

1. Ensure that the camera is initialized.
2. Use the controller to take a picture and ensure that it returns a `Future<XFile>`.

It is good practice to wrap these operations in a `try / catch` block in order to handle any errors that might occur.

dart

```
FloatingActionButton(
  // Provide an onPressed callback.
  onPressed: () async {
    // Take the Picture in a try / catch block. If anything goes wrong,
    // catch the error.
    try {
      // Ensure that the camera is initialized.
      await _initializeControllerFuture;
​
      // Attempt to take a picture and then get the location
      // where the image file is saved.
      final image = await _controller.takePicture();
    } catch (e) {
      // If an error occurs, log the error to the console.
      print(e);
    }
  },
  child: const Icon(Icons.camera_alt),
)
```

## 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 the path created in the previous step.

dart

```
Image.file(File('path/to/my/picture.png'));
```

## Complete example

[#](#complete-example)

dart

```
import 'dart:async';
import 'dart:io';
​
import 'package:camera/camera.dart';
import 'package:material_ui/material_ui.dart';
​
Future<void> main() async {
  // Ensure that plugin services are initialized so that `availableCameras()`
  // can be called before `runApp()`
  WidgetsFlutterBinding.ensureInitialized();
​
  // Obtain a list of the available cameras on the device.
  final cameras = await availableCameras();
​
  // 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.
        camera: firstCamera,
      ),
    ),
  );
}
​
// A screen that allows users to take a picture using a given camera.
class TakePictureScreen extends StatefulWidget {
  const TakePictureScreen({super.key, required this.camera});
​
  final CameraDescription camera;
​
  @override
  TakePictureScreenState createState() => TakePictureScreenState();
}
​
class TakePictureScreenState extends State<TakePictureScreen> {
  late CameraController _controller;
  late Future<void> _initializeControllerFuture;
​
  @override
  void initState() {
    super.initState();
    // To display the current output from the Camera,
    // create a CameraController.
    _controller = CameraController(
      // Get a specific camera from the list of available cameras.
      widget.camera,
      // Define the resolution to use.
      ResolutionPreset.medium,
    );
​
    // Next, initialize the controller. This returns a Future.
    _initializeControllerFuture = _controller.initialize();
  }
​
  @override
  void dispose() {
    // Dispose of the controller when the widget is disposed.
    _controller.dispose();
    super.dispose();
  }
​
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const 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.
      body: FutureBuilder<void>(
        future: _initializeControllerFuture,
        builder: (context, snapshot) {
          if (snapshot.connectionState == ConnectionState.done) {
            // If the Future is complete, display the preview.
            return CameraPreview(_controller);
          } else {
            // Otherwise, display a loading indicator.
            return const Center(child: CircularProgressIndicator());
          }
        },
      ),
      floatingActionButton: FloatingActionButton(
        // Provide an onPressed callback.
        onPressed: () async {
          // Take the Picture in a try / catch block. If anything goes wrong,
          // catch the error.
          try {
            // Ensure that the camera is initialized.
            await _initializeControllerFuture;
​
            // Attempt to take a picture and get the file `image`
            // where it was saved.
            final image = await _controller.takePicture();
​
            if (!context.mounted) return;
​
            // If the picture was taken, display it on a new screen.
            await Navigator.of(context).push(
              MaterialPageRoute<void>(
                builder: (context) => DisplayPictureScreen(
                  // Pass the automatically generated path to
                  // the DisplayPictureScreen widget.
                  imagePath: image.path,
                ),
              ),
            );
          } catch (e) {
            // If an error occurs, log the error to the console.
            print(e);
          }
        },
        child: const Icon(Icons.camera_alt),
      ),
    );
  }
}
​
// A widget that displays the picture taken by the user.
class DisplayPictureScreen extends StatelessWidget {
  final String imagePath;
​
  const DisplayPictureScreen({super.key, required this.imagePath});
​
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const 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.
      body: Image.file(File(imagePath)),
    );
  }
}
```

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-09-29. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/plugins/picture-using-camera.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/cookbook/plugins/picture-using-camera\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/plugins/picture-using-camera.md "Report an issue with this page").

----
url: https://docs.flutter.dev/ui/animations/overview.md
----

\# Animations API overview > An overview of animation concepts. The animation system in Flutter is based on typed \[\`Animation\`]\[] objects. Widgets can either incorporate these animations in their build functions directly by reading their current value and listening to their state changes or they can use the animations as the basis of more elaborate animations that they pass along to other widgets. ## Animation The primary building block of the animation system is the \[\`Animation\`]\[] class. An animation represents a value of a specific type that can change over the lifetime of the animation. Most widgets that perform an animation receive an \`Animation\` object as a parameter, from which they read the current value of the animation and to which they listen for changes to that value. ### \`addListener\` Whenever the animation's value changes, the animation notifies all the listeners added with \[\`addListener\`]\[]. Typically, a \[\`State\`]\[] object that listens to an animation calls \[\`setState\`]\[] on itself in its listener callback to notify the widget system that it needs to rebuild with the new value of the animation. This pattern is so common that Flutter provides widgets that rebuild when animations change value: \[\`AnimatedWidget\`]\[] and \[\`AnimatedBuilder\`]\[]. The first, \`AnimatedWidget\`, is most useful for stateless animated widgets. To use \`AnimatedWidget\`, subclass it and implement the \[\`build\`]\[] function. The second, \`AnimatedBuilder\`, is useful for more complex widgets that include an animation as part of a larger build function. To use \`AnimatedBuilder\`, construct the widget and pass it a \`builder\` function. To rebuild in response to other kinds of \`Listenable\` objects, use \[\`ListenableBuilder\`]\[]. For implicit animations, which manage their own controller, use an \[\`ImplicitlyAnimatedWidget\`]\[] subclass, such as \`AnimatedOpacity\` or \[\`TweenAnimationBuilder\`]\[]. ### \`addStatusListener\` Animations also provide an \[\`AnimationStatus\`]\[], which indicates how the animation will evolve over time. Whenever the animation's status changes, the animation notifies all the listeners added with \[\`addStatusListener\`]\[]. Typically, animations start out in the \`dismissed\` status, which means they're at the beginning of their range. For example, animations that progress from 0.0 to 1.0 will be \`dismissed\` when their value is 0.0. An animation might then run \`forward\` (from 0.0 to 1.0) or perhaps in \`reverse\` (from 1.0 to 0.0). Eventually, if the animation reaches the end of its range (1.0), the animation reaches the \`completed\` status. ## AnimationController To create an animation, first create an \[\`AnimationController\`]\[]. As well as being an animation itself, an \`AnimationController\` lets you control the animation. For example, you can tell the controller to play the animation \[\`forward\`]\[] or \[\`stop\`]\[] the animation. You can also \[\`fling\`]\[] animations, which uses a physical simulation, such as a spring, to drive the animation. Once you've created an animation controller, you can start building other animations based on it. For example, you can create a \[\`ReverseAnimation\`]\[] that mirrors the original animation but runs in the opposite direction (from 1.0 to 0.0). Similarly, you can create a \[\`CurvedAnimation\`]\[] whose value is adjusted by a \[\`Curve\`]\[] (such as the predefined curves in \[\`Curves\`]\[]). ## Tweens To animate beyond the 0.0 to 1.0 interval, you can use a \[\`Tween\`]\[], which interpolates between its \[\`begin\`]\[] and \[\`end\`]\[] values. Many types have specific \`Tween\` subclasses that provide type-specific interpolation. For example, \[\`ColorTween\`]\[] interpolates between colors and \[\`RectTween\`]\[] interpolates between rects. You can define your own interpolations by creating your own subclass of \`Tween\` and overriding its \[\`lerp\`]\[] function. By itself, a tween only defines how to interpolate between two values. To get a concrete value for the current frame of an animation, you also need an animation to determine the current state. There are two ways to combine a tween with an animation to get a concrete value: 1. You can \[\`evaluate\`]\[] the tween at the current value of an animation. This approach is most useful for widgets that are already listening to the animation and hence rebuilding whenever the animation changes value. 2. You can \[\`animate\`]\[] the tween based on the animation. Rather than returning a single value, the animate function returns a new \`Animation\` that incorporates the tween. This approach is most useful when you want to give the newly created animation to another widget, which can then read the current value that incorporates the tween as well as listen for changes to the value. ## Architecture Animations are built from a number of core building blocks. ### Scheduler The \[\`SchedulerBinding\`]\[] is a singleton class that exposes the Flutter scheduling primitives. For this discussion, the key primitive is the frame callbacks. Each time a frame needs to be shown on the screen, Flutter's engine triggers a "begin frame" callback that the scheduler multiplexes to all the listeners registered using \[\`scheduleFrameCallback()\`]\[]. All these callbacks are given the official time stamp of the frame, in the form of a \`Duration\` from some arbitrary epoch. Since all the callbacks have the same time, any animations triggered from these callbacks will appear to be exactly synchronized even if they take a few milliseconds to be executed. ### Tickers The \[\`Ticker\`]\[] class hooks into the scheduler's \[\`scheduleFrameCallback()\`]\[] mechanism to invoke a callback every tick. A \`Ticker\` can be started and stopped. When started, it returns a \`Future\` that will resolve when it is stopped. Each tick, the \`Ticker\` provides the callback with the duration since the first tick after it was started. Because tickers always give their elapsed time relative to the first tick after they were started, tickers are all synchronized. If you start three tickers at different times between two ticks, they will all nonetheless be synchronized with the same starting time, and will subsequently tick in lockstep. Like people at a bus stop, all the tickers wait for a regularly occurring event (the tick) to begin moving (counting time). ### Simulations The \[\`Simulation\`]\[] abstract class maps a relative time value (an elapsed time) to a double value, and has a notion of completion. In principle simulations are stateless, but in practice some simulations (for example, \[\`BouncingScrollSimulation\`]\[] and \[\`ClampingScrollSimulation\`]\[]) change state irreversibly when queried. There are \[various concrete implementations]\[] of the \`Simulation\` class for different effects. ### Animatables The \[\`Animatable\`]\[] abstract class maps a double to a value of a particular type. An \`Animatable\` doesn't track animation progress, though some subclasses have mutable configuration. #### Tweens The \[\`Tween\`]\[] class maps a double value nominally in the range 0.0-1.0 to a typed value (for example, a \`Color\`, or another double). It is an \`Animatable\`. It has a notion of an output type (\`T\`), a \`begin\` value and an \`end\` value of that type, and a way to interpolate (\`lerp\`) between the begin and end values for a given input value (the double nominally in the range 0.0-1.0). A \`Tween\` doesn't track animation progress, but its \`begin\` and \`end\` values are mutable. #### Composing animatables Passing an \`Animatable\` (the parent) to the \`chain()\` method of an \`Animatable\` object creates a new \`Animatable\` subclass that applies the parent's mapping then the child's mapping. ### Curves The \[\`Curve\`]\[] abstract class maps doubles nominally in the range 0.0-1.0 to doubles nominally in the range 0.0-1.0. \`Curve\` classes are stateless and immutable. ### Animations The \[\`Animation\`]\[] abstract class provides a value of a given type, a concept of animation direction and animation status, and a listener interface to register callbacks that get invoked when the value or status change. Some subclasses of \`Animation\` have values that never change (\[\`kAlwaysCompleteAnimation\`]\[], \[\`kAlwaysDismissedAnimation\`]\[], \[\`AlwaysStoppedAnimation\`]\[]); registering callbacks on these has no effect as the callbacks are never called. The \`Animation\` variant is special because it can be used to represent a double nominally in the range 0.0-1.0, which is the input expected by \`Curve\` and \`Tween\` classes, as well as some further subclasses of \`Animation\`. Some \`Animation\` subclasses are stateless, merely forwarding listeners to their parents. Some are very stateful. #### Composable animations Most \`Animation\` subclasses take an explicit "parent" \`Animation\`. They are driven by that parent. The \`CurvedAnimation\` subclass takes an \`Animation\` object (the parent), a forward curve, and an optional reverse curve. It uses the parent's value as input to the curve for the current direction. If the parent reverses before reaching an endpoint, \`CurvedAnimation\` keeps using the same curve to avoid an abrupt jump in value. The \`ReverseAnimation\` subclass takes an \`Animation\` object as its parent and reverses all the values of the animation. It assumes the parent is using a value nominally in the range 0.0-1.0 and returns a value in the range 1.0-0.0. The status and direction of the parent animation are also reversed. \`ReverseAnimation\` is immutable and stateless. The \`ProxyAnimation\` subclass takes an \`Animation\` object as its parent and merely forwards the current state of that parent. However, the parent is mutable. The \`TrainHoppingAnimation\` subclass takes two parents, and switches between them when their values cross. #### Animation controllers The \[\`AnimationController\`]\[] is a stateful \`Animation\` that uses a \`Ticker\` to advance its progress each frame. It can be started and stopped. At each tick, it takes the time elapsed since it was started and passes it to a \`Simulation\` to obtain a value. That is then the value it reports. If the \`Simulation\` reports that at that time it has ended, then the controller stops itself. The animation controller can be given a lower and upper bound to animate between, and a duration. In the common case (using \`forward()\` or \`reverse()\`), the animation controller performs a linear interpolation from the lower bound to the upper bound (or vice versa, for the reverse direction) over the given duration. When using \`repeat()\`, the animation controller uses a linear interpolation between the given bounds over the given duration. It repeats indefinitely unless you specify a \`count\` or stop it. When using \`animateTo()\`, the animation controller interpolates from the current value to the given target, using a linear curve unless you specify a different \`curve\`. If you omit \`duration\`, it scales the controller's duration by the distance to the target as a fraction of the controller's range. When using \`fling()\`, the animation controller uses a spring simulation with the given initial velocity and optional \`SpringDescription\`. When using \`animateWith()\`, the given simulation is used to drive the controller. These methods return a \`TickerFuture\` that completes when the animation finishes successfully. Canceling the animation, including by starting another animation or disposing the controller, leaves this future incomplete. Use its \`orCancel\` property to await a future that throws \`TickerCanceled\` on cancellation. #### Attaching animatables to animations Passing an \`Animation\` (the new parent) to the \`animate()\` method of an \`Animatable\` object creates a new \`Animation\` subclass that acts like the \`Animatable\` but is driven from the given parent. \[\`addListener\`]: https\://api.flutter.dev/flutter/animation/Animation/addListener.html \[\`addStatusListener\`]: https\://api.flutter.dev/flutter/animation/Animation/addStatusListener.html \[\`AlwaysStoppedAnimation\`]: https\://api.flutter.dev/flutter/animation/AlwaysStoppedAnimation-class.html \[\`Animatable\`]: https\://api.flutter.dev/flutter/animation/Animatable-class.html \[\`animate\`]: https\://api.flutter.dev/flutter/animation/Animatable/animate.html \[\`AnimatedBuilder\`]: https\://api.flutter.dev/flutter/widgets/AnimatedBuilder-class.html \[\`AnimationController\`]: https\://api.flutter.dev/flutter/animation/AnimationController-class.html \[\`AnimatedWidget\`]: https\://api.flutter.dev/flutter/widgets/AnimatedWidget-class.html \[\`Animation\`]: https\://api.flutter.dev/flutter/animation/Animation-class.html \[\`AnimationStatus\`]: https\://api.flutter.dev/flutter/animation/AnimationStatus.html \[\`begin\`]: https\://api.flutter.dev/flutter/animation/Tween/begin.html \[\`BouncingScrollSimulation\`]: https\://api.flutter.dev/flutter/widgets/BouncingScrollSimulation-class.html \[\`build\`]: https\://api.flutter.dev/flutter/widgets/AnimatedWidget/build.html \[\`ClampingScrollSimulation\`]: https\://api.flutter.dev/flutter/widgets/ClampingScrollSimulation-class.html \[\`ColorTween\`]: https\://api.flutter.dev/flutter/animation/ColorTween-class.html \[\`Curve\`]: https\://api.flutter.dev/flutter/animation/Curve-class.html \[\`Curves\`]: https\://api.flutter.dev/flutter/animation/Curves-class.html \[\`CurvedAnimation\`]: https\://api.flutter.dev/flutter/animation/CurvedAnimation-class.html \[\`end\`]: https\://api.flutter.dev/flutter/animation/Tween/end.html \[\`evaluate\`]: https\://api.flutter.dev/flutter/animation/Animatable/evaluate.html \[\`fling\`]: https\://api.flutter.dev/flutter/animation/AnimationController/fling.html \[\`forward\`]: https\://api.flutter.dev/flutter/animation/AnimationController/forward.html \[\`ImplicitlyAnimatedWidget\`]: https\://api.flutter.dev/flutter/widgets/ImplicitlyAnimatedWidget-class.html \[\`kAlwaysCompleteAnimation\`]: https\://api.flutter.dev/flutter/animation/kAlwaysCompleteAnimation-constant.html \[\`kAlwaysDismissedAnimation\`]: https\://api.flutter.dev/flutter/animation/kAlwaysDismissedAnimation-constant.html \[\`lerp\`]: https\://api.flutter.dev/flutter/animation/Tween/lerp.html \[\`ListenableBuilder\`]: https\://api.flutter.dev/flutter/widgets/ListenableBuilder-class.html \[\`RectTween\`]: https\://api.flutter.dev/flutter/animation/RectTween-class.html \[\`ReverseAnimation\`]: https\://api.flutter.dev/flutter/animation/ReverseAnimation-class.html \[\`scheduleFrameCallback()\`]: https\://api.flutter.dev/flutter/scheduler/SchedulerBinding/scheduleFrameCallback.html \[\`SchedulerBinding\`]: https\://api.flutter.dev/flutter/scheduler/SchedulerBinding-mixin.html \[\`setState\`]: https\://api.flutter.dev/flutter/widgets/State/setState.html \[\`Simulation\`]: https\://api.flutter.dev/flutter/physics/Simulation-class.html \[\`State\`]: https\://api.flutter.dev/flutter/widgets/State-class.html \[\`stop\`]: https\://api.flutter.dev/flutter/animation/AnimationController/stop.html \[\`Ticker\`]: https\://api.flutter.dev/flutter/scheduler/Ticker-class.html \[\`Tween\`]: https\://api.flutter.dev/flutter/animation/Tween-class.html \[\`TweenAnimationBuilder\`]: https\://api.flutter.dev/flutter/widgets/TweenAnimationBuilder-class.html \[various concrete implementations]: https\://api.flutter.dev/flutter/physics/physics-library.html

----
url: https://docs.flutter.dev/add-to-app.md
----

\# Add Flutter to an existing app > Adding Flutter as a library to an existing Android, iOS, macOS, or web app. ## Add-to-app If you are writing a new application from scratch, it is easy to \[get started]\[] using Flutter. But what if you already have an app that's not written in Flutter, and it's impractical to start from scratch? For those situations, Flutter can be integrated into your existing application piecemeal, as a module. This feature is known as "add-to-app". The module can be imported into your existing app to render part of your app using Flutter, while the rest can be rendered using existing technology. This method can also be used to run shared non-UI logic by taking advantage of Dart's portability and interoperability with other languages. Add-to-app is currently supported on Android, iOS, macOS, and web. Flutter supports two flavors of add-to-app: - \*\*Multi-engine\*\*: supported on Android, iOS, and macOS, allows running one or more instances of Flutter, each rendering a widget embedded into the host application. Each instance is a separate Dart program, running in isolation from other programs. Having multiple Flutter instances allows each instance to maintain independent application and UI state while using minimal memory resources. See more in the \[multiple Flutters]\[] page. - \*\*Multi-view\*\*: supported on the web, allows creating multiple \[FlutterView]\[]s, each rendering a widget embedded into the host application. In this mode there's only one Dart program and all views and widgets can share objects. Add-to-app supports integrating multiple Flutter views of any size, supporting various use-cases. Two of the most common use-cases are: \* \*\*Hybrid navigation stacks\*\*: an app is made of multiple screens, some of which are rendered by Flutter, and others by another framework. The user can navigate from one screen to another freely, no matter which framework is used to render the screen. \* \*\*Partial-screen views\*\*: a screen in the app renders multiple widgets, some of which are rendered by Flutter, and others by another framework. The user can scroll and interact with any widget freely, no matter which framework is used to render the widget. ## Supported features ### Add to Android applications \* Auto-build and import the Flutter module by adding a Flutter SDK hook to your Gradle script. \* Build your Flutter module into a generic \[Android Archive (AAR)]\[] for integration into your own build system and for better Jetifier interoperability with AndroidX. \* \[\`FlutterEngine\`]\[java-engine] API for starting and persisting your Flutter environment independently of attaching a \[\`FlutterActivity\`]\[]/\[\`FlutterFragment\`]\[] etc. \* Android Studio Android/Flutter co-editing and module creation/import wizard. \* Java and Kotlin host apps are supported. \* Flutter modules can use \[Flutter plugins]\[] to interact with the platform. \* Support for Flutter debugging and stateful hot reload by using \`flutter attach\` from IDEs or the command line to connect to an app that contains Flutter. ### Add to iOS applications \* Build your Flutter module into a Swift package for integration into your own build system. \* Auto-build and import the Flutter module using Xcode build phases. \* \[\`FlutterEngine\`]\[ios-engine] API for starting and persisting your Flutter environment independently of attaching a \[\`FlutterViewController\`]\[]. \* Objective-C and Swift host apps supported. \* Flutter modules can use \[Flutter plugins]\[] to interact with the platform. \* Support for Flutter debugging and stateful hot reload by using \`flutter attach\` from IDEs or the command line to connect to an app that contains Flutter. See our \[add-to-app GitHub Samples repository]\[] for sample projects in Android and iOS that import a Flutter module for UI. ### Add to macOS applications \* Build your Flutter module into a Swift package for integration into your own build system. \* Auto-build and import the Flutter module using Xcode build phases. \* \[\`FlutterEngine\`]\[macos-engine] API for starting and persisting your Flutter environment independently of attaching a \[\`FlutterViewController\`]\[macos-flutterviewcontroller]. \* Swift host apps supported. \* Flutter modules can use \[Flutter plugins]\[] to interact with the platform. \* Support for Flutter debugging and stateful hot reload by using \`flutter attach\` from IDEs or the command line to connect to an app that contains Flutter. ### Add to web applications Flutter can be added to any existing HTML DOM-based web app written in any client-side Dart web framework (\[jaspr]\[], \[ngdart]\[], \[over\_react]\[], etc), any client-side JS framework (\[React]\[], \[Angular]\[], \[Vue.js]\[], etc), any server-side rendered framework (\[Django]\[], \[Ruby on Rails]\[], \[Apache Struts]\[], etc), or even no framework (affectionately known as "\[VanillaJS]\[]"). The minimum requirement is only that your existing application and its framework support importing JavaScript libraries, and creating HTML elements for Flutter to render into. To add Flutter to an existing app, build it normally, then follow the \[embedding instructions]\[] for putting Flutter views onto the page. \[jaspr]: https\://pub.dev/packages/jaspr \[ngdart]: https\://pub.dev/packages/ngdart \[over\_react]: https\://pub.dev/packages/over\_react \[React]: https\://react.dev/ \[Angular]: https\://angular.dev/ \[Vue.js]: https\://vuejs.org/ \[Django]: https\://www\.djangoproject.com/ \[Ruby on Rails]: https\://rubyonrails.org/ \[Apache Struts]: https\://struts.apache.org/ \[VanillaJS]: http\://vanilla-js.com/ \[embedding instructions]: /platform-integration/web/embedding-flutter-web#embedded-mode ## Get started To get started, see our project integration guide for Android, web, iOS, and macOS:

[Android](/add-to-app/android/project-setup)

[Web](/platform-integration/web/embedding-flutter-web#embedded-mode)

[iOS](/add-to-app/ios/project-setup)

[macOS](/add-to-app/macos/project-setup)

\## API usage After Flutter is integrated into your project, see our API usage guides at the following links:

[Android](/add-to-app/android/add-flutter-screen)

[web](/platform-integration/web/embedding-flutter-web#manage-flutter-views-from-js)

[iOS](/add-to-app/ios/add-flutter-screen)

[macOS](/add-to-app/macos/add-flutter-screen)

\## Limitations Mobile limitations: \* Multi-view mode is not supported (multi-engine only). \* Packing multiple Flutter libraries into an application isn't supported. \* Plugins that don't support \`FlutterPlugin\` might have unexpected behaviors if they make assumptions that are untenable in add-to-app (such as assuming that a Flutter \`Activity\` is always present). \* On Android, the Flutter module only supports AndroidX applications. Web limitations: \* Multi-engine mode is not supported (multi-view only). \* There's no way to completely "shutdown" the Flutter engine. The app can remove all the \[FlutterView]\[] objects and make sure all data is garbage collected using normal Dart concepts. However, the engine will remain warmed up, even if it's not rendering anything. \[get started]: /learn/pathway \[add-to-app GitHub Samples repository]: https\://github.com/flutter/samples/tree/main/add\_to\_app \[Android Archive (AAR)]: https\://developer.android.com/studio/projects/android-library \[Flutter plugins]: https\://pub.dev/flutter \[\`FlutterActivity\`]: https\://api.flutter.dev/javadoc/io/flutter/embedding/android/FlutterActivity.html \[java-engine]: https\://api.flutter.dev/javadoc/io/flutter/embedding/engine/FlutterEngine.html \[ios-engine]: https\://api.flutter.dev/ios-embedder/interface\_flutter\_engine.html \[FlutterFire]: https\://github.com/firebase/flutterfire/tree/main/packages \[\`FlutterFragment\`]: https\://api.flutter.dev/javadoc/io/flutter/embedding/android/FlutterFragment.html \[\`FlutterPlugin\`]: https\://api.flutter.dev/javadoc/io/flutter/embedding/engine/plugins/FlutterPlugin.html \[\`FlutterViewController\`]: https\://api.flutter.dev/ios-embedder/interface\_flutter\_view\_controller.html \[iOS Framework]: https\://developer.apple.com/library/archive/documentation/MacOSX/Conceptual/BPFrameworks/Concepts/WhatAreFrameworks.html \[maintained by the Flutter team]: https\://github.com/flutter/packages/tree/main/packages \[multiple Flutters]: /add-to-app/multiple-flutters \[FlutterView]: https\://api.flutter.dev/flutter/dart-ui/FlutterView-class.html \[macos-engine]: https\://api.flutter.dev/macos-embedder/interface\_flutter\_engine.html \[macos-flutterviewcontroller]: https\://api.flutter.dev/macos-embedder/interface\_flutter\_view\_controller.html

----
url: https://docs.flutter.dev/data-and-backend/serialization/json
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top JSON and serialization](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

It is hard to think of a mobile app that doesn't need to communicate with a web server or easily store structured data at some point. When making network-connected apps, the chances are that it needs to consume some good old JSON, sooner or later.

This guide looks into ways of using JSON with Flutter. It covers which JSON solution to use in different scenarios, and why.

[Watch on YouTube in a new tab: "dart:convert (Technique of the Week)"](https://www.youtube.com/watch/ngsxzZt5DoY)

## Which JSON serialization method is right for me?

[#](#which-json-serialization-method-is-right-for-me)

This article covers two general strategies for working with JSON:

* Manual serialization
* Automated serialization using code generation

Different projects come with different complexities and use cases. For smaller proof-of-concept projects or quick prototypes, using code generators might be overkill. For apps with several JSON models with more complexity, encoding by hand can quickly become tedious, repetitive, and lend itself to many small errors.

### Use manual serialization for smaller projects

[#](#use-manual-serialization-for-smaller-projects)

Manual JSON decoding refers to using the built-in JSON decoder in `dart:convert`. It involves passing the raw JSON string to the `jsonDecode()` function, and then looking up the values you need in the resulting `Map<String, dynamic>`. It has no external dependencies or particular setup process, and it's good for a quick proof of concept.

Manual decoding does not perform well when your project becomes bigger. Writing decoding logic by hand can become hard to manage and error-prone. If you have a typo when accessing a nonexistent JSON field, your code throws an error during runtime.

If you do not have many JSON models in your project and are looking to test a concept quickly, manual serialization might be the way you want to start. For an example of manual encoding, see [Serializing JSON manually using dart:convert](#manual-encoding).

### Use code generation for medium to large projects

[#](#use-code-generation-for-medium-to-large-projects)

JSON serialization with code generation means having an external library generate the encoding boilerplate for you. After some initial setup, you run a file watcher that generates the code from your model classes. For example, [`json_serializable`](https://pub.dev/packages/json_serializable) and [`built_value`](https://pub.dev/packages/built_value) 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 might produce visual clutter in your project navigator.

You might want to use generated code for JSON serialization when you have a medium or a larger project. To see an example of code generation based JSON encoding, see [Serializing JSON using code generation libraries](#code-generation).

## Is there a GSON/​Jackson/​Moshi equivalent in Flutter?

[#](#is-there-a-gsonwbrjacksonwbrmoshi-equivalent-in-flutter)

The simple answer is no.

Such a library would require using runtime [reflection](https://en.wikipedia.org/wiki/Reflection_\(computer_programming\)), which is disabled in Flutter. Runtime reflection interferes with [tree shaking](https://en.wikipedia.org/wiki/Tree_shaking), which Dart has supported for quite a long time. With tree shaking, you can "shake off" unused code from your release builds. This optimizes the app's size significantly.

Since reflection makes all code implicitly used by default, it makes tree shaking difficult. The tools cannot know what parts are unused at runtime, so the redundant code is hard to strip away. App sizes cannot be easily optimized when using reflection.

Although you cannot use runtime reflection with Flutter, some libraries give you similarly easy-to-use APIs but are based on code generation instead. This approach is covered in more detail in the [code generation libraries](#code-generation) section.

[]()

## Serializing JSON manually using dart:convert

[#](#serializing-json-manually-using-dart-convert)

Basic JSON serialization in Flutter is very simple. Flutter has a built-in `dart:convert` library that includes a straightforward JSON encoder and decoder.

The following sample JSON implements a simple user model.

json

```
{
  "name": "John Smith",
  "email": "john@example.com"
}
```

With `dart:convert`, you can serialize this JSON model in two ways.

### Serializing JSON inline

[#](#serializing-json-inline)

By looking at the [`dart:convert`](https://api.dart.dev/dart-convert) documentation, you'll see that you can decode the JSON by calling the `jsonDecode()` function, with the JSON string as the method argument.

dart

```
final user = jsonDecode(jsonString) as Map<String, dynamic>;
​
print('Howdy, ${user['name']}!');
print('We sent the verification link to ${user['email']}.');
```

Unfortunately, `jsonDecode()` returns a `dynamic`, meaning that you do not know the types of the values until runtime. With this approach, you lose most of the statically typed language features: type safety, autocompletion and most importantly, compile-time exceptions. Your code will become instantly more error-prone.

For example, whenever you access the `name` or `email` fields, you could quickly introduce a typo. A typo that the compiler doesn't know about since the JSON lives in a map structure.

### Serializing JSON inside model classes

[#](#serializing-json-inside-model-classes)

Combat the previously mentioned problems by introducing a plain model class, called `User` in this example. Inside the `User` class, you'll find:

* A `User.fromJson()` constructor, for constructing a new `User` instance from a map structure.
* A `toJson()` method, which converts a `User` instance into a map.

With this approach, the *calling code* can have type safety, autocompletion for the `name` and `email` fields, and compile-time exceptions. If you make typos or treat the fields as `int`s instead of `String`s, the app won't compile, instead of crashing at runtime.

**user.dart**

dart

```
class User {
  final String name;
  final String email;
​
  User(this.name, this.email);
​
  User.fromJson(Map<String, dynamic> json)
    : name = json['name'] as String,
      email = json['email'] as String;
​
  Map<String, dynamic> toJson() => {'name': name, 'email': email};
}
```

The responsibility of the decoding logic is now moved inside the model itself. With this new approach, you can decode a user easily.

dart

```
final userMap = jsonDecode(jsonString) as Map<String, dynamic>;
final user = User.fromJson(userMap);
​
print('Howdy, ${user.name}!');
print('We sent the verification link to ${user.email}.');
```

To encode a user, pass the `User` object to the `jsonEncode()` function. You don't need to call the `toJson()` method, since `jsonEncode()` already does it for you.

dart

```
String json = jsonEncode(user);
```

With this approach, the calling code doesn't have to worry about JSON serialization at all. However, the model class still definitely has to. In a production app, you would want to ensure that the serialization works properly. In practice, the `User.fromJson()` and `User.toJson()` methods both need to have unit tests in place to verify correct behavior.

However, real-world scenarios are not always that simple. Sometimes JSON API responses are more complex, for example since they contain nested JSON objects that must be parsed through their own model class.

It would be nice if there were something that handled the JSON encoding and decoding for you. Luckily, there is!

[]()

## Serializing JSON using code generation libraries

[#](#serializing-json-using-code-generation-libraries)

Although there are other libraries available, this guide uses [`json_serializable`](https://pub.dev/packages/json_serializable), an automated source code generator that generates the JSON serialization boilerplate for you.

Since the serialization code is not handwritten or maintained manually anymore, you minimize the risk of having JSON serialization exceptions at runtime.

### Setting up json\_serializable in a project

[#](#setting-up-json-serializable-in-a-project)

To include `json_serializable` in your project, you need one regular dependency, and two *dev dependencies*. In short, *dev dependencies* are dependencies that are not included in our app source code—they are only used in the development environment.

To add the dependencies, run `flutter pub add`:

```
$ flutter pub add json_annotation dev:build_runner dev:json_serializable
```

Run `flutter pub get` inside your project root folder (or click **Packages get** in your editor) to make these new dependencies available in your project.

### Creating model classes the json\_serializable way

[#](#creating-model-classes-the-json-serializable-way)

The following shows how to convert the `User` class to a `json_serializable` class. For the sake of simplicity, this code uses the simplified JSON model from the previous samples.

**user.dart**

dart

```
import 'package:json_annotation/json_annotation.dart';
​
/// This allows the `User` class to access private members in
/// the generated file. The value for this is *.g.dart, where
/// the star denotes the source file name.
part 'user.g.dart';
​
/// An annotation for the code generator to know that this class needs the
/// JSON serialization logic to be generated.
@JsonSerializable()
class User {
  User(this.name, this.email);
​
  String name;
  String email;
​
  /// A necessary factory constructor for creating a new User instance
  /// from a map. Pass the map to the generated `_$UserFromJson()` constructor.
  /// The constructor is named after the source class, in this case, User.
  factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
​
  /// `toJson` is the convention for a class to declare support for serialization
  /// to JSON. The implementation simply calls the private, generated
  /// helper method `_$UserToJson`.
  Map<String, dynamic> toJson() => _$UserToJson(this);
}
```

With this setup, the source code generator generates code for encoding and decoding the `name` and `email` fields from JSON.

If needed, it is also easy to customize the naming strategy. For example, if the API returns objects with *snake\_case*, and you want to use *lowerCamelCase* in your models, you can use the `@JsonKey` annotation with a name parameter:

dart

```
/// Tell json_serializable that "registration_date_millis" should be
/// mapped to this property.
@JsonKey(name: 'registration_date_millis')
final int registrationDateMillis;
```

It's best if both server and client follow the same naming strategy. `@JsonSerializable()` provides `fieldRename` enum for totally converting dart fields into JSON keys.

Modifying `@JsonSerializable(fieldRename: FieldRename.snake)` is equivalent to adding `@JsonKey(name: '<snake_case>')` to each field.

Sometimes server data is uncertain, so it is necessary to verify and protect data on client. Other commonly used `@JsonKey` annotations include:

dart

```
/// Tell json_serializable to use "defaultValue" if the JSON doesn't
/// contain this key or if the value is `null`.
@JsonKey(defaultValue: false)
final bool isAdult;
​
/// When `true` tell json_serializable that JSON must contain the key,
/// If the key doesn't exist, an exception is thrown.
@JsonKey(required: true)
final String id;
​
/// When `true` tell json_serializable that generated code should
/// ignore this field completely.
@JsonKey(ignore: true)
final String verificationCode;
```

### Running the code generation utility

[#](#running-the-code-generation-utility)

When creating `json_serializable` classes the first time, you'll get errors similar to the following:

```
Target of URI hasn't been generated: 'user.g.dart'.
```

These errors are entirely normal and are simply because the generated code for the model class does not exist yet. To resolve this, run the code generator that generates the serialization boilerplate.

There are two ways of running the code generator.

#### One-time code generation

[#](#one-time-code-generation)

By running `dart run build_runner build --delete-conflicting-outputs` in the project root, you generate JSON serialization code for your models whenever they are needed. This triggers a one-time build that goes through the source files, picks the relevant ones, and generates the necessary serialization code for them.

While this is convenient, it would be nice if you did not have to run the build manually every time you make changes in your model classes.

#### Generating code continuously

[#](#generating-code-continuously)

A *watcher* makes our source code generation process more convenient. It watches changes in our project files and automatically builds the necessary files when needed. Start the watcher by running `dart run build_runner watch --delete-conflicting-outputs` in the project root.

It is safe to start the watcher once and leave it running in the background.

### Consuming json\_serializable models

[#](#consuming-json-serializable-models)

To decode a JSON string the `json_serializable` way, you do not have actually to make any changes to our previous code.

dart

```
final userMap = jsonDecode(jsonString) as Map<String, dynamic>;
final user = User.fromJson(userMap);
```

The same goes for encoding. The calling API is the same as before.

dart

```
String json = jsonEncode(user);
```

With `json_serializable`, you can forget any manual JSON serialization in the `User` class. The source code generator creates a file called `user.g.dart`, that has all the necessary serialization logic. You no longer have to write automated tests to ensure that the serialization works—it's now *the library's responsibility* to make sure the serialization works appropriately.

## Generating code for nested classes

[#](#generating-code-for-nested-classes)

You might have code that has nested classes within a class. If that is the case, and you have tried to pass the class in JSON format as an argument to a service (such as Firebase, for example), you might have experienced an `Invalid argument` error.

Consider the following `Address` class:

dart

```
import 'package:json_annotation/json_annotation.dart';
part 'address.g.dart';
​
@JsonSerializable()
class Address {
  String street;
  String city;
​
  Address(this.street, this.city);
​
  factory Address.fromJson(Map<String, dynamic> json) =>
      _$AddressFromJson(json);
  Map<String, dynamic> toJson() => _$AddressToJson(this);
}
```

The `Address` class is nested inside the `User` class:

dart

```
import 'package:json_annotation/json_annotation.dart';
​
import 'address.dart';
​
part 'user.g.dart';
​
@JsonSerializable()
class User {
  User(this.name, this.address);
​
  String name;
  Address address;
​
  factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
  Map<String, dynamic> toJson() => _$UserToJson(this);
}
```

Running `dart run build_runner build --delete-conflicting-outputs` in the terminal creates the `*.g.dart` file, but the private `_$UserToJson()` function looks something like the following:

dart

```
Map<String, dynamic> _$UserToJson(User instance) => <String, dynamic>{
  'name': instance.name,
  'address': instance.address,
};
```

All looks fine now, but if you do a print() on the user object:

dart

```
Address address = Address('My st.', 'New York');
User user = User('John', address);
print(user.toJson());
```

The result is:

json

```
{name: John, address: Instance of 'address'}
```

When what you probably want is output like the following:

json

```
{name: John, address: {street: My st., city: New York}}
```

To make this work, pass `explicitToJson: true` in the `@JsonSerializable()` annotation over the class declaration. The `User` class now looks as follows:

dart

```
import 'package:json_annotation/json_annotation.dart';
​
import 'address.dart';
​
part 'user.g.dart';
​
@JsonSerializable(explicitToJson: true)
class User {
  User(this.name, this.address);
​
  String name;
  Address address;
​
  factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
  Map<String, dynamic> toJson() => _$UserToJson(this);
}
```

For more information, see [`explicitToJson`](https://pub.dev/documentation/json_annotation/latest/json_annotation/JsonSerializable/explicitToJson.html) in the [`JsonSerializable`](https://pub.dev/documentation/json_annotation/latest/json_annotation/JsonSerializable-class.html) class for the [`json_annotation`](https://pub.dev/packages/json_annotation) package.

## Further references

[#](#further-references)

For more information, see the following resources:

* The [`dart:convert`](https://api.dart.dev/dart-convert) and [`JsonCodec`](https://api.dart.dev/dart-convert/JsonCodec-class.html) documentation
* The [`json_serializable`](https://pub.dev/packages/json_serializable) package on pub.dev
* The [`json_serializable` examples](https://github.com/google/json_serializable.dart/blob/master/example/lib/example.dart) on GitHub
* The [Dive into Dart's patterns and records](https://codelabs.developers.google.com/codelabs/dart-patterns-records) codelab
* This ultimate guide about [how to parse JSON in Dart/Flutter](https://codewithandrea.com/articles/parse-json-dart/)

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-07-31. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/data-and-backend/serialization/json.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/data-and-backend/serialization/json\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/data-and-backend/serialization/json.md "Report an issue with this page").

----
url: https://docs.flutter.dev/tools/editors.md
----

\# Flutter editor support > Editor support for Dart and Flutter. You can build apps with Flutter using any text editor or integrated development environment (IDE) combined with Flutter's command-line tools. The Flutter team recommends using an editor that supports a Flutter extension or plugin, like VS Code and Android Studio. These plugins provide code completion, syntax highlighting, widget editing assists, debug support, and much more. ## Local editors The Flutter team supports plugins for VS Code, Android Studio, and IntelliJ. The plugins provide extensive development and debugging support as well as deep integrations with the \[Dart analyzer]\[] and \[Dart and Flutter DevTools]\[].

[Visual Studio Code](/tools/vs-code)

[Develop and debug Flutter apps in a streamlined and customizable code editor.](/tools/vs-code)

[Android Studio and IntelliJ](/tools/android-studio)

[Develop and debug Flutter apps in an IDE with rich language support and integrated tooling.](/tools/android-studio)

\[Dart analyzer]: https\://dart.dev/tools/analysis \[Dart and Flutter DevTools]: /tools/devtools ## Online editors You can quickly try Flutter online without any local setup with one of the following editors.

[DartPad](https://dartpad.dev)

[Quickly build and run simple single-file Flutter apps on the web.](https://dartpad.dev)

\## Other editors You can develop Dart and Flutter apps using any other text editor and terminal. Depending on the editor, you can integrate the Dart SDK's support for the \[Language Server Protocol]\[lsp] and the \[Debug Adapter Protocol]\[dap] to enable rich code editing and debugging features for both Dart and Flutter. \[lsp]: https\://github.com/dart-lang/sdk/tree/main/pkg/analysis\_server/tool/lsp\_spec/README.md \[dap]: https\://github.com/dart-lang/sdk/blob/main/third\_party/pkg/dap/tool/README.md

----
url: https://docs.flutter.dev/ui/accessibility/assistive-technologies
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Accessibility technologies](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

## Summary

[#](#summary)

Assistive technologies are essential for making digital content accessible to individuals with disabilities. This document provides an overview of two key categories of assistive technologies relevant to Flutter development: screen readers for users with visual impairments and mobility support tools for those with motor limitations. By understanding and testing with these technologies, you can ensure your Flutter application provides a more inclusive and user-friendly experience for everyone.

## Screen readers

[#](#screen-readers)

For mobile, screen readers ([TalkBack](https://support.google.com/accessibility/android/answer/6283677?hl=en), [VoiceOver](https://www.apple.com/lae/accessibility/iphone/vision/)) enable visually impaired users to get spoken feedback about the contents of the screen and interact with the UI by using gestures on mobile and keyboard shortcuts on desktop. Turn on VoiceOver or TalkBack on your mobile device and navigate around your app.

**To turn on the screen reader on your device, complete the following steps:**

* [Android](#6-tab-panel)
* [iOS or iPadOS](#7-tab-panel)
* [Browsers](#8-tab-panel)
* [Desktop](#9-tab-panel)

1. On your device, open **Settings**.
2. Select **Accessibility** and then **TalkBack**.
3. Turn 'Use TalkBack' on or off.
4. Select Ok.

To learn how to find and customize Android's accessibility features, view the following video.

[Watch on YouTube in a new tab: "Customize Pixel and Android accessibility features"](https://www.youtube.com/watch/FQyj_XTl01w)

1. On your device, open **Settings > Accessibility > VoiceOver**
2. Turn the VoiceOver setting on or off

To learn how to find and customize iOS accessibility features, view the following video.

[Watch on YouTube in a new tab: "How to navigate your iPhone or iPad with VoiceOver"](https://www.youtube.com/watch/ROIe49kXOc8)

For web, the following screen readers are currently supported:

Mobile browsers:

* iOS - VoiceOver
* Android - TalkBack

Desktop browsers:

* macOS - VoiceOver
* Windows - JAWs & NVDA

Screen readers users on web must toggle the "Enable accessibility" button to build the semantics tree. Users can skip this step if you programmatically auto-enable accessibility for your app using this API:

dart

```
import 'package:material_ui/material_ui.dart';
import 'package:flutter/semantics.dart';
​
void main() {
  runApp(const MyApp());
  SemanticsBinding.instance.ensureSemantics();
}
```

Windows comes with a screen reader called Narrator but some developers recommend using the more popular NVDA screen reader. To learn about using NVDA to test Windows apps, check out [Screen Readers 101 For Front-End Developers (Windows)](https://evinced.com/blog/screen-readers-101-for-front-end-developers-windows).

On a Mac, you can use the desktop version of VoiceOver, which is included in macOS.

[Watch on YouTube in a new tab: "Screen reader basics: VoiceOver"](https://www.youtube.com/watch/5R-6WvAihms)

On Linux, a popular screen reader is called Orca. It comes pre-installed with some distributions and is available on package repositories such as `apt`. To learn about using Orca, check out [Getting started with Orca screen reader on Gnome desktop](https://www.a11yproject.com/posts/getting-started-with-orca).



Check out the following [video demo](https://www.youtube.com/watch?v=A6Sx0lBP8PI) to see how to use VoiceOver with the now-archived [Flutter Gallery](https://flutter-gallery-archive.web.app) web app.

Flutter's standard widgets generate an accessibility tree automatically. However, if your app needs something different, it can be customized using the [`Semantics` widget](https://api.flutter.dev/flutter/widgets/Semantics-class.html).

When there is text in your app that should be voiced with a specific voice, inform the screen reader which voice to use by calling [`TextSpan.locale`](https://api.flutter.dev/flutter/painting/TextSpan/locale.html). `MaterialApp.locale` and `Localizations.override` will affect screen reader voices starting from flutter 3.38 release. Usually, the screen reader uses the system voice except where you explicitly set it with `TextSpan.locale`.

## Mobility support

[#](#mobility-support)

For users with limited dexterity or hand strength, mobility support features can be helpful. Both Android and iOS offer a range of tools designed to make navigation and control easier. These features allow users to operate their devices through external switches, voice commands, or simplified on-screen menus.

Android provides Switch Access, Voice Access and Accessibility Menu, while iOS offers Switch Control, Voice Control, and AssistiveTouch. Understanding these tools helps in creating apps that are usable by people with diverse physical abilities.

| OS      | Features               | Functions                                                                                        |
| ------- | ---------------------- | ------------------------------------------------------------------------------------------------ |
| Android | **Switch Access**      | As an alternate input method, you can use Switch Access and Camera Switches                      |
| Android | **Voice Access**       | Control your device with your voice                                                              |
| Android | **Accessibility Menu** | A floating, on-screen menu that provides simplified buttons to control essential phone functions |
| iOS     | **Switch Control**     | Use switches as an alternate input methods                                                       |
| iOS     | **Voice Control**      | Control your device with your voice                                                              |
| iOS     | **AssistiveTouch**     | Use AssistiveTouch to replace multi-finger gestures or hardware button actions                   |

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-09-29. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/ui/accessibility/assistive-technologies.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/ui/accessibility/assistive-technologies\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/ui/accessibility/assistive-technologies.md "Report an issue with this page").

----
url: https://docs.flutter.dev/ui/design/graphics/fragment-shaders.md
----

\# Writing and using fragment shaders > How to author and use fragment shaders to create custom visual effects in your Flutter app. :::note Both the Skia and \[Impeller]\[] backends support writing a custom shader. Except where noted, the same instructions apply to both. ::: \[Impeller]: /perf/impeller Custom shaders can be used to provide rich graphical effects beyond those provided by the Flutter SDK. A shader is a program authored in a small, Dart-like language, known as GLSL, and executed on the user's GPU. Custom shaders are added to a Flutter project by listing them in the \`pubspec.yaml\` file, and obtained using the \[\`FragmentProgram\`]\[] API. \[\`FragmentProgram\`]: https\://api.flutter.dev/flutter/dart-ui/FragmentProgram-class.html ## Adding shaders to an application Shaders, in the form of GLSL files with the \`.frag\` extension, must be declared in the \`shaders\` section of your project's \`pubspec.yaml\` file. The Flutter command-line tool compiles the shader to its appropriate backend format, and generates its necessary runtime metadata. The compiled shader is then included in the application just like an asset. \`\`\`yaml flutter: shaders: - shaders/myshader.frag \`\`\` When running in debug mode, changes to a shader program trigger recompilation and update the shader during hot reload or hot restart. Shaders from packages are added to a project with \`packages/$pkgname\` prefixed to the shader program's name (where \`$pkgname\` is the name of the package). ### Loading shaders at runtime To load a shader into a \`FragmentProgram\` object at runtime, use the \[\`FragmentProgram.fromAsset\`]\[] constructor. The asset's name is the same as the path to the shader given in the \`pubspec.yaml\` file. \[\`FragmentProgram.fromAsset\`]: https\://api.flutter.dev/flutter/dart-ui/FragmentProgram/fromAsset.html \`\`\`dart void loadMyShader() async { var program = await FragmentProgram.fromAsset('shaders/myshader.frag'); } \`\`\` The \`FragmentProgram\` object can be used to create one or more \[\`FragmentShader\`]\[] instances. A \`FragmentShader\` object represents a fragment program along with a particular set of \_uniforms\_ (configuration parameters). The available uniforms depends on how the shader was defined. \[\`FragmentShader\`]: https\://api.flutter.dev/flutter/dart-ui/FragmentShader-class.html \`\`\`dart void updateShader(Canvas canvas, Rect rect, FragmentProgram program) { var shader = program.fragmentShader(); shader.setFloat(0, 42.0); canvas.drawRect(rect, Paint()..shader = shader); } \`\`\` ### Canvas API Fragment shaders can be used with most Canvas APIs by setting \[\`Paint.shader\`]\[]. For example, when using \[\`Canvas.drawRect\`]\[] the shader is evaluated for all fragments within the rectangle. For an API like \[\`Canvas.drawPath\`]\[] with a stroked path, the shader is evaluated for all fragments within the stroked line. Some APIs, such as \[\`Canvas.drawImage\`]\[], ignore the value of the shader. \[\`Canvas.drawImage\`]: https\://api.flutter.dev/flutter/dart-ui/Canvas/drawImage.html \[\`Canvas.drawRect\`]: https\://api.flutter.dev/flutter/dart-ui/Canvas/drawRect.html \[\`Canvas.drawPath\`]: https\://api.flutter.dev/flutter/dart-ui/Canvas/drawPath.html \[\`Paint.shader\`]: https\://api.flutter.dev/flutter/dart-ui/Paint/shader.html \`\`\`dart void paint(Canvas canvas, Size size, FragmentShader shader) { // Draws a rectangle with the shader used as a color source. canvas.drawRect( Rect.fromLTWH(0, 0, size.width, size.height), Paint()..shader = shader, ); // Draws a stroked rectangle with the shader only applied to the fragments // that lie within the stroke. canvas.drawRect( Rect.fromLTWH(0, 0, size.width, size.height), Paint() ..style = PaintingStyle.stroke ..shader = shader, ) } \`\`\` ### ImageFilter API Fragment shaders can also be used with the \[\`ImageFilter\`]\[] API. This allows using custom fragment shaders with the \[\`ImageFiltered\`]\[] class or the \[\`BackdropFilter\`]\[] class to apply shaders to already rendered content. \[\`ImageFilter\`]\[] provides a constructor, \[\`ImageFilter.shader\`]\[], for creating an \[\`ImageFilter\`]\[] with a custom fragment shader. :::warning The \`ImageFilter\` API for custom shaders is only supported by the \[Impeller]\[] backend. Using it on other backends will throw an error. ::: \`\`\`dart Widget build(BuildContext context, FragmentShader shader) { return ClipRect( child: SizedBox( width: 300, height: 300, child: BackdropFilter( filter: ImageFilter.shader(shader), child: Container( color: Colors.transparent, ), ), ), ); } \`\`\` When using \[\`ImageFilter\`]\[] with \[\`BackdropFilter\`]\[], a \[\`ClipRect\`]\[] can be used to limit the area that is affected by the \[\`ImageFilter\`]\[]. Without a \[\`ClipRect\`]\[] the \[\`BackdropFilter\`]\[] will be applied to the whole screen. \`ImageFilter\` fragment shaders receive some uniforms automatically from the engine. The \`sampler2D\` value at index 0 is set to the filter input image, and the \`float\` values at indices 0 and 1 are set to the image's width and height. Your shader must specify this constructor to accept these values (for example, a \`sampler2D\` and a \`vec2\`), but you should not set them from your Dart code. When targeting OpenGLES the y-coordinates of the texture will be flipped so the fragment shader should un-flip the UVs when sampling from textures provided by the engine. \`\`\`glsl #version 460 core #include out vec4 fragColor; // These uniforms are automatically set by the engine. uniform vec2 u\_size; uniform sampler2D u\_texture; void main() { vec2 uv = FlutterFragCoord().xy / u\_size; #ifdef IMPELLER\_TARGET\_OPENGLES // When sampling from u\_texture on OpenGLES the y-coordinates will be flipped. uv.y = 1.0 - uv.y; #endif vec4 color = texture(u\_texture, uv); float gray = dot(color.rgb, vec3(0.299, 0.587, 0.114)); fragColor = vec4(vec3(gray), color.a); } \`\`\` \[\`ImageFilter\`]: https\://api.flutter.dev/flutter/dart-ui/ImageFilter-class.html \[\`ImageFiltered\`]: https\://api.flutter.dev/flutter/widgets/ImageFiltered-class.html \[\`BackdropFilter\`]: https\://api.flutter.dev/flutter/widgets/BackdropFilter-class.html \[\`ImageFilter.shader\`]: https\://api.flutter.dev/flutter/dart-ui/ImageFilter/ImageFilter.shader.html \[\`ClipRect\`]: https\://api.flutter.dev/flutter/widgets/ClipRect-class.html ## Authoring shaders Fragment shaders are authored as GLSL source files. By convention, these files have the \`.frag\` extension. (Flutter doesn't support vertex shaders, which would have the \`.vert\` extension.) Any GLSL version from 460 down to 100 is supported, though some available features are restricted. The rest of the examples in this document use version \`460 core\`. Shaders are subject to the following limitations when used with Flutter: \* UBOs and SSBOs aren't supported \* \`sampler2D\` is the only supported sampler type \* Only the two-argument version of \`texture\` (sampler and uv) is supported \* No additional varying inputs can be declared \* All precision hints are ignored when targeting Skia \* Unsigned integers and booleans aren't supported ### Uniforms A fragment program can be configured by defining \`uniform\` values in the GLSL shader source and then setting these values in Dart for each fragment shader instance. Floating point uniforms with the GLSL types \`float\`, \`vec2\`, \`vec3\`, and \`vec4\` are set using the \[\`FragmentShader.setFloat\`]\[] or \[\`FragmentShader.getUniformFloat\`]\[] method. GLSL sampler values, which use the \`sampler2D\` type, are set using the \[\`FragmentShader.setImageSampler\`]\[] or \[\`FragmentShader.getImageSampler\`]\[] method. The correct index for each \`uniform\` value is determined by the order that the uniform values are defined in the fragment program. For data types composed of multiple floats, such as a \`vec4\`, you must call \[\`FragmentShader.setFloat\`]\[] or \[\`UniformFloatSlot.set\`]\[] once for each value. \[\`FragmentShader.setFloat\`]: https\://api.flutter.dev/flutter/dart-ui/FragmentShader/setFloat.html \[\`UniformFloatSlot.set\`]: https\://api.flutter.dev/flutter/dart-ui/UniformFloatSlot/set.html \[\`FragmentShader.getUniformFloat\`]: https\://api.flutter.dev/flutter/dart-ui/FragmentShader/getUniformFloat.html \[\`FragmentShader.setImageSampler\`]: https\://api.flutter.dev/flutter/dart-ui/FragmentShader/setImageSampler.html \[\`FragmentShader.getImageSampler\`]: https\://api.flutter.dev/flutter/dart-ui/FragmentShader/getImageSampler.html For example, given the following uniforms declarations in a GLSL fragment program: \`\`\`glsl uniform float uScale; uniform sampler2D uTexture; uniform vec2 uMagnitude; uniform vec4 uColor; \`\`\` The corresponding Dart code to initialize these \`uniform\` values is as follows: \`\`\`dart class Foobar { late final UniformFloatSlot \_scale; late final List \_magnitude; late final List \_color; late final ImageSamplerSlot \_texture; void setUp(FragmentShader shader) { \_scale = shader.getUniformFloat('uScale'); \_magnitude = List.generate(2, (int index) { return shader.getUniformFloat('uMagnitude', index); }); \_color = List.generate(4, (int index) { return shader.getUniformFloat('uColor', index); }); \_texture = shader.getImageSampler('uTexture'); } void update(Color color, Image image) { \_scale.set(23); \_magnitude\[0].set(114); \_magnitude\[1].set(83); \_color\[0].set(color.r \* color.a); \_color\[1].set(color.g \* color.a); \_color\[2].set(color.b \* color.a); \_color\[3].set(color.a); \_texture.set(image); } } \`\`\` When using \[\`FragmentShader.setFloat\`]\[] note that the indices do not count the \`sampler2D\` uniform. This uniform is set separately with \[\`FragmentShader.setImageSampler\`]\[], with the index starting over at 0. Any float uniforms that are left uninitialized will default to \`0.0\`. The reflection data generated by the Flutter's shader compiler can be audited with the following commands in order to see things like uniform offsets. \`\`\`shell cd $FLUTTER # Generate the .sl file. \`find bin/ -name impellerc\` \ --runtime-stage-metal \ --iplr \ --input=path/to/myshader.frag \ --sl=foo.sl \ --spirv=foo.spirv \ --include=engine/src/flutter/impeller/compiler/shader\_lib/ \ --input-type=frag # Convert the .sl file to .json flatc \ --json \ ./engine/src/flutter/impeller/runtime\_stage/runtime\_stage.fbs \ -- ./foo.sl # View results cat foo.json \`\`\` #### Current position The shader has access to a \`varying\` value that contains the local coordinates for the particular fragment being evaluated. Use this feature to compute effects that depend on the current position, which can be accessed by importing the \`flutter/runtime\_effect.glsl\` library and calling the \`FlutterFragCoord\` function. For example: \`\`\`glsl #include void main() { vec2 currentPos = FlutterFragCoord().xy; } \`\`\` The value returned from \`FlutterFragCoord\` is distinct from \`gl\_FragCoord\`. \`gl\_FragCoord\` provides the screen space coordinates and should generally be avoided to ensure that shaders are consistent across backends. When targeting a Skia backend, the calls to \`gl\_FragCoord\` are rewritten to access local coordinates but this rewriting isn't possible with Impeller. #### Colors There isn't a built-in data type for colors. Instead they are commonly represented as a \`vec4\` with each component corresponding to one of the RGBA color channels. The single output \`fragColor\` expects that the color value is normalized to be in the range of \`0.0\` to \`1.0\` and that it has premultiplied alpha. This is different than typical Flutter colors which use a \`0-255\` value encoding and have unpremultipled alpha. #### Samplers A sampler provides access to a \`dart:ui\` \`Image\` object. This image can be acquired either from a decoded image or from part of the application using \[\`Scene.toImageSync\`]\[] or \[\`Picture.toImageSync\`]\[]. \[\`Picture.toImageSync\`]: https\://api.flutter.dev/flutter/dart-ui/Picture/toImageSync.html \[\`Scene.toImageSync\`]: https\://api.flutter.dev/flutter/dart-ui/Scene/toImageSync.html ##### Sampler usage in GLSL example \`\`\`glsl #include uniform vec2 uSize; uniform sampler2D uTexture; out vec4 fragColor; void main() { vec2 uv = FlutterFragCoord().xy / uSize; fragColor = texture(uTexture, uv); } \`\`\` By default, the image uses \[\`TileMode.clamp\`]\[] to determine how values outside of the range of \`\[0, 1]\` behave. Customization of the tile mode is not supported and needs to be emulated in the shader. \[\`TileMode.clamp\`]: https\://api.flutter.dev/flutter/dart-ui/TileMode.html ##### \`toImageSync\` example \`\`\`dart class SDFPainter { SDFPainter(this.sdfShader, this.renderShader); FragmentShader sdfShader; FragmentShader renderShader; Image? \_sdf; bool isDirty = false; double radius = 0.5; void paint(Canvas canvas, Size size) { if (\_sdf == null || isDirty) { final recorder = PictureRecorder(); final subCanvas = Canvas(recorder); final paint = Paint()..shader = sdfShader; sdfShader.setFloat(0, size.width); sdfShader.setFloat(1, size.height); sdfShader.setFloat(2, radius); subCanvas.drawRect(Rect.fromLTWH(0, 0, size.width, size.height), paint); final picture = recorder.endRecording(); \_sdf = picture.toImageSync(size.width.toInt(), size.height.toInt()); isDirty = false; } renderShader.setFloat(0, size.width); renderShader.setFloat(1, size.height); renderShader.setImageSampler(0, \_sdf!); canvas.drawRect( Rect.fromLTWH(0, 0, size.width, size.height), Paint()..shader = renderShader, ); } } \`\`\` ## Performance considerations When targeting the Skia backend, loading the shader might be expensive since it must be compiled to the appropriate platform-specific shader at runtime. If you intend to use one or more shaders during an animation, consider precaching the fragment program objects before starting the animation. You can reuse a \`FragmentShader\` object across frames; this is more efficient than creating a new \`FragmentShader\` for each frame. For a more detailed guide on writing performant shaders, check out \[Writing efficient shaders]\[] on GitHub. \[Writing efficient shaders]: https\://github.com/flutter/flutter/blob/main/docs/engine/impeller/docs/shader\_optimization.md ## Other resources For more information, here are a few resources. \* \[The Book of Shaders]\[] by Patricio Gonzalez Vivo and Jen Lowe \* \[Shader toy]\[], a collaborative shader playground \* \[\`simple\_shader\`]\[], a simple Flutter fragment shaders sample project \* \[\`flutter\_shaders\`]\[], a package that simplifies using fragment shaders in Flutter \[Shader toy]: https\://www\.shadertoy.com/ \[The Book of Shaders]: https\://thebookofshaders.com/ \[\`simple\_shader\`]: https\://github.com/flutter/samples/tree/main/simple\_shader \[\`flutter\_shaders\`]: https\://pub.dev/packages/flutter\_shaders

----
url: https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.28.4
----

The 2.28.4 release of the Dart and Flutter DevTools includes the following changes among other general improvements. To learn more about DevTools, check out the [DevTools overview](https://docs.flutter.dev/tools/devtools).

This was a cherry-pick release on top of DevTools 2.28.3. To learn about the improvements included in DevTools 2.28.3, please read the [release notes](/tools/devtools/release-notes/release-notes-2.28.3).

## Inspector updates

[#](#inspector-updates)

* Added link to package directory documentation, from the inspect settings dialog - [#6825](https://github.com/flutter/devtools/pull/6825)

* Fixed a bug where widgets owned by the Flutter framework were showing up in the widget tree view - [#6857](https://github.com/flutter/devtools/pull/6857)

## Full commit history

[#](#full-commit-history)

To find a complete list of changes in this release, check out the [DevTools git log](https://github.com/flutter/devtools/tree/v2.28.4).

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/release-notes/release-notes-2.28.4.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.28.4\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/release-notes/release-notes-2.28.4.md "Report an issue with this page").

----
url: https://docs.flutter.dev/app-architecture/design-patterns/sql
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Persistent storage architecture: SQL](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

Most Flutter applications, no matter how small or big they are, might require storing data on the user’s device at some point. For example, API keys, user preferences or data that should be available offline.

In this recipe, you will learn how to integrate persistent storage for complex data using SQL in a Flutter application following the Flutter Architecture design pattern.

To learn how to store simpler key-value data, take a look at the Cookbook recipe: [Persistent storage architecture: Key-value data](/app-architecture/design-patterns/key-value-data).

To read this recipe, you should be familiar with SQL and SQLite. If you need help, you can read the [Persist data with SQLite](/cookbook/persistence/sqlite) recipe before reading this one.

This example uses [`sqflite`](https://pub.dev/packages/sqflite) with the [`sqflite_common_ffi`](https://pub.dev/packages/sqflite_common_ffi) plugin, which combined support for mobile and desktop. Support for web is provided in the experimental plugin [`sqflite_common_ffi_web`](https://pub.dev/packages/sqflite_common_ffi_web) but it's not included in this example.

## Example application: ToDo list application

[#](#example-application-todo-list-application)

The example application consists of a single screen with an app bar at the top, a list of items, and a text field input at the bottom.

The body of the application contains the `TodoListScreen`. This screen contains a `ListView` of `ListTile` items, each one representing a ToDo item. At the bottom, a `TextField` allows users to create new ToDo items by writing the task description and then tapping on the “Add” `FilledButton`.

Users can tap on the delete `IconButton` to delete the ToDo item.

The list of ToDo items is stored locally using a database service, and restored when the user starts the application.

## Storing complex data with SQL

[#](#storing-complex-data-with-sql)

This functionality follows the recommended [Flutter Architecture design](/app-architecture), containing a UI layer and a data layer. Additionally, in the domain layer you will find the data model used.

* UI layer with `TodoListScreen` and `TodoListViewModel`
* Domain layer with `Todo` data class
* Data layer with `TodoRepository` and `DatabaseService`

### ToDo list presentation layer

[#](#todo-list-presentation-layer)

The `TodoListScreen` is a Widget that contains the UI in charge of displaying and creating the ToDo items. It follows the [MVVM pattern](/app-architecture/guide#mvvm) and is accompanied by the `TodoListViewModel`, which contains the list of ToDo items and three commands to load, add, and delete ToDo items.

This screen is divided into two parts, one containing the list of ToDo items, implemented using a `ListView`, and the other is a `TextField` and a `Button`, used for creating new ToDo items.

The `ListView` is wrapped by a `ListenableBuilder`, which listens to changes in the `TodoListViewModel`, and shows a `ListTile` for each ToDo item.

dart

```
ListenableBuilder(
  listenable: widget.viewModel,
  builder: (context, child) {
    return ListView.builder(
      itemCount: widget.viewModel.todos.length,
      itemBuilder: (context, index) {
        final todo = widget.viewModel.todos[index];
        return ListTile(
          title: Text(todo.task),
          trailing: IconButton(
            icon: const Icon(Icons.delete),
            onPressed: () => widget.viewModel.delete.execute(todo.id),
          ),
        );
      },
    );
  },
)
```

The list of ToDo items is defined in the `TodoListViewModel`, and loaded by the `load` command. This method calls the `TodoRepository` and fetches the list of ToDo items.

dart

```
List<Todo> _todos = [];
​
List<Todo> get todos => _todos;
​
Future<Result<void>> _load() async {
  try {
    final result = await _todoRepository.fetchTodos();
    switch (result) {
      case Ok<List<Todo>>():
        _todos = result.value;
        return Result.ok(null);
      case Error():
        return Result.error(result.error);
    }
  } on Exception catch (e) {
    return Result.error(e);
  } finally {
    notifyListeners();
  }
}
```

Pressing the `FilledButton`, executes the `add` command and passes in the text controller value.

dart

```
FilledButton.icon(
  onPressed: () =>
      widget.viewModel.add.execute(_controller.text),
  label: const Text('Add'),
  icon: const Icon(Icons.add),
)
```

The `add` command then calls the `TodoRepository.createTodo()` method with the task description text and it creates a new ToDo item.

The `createTodo()` method returns the newly created ToDo, which is then added to the `_todo` list in the view model.

ToDo items contain a unique identifier generated by the database. This is why the view model doesn’t create the ToDo item, but rather the `TodoRepository` does.

dart

```
Future<Result<void>> _add(String task) async {
  try {
    final result = await _todoRepository.createTodo(task);
    switch (result) {
      case Ok<Todo>():
        _todos.add(result.value);
        return Result.ok(null);
      case Error():
        return Result.error(result.error);
    }
  } on Exception catch (e) {
    return Result.error(e);
  } finally {
    notifyListeners();
  }
}
```

Finally, the `TodoListScreen` also listens to the result in the `add` command. When the action completes, the `TextEditingController` is cleared.

dart

```
void _onAdd() {
  // Clear the text field when the add command completes.
  if (widget.viewModel.add.completed) {
    widget.viewModel.add.clearResult();
    _controller.clear();
  }
}
```

When a user taps on the `IconButton` in the `ListTile`, the delete command is executed.

dart

```
IconButton(
  icon: const Icon(Icons.delete),
  onPressed: () => widget.viewModel.delete.execute(todo.id),
)
```

Then, the view model calls the `TodoRepository.deleteTodo()` method, passing the unique ToDo item identifier. A correct result removes the ToDo item from the view model *and* the screen.

dart

```
Future<Result<void>> _delete(int id) async {
  try {
    final result = await _todoRepository.deleteTodo(id);
    switch (result) {
      case Ok<void>():
        _todos.removeWhere((todo) => todo.id == id);
        return Result.ok(null);
      case Error():
        return Result.error(result.error);
    }
  } on Exception catch (e) {
    return Result.error(e);
  } finally {
    notifyListeners();
  }
}
```

### Todo list domain layer

[#](#todo-list-domain-layer)

The domain layer of this example application contains the `Todo` item data model.

Items are presented by an immutable data class. In this case, the application uses the `freezed` package to generate the code.

The class has two properties, an ID represented by an `int`, and a task description, represented by a `String`.

dart

```
@freezed
abstract class Todo with _$Todo {
  const factory Todo({
    /// The unique identifier of the Todo item.
    required int id,
​
    /// The task description of the Todo item.
    required String task,
  }) = _Todo;
}
```

### Todo list data layer

[#](#todo-list-data-layer)

The data layer of this functionality is composed of two classes, the `TodoRepository` and the `DatabaseService`.

The `TodoRepository` acts as the source of truth for all the ToDo items. View models must use this repository to access to the ToDo list, and it should not expose any implementation details on how they are stored.

Internally, the `TodoRepository` uses the `DatabaseService`, which implements the access to the SQL database using the `sqflite` package. You can implement the same `DatabaseService` using other storage packages like `sqlite3`, `drift` or even cloud storage solutions like `firebase_database`.

The `TodoRepository` checks if the database is open before every request and opens it if necessary.

It implements the `fetchTodos()`, `createTodo()`, and `deleteTodo()` methods.

dart

```
class TodoRepository {
  TodoRepository({required this._database});
​
  final DatabaseService _database;
​
  Future<Result<List<Todo>>> fetchTodos() async {
    if (!_database.isOpen()) {
      await _database.open();
    }
    return _database.getAll();
  }
​
  Future<Result<Todo>> createTodo(String task) async {
    if (!_database.isOpen()) {
      await _database.open();
    }
    return _database.insert(task);
  }
​
  Future<Result<void>> deleteTodo(int id) async {
    if (!_database.isOpen()) {
      await _database.open();
    }
    return _database.delete(id);
  }
}
```

The `DatabaseService` implements the access to the SQLite database using the `sqflite` package.

It’s a good idea to define the table and column names as constants to avoid typos when writing SQL code.

dart

```
static const String _todoTableName = 'todo';
static const String _idColumnName = '_id';
static const String _taskColumnName = 'task';
```

The `open()` method opens the existing database, or creates a new one if it doesn’t exist.

dart

```
Future<void> open() async {
  _database = await databaseFactory.openDatabase(
    join(await databaseFactory.getDatabasesPath(), 'app_database.db'),
    options: OpenDatabaseOptions(
      onCreate: (db, version) {
        return db.execute(
          'CREATE TABLE $_todoTableName($_idColumnName INTEGER PRIMARY KEY AUTOINCREMENT, $_taskColumnName TEXT)',
        );
      },
      version: 1,
    ),
  );
}
```

Note that the column `id` is set as `primary key` and `autoincrement`; this means that each newly inserted item is assigned a new value for the `id` column.

The `insert()` method creates a new ToDo item in the database, and returns a newly created Todo instance. The `id` is generated as mentioned before.

dart

```
Future<Result<Todo>> insert(String task) async {
  try {
    final id = await _database!.insert(_todoTableName, {
      _taskColumnName: task,
    });
    return Result.ok(Todo(id: id, task: task));
  } on Exception catch (e) {
    return Result.error(e);
  }
}
```

All the `DatabaseService` operations use the `Result` class to return a value, as recommended by the [Flutter architecture recommendations](/app-architecture). This facilitates handling errors in further steps in the application code.

The `getAll()` method performs a database query, obtaining all the values in the `id` and `task` columns. For each entry, it creates a `Todo` class instance.

dart

```
Future<Result<List<Todo>>> getAll() async {
  try {
    final entries = await _database!.query(
      _todoTableName,
      columns: [_idColumnName, _taskColumnName],
    );
    final list = entries
        .map(
          (element) => Todo(
            id: element[_idColumnName] as int,
            task: element[_taskColumnName] as String,
          ),
        )
        .toList();
    return Result.ok(list);
  } on Exception catch (e) {
    return Result.error(e);
  }
}
```

The `delete()` method performs a database delete operation based on the ToDo item `id`.

In this case, if no items were deleted an error is returned, indicating that something went wrong.

dart

```
Future<Result<void>> delete(int id) async {
  try {
    final rowsDeleted = await _database!.delete(
      _todoTableName,
      where: '$_idColumnName = ?',
      whereArgs: [id],
    );
    if (rowsDeleted == 0) {
      return Result.error(Exception('No todo found with id $id'));
    }
    return Result.ok(null);
  } on Exception catch (e) {
    return Result.error(e);
  }
}
```

## Putting it all together

[#](#putting-it-all-together)

In the `main()` method of your application, first initialize the `DatabaseService`, which requires different initialization code on different platforms. Then, pass the newly created `DatabaseService` into the `TodoRepository` which is itself passed into the `MainApp` as a constructor argument dependency.

dart

```
void main() {
  final DatabaseService databaseService;
  if (kIsWeb) {
    throw UnsupportedError('Platform not supported');
  } else if (Platform.isLinux || Platform.isWindows || Platform.isMacOS) {
    // Initialize the FFI-based SQLite.
    sqfliteFfiInit();
    databaseService = DatabaseService(databaseFactory: databaseFactoryFfi);
  } else {
    // Use the default native SQLite.
    databaseService = DatabaseService(databaseFactory: databaseFactory);
  }
​
  runApp(
    MainApp(
      // ···
      todoRepository: TodoRepository(database: databaseService),
    ),
  );
}
```

Then, when the `TodoListScreen` is created, also create the `TodoListViewModel` and pass the `TodoRepository` to it as dependency.

dart

```
TodoListScreen(
  viewModel: TodoListViewModel(todoRepository: widget.todoRepository),
)
```

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-09-02. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/app-architecture/design-patterns/sql.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/app-architecture/design-patterns/sql\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/app-architecture/design-patterns/sql.md "Report an issue with this page").

----
url: https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.28.1
----

The 2.28.1 release of the Dart and Flutter DevTools includes the following changes among other general improvements. To learn more about DevTools, check out the [DevTools overview](https://docs.flutter.dev/tools/devtools).

## General updates

[#](#general-updates)

* Added support for DevTools extensions. This means if you are debugging an app that depends on `package:foo`, and `package:foo` provides a DevTools extension, you will see a "Foo" tab display in DevTools that you can use to debug your app. To provide a DevTools extension for your pub package, check out the getting started guide for [package:devtools\_extensions](https://pub.dev/packages/devtools_extensions)!

- Fixed theming bug in isolate selector - [#6403](https://github.com/flutter/devtools/pull/6403)
- Fixed isolate bug where main isolate was not reselecting on hot restart - [#6436](https://github.com/flutter/devtools/pull/6436)
- Show the hot reload button for Dart server apps that support hot reload - [#6341](https://github.com/flutter/devtools/pull/6341)
- Fixed exceptions on hot restart - [#6451](https://github.com/flutter/devtools/pull/6451), [#6450](https://github.com/flutter/devtools/pull/6450)

## Inspector updates

[#](#inspector-updates)

* Fixed bug where inspector service calls were done on the selected isolate, instead of the main isolate - [#6434](https://github.com/flutter/devtools/pull/6434)

## Logging updates

[#](#logging-updates)

* Improved responsiveness of the top bar on the Logging view - [#6281](https://github.com/flutter/devtools/pull/6281)

* Added the ability to copy filtered logs - [#6260](https://github.com/flutter/devtools/pull/6260)

## Full commit history

[#](#full-commit-history)

To find a complete list of changes in this release, check out the [DevTools git log](https://github.com/flutter/devtools/tree/v2.28.1).

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/release-notes/release-notes-2.28.1.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.28.1\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/release-notes/release-notes-2.28.1.md "Report an issue with this page").

----
url: https://docs.flutter.dev/ui/widgets/material2
----

Widgets implementing the Material 2 design guidelines.

## App structure and navigation

[App​Bar](https://pub.dev/documentation/material_ui/latest/material_ui/AppBar-class.html)

[Container that displays content and actions at the top of a screen.](https://pub.dev/documentation/material_ui/latest/material_ui/AppBar-class.html)

[Bottom​Navigation​Bar](https://pub.dev/documentation/material_ui/latest/material_ui/BottomNavigationBar-class.html)

[Container that includes tools to explore and switch between top-level views in a single tap.](https://pub.dev/documentation/material_ui/latest/material_ui/BottomNavigationBar-class.html)

[Drawer](https://pub.dev/documentation/material_ui/latest/material_ui/Drawer-class.html)

[A Material Design panel that slides in horizontally from the edge of a Scaffold to show navigation links in an application.](https://pub.dev/documentation/material_ui/latest/material_ui/Drawer-class.html)

[Material​App](https://pub.dev/documentation/material_ui/latest/material_ui/MaterialApp-class.html)

[A convenience widget that wraps a number of widgets that are commonly required for applications implementing Material Design.](https://pub.dev/documentation/material_ui/latest/material_ui/MaterialApp-class.html)

[Scaffold](https://pub.dev/documentation/material_ui/latest/material_ui/Scaffold-class.html)

[Implements the basic Material Design visual layout structure. This class provides APIs for showing drawers, snack bars, and bottom sheets.](https://pub.dev/documentation/material_ui/latest/material_ui/Scaffold-class.html)

[Sliver​App​Bar](https://pub.dev/documentation/material_ui/latest/material_ui/SliverAppBar-class.html)

[A material design app bar that integrates with a CustomScrollView.](https://pub.dev/documentation/material_ui/latest/material_ui/SliverAppBar-class.html)

[Widgets​App](https://api.flutter.dev/flutter/widgets/WidgetsApp-class.html)

[A convenience class that wraps a number of widgets that are commonly required for an application.](https://api.flutter.dev/flutter/widgets/WidgetsApp-class.html)

## Buttons

[Dropdown​Button](https://pub.dev/documentation/material_ui/latest/material_ui/DropdownButton-class.html)

[Shows the currently selected item and an arrow that opens a menu for selecting another item.](https://pub.dev/documentation/material_ui/latest/material_ui/DropdownButton-class.html)

[Elevated​Button](https://pub.dev/documentation/material_ui/latest/material_ui/ElevatedButton-class.html)

[A Material Design elevated button. A filled button whose material elevates when pressed.](https://pub.dev/documentation/material_ui/latest/material_ui/ElevatedButton-class.html)

[Floating​Action​Button](https://pub.dev/documentation/material_ui/latest/material_ui/FloatingActionButton-class.html)

[Clickable block containing an icon that keeps a key action always in reach.](https://pub.dev/documentation/material_ui/latest/material_ui/FloatingActionButton-class.html)

[Extended Floating​Action​Button](https://pub.dev/documentation/material_ui/latest/material_ui/FloatingActionButton/FloatingActionButton.extended.html)

[Clickable block that triggers an action. These wider blocks can fit a text label and provide a larger target area.](https://pub.dev/documentation/material_ui/latest/material_ui/FloatingActionButton/FloatingActionButton.extended.html)

[Icon​Button](https://pub.dev/documentation/material_ui/latest/material_ui/IconButton-class.html)

[Clickable icons to prompt app users to take supplementary actions.](https://pub.dev/documentation/material_ui/latest/material_ui/IconButton-class.html)

[Outlined​Button](https://pub.dev/documentation/material_ui/latest/material_ui/OutlinedButton-class.html)

[A Material Design outlined button, essentially a TextButton with an outlined border.](https://pub.dev/documentation/material_ui/latest/material_ui/OutlinedButton-class.html)

[Popup​Menu​Button](https://pub.dev/documentation/material_ui/latest/material_ui/PopupMenuButton-class.html)

[Displays a menu when pressed and calls onSelected when the menu is dismissed because an item was selected.](https://pub.dev/documentation/material_ui/latest/material_ui/PopupMenuButton-class.html)

[Text​Button](https://pub.dev/documentation/material_ui/latest/material_ui/TextButton-class.html)

[A Material Design text button. A simple flat button without a border outline.](https://pub.dev/documentation/material_ui/latest/material_ui/TextButton-class.html)

## Input and selections

[Checkbox](https://pub.dev/documentation/material_ui/latest/material_ui/Checkbox-class.html)

[Form control that app users can set or clear to select one or more options from a set.](https://pub.dev/documentation/material_ui/latest/material_ui/Checkbox-class.html)

[Date​Picker](https://pub.dev/documentation/material_ui/latest/material_ui/showDatePicker.html)

[Calendar interface used to select a date or a range of dates.](https://pub.dev/documentation/material_ui/latest/material_ui/showDatePicker.html)

[Radio](https://pub.dev/documentation/material_ui/latest/material_ui/Radio-class.html)

[Form control that app users can set or clear to select only one option from a set.](https://pub.dev/documentation/material_ui/latest/material_ui/Radio-class.html)

[Slider](https://pub.dev/documentation/material_ui/latest/material_ui/Slider-class.html)

[Form control that enables selecting a range of values.](https://pub.dev/documentation/material_ui/latest/material_ui/Slider-class.html)

[Switch](https://pub.dev/documentation/material_ui/latest/material_ui/Switch-class.html)

[Toggle control that changes the state of a single item to on or off.](https://pub.dev/documentation/material_ui/latest/material_ui/Switch-class.html)

[Text​Field](https://pub.dev/documentation/material_ui/latest/material_ui/TextField-class.html)

[Box into which app users can enter text. They appear in forms and dialogs.](https://pub.dev/documentation/material_ui/latest/material_ui/TextField-class.html)

## Dialogs, alerts, and panels

[Alert​Dialog](https://pub.dev/documentation/material_ui/latest/material_ui/AlertDialog-class.html)

[Hovering containers that prompt app users to provide more data or make a decision.](https://pub.dev/documentation/material_ui/latest/material_ui/AlertDialog-class.html)

[Expansion​Panel](https://pub.dev/documentation/material_ui/latest/material_ui/ExpansionPanel-class.html)

[Expansion panels contain creation flows and allow lightweight editing of an element. The ExpansionPanel widget implements this component.](https://pub.dev/documentation/material_ui/latest/material_ui/ExpansionPanel-class.html)

[Simple​Dialog](https://pub.dev/documentation/material_ui/latest/material_ui/SimpleDialog-class.html)

[Simple dialogs can provide additional details or actions about a list item. For example they can display avatars icons clarifying subtext or orthogonal actions (such...](https://pub.dev/documentation/material_ui/latest/material_ui/SimpleDialog-class.html)

[Snack​Bar](https://pub.dev/documentation/material_ui/latest/material_ui/SnackBar-class.html)

[Brief messages about app processes that display at the bottom of the screen.](https://pub.dev/documentation/material_ui/latest/material_ui/SnackBar-class.html)

## Information displays

[Card](https://pub.dev/documentation/material_ui/latest/material_ui/Card-class.html)

[Container for short, related pieces of content displayed in a box with rounded corners and a drop shadow.](https://pub.dev/documentation/material_ui/latest/material_ui/Card-class.html)

[Chip](https://pub.dev/documentation/material_ui/latest/material_ui/Chip-class.html)

[Small blocks that simplify entering information, making selections, filtering content, or triggering actions.](https://pub.dev/documentation/material_ui/latest/material_ui/Chip-class.html)

[Circular​Progress​Indicator](https://pub.dev/documentation/material_ui/latest/material_ui/CircularProgressIndicator-class.html)

[Circular progress indicator that spins to indicate a busy application.](https://pub.dev/documentation/material_ui/latest/material_ui/CircularProgressIndicator-class.html)

[Data​Table](https://pub.dev/documentation/material_ui/latest/material_ui/DataTable-class.html)

[Data tables display sets of raw data. They usually appear in desktop enterprise products. The DataTable widget implements this component.](https://pub.dev/documentation/material_ui/latest/material_ui/DataTable-class.html)

[Grid​View](https://api.flutter.dev/flutter/widgets/GridView-class.html)

[A grid list consists of a repeated pattern of cells arrayed in a vertical and horizontal layout. The GridView widget implements this component.](https://api.flutter.dev/flutter/widgets/GridView-class.html)

[Icon](https://api.flutter.dev/flutter/widgets/Icon-class.html)

[A Material Design icon.](https://api.flutter.dev/flutter/widgets/Icon-class.html)

[Image](https://api.flutter.dev/flutter/widgets/Image-class.html)

[A widget that displays an image.](https://api.flutter.dev/flutter/widgets/Image-class.html)

[Linear​Progress​Indicator](https://pub.dev/documentation/material_ui/latest/material_ui/LinearProgressIndicator-class.html)

[Vertical line that changes color as an ongoing process, such as loading an app or submitting a form, completes.](https://pub.dev/documentation/material_ui/latest/material_ui/LinearProgressIndicator-class.html)

[Tooltip](https://pub.dev/documentation/material_ui/latest/material_ui/Tooltip-class.html)

[Tooltips provide text labels that help explain the function of a button or other user interface action. Wrap the button in a Tooltip widget to...](https://pub.dev/documentation/material_ui/latest/material_ui/Tooltip-class.html)

## Layout

[Divider](https://pub.dev/documentation/material_ui/latest/material_ui/Divider-class.html)

[Thin line that groups content in lists and containers.](https://pub.dev/documentation/material_ui/latest/material_ui/Divider-class.html)

[List​Tile](https://pub.dev/documentation/material_ui/latest/material_ui/ListTile-class.html)

[A single fixed-height row that typically contains some text as well as a leading or trailing icon.](https://pub.dev/documentation/material_ui/latest/material_ui/ListTile-class.html)

[Stepper](https://pub.dev/documentation/material_ui/latest/material_ui/Stepper-class.html)

[A Material Design stepper widget that displays progress through a sequence of steps.](https://pub.dev/documentation/material_ui/latest/material_ui/Stepper-class.html)

Find more widgets in the [widget catalog](/ui/widgets).

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-09-29. [Report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/ui/widgets/material2 "Report an issue with this page").

----
url: https://docs.flutter.dev/platform-integration/web/web-dev-config-file
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Set up a web development configuration file](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

Flutter web includes a development server that defaults to serving your application in the `localhost` domain using HTTP on a randomly assigned port. While command-line arguments offer a quick way to modify the server's behavior, this document focuses on a more structured approach: defining your server's behavior through a centralized `web_dev_config.yaml` file. This configuration file allows you to customize server settings—host, port, HTTPS settings, and proxy rules—ensuring a consistent development environment.

## Create a configuration file

[#](#create-a-configuration-file)

Add a `web_dev_config.yaml` file to the root directory of your Flutter project. If you haven't set up a Flutter project, visit [Building a web application with Flutter](/platform-integration/web/building) to get started.

## Add configuration settings

[#](#add-configuration-settings)

### Basic server configuration

[#](#basic-server-configuration)

You can define the host, port, and HTTPS settings for your development server.

web\_dev\_config.yaml

yaml

```
server:
  host: "0.0.0.0" # Defines the binding address <string>
  port: 8080 # Specifies the port <int> for the development server
  https:
    cert-path: "/path/to/cert.pem" # Path <string> to your TLS certificate
    cert-key-path: "/path/to/key.pem" # Path <string> to TLS certificate key
```

### Custom headers

[#](#custom-headers)

You can also inject custom HTTP headers into the development server's responses.

web\_dev\_config.yaml

yaml

```
server:
  headers:
    - name: "X-Custom-Header" # Name <string> of the HTTP header
      value: "MyValue" # Value <string> of the HTTP header
    - name: "Cache-Control"
      value: "no-cache, no-store, must-revalidate"
```

### Proxy configuration

[#](#proxy-configuration)

Requests are matched in order from the `web_dev_config.yaml` file.

#### Basic string proxy

[#](#basic-string-proxy)

Use the `prefix` field for simple path prefix matching.

web\_dev\_config.yaml

yaml

```
server:
  proxy:
    - target: "http://localhost:5000/" # Base URL <string> of your backend
      prefix: "/users/" # Path <string>
    - target: "http://localhost:3000/"
      prefix: "/data/"
      replace: "/report/" # Replacement <string> of path in redirected URL (optional)
    - target: "http://localhost:4000/"
      prefix: "/products/"
      replace: ""
```

**Explanation:**

* A request to `/users/names` is forwarded to `http://localhost:5000/users/names`.
* A request to `/data/2023/` is forwarded to `http://localhost:3000/report/2023` because `replace: “/report/”` replaces the `/data/` prefix.
* A request to `/products/item/123` is forwarded to `http://localhost:4000/item/123` because `replace: ""` removes the `/products/` prefix by replacing it with an empty string.

#### Advanced regex proxy

[#](#advanced-regex-proxy)

You can also use the `regex` field for more flexible and complex matching.

web\_dev\_config.yaml

yaml

```
server:
  proxy:
    - target: "http://localhost:5000/"
      regex: "/users/(\d+)/$" # Path <string> matches requests like /users/123/
    - target: "http://localhost:4000/"
      regex: "^/api/(v\d+)/(.*)" # Matches requests like /api/v1/users
      replace: "/$2?apiVersion=$1" # Allows capture groups (optional)
```

**Explanation:**

* A request to `/users/123/` matches the first rule exactly, so it is forwarded to `http://localhost:5000/users/123/`.
* A request to `/api/v1/users/profile/` starts with the second rule path so it is forwarded to `http://localhost:4000/users/profile/?apiVersion=v1`.

## Configuration precedence

[#](#configuration-precedence)

Remember the order of precedence for settings:

1. **Command-line arguments** (such as `--web-hostname`, `--web-port`)
2. **`web_dev_config.yaml` settings**
3. **Built-in default values**

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/platform-integration/web/web-dev-config-file.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/platform-integration/web/web-dev-config-file\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/platform-integration/web/web-dev-config-file.md "Report an issue with this page").

----
url: https://docs.flutter.dev/flutter-for/web-devs
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Flutter for web developers](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

This page is for users who are familiar with the HTML and CSS syntax for arranging components of an application's UI. It maps HTML/CSS code snippets to their Flutter/Dart code equivalents.

Flutter is a framework for building cross-platform applications that uses the Dart programming language. To understand some differences between programming with Dart and programming with JavaScript, see [Learning Dart as a JavaScript Developer](https://dart.dev/guides/language/coming-from/js-to-dart).

One of the fundamental differences between designing a web layout and a Flutter layout, is learning how constraints work, and how widgets are sized and positioned. To learn more, see [Understanding constraints](/ui/layout/constraints).

The examples assume:

* The HTML document starts with `<!DOCTYPE html>`, and the CSS box model for all HTML elements is set to [`border-box`](https://css-tricks.com/box-sizing/), for consistency with the Flutter model.

  css

  ```
  {
      box-sizing: border-box;
  }
  ```

* In Flutter, the default styling of the 'Lorem ipsum' text is defined by the `bold24Roboto` variable as follows, to keep the syntax simple:

  dart

  ```
  TextStyle bold24Roboto = const TextStyle(
    color: Colors.white,
    fontSize: 24,
    fontWeight: FontWeight.bold,
  );
  ```

## Performing basic layout operations

[#](#performing-basic-layout-operations)

The following examples show how to perform the most common UI layout tasks.

### Styling and aligning text

[#](#styling-and-aligning-text)

Font style, size, and other text attributes that CSS handles with the font and color properties are individual properties of a [`TextStyle`](https://api.flutter.dev/flutter/painting/TextStyle-class.html) child of a [`Text`](https://api.flutter.dev/flutter/widgets/Text-class.html) widget.

For text-align property in CSS that is used for aligning text, there is a textAlign property of a [`Text`](https://api.flutter.dev/flutter/widgets/Text-class.html) widget.

In both HTML and Flutter, child elements or widgets are anchored at the top left, by default.

css

```
<div class="grey-box">
  Lorem ipsum
</div>
​
.grey-box {
    background-color: #e0e0e0; /* grey 300 */
    width: 320px;
    height: 240px;
    font: 900 24px Georgia;
}
```

dart

```
final container = Container(
  // grey box
  width: 320,
  height: 240,
  color: Colors.grey[300],
  child: const Text(
    'Lorem ipsum',
    style: TextStyle(
      fontFamily: 'Georgia',
      fontSize: 24,
      fontWeight: FontWeight.bold,
    ),
    textAlign: TextAlign.center,
  ),
);
```

### Setting background color

[#](#setting-background-color)

In Flutter, you set the background color using the `color` property or the `decoration` property of a [`Container`](https://api.flutter.dev/flutter/widgets/Container-class.html). However, you cannot supply both, since it would potentially result in the decoration drawing over the background color. The `color` property should be preferred when the background is a simple color. For other cases, such as gradients or images, use the `decoration` property.

The CSS examples use the hex color equivalents to the Material color palette.

css

```
<div class="grey-box">
  Lorem ipsum
</div>
​
.grey-box {
    background-color: #e0e0e0; /* grey 300 */
    width: 320px;
    height: 240px;
    font: 900 24px Roboto;
}
```

dart

```
final container = Container(
  // grey box
  width: 320,
  height: 240,
  color: Colors.grey[300],
  child: Text(
    'Lorem ipsum',
    style: bold24Roboto,
  ),
);
```

dart

```
final container = Container(
  // grey box
  width: 320,
  height: 240,
  decoration: BoxDecoration(
    color: Colors.grey[300],
  ),
  child: Text(
    'Lorem ipsum',
    style: bold24Roboto,
  ),
);
```

### Centering components

[#](#centering-components)

A [`Center`](https://api.flutter.dev/flutter/widgets/Center-class.html) widget centers its child both horizontally and vertically.

To accomplish a similar effect in CSS, the parent element uses either a flex or table-cell display behavior. The examples on this page show the flex behavior.

css

```
<div class="grey-box">
  Lorem ipsum
</div>
​
.grey-box {
    background-color: #e0e0e0; /* grey 300 */
    width: 320px;
    height: 240px;
    font: 900 24px Roboto;
    display: flex;
    align-items: center;
    justify-content: center;
}
```

dart

```
final container = Container(
  // grey box
  width: 320,
  height: 240,
  color: Colors.grey[300],
  child: Center(
    child: Text(
      'Lorem ipsum',
      style: bold24Roboto,
    ),
  ),
);
```

### Setting container width

[#](#setting-container-width)

To specify the width of a [`Container`](https://api.flutter.dev/flutter/widgets/Container-class.html) widget, use its `width` property. This is a fixed width, unlike the CSS max-width property that adjusts the container width up to a maximum value. To mimic that effect in Flutter, use the `constraints` property of the Container. Create a new [`BoxConstraints`](https://api.flutter.dev/flutter/rendering/BoxConstraints-class.html) widget with a `minWidth` or `maxWidth`.

For nested Containers, if the parent's width is less than the child's width, the child Container sizes itself to match the parent.

css

```
<div class="grey-box">
  <div class="red-box">
    Lorem ipsum
  </div>
</div>
​
.grey-box {
    background-color: #e0e0e0; /* grey 300 */
    width: 320px;
    height: 240px;
    font: 900 24px Roboto;
    display: flex;
    align-items: center;
    justify-content: center;
}
.red-box {
    background-color: #ef5350; /* red 400 */
    padding: 16px;
    color: #ffffff;
    width: 100%;
    max-width: 240px;
}
```

dart

```
final container = Container(
  // grey box
  width: 320,
  height: 240,
  color: Colors.grey[300],
  child: Center(
    child: Container(
      // red box
      width: 240, // max-width is 240
      padding: const EdgeInsets.all(16),
      decoration: BoxDecoration(
        color: Colors.red[400],
      ),
      child: Text(
        'Lorem ipsum',
        style: bold24Roboto,
      ),
    ),
  ),
);
```

## Manipulating position and size

[#](#manipulating-position-and-size)

The following examples show how to perform more complex operations on widget position, size, and background.

### Setting absolute position

[#](#setting-absolute-position)

By default, widgets are positioned relative to their parent.

To specify an absolute position for a widget as x-y coordinates, nest it in a [`Positioned`](https://api.flutter.dev/flutter/widgets/Positioned-class.html) widget that is, in turn, nested in a [`Stack`](https://api.flutter.dev/flutter/widgets/Stack-class.html) widget.

css

```
<div class="grey-box">
  <div class="red-box">
    Lorem ipsum
  </div>
</div>
​
.grey-box {
    position: relative;
    background-color: #e0e0e0; /* grey 300 */
    width: 320px;
    height: 240px;
    font: 900 24px Roboto;
}
.red-box {
    background-color: #ef5350; /* red 400 */
    padding: 16px;
    color: #ffffff;
    position: absolute;
    top: 24px;
    left: 24px;
}
```

dart

```
final container = Container(
  // grey box
  width: 320,
  height: 240,
  color: Colors.grey[300],
  child: Stack(
    children: [
      Positioned(
        // red box
        left: 24,
        top: 24,
        child: Container(
          padding: const EdgeInsets.all(16),
          decoration: BoxDecoration(
            color: Colors.red[400],
          ),
          child: Text(
            'Lorem ipsum',
            style: bold24Roboto,
          ),
        ),
      ),
    ],
  ),
);
```

### Rotating components

[#](#rotating-components)

To rotate a widget, nest it in a [`Transform`](https://api.flutter.dev/flutter/widgets/Transform-class.html) widget. Use the `Transform` widget's `alignment` and `origin` properties to specify the transform origin (fulcrum) in relative and absolute terms, respectively.

For a simple 2D rotation, in which the widget is rotated on the Z axis, create a new [`Matrix4`](https://api.flutter.dev/flutter/vector_math_64/Matrix4-class.html) identity object and use its `rotateZ()` method to specify the rotation factor using radians (degrees × π / 180).

css

```
<div class="grey-box">
  <div class="red-box">
    Lorem ipsum
  </div>
</div>
​
.grey-box {
    background-color: #e0e0e0; /* grey 300 */
    width: 320px;
    height: 240px;
    font: 900 24px Roboto;
    display: flex;
    align-items: center;
    justify-content: center;
}
.red-box {
    background-color: #ef5350; /* red 400 */
    padding: 16px;
    color: #ffffff;
    transform: rotate(15deg);
}
```

dart

```
final container = Container(
  // grey box
  width: 320,
  height: 240,
  color: Colors.grey[300],
  child: Center(
    child: Transform(
      alignment: Alignment.center,
      transform: Matrix4.identity()..rotateZ(15 * 3.1415927 / 180),
      child: Container(
        // red box
        padding: const EdgeInsets.all(16),
        decoration: BoxDecoration(
          color: Colors.red[400],
        ),
        child: Text(
          'Lorem ipsum',
          style: bold24Roboto,
          textAlign: TextAlign.center,
        ),
      ),
    ),
  ),
);
```

### Scaling components

[#](#scaling-components)

To scale a widget up or down, nest it in a [`Transform`](https://api.flutter.dev/flutter/widgets/Transform-class.html) widget. Use the Transform widget's `alignment` and `origin` properties to specify the transform origin (fulcrum) in relative or absolute terms, respectively.

For a simple scaling operation along the x-axis, create a new [`Matrix4`](https://api.flutter.dev/flutter/vector_math_64/Matrix4-class.html) identity object and use its `scale()` method to specify the scaling factor.

When you scale a parent widget, its child widgets are scaled accordingly.

css

```
<div class="grey-box">
  <div class="red-box">
    Lorem ipsum
  </div>
</div>
​
.grey-box {
    background-color: #e0e0e0; /* grey 300 */
    width: 320px;
    height: 240px;
    font: 900 24px Roboto;
    display: flex;
    align-items: center;
    justify-content: center;
}
.red-box {
    background-color: #ef5350; /* red 400 */
    padding: 16px;
    color: #ffffff;
    transform: scale(1.5);
}
```

dart

```
final container = Container(
  // grey box
  width: 320,
  height: 240,
  color: Colors.grey[300],
  child: Center(
    child: Transform(
      alignment: Alignment.center,
      transform: Matrix4.identity()..scaleByDouble(1.5, 1.5, 1.5, 1.5),
      child: Container(
        // red box
        padding: const EdgeInsets.all(16),
        decoration: BoxDecoration(
          color: Colors.red[400],
        ),
        child: Text(
          'Lorem ipsum',
          style: bold24Roboto,
          textAlign: TextAlign.center,
        ),
      ),
    ),
  ),
);
```

### Applying a linear gradient

[#](#applying-a-linear-gradient)

To apply a linear gradient to a widget's background, nest it in a [`Container`](https://api.flutter.dev/flutter/widgets/Container-class.html) widget. Then use the `Container` widget's `decoration` property to create a [`BoxDecoration`](https://api.flutter.dev/flutter/painting/BoxDecoration-class.html) object, and use `BoxDecoration`'s `gradient` property to transform the background fill.

The gradient "angle" is based on the Alignment (x, y) values:

* If the beginning and ending x values are equal, the gradient is vertical (0° | 180°).
* If the beginning and ending y values are equal, the gradient is horizontal (90° | 270°).

#### Vertical gradient

[#](#vertical-gradient)

css

```
<div class="grey-box">
  <div class="red-box">
    Lorem ipsum
  </div>
</div>
​
.grey-box {
    background-color: #e0e0e0; /* grey 300 */
    width: 320px;
    height: 240px;
    font: 900 24px Roboto;
    display: flex;
    align-items: center;
    justify-content: center;
}
.red-box {
    padding: 16px;
    color: #ffffff;
    background: linear-gradient(180deg, #ef5350, rgba(0, 0, 0, 0) 80%);
}
```

dart

```
final container = Container(
  // grey box
  width: 320,
  height: 240,
  color: Colors.grey[300],
  child: Center(
    child: Container(
      // red box
      decoration: const BoxDecoration(
        gradient: LinearGradient(
          begin: Alignment.topCenter,
          end: Alignment(0.0, 0.6),
          colors: <Color>[
            Color(0xffef5350),
            Color(0x00ef5350),
          ],
        ),
      ),
      padding: const EdgeInsets.all(16),
      child: Text(
        'Lorem ipsum',
        style: bold24Roboto,
      ),
    ),
  ),
);
```

#### Horizontal gradient

[#](#horizontal-gradient)

css

```
<div class="grey-box">
  <div class="red-box">
    Lorem ipsum
  </div>
</div>
​
.grey-box {
    background-color: #e0e0e0; /* grey 300 */
    width: 320px;
    height: 240px;
    font: 900 24px Roboto;
    display: flex;
    align-items: center;
    justify-content: center;
}
.red-box {
    padding: 16px;
    color: #ffffff;
    background: linear-gradient(90deg, #ef5350, rgba(0, 0, 0, 0) 80%);
}
```

dart

```
final container = Container(
  // grey box
  width: 320,
  height: 240,
  color: Colors.grey[300],
  child: Center(
    child: Container(
      // red box
      padding: const EdgeInsets.all(16),
      decoration: const BoxDecoration(
        gradient: LinearGradient(
          begin: Alignment(-1.0, 0.0),
          end: Alignment(0.6, 0.0),
          colors: <Color>[
            Color(0xffef5350),
            Color(0x00ef5350),
          ],
        ),
      ),
      child: Text(
        'Lorem ipsum',
        style: bold24Roboto,
      ),
    ),
  ),
);
```

## Manipulating shapes

[#](#manipulating-shapes)

The following examples show how to make and customize shapes.

### Rounding corners

[#](#rounding-corners)

To round the corners of a rectangular shape, use the `borderRadius` property of a [`BoxDecoration`](https://api.flutter.dev/flutter/painting/BoxDecoration-class.html) object. Create a new [`BorderRadius`](https://api.flutter.dev/flutter/painting/BorderRadius-class.html) object that specifies the radius for rounding each corner.

css

```
<div class="grey-box">
  <div class="red-box">
    Lorem ipsum
  </div>
</div>
​
.grey-box {
    background-color: #e0e0e0; /* grey 300 */
    width: 320px;
    height: 240px;
    font: 900 24px Roboto;
    display: flex;
    align-items: center;
    justify-content: center;
}
.red-box {
    background-color: #ef5350; /* red 400 */
    padding: 16px;
    color: #ffffff;
    border-radius: 8px;
}
```

dart

```
final container = Container(
  // grey box
  width: 320,
  height: 240,
  color: Colors.grey[300],
  child: Center(
    child: Container(
      // red circle
      padding: const EdgeInsets.all(16),
      decoration: BoxDecoration(
        color: Colors.red[400],
        borderRadius: const BorderRadius.all(
          Radius.circular(8),
        ),
      ),
      child: Text(
        'Lorem ipsum',
        style: bold24Roboto,
      ),
    ),
  ),
);
```

### Adding box shadows

[#](#adding-box-shadows)

In CSS you can specify shadow offset and blur in shorthand, using the box-shadow property. This example shows two box shadows, with properties:

* `xOffset: 0px, yOffset: 2px, blur: 4px, color: black @80% alpha`
* `xOffset: 0px, yOffset: 06x, blur: 20px, color: black @50% alpha`

In Flutter, each property and value is specified separately. Use the `boxShadow` property of `BoxDecoration` to create a list of [`BoxShadow`](https://api.flutter.dev/flutter/painting/BoxShadow-class.html) widgets. You can define one or multiple `BoxShadow` widgets, which can be stacked to customize the shadow depth, color, and so on.

css

```
<div class="grey-box">
  <div class="red-box">
    Lorem ipsum
  </div>
</div>
​
.grey-box {
    background-color: #e0e0e0; /* grey 300 */
    width: 320px;
    height: 240px;
    font: 900 24px Roboto;
    display: flex;
    align-items: center;
    justify-content: center;
}
.red-box {
    background-color: #ef5350; /* red 400 */
    padding: 16px;
    color: #ffffff;
    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.8),
              0 6px 20px rgba(0, 0, 0, 0.5);
}
```

dart

```
final container = Container(
  // grey box
  width: 320,
  height: 240,
  margin: const EdgeInsets.only(bottom: 16),
  decoration: BoxDecoration(
    color: Colors.grey[300],
  ),
  child: Center(
    child: Container(
      // red box
      padding: const EdgeInsets.all(16),
      decoration: BoxDecoration(
        color: Colors.red[400],
        boxShadow: const <BoxShadow>[
          BoxShadow(
            color: Color(0xcc000000),
            offset: Offset(0, 2),
            blurRadius: 4,
          ),
          BoxShadow(
            color: Color(0x80000000),
            offset: Offset(0, 6),
            blurRadius: 20,
          ),
        ],
      ),
      child: Text(
        'Lorem ipsum',
        style: bold24Roboto,
      ),
    ),
  ),
);
```

### Making circles and ellipses

[#](#making-circles-and-ellipses)

Making a circle in CSS requires a workaround of applying a border-radius of 50% to all four sides of a rectangle, though there are [basic shapes](https://developer.mozilla.org/en-US/docs/Web/CSS/basic-shape).

While this approach is supported with the `borderRadius` property of [`BoxDecoration`](https://api.flutter.dev/flutter/painting/BoxDecoration-class.html), Flutter provides a `shape` property with [`BoxShape` enum](https://api.flutter.dev/flutter/painting/BoxShape.html) for this purpose.

css

```
<div class="grey-box">
  <div class="red-circle">
    Lorem ipsum
  </div>
</div>
​
.grey-box {
    background-color: #e0e0e0; /* grey 300 */
    width: 320px;
    height: 240px;
    font: 900 24px Roboto;
    display: flex;
    align-items: center;
    justify-content: center;
}
.red-circle {
    background-color: #ef5350; /* red 400 */
    padding: 16px;
    color: #ffffff;
    text-align: center;
    width: 160px;
    height: 160px;
    border-radius: 50%;
}
```

dart

```
final container = Container(
  // grey box
  width: 320,
  height: 240,
  color: Colors.grey[300],
  child: Center(
    child: Container(
      // red circle
      decoration: BoxDecoration(
        color: Colors.red[400],
        shape: BoxShape.circle,
      ),
      padding: const EdgeInsets.all(16),
      width: 160,
      height: 160,
      child: Text(
        'Lorem ipsum',
        style: bold24Roboto,
        textAlign: TextAlign.center,
      ),
    ),
  ),
);
```

## Manipulating text

[#](#manipulating-text)

The following examples show how to specify fonts and other text attributes. They also show how to transform text strings, customize spacing, and create excerpts.

### Adjusting text spacing

[#](#adjusting-text-spacing)

In CSS, you specify the amount of white space between each letter or word by giving a length value for the letter-spacing and word-spacing properties, respectively. The amount of space can be in px, pt, cm, em, etc.

In Flutter, you specify white space as logical pixels (negative values are allowed) for the `letterSpacing` and `wordSpacing` properties of a [`TextStyle`](https://api.flutter.dev/flutter/painting/TextStyle-class.html) child of a `Text` widget.

css

```
<div class="grey-box">
  <div class="red-box">
    Lorem ipsum
  </div>
</div>
​
.grey-box {
    background-color: #e0e0e0; /* grey 300 */
    width: 320px;
    height: 240px;
    font: 900 24px Roboto;
    display: flex;
    align-items: center;
    justify-content: center;
}
.red-box {
    background-color: #ef5350; /* red 400 */
    padding: 16px;
    color: #ffffff;
    letter-spacing: 4px;
}
```

dart

```
final container = Container(
  // grey box
  width: 320,
  height: 240,
  color: Colors.grey[300],
  child: Center(
    child: Container(
      // red box
      padding: const EdgeInsets.all(16),
      decoration: BoxDecoration(
        color: Colors.red[400],
      ),
      child: const Text(
        'Lorem ipsum',
        style: TextStyle(
          color: Colors.white,
          fontSize: 24,
          fontWeight: FontWeight.w900,
          letterSpacing: 4,
        ),
      ),
    ),
  ),
);
```

### Making inline formatting changes

[#](#making-inline-formatting-changes)

A [`Text`](https://api.flutter.dev/flutter/widgets/Text-class.html) widget lets you display text with some formatting characteristics. To display text that uses multiple styles (in this example, a single word with emphasis), use a [`RichText`](https://api.flutter.dev/flutter/widgets/RichText-class.html) widget instead. Its `text` property can specify one or more [`TextSpan`](https://api.flutter.dev/flutter/painting/TextSpan-class.html) objects that can be individually styled.

In the following example, "Lorem" is in a `TextSpan` with the default (inherited) text styling, and "ipsum" is in a separate `TextSpan` with custom styling.

css

```
<div class="grey-box">
  <div class="red-box">
    Lorem <em>ipsum</em>
  </div>
</div>
​
.grey-box {
    background-color: #e0e0e0; /* grey 300 */
    width: 320px;
    height: 240px;
    font: 900 24px Roboto;
    display: flex;
    align-items: center;
    justify-content: center;
}
.red-box {
    background-color: #ef5350; /* red 400 */
    padding: 16px;
    color: #ffffff;
}
.red-box em {
    font: 300 48px Roboto;
    font-style: italic;
}
```

dart

```
final container = Container(
  // grey box
  width: 320,
  height: 240,
  color: Colors.grey[300],
  child: Center(
    child: Container(
      // red box
      decoration: BoxDecoration(
        color: Colors.red[400],
      ),
      padding: const EdgeInsets.all(16),
      child: RichText(
        text: TextSpan(
          style: bold24Roboto,
          children: const <TextSpan>[
            TextSpan(text: 'Lorem '),
            TextSpan(
              text: 'ipsum',
              style: TextStyle(
                fontWeight: FontWeight.w300,
                fontStyle: FontStyle.italic,
                fontSize: 48,
              ),
            ),
          ],
        ),
      ),
    ),
  ),
);
```

### Creating text excerpts

[#](#creating-text-excerpts)

An excerpt displays the initial line(s) of text in a paragraph, and handles the overflow text, often using an ellipsis.

In Flutter, use the `maxLines` property of a [`Text`](https://api.flutter.dev/flutter/widgets/Text-class.html) widget to specify the number of lines to include in the excerpt, and the `overflow` property for handling overflow text.

css

```
<div class="grey-box">
  <div class="red-box">
    Lorem ipsum dolor sit amet, consec etur
  </div>
</div>
​
.grey-box {
    background-color: #e0e0e0; /* grey 300 */
    width: 320px;
    height: 240px;
    font: 900 24px Roboto;
    display: flex;
    align-items: center;
    justify-content: center;
}
.red-box {
    background-color: #ef5350; /* red 400 */
    padding: 16px;
    color: #ffffff;
    overflow: hidden;
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
}
```

dart

```
final container = Container(
  // grey box
  width: 320,
  height: 240,
  color: Colors.grey[300],
  child: Center(
    child: Container(
      // red box
      decoration: BoxDecoration(
        color: Colors.red[400],
      ),
      padding: const EdgeInsets.all(16),
      child: Text(
        'Lorem ipsum dolor sit amet, consec etur',
        style: bold24Roboto,
        overflow: TextOverflow.ellipsis,
        maxLines: 1,
      ),
    ),
  ),
);
```

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-11. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/flutter-for/web-devs.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/flutter-for/web-devs\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/flutter-for/web-devs.md "Report an issue with this page").

----
url: https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.58.0
----

The 2.58.0 release of the Dart and Flutter DevTools includes the following changes among other general improvements. To learn more about DevTools, check out the [DevTools overview](/tools/devtools).

## Inspector updates

[#](#inspector-updates)

* Deleted the option to use the legacy inspector. [#9782](https://github.com/flutter/devtools/pull/9782)
* Fixed an issue where navigating the Inspector widget tree with the keyboard arrow keys did not update the selected widget in the connected Flutter app. [#9810](https://github.com/flutter/devtools/pull/9810)
* Fixed an issue where clicking a widget row after collapsing a subtree with the left arrow key unexpectedly re-expanded the subtree. [#9810](https://github.com/flutter/devtools/pull/9810)
* Fixed an issue where collapsing the Inspector widget tree to a single row with the left arrow key caused a loading spinner to appear instead of showing the root node. [#9810](https://github.com/flutter/devtools/pull/9810)

## Performance updates

[#](#performance-updates)

* Fixed an issue where 'More Debug Options' showed options as unselected in profile mode even when selected. [#9813](https://github.com/flutter/devtools/issues/9813)

## Debugger updates

[#](#debugger-updates)

* Fixed an issue where long string values in the console/variables view would overflow and overlap with other elements. [#7112](https://github.com/flutter/devtools/issues/7112)

## Network profiler updates

[#](#network-profiler-updates)

* Added response size column to the Network tab and displayed response size in the request inspector overview.\
  [#9744](https://github.com/flutter/devtools/pull/9744)

* Improved HTTP request status classification in the Network tab to better distinguish cancelled, completed, and in-flight requests (for example, avoiding some cases where cancelled requests appeared as pending). [#9683](https://github.com/flutter/devtools/pull/9683)

* Added a filter setting to hide HTTP-profiler socket data.\
  [#9698](https://github.com/flutter/devtools/pull/9698)

## Logging updates

[#](#logging-updates)

* Fixed an issue where log messages containing newline characters were incorrectly split into multiple separate entries in the Logging screen. [#9757](https://github.com/flutter/devtools/pull/9757)

## Deep links tool updates

[#](#deep-links-tool-updates)

* Pluralized "domain" and "path" in the validation summary notification titles when multiple errors are present. [#9790](https://github.com/flutter/devtools/pull/9790)

## Full commit history

[#](#full-commit-history)

To find a complete list of changes in this release, check out the [DevTools git log](https://github.com/flutter/devtools/tree/v2.58.0).

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-06. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/release-notes/release-notes-2.58.0.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.58.0\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/release-notes/release-notes-2.58.0.md "Report an issue with this page").

----
url: https://docs.flutter.dev/ui.md
----

\# Building user interfaces with Flutter > Introduction to user interface development in Flutter.

<!--?code-excerpt path-base="ui/widgets_intro/"?-->

Flutter widgets are built using a modern framework that takes inspiration from \[React]\[]. The central idea is that you build your UI out of widgets. Widgets describe what their view should look like given their current configuration and state. When a widget's state changes, the widget rebuilds its description, which the framework diffs against the previous description in order to determine the minimal changes needed in the underlying render tree to transition from one state to the next. Flutter structures its UI layer into two tiers: the style-neutral core \`widgets\` library (provided directly by the Flutter SDK) and design system packages \`material\_ui\` and \`cupertino\_ui\`, available on pub.dev. Understanding the distinction helps you decide which package to import for your app or package. ## The \`widgets\` package The \`widgets\`library (\`package:flutter/widgets.dart\`) provides the foundational, style-neutral building blocks of the Flutter framework. It includes layout primitives (\`Row,\` \`Column,\` \`Stack)\`, interaction models (\`GestureDetector\`), animation controllers, and base presentation widgets (\`CustomPaint\`, \`Image\`, \`RichText\`). ### When to use \`widgets\` \* \*\*Building a custom design system:\*\* If your application uses a bespoke visual language that doesn't strictly follow Material Design or Apple Human Interface Guidelines, build on top of widgets rather than overriding opinionated design system defaults. \* \*\*Creating design-agnostic packages:\*\* If you publish a reusable component or utility package on pub.dev, importing \`package:flutter/widgets.dart\` ensures your package can be used in both Material and Cupertino applications without introducing unnecessary dependencies. \* \*\*Embedded and non-standard devices:\*\* For automotive, kiosks, or embedded displays where platform conventions don't apply and minimal bundle size is critical. ## The \`material\_ui\` package The \`material\_ui\` package (\`package:material\_ui/material\_ui.dart\`) implements Google's Material 3 design system. It layers visual styling, dynamic color, elevation, typography, and interaction feedback (such as ripples) on top of the base widgets. ### When to use \`material\_ui\` \* \*\*Building Android or cross-platform apps:\*\* When you want a modern, cohesive look across Android, web, and desktop with out-of-the-box accessibility, transitions, and theming. \* \*\*Rapid prototyping and feature development:\*\* Material 3 supplies pre-composed screen scaffolding (\`Scaffold\`, \`AppBar\`, \`NavigationBar\`) and interactive controls (\`ElevatedButton,\` \`FloatingActionButton,\` \`TextField,\` \`Dialog)\` so you don't need to assemble them from scratch. \* \*\*Leveraging Material theming:\*\* When you want to take advantage of \`ColorScheme.fromSeed\`, light and dark mode switching, and dynamic color extraction on Android. ## The \`cupertino\_ui\` package The \`cupertino\_ui\` package (\`package:cupertino\_ui/cupertino\_ui.dart\`) implements Apple's Human Interface Guidelines. It recreates iOS-specific visual styles, translucent blurs, haptic touches, and navigation gestures. ### When to use \`cupertino\_ui\` \* \*\*Targeting iOS and macOS native aesthetics:\*\* When your application's primary goal is to blend seamlessly into the Apple ecosystem with familiar iOS controls (\`CupertinoNavigationBar\`, \`CupertinoButton\`, \`CupertinoTabBar\`, \`CupertinoActionSheet\`). \* \*\*Platform-adaptive applications:\*\* When building adaptive experiences where your code renders \`material\_ui\` components on Android and \`cupertino\_ui\` components on iOS. The following table summarizes the differences between these packages: | Feature | \`widgets\` | \`material\_ui\` | \`cupertino\_ui\` | | --- | --- | --- | --- | | Source | Flutter SDK (\`flutter\`) | pub.dev | pub.dev | | Design system | None (style-neutral) | Material 3 | Apple HIG (iOS) 3 | | Application root | \`WidgetsApp\` | \`MaterialApp\` | \`CupertinoApp\` | | Scaffolding & layout | Raw layout (\`Stack\`, \`Column\`) | \`Scaffold\`, \`AppBar\` | \`CupertinoPageScaffold\` | | Primary use case | Custom design systems, reusable packages | Android & cross-platform apps | iOS-first & adaptive apps | {:.table .table-striped} ## Hello world The minimal Flutter app simply calls the \[\`runApp()\`]\[] function with a widget:

<!--?code-excerpt "lib/main.dart"?-->

\`\`\`dartpad title="Flutter Hello World hands-on example in DartPad" run="true" import 'package:flutter/material.dart'; void main() { runApp( const Center( child: Text( 'Hello, world!', textDirection: TextDirection.ltr, style: TextStyle(color: Colors.blue), ), ), ); } \`\`\` The \`runApp()\` function takes the given \[\`Widget\`]\[] and makes it the root of the widget tree. In this example, the widget tree consists of two widgets, the \[\`Center\`]\[] widget and its child, the \[\`Text\`]\[] widget. The framework forces the root widget to cover the screen, which means the text "Hello, world" ends up centered on screen. The text direction needs to be specified in this instance; when the \`MaterialApp\` widget is used, this is taken care of for you, as demonstrated later. When writing an app, you'll commonly author new widgets that are subclasses of either \[\`StatelessWidget\`]\[] or \[\`StatefulWidget\`]\[], depending on whether your widget manages any state. A widget's main job is to implement a \[\`build()\`]\[] function, which describes the widget in terms of other, lower-level widgets. The framework builds those widgets in turn until the process bottoms out in widgets that represent the underlying \[\`RenderObject\`]\[], which computes and describes the geometry of the widget. ## Basic widgets Flutter comes with a suite of powerful basic widgets, of which the following are commonly used: \*\*\[\`Text\`]\[]\*\* : The \`Text\` widget lets you create a run of styled text within your application. \*\*\[\`Row\`]\[], \[\`Column\`]\[]\*\* : These flex widgets let you create flexible layouts in both the horizontal (\`Row\`) and vertical (\`Column\`) directions. The design of these objects is based on the web's flexbox layout model. \*\*\[\`Stack\`]\[]\*\* : Instead of being linearly oriented (either horizontally or vertically), a \`Stack\` widget lets you place widgets on top of each other in paint order. You can then use the \[\`Positioned\`]\[] widget on children of a \`Stack\` to position them relative to the top, right, bottom, or left edge of the stack. Stacks are based on the web's absolute positioning layout model. \*\*\[\`Container\`]\[]\*\* : The \`Container\` widget lets you create a rectangular visual element. A container can be decorated with a \[\`BoxDecoration\`]\[], such as a background, a border, or a shadow. A \`Container\` can also have margins, padding, and constraints applied to its size. In addition, a \`Container\` can be transformed in three-dimensional space using a matrix. Below are some simple widgets that combine these and other widgets:

<!--?code-excerpt "lib/main_myappbar.dart"?-->

\`\`\`dartpad title="Flutter combining widgets hands-on example in DartPad" run="true" import 'package:flutter/material.dart'; class MyAppBar extends StatelessWidget { const MyAppBar({required this.title, super.key}); // Fields in a Widget subclass are always marked "final". final Widget title; @override Widget build(BuildContext context) { return Container( height: 56, // in logical pixels padding: const EdgeInsets.symmetric(horizontal: 8), decoration: BoxDecoration(color: Colors.blue\[500]), // Row is a horizontal, linear layout. child: Row( children: \[ const IconButton( icon: Icon(Icons.menu), tooltip: 'Navigation menu', onPressed: null, // null disables the button ), // Expanded expands its child // to fill the available space. Expanded(child: title), const IconButton( icon: Icon(Icons.search), tooltip: 'Search', onPressed: null, ), ], ), ); } } class MyScaffold extends StatelessWidget { const MyScaffold({super.key}); @override Widget build(BuildContext context) { // Material is a conceptual piece // of paper on which the UI appears. return Material( // Column is a vertical, linear layout. child: Column( children: \[ MyAppBar( title: Text( 'Example title', style: Theme.of(context) // .primaryTextTheme .titleLarge, ), ), const Expanded(child: Center(child: Text('Hello, world!'))), ], ), ); } } void main() { runApp( const MaterialApp( title: 'My app', // used by the OS task switcher home: SafeArea(child: MyScaffold()), ), ); } \`\`\` Be sure to have a \`uses-material-design: true\` entry in the \`flutter\` section of your \`pubspec.yaml\` file. It allows you to use the predefined set of \[Material icons]\[]. It's generally a good idea to include this line if you are using the Materials library. \`\`\`yaml name: my\_app flutter: uses-material-design: true \`\`\` Many Material Design widgets need to be inside of a \[\`MaterialApp\`]\[] to display properly, in order to inherit theme data. Therefore, run the application with a \`MaterialApp\`. The \`MyAppBar\` widget creates a \[\`Container\`]\[] with a height of 56 device-independent pixels with an internal padding of 8 pixels, both on the left and the right. Inside the container, \`MyAppBar\` uses a \[\`Row\`]\[] layout to organize its children. The middle child, the \`title\` widget, is marked as \[\`Expanded\`]\[], which means it expands to fill any remaining available space that hasn't been consumed by the other children. You can have multiple \`Expanded\` children and determine the ratio in which they consume the available space using the \[\`flex\`]\[] argument to \`Expanded\`. The \`MyScaffold\` widget organizes its children in a vertical column. At the top of the column it places an instance of \`MyAppBar\`, passing the app bar a \[\`Text\`]\[] widget to use as its title. Passing widgets as arguments to other widgets is a powerful technique that lets you create generic widgets that can be reused in a wide variety of ways. Finally, \`MyScaffold\` uses an \[\`Expanded\`]\[] to fill the remaining space with its body, which consists of a centered message. For more information, check out \[Layouts]\[]. ## Using Material components Flutter provides a number of widgets that help you build apps that follow Material Design. A Material app starts with the \[\`MaterialApp\`]\[] widget, which builds a number of useful widgets at the root of your app, including a \[\`Navigator\`]\[], which manages a stack of widgets identified by strings, also known as "routes". The \`Navigator\` lets you transition smoothly between screens of your application. Using the \[\`MaterialApp\`]\[] widget is entirely optional but a good practice.

<!--?code-excerpt "lib/main_tutorial.dart"?-->

\`\`\`dartpad title="Flutter Material design hands-on example in DartPad" run="true" import 'package:flutter/material.dart'; void main() { runApp(const MaterialApp(title: 'Flutter Tutorial', home: TutorialHome())); } class TutorialHome extends StatelessWidget { const TutorialHome({super.key}); @override Widget build(BuildContext context) { // Scaffold is a layout for // the major Material Components. return Scaffold( appBar: AppBar( leading: const IconButton( icon: Icon(Icons.menu), tooltip: 'Navigation menu', onPressed: null, ), title: const Text('Example title'), actions: const \[ IconButton( icon: Icon(Icons.search), tooltip: 'Search', onPressed: null, ), ], ), // body is the majority of the screen. body: const Center(child: Text('Hello, world!')), floatingActionButton: const FloatingActionButton( tooltip: 'Add', // used by assistive technologies onPressed: null, child: Icon(Icons.add), ), ); } } \`\`\` Now that the code has switched from \`MyAppBar\` and \`MyScaffold\` to the \[\`AppBar\`]\[] and \[\`Scaffold\`]\[] widgets, and from \`material.dart\`, the app is starting to look a bit more Material. For example, the app bar has a shadow and the title text inherits the correct styling automatically. A floating action button is also added. Notice that widgets are passed as arguments to other widgets. The \[\`Scaffold\`]\[] widget takes a number of different widgets as named arguments, each of which are placed in the \`Scaffold\` layout in the appropriate place. Similarly, the \[\`AppBar\`]\[] widget lets you pass in widgets for the \[\`leading\`]\[] widget, and the \[\`actions\`]\[] of the \[\`title\`]\[] widget. This pattern recurs throughout the framework and is something you might consider when designing your own widgets. For more information, check out \[Material Components widgets]\[]. ## Handling gestures Most applications include some form of user interaction with the system. The first step in building an interactive application is to detect input gestures. See how that works by creating a simple button:

<!--?code-excerpt "lib/main_mybutton.dart"?-->

\`\`\`dartpad title="Flutter button hands-on example in DartPad" run="true" import 'package:flutter/material.dart'; class MyButton extends StatelessWidget { const MyButton({super.key}); @override Widget build(BuildContext context) { return GestureDetector( onTap: () { print('MyButton was tapped!'); }, child: Container( height: 50, padding: const EdgeInsets.all(8), margin: const EdgeInsets.symmetric(horizontal: 8), decoration: BoxDecoration( borderRadius: BorderRadius.circular(5), color: Colors.lightGreen\[500], ), child: const Center(child: Text('Engage')), ), ); } } void main() { runApp( const MaterialApp( home: Scaffold(body: Center(child: MyButton())), ), ); } \`\`\` The \[\`GestureDetector\`]\[] widget doesn't have a visual representation but instead detects gestures made by the user. When the user taps the \[\`Container\`]\[], the \`GestureDetector\` calls its \[\`onTap()\`]\[] callback, in this case printing a message to the console. You can use \`GestureDetector\` to detect a variety of input gestures, including taps, drags, and scales. Many widgets use a \[\`GestureDetector\`]\[] to provide optional callbacks for other widgets. For example, the \[\`IconButton\`]\[], \[\`ElevatedButton\`]\[], and \[\`FloatingActionButton\`]\[] widgets have \[\`onPressed()\`]\[] callbacks that are triggered when the user taps the widget. For more information, check out \[Gestures in Flutter]\[]. ## Changing widgets in response to input So far, this page has used only stateless widgets. Stateless widgets receive arguments from their parent widget, which they store in \[\`final\`]\[] member variables. When a widget is asked to \[\`build()\`]\[], it uses these stored values to derive new arguments for the widgets it creates. In order to build more complex experiences—for example, to react in more interesting ways to user input—applications typically carry some state. Flutter uses \`StatefulWidgets\` to capture this idea. \`StatefulWidgets\` are special widgets that know how to generate \`State\` objects, which are then used to hold state. Consider this basic example, using the \[\`ElevatedButton\`]\[] mentioned earlier:

<!--?code-excerpt "lib/main_counter.dart"?-->

\`\`\`dartpad title="Flutter state management hands-on example in DartPad" run="true" import 'package:flutter/material.dart'; class Counter extends StatefulWidget { // This class is the configuration for the state. // It holds the values (in this case nothing) provided // by the parent and used by the build method of the // State. Fields in a Widget subclass are always marked // "final". const Counter({super.key}); @override State createState() => \_CounterState(); } class \_CounterState extends State { int \_counter = 0; void \_increment() { setState(() { // This call to setState tells the Flutter framework // that something has changed in this State, which // causes it to rerun the build method below so that // the display can reflect the updated values. If you // change \_counter without calling setState(), then // the build method won't be called again, and so // nothing would appear to happen. \_counter++; }); } @override Widget build(BuildContext context) { // This method is rerun every time setState is called, // for instance, as done by the \_increment method above. // The Flutter framework has been optimized to make // rerunning build methods fast, so that you can just // rebuild anything that needs updating rather than // having to individually changes instances of widgets. return Row( mainAxisAlignment: MainAxisAlignment.center, children: \[ ElevatedButton(onPressed: \_increment, child: const Text('Increment')), const SizedBox(width: 16), Text('Count: $\_counter'), ], ); } } void main() { runApp( const MaterialApp( home: Scaffold(body: Center(child: Counter())), ), ); } \`\`\` You might wonder why \`StatefulWidget\` and \`State\` are separate objects. In Flutter, these two types of objects have different life cycles. \`Widgets\` are temporary objects, used to construct a presentation of the application in its current state. \`State\` objects, on the other hand, are persistent between calls to \`build()\`, allowing them to remember information. The example above accepts user input and directly uses the result in its \`build()\` method. In more complex applications, different parts of the widget hierarchy might be responsible for different concerns; for example, one widget might present a complex user interface with the goal of gathering specific information, such as a date or location, while another widget might use that information to change the overall presentation. In Flutter, change notifications flow "up" the widget hierarchy by way of callbacks, while current state flows "down" to the stateless widgets that do presentation. The common parent that redirects this flow is the \`State\`. The following slightly more complex example shows how this works in practice:

<!--?code-excerpt "lib/main_counterdisplay.dart"?-->

\`\`\`dartpad title="Flutter Hello World hands-on example in DartPad" run="true" import 'package:flutter/material.dart'; class CounterDisplay extends StatelessWidget { const CounterDisplay({required this.count, super.key}); final int count; @override Widget build(BuildContext context) { return Text('Count: $count'); } } class CounterIncrementor extends StatelessWidget { const CounterIncrementor({required this.onPressed, super.key}); final VoidCallback onPressed; @override Widget build(BuildContext context) { return ElevatedButton(onPressed: onPressed, child: const Text('Increment')); } } class Counter extends StatefulWidget { const Counter({super.key}); @override State createState() => \_CounterState(); } class \_CounterState extends State { int \_counter = 0; void \_increment() { setState(() { ++\_counter; }); } @override Widget build(BuildContext context) { return Row( mainAxisAlignment: MainAxisAlignment.center, children: \[ CounterIncrementor(onPressed: \_increment), const SizedBox(width: 16), CounterDisplay(count: \_counter), ], ); } } void main() { runApp( const MaterialApp( home: Scaffold(body: Center(child: Counter())), ), ); } \`\`\` Notice the creation of two new stateless widgets, cleanly separating the concerns of \_displaying\_ the counter (\`CounterDisplay\`) and \_changing\_ the counter (\`CounterIncrementor\`). Although the net result is the same as the previous example, the separation of responsibility allows greater complexity to be encapsulated in the individual widgets, while maintaining simplicity in the parent. For more information, check out: \* \[\`StatefulWidget\`]\[] \* \[\`setState()\`]\[] ## Bringing it all together What follows is a more complete example that brings together these concepts: A hypothetical shopping application displays various products offered for sale, and maintains a shopping cart for intended purchases. Start by defining the presentation class, \`ShoppingListItem\`:

<!--?code-excerpt "lib/main_shoppingitem.dart"?-->

\`\`\`dartpad title="Flutter complete shopping list item hands-on example in DartPad" run="true" import 'package:flutter/material.dart'; class Product { const Product({required this.name}); final String name; } typedef CartChangedCallback = void Function(Product product, bool inCart); class ShoppingListItem extends StatelessWidget { ShoppingListItem({ required this.product, required this.inCart, required this.onCartChanged, }) : super(key: ObjectKey(product)); final Product product; final bool inCart; final CartChangedCallback onCartChanged; Color \_getColor(BuildContext context) { // The theme depends on the BuildContext because different // parts of the tree can have different themes. // The BuildContext indicates where the build is // taking place and therefore which theme to use. return inCart // ? Colors.black54 : Theme.of(context).primaryColor; } TextStyle? \_getTextStyle(BuildContext context) { if (!inCart) return null; return const TextStyle( color: Colors.black54, decoration: TextDecoration.lineThrough, ); } @override Widget build(BuildContext context) { return ListTile( onTap: () { onCartChanged(product, inCart); }, leading: CircleAvatar( backgroundColor: \_getColor(context), child: Text(product.name\[0]), ), title: Text(product.name, style: \_getTextStyle(context)), ); } } void main() { runApp( MaterialApp( home: Scaffold( body: Center( child: ShoppingListItem( product: const Product(name: 'Chips'), inCart: true, onCartChanged: (product, inCart) {}, ), ), ), ), ); } \`\`\` The \`ShoppingListItem\` widget follows a common pattern for stateless widgets. It stores the values it receives in its constructor in \[\`final\`]\[] member variables, which it then uses during its \[\`build()\`]\[] function. For example, the \`inCart\` boolean toggles between two visual appearances: one that uses the primary color from the current theme, and another that uses gray. When the user taps the list item, the widget doesn't modify its \`inCart\` value directly. Instead, the widget calls the \`onCartChanged\` function it received from its parent widget. This pattern lets you store state higher in the widget hierarchy, which causes the state to persist for longer periods of time. In the extreme, the state stored on the widget passed to \[\`runApp()\`]\[] persists for the lifetime of the application. When the parent receives the \`onCartChanged\` callback, the parent updates its internal state, which triggers the parent to rebuild and create a new instance of \`ShoppingListItem\` with the new \`inCart\` value. Although the parent creates a new instance of \`ShoppingListItem\` when it rebuilds, that operation is cheap because the framework compares the newly built widgets with the previously built widgets and applies only the differences to the underlying \[\`RenderObject\`]\[]. Here's an example parent widget that stores mutable state:

<!--?code-excerpt "lib/main_shoppinglist.dart"?-->

\`\`\`dartpad title="Flutter storing mutable state hands-on example in DartPad" run="true" import 'package:flutter/material.dart'; class Product { const Product({required this.name}); final String name; } typedef CartChangedCallback = void Function(Product product, bool inCart); class ShoppingListItem extends StatelessWidget { ShoppingListItem({ required this.product, required this.inCart, required this.onCartChanged, }) : super(key: ObjectKey(product)); final Product product; final bool inCart; final CartChangedCallback onCartChanged; Color \_getColor(BuildContext context) { // The theme depends on the BuildContext because different // parts of the tree can have different themes. // The BuildContext indicates where the build is // taking place and therefore which theme to use. return inCart // ? Colors.black54 : Theme.of(context).primaryColor; } TextStyle? \_getTextStyle(BuildContext context) { if (!inCart) return null; return const TextStyle( color: Colors.black54, decoration: TextDecoration.lineThrough, ); } @override Widget build(BuildContext context) { return ListTile( onTap: () { onCartChanged(product, inCart); }, leading: CircleAvatar( backgroundColor: \_getColor(context), child: Text(product.name\[0]), ), title: Text(product.name, style: \_getTextStyle(context)), ); } } class ShoppingList extends StatefulWidget { const ShoppingList({required this.products, super.key}); final List products; // The framework calls createState the first time // a widget appears at a given location in the tree. // If the parent rebuilds and uses the same type of // widget (with the same key), the framework re-uses // the State object instead of creating a new State object. @override State createState() => \_ShoppingListState(); } class \_ShoppingListState extends State { final \_shoppingCart = {}; void \_handleCartChanged(Product product, bool inCart) { setState(() { // When a user changes what's in the cart, you need // to change \_shoppingCart inside a setState call to // trigger a rebuild. // The framework then calls build, below, // which updates the visual appearance of the app. if (!inCart) { \_shoppingCart.add(product); } else { \_shoppingCart.remove(product); } }); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('Shopping List')), body: ListView( padding: const EdgeInsets.symmetric(vertical: 8), children: widget.products.map((product) { return ShoppingListItem( product: product, inCart: \_shoppingCart.contains(product), onCartChanged: \_handleCartChanged, ); }).toList(), ), ); } } void main() { runApp( const MaterialApp( title: 'Shopping App', home: ShoppingList( products: \[ Product(name: 'Eggs'), Product(name: 'Flour'), Product(name: 'Chocolate chips'), ], ), ), ); } \`\`\` The \`ShoppingList\` class extends \[\`StatefulWidget\`]\[], which means this widget stores mutable state. When the \`ShoppingList\` widget is first inserted into the tree, the framework calls the \[\`createState()\`]\[] function to create a fresh instance of \`\_ShoppingListState\` to associate with that location in the tree. (Notice that subclasses of \[\`State\`]\[] are typically named with leading underscores to indicate that they are private implementation details.) When this widget's parent rebuilds, the parent creates a new instance of \`ShoppingList\`, but the framework reuses the \`\_ShoppingListState\` instance that is already in the tree rather than calling \`createState\` again. To access properties of the current \`ShoppingList\`, the \`\_ShoppingListState\` can use its \[\`widget\`]\[] property. If the parent rebuilds and creates a new \`ShoppingList\`, the \`\_ShoppingListState\` rebuilds with the new widget value. If you wish to be notified when the \`widget\` property changes, override the \[\`didUpdateWidget()\`]\[] function, which is passed an \`oldWidget\` to let you compare the old widget with the current widget. When handling the \`onCartChanged\` callback, the \`\_ShoppingListState\` mutates its internal state by either adding or removing a product from \`\_shoppingCart\`. To signal to the framework that it changed its internal state, it wraps those calls in a \[\`setState()\`]\[] call. Calling \`setState\` marks this widget as dirty and schedules it to be rebuilt the next time your app needs to update the screen. If you forget to call \`setState\` when modifying the internal state of a widget, the framework won't know your widget is dirty and might not call the widget's \[\`build()\`]\[] function, which means the user interface might not update to reflect the changed state. By managing state in this way, you don't need to write separate code for creating and updating child widgets. Instead, you simply implement the \`build\` function, which handles both situations. ## Responding to widget lifecycle events After calling \[\`createState()\`]\[] on the \`StatefulWidget\`, the framework inserts the new state object into the tree and then calls \[\`initState()\`]\[] on the state object. A subclass of \[\`State\`]\[] can override \`initState\` to do work that needs to happen just once. For example, override \`initState\` to configure animations or to subscribe to platform services. Implementations of \`initState\` are required to start by calling \`super.initState\`. When a state object is no longer needed, the framework calls \[\`dispose()\`]\[] on the state object. Override the \`dispose\` function to do cleanup work. For example, override \`dispose\` to cancel timers or to unsubscribe from platform services. Implementations of \`dispose\` typically end by calling \`super.dispose\`. For more information, check out \[\`State\`]\[]. ## Keys Use keys to control which widgets the framework matches up with other widgets when a widget rebuilds. By default, the framework matches widgets in the current and previous build according to their \[\`runtimeType\`]\[] and the order in which they appear. With keys, the framework requires that the two widgets have the same \[\`key\`]\[] as well as the same \`runtimeType\`. Keys are most useful in widgets that build many instances of the same type of widget. For example, the \`ShoppingList\` widget, which builds just enough \`ShoppingListItem\` instances to fill its visible region: \* Without keys, the first entry in the current build would always sync with the first entry in the previous build, even if, semantically, the first entry in the list just scrolled off screen and is no longer visible in the viewport. \* By assigning each entry in the list a "semantic" key, the infinite list can be more efficient because the framework syncs entries with matching semantic keys and therefore similar (or identical) visual appearances. Moreover, syncing the entries semantically means that state retained in stateful child widgets remains attached to the same semantic entry rather than the entry in the same numerical position in the viewport. For more information, check out the \[\`Key\`]\[] API. ## Global keys Use global keys to uniquely identify child widgets. Global keys must be globally unique across the entire widget hierarchy, unlike local keys which need only be unique among siblings. Because they are globally unique, a global key can be used to retrieve the state associated with a widget. For more information, check out the \[\`GlobalKey\`]\[] API. \[\`actions\`]: https\://pub.dev/documentation/material\_ui/latest/material\_ui/AppBar-class.html#actions \[adding interactivity to your Flutter app]: /ui/interactivity \[\`AppBar\`]: https\://pub.dev/documentation/material\_ui/latest/material\_ui/AppBar-class.html \[\`BoxDecoration\`]: https\://api.flutter.dev/flutter/painting/BoxDecoration-class.html \[\`build()\`]: https\://api.flutter.dev/flutter/widgets/StatelessWidget/build.html \[building layouts]: /ui/layout \[\`Center\`]: https\://api.flutter.dev/flutter/widgets/Center-class.html \[\`Column\`]: https\://api.flutter.dev/flutter/widgets/Column-class.html \[\`Container\`]: https\://api.flutter.dev/flutter/widgets/Container-class.html \[\`createState()\`]: https\://api.flutter.dev/flutter/widgets/StatefulWidget-class.html#createState \[Cupertino components]: /ui/widgets/cupertino \[\`CupertinoApp\`]: https\://pub.dev/documentation/cupertino\_ui/latest/cupertino\_ui/CupertinoApp-class.html \[\`CupertinoNavigationBar\`]: https\://pub.dev/documentation/cupertino\_ui/latest/cupertino\_ui/CupertinoNavigationBar-class.html \[\`didUpdateWidget()\`]: https\://api.flutter.dev/flutter/widgets/State-class.html#didUpdateWidget \[\`dispose()\`]: https\://api.flutter.dev/flutter/widgets/State-class.html#dispose \[\`Expanded\`]: https\://api.flutter.dev/flutter/widgets/Expanded-class.html \[\`final\`]: https\://dart.dev/language/variables#final-and-const \[\`flex\`]: https\://api.flutter.dev/flutter/widgets/Expanded-class.html#flex \[\`FloatingActionButton\`]: https\://pub.dev/documentation/material\_ui/latest/material\_ui/FloatingActionButton-class.html \[Gestures in Flutter]: /ui/interactivity/gestures \[\`GestureDetector\`]: https\://api.flutter.dev/flutter/widgets/GestureDetector-class.html \[\`GlobalKey\`]: https\://api.flutter.dev/flutter/widgets/GlobalKey-class.html \[\`IconButton\`]: https\://pub.dev/documentation/material\_ui/latest/material\_ui/IconButton-class.html \[\`initState()\`]: https\://api.flutter.dev/flutter/widgets/State-class.html#initState \[\`key\`]: https\://api.flutter.dev/flutter/widgets/Widget-class.html#key \[\`Key\`]: https\://api.flutter.dev/flutter/foundation/Key-class.html \[Layouts]: /ui/widgets/layout \[\`leading\`]: https\://pub.dev/documentation/material\_ui/latest/material\_ui/AppBar-class.html#leading \[Material Components widgets]: /ui/widgets/material \[Material icons]: https\://design.google.com/icons/ \[\`MaterialApp\`]: https\://pub.dev/documentation/material\_ui/latest/material\_ui/MaterialApp-class.html \[\`Navigator\`]: https\://api.flutter.dev/flutter/widgets/Navigator-class.html \[\`onPressed()\`]: https\://pub.dev/documentation/material\_ui/latest/material\_ui/ElevatedButton-class.html#onPressed \[\`onTap()\`]: https\://api.flutter.dev/flutter/widgets/GestureDetector-class.html#onTap \[\`Positioned\`]: https\://api.flutter.dev/flutter/widgets/Positioned-class.html \[\`ElevatedButton\`]: https\://pub.dev/documentation/material\_ui/latest/material\_ui/ElevatedButton-class.html \[React]: https\://react.dev \[\`RenderObject\`]: https\://api.flutter.dev/flutter/rendering/RenderObject-class.html \[\`Row\`]: https\://api.flutter.dev/flutter/widgets/Row-class.html \[\`runApp()\`]: https\://api.flutter.dev/flutter/widgets/runApp.html \[\`runtimeType\`]: https\://api.flutter.dev/flutter/widgets/Widget-class.html#runtimeType \[\`Scaffold\`]: https\://pub.dev/documentation/material\_ui/latest/material\_ui/Scaffold-class.html \[\`setState()\`]: https\://api.flutter.dev/flutter/widgets/State/setState.html \[\`Stack\`]: https\://api.flutter.dev/flutter/widgets/Stack-class.html \[\`State\`]: https\://api.flutter.dev/flutter/widgets/State-class.html \[\`StatefulWidget\`]: https\://api.flutter.dev/flutter/widgets/StatefulWidget-class.html \[\`StatelessWidget\`]: https\://api.flutter.dev/flutter/widgets/StatelessWidget-class.html \[\`Text\`]: https\://api.flutter.dev/flutter/widgets/Text-class.html \[\`title\`]: https\://pub.dev/documentation/material\_ui/latest/material\_ui/AppBar-class.html#title \[\`widget\`]: https\://api.flutter.dev/flutter/widgets/State-class.html#widget \[\`Widget\`]: https\://api.flutter.dev/flutter/widgets/Widget-class.html

----
url: https://docs.flutter.dev/dash.md
----

\# Who is Dash? > Learn more about the Flutter and Dart mascot, Dash. This is Dash: !\[Dash by herself]\(/assets/images/dash/Dash.png){:width="50%"}\
Dash is the mascot for the Dart language and the Flutter framework. :::note You can now make your \*\*own\*\* digital Dash using \[Dashatar]\[], a Flutter app for web and mobile! ::: !\[A sample of Dashatars]\(/assets/images/dash/Dashatars.png){:width="100%"}\
!\[GIF of Dash fainting created for Flutter Engage]\(/assets/images/dash/dash-fainting.webp) \[Dashatar]: https\://dashatar-dev.web.app/#/ ## How did it all start? As soon as Shams Zakhour started working as a Dart writer at Google in December 2013, she started advocating for a Dart mascot. After documenting Java for 14 years, she had observed how beloved the Java mascot, \[Duke]\[], had become, and she wanted something similar for Dart. \[Duke]: https\://www\.oracle.com/java/duke.html But the idea didn't gain momentum until 2017, when one of the Flutter engineers, Nina Chen, suggested it on an internal mailing list. The Flutter VP at the time, Joshy Joseph, approved the idea and asked the organizer for the \[2018 Dart Conference]\[], Linda Rasmussen, to make it happen. \[2018 Dart Conference]: https\://events.dartlang.org/2018/dartconf/ Once Shams heard about these plans, she rushed to Linda and asked to own and drive the project to produce the plushies for the conference. Linda had already elicited some design sketches, which she handed off. Starting with the sketches, Shams located a vendor who could work within an aggressive deadline (competing with Lunar New Year), and started the process of creating the specs for the plushy. That's right, Dash was originally a \*\*Dart\*\* mascot, not a Flutter mascot. Here are some early mockups and one of the first prototypes: !\[1st mockup made by Novicell.dk]\(/assets/images/dash/early-dash-sketches.png){:width="35%"} !\[2nd mockup by Squishable.com]\(/assets/images/dash/early-dash-sketches2.jpg){:width="35%"}\
!\[Prototype 1]\(/assets/images/dash/early-dash-sketches3.jpg){:width="35%"} !\[Playing with embroidery ideas]\(/assets/images/dash/early-dash-sketches4.jpg){:width="35%"}\
!\[First prototype]\(/assets/images/dash/early-dash-sketches5.jpg){:width="50%"}\
The first prototype had uneven eyes ## Why a hummingbird? Early on, a hummingbird image was created for the Dart team to use for presentations and the web. The hummingbird represents that Dart is a speedy language. !\[Early hummingbird drawing]\(/assets/images/dash/DartHummingbird.jpg){:width="35%"}\
However, hummingbirds are pointed and angular and we wanted a cuddly plushy, so we chose a round hummingbird. Shams specified which color would go where, the tail shape, the tuft of hair, the eyes...all the little details. The vendor sent the specs to two manufacturers who returned the prototypes some weeks later. !\[The first Dash prototypes]\(/assets/images/dash/dash-prototypes.jpg){:width="35%"} !\[The first Dash prototypes]\(/assets/images/dash/dash-prototypes2.jpg){:width="35%"}\
While the manufacturing process was proceeding, Shams chose a name for the plushy: Dash, because it was an early code name for the Dart project, it was gender neutral, and it seemed appropriate for a hummingbird. Many boxes of Dash plushies arrived in southern California just in time for the conference. They were eagerly adopted by Dart \_and\_ Flutter enthusiasts. The people have spoken, so Dash is now the mascot for Flutter \*\*and\*\* Dart. !\[Dash 1.0]\(/assets/images/dash/dash-1.0.jpg){:width="35%"}\
Dash 1.0 !\[Piles of Dashes awaiting conference goers]\(/assets/images/dash/dash-conference-swag.jpg){:width="50%"}\
Conference swag Since the creation of Dash 1.0, we've made two more versions. Marketing slightly changed the Dart and Flutter color scheme after Dash 1.0 was created, so Dash 2.0 reflects the updated scheme (which removed the green color). Dash 2.1 is a smaller size and has a few more color tweaks. The smaller size is easier to ship, and fits better in a claw machine! !\[Dash 2.0 and 2.1]\(/assets/images/dash/BigDashAndLittleDash.png){:width="50%"}\
Dash 2.0 and 2.1 !\[Flutter Interact claw machine]\(/assets/images/dash/DashClawMachine.png){:width="50%"}\
\## Dash facts \* Dash is female, but she doesn't mind being called \_they\_, \_their\_, \_he\_, or \_him\_. \* Dash has an \[Instagram account]\[]. \* Dash has a \*\*straight\*\* beak. \*\*Please, don't depict Dash with a curved beak.\*\* \* We also have Mega-Dash, a life-sized mascot who is currently resting in a Google office. !\[Mega-Dash in the office]\(/assets/images/dash/MegaDashChilling.png){:width="50%"} Mega-Dash made her first appearance at the \[Flutter Interact]\[] event in Brooklyn, New York, on December 11, 2019. \* We also have a Dash puppet that Shams made from one of the first plushies. !\[Nilay and the Dash puppet]\(/assets/images/dash/NilayDashPuppet.png){:width="50%"} A number of our YouTube videos feature the Dash puppet, voiced by Emily Fortuna, one of the early (and much loved) Flutter Developer Advocates. !\[Born to Hot Reload jacket]\(/assets/images/dash/ShamsDashJacket.png){:width="35%"} \[Flutter Interact]: https\://www\.youtube.com/playlist?list=PLjxrf2q8roU0o0wKRJTjyN0pSUA6TI8lg \[Instagram account]: https\://www\.instagram.com/dash\_the\_dartlang\_plushy/

----
url: https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.30.0
----

The 2.30.0 release of the Dart and Flutter DevTools includes the following changes among other general improvements. To learn more about DevTools, check out the [DevTools overview](https://docs.flutter.dev/tools/devtools).

## Performance updates

[#](#performance-updates)

* Add an indicator of the rendering engine to the Flutter Frames chart. - [#6771](https://github.com/flutter/devtools/pull/6771)

* Improve messaging when we do not have analysis data available for a Flutter frame. - [#6768](https://github.com/flutter/devtools/pull/6768)

## VS Code Sidebar updates

[#](#vs-code-sidebar-updates)

* The Flutter Sidebar provided to VS Code now has the ability to enable new platforms if a device is available for a platform that is not enabled for the current project. This also requires a corresponding Dart extension for VS Code update to appear. - [#6688](https://github.com/flutter/devtools/pull/6688)

* The DevTools menu in the sidebar now has an entry "Open in Browser" that opens DevTools in an external browser window even when VS Code settings are set to usually use embedded DevTools. - [#6736](https://github.com/flutter/devtools/pull/6736)

## Full commit history

[#](#full-commit-history)

To find a complete list of changes in this release, check out the [DevTools git log](https://github.com/flutter/devtools/tree/v2.30.0).

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/release-notes/release-notes-2.30.0.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.30.0\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/release-notes/release-notes-2.30.0.md "Report an issue with this page").

----
url: https://docs.flutter.dev/cookbook/games
----

[Add multiplayer support using Firestore](/cookbook/games/firestore-multiplayer)

[How to use use Firebase Cloud Firestore to implement multiplayer in your game.](/cookbook/games/firestore-multiplayer)

[Add achievements and leaderboards to your mobile game](/cookbook/games/achievements-leaderboard)

[How to use the games\_services plugin to add functionality to your game.](/cookbook/games/achievements-leaderboard)

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/games/index.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/cookbook/games\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/games/index.md "Report an issue with this page").

----
url: https://docs.flutter.dev/app-architecture/case-study/data-layer
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Data layer](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

The data layer of an application, known as the *model* in MVVM terminology, is the source of truth for all application data. As the source of truth, it's the only place that application data should be updated.

It's responsible for consuming data from various external APIs, exposing that data to the UI, handling events from the UI that require data to be updated, and sending update requests to those external APIs as needed.

The data layer in this guide has two main components, [repositories](/app-architecture/guide#repositories) and [services](/app-architecture/guide#services).

* **Repositories** are the source of the truth for application data, and contain logic that relates to that data, like updating the data in response to new user events or polling for data from services. Repositories are responsible for synchronizing the data when offline capabilities are supported, managing retry logic, and caching data.
* **Services** are stateless Dart classes that interact with APIs, like HTTP servers and platform plugins. Any data that your application needs that isn't created inside the application code itself should be fetched from within service classes.

## Define a service

[#](#define-a-service)

A service class is the least ambiguous of all the architecture components. It's stateless, and its functions don't have side effects. Its only job is to wrap an external API. There's generally one service class per data source, such as a client HTTP server or a platform plugin.

In the Compass app, for example, there's an [`APIClient`](https://github.com/flutter/samples/blob/main/compass_app/app/lib/data/services/api/api_client.dart) service that handles the CRUD calls to the client-facing server.

api\_client.dart

dart

```
class ApiClient {
  // Some code omitted for demo purposes.
​
  Future<Result<List<ContinentApiModel>>> getContinents() async { /* ... */ }
​
  Future<Result<List<DestinationApiModel>>> getDestinations() async { /* ... */ }
​
  Future<Result<List<ActivityApiModel>>> getActivityByDestination(String ref) async { /* ... */ }
​
  Future<Result<List<BookingApiModel>>> getBookings() async { /* ... */ }
​
  Future<Result<BookingApiModel>> getBooking(int id) async { /* ... */ }
​
  Future<Result<BookingApiModel>> postBooking(BookingApiModel booking) async { /* ... */ }
​
  Future<Result<void>> deleteBooking(int id) async { /* ... */ }
​
  Future<Result<UserApiModel>> getUser() async { /* ... */ }
}
```

The service itself is a class, where each method wraps a different API endpoint and exposes asynchronous response objects. Continuing the earlier example of deleting a saved booking, the `deleteBooking` method returns a `Future<Result<void>>`.

## Define a repository

[#](#define-a-repository)

A repository's sole responsibility is to manage application data. A repository is the source of truth for a single type of application data, and it should be the only place where that data type is mutated. The repository is responsible for polling new data from external sources, handling retry logic, managing cached data, and transforming raw data into domain models.

You should have a separate repository for each different type of data in your application. For example, the Compass app has repositories called `UserRepository`, `BookingRepository`, `AuthRepository`, `DestinationRepository`, and more.

The following example is the `BookingRepository` from the Compass app, and shows the basic structure of a repository.

booking\_repository\_remote.dart

dart

```
class BookingRepositoryRemote implements BookingRepository {
  BookingRepositoryRemote({
    required ApiClient apiClient,
  }) : _apiClient = apiClient;
​
  final ApiClient _apiClient;
  List<Destination>? _cachedDestinations;
​
  Future<Result<void>> createBooking(Booking booking) async {...}
  Future<Result<Booking>> getBooking(int id) async {...}
  Future<Result<List<BookingSummary>>> getBookingsList() async {...}
  Future<Result<void>> delete(int id) async {...}
}
```

The `BookingRepository` takes the `ApiClient` service as an input, which it uses to get and update the raw data from the server. It's important that the service is a private member, so that the UI layer can't bypass the repository and call a service directly.

With the `ApiClient` service, the repository can poll for updates to a user's saved bookings that might happen on the server, and make `POST` requests to delete saved bookings.

The raw data that a repository transforms into application models can come from multiple sources and multiple services, and therefore repositories and services have a many-to-many relationship. A service can be used by any number of repositories, and a repository can use more than one service.

### Domain models

[#](#domain-models)

The `BookingRepository` outputs `Booking` and `BookingSummary` objects, which are *domain models*. All repositories output corresponding domain models. These data models differ from API models in that they only contain the data needed by the rest of the app. API models contain raw data that often needs to be filtered, combined, or deleted to be useful to the app's view models. The repo refines the raw data and outputs it as domain models.

In the example app, domain models are exposed through return values on methods like `BookingRepository.getBooking`. The `getBooking` method is responsible for getting the raw data from the `ApiClient` service, and transforming it into a `Booking` object. It does this by combining data from multiple service endpoints.

booking\_repository\_remote.dart

dart

```
// This method was edited for brevity.
Future<Result<Booking>> getBooking(int id) async {
  try {
    // Get the booking by ID from server.
    final resultBooking = await _apiClient.getBooking(id);
    if (resultBooking is Error<BookingApiModel>) {
      return Result.error(resultBooking.error);
    }
    final booking = resultBooking.asOk.value;
​
    final destination = _apiClient.getDestination(booking.destinationRef);
    final activities = _apiClient.getActivitiesForBooking(
            booking.activitiesRef);
​
    return Result.ok(
      Booking(
        startDate: booking.startDate,
        endDate: booking.endDate,
        destination: destination,
        activity: activities,
      ),
    );
  } on Exception catch (e) {
    return Result.error(e);
  }
}
```

### Complete the event cycle

[#](#complete-the-event-cycle)

Throughout this page, you've seen how a user can delete a saved booking, starting with an event—a user swiping on a `Dismissible` widget. The view model handles that event by delegating the actual data mutation to the `BookingRepository`. The following snippet shows the `BookingRepository.deleteBooking` method.

booking\_repository\_remote.dart

dart

```
Future<Result<void>> delete(int id) async {
  try {
    return _apiClient.deleteBooking(id);
  } on Exception catch (e) {
    return Result.error(e);
  }
}
```

The repository sends a `POST` request to the API client with the `_apiClient.deleteBooking` method, and returns a `Result`. The `HomeViewModel` consumes the `Result` and the data it contains, then ultimately calls `notifyListeners`, completing the cycle.

## Feedback

[#](#feedback)

As this section of the website is evolving, we [welcome your feedback](https://google.qualtrics.com/jfe/form/SV_4T0XuR9Ts29acw6?page=%22case-study/data-layer%22)!

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/app-architecture/case-study/data-layer.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/app-architecture/case-study/data-layer\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/app-architecture/case-study/data-layer.md "Report an issue with this page").

----
url: https://docs.flutter.dev/cookbook/effects/staggered-menu-animation.md
----

\# Create a staggered menu animation > How to implement a staggered menu animation.

<!--?code-excerpt path-base="cookbook/effects/staggered_menu_animation"?-->

A single app screen might contain multiple animations. Playing all of the animations at the same time can be overwhelming. Playing the animations one after the other can take too long. A better option is to stagger the animations. Each animation begins at a different time, but the animations overlap to create a shorter duration. In this recipe, you build a drawer menu with animated content that is staggered and has a button that pops in at the bottom. The following animation shows the app's behavior: !\[Staggered Menu Animation Example]\(/assets/images/docs/cookbook/effects/StaggeredMenuAnimation.webp){:.site-mobile-screenshot} ## Create the menu without animations The drawer menu displays a list of titles, followed by a Get started button at the bottom of the menu. Define a stateful widget called \`Menu\` that displays the list and button in static locations.

<!--?code-excerpt "lib/step1.dart (step1)"?-->

\`\`\`dart class Menu extends StatefulWidget { const Menu({super.key}); @override State

----
url: https://docs.flutter.dev/ui/adaptive-responsive/capabilities.md
----

\# Capabilities & policies > Learn how to adapt your app to the capabilities and policies required by the platform, app store, your company, and so on. Most real-world apps have the need to adapt to the capabilities and policies of different devices and platforms. This page contains advice for how to handle these scenarios in your code. ## Design to the strengths of each device type Consider the unique strengths and weaknesses of different devices. Beyond their screen size and inputs, such as touch, mouse, keyboard, what other unique capabilities can you leverage? Flutter enables your code to \_run\_ on different devices, but strong design is more than just running code. Think about what each platform does best and see if there are unique capabilities to leverage. For example: Apple's App Store and Google's Play Store have different rules that apps need to abide by. Different host operating systems have differing capabilities across time as well as each other. Another example is leveraging the web's extremely low barrier for sharing. If you're deploying a web app, decide what deep links to support, and design the navigation routes with those in mind. Flutter's recommended pattern for handling different behavior based on these unique capabilities is to create a set of \`Capability\` and \`Policy\` classes for your app. ### Capabilities A \_capability\_ defines what the code or device \_can\_ do. Examples of capabilities include: \* The existence of an API \* OS-enforced restrictions \* Physical hardware requirements (like a camera) ### Policies A \_policy\_ defines what the code \_should\_ do. Examples of policies include: \* App store guidelines \* Design preferences \* Assets or copy that refers to the host device \* Features enabled on the server side ### How to structure policy code The simplest mechanical way is \`Platform.isAndroid\`, \`Platform.isIOS\`, and \`kIsWeb\`. These APIs mechanically let you know where the code is running but have some problems as the app expands where it can run, and as host platforms add functionality. The following guidelines explain best practices when developing the capabilities and policies for your app: \*\*Avoid using \`Platform.isAndroid\` and similar functions to make layout decisions or assumptions about what a device can do.\*\* Instead, describe what you want to branch on in a method. Example: Your app has a link to buy something in a website, but you don't want to show that link on iOS devices for policy reasons. \`\`\`dart bool shouldAllowPurchaseClick() { // Banned by Apple App Store guidelines. return !Platform.isIOS; } ... TextSpan( text: 'Buy in browser', style: new TextStyle(color: Colors.blue), recognizer: shouldAllowPurchaseClick ? TapGestureRecognizer() ..onTap = () { launch('') : null; } : null, \`\`\` What did you get by adding an additional layer of indirection? The code makes it more clear why the branched path exists. This method can exist directly in the class but it's likely that other parts of the code might need this same check. If so, put the code in a class. \`\`\`dart title="policy.dart" class Policy { bool shouldAllowPurchaseClick() { // Banned by Apple App Store guidelines. return !Platform.isIOS; } } \`\`\` With this code in a class, any widget test can mock \`Policy().shouldAllowPurchaseClick\` and verify the behavior independently of where the device runs. It also means that later, if you decide that buying on the web isn't the right flow for Android users, you can change the implementation and the tests for clickable text won't need to change. ## Capabilities Sometimes you want your code to do something but the API doesn't exist, or maybe you depend on a plugin feature that isn't yet implemented on all of the platforms you support. This is a limitation of what the device \_can\_ do. Those situations are similar to the policy decisions described above, but these are referred to as \_capabilities\_. Why separate policy classes from capabilities when the structure of the classes is similar? The Flutter team has found with productionized apps that making a logical distinction between what apps \_can\_ do and what they \_should\_ do helps larger products respond to changes in what platforms can do or require in addition to your own preferences after the initial code is written. For example, consider the case where one platform adds a new permission that requires users to interact with a system dialog before your code calls a sensitive API. Your team does the work for platform 1 and creates a capability named \`requirePermissionDialogFlow\`. Then, if and when platform 2 adds a similar requirement but only for new API versions, then the implementation of \`requirePermissionDialogFlow\` can now check the API level and return true for platform 2. You've leveraged the work you already did. ## Policies We encourage starting with a \`Policy\` class initially even if it seems like you won't make many policy based decisions. As the complexity of the class grows or the number of inputs expands, you might decide to break up the policy class by feature or some other criteria. For policy implementation, you can use compile time, run time, or Remote Procedure Call (RPC) backed implementations. Compile-time policy checks are good for platforms where the preference is unlikely to change and where accidentally changing the value might have large consequences. For example, if a platform requires that you not link to the Play store, or requires that you use a specific payment provider given the content of your app. Runtime checks can be good for determining if there is a touch screen the user can use. Android has a feature you can check and your web implementation could check for max touch points. RPC-backed policy changes are good for incremental feature rollout or for decisions that might change later. ## Summary Use a \`Capability\` class to define what the code \*can\* do. You might check against the existence of an API, OS-enforced restrictions, and physical hardware requirements (like a camera). A capability usually involves compile or runtime checks. Use a \`Policy\` class (or classes depending on complexity) to define what the code \_should\_ do to comply with App store guidelines, design preferences, and assets or copy that need to refer to the host device. Policies can be a mix of compile, runtime, or RPC checks. Test the branching code by mocking capabilities and policies so the widget tests don't need to change when capabilities or policies change. Name the methods in your capabilities and policies classes based on what they are trying to branch, rather than on device type.

----
url: https://docs.flutter.dev/flutter-for/web-devs.md
----

\# Flutter for web developers > Learn how to apply Web developer knowledge when building Flutter apps.

<!--?code-excerpt path-base="get-started/flutter-for/web_devs"?-->

This page is for users who are familiar with the HTML and CSS syntax for arranging components of an application's UI. It maps HTML/CSS code snippets to their Flutter/Dart code equivalents. Flutter is a framework for building cross-platform applications that uses the Dart programming language. To understand some differences between programming with Dart and programming with JavaScript, see \[Learning Dart as a JavaScript Developer]\[]. One of the fundamental differences between designing a web layout and a Flutter layout, is learning how constraints work, and how widgets are sized and positioned. To learn more, see \[Understanding constraints]\[]. The examples assume: \* The HTML document starts with \`\`, and the CSS box model for all HTML elements is set to \[\`border-box\`]\[], for consistency with the Flutter model. \`\`\`css { box-sizing: border-box; } \`\`\` \* In Flutter, the default styling of the 'Lorem ipsum' text is defined by the \`bold24Roboto\` variable as follows, to keep the syntax simple:

<!--?code-excerpt "lib/main.dart (text-style)"?-->

\`\`\`dart TextStyle bold24Roboto = const TextStyle( color: Colors.white, fontSize: 24, fontWeight: FontWeight.bold, ); \`\`\` :::secondary How is react-style, or \_declarative\_, programming different from the traditional imperative style? For a comparison, see \[Introduction to declarative UI]\[]. ::: ## Performing basic layout operations The following examples show how to perform the most common UI layout tasks. ### Styling and aligning text Font style, size, and other text attributes that CSS handles with the font and color properties are individual properties of a \[\`TextStyle\`]\[] child of a \[\`Text\`]\[] widget. For text-align property in CSS that is used for aligning text, there is a textAlign property of a \[\`Text\`]\[] widget. In both HTML and Flutter, child elements or widgets are anchored at the top left, by default. \`\`\`css highlightLines=9

Lorem ipsum

.grey-box { background-color: #e0e0e0; /\* grey 300 \*/ width: 320px; height: 240px; font: 900 24px Georgia; } \`\`\` \`\`\`dart highlightLines=8-13 final container = Container( // grey box width: 320, height: 240, color: Colors.grey\[300], child: const Text( 'Lorem ipsum', style: TextStyle( fontFamily: 'Georgia', fontSize: 24, fontWeight: FontWeight.bold, ), textAlign: TextAlign.center, ), ); \`\`\` ### Setting background color In Flutter, you set the background color using the \`color\` property or the \`decoration\` property of a \[\`Container\`]\[]. However, you cannot supply both, since it would potentially result in the decoration drawing over the background color. The \`color\` property should be preferred when the background is a simple color. For other cases, such as gradients or images, use the \`decoration\` property. The CSS examples use the hex color equivalents to the Material color palette. \`\`\`css highlightLines=6

Lorem ipsum

.grey-box { background-color: #e0e0e0; /\* grey 300 \*/ width: 320px; height: 240px; font: 900 24px Roboto; } \`\`\` \`\`\`dart highlightLines=5 final container = Container( // grey box width: 320, height: 240, color: Colors.grey\[300], child: Text( 'Lorem ipsum', style: bold24Roboto, ), ); \`\`\` \`\`\`dart highlightLines=5-7 final container = Container( // grey box width: 320, height: 240, decoration: BoxDecoration( color: Colors.grey\[300], ), child: Text( 'Lorem ipsum', style: bold24Roboto, ), ); \`\`\` ### Centering components A \[\`Center\`]\[] widget centers its child both horizontally and vertically. To accomplish a similar effect in CSS, the parent element uses either a flex or table-cell display behavior. The examples on this page show the flex behavior. \`\`\`css highlightLines=10-12

Lorem ipsum

.grey-box { background-color: #e0e0e0; /\* grey 300 \*/ width: 320px; height: 240px; font: 900 24px Roboto; display: flex; align-items: center; justify-content: center; } \`\`\` \`\`\`dart highlightLines=6-7 final container = Container( // grey box width: 320, height: 240, color: Colors.grey\[300], child: Center( child: Text( 'Lorem ipsum', style: bold24Roboto, ), ), ); \`\`\` ### Setting container width To specify the width of a \[\`Container\`]\[] widget, use its \`width\` property. This is a fixed width, unlike the CSS max-width property that adjusts the container width up to a maximum value. To mimic that effect in Flutter, use the \`constraints\` property of the Container. Create a new \[\`BoxConstraints\`]\[] widget with a \`minWidth\` or \`maxWidth\`. For nested Containers, if the parent's width is less than the child's width, the child Container sizes itself to match the parent. \`\`\`css highlightLines=9,20-21

Lorem ipsum

.grey-box { background-color: #e0e0e0; /\* grey 300 \*/ width: 320px; height: 240px; font: 900 24px Roboto; display: flex; align-items: center; justify-content: center; } .red-box { background-color: #ef5350; /\* red 400 \*/ padding: 16px; color: #ffffff; width: 100%; max-width: 240px; } \`\`\` \`\`\`dart highlightLines=3,9 final container = Container( // grey box width: 320, height: 240, color: Colors.grey\[300], child: Center( child: Container( // red box width: 240, // max-width is 240 padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: Colors.red\[400], ), child: Text( 'Lorem ipsum', style: bold24Roboto, ), ), ), ); \`\`\` ## Manipulating position and size The following examples show how to perform more complex operations on widget position, size, and background. ### Setting absolute position By default, widgets are positioned relative to their parent. To specify an absolute position for a widget as x-y coordinates, nest it in a \[\`Positioned\`]\[] widget that is, in turn, nested in a \[\`Stack\`]\[] widget. \`\`\`css highlightLines=8,18-20

Lorem ipsum

.grey-box { position: relative; background-color: #e0e0e0; /\* grey 300 \*/ width: 320px; height: 240px; font: 900 24px Roboto; } .red-box { background-color: #ef5350; /\* red 400 \*/ padding: 16px; color: #ffffff; position: absolute; top: 24px; left: 24px; } \`\`\` \`\`\`dart highlightLines=6-7,10-11 final container = Container( // grey box width: 320, height: 240, color: Colors.grey\[300], child: Stack( children: \[ Positioned( // red box left: 24, top: 24, child: Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: Colors.red\[400], ), child: Text( 'Lorem ipsum', style: bold24Roboto, ), ), ), ], ), ); \`\`\` ### Rotating components To rotate a widget, nest it in a \[\`Transform\`]\[] widget. Use the \`Transform\` widget's \`alignment\` and \`origin\` properties to specify the transform origin (fulcrum) in relative and absolute terms, respectively. For a simple 2D rotation, in which the widget is rotated on the Z axis, create a new \[\`Matrix4\`]\[] identity object and use its \`rotateZ()\` method to specify the rotation factor using radians (degrees × π / 180). \`\`\`css highlightLines=20

Lorem ipsum

.grey-box { background-color: #e0e0e0; /\* grey 300 \*/ width: 320px; height: 240px; font: 900 24px Roboto; display: flex; align-items: center; justify-content: center; } .red-box { background-color: #ef5350; /\* red 400 \*/ padding: 16px; color: #ffffff; transform: rotate(15deg); } \`\`\` \`\`\`dart highlightLines=7-10, final container = Container( // grey box width: 320, height: 240, color: Colors.grey\[300], child: Center( child: Transform( alignment: Alignment.center, transform: Matrix4.identity()..rotateZ(15 \* 3.1415927 / 180), child: Container( // red box padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: Colors.red\[400], ), child: Text( 'Lorem ipsum', style: bold24Roboto, textAlign: TextAlign.center, ), ), ), ), ); \`\`\` ### Scaling components To scale a widget up or down, nest it in a \[\`Transform\`]\[] widget. Use the Transform widget's \`alignment\` and \`origin\` properties to specify the transform origin (fulcrum) in relative or absolute terms, respectively. For a simple scaling operation along the x-axis, create a new \[\`Matrix4\`]\[] identity object and use its \`scale()\` method to specify the scaling factor. When you scale a parent widget, its child widgets are scaled accordingly. \`\`\`css highlightLines=20

Lorem ipsum

.grey-box { background-color: #e0e0e0; /\* grey 300 \*/ width: 320px; height: 240px; font: 900 24px Roboto; display: flex; align-items: center; justify-content: center; } .red-box { background-color: #ef5350; /\* red 400 \*/ padding: 16px; color: #ffffff; transform: scale(1.5); } \`\`\` \`\`\`dart highlightLines=7-10 final container = Container( // grey box width: 320, height: 240, color: Colors.grey\[300], child: Center( child: Transform( alignment: Alignment.center, transform: Matrix4.identity()..scaleByDouble(1.5, 1.5, 1.5, 1.5), child: Container( // red box padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: Colors.red\[400], ), child: Text( 'Lorem ipsum', style: bold24Roboto, textAlign: TextAlign.center, ), ), ), ), ); \`\`\` ### Applying a linear gradient To apply a linear gradient to a widget's background, nest it in a \[\`Container\`]\[] widget. Then use the \`Container\` widget's \`decoration\` property to create a \[\`BoxDecoration\`]\[] object, and use \`BoxDecoration\`'s \`gradient\` property to transform the background fill. The gradient "angle" is based on the Alignment (x, y) values: \* If the beginning and ending x values are equal, the gradient is vertical (0° | 180°). \* If the beginning and ending y values are equal, the gradient is horizontal (90° | 270°). #### Vertical gradient \`\`\`css highlightLines=19

Lorem ipsum

.grey-box { background-color: #e0e0e0; /\* grey 300 \*/ width: 320px; height: 240px; font: 900 24px Roboto; display: flex; align-items: center; justify-content: center; } .red-box { padding: 16px; color: #ffffff; background: linear-gradient(180deg, #ef5350, rgba(0, 0, 0, 0) 80%); } \`\`\` \`\`\`dart highlightLines=9-18 final container = Container( // grey box width: 320, height: 240, color: Colors.grey\[300], child: Center( child: Container( // red box decoration: const BoxDecoration( gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment(0.0, 0.6), colors: \[ Color(0xffef5350), Color(0x00ef5350), ], ), ), padding: const EdgeInsets.all(16), child: Text( 'Lorem ipsum', style: bold24Roboto, ), ), ), ); \`\`\` #### Horizontal gradient \`\`\`css highlightLines=19

Lorem ipsum

.grey-box { background-color: #e0e0e0; /\* grey 300 \*/ width: 320px; height: 240px; font: 900 24px Roboto; display: flex; align-items: center; justify-content: center; } .red-box { padding: 16px; color: #ffffff; background: linear-gradient(90deg, #ef5350, rgba(0, 0, 0, 0) 80%); } \`\`\` \`\`\`dart highlightLines=10-19 final container = Container( // grey box width: 320, height: 240, color: Colors.grey\[300], child: Center( child: Container( // red box padding: const EdgeInsets.all(16), decoration: const BoxDecoration( gradient: LinearGradient( begin: Alignment(-1.0, 0.0), end: Alignment(0.6, 0.0), colors: \[ Color(0xffef5350), Color(0x00ef5350), ], ), ), child: Text( 'Lorem ipsum', style: bold24Roboto, ), ), ), ); \`\`\` ## Manipulating shapes The following examples show how to make and customize shapes. ### Rounding corners To round the corners of a rectangular shape, use the \`borderRadius\` property of a \[\`BoxDecoration\`]\[] object. Create a new \[\`BorderRadius\`]\[] object that specifies the radius for rounding each corner. \`\`\`css highlightLines=20

Lorem ipsum

.grey-box { background-color: #e0e0e0; /\* grey 300 \*/ width: 320px; height: 240px; font: 900 24px Roboto; display: flex; align-items: center; justify-content: center; } .red-box { background-color: #ef5350; /\* red 400 \*/ padding: 16px; color: #ffffff; border-radius: 8px; } \`\`\` \`\`\`dart highlightLines=12-14 final container = Container( // grey box width: 320, height: 240, color: Colors.grey\[300], child: Center( child: Container( // red circle padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: Colors.red\[400], borderRadius: const BorderRadius.all( Radius.circular(8), ), ), child: Text( 'Lorem ipsum', style: bold24Roboto, ), ), ), ); \`\`\` ### Adding box shadows In CSS you can specify shadow offset and blur in shorthand, using the box-shadow property. This example shows two box shadows, with properties: \* \`xOffset: 0px, yOffset: 2px, blur: 4px, color: black @80% alpha\` \* \`xOffset: 0px, yOffset: 06x, blur: 20px, color: black @50% alpha\` In Flutter, each property and value is specified separately. Use the \`boxShadow\` property of \`BoxDecoration\` to create a list of \[\`BoxShadow\`]\[] widgets. You can define one or multiple \`BoxShadow\` widgets, which can be stacked to customize the shadow depth, color, and so on. \`\`\`css highlightLines=20-21

Lorem ipsum

.grey-box { background-color: #e0e0e0; /\* grey 300 \*/ width: 320px; height: 240px; font: 900 24px Roboto; display: flex; align-items: center; justify-content: center; } .red-box { background-color: #ef5350; /\* red 400 \*/ padding: 16px; color: #ffffff; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.8), 0 6px 20px rgba(0, 0, 0, 0.5); } \`\`\` \`\`\`dart highlightLines=15-26 final container = Container( // grey box width: 320, height: 240, margin: const EdgeInsets.only(bottom: 16), decoration: BoxDecoration( color: Colors.grey\[300], ), child: Center( child: Container( // red box padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: Colors.red\[400], boxShadow: const \[ BoxShadow( color: Color(0xcc000000), offset: Offset(0, 2), blurRadius: 4, ), BoxShadow( color: Color(0x80000000), offset: Offset(0, 6), blurRadius: 20, ), ], ), child: Text( 'Lorem ipsum', style: bold24Roboto, ), ), ), ); \`\`\` ### Making circles and ellipses Making a circle in CSS requires a workaround of applying a border-radius of 50% to all four sides of a rectangle, though there are \[basic shapes]\[]. While this approach is supported with the \`borderRadius\` property of \[\`BoxDecoration\`]\[], Flutter provides a \`shape\` property with \[\`BoxShape\` enum]\[] for this purpose. \`\`\`css highlightLines=20-23

Lorem ipsum

.grey-box { background-color: #e0e0e0; /\* grey 300 \*/ width: 320px; height: 240px; font: 900 24px Roboto; display: flex; align-items: center; justify-content: center; } .red-circle { background-color: #ef5350; /\* red 400 \*/ padding: 16px; color: #ffffff; text-align: center; width: 160px; height: 160px; border-radius: 50%; } \`\`\` \`\`\`dart highlightLines=11,14-15,19 final container = Container( // grey box width: 320, height: 240, color: Colors.grey\[300], child: Center( child: Container( // red circle decoration: BoxDecoration( color: Colors.red\[400], shape: BoxShape.circle, ), padding: const EdgeInsets.all(16), width: 160, height: 160, child: Text( 'Lorem ipsum', style: bold24Roboto, textAlign: TextAlign.center, ), ), ), ); \`\`\` ## Manipulating text The following examples show how to specify fonts and other text attributes. They also show how to transform text strings, customize spacing, and create excerpts. ### Adjusting text spacing In CSS, you specify the amount of white space between each letter or word by giving a length value for the letter-spacing and word-spacing properties, respectively. The amount of space can be in px, pt, cm, em, etc. In Flutter, you specify white space as logical pixels (negative values are allowed) for the \`letterSpacing\` and \`wordSpacing\` properties of a \[\`TextStyle\`]\[] child of a \`Text\` widget. \`\`\`css highlightLines=20

Lorem ipsum

.grey-box { background-color: #e0e0e0; /\* grey 300 \*/ width: 320px; height: 240px; font: 900 24px Roboto; display: flex; align-items: center; justify-content: center; } .red-box { background-color: #ef5350; /\* red 400 \*/ padding: 16px; color: #ffffff; letter-spacing: 4px; } \`\`\` \`\`\`dart highlightLines=19 final container = Container( // grey box width: 320, height: 240, color: Colors.grey\[300], child: Center( child: Container( // red box padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: Colors.red\[400], ), child: const Text( 'Lorem ipsum', style: TextStyle( color: Colors.white, fontSize: 24, fontWeight: FontWeight.w900, letterSpacing: 4, ), ), ), ), ); \`\`\` ### Making inline formatting changes A \[\`Text\`]\[] widget lets you display text with some formatting characteristics. To display text that uses multiple styles (in this example, a single word with emphasis), use a \[\`RichText\`]\[] widget instead. Its \`text\` property can specify one or more \[\`TextSpan\`]\[] objects that can be individually styled. In the following example, "Lorem" is in a \`TextSpan\` with the default (inherited) text styling, and "ipsum" is in a separate \`TextSpan\` with custom styling. \`\`\`css highlightLines=3,11,21-4

Lorem *ipsum*

.grey-box { background-color: #e0e0e0; /\* grey 300 \*/ width: 320px; height: 240px; font: 900 24px Roboto; display: flex; align-items: center; justify-content: center; } .red-box { background-color: #ef5350; /\* red 400 \*/ padding: 16px; color: #ffffff; } .red-box em { font: 300 48px Roboto; font-style: italic; } \`\`\` \`\`\`dart highlightLines=13-28 final container = Container( // grey box width: 320, height: 240, color: Colors.grey\[300], child: Center( child: Container( // red box decoration: BoxDecoration( color: Colors.red\[400], ), padding: const EdgeInsets.all(16), child: RichText( text: TextSpan( style: bold24Roboto, children: const \[ TextSpan(text: 'Lorem '), TextSpan( text: 'ipsum', style: TextStyle( fontWeight: FontWeight.w300, fontStyle: FontStyle.italic, fontSize: 48, ), ), ], ), ), ), ), ); \`\`\` ### Creating text excerpts An excerpt displays the initial line(s) of text in a paragraph, and handles the overflow text, often using an ellipsis. In Flutter, use the \`maxLines\` property of a \[\`Text\`]\[] widget to specify the number of lines to include in the excerpt, and the \`overflow\` property for handling overflow text. \`\`\`css highlightLines=20-23

Lorem ipsum dolor sit amet, consec etur

.grey-box { background-color: #e0e0e0; /\* grey 300 \*/ width: 320px; height: 240px; font: 900 24px Roboto; display: flex; align-items: center; justify-content: center; } .red-box { background-color: #ef5350; /\* red 400 \*/ padding: 16px; color: #ffffff; overflow: hidden; display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; } \`\`\` \`\`\`dart highlightLines=16-17 final container = Container( // grey box width: 320, height: 240, color: Colors.grey\[300], child: Center( child: Container( // red box decoration: BoxDecoration( color: Colors.red\[400], ), padding: const EdgeInsets.all(16), child: Text( 'Lorem ipsum dolor sit amet, consec etur', style: bold24Roboto, overflow: TextOverflow\.ellipsis, maxLines: 1, ), ), ), ); \`\`\` \[basic shapes]: https\://developer.mozilla.org/en-US/docs/Web/CSS/basic-shape \[\`border-box\`]: https\://css-tricks.com/box-sizing/ \[\`BorderRadius\`]: https\://api.flutter.dev/flutter/painting/BorderRadius-class.html \[\`BoxDecoration\`]: https\://api.flutter.dev/flutter/painting/BoxDecoration-class.html \[\`BoxConstraints\`]: https\://api.flutter.dev/flutter/rendering/BoxConstraints-class.html \[\`BoxShape\` enum]: https\://api.flutter.dev/flutter/painting/BoxShape.html \[\`BoxShadow\`]: https\://api.flutter.dev/flutter/painting/BoxShadow-class.html \[\`Center\`]: https\://api.flutter.dev/flutter/widgets/Center-class.html \[\`Container\`]: https\://api.flutter.dev/flutter/widgets/Container-class.html \[Introduction to declarative UI]: /flutter-for/declarative \[Learning Dart as a JavaScript Developer]: https\://dart.dev/guides/language/coming-from/js-to-dart \[\`Matrix4\`]: https\://api.flutter.dev/flutter/vector\_math\_64/Matrix4-class.html \[\`Positioned\`]: https\://api.flutter.dev/flutter/widgets/Positioned-class.html \[\`RichText\`]: https\://api.flutter.dev/flutter/widgets/RichText-class.html \[\`Stack\`]: https\://api.flutter.dev/flutter/widgets/Stack-class.html \[\`Text\`]: https\://api.flutter.dev/flutter/widgets/Text-class.html \[\`TextSpan\`]: https\://api.flutter.dev/flutter/painting/TextSpan-class.html \[\`TextStyle\`]: https\://api.flutter.dev/flutter/painting/TextStyle-class.html \[\`Transform\`]: https\://api.flutter.dev/flutter/widgets/Transform-class.html \[Understanding constraints]: /ui/layout/constraints

----
url: https://docs.flutter.dev/tools/devtools/custom-tool.md
----

\# Build custom tooling in Flutter and Dart DevTools Have you ever wanted to build developer tooling for Dart and Flutter but didn’t know where to start? Or maybe you didn’t want to go through all the work of establishing a connection to a running Dart or Flutter application to access debugging data? Then, even if you did create a development tool, how would you deploy it or give users easy access to it? You can create developer tooling without all these hurdles. With the Dart & Flutter DevTools extensions framework, you can easily build developer tooling that is tightly integrated with the existing DevTools tooling suite. Extensions are built using Flutter web and leverage existing frameworks and utilities from DevTools to simplify the developer tool authoring experience. !\[Example DevTools extension for \`package:foo\`]\(/assets/images/docs/tools/devtools/custom-devtools-extension.png) ## How do DevTools extensions work? Extensions are shipped as part of a pub package. You can add a DevTools extension to an existing pub package, or you can create a new package that provides a DevTools extension only. In both these scenarios, the end-user must list a dependency on the package providing the DevTools extension in order to see the extension in DevTools. For example, imagine we have some \`package:foo\`, and this package provides a DevTools extension. When a user depends on \`package:foo\` in their app, they automatically get access to the DevTools extension provided by this package. When DevTools detects the \`package:foo\` extension is available, based on information from the user’s app or from their IDE, a new tab "Foo" will be added to DevTools that contains the developer tools provided by \`package:foo\`. !\[Diagram showing how a DevTools extension works]\(/assets/images/docs/tools/devtools/how-devtools-extension-works.png) Some examples of packages that have added a DevTools extension to an existing package are \[\`package:shared\_preferences\`]\[], \[\`package:provider\`]\[], \[\`package:patrol\`]\[], and \[\`package:drift\`]\[]. \[\`package:drift\`]: https\://pub.dev/packages/drift \[\`package:patrol\`]: https\://pub.dev/packages/patrol \[\`package:provider\`]: https\://pub.dev/packages/provider \[\`package:shared\_preferences\`]: https\://pub.dev/packages/shared\_preferences ## What types of tools are supported? With the DevTools extensions framework you can build many types of tools, including: \* Companion tools for existing packages. \* New tools that are shipped as their own package. \* Tools that interact with a running application. \* Tools that interact with project files opened in the IDE. \* Tools that interact with the Analysis server. The DevTools Extensions framework comes with out-of-the-box features that make distributing your extension to users seamless. For example, users can: \* Use your tool from DevTools in the browser. \* Use your tool embedded directly in their IDE. \* Discover and open your tool from Dart and Flutter supported IDEs. Next, learn how to write a DevTools extension. --- ## Write a DevTools extension Before you get started, you need: \* Flutter SDK >= 3.17 & Dart SDK >= 3.2. \* A \[pub]\[] package that (in your opinion) needs a custom DevTools extension. \[pub]: https\://pub.dev ### Set up your package hierarchy You will provide either a standalone extension or a companion extension. #### Standalone extension For a standalone extension (that isn't being shipped as part of an existing pub package), your extension can include source code in the same package that the extension is shipped with. This simplifies development, and since users of your package will add your package as a dev\_dependency, the size of your package won't affect the user's app size. Your package structure will look like the following: \`\`\`yaml my\_new\_tool extension/ devtools/ build/ ... # pre-compiled output of the Flutter web app under lib/ config.yaml lib/ # source code for your extension Flutter web app src/ ... \`\`\` Since the extension is built as a Flutter web app, use \`flutter create\` to generate the package: \`\`\`console flutter create --template app --platforms web my\_new\_tool \`\`\` Next, use the \`my\_new\_tool\` package to configure your extension in the next step. #### Companion extensions For a companion extension (that is shipped as part of an existing pub package), consider placing your extension source code outside of your pub package. This keeps your package size as small as possible, to avoid inflating the size of user apps that depend on your package. Here is the recommended package structure: \`\`\`yaml foo/ # formerly the repository root of your pub package packages/ foo/ # your pub package extension/ devtools/ build/ ... # pre-compiled output of foo\_devtools\_extension/lib config.yaml foo\_devtools\_extension/ lib/ # source code for your extension Flutter web app \`\`\` ## Configure your extension In the Dart package that provides the DevTools extension to users, add a top-level extension directory: \`\`\`yaml foo/ extension/ lib/ ... \`\`\` Under the \`extension\` directory, create the following structure \*\*exactly as shown\*\*: \`\`\`yaml extension/ devtools/ build/ config.yaml \`\`\` The \`config.yaml\` file contains metadata that DevTools needs to load the extension: \`\`\`yaml name: foo version: 0.0.1 issueTracker: materialIconCodePoint: '0xe0b1' requiresConnection: true # optional field - defaults to true \`\`\` Copy the \`config.yaml\` file content as shown and paste it into the \`config.yaml\` file you just created in your package. \*\*It's important that you use the exact file name and field names as shown, or else your extension might fail to load in DevTools\*\*. For each key, fill in the appropriate value for your package. \* \`name\`: The package name for this DevTools extension. The value of this field is used in the extension page title bar. \[\*\*required\*\*] \* \`version\`: The version of your DevTools extension. This version number should evolve over time as you ship new features for your extension. The value of this field is used in the extension page title bar. \[\*\*required\*\*] \* \`issueTracker\`: The URL for your issue tracker. When a user clicks the \*\*Report an issue\*\* link in the DevTools UI, they are directed to this URL. \[\*\*required\*\*] !\[DevTools extension screen title bar]\(/assets/images/docs/tools/devtools/devtools-extension-screen-title-bar.png){:width="80%"} \* \`materialIconCodePoint\`: Corresponds to the codepoint value of an icon from \[\`material/icons.dart\`]\[]. This icon is used for the extension’s tab in the top-level DevTools tab bar. \[\*\*required\*\*] !\[DevTools extension tab icon]\(/assets/images/docs/tools/devtools/devtools-extension-tab-icon.png){:width="12%"} \* \`requiresConnection\`: Indicates whether the extension requires a connected Dart or Flutter app to use. This is an optional field that will default to \`true\` if unspecified. \[\*\*optional\*\*] For the most up-to-date documentation on the \`config.yaml\` spec, visit \[extension\_config\_spec.md]\[]. \[extension\_config\_spec.md]: https\://github.com/flutter/devtools/blob/master/packages/devtools\_extensions/extension\_config\_spec.md \[\`material/icons.dart\`]: https\://github.com/flutter/flutter/blob/master/packages/flutter/lib/src/material/icons.dart ## Build your extension Use the following steps to build an extension. ### Create the Flutter web app :::note Skip this step if you are building a standalone extension, since you already did this when you set up your package hierarchy. ::: From the directory where you want your extension source code to live, run the following command, replacing \`foo\_devtools\_extension\` with \`\_devtools\_extension\`: \`\`\`console flutter create --template app --platforms web foo\_devtools\_extension \`\`\` ### Add the \`package:devtools\_extensions\` dependency \`\`\`console flutter pub add devtools\_extensions \`\`\` You will likely also want to add a dependency on \[\`package:devtools\_app\_shared\`]\[], which contains shared services, utilities, and UI components to use while building your extension. Visit \[\`devtools\_app\_shared/example\`]\[] for sample usages. \`\`\`console flutter pub add devtools\_app\_shared \`\`\` \[\`package:devtools\_app\_shared\`]: https\://pub.dev/packages/devtools\_app\_shared \[\`devtools\_app\_shared/example\`]: https\://github.com/flutter/devtools/tree/master/packages/devtools\_app\_shared/example ### Add the \`DevToolsExtension\` widget In \`lib/main.dart\`, add the following imports: \`\`\`dart import 'package:devtools\_extensions/devtools\_extensions.dart'; import 'package:material\_ui/material\_ui.dart'; void main() { runApp(const FooDevToolsExtension()); } class FooDevToolsExtension extends StatelessWidget { const FooDevToolsExtension({super.key}); @override Widget build(BuildContext context) { return const DevToolsExtension( child: Placeholder(), // Build your extension here ); } } \`\`\` The \`DevToolsExtension\` widget automatically initializes all extensions required to interact with DevTools. From anywhere in your extension web app, you can access the following globals: \* \`extensionManager\`: a manager for interacting with DevTools or the extensions framework. \* \`serviceManager\`: a manager for interacting with the connected vm service, if present. \* \`dtdManager\`: a manager for interacting with the Dart Tooling Daemon, if present. ## Debug your extension When developing and maintaining your DevTools extension, you’ll want to run, debug, and test your extension Flutter web app. You have a couple of different options for this, outlined below. ### Option A: Use the Simulated DevTools Environment (recommended for development) For debugging purposes, you will likely want to use the "simulated DevTools environment". This is a simulated environment that allows you to build your extension without having to develop it as an embedded iFrame in DevTools. Running your extension this way will wrap your extension with an environment that simulates the DevTools-to-DevTools extension connection. It also gives you access to hot restart and a faster development cycle. !\[Debugging an extension with the Simulated DevTools Environment]\(/assets/images/docs/tools/devtools/devtools-extension-debugger.png) 1. \_Your DevTools extension.\_ 2. \_The VM service URI for a test app that your DevTools extension will interact with. This app should depend on your extension’s parent package (\`package:foo\` in this example).\_ 3. \_Buttons to perform actions that a user may trigger from DevTools.\_ 4. \_Logs showing the messages that will be sent between your extension and DevTools.\_ The simulated environment is enabled by the environment parameter \`use\_simulated\_environment\`. To run your extension web app with this flag enabled, add a configuration to your \`launch.json\` file in VS code: \`\`\`json { ... "configurations": \[ ... { "name": "foo\_devtools\_extension + simulated environment", "cwd": "packages/foo\_devtools\_extension", "request": "launch", "type": "dart", "args": \[ "--dart-define=use\_simulated\_environment=true" ], }, ] } \`\`\` or launch your app from the command line with the added flag: \`\`\`console flutter run -d chrome -dart-define=use\_simulated\_environment=true \`\`\` ### Option B: Use a real DevTools environment Once you develop your extension to a point where you are ready to test your changes in a real DevTools environment, you need to perform a series of setup steps:

1. Develop your extension to a point where you are ready to test your changes in a real DevTools environment. Build your flutter web app and copy the built assets from \`your\_extension\_web\_app/build/web\` to your pub package's \`extension/devtools/build directory\`. Use the \`build\_and\_copy\` command from \`package:devtools\_extensions\` to help with this step. \`\`\`console cd your\_extension\_web\_app; flutter pub get; dart run devtools\_extensions build\_and\_copy --source=. --dest=path/to/your\_pub\_package/extension/devtools \`\`\` :::note If you are using the recommended package structure for adding an extension to an existing pub package, the value for \`--dest\` should be \`../your\_pub\_package/extension/devtools\`. ::: To ensure that your extension is setup properly for loading in DevTools, run the \`validate\` command from \`package:devtools\_extensions\`. The \`--package\` argument should point to the root of the Dart package that this extension will be published with. \`\`\`console cd your\_extension\_web\_app; flutter pub get; dart run devtools\_extensions validate --package=path/to/your\_pub\_package \`\`\`
2. Prepare a test environment with a dependency on your pub package that is providing the extension. In the Dart or Flutter project where you are adding a dependency on your package, add a \[\`path\`]\[] dependency that points to your local package source code (the package that contains the \`extension/devtools/\` directory with your extension's assets). Once you have done this, run \`pub get\` on the package. \* \*\*If your extension requires a running application\*\*, then you’ll need to run the app that depends on your extension. \* \*\*If your extension does not require a running application\*\*, then you will need to open the test Dart or Flutter project that depends on your package in a supported IDE (VS Code or IntelliJ / Android Studio). \[\`path\`]: https\://dart.dev/tools/pub/dependencies#path-packages
3. Start DevTools Use one of the following ways to start DevTools: \* \*\*If your extension requires a running application\*\*, you can open DevTools either from the URI that was printed to the command line when you ran the test app, or from the IDE where you ran your test app. \* \*\*If your extension does not require a running application\*\*, you can open your Dart or Flutter project that depends on your package in a supported IDE (VS Code or IntelliJ / Android Studio). Open DevTools from the IDE to view your extension in the browser. \* \*\*If you need local or unreleased changes from DevTools\*\*, you’ll need to build and run DevTools from source. See the DevTools \[CONTRIBUTING.md]\[] for a guide on how to do this. You’ll need to build DevTools with the server and the front end to test extensions (\[instructions]\[]).
4. Connect your test app to DevTools if it is not connected already, and you should see a tab in the DevTools app bar for your extension. The enabled or disabled state of your extension is managed by DevTools, which is exposed from an \*\*Extensions\*\* menu in DevTools, available from the action buttons in the upper right corner of the screen.

Once you've opened DevTools, a tab in the DevTools app bar should appear for your extension. The enabled or disabled state of your extension is managed by DevTools, which is exposed from an "Extensions" menu, available from the action buttons in the upper right corner of the screen. !\[DevTools Extensions menu button]\(/assets/images/docs/tools/devtools/devtools-extensions-menu-button.png){:width="80%"} !\[DevTools Extensions menu]\(/assets/images/docs/tools/devtools/devtools-extensions-menu.png) \[CONTRIBUTING.md]: https\://github.com/flutter/devtools/tree/master/packages/devtools\_extensions \[instructions]: https\://github.com/flutter/devtools/blob/master/CONTRIBUTING.md#development-devtools-server--devtools-flutter-web-app ## Publish your package with a DevTools extension For a package to provide a DevTools extension to its users, it must be published with the expected content in the \`your\_pub\_package/extension/devtools/\` directory (as described in the preceding setup instructions).

1. Ensure the \`extension/devtools/config.yaml\` file exists and is configured per the specifications above. You can run the \`validate\` command from \`package:devtools\_extensions\` to verify. \`\`\`console cd your\_extension\_web\_app; flutter pub get; dart run devtools\_extensions validate --package=path/to/pkg\_providing\_your\_extension\_assets \`\`\`
2. Use the \`build\_and\_copy\` command provided by \`package:devtools\_extensions\` to build your extension and copy the output to the \`extension/devtools\` directory: \`\`\`console cd your\_extension\_web\_app; flutter pub get; dart run devtools\_extensions build\_and\_copy --source=. --dest=path/to/your\_pub\_package/extension/devtools \`\`\`

Then, publish your package on \[\`pub.dev\`]\[]: \`\`\`console flutter pub publish \`\`\` When running \`pub publish\`, you will see a warning if you do not have the \`config.yaml\` file and a non-empty \`build\` directory as required. For additional guidance around publishing your package, visit the \`package:devtools\_extensions\` \[publishing guide]\[]. \[\`pub.dev\`]: https\://pub.dev \[publishing guide]: https\://pub.dev/packages/devtools\_extensions#publish-your-package-with-a-devtools-extension --- That’s it! Now, when a user depends on the latest version of your package, they will automatically get access to the tools you provide in your DevTools extension. You might find the following links useful: \* For the latest info on this feature, visit the \[DevTools Extensions README]\[] on GitHub. \* For issues and feature requests, \[file an issue]\[] on the DevTools issue tracker. \* For general support and access to the community of DevTools extension authors, check out the \[#devtools-extension-authors]\[extensions-discord] Discord channel (you will first need to join the \[Flutter Discord server]\[]). \[DevTools Extensions README]: https\://github.com/flutter/devtools/blob/master/packages/devtools\_extensions/README.md \[extensions-discord]: https\://discord.com/channels/608014603317936148/1159561514072690739 \[file an issue]: https\://github.com/flutter/devtools/issues \[Flutter Discord server]: https\://github.com/flutter/flutter/wiki/Chat

----
url: https://docs.flutter.dev/ui/design/material
----

Material Design is an open-source design system built and supported by Google designers and developers.

## More information

[#](#more-information)

To learn more about Material Design and Flutter, check out:

* [Material.io developer documentation](https://m3.material.io/develop/flutter)

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-09-29. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/ui/design/material/index.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/ui/design/material\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/ui/design/material/index.md "Report an issue with this page").

----
url: https://docs.flutter.dev/
----

[Set up your environment and start building.](/install)

[Dip into the rich set of Flutter widgets available in the SDK.](/ui/widgets)

[Bookmark the API reference docs for the Flutter framework.](https://api.flutter.dev)

[Browse tutorials, sample code, workshops, and recipes.](/learn)

[View the many videos on the Flutter YouTube channel.](https://www.youtube.com/@flutterdev)

[Learn how to build with and integrate powerful AI tools.](/ai/get-started)

**To see changes to the site since our last release, see [What's new](/release/whats-new).**

## New to Flutter?

[#](#new-to-flutter)

Ready to build beautiful, multiplatform apps from a single codebase? First, [set up your Flutter environment](/install/quick), then follow the [Flutter learning pathway](/learn/pathway).

### Docs

[#](#docs)

Coming from another platform? Check out Flutter for: [Android](/flutter-for/android-devs), [SwiftUI](/flutter-for/swiftui-devs), [UIKit](/flutter-for/uikit-devs), [React Native](/flutter-for/react-native-devs), and [Xamarin.Forms](/flutter-for/xamarin-forms-devs) developers.

* [Building layouts](/ui/layout)

  Learn how to create layouts in Flutter, where everything is a [widget](/resources/glossary#widget)WidgetThe basic building block of a Flutter user interface. [Learn more](/resources/glossary#widget "Learn more about 'Widget' and find related resources.").

* [Understanding constraints](/ui/layout/constraints)

  Once you understand that "Constraints flow down. Sizes flow up. Parents set positions", then you are well on your way to understanding Flutter's layout model.

* [Adding interactivity to your Flutter app](/ui/interactivity)

  Learn how to add a stateful widget to your app.

* [FAQ](/resources/faq)

  Get the answers to frequently asked questions.

### Videos

[#](#videos)

Check out the latest in Dart and Flutter [Watch on YouTube in a new tab: "What's New in Dart and Flutter 3.44"](https://www.youtube.com/watch/I1uIbGh1dGE)



For more of the latest on Dart and Flutter, check out the technical sessions from [Google I/O](https://blog.flutter.dev/thats-a-wrap-everything-flutter-at-google-i-o-2026-f316e57186e3), like these!

[Watch on YouTube in a new tab: "Vibe once, run anywhere with Google and Antigravity"](https://www.youtube.com/watch/UNdQhnpm8GY)

[Watch on YouTube in a new tab: "How to write really good Flutter code"](https://www.youtube.com/watch/O0UjId1VoRU)

Also, we launched a new 4-video series at Google I/O called the Full-stack developer guide:

[Watch on YouTube in a new tab: "The Full-stack developer guide"](https://www.youtube.com/watch/hNbY3Bmgojg)

You can find the 4 videos in this series, as well as a wrap-up video, on the [Google I/O wrap-up](https://blog.flutter.dev/thats-a-wrap-everything-flutter-at-google-i-o-2026-f316e57186e3) blog post.

To learn about all of the Flutter video series, visit our [videos](/resources/videos) page.

We release new videos almost every week!

[Check out the Flutter YouTube channel](https://www.youtube.com/@flutterdev)

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-08-28. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/index.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/index.md "Report an issue with this page").

----
url: https://docs.flutter.dev/ui/widgets/painting
----

These widgets apply visual effects to the children without changing their layout, size, or position.

[Backdrop​Filter](https://api.flutter.dev/flutter/widgets/BackdropFilter-class.html)

[A widget that applies a filter to the existing painted content and then paints a child. This effect is relatively expensive, especially if the filter...](https://api.flutter.dev/flutter/widgets/BackdropFilter-class.html)

[Clip​Oval](https://api.flutter.dev/flutter/widgets/ClipOval-class.html)

[A widget that clips its child using an oval.](https://api.flutter.dev/flutter/widgets/ClipOval-class.html)

[Clip​Path](https://api.flutter.dev/flutter/widgets/ClipPath-class.html)

[A widget that clips its child using a path.](https://api.flutter.dev/flutter/widgets/ClipPath-class.html)

[Clip​Rect](https://api.flutter.dev/flutter/widgets/ClipRect-class.html)

[A widget that clips its child using a rectangle.](https://api.flutter.dev/flutter/widgets/ClipRect-class.html)

[Custom​Paint](https://api.flutter.dev/flutter/widgets/CustomPaint-class.html)

[A widget that provides a canvas on which to draw during the paint phase.](https://api.flutter.dev/flutter/widgets/CustomPaint-class.html)

[Decorated​Box](https://api.flutter.dev/flutter/widgets/DecoratedBox-class.html)

[A widget that paints a Decoration either before or after its child paints.](https://api.flutter.dev/flutter/widgets/DecoratedBox-class.html)

[Fractional​Translation](https://api.flutter.dev/flutter/widgets/FractionalTranslation-class.html)

[A widget that applies a translation expressed as a fraction of the box's size before painting its child.](https://api.flutter.dev/flutter/widgets/FractionalTranslation-class.html)

[Opacity](https://api.flutter.dev/flutter/widgets/Opacity-class.html)

[A widget that makes its child partially transparent.](https://api.flutter.dev/flutter/widgets/Opacity-class.html)

[Rotated​Box](https://api.flutter.dev/flutter/widgets/RotatedBox-class.html)

[A widget that rotates its child by a integral number of quarter turns.](https://api.flutter.dev/flutter/widgets/RotatedBox-class.html)

[Transform](https://api.flutter.dev/flutter/widgets/Transform-class.html)

[A widget that applies a transformation before painting its child.](https://api.flutter.dev/flutter/widgets/Transform-class.html)

Find more widgets in the [widget catalog](/ui/widgets).

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-09-29. [Report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/ui/widgets/painting "Report an issue with this page").

----
url: https://docs.flutter.dev/add-to-app/android/add-flutter-fragment
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Add a Flutter Fragment to an Android app](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

This guide describes how to add a Flutter `Fragment` to an existing Android app. In Android, a [`Fragment`](https://developer.android.com/guide/components/fragments) represents a modular piece of a larger UI. A `Fragment` might be used to present a sliding drawer, tabbed content, a page in a `ViewPager`, or it might simply represent a normal screen in a single-`Activity` app. Flutter provides a [`FlutterFragment`](https://api.flutter.dev/javadoc/io/flutter/embedding/android/FlutterFragment.html) so that developers can present a Flutter experience any place that they can use a regular `Fragment`.

If an `Activity` is equally applicable for your application needs, consider [using a `FlutterActivity`](/add-to-app/android/add-flutter-screen) instead of a `FlutterFragment`, which is quicker and easier to use.

`FlutterFragment` allows developers to control the following details of the Flutter experience within the `Fragment`:

* Initial Flutter route
* Dart entrypoint to execute
* Opaque vs translucent background
* Whether `FlutterFragment` should control its surrounding `Activity`
* Whether a new [`FlutterEngine`](https://api.flutter.dev/javadoc/io/flutter/embedding/engine/FlutterEngine.html) or a cached `FlutterEngine` should be used

`FlutterFragment` also comes with a number of calls that must be forwarded from its surrounding `Activity`. These calls allow Flutter to react appropriately to OS events.

All varieties of `FlutterFragment`, and its requirements, are described in this guide.

## Add a `FlutterFragment` to an `Activity` with a new `FlutterEngine`

[#](#add-a-flutterfragment-to-an-activity-with-a-new-flutterengine)

The first thing to do to use a `FlutterFragment` is to add it to a host `Activity`.

To add a `FlutterFragment` to a host `Activity`, instantiate and attach an instance of `FlutterFragment` in `onCreate()` within the `Activity`, or at another time that works for your app:

* [Kotlin](#79-tab-panel)
* [Java](#80-tab-panel)

MyActivity.kt

kotlin

```
class MyActivity : FragmentActivity() {
  companion object {
    // Define a tag String to represent the FlutterFragment within this
    // Activity's FragmentManager. This value can be whatever you'd like.
    private const val TAG_FLUTTER_FRAGMENT = "flutter_fragment"
  }
​
  // Declare a local variable to reference the FlutterFragment so that you
  // can forward calls to it later.
  private var flutterFragment: FlutterFragment? = null
​
  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
​
    // Inflate a layout that has a container for your FlutterFragment. For
    // this example, assume that a FrameLayout exists with an ID of
    // R.id.fragment_container.
    setContentView(R.layout.my_activity_layout)
​
    // Get a reference to the Activity's FragmentManager to add a new
    // FlutterFragment, or find an existing one.
    val fragmentManager: FragmentManager = supportFragmentManager
​
    // Attempt to find an existing FlutterFragment, in case this is not the
    // first time that onCreate() was run.
    flutterFragment = fragmentManager
      .findFragmentByTag(TAG_FLUTTER_FRAGMENT) as FlutterFragment?
​
    // Create and attach a FlutterFragment if one does not exist.
    if (flutterFragment == null) {
      var newFlutterFragment = FlutterFragment.createDefault()
      flutterFragment = newFlutterFragment
      fragmentManager
        .beginTransaction()
        .add(
          R.id.fragment_container,
          newFlutterFragment,
          TAG_FLUTTER_FRAGMENT
        )
        .commit()
    }
  }
}
```

MyActivity.java

java

```
public class MyActivity extends FragmentActivity {
    // Define a tag String to represent the FlutterFragment within this
    // Activity's FragmentManager. This value can be whatever you'd like.
    private static final String TAG_FLUTTER_FRAGMENT = "flutter_fragment";
​
    // Declare a local variable to reference the FlutterFragment so that you
    // can forward calls to it later.
    private FlutterFragment flutterFragment;
​
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
​
        // Inflate a layout that has a container for your FlutterFragment.
        // For this example, assume that a FrameLayout exists with an ID of
        // R.id.fragment_container.
        setContentView(R.layout.my_activity_layout);
​
        // Get a reference to the Activity's FragmentManager to add a new
        // FlutterFragment, or find an existing one.
        FragmentManager fragmentManager = getSupportFragmentManager();
​
        // Attempt to find an existing FlutterFragment,
        // in case this is not the first time that onCreate() was run.
        flutterFragment = (FlutterFragment) fragmentManager
            .findFragmentByTag(TAG_FLUTTER_FRAGMENT);
​
        // Create and attach a FlutterFragment if one does not exist.
        if (flutterFragment == null) {
            flutterFragment = FlutterFragment.createDefault();
​
            fragmentManager
                .beginTransaction()
                .add(
                    R.id.fragment_container,
                    flutterFragment,
                    TAG_FLUTTER_FRAGMENT
                )
                .commit();
        }
    }
}
```

The previous code is sufficient to render a Flutter UI that begins with a call to your `main()` Dart entrypoint, an initial Flutter route of `/`, and a new `FlutterEngine`. However, this code is not sufficient to achieve all expected Flutter behavior. Flutter depends on various OS signals that must be forwarded from your host `Activity` to `FlutterFragment`. These calls are shown in the following example:

* [Kotlin](#81-tab-panel)
* [Java](#82-tab-panel)

MyActivity.kt

kotlin

```
class MyActivity : FragmentActivity() {
  override fun onPostResume() {
    super.onPostResume()
    flutterFragment!!.onPostResume()
  }
​
  override fun onNewIntent(@NonNull intent: Intent) {
    flutterFragment!!.onNewIntent(intent)
  }
​
  override fun onBackPressed() {
    flutterFragment!!.onBackPressed()
  }
​
  override fun onRequestPermissionsResult(
    requestCode: Int,
    permissions: Array<String?>,
    grantResults: IntArray
  ) {
    flutterFragment!!.onRequestPermissionsResult(
      requestCode,
      permissions,
      grantResults
    )
  }
​
  override fun onActivityResult(
    requestCode: Int,
    resultCode: Int,
    data: Intent?
  ) {
    super.onActivityResult(requestCode, resultCode, data)
    flutterFragment!!.onActivityResult(
      requestCode,
      resultCode,
      data
    )
  }
​
  override fun onUserLeaveHint() {
    flutterFragment!!.onUserLeaveHint()
  }
​
  override fun onTrimMemory(level: Int) {
    super.onTrimMemory(level)
    flutterFragment!!.onTrimMemory(level)
  }
}
```

MyActivity.java

java

```
public class MyActivity extends FragmentActivity {
    @Override
    public void onPostResume() {
        super.onPostResume();
        flutterFragment.onPostResume();
    }
​
    @Override
    protected void onNewIntent(@NonNull Intent intent) {
        flutterFragment.onNewIntent(intent);
    }
​
    @Override
    public void onBackPressed() {
        flutterFragment.onBackPressed();
    }
​
    @Override
    public void onRequestPermissionsResult(
        int requestCode,
        @NonNull String[] permissions,
        @NonNull int[] grantResults
    ) {
        flutterFragment.onRequestPermissionsResult(
            requestCode,
            permissions,
            grantResults
        );
    }
​
    @Override
    public void onActivityResult(
        int requestCode,
        int resultCode,
        @Nullable Intent data
    ) {
        super.onActivityResult(requestCode, resultCode, data);
        flutterFragment.onActivityResult(
            requestCode,
            resultCode,
            data
        );
    }
​
    @Override
    public void onUserLeaveHint() {
        flutterFragment.onUserLeaveHint();
    }
​
    @Override
    public void onTrimMemory(int level) {
        super.onTrimMemory(level);
        flutterFragment.onTrimMemory(level);
    }
}
```

With the OS signals forwarded to Flutter, your `FlutterFragment` works as expected. You have now added a `FlutterFragment` to your existing Android app.

The simplest integration path uses a new `FlutterEngine`, which comes with a non-trivial initialization time, leading to a blank UI until Flutter is initialized and rendered the first time. Most of this time overhead can be avoided by using a cached, pre-warmed `FlutterEngine`, which is discussed in [Using a pre-warmed `FlutterEngine`](#using-a-pre-warmed-flutterengine).

### Automatic back button and predictive back handling

[#](#automatic-back-button-and-predictive-back-handling)

When embedding a `FlutterFragment` into a native Android app on Android 13 or higher (API level 33+), you can configure the fragment to automatically handle system back button presses and predictive back gestures without manually forwarding `onBackPressed()`.

Instead of overriding `onBackPressed` as shown above, set `shouldAutomaticallyHandleOnBackPressed(true)` when building your fragment:

* [Kotlin](#83-tab-panel)
* [Java](#84-tab-panel)

kotlin

```
val flutterFragment = FlutterFragment.withNewEngine()
    .shouldAutomaticallyHandleOnBackPressed(true)
    .build()
```

java

```
FlutterFragment flutterFragment = FlutterFragment.withNewEngine()
    .shouldAutomaticallyHandleOnBackPressed(true)
    .build();
```

## Using a pre-warmed `FlutterEngine`

[#](#using-a-pre-warmed-flutterengine)

By default, a `FlutterFragment` creates its own instance of a `FlutterEngine`, which requires non-trivial warm-up time. This means your user sees a blank `Fragment` for a brief moment. You can mitigate most of this warm-up time by using an existing, pre-warmed instance of `FlutterEngine`.

To use a pre-warmed `FlutterEngine` in a `FlutterFragment`, instantiate a `FlutterFragment` with the `withCachedEngine()` factory method.

* [Kotlin](#85-tab-panel)
* [Java](#86-tab-panel)

MyApplication.kt

kotlin

```
// Somewhere in your app, before your FlutterFragment is needed,
// like in the Application class ...
// Instantiate a FlutterEngine.
val flutterEngine = FlutterEngine(context)
​
// Start executing Dart code in the FlutterEngine.
flutterEngine.getDartExecutor().executeDartEntrypoint(
    DartEntrypoint.createDefault()
)
​
// Cache the pre-warmed FlutterEngine to be used later by FlutterFragment.
FlutterEngineCache
  .getInstance()
  .put("my_engine_id", flutterEngine)
```

MyActivity.java

kotlin

```
FlutterFragment.withCachedEngine("my_engine_id").build()
```

MyApplication.java

java

```
// Somewhere in your app, before your FlutterFragment is needed,
// like in the Application class ...
// Instantiate a FlutterEngine.
FlutterEngine flutterEngine = new FlutterEngine(context);
​
// Start executing Dart code in the FlutterEngine.
flutterEngine.getDartExecutor().executeDartEntrypoint(
    DartEntrypoint.createDefault()
);
​
// Cache the pre-warmed FlutterEngine to be used later by FlutterFragment.
FlutterEngineCache
  .getInstance()
  .put("my_engine_id", flutterEngine);
```

MyActivity.java

java

```
FlutterFragment.withCachedEngine("my_engine_id").build();
```

`FlutterFragment` internally knows about [`FlutterEngineCache`](https://api.flutter.dev/javadoc/io/flutter/embedding/engine/FlutterEngineCache.html) and retrieves the pre-warmed `FlutterEngine` based on the ID given to `withCachedEngine()`.

By providing a pre-warmed `FlutterEngine`, as previously shown, your app renders the first Flutter frame as quickly as possible.

#### Initial route with a cached engine

[#](#initial-route-with-a-cached-engine)

The concept of an initial route is available when configuring a `FlutterActivity` or a `FlutterFragment` with a new `FlutterEngine`. However, `FlutterActivity` and `FlutterFragment` don't offer the concept of an initial route when using a cached engine. This is because a cached engine is expected to already be running Dart code, which means it's too late to configure the initial route.

Developers that would like their cached engine to begin with a custom initial route can configure their cached `FlutterEngine` to use a custom initial route just before executing the Dart entrypoint. The following example demonstrates the use of an initial route with a cached engine:

* [Kotlin](#87-tab-panel)
* [Java](#88-tab-panel)

MyApplication.kt

kotlin

```
class MyApplication : Application() {
  lateinit var flutterEngine : FlutterEngine
  override fun onCreate() {
    super.onCreate()
    // Instantiate a FlutterEngine.
    flutterEngine = FlutterEngine(this)
    // Configure an initial route.
    flutterEngine.navigationChannel.setInitialRoute("your/route/here");
    // Start executing Dart code to pre-warm the FlutterEngine.
    flutterEngine.dartExecutor.executeDartEntrypoint(
      DartExecutor.DartEntrypoint.createDefault()
    )
    // Cache the FlutterEngine to be used by FlutterActivity or FlutterFragment.
    FlutterEngineCache
      .getInstance()
      .put("my_engine_id", flutterEngine)
  }
}
```

MyApplication.java

java

```
public class MyApplication extends Application {
  @Override
  public void onCreate() {
    super.onCreate();
    // Instantiate a FlutterEngine.
    flutterEngine = new FlutterEngine(this);
    // Configure an initial route.
    flutterEngine.getNavigationChannel().setInitialRoute("your/route/here");
    // Start executing Dart code to pre-warm the FlutterEngine.
    flutterEngine.getDartExecutor().executeDartEntrypoint(
      DartEntrypoint.createDefault()
    );
    // Cache the FlutterEngine to be used by FlutterActivity or FlutterFragment.
    FlutterEngineCache
      .getInstance()
      .put("my_engine_id", flutterEngine);
  }
}
```

By setting the initial route of the navigation channel, the associated `FlutterEngine` displays the desired route upon initial execution of the `runApp()` Dart function.

Changing the initial route property of the navigation channel after the initial execution of `runApp()` has no effect. Developers who would like to use the same `FlutterEngine` between different `Activity`s and `Fragment`s and switch the route between those displays need to set up a method channel and explicitly instruct their Dart code to change `Navigator` routes.

## Display a splash screen

[#](#display-a-splash-screen)

The initial display of Flutter content requires some wait time, even if a pre-warmed `FlutterEngine` is used. To help improve the user experience around this brief waiting period, Flutter supports the display of a splash screen (also known as "launch screen") until Flutter renders its first frame. For instructions about how to show a launch screen, see the [splash screen guide](/platform-integration/android/splash-screen).

## Run Flutter with a specified initial route

[#](#run-flutter-with-a-specified-initial-route)

An Android app might contain many independent Flutter experiences, running in different `FlutterFragment`s, with different `FlutterEngine`s. In these scenarios, it's common for each Flutter experience to begin with different initial routes (routes other than `/`). To facilitate this, `FlutterFragment`'s `Builder` allows you to specify a desired initial route, as shown:

* [Kotlin](#89-tab-panel)
* [Java](#90-tab-panel)

MyActivity.kt

kotlin

```
// With a new FlutterEngine.
val flutterFragment = FlutterFragment.withNewEngine()
    .initialRoute("myInitialRoute/")
    .build()
```

MyActivity.java

java

```
// With a new FlutterEngine.
FlutterFragment flutterFragment = FlutterFragment.withNewEngine()
    .initialRoute("myInitialRoute/")
    .build();
```

## Run Flutter from a specified entrypoint

[#](#run-flutter-from-a-specified-entrypoint)

Similar to varying initial routes, different `FlutterFragment`s might want to execute different Dart entrypoints. In a typical Flutter app, there is only one Dart entrypoint: `main()`, but you can define other entrypoints.

`FlutterFragment` supports specification of the desired Dart entrypoint to execute for the given Flutter experience. To specify an entrypoint, build `FlutterFragment`, as shown:

* [Kotlin](#91-tab-panel)
* [Java](#92-tab-panel)

MyActivity.kt

kotlin

```
val flutterFragment = FlutterFragment.withNewEngine()
    .dartEntrypoint("mySpecialEntrypoint")
    .build()
```

MyActivity.java

java

```
FlutterFragment flutterFragment = FlutterFragment.withNewEngine()
    .dartEntrypoint("mySpecialEntrypoint")
    .build();
```

The `FlutterFragment` configuration results in the execution of a Dart entrypoint called `mySpecialEntrypoint()`. Notice that the parentheses `()` are not included in the `dartEntrypoint` `String` name.

## Control `FlutterFragment`'s render mode

[#](#control-flutterfragments-render-mode)

`FlutterFragment` can either use a `SurfaceView` to render its Flutter content, or it can use a `TextureView`. The default is `SurfaceView`, which is significantly better for performance than `TextureView`. However, `SurfaceView` can't be interleaved in the middle of an Android `View` hierarchy. A `SurfaceView` must either be the bottommost `View` in the hierarchy, or the topmost `View` in the hierarchy. Additionally, on Android versions before Android N, `SurfaceView`s can't be animated because their layout and rendering aren't synchronized with the rest of the `View` hierarchy. If either of these use cases are requirements for your app, then you need to use `TextureView` instead of `SurfaceView`. Select a `TextureView` by building a `FlutterFragment` with a `texture` `RenderMode`:

* [Kotlin](#93-tab-panel)
* [Java](#94-tab-panel)

MyActivity.kt

kotlin

```
// With a new FlutterEngine.
val flutterFragment = FlutterFragment.withNewEngine()
    .renderMode(FlutterView.RenderMode.texture)
    .build()
​
// With a cached FlutterEngine.
val flutterFragment = FlutterFragment.withCachedEngine("my_engine_id")
    .renderMode(FlutterView.RenderMode.texture)
    .build()
```

MyActivity.java

java

```
// With a new FlutterEngine.
FlutterFragment flutterFragment = FlutterFragment.withNewEngine()
    .renderMode(FlutterView.RenderMode.texture)
    .build();
​
// With a cached FlutterEngine.
FlutterFragment flutterFragment = FlutterFragment.withCachedEngine("my_engine_id")
    .renderMode(FlutterView.RenderMode.texture)
    .build();
```

Using the configuration shown, the resulting `FlutterFragment` renders its UI to a `TextureView`.

## Display a `FlutterFragment` with transparency

[#](#display-a-flutterfragment-with-transparency)

By default, `FlutterFragment` renders with an opaque background, using a `SurfaceView`. (See "Control `FlutterFragment`'s render mode.") That background is black for any pixels that aren't painted by Flutter. Rendering with an opaque background is the preferred rendering mode for performance reasons. Flutter rendering with transparency on Android negatively affects performance. However, there are many designs that require transparent pixels in the Flutter experience that show through to the underlying Android UI. For this reason, Flutter supports translucency in a `FlutterFragment`.

To enable transparency for a `FlutterFragment`, build it with the following configuration:

* [Kotlin](#95-tab-panel)
* [Java](#96-tab-panel)

MyActivity.kt

kotlin

```
// Using a new FlutterEngine.
val flutterFragment = FlutterFragment.withNewEngine()
    .transparencyMode(FlutterView.TransparencyMode.transparent)
    .build()
​
// Using a cached FlutterEngine.
val flutterFragment = FlutterFragment.withCachedEngine("my_engine_id")
    .transparencyMode(FlutterView.TransparencyMode.transparent)
    .build()
```

MyActivity.java

java

```
// Using a new FlutterEngine.
FlutterFragment flutterFragment = FlutterFragment.withNewEngine()
    .transparencyMode(FlutterView.TransparencyMode.transparent)
    .build();
​
// Using a cached FlutterEngine.
FlutterFragment flutterFragment = FlutterFragment.withCachedEngine("my_engine_id")
    .transparencyMode(FlutterView.TransparencyMode.transparent)
    .build();
```

## The relationship between `FlutterFragment` and its `Activity`

[#](#the-relationship-between-flutterfragment-and-its-activity)

Some apps choose to use `Fragment`s as entire Android screens. In these apps, it would be reasonable for a `Fragment` to control system chrome like Android's status bar, navigation bar, and orientation.

In other apps, `Fragment`s are used to represent only a portion of a UI. A `FlutterFragment` might be used to implement the inside of a drawer, a video player, or a single card. In these situations, it would be inappropriate for the `FlutterFragment` to affect Android's system chrome because there are other UI pieces within the same `Window`.

`FlutterFragment` comes with a concept that helps differentiate between the case when a `FlutterFragment` should be able to control its host `Activity`, and the cases when a `FlutterFragment` should only affect its own behavior. To prevent a `FlutterFragment` from exposing its `Activity` to Flutter plugins, and to prevent Flutter from controlling the `Activity`'s system UI, use the `shouldAttachEngineToActivity()` method in `FlutterFragment`'s `Builder`, as shown:

* [Kotlin](#97-tab-panel)
* [Java](#98-tab-panel)

MyActivity.kt

kotlin

```
// Using a new FlutterEngine.
val flutterFragment = FlutterFragment.withNewEngine()
    .shouldAttachEngineToActivity(false)
    .build()
​
// Using a cached FlutterEngine.
val flutterFragment = FlutterFragment.withCachedEngine("my_engine_id")
    .shouldAttachEngineToActivity(false)
    .build()
```

MyActivity.java

java

```
// Using a new FlutterEngine.
FlutterFragment flutterFragment = FlutterFragment.withNewEngine()
    .shouldAttachEngineToActivity(false)
    .build();
​
// Using a cached FlutterEngine.
FlutterFragment flutterFragment = FlutterFragment.withCachedEngine("my_engine_id")
    .shouldAttachEngineToActivity(false)
    .build();
```

Passing `false` to the `shouldAttachEngineToActivity()` `Builder` method prevents Flutter from interacting with the surrounding `Activity`. The default value is `true`, which allows Flutter and Flutter plugins to interact with the surrounding `Activity`.

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-08-01. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/add-to-app/android/add-flutter-fragment.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/add-to-app/android/add-flutter-fragment\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/add-to-app/android/add-flutter-fragment.md "Report an issue with this page").

----
url: https://docs.flutter.dev/ui/accessibility/ui-design-and-styling.md
----

\# UI design & styling > Information on Flutter's accessibility support. To create an accessible app, design your UI with accessibility in mind. This page covers key aspects of accessible UI design and styling. ## Large fonts Both Android and iOS contain system settings to configure the desired font sizes used by apps. Flutter text widgets respect this OS setting when determining font sizes. Font sizes are calculated automatically by Flutter based on the OS setting. However, as a developer you should make sure your layout has enough room to render all its contents when the font sizes are increased. For example, you can test all parts of your app on a small-screen device configured to use the largest font setting. To adjust font sizes: on iOS, go to Settings > Accessibility > Display & Text Size; on Android, go to Settings > Font size. ### Example The following two screenshots show the standard Flutter app template rendered with the default iOS font setting, and with the largest font setting selected in iOS accessibility settings.## Sufficient contrast Sufficient color contrast makes text and images easier to read. Along with benefitting users with various visual impairments, sufficient color contrast helps all users when viewing an interface on devices in extreme lighting conditions, such as when exposed to direct sunlight or on a display with low brightness. The \[W3C recommends]\[]: \* At least 4.5:1 for small text (below 18 point regular or 14 point bold) \* At least 3.0:1 for large text (18 point and above regular or 14 point and above bold) You can test contrast using Flutter's \[Accessibility Guideline API]\[]. For more details on testing, check out the \[accessibility testing page]\(/ui/accessibility/accessibility-testing/). \[W3C recommends]: https\://www\.w3.org/TR/UNDERSTANDING-WCAG20/visual-audio-contrast-contrast.html ## Tap target size Controls that are too small are hard for many people to interact with and select. Ensure that interactive elements have a large enough tap target to be easily pressed by users. Both \[Android]\[] and \[iOS]\[] recommend a minimum tap target size of 48x48 dp and 44x44 pts respectively. The \[W3C] recommends a minimum target size of 44 by 44 CSS pixels. You can test tap target size using Flutter's \[Accessibility Guideline API]\[]. For more details on testing, check out the \[accessibility testing page]\(/ui/accessibility/accessibility-testing/). \[Android]: https\://developer.android.com/guide/topics/ui/accessibility/apps#large-controls \[iOS]: https\://developer.apple.com/design/human-interface-guidelines/accessibility#Mobility \[W3C]: https\://www\.w3.org/WAI/WCAG21/Understanding/target-size.html \[Accessibility Guideline API]: https\://api.flutter.dev/flutter/flutter\_test/AccessibilityGuideline-class.html ## Other accessibility features You can check the \[AccessibilityFeatures] class for additional accessibility features that may be enabled by the platform, such as bold text, high contrast, and inverted colors. \[AccessibilityFeatures]: https\://api.flutter.dev/flutter/dart-ui/AccessibilityFeatures-class.html

----
url: https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.61.0.md
----

\# DevTools 2.61.0 release notes The 2.61.0 release of the Dart and Flutter DevTools includes the following changes among other general improvements. To learn more about DevTools, check out the \[DevTools overview]\(/tools/devtools). ## General updates \* Fixed unreadable text in the release notes panel, where blockquotes were drawn on a hard coded light blue background in the dark theme. \[#9957]\(https\://github.com/flutter/devtools/pull/9957) \* Added support for proxying the VM service connection over a \`MessagePort\` using \`?uri=messageport:\`. \[#9964]\(https\://github.com/flutter/devtools/pull/9964) ## Inspector updates \* Added the widget source file path to the Inspector details pane (\`filename.dart:line:column\`), matching legacy Inspector behavior. \[#9972]\(https\://github.com/flutter/devtools/pull/9972), \[#9922]\(https\://github.com/flutter/devtools/issues/9922) ## Performance updates \* Fixed a bug where the Flutter frames chart could appear frozen after switching away from the Performance screen and coming back. \[#9960]\(https\://github.com/flutter/devtools/pull/9960) \* Added a message in the Performance panel when widget rebuild tracking is unavailable because the app is running in profile mode. \[#9755]\(https\://github.com/flutter/devtools/pull/9755) ## CPU profiler updates \* Fixed a bug where resizing the CPU flame chart changes the timing values across the top of the chart. \[#9915]\(https\://github.com/flutter/devtools/pull/9915) ## Memory updates \* Added the ability to pin classes to the top of the Profile Memory table. \[#8898]\(https\://github.com/flutter/devtools/issues/8898) ## Debugger updates \* Prevented values from being garbage-collected, while being evaluated. \[#9885]\(https\://github.com/flutter/devtools/pull/9885) \* Updated to latest version of the Dart syntax highlighting grammar \[#9920]\(https\://github.com/flutter/devtools/pull/9920) \* Fixed a bug in the TextMate grammar parser that could result in code after comments being classified as comments. \[#9921]\(https\://github.com/flutter/devtools/pull/9921) \* Fixed an overflow in the debugging controls when the Debugger screen is narrow, such as when DevTools is embedded in an IDE side panel. The controls now scroll horizontally instead of overflowing. \[#9949]\(https\://github.com/flutter/devtools/pull/9949) ## Network profiler updates \* Fixed Copy as cURL omitting request headers and body for failed or timed-out requests. \[#9963]\(https\://github.com/flutter/devtools/pull/9963) \* Added WebSocket support to the Network profiler, including WebSocket connection details, lifecycle events, frame-level inspection, and connection timing information. \[#9968]\(https\://github.com/flutter/devtools/pull/9968) \* Fixed an issue where the Network tab would stop capturing HTTP requests after a hot restart. \[#9856]\(https\://github.com/flutter/devtools/pull/9856) \* Fixed an issue where the Network tab would stop capturing new HTTP requests after pressing Clear while recording. \[#9856]\(https\://github.com/flutter/devtools/pull/9856) ## Logging updates \* Corrected time units and cumulative nature of GC events. \[#9890]\(https\://github.com/flutter/devtools/pull/9890) \* Fixed a bug where long \`dart:developer\` log messages lost their structured Details view. \[#10011]\(https\://github.com/flutter/devtools/pull/10011) ## Deep links tool updates \* Added a "Watch tutorial" link to the status line that points to the \[deep links video tutorial]\(https\://youtu.be/d7sZL6h1Elw). \[#9925]\(https\://github.com/flutter/devtools/pull/9925) \* Validated build option parameters and surfaced iOS universal link settings errors in the Deep Links tool. \[#10022]\(https\://github.com/flutter/devtools/pull/10022) ## DevTools extension updates \* Hide the DevTools extensions menu button in single-screen embedded mode (\`EmbedMode.embedOne\`) on standard screens. \[#8507]\(https\://github.com/flutter/devtools/issues/8507) \* Added iframe sandboxing for embedded DevTools extensions to enforce origin isolation. \[#9967]\(https\://github.com/flutter/devtools/pull/9967) \* Improved DevTools extension isolation by tracking the providing package name for enablement, deduplication, and asset loading. \[#9981]\(https\://github.com/flutter/devtools/pull/9981) ## Full commit history To find a complete list of changes in this release, check out the \[DevTools git log]\(https\://github.com/flutter/devtools/tree/v2.61.0).

----
url: https://docs.flutter.dev/platform-integration/macos/building.md
----

\# Building macOS apps with Flutter > Platform-specific considerations for building for macOS with Flutter. This page discusses considerations unique to building macOS apps with Flutter, including shell integration and distribution of macOS apps through the Apple Store. ## Integrating with macOS look and feel While you can use any visual style or theme you choose to build a macOS app, you might want to adapt your app to more fully align with the macOS look and feel. Flutter includes the \[Cupertino] widget set, which provides a set of widgets for the current iOS design language. Many of these widgets, including sliders, switches and segmented controls, are also appropriate for use on macOS. Alternatively, you might find the \[macos\_ui]\[] package a good fit for your needs. This package provides widgets and themes that implement the macOS design language, including a \`MacosWindow\` frame and scaffold, toolbars, pulldown and pop-up buttons, and modal dialogs. \[Cupertino]: /ui/widgets/cupertino \[macos\_ui]: https\://pub.dev/packages/macos\_ui ## Building macOS apps :::warning macOS Intel (x64) deprecation As Apple phases out Intel-based Macs, Flutter is phasing out support for Intel (x64) hardware. For details on the timeline and impact, refer to the \[macOS Intel deprecation strategy]\[]. \[macOS Intel deprecation strategy]: https\://flutter.dev/go/macos-intel-deprecation ::: To distribute your macOS application, you can either \[distribute it through the macOS App Store]\[], or you can distribute the \`.app\` itself, perhaps from your own website. You need to notarize your macOS application before distributing it outside the macOS App Store. The first step in both of the above processes involves working with your application inside of Xcode. To be able to compile your application from inside of Xcode you first need to build the application for release using the \`flutter build\` command, then open the Flutter macOS Runner application. \`\`\`bash flutter build macos open macos/Runner.xcworkspace \`\`\` Once inside of Xcode, follow either Apple's \[documentation on notarizing macOS Applications]\[], or \[on distributing an application through the App Store]\[]. You should also read through the \[macOS-specific support]\(#entitlements-and-the-app-sandbox) section below to understand how entitlements, the App Sandbox, and the Hardened Runtime impact your distributable application. \[Build and release a macOS app]\[] provides a more detailed step-by-step walkthrough of releasing a Flutter app to the App Store. \[distribute it through the macOS App Store]: https\://developer.apple.com/macos/submit/ \[documentation on notarizing macOS Applications]:https\://developer.apple.com/documentation/xcode/notarizing\_macos\_software\_before\_distribution \[on distributing an application through the App Store]: https\://help.apple.com/xcode/mac/current/#/dev067853c94 \[Build and release a macOS app]: /deployment/macos ## Entitlements and the App Sandbox macOS builds are configured by default to be signed, and sandboxed with App Sandbox. This means that if you want to confer specific capabilities or services on your macOS app, such as the following: \* Accessing the internet \* Capturing movies and images from the built-in camera \* Accessing files Then you must set up specific \_entitlements\_ in Xcode. The following section tells you how to do this. ### Setting up entitlements Managing sandbox settings is done in the \`macos/Runner/\*.entitlements\` files. When editing these files, you shouldn't remove the original \`Runner-DebugProfile.entitlements\` exceptions (that support incoming network connections and JIT), as they're necessary for the \`debug\` and \`profile\` modes to function correctly. If you're used to managing entitlement files through the \*\*Xcode capabilities UI\*\*, be aware that the capabilities editor updates only one of the two files or, in some cases, it creates a whole new entitlements file and switches the project to use it for all configurations. Either scenario causes issues. We recommend that you edit the files directly. Unless you have a very specific reason, you should always make identical changes to both files. If you keep the App Sandbox enabled (which is required if you plan to distribute your application in the \[App Store]\[]), you need to manage entitlements for your application when you add certain plugins or other native functionality. For instance, using the \[\`file\_selector\`]\[] plugin requires adding either the \`com.apple.security.files.user-selected.read-only\` or \`com.apple.security.files.user-selected.read-write\` entitlement. Another common entitlement is \`com.apple.security.network.client\`, which you must add if you make any network requests. Without the \`com.apple.security.network.client\` entitlement, for example, network requests fail with a message such as: \`\`\`console flutter: SocketException: Connection failed (OS Error: Operation not permitted, errno = 1), address = example.com, port = 443 \`\`\` :::important The \`com.apple.security.network.server\` entitlement, which allows incoming network connections, is enabled by default only for \`debug\` and \`profile\` builds to enable communications between Flutter tools and a running app. If you need to allow incoming network requests in your application, you must add the \`com.apple.security.network.server\` entitlement to \`Runner-Release.entitlements\` as well, otherwise your application will work correctly for debug or profile testing, but will fail with release builds. ::: For more information on these topics, see \[App Sandbox]\[] and \[Entitlements]\[] on the Apple Developer site. \[App Sandbox]: https\://developer.apple.com/documentation/security/app\_sandbox \[App Store]: https\://developer.apple.com/app-store/submissions/ \[Entitlements]: https\://developer.apple.com/documentation/bundleresources/entitlements \[\`file\_selector\`]: https\://pub.dev/packages/file\_selector ## Hardened Runtime If you choose to distribute your application outside of the App Store, you need to notarize your application for compatibility with macOS. This requires enabling the Hardened Runtime option. Once you have enabled it, you need a valid signing certificate in order to build. By default, the entitlements file allows JIT for debug builds but, as with App Sandbox, you might need to manage other entitlements. If you have both App Sandbox and Hardened Runtime enabled, you might need to add multiple entitlements for the same resource. For instance, microphone access would require both \`com.apple.security.device.audio-input\` (for Hardened Runtime) and \`com.apple.security.device.microphone\` (for App Sandbox). For more information on this topic, see \[Hardened Runtime]\[] on the Apple Developer site. \[Hardened Runtime]: https\://developer.apple.com/documentation/security/hardened\_runtime

----
url: https://docs.flutter.dev/ui/layout/scrolling/slivers.md
----

\# Using slivers to achieve fancy scrolling > Where to find information on using slivers to implement fancy scrolling effects, like elastic scrolling, in Flutter. A sliver is a portion of a scrollable area that you can define to behave in a special way. You can use slivers to achieve custom scrolling effects, such as elastic scrolling. For a free, instructor-led video workshop that uses DartPad, check out the following video about using slivers. ## Resources For more information on implementing fancy scrolling effects in Flutter, see the following resources: \*\*\[Slivers, Demystified]\[]\*\* : A Flutter blog post that explains how to implement custom scrolling using the sliver classes. \*\*\[SliverAppBar]\[sliver-app-bar-video]\*\* : A one-minute Widget-of-the-week video that gives an overview of the \`SliverAppBar\` widget. \*\*\[SliverList and SliverGrid]\[]\*\* : A one-minute Widget-of-the-week video that gives an overview of the \`SliverList\` and \`SliverGrid\` widgets. \*\*\[Slivers explained - Making dynamic layouts]\[]\*\* : A 50-minute episode of \[The Boring Show]\[] where Ian Hickson, Flutter's Tech Lead, and Filip Hracek discuss the power of slivers. ## API docs To learn more about the available sliver APIs, check out these related API docs: \* \[\`CustomScrollView\`]\[] \* \[\`SliverAppBar\`]\[] \* \[\`SliverGrid\`]\[] \* \[\`SliverList\`]\[] \[\`CustomScrollView\`]: https\://api.flutter.dev/flutter/widgets/CustomScrollView-class.html \[sliver-app-bar-video]: https\://www\.youtube.com/watch?v=R9C5KMJKluE \[\`SliverAppBar\`]: https\://pub.dev/documentation/material\_ui/latest/material\_ui/SliverAppBar-class.html \[\`SliverGrid\`]: https\://api.flutter.dev/flutter/widgets/SliverGrid-class.html \[SliverList and SliverGrid]: https\://www\.youtube.com/watch?v=ORiTTaVY6mM \[\`SliverList\`]: https\://api.flutter.dev/flutter/widgets/SliverList-class.html \[Slivers, DeMystified]: https\://blog.flutter.dev/slivers-demystified-6ff68ab0296f \[Slivers explained - Making dynamic layouts]: https\://www\.youtube.com/watch?v=Mz3kHQxBjGg \[The Boring Show]: https\://www\.youtube.com/playlist?list=PLOU2XLYxmsIK0r\_D-zWcmJ1plIcDNnRkK

----
url: https://docs.flutter.dev/ui/design/cupertino
----

The Flutter Cupertino package is a collection of widgets that implement Apple's iOS design language for Flutter apps. The widgets have a similar look and feel to iOS, including rounded corners, gradients, and minimalistic design. They also include iOS interactions and animations.

Previously, Cupertino was part of the core Flutter SDK, but it has been decoupled from the framework. You can access Cupertino from the [`cupertino_ui`](https://pub.dev/packages/cupertino_ui) package on pub.dev.

The following 15-minute video provides a high-level glimpse of the Cupertino package:

[Watch on YouTube in a new tab: "Flutter's Cupertino Package"](https://www.youtube.com/watch/3PdUaidHc-E)

To see some Cupertino widgets in action, the following videos from the Widget of the Week series cover a few of them.

[Watch on YouTube in a new tab: "CupertinoRadio (Widget of the Week)"](https://www.youtube.com/watch/D0xwcz2IqAY)

[Watch on YouTube in a new tab: "CupertinoSheetRoute (Widget of the Week)"](https://www.youtube.com/watch/5H-WvH5O29I)

[Watch on YouTube in a new tab: "CupertinoSlidingSegmentedControl (Widget of the Week)"](https://www.youtube.com/watch/esnBf6V4C34)

[Watch on YouTube in a new tab: "CupertinoCheckbox (Widget of the Week)"](https://www.youtube.com/watch/ua54JU7k1Us)

[Watch on YouTube in a new tab: "CupertinoSwitch (Widget of the Week)"](https://www.youtube.com/watch/24tg_N4sdMQ)

## More information

[#](#more-information)

To learn more about using the Cupertino package with Flutter, check out the following resources:

* The (mostly visual) [Cupertino widget catalog](/ui/widgets/cupertino)
* The [Cupertino library](https://pub.dev/packages/cupertino_ui) page on pub.dev
* The [Cupertino API examples](https://github.com/flutter/flutter/tree/main/examples/api/lib/cupertino) on the flutter/flutter repo. ([Instructions](https://github.com/flutter/flutter/tree/main/examples/api#api-example-code)) For example, to run `CupertinoSwitch`:

```
cd path/to/flutter
cd examples/api
flutter run lib/cupertino/switch/cupertino_switch.0.dart
```

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-09-29. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/ui/design/cupertino/index.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/ui/design/cupertino\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/ui/design/cupertino/index.md "Report an issue with this page").

----
url: https://docs.flutter.dev/learn/pathway/tutorial/navigation.md
----

\# Stack-based navigation > Learn how to navigate from one page to another in a Flutter app. Learn to navigate between screens with Navigator.push and implement adaptive navigation patterns for different screen sizes. title: What you'll accomplish items: - title: Navigate between screens with Navigator.push icon: open\_in\_new - title: Use CupertinoPageRoute for iOS-style transitions icon: swipe\_right - title: Create different navigation patterns for each screen size icon: devices --- ### Introduction Now that you understand slivers and scrolling, you can implement navigation between screens. In this lesson, you'll update the small-screen view such that when a contact group is tapped, it navigates to the contact list for that group. First, revert changes in the adaptive layout widget so that it displays the \`ContactGroupsPage\` by default on small screens.

<!--?code-excerpt "fwe/rolodex/lib/step4_navigation/screens/adaptive_layout.dart (reverted-state)"?-->

\`\`\`dart class \_AdaptiveLayoutState extends State { int selectedListId = 0; void \_onContactListSelected(int listId) { setState(() { selectedListId = listId; }); } @override Widget build(BuildContext context) { return LayoutBuilder( builder: (context, constraints) { final isLargeScreen = constraints.maxWidth > largeScreenMinWidth; if (isLargeScreen) { return \_buildLargeScreenLayout(); } else { return const ContactGroupsPage(); // Reverted } }, ); } // ··· } \`\`\` ### Add navigation to contact groups The \`ContactGroupsPage\` already uses a \`\_ContactGroupsView\` and provides it with a callback. That callback needs to be updated to navigate when a group is tapped, rather than printing the group to the console. Ensure that the \`onListSelected\` callback and imports in \`lib/screens/contact\_groups.dart\` are implemented as follows:

<!--?code-excerpt "fwe/rolodex/lib/step4_navigation/screens/contact_groups.dart (contact_groups_page)"?-->

\`\`\`dart title="lib/screens/contact\_groups.dart" import 'contacts.dart'; class ContactGroupsPage extends StatelessWidget { const ContactGroupsPage({super.key}); @override Widget build(BuildContext context) { return \_ContactGroupsView( onListSelected: (list) => Navigator.of(context).push( CupertinoPageRoute( title: list.title, builder: (context) => ContactListsPage(listId: list.id), ), ), ); } } \`\`\` This small code block contains the most important new information on this page. \`Navigator.of(context)\` retrieves the nearest \`Navigator\` widget from the widget tree. The \`push\` method adds a new route to the navigator's stack, and displays the widget returned from the \`builder\` property. This is the most basic implementation of using stack-based navigation, where new screens are pushed on top of the current screen. To navigate back to the previous screen, you'd use the \`Navigator.pop\` method. \`CupertinoPageRoute\` creates iOS-style page transitions with the following features: - A slide-in animation from the right. - Automatic back button support. - Proper title handling. - Swipe-to-go-back gesture support. ### Create the sidebar component for large screens For large screens, you need a sidebar that doesn't navigate but instead updates the main content area. Thanks to the refactoring in the previous step, creating this component is more straightforward. Add this widget to the bottom of \`lib/screens/contact\_groups.dart\`:

<!--?code-excerpt "fwe/rolodex/lib/step4_navigation/screens/contact_groups.dart (contact_groups_sidebar)"?-->

\`\`\`dart /// A sidebar component for selecting contact groups on large screens. class ContactGroupsSidebar extends StatelessWidget { const ContactGroupsSidebar({ super.key, required this.selectedListId, required this.onListSelected, }); final int selectedListId; final void Function(int) onListSelected; @override Widget build(BuildContext context) { return \_ContactGroupsView( selectedListId: selectedListId, onListSelected: (list) => onListSelected(list.id), ); } } \`\`\` This sidebar component reuses the \`\_ContactGroupsView\` and provides a different callback. Instead of navigating, it calls \`onListSelected\` with the ID of the tapped list. It also passes the \`selectedListId\` to \`\_ContactGroupsView\` so that the selected item can be highlighted. ### Create the detail view for large screens For the large screen layout, you need a detail view that doesn't show navigation controls. Just like the sidebar, this can be recreated by reusing the \`\_ContactListView\`. Add this widget to the bottom of your \`contacts.dart\` file:

<!--?code-excerpt "fwe/rolodex/lib/step4_navigation/screens/contacts.dart (contact_list_detail)"?-->

\`\`\`dart class ContactListDetail extends StatelessWidget { const ContactListDetail({super.key, required this.listId}); final int listId; @override Widget build(BuildContext context) { return \_ContactListView(listId: listId, automaticallyImplyLeading: false); } } \`\`\` The detail view reuses \`\_ContactListView\` and sets the \`automaticallyImplyLeading\` parameter to \`false\` to hide the back button, as navigation is handled by the sidebar. ### Connect the sidebar to the adaptive layout Now, connect the sidebar to your adaptive layout. Update your \`adaptive\_layout.dart\` file to import the necessary files and update the large screen layout:

<!--?code-excerpt "fwe/rolodex/lib/step4_navigation/screens/adaptive_layout.dart (imports)"?-->

\`\`\`dart import 'package:cupertino\_ui/cupertino\_ui.dart'; import 'contact\_groups.dart'; import 'contacts.dart'; \`\`\` Then update the \`\_buildLargeScreenLayout\` method:

<!--?code-excerpt "fwe/rolodex/lib/step4_navigation/screens/adaptive_layout.dart (build_large_screen)"?-->

\`\`\`dart Widget \_buildLargeScreenLayout() { return CupertinoPageScaffold( backgroundColor: CupertinoColors.extraLightBackgroundGray, child: SafeArea( child: Row( children: \[ SizedBox( width: 320, child: ContactGroupsSidebar( selectedListId: selectedListId, onListSelected: \_onContactListSelected, ), ), Container(width: 1, color: CupertinoColors.separator), Expanded(child: ContactListDetail(listId: selectedListId)), ], ), ), ); } \`\`\` This code creates the classic menu-detail layout where the sidebar controls the content of the detail area. ### Test the adaptive navigation behavior Hot reload your app and test the navigation: \*\*Small screens (<600px width):\*\* - Tap contact groups to navigate to contact details. - Use the back button or a swipe gesture to return. - This is a classic stack-based navigation flow. \*\*Large screens (>600px width):\*\* - Click contact groups in the sidebar to update the detail view. - There is no navigation stack. The selection updates the content area. - This is a master-detail interface pattern. The app automatically chooses the appropriate navigation pattern based on screen size. This provides an optimal experience on both phones and tablets. :::note If you resize the app from a small screen to a large screen while on a contact detail page, and then press the back button, you might see a \`Hero\` tag exception. This happens because the \`Navigator\` still holds the pushed small-screen route while the large-screen layout simultaneously renders the detail view, causing duplicate widgets (like the navigation bar) in the widget tree during transition. This is an expected edge case for this simple architecture and can be ignored for the purpose of this learning pathway. ::: ### Review title: What you accomplished subtitle: Here's a summary of what you built and learned in this lesson. completed: true items: - title: Navigated between screens with Navigator.push icon: open\_in\_new details: >- \`Navigator.of(context).push\` adds a new route to the navigation stack. This is the foundation of stack-based navigation, where screens are pushed on top of each other and popped to go back. - title: Used CupertinoPageRoute for iOS-style transitions icon: swipe\_right details: >- \`CupertinoPageRoute\` provides support for native iOS navigation features: slide-in animations from the right, automatic back buttons, proper title handling, and swipe-to-go-back gesture support. - title: Implemented adaptive navigation patterns icon: devices details: >- You set up different navigation patterns for small and large screens. Small screens use stack-based navigation where tapping a group pushes a new screen. Large screens use a master-detail pattern where selecting a group updates the detail panel without navigation. - title: Completed the Rolodex app icon: celebration details: >- You've built a complete iOS-style contacts app with adaptive layouts, advanced scrolling, collapsible headers with search, and responsive navigation. These are common patterns used in production apps! ### Test yourself - question: "What does \`Navigator.of(context).push\` do?" options: - text: Replaces the current screen with a new one. correct: false explanation: "Push adds to the stack; \`pushReplacement\` replaces the current screen." - text: Adds a new route to the navigation stack, displaying it on top of the current screen. correct: true explanation: Push adds the new route to the stack, allowing users to go back to the previous screen. - text: Removes the current screen from the navigation stack. correct: false explanation: "That's what \`pop\` does; push adds a new screen." - text: Opens a dialog box over the current screen. correct: false explanation: "Dialogs use \`showDialog\`; Navigator.push navigates to a full screen." - question: "What does \`Navigator.of(context).pop()\` do?" options: - text: Closes the entire app. correct: false explanation: Pop only removes the current route; it doesn't close the app. - text: Removes the current route from the navigation stack, returning to the previous screen. correct: true explanation: Pop removes the top route from the stack, revealing the screen beneath it. - text: Clears all routes and shows the home screen. correct: false explanation: That would require popUntil or pushAndRemoveUntil; pop removes only the top route. - text: Refreshes the current screen with new data. correct: false explanation: Pop navigates back; to refresh, you'd use setState or other state management.

----
url: https://docs.flutter.dev/learn/pathway.md
----

\# Flutter learning pathway > This learning pathway walks you through the basics of both Dart and Flutter.Welcome to the Dart and Flutter Getting Started pathway. In it, you'll set up your development environment, learn how to write Dart code, and build three small Flutter apps step-by-step. This learning pathway spans the Dart and Flutter websites, YouTube, and your IDE. By the end, you'll have a solid foundation in both Dart and Flutter. ### Set up your development environment Before you can start building Flutter apps, you'll need to set up your development environment. Follow the installation guide to get Flutter and all required dependencies installed on your machine. \[Get Started →]\(/learn/pathway/quick-install) ### Complete the Dart Getting Started tutorial Flutter uses the Dart programming language. If you're new to Dart, complete this interactive tutorial to learn the fundamentals—variables, functions, classes, and more. If you're in a hurry and already familiar with modern, object-oriented programming languages, you can safely skip this step. [Dart Getting Started tutorial → ](https://dart.dev/learn/tutorial)### Complete the Flutter Getting Started tutorial Now that you have Dart skills under your belt, dive into Flutter! This hands-on tutorial walks you through building three small Flutter apps step-by-step. \[Flutter Getting Started tutorial →]\(/learn/pathway/tutorial) ### Understand how Flutter works Deepen your understanding of how Flutter works under the hood by watching this video series. Learn about the widget tree, rendering pipeline, and what makes Flutter unique. This series provides the knowledge you need to know to take full advantage of the framework. \[Watch: How Flutter Works →]\(/learn/pathway/how-flutter-works) ## Ready to start? Get started by installing Flutter and setting up your development environment: \[Start with Step 1: Quick Install →]\(/learn/pathway/quick-install) We're always looking for ways to improve the Getting Started experience. Moving forward, we plan to add more resources to this section of the website. We'd love to know what you think and what you'd like to see. Please take a moment to share your feedback with us. [Leave feedback](https://google.qualtrics.com/jfe/form/SV_6A9KxXR7XmMrNsy?page='index')

----
url: https://docs.flutter.dev/cookbook/testing/integration/profiling.md
----

\# Measure performance with an integration test > How to profile performance for a Flutter app.

<!--?code-excerpt path-base="cookbook/testing/integration/profiling/"?-->

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. :::note Recording performance timelines isn't supported on web. For performance profiling on web, see \[Debugging performance for web apps]\[] ::: This recipe uses the following steps: 1. Write a test that scrolls through a list of items. 2. Record the performance of the app. 3. Save the results to disk. 4. Run the test. 5. Review the results. ## 1. Write a test that scrolls through a list of items 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]\[] recipe in widget tests. Follow the instructions in that recipe to create an app and write a test to verify that everything works as expected. ## 2. Record the performance of the app Next, record the performance of the app as it scrolls through the list. Perform this task using the \[\`traceAction()\`]\[] method provided by the \[\`IntegrationTestWidgetsFlutterBinding\`]\[] class. This method runs the provided function and records 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()\` creates a report data \`Map\` that contains the \`Timeline\`. Specify the \`reportKey\` when running more than one \`traceAction\`. By default all \`Timelines\` are stored with the key \`timeline\`, in this example the \`reportKey\` is changed to \`scrolling\_timeline\`.

<!--?code-excerpt "integration_test/scrolling_test.dart (traceAction)"?-->

\`\`\`dart await binding.traceAction(() async { // Scroll until the item to be found appears. await tester.scrollUntilVisible( itemFinder, 500.0, scrollable: listFinder, ); }, reportKey: 'scrolling\_timeline'); \`\`\` ## 3. Save the results to disk 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, convert the \`Timeline\` into a \[\`TimelineSummary\`]\[]. The \`TimelineSummary\` can perform two tasks that make it easier to review the results: 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\`. To capture the results, create a file named \`perf\_driver.dart\` in the \`test\_driver\` folder and add the following code:

<!--?code-excerpt "test_driver/perf_driver.dart"?-->

\`\`\`dart import 'package:flutter\_driver/flutter\_driver.dart' as driver; import 'package:integration\_test/integration\_test\_driver.dart'; Future main() { return integrationDriver( responseDataCallback: (data) async { if (data != null) { final timeline = driver.Timeline.fromJson( data\['scrolling\_timeline'] as Map, ); // Convert the Timeline into a TimelineSummary that's easier to // read and understand. final summary = driver.TimelineSummary.summarize(timeline); // Then, 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, save the summary to disk by setting includeSummary // to true await summary.writeTimelineToFile( 'scrolling\_timeline', pretty: true, includeSummary: true, ); } }, ); } \`\`\` The \`integrationDriver\` function has a \`responseDataCallback\` which you can customize. By default, it writes the results to the \`integration\_response\_data.json\` file, but you can customize it to generate a summary like in this example. ## 4. Run the test 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: \`\`\`console flutter drive \ --driver=test\_driver/perf\_driver.dart \ --target=integration\_test/scrolling\_test.dart \ --profile \`\`\` The \`--profile\` option means to compile the app for the "profile mode" rather than the "debug mode", so that the benchmark result is closer to what will be experienced by end users. :::note Run the command with \`--no-dds\` when running on a mobile device or emulator. This option disables the Dart Development Service (DDS), which won't be accessible from your computer. ::: ## 5. Review the results 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, 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\`. The tracing tools provide a convenient interface for inspecting the timeline data to discover the source of a performance issue. ### Summary example \`\`\`json { "average\_frame\_build\_time\_millis": 4.2592592592592595, "worst\_frame\_build\_time\_millis": 21.0, "missed\_frame\_build\_budget\_count": 2, "average\_frame\_rasterizer\_time\_millis": 5.518518518518518, "worst\_frame\_rasterizer\_time\_millis": 51.0, "missed\_frame\_rasterizer\_budget\_count": 10, "frame\_count": 54, "frame\_build\_times": \[ 6874, 5019, 3638 ], "frame\_rasterizer\_times": \[ 51955, 8468, 3129 ] } \`\`\` ## Complete example \*\*integration\_test/scrolling\_test.dart\*\*

<!--?code-excerpt "integration_test/scrolling_test.dart" replace="/your_integration_test/your_package/g;"?-->

\`\`\`dart import 'package:flutter\_test/flutter\_test.dart'; import 'package:integration\_test/integration\_test.dart'; import 'package:material\_ui/material\_ui.dart'; import 'package:your\_package/main.dart'; void main() { final binding = IntegrationTestWidgetsFlutterBinding.ensureInitialized(); testWidgets('Counter increments smoke test', (tester) async { // Build our app and trigger a frame. await tester.pumpWidget( MyApp(items: List.generate(10000, (i) => 'Item $i')), ); final listFinder = find.byType(Scrollable); final itemFinder = find.byKey(const ValueKey('item\_50\_text')); await binding.traceAction(() async { // Scroll until the item to be found appears. await tester.scrollUntilVisible( itemFinder, 500.0, scrollable: listFinder, ); }, reportKey: 'scrolling\_timeline'); }); } \`\`\` \*\*test\_driver/perf\_driver.dart\*\*

<!--?code-excerpt "test_driver/perf_driver.dart"?-->

\`\`\`dart import 'package:flutter\_driver/flutter\_driver.dart' as driver; import 'package:integration\_test/integration\_test\_driver.dart'; Future main() { return integrationDriver( responseDataCallback: (data) async { if (data != null) { final timeline = driver.Timeline.fromJson( data\['scrolling\_timeline'] as Map, ); // Convert the Timeline into a TimelineSummary that's easier to // read and understand. final summary = driver.TimelineSummary.summarize(timeline); // Then, 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, save the summary to disk by setting includeSummary // to true await summary.writeTimelineToFile( 'scrolling\_timeline', pretty: true, includeSummary: true, ); } }, ); } \`\`\` \[\`IntegrationTestWidgetsFlutterBinding\`]: https\://api.flutter.dev/flutter/package-integration\_test\_integration\_test/IntegrationTestWidgetsFlutterBinding-class.html \[Scrolling]: /cookbook/testing/widget/scrolling \[\`Timeline\`]: https\://api.flutter.dev/flutter/flutter\_driver/Timeline-class.html \[\`TimelineSummary\`]: https\://api.flutter.dev/flutter/flutter\_driver/TimelineSummary-class.html \[\`traceAction()\`]: https\://api.flutter.dev/flutter/flutter\_driver/FlutterDriver/traceAction.html \[Debugging performance for web apps]: /perf/web-performance

----
url: https://docs.flutter.dev/security.md
----

\# Security > An overview of the Flutter's team philosophy and processes for security. The Flutter team takes the security of Flutter and the applications created with it seriously. This page describes how to report any vulnerabilities you might find, and lists best practices to minimize the risk of introducing a vulnerability. ## Security philosophy Flutter security strategy is based on five key pillars: \* \*\*Identify\*\*: Track and prioritize key security risks by identifying core assets, key threats, and vulnerabilities. \* \*\*Detect\*\*: Detect and identify vulnerabilities using techniques and tools like vulnerability scanning, static application security testing, and fuzzing. \* \*\*Protect\*\*: Eliminate risks by mitigating known vulnerabilities and protect critical assets against source threats. \* \*\*Respond\*\*: Define processes to report, triage, and respond to vulnerabilities or attacks. \* \*\*Recover\*\*: Build capabilities to contain and recover from an incident with minimal impact. ## Reporting vulnerabilities Before reporting a security vulnerability found by a static analysis tool, consider checking our list of \[known false positives]\[]. Report security vulnerabilities to \[https\://g.co/vulnz]\(https\://g.co/vulnz) and include a description of the issue, the steps you took to create the issue, affected versions, and if known, mitigations for the issue. We use g.co/vulnz for our intake, and do coordination and disclosure on GitHub (including using GitHub Security Advisory). The Google Security Team will respond within 5 working days of your report on g.co/vulnz. You may also reach out to the team via our public Discord chat channels; however, please also make sure to make vulnerability reports to g.co/vulnz, and avoid revealing information about vulnerabilities in public if that could put users at risk. You should expect a close collaboration as we work to resolve the security vulnerability you have reported. Please reach out to security\@flutter.dev only if you do not receive a response to a g.co/vulnz report within the above mentioned 5 working days. For more details on how we handle security vulnerabilities, see our \[security policy]\[]. \[Discord chat channels]: https\://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md \[known false positives]: /reference/security-false-positives \[security policy]: https\://github.com/flutter/flutter/security/policy ## Flagging existing issues as security-related If you believe that an existing GitHub issue is security-related, we ask that you both report the issue to g.co/vulnz and send an email to security\@flutter.dev. The email should include the GitHub issue ID and a short description of why it should be handled according to this security policy. Security reports are not tracked explicitly in the GitHub issue database. We use GitHub's security advisory feature to track open security reports. ## Supported versions We commit to publishing security updates for the version of Flutter currently on the \`stable\` branch. ## Expectations We treat security reports equivalent to a P0 priority level. This means that we attempt to fix them as quickly as possible. Depending on our release schedule, we will release either a new beta or a stable hotfix for any major security report found in the most recent stable version of our SDK, whichever is most expedient. Any vulnerability reported for flutter websites like docs.flutter.dev doesn't require a release and will be fixed in the website itself. ## Bug Bounty programs Non-Google teams that use or contribute to Flutter are also welcome to include Flutter within the scope of their bug bounty programs. To have your program listed, please contact \`security\@flutter.dev\`. Google considers Flutter to be in scope for the \[Google Open Source Software Vulnerability Reward Program]\[google-oss-vrp]. \[google-oss-vrp]: https\://bughunters.google.com/open-source-security ## Receiving security updates The best way to receive security updates is to subscribe to the \[flutter-announce]\[] mailing list or watch updates to the \[Discord channel]\[]. We also announce security updates in the technical release blog post. \[Discord channel]: https\://discord.gg/BS8KZyg \[flutter-announce]: https\://groups.google.com/forum/#!forum/flutter-announce ## Best practices \* \*\*Keep current with the latest Flutter SDK releases.\*\* We regularly update Flutter, and these updates might fix security defects discovered in previous versions. \* \*\*Keep your application's dependencies up to date.\*\* Make sure you \[upgrade your package dependencies]\[] to keep the dependencies up to date. Avoid pinning to specific versions for your dependencies and, if you do, make sure you check periodically to see if your dependencies have had security updates, and update the pin accordingly. \* \*\*Keep your copy of Flutter up to date.\*\* Private, customized versions of Flutter tend to fall behind the current version and might not include important security fixes and enhancements. Instead, routinely update your copy of Flutter. If you're making changes to improve Flutter, be sure to update your fork and consider sharing your changes with the community. \[upgrade your package dependencies]: /install/upgrade

----
url: https://docs.flutter.dev/tools/formatting.md
----

\# Code formatting > Flutter's code formatter formats your code following recommended style guidelines. 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 might be better spent on code behavior rather than code style. ## Automatically formatting code in VS Code Install the \`Flutter\` extension (see \[VS Code setup]\[]) to get automatic formatting of code in VS Code. To automatically format the code in the current source code window, right-click in the code window and select \`Format Document\`. You can add a keyboard shortcut to this VS Code \*\*Preferences\*\*. To automatically format code whenever you save a file, set the \`editor.formatOnSave\` setting to \`true\`. \[VS Code setup]: /tools/vs-code#setup ## Automatically formatting code in Android Studio and IntelliJ Install the \`Dart\` plugin (see \[Android Studio and IntelliJ setup]\[]) to get automatic formatting of code in Android Studio and IntelliJ. To format your code in the current source code window: \* On macOS, press `Cmd` + `Option` + `L`. \* On Windows and Linux, press `Ctrl` + `Alt` + `L`. Android Studio and IntelliJ also provide a checkbox named \*\*Format code on save\*\* on the Flutter page in \*\*Preferences\*\* on macOS or \*\*Settings\*\* on Windows and Linux. This option corrects formatting in the current file when you save it. \[Android Studio and IntelliJ setup]: /tools/android-studio#setup ## Automatically formatting code with the \`dart\` command To correct code formatting in the command line interface (CLI), run the \`dart format\` command: \`\`\`console $ dart format path1 path2 \[...] \`\`\` To learn more about the Dart formatter, check out the dart.dev docs on \[\`dart format\`]\[]. \[\`dart format\`]: https\://dart.dev/tools/dart-format

----
url: https://docs.flutter.dev/app-architecture/case-study/testing
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Testing each layer](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

## Testing the UI layer

[#](#testing-the-ui-layer)

One way to determine whether your architecture is sound is considering how easy (or difficult) the application is to test. Because view models and views have well-defined inputs, their dependencies can easily be mocked or faked, and unit tests are easily written.

### ViewModel unit tests

[#](#viewmodel-unit-tests)

To test the UI logic of the view model, you should write unit tests that don't rely on Flutter libraries or testing frameworks.

Repositories are a view model's only dependencies (unless you're implementing [use-cases](/app-architecture/guide#optional-domain-layer)), and writing `mocks` or `fakes` of the repository is the only setup you need to do. In this example test, a fake called `FakeBookingRepository` is used.

home\_screen\_test.dart

dart

```
void main() {
  group('HomeViewModel tests', () {
    test('Load bookings', () {
      // HomeViewModel._load is called in the constructor of HomeViewModel.
      final viewModel = HomeViewModel(
        bookingRepository: FakeBookingRepository()
          ..createBooking(kBooking),
        userRepository: FakeUserRepository(),
      );
​
      expect(viewModel.bookings.isNotEmpty, true);
    });
  });
}
```

The [`FakeBookingRepository`](https://github.com/flutter/samples/blob/main/compass_app/app/testing/fakes/repositories/fake_booking_repository.dart) class implements [`BookingRepository`](https://github.com/flutter/samples/tree/main/compass_app/app/lib/data/repositories/booking). In the [data layer section](/app-architecture/case-study/data-layer) of this case-study, the `BookingRepository` class is explained thoroughly.

fake\_booking\_repository.dart

dart

```
class FakeBookingRepository implements BookingRepository {
  List<Booking> bookings = List.empty(growable: true);
​
  @override
  Future<Result<void>> createBooking(Booking booking) async {
    bookings.add(booking);
    return Result.ok(null);
  }
  // ...
}
```

### View widget tests

[#](#view-widget-tests)

Once you've written tests for your view model, you've already created the fakes you need to write widget tests as well. The following example shows how the `HomeScreen` widget tests are set up using the `HomeViewModel` and needed repositories:

home\_screen\_test.dart

dart

```
void main() {
  group('HomeScreen tests', () {
    late HomeViewModel viewModel;
    late MockGoRouter goRouter;
    late FakeBookingRepository bookingRepository;
​
    setUp(() {
      bookingRepository = FakeBookingRepository()
        ..createBooking(kBooking);
      viewModel = HomeViewModel(
        bookingRepository: bookingRepository,
        userRepository: FakeUserRepository(),
      );
      goRouter = MockGoRouter();
      when(() => goRouter.push(any())).thenAnswer((_) => Future.value(null));
    });
​
    // ...
  });
}
```

This setup creates the two fake repositories needed, and passes them into a `HomeViewModel` object. This class doesn't need to be faked.

After the view model and its dependencies are defined, the Widget tree that will be tested needs to be created. In the tests for `HomeScreen`, a `loadWidget` method is defined.

home\_screen\_test.dart

dart

```
void main() {
  group('HomeScreen tests', () {
    late HomeViewModel viewModel;
    late MockGoRouter goRouter;
    late FakeBookingRepository bookingRepository;
​
    setUp(
      // ...
    );
​
    void loadWidget(WidgetTester tester) async {
      await testApp(
        tester,
        ChangeNotifierProvider.value(
          value: FakeAuthRepository() as AuthRepository,
          child: Provider.value(
            value: FakeItineraryConfigRepository() as ItineraryConfigRepository,
            child: HomeScreen(viewModel: viewModel),
          ),
        ),
        goRouter: goRouter,
      );
    }
​
    // ...
  });
}
```

This method turns around and calls `testApp`, a generalized method used for all widget tests in the compass app. It looks like this:

testing/app.dart

dart

```
void testApp(
  WidgetTester tester,
  Widget body, {
  GoRouter? goRouter,
}) async {
  tester.view.devicePixelRatio = 1.0;
  await tester.binding.setSurfaceSize(const Size(1200, 800));
  await mockNetworkImages(() async {
    await tester.pumpWidget(
      MaterialApp(
        localizationsDelegates: [
          GlobalWidgetsLocalizations.delegate,
          GlobalMaterialLocalizations.delegate,
          AppLocalizationDelegate(),
        ],
        theme: AppTheme.lightTheme,
        home: InheritedGoRouter(
          goRouter: goRouter ?? MockGoRouter(),
          child: Scaffold(
            body: body,
          ),
        ),
      ),
    );
  });
}
```

This function's only job is to create a widget tree that can be tested.

The `loadWidget` method passes in the unique parts of a widget tree for testing. In this case, that includes the `HomeScreen` and its view model, as well as some additional faked repositories that are higher in the widget tree.

The most important thing to take away is that view and view model tests only require mocking repositories if your architecture is sound.

## Testing the data layer

[#](#testing-the-data-layer)

Similar to the UI layer, the components of the data layer have well-defined inputs and outputs, making both sides fake-able. To write unit tests for any given repository, mock the services that it depends on. The following example shows a unit test for the `BookingRepository`.

booking\_repository\_remote\_test.dart

dart

```
void main() {
  group('BookingRepositoryRemote tests', () {
    late BookingRepository bookingRepository;
    late FakeApiClient fakeApiClient;
​
    setUp(() {
      fakeApiClient = FakeApiClient();
      bookingRepository = BookingRepositoryRemote(
        apiClient: fakeApiClient,
      );
    });
​
    test('should get booking', () async {
      final result = await bookingRepository.getBooking(0);
      final booking = result.asOk.value;
      expect(booking, kBooking);
    });
  });
}
```

To learn more about writing mocks and fakes, check out examples in the [Compass App `testing` directory](https://github.com/flutter/samples/tree/main/compass_app/app/testing) or read [Flutter's testing documentation](/testing/overview).

## Feedback

[#](#feedback)

As this section of the website is evolving, we [welcome your feedback](https://google.qualtrics.com/jfe/form/SV_4T0XuR9Ts29acw6?page=%22case-study/testing%22)!

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/app-architecture/case-study/testing.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/app-architecture/case-study/testing\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/app-architecture/case-study/testing.md "Report an issue with this page").

----
url: https://docs.flutter.dev/cookbook/maintenance
----

[Report errors to a service](/cookbook/maintenance/error-reporting)

[How to keep track of errors that users encounter.](/cookbook/maintenance/error-reporting)

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/maintenance/index.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/cookbook/maintenance\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/maintenance/index.md "Report an issue with this page").

----
url: https://docs.flutter.dev/app-architecture/case-study/data-layer.md
----

\# Data layer > A walk-through of the data layer of an app that implements MVVM architecture. The data layer of an application, known as the \*model\* in MVVM terminology, is the source of truth for all application data. As the source of truth, it's the only place that application data should be updated. It's responsible for consuming data from various external APIs, exposing that data to the UI, handling events from the UI that require data to be updated, and sending update requests to those external APIs as needed. The data layer in this guide has two main components, \[repositories]\[] and \[services]\[]. !\[A diagram that highlights the data layer components of an application.]\(/assets/images/docs/app-architecture/guide/feature-architecture-simplified-Data-highlighted.png) \* \*\*Repositories\*\* are the source of the truth for application data, and contain logic that relates to that data, like updating the data in response to new user events or polling for data from services. Repositories are responsible for synchronizing the data when offline capabilities are supported, managing retry logic, and caching data. \* \*\*Services\*\* are stateless Dart classes that interact with APIs, like HTTP servers and platform plugins. Any data that your application needs that isn't created inside the application code itself should be fetched from within service classes. ## Define a service A service class is the least ambiguous of all the architecture components. It's stateless, and its functions don't have side effects. Its only job is to wrap an external API. There's generally one service class per data source, such as a client HTTP server or a platform plugin. !\[A diagram that shows the inputs and outputs of service objects.]\(/assets/images/docs/app-architecture/case-study/mvvm-case-study-services-architecture.png) In the Compass app, for example, there's an \[\`APIClient\`]\[] service that handles the CRUD calls to the client-facing server. \`\`\`dart title=api\_client.dart class ApiClient { // Some code omitted for demo purposes. Future>> getContinents() async { /\* ... \*/ } Future>> getDestinations() async { /\* ... \*/ } Future>> getActivityByDestination(String ref) async { /\* ... \*/ } Future>> getBookings() async { /\* ... \*/ } Future> getBooking(int id) async { /\* ... \*/ } Future> postBooking(BookingApiModel booking) async { /\* ... \*/ } Future> deleteBooking(int id) async { /\* ... \*/ } Future> getUser() async { /\* ... \*/ } } \`\`\` The service itself is a class, where each method wraps a different API endpoint and exposes asynchronous response objects. Continuing the earlier example of deleting a saved booking, the \`deleteBooking\` method returns a \`Future>\`. :::note Some methods return data classes that are specifically for raw data from the API, such as the \`BookingApiModel\` class. As you'll soon see, repositories extract data and expose it in a different format. ::: ## Define a repository A repository's sole responsibility is to manage application data. A repository is the source of truth for a single type of application data, and it should be the only place where that data type is mutated. The repository is responsible for polling new data from external sources, handling retry logic, managing cached data, and transforming raw data into domain models. !\[A diagram that highlights the repository component of an application.]\(/assets/images/docs/app-architecture/guide/feature-architecture-simplified-Repository-highlighted.png) You should have a separate repository for each different type of data in your application. For example, the Compass app has repositories called \`UserRepository\`, \`BookingRepository\`, \`AuthRepository\`, \`DestinationRepository\`, and more. The following example is the \`BookingRepository\` from the Compass app, and shows the basic structure of a repository. \`\`\`dart title=booking\_repository\_remote.dart class BookingRepositoryRemote implements BookingRepository { BookingRepositoryRemote({ required ApiClient apiClient, }) : \_apiClient = apiClient; final ApiClient \_apiClient; List? \_cachedDestinations; Future> createBooking(Booking booking) async {...} Future> getBooking(int id) async {...} Future>> getBookingsList() async {...} Future> delete(int id) async {...} } \`\`\` :::note Development versus staging environments The class in the previous example is \`BookingRepositoryRemote\`, which extends an abstract class called \`BookingRepository\`. This base class is used to create repositories for different environments. For example, the compass app also has a class called \`BookingRepositoryLocal\`, which is used for local development. You can see the differences between the \[\`BookingRepository\` classes on GitHub]\[]. ::: The \`BookingRepository\` takes the \`ApiClient\` service as an input, which it uses to get and update the raw data from the server. It's important that the service is a private member, so that the UI layer can't bypass the repository and call a service directly. With the \`ApiClient\` service, the repository can poll for updates to a user's saved bookings that might happen on the server, and make \`POST\` requests to delete saved bookings. The raw data that a repository transforms into application models can come from multiple sources and multiple services, and therefore repositories and services have a many-to-many relationship. A service can be used by any number of repositories, and a repository can use more than one service. !\[A diagram that highlights the data layer components of an application.]\(/assets/images/docs/app-architecture/guide/feature-architecture-simplified-Data-highlighted.png) ### Domain models The \`BookingRepository\` outputs \`Booking\` and \`BookingSummary\` objects, which are \*domain models\*. All repositories output corresponding domain models. These data models differ from API models in that they only contain the data needed by the rest of the app. API models contain raw data that often needs to be filtered, combined, or deleted to be useful to the app's view models. The repo refines the raw data and outputs it as domain models. In the example app, domain models are exposed through return values on methods like \`BookingRepository.getBooking\`. The \`getBooking\` method is responsible for getting the raw data from the \`ApiClient\` service, and transforming it into a \`Booking\` object. It does this by combining data from multiple service endpoints. \`\`\`dart title=booking\_repository\_remote.dart highlightLines=14-21 // This method was edited for brevity. Future> getBooking(int id) async { try { // Get the booking by ID from server. final resultBooking = await \_apiClient.getBooking(id); if (resultBooking is Error) { return Result.error(resultBooking.error); } final booking = resultBooking.asOk.value; final destination = \_apiClient.getDestination(booking.destinationRef); final activities = \_apiClient.getActivitiesForBooking( booking.activitiesRef); return Result.ok( Booking( startDate: booking.startDate, endDate: booking.endDate, destination: destination, activity: activities, ), ); } on Exception catch (e) { return Result.error(e); } } \`\`\` :::note In the Compass app, service classes return \`Result\` objects. \`Result\` is a utility class that wraps asynchronous calls and makes it easier to handle errors and manage UI state that relies on asynchronous calls. This pattern is a recommendation, but not a requirement. The architecture recommended in this guide can be implemented without it. You can learn about this class in the \[Result cookbook recipe]\[]. ::: ### Complete the event cycle Throughout this page, you've seen how a user can delete a saved booking, starting with an event—a user swiping on a \`Dismissible\` widget. The view model handles that event by delegating the actual data mutation to the \`BookingRepository\`. The following snippet shows the \`BookingRepository.deleteBooking\` method. \`\`\`dart title=booking\_repository\_remote.dart Future> delete(int id) async { try { return \_apiClient.deleteBooking(id); } on Exception catch (e) { return Result.error(e); } } \`\`\` The repository sends a \`POST\` request to the API client with the \`\_apiClient.deleteBooking\` method, and returns a \`Result\`. The \`HomeViewModel\` consumes the \`Result\` and the data it contains, then ultimately calls \`notifyListeners\`, completing the cycle. \[repositories]: /app-architecture/guide#repositories \[services]: /app-architecture/guide#services \[\`APIClient\`]: https\://github.com/flutter/samples/blob/main/compass\_app/app/lib/data/services/api/api\_client.dart \[\`sealed\`]: https\://dart.dev/language/class-modifiers#sealed \[\`BookingRepository\` classes on GitHub]: https\://github.com/flutter/samples/tree/main/compass\_app/app/lib/data/repositories/booking \[Result cookbook recipe]: /app-architecture/design-patterns/result ## Feedback As this section of the website is evolving, we \[welcome your feedback]\[]! \[welcome your feedback]: https\://google.qualtrics.com/jfe/form/SV\_4T0XuR9Ts29acw6?page="case-study/data-layer"

----
url: https://docs.flutter.dev/learn
----

## For beginners

[#](#for-beginners)

Start here for an overview of how to get set up, build Flutter apps, write Dart code, and understand how Flutter works.

This is the **recommended** set of resources to start learning Flutter! Learn how to write Dart code in the first tutorial, then build a series of apps using Flutter in the second. You'll learn the essential best practices for Flutter app development, app architecture, fetching data from the network, and more. Great for beginners with minimal programming experience.

[Start learning](/learn/pathway)

## For experienced developers

[#](#for-experienced-developers)

Dive deeper into more specific topics in Flutter development with sample code, YouTube videos, codelabs, and more.

Find sample code, YouTube videos, codelabs, and more written by the experts on the Flutter team at Google. This index contains the resources you need to learn any aspect of Flutter.

[Explore all resources](/reference/learning-resources)

Learn the recommended app architecture to build a Flutter app. This guide contains everything you need to know to understand and implement MVVM architecture in a Flutter app.

[Start learning](/app-architecture)

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/learn/index.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/learn\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/learn/index.md "Report an issue with this page").

----
url: https://docs.flutter.dev/deployment/web.md
----

\# Build and release a web app > How to prepare for and release a web app. During a typical development cycle, you test an app using \`flutter run -d chrome\` (for example) at the command line. This builds a \_debug\_ version of your app. This page helps you prepare a \_release\_ version of your app and covers the following topics: \* \[Building the app for release]\(#building-the-app-for-release) \* \[Deploying to the web]\(#deploying-to-the-web) \* \[Deploying to Firebase Hosting]\(#deploying-to-firebase-hosting) \* \[Handling images on the web]\(#handling-images-on-the-web) \* \[Opting-in to WebAssembly]\(#opting-in-to-webassembly) \* \[Minification]\(#minification) ## Building the app for release Build the app for deployment using the \`flutter build web\` command. \`\`\`console flutter build web \`\`\` To build your application with WebAssembly (Wasm), pass the \`--wasm\` flag: \`\`\`console flutter build web --wasm \`\`\` This generates the app, including the assets, and places the files into the \`/build/web\` directory of the project. To validate the release build of your app, launch a web server (for example, \`python -m http.server 8000\`, or by using the \[dhttpd]\[] package), and open the /build/web directory. Navigate to \`localhost:8000\` in your browser (given the python SimpleHTTPServer example) to view the release version of your app. ## Additional build flags You might need to deploy a profile or debug build for testing. To do this, pass the \`--profile\` or \`--debug\` flag to the \`flutter build web\` command. Profile builds are specialized for performance profiling using Chrome DevTools, and debug builds can be used to configure dart2js to respect assertions and change the optimization level (using the \`-O\` flag.) ### Source maps To generate source maps for symbolication in crash reporting services, pass the \`--source-maps\` flag: \`\`\`console flutter build web --source-maps \`\`\` Generating source maps creates separate \`.js.map\` or \`.wasm.map\` files (or both, depending on your build target) in the \`build/web\` directory without adding binary overhead to the compiled output. :::warning Exclude source maps from public hosting By default, standard web deployment tools upload all files in \`build/web/\`. If source maps are deployed publicly, browser DevTools will automatically download them, exposing un-minified symbol names, class hierarchies, and local file paths. Upload \`.map\` files to your private error-monitoring service (such as Sentry or Datadog) during CI/CD, and exclude \`\*.map\` files from public web hosting (for example, using \`"ignore": \["\*\*/\*.map"]\` in \`firebase.json\` or stripping them prior to deployment). ::: ## Opting-in to WebAssembly To compile and optimize your app for WebAssembly, use the \`--wasm\` flag when running or building. For more information, see \[Support for WebAssembly (Wasm)]\[Wasm support]. :::important Serving WebAssembly builds WebAssembly builds use multiple threads for improved rendering performance and require your web server to return specific COEP/COOP HTTP response headers (\`Cross-Origin-Embedder-Policy: credentialless\` and \`Cross-Origin-Opener-Policy: same-origin\`). For full details, see \[Support for WebAssembly (Wasm)]\[Wasm support]. ::: ## Deploying to the web When you are ready to deploy your app, upload the release bundle to Firebase, the cloud, or a similar service. Here are a few possibilities, but there are many others: \* \[Firebase Hosting]\[] \* \[GitHub Pages]\[] \* \[Google Cloud Hosting]\[] ## Deploying to Firebase Hosting You can use the Firebase CLI to build and release your Flutter app with Firebase Hosting. ### Before you begin To get started, \[install or update]\[install-firebase-cli] the Firebase CLI: \`\`\`console npm install -g firebase-tools \`\`\` ### Initialize Firebase 1. Enable the web frameworks preview to the \[Firebase framework-aware CLI]\[]: \`\`\`console firebase experiments:enable webframeworks \`\`\` 2. In an empty directory or an existing Flutter project, run the initialization command: \`\`\`console firebase init hosting \`\`\` 3. Answer \`yes\` when asked if you want to use a web framework. 4. If you're in an empty directory, you'll be asked to choose your web framework. Choose \`Flutter Web\`. 5. Choose your hosting source directory; this could be an existing flutter app. 6. Select a region to host your files. 7. Choose whether to set up automatic builds and deploys with GitHub. 8. Deploy the app to Firebase Hosting: \`\`\`console firebase deploy \`\`\` Running this command automatically runs \`flutter build web --release\`, so you don't have to build your app in a separate step. To learn more, visit the official \[Firebase Hosting]\[] documentation for Flutter on the web. ## Handling images on the web The web supports the standard \`Image\` widget to display images. By design, web browsers run untrusted code without harming the host computer. This limits what you can do with images compared to mobile and desktop platforms. For more information, see \[Displaying images on the web]\[]. ## Minification To improve app start-up the compiler reduces the size of the compiled code by removing unused code (known as \_tree shaking\_), and by renaming code symbols to shorter strings (e.g. by renaming \`AlignmentGeometryTween\` to something like \`ab\`). Which of these two optimizations are applied depends on the build mode: | Type of web app build | Code minified? | Tree shaking performed? | |-----------------------|----------------|-------------------------| | debug | No | No | | profile | No | Yes | | release | Yes | Yes | ## Embedding a Flutter app into an HTML page See \[Embedding Flutter web]\[]. \[Embedding Flutter web]: /platform-integration/web/embedding-flutter-web \[dhttpd]: https\://pub.dev/packages/dhttpd \[Displaying images on the web]: /platform-integration/web/web-images \[Firebase Hosting]: https\://firebase.google.com/docs/hosting/frameworks/flutter \[Firebase framework-aware CLI]: https\://firebase.google.com/docs/hosting/frameworks/frameworks-overview \[install-firebase-cli]: https\://firebase.google.com/docs/cli#install\_the\_firebase\_cli \[GitHub Pages]: https\://pages.github.com/ \[give us feedback]: https\://github.com/flutter/flutter/issues/new?title=%5Bweb%5D:+%3Cdescribe+issue+here%3E\&labels=%E2%98%B8+platform-web\&body=Describe+your+issue+and+include+the+command+you%27re+running,+flutter\_web%20version,+browser+version \[Google Cloud Hosting]: https\://cloud.google.com/solutions/web-hosting \[Wasm support]: /platform-integration/web/wasm \[Web renderers]: /platform-integration/web/renderers

----
url: https://docs.flutter.dev/cookbook/testing/unit/mocking
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Mock dependencies using Mockito](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

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 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 and 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 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. Write these alternative implementations by hand or make use of the [Mockito package](https://pub.dev/packages/mockito) as a shortcut.

This recipe demonstrates the basics of mocking with the Mockito package using the following steps:

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.

For more information, see the [Mockito package](https://pub.dev/packages/mockito) documentation.

## 1. Add the package dependencies

[#](#1-add-the-package-dependencies)

To use the `mockito` package, add it to the `pubspec.yaml` file along with the `flutter_test` dependency in the `dev_dependencies` section.

This example also uses the `http` package, so define that dependency in the `dependencies` section.

`mockito: 5.0.0` supports Dart's null safety thanks to code generation. To run the required code generation, add the `build_runner` dependency in the `dev_dependencies` section.

To add the dependencies, run `flutter pub add`:

```
$ flutter pub add http dev:mockito dev:build_runner
```

## 2. Create a function to test

[#](#2-create-a-function-to-test)

In this example, unit test the `fetchAlbum` function from the [Fetch data from the internet](/cookbook/networking/fetch-data) recipe. To test this function, make two changes:

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, 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.

The function should now look like this:

dart

```
Future<Album> fetchAlbum(http.Client client) async {
  final response = await client.get(
    Uri.parse('https://jsonplaceholder.typicode.com/albums/1'),
  );
​
  if (response.statusCode == 200) {
    // If the server did return a 200 OK response,
    // then parse the JSON.
    return Album.fromJson(jsonDecode(response.body) as Map<String, dynamic>);
  } else {
    // If the server did not return a 200 OK response,
    // then throw an exception.
    throw Exception('Failed to load album');
  }
}
```

In your app code, you can provide an `http.Client` to the `fetchAlbum` method directly with `fetchAlbum(http.Client())`. `http.Client()` creates a default `http.Client`.

## 3. Create a test file with a mock `http.Client`

[#](#3-create-a-test-file-with-a-mock-http-client)

Next, create a test file.

Following the advice in the [Introduction to unit testing](/cookbook/testing/unit/introduction) recipe, create a file called `fetch_album_test.dart` in the root `test` folder.

Add the annotation `@GenerateMocks([], customMocks: [MockSpec<http.Client>(as: #MockHttpClient)])` to the main function to generate a `MockHttpClient` class with `mockito`.

The generated `MockHttpClient` class implements the `http.Client` class. This allows you to pass the `MockHttpClient` to the `fetchAlbum` function, and return different http responses in each test.

The generated mocks will be located in `fetch_album_test.mocks.dart`. Import this file to use them.

dart

```
import 'package:http/http.dart' as http;
import 'package:mocking/main.dart';
import 'package:mockito/annotations.dart';
​
// Generate a MockClient using the Mockito package.
// Create new instances of this class in each test.
// Note: Naming the generated mock `MockHttpClient` to avoid confusion with
// `MockClient` from `package:http/testing.dart`.
@GenerateMocks([], customMocks: [MockSpec<http.Client>(as: #MockHttpClient)])
void main() {
}
```

Next, generate the mocks running the following command:

```
$ dart run build_runner build
```

## 4. Write a test for each condition

[#](#4-write-a-test-for-each-condition)

The `fetchAlbum()` function does one of two things:

1. Returns an `Album` if the http call succeeds
2. Throws an `Exception` if the http call fails

Therefore, you want to test these two conditions. Use the `MockHttpClient` class to return an "Ok" response for the success test, and an error response for the unsuccessful test. Test these conditions using the `when()` function provided by Mockito:

dart

```
import 'package:flutter_test/flutter_test.dart';
import 'package:http/http.dart' as http;
import 'package:mocking/main.dart';
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
​
import 'fetch_album_test.mocks.dart';
​
// Generate a MockClient using the Mockito package.
// Create new instances of this class in each test.
// Note: Naming the generated mock `MockHttpClient` to avoid confusion with
// `MockClient` from `package:http/testing.dart`.
@GenerateMocks([], customMocks: [MockSpec<http.Client>(as: #MockHttpClient)])
void main() {
  group('fetchAlbum', () {
    test('returns an Album if the http call completes successfully', () async {
      final client = MockHttpClient();
​
      // Use Mockito to return a successful response when it calls the
      // provided http.Client.
      when(
        client.get(Uri.parse('https://jsonplaceholder.typicode.com/albums/1')),
      ).thenAnswer(
        (_) async =>
            http.Response('{"userId": 1, "id": 2, "title": "mock"}', 200),
      );
​
      expect(await fetchAlbum(client), isA<Album>());
    });
​
    test('throws an exception if the http call completes with an error', () {
      final client = MockHttpClient();
​
      // Use Mockito to return an unsuccessful response when it calls the
      // provided http.Client.
      when(
        client.get(Uri.parse('https://jsonplaceholder.typicode.com/albums/1')),
      ).thenAnswer((_) async => http.Response('Not Found', 404));
​
      expect(fetchAlbum(client), throwsException);
    });
  });
}
```

## 5. Run the tests

[#](#5-run-the-tests)

Now that you have a `fetchAlbum()` function with tests in place, run the tests.

```
$ flutter test test/fetch_album_test.dart
```

You can also run tests inside your favorite editor by following the instructions in the [Introduction to unit testing](/cookbook/testing/unit/introduction) recipe.

## Complete example

[#](#complete-example)

##### lib/main.dart

[#](#libmain-dart)

dart

```
import 'dart:async';
import 'dart:convert';
​
import 'package:http/http.dart' as http;
import 'package:material_ui/material_ui.dart';
​
Future<Album> fetchAlbum(http.Client client) async {
  final response = await client.get(
    Uri.parse('https://jsonplaceholder.typicode.com/albums/1'),
  );
​
  if (response.statusCode == 200) {
    // If the server did return a 200 OK response,
    // then parse the JSON.
    return Album.fromJson(jsonDecode(response.body) as Map<String, dynamic>);
  } else {
    // If the server did not return a 200 OK response,
    // then throw an exception.
    throw Exception('Failed to load album');
  }
}
​
class Album {
  final int userId;
  final int id;
  final String title;
​
  const Album({required this.userId, required this.id, required this.title});
​
  factory Album.fromJson(Map<String, dynamic> json) {
    return Album(
      userId: json['userId'] as int,
      id: json['id'] as int,
      title: json['title'] as String,
    );
  }
}
​
void main() => runApp(const MyApp());
​
class MyApp extends StatefulWidget {
  const MyApp({super.key});
​
  @override
  State<MyApp> createState() => _MyAppState();
}
​
class _MyAppState extends State<MyApp> {
  late final Future<Album> futureAlbum;
​
  @override
  void initState() {
    super.initState();
    futureAlbum = fetchAlbum(http.Client());
  }
​
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Fetch Data Example',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
      ),
      home: Scaffold(
        appBar: AppBar(title: const Text('Fetch Data Example')),
        body: Center(
          child: FutureBuilder<Album>(
            future: futureAlbum,
            builder: (context, snapshot) {
              if (snapshot.hasData) {
                return Text(snapshot.data!.title);
              } else if (snapshot.hasError) {
                return Text('${snapshot.error}');
              }
​
              // By default, show a loading spinner.
              return const CircularProgressIndicator();
            },
          ),
        ),
      ),
    );
  }
}
```

##### test/fetch\_album\_test.dart

[#](#testfetch-album-test-dart)

dart

```
import 'package:flutter_test/flutter_test.dart';
import 'package:http/http.dart' as http;
import 'package:mocking/main.dart';
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
​
import 'fetch_album_test.mocks.dart';
​
// Generate a MockClient using the Mockito package.
// Create new instances of this class in each test.
// Note: Naming the generated mock `MockHttpClient` to avoid confusion with
// `MockClient` from `package:http/testing.dart`.
@GenerateMocks([], customMocks: [MockSpec<http.Client>(as: #MockHttpClient)])
void main() {
  group('fetchAlbum', () {
    test('returns an Album if the http call completes successfully', () async {
      final client = MockHttpClient();
​
      // Use Mockito to return a successful response when it calls the
      // provided http.Client.
      when(
        client.get(Uri.parse('https://jsonplaceholder.typicode.com/albums/1')),
      ).thenAnswer(
        (_) async =>
            http.Response('{"userId": 1, "id": 2, "title": "mock"}', 200),
      );
​
      expect(await fetchAlbum(client), isA<Album>());
    });
​
    test('throws an exception if the http call completes with an error', () {
      final client = MockHttpClient();
​
      // Use Mockito to return an unsuccessful response when it calls the
      // provided http.Client.
      when(
        client.get(Uri.parse('https://jsonplaceholder.typicode.com/albums/1')),
      ).thenAnswer((_) async => http.Response('Not Found', 404));
​
      expect(fetchAlbum(client), throwsException);
    });
  });
}
```

## Summary

[#](#summary)

In this example, you've learned how to use Mockito to test functions or classes that depend on web services or databases. This is only a short introduction to the Mockito library and the concept of mocking. For more information, see the documentation provided by the [Mockito package](https://pub.dev/packages/mockito).

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-09-29. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/testing/unit/mocking.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/cookbook/testing/unit/mocking\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/testing/unit/mocking.md "Report an issue with this page").

----
url: https://docs.flutter.dev/deployment/flavors
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Set up Flutter flavors for Android](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

This guide shows you how to create Flutter flavors for an Android app.

## Overview

[#](#overview)

A Flutter flavor when used with Android represents a unified term for various platform-specific features. For example, a flavor could determine which icon, app name, API key, feature flag, and logging level is associated with a specific version of your app.

If you want to create Flutter flavors for an Android app, you can do this in Flutter. In Android, a Flutter flavor is referred to as a [*product flavor*](https://developer.android.com/build/build-variants#product-flavors).

The following illustrates an example of the Android [*build variants*](https://developer.android.com/build/build-variants) that are created when an Android app has two product flavors (`staging`, `production`) and two build types (`debug`, `release`):

| Product flavors | Build types | Resulting build variants          |
| --------------- | ----------- | --------------------------------- |
| staging         | debug       | stagingDebug stagingRelease       |
| production      | release     | productionDebug productionRelease |

## Configure your product flavors

[#](#using-flavors-in-android)

Complete the following steps to add two Android product flavors called `staging` and `production` to a new Flutter project called `flavors_example`, and then test your project to make sure that the flavors work as expected.

1. Create a new Flutter project called `flavors_example` with Kotlin as the preferred Android language. By default, the project includes the `debug` and `release` Android build types.

   console

   ```
   $ flutter create --android-language kotlin flavors_example
   ```

2. Add the product flavors called `staging` and `production` to the `flavors_example` project.

   * In the `flavors_example` project, navigate to the `android/app/` directory and open `build.gradle.kts`.

   * Add the `flavorDimensions` property and the `productFlavors` properties inside the `android {}` block. The default template explicitly defines the `release` build type, while the `debug` build type is implicit:

     build.gradle.kts

     kotlin

     ```
     android {
         ...
         buildTypes {
             release {
                 // TODO: Add your own signing config for the release build.
                 // Signing with the debug keys for now, so `flutter run --release` works.
                 signingConfig = signingConfigs.getByName("debug")
             }
         }
         ...
         flavorDimensions += "default"
         productFlavors {
             create("staging") {
                 dimension = "default"
                 applicationIdSuffix = ".staging"
             }
             create("production") {
                 dimension = "default"
                 applicationIdSuffix = ".production"
             }
         }
     }
     ```

3. To make sure that you've set up everything correctly, run your app on the Android product flavors. You won't see any differences because the configuration settings haven't changed, but you do want to make sure that the app can run.

   * Start an Android emulator or connect a physical device with developer options enabled.

   * In the console, navigate to the `flavors_example` directory and enter the following command to test the `staging` flavor:

     console

     ```
     $ flutter run --flavor staging
     ```

   * Repeat the previous step for the `production` flavor.

4. If everything runs, you're ready to customize your configurations. For more information, see [Customize configurations](#customize-configurations).

## Launch a flavor

[#](#launching-your-app-flavors)

After you've created the product flavors for an Android app, you can launch a specific product flavor through Flutter.

You can launch a product flavor with the Flutter CLI using the following steps:

1. Start an Android emulator or connect a physical device with developer options enabled.

2. In the console, navigate to the `flavors_example` directory and enter the following command:

console

```
$ flutter (run | build <subcommand>) --flavor <flavor_name>
```

* `(run | build <subcommand>)`: Replace this with one of the following:

  * `run`: Runs the app in debug mode.
  * `build`: Builds either an APK or an appbundle.
    * `<subcommand>`: Either `apk` or `appbundle`.

* `<flavor_name>`: Replace this with the name of your Android product flavor (for example: `staging`, `production`).

Example:

console

```
$ flutter build apk --flavor staging
```

## Use flavors in Flutter code

[#](#use-flavors-in-flutter-code)

After you've configured your product flavors, you can change your app's behavior—such as pointing to different API endpoints or changing the theme—based on the active flavor.

The Flutter framework provides the `appFlavor` constant, which retrieves the name of the current flavor as a `String`. This value matches the flavor name passed to the `--flavor` flag during the `flutter run` or `flutter build` process.

### Access the current flavor

[#](#access-the-current-flavor)

1. **Import the services library:** To access the `appFlavor` constant, add the following import to your Dart file:

   dart

   ```
   import 'package:flutter/services.dart';
   ```

2. **Check the flavor value:** Use the `appFlavor` constant in your application logic (often in `main()`) to handle flavor-specific configurations:

   dart

   ```
   void main() {
     // appFlavor will match the flavor name from build.gradle.kts
     if (appFlavor == 'production') {
       // Logic for production environment
       Config.apiUrl = 'https://api.flavors_example.com';
     } else if (appFlavor == 'staging') {
       // Logic for staging environment
       Config.apiUrl = 'https://staging.api.flavors_example.com';
     }
   ​
     runApp(const MyApp());
   }
   ```

## Customize configurations

[#](#customize-configurations)

After you've added product flavors, you can customize them for your Android app.

### Create a distinct app display name

[#](#create-a-distinct-app-display-name)

If you have multiple product flavors, a distinct app name can quickly identify which flavor your deployed app is using.

The following steps show how to add distinct app display names for two product flavors called `staging` and `production` in a project called `flavors_example`.

1. Create flavor-specific resource directories:

   * Navigate to the `android/app/src` directory.

   * Create a directory called `staging/res/values`.

   * Navigate to the `staging/res/values` directory.

   * Create the following `strings.xml` file:

     strings.xml

     xml

     ```
     <?xml version="1.0" encoding="utf-8"?>
     <resources>
         <string name="app_name">Flavors staging</string>
     </resources>
     ```

   * Repeat the previous steps for the `production` flavor, and set the value of `app_name` to `Flavors production`.

2. Update `AndroidManifest.xml` in your IDE:

   * In the `flavors_example` project, navigate to `android/app/src/main` and open `AndroidManifest.xml`.

   * Replace the value for `android:label` with `@string/app_name`.

     AndroidManifest.xml

     xml

     ```
     <manifest xmlns:android="http://schemas.android.com/apk/res/android">
         <application
           android:label="@string/app_name"
           ...
         />
     />
     ```

3. Launch the app for each product flavor (`staging`, `production`) and check to make sure that the app display name has changed for each.

   * To launch a product flavor, see the steps in [Launch a flavor](#launching-your-app-flavors).

   * In the Android App Emulator, go to the list of apps. You should see one for `Flavors p...` and `Flavors s...`.

   * To see more information for `Flavors p...` or `Flavors s...`, long-press the icon for one of them and select `App info`.

### Create distinct icons

[#](#create-distinct-icons)

If you have multiple product flavors, a distinct icon for each configuration can help you quickly identify which flavor your deployed app is using.

The following steps show how to add a distinct icon for two product flavors called `staging` and `production` in a project called `flavors_example`.

1. Prepare your icons:

   * Design your `staging` icon and `production` icon in the design tool of your choice.

   * Generate versions of the `staging` icon and `production` icon in the following sizes and them in `PNG` format:

     * mipmap-mdpi (48x48 pixels)
     * mipmap-hdpi (72x72 pixels)
     * mipmap-xhdpi (96x96 pixels)
     * mipmap-xxhdpi (144x144 pixels)
     * mipmap-xxxhdpi (192x192 pixels)

2. Create flavor-specific resource directories:

   * Navigate to the `android/app/src` directory.

   * Create a directory called `staging/res`.

   * Navigate to the `staging/res` directory.

   * Create the following `mipmap` directories and move the versions of the `staging` icon into them:

     * `mipmap-mdpi/48x48_staging.png`
     * `mipmap-hdpi/72x72_staging.png`
     * `mipmap-xhdpi/96x96_staging.png`
     * `mipmap-xxhdpi/144x144_staging.png`
     * `mipmap-xxxhdpi/192x192_staging.png`

   * Repeat the previous steps for the `production` flavor directories and icons.

   * Rename all of the icons to `ic_launcher.png`.

3. Double-check the configurations in `AndroidManifest.xml` in your IDE:

   * In the `flavors_example` project, navigate to `android/app/src/main` and open `AndroidManifest.xml`.

   * Make sure that the value for `android:icon` is `@mipmap/ic_launcher`.

4. Launch the app for each product flavor (`staging`, `production`) and check to make sure that the app icon has changed for each. To launch a product flavor, see the steps in [Launch a flavor](#launching-your-app-flavors).

### Bundle assets

[#](#bundle-assets)

If you have assets that are only used in a specific flavor in your app, you can configure them to only be bundled into your app when launching that flavor. This prevents your app bundle size from being bloated by unused assets. To bundle assets for each flavor, add the `flavors` subfield to the `assets` field in your project's pubspec. To learn more, see the [`assets` field](/tools/pubspec#assets) in [Flutter pubspec options](/tools/pubspec).

### Set a default flavor

[#](#set-a-default-flavor)

You can have your app use a specific flavor when you launch your app without specifying a flavor. To do this, you need to add the `default-flavor` field to your project's pubspec. To learn more, see the [`default-flavor` field](/tools/pubspec#default-flavor) in [Flutter pubspec options](/tools/pubspec).

### Add unique build settings

[#](#add-unique-build-settings)

If you have additional build settings that you would like to configure for a specific Android product flavor, see Android's [Configure build variants](https://developer.android.com/build/build-variants).

While it is possible to set `abiFilters` in product flavors, it is not recommended. Prefer configuring `abiFilters` in `defaultConfig` when possible. When setting `abiFilters` in product flavors, pass `-Pdisable-abi-filtering=true` when running `flutter build` or `flutter run`.

## More information

[#](#more-information)

For more information on creating and using flavors, check out the following resources:

* [Set up Flutter flavors for iOS and macOS](/deployment/flavors-ios)
* [Set up Flutter flavors for Linux](/deployment/flavors-linux)
* [Set up Flutter flavors for Windows](/deployment/flavors-windows)
* [Build flavors in Flutter (Android and iOS) with Firebase](https://medium.com/@animeshjain/build-flavors-in-flutter-android-and-ios-with-different-firebase-projects-per-flavor-27c5c5dac10b)
* [How to Setup Flutter & Firebase with Multiple Flavors using the FlutterFire CLI](https://codewithandrea.com/articles/flutter-firebase-multiple-flavors-flutterfire-cli/)

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-09-29. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/deployment/flavors.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/deployment/flavors\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/deployment/flavors.md "Report an issue with this page").

----
url: https://docs.flutter.dev/data-and-backend/state-mgmt/options.md
----

\# Approaches to state management > An introduction to different approaches to managing state in Flutter apps. State management is a complex topic. If you feel that some of your questions haven't been answered, or that the approach described on these pages is not viable for your use cases, you are probably right. Learn more from the following resources, many of which have been contributed by the Flutter community. ## General overview Things to review before selecting an approach. \* \[Introduction to state management]\[], which is the beginning of this very section (for those of you who arrived directly to this \_Options\_ page and missed the previous pages) \* \[Pragmatic State Management in Flutter]\[], a video from Google I/O 2019 \* \[Flutter Architecture Samples]\[], by Brian Egan \[Flutter Architecture Samples]: https\://fluttersamples.com/ \[Introduction to state management]: /data-and-backend/state-mgmt/intro \[Pragmatic State Management in Flutter]: https\://www\.youtube.com/watch?v=d\_m5csmrf7I ## Built-in approaches ### \`setState\` The low-level approach to use for widget-specific, ephemeral state. \* \[Adding interactivity to your Flutter app]\[], a Flutter tutorial \* \[Basic state management in Google Flutter]\[], by Agung Surya \[Adding interactivity to your Flutter app]: /ui/interactivity \[Basic state management in Google Flutter]: https\://medium.com/@agungsurya/basic-state-management-in-google-flutter-6ee73608f96d []()### \`ValueNotifier\` and \`InheritedNotifier\` An approach using only Flutter provided APIs to update state and notify the UI of changes. \* \[State Management using ValueNotifier and InheritedNotifier]\[], by Tadas Petra \[State Management using ValueNotifier and InheritedNotifier]: https\://www\.hungrimind.com/articles/flutter-state-management []()### \`InheritedWidget\` and \`InheritedModel\` The low-level approach used to communicate between ancestors and children in the widget tree. This is what \`package:provider\` and many other approaches use under the hood. The following instructor-led video workshop covers how to use \`InheritedWidget\`: Other useful docs include: \* \[InheritedWidget docs]\[] \* \[Managing Flutter Application State With InheritedWidgets]\[], by Hans Muller \* \[Inheriting Widgets]\[], by Mehmet Fidanboylu \* \[Widget - State - Context - InheritedWidget]\[], by Didier Bolelens \[InheritedWidget docs]: https\://api.flutter.dev/flutter/widgets/InheritedWidget-class.html \[Inheriting Widgets]: https\://medium.com/@mehmetf\_71205/inheriting-widgets-b7ac56dbbeb1 \[Managing Flutter Application State With InheritedWidgets]: https\://blog.flutter.dev/managing-flutter-application-state-with-inheritedwidgets-1140452befe1 \[Widget - State - Context - InheritedWidget]: https\://flutteris.com/blog/en/widget-state-context-inheritedwidget ## Community-provided packages Depending on the complexity of your app and preferences of your team, you might find adopting a state management package useful. State management packages often help reduce boilerplate code, provide specialized debugging tools, and can help enable a clearer and consistent application architecture. The Flutter community offers a wide variety of state management packages. The best choice for your app often depends on the app's complexity, your team's preferences, and the specific problems you need to solve. To begin exploring the available options, check out the \[\`#state-management\`]\[]{: target="\_blank"} topic on the pub.dev site and refine the search to find packages that match your needs.

[State management packages](https://pub.dev/packages?q=topic%3Astate-management)

[Explore the variety of state-management packages built by and for the Flutter community.](https://pub.dev/packages?q=topic%3Astate-management)

:::tip If you've developed a state management package that you think would be useful to the Flutter community, consider \[adding the \`state-management\` topic]\[pub-topics] and \[publishing the package]\[pub-publish] to pub.dev. ::: \[\`#state-management\`]: https\://pub.dev/packages?q=topic%3Astate-management \[pub-topics]: https\://dart.dev/tools/pub/pubspec#topics \[pub-publish]: https\://dart.dev/tools/pub/publishing

----
url: https://docs.flutter.dev/cookbook/games/firestore-multiplayer.md
----

\# Add multiplayer support using Firestore > How to use use Firebase Cloud Firestore to implement multiplayer in your game.

<!--?code-excerpt path-base="cookbook/games/firestore_multiplayer"?-->

Multiplayer games need a way to synchronize game states between players. Broadly speaking, two types of multiplayer games exist: 1. \*\*High tick rate\*\*. These games need to synchronize game states many times per second with low latency. These would include action games, sports games, fighting games. 2. \*\*Low tick rate\*\*. These games only need to synchronize game states occasionally with latency having less impact. These would include card games, strategy games, puzzle games. This resembles the differentiation between real-time versus turn-based games, though the analogy falls short. For example, real-time strategy games run—as the name suggests—in real-time, but that doesn't correlate to a high tick rate. These games can simulate much of what happens in between player interactions on local machines. Therefore, they don't need to synchronize game states that often. !\[An illustration of two mobile phones and a two-way arrow between them]\(/assets/images/docs/cookbook/multiplayer-two-mobiles.jpg){:.site-illustration} If you can choose low tick rates as a developer, you should. Low tick lowers latency requirements and server costs. Sometimes, a game requires high tick rates of synchronization. For those cases, solutions such as Firestore \*don't make a good fit\*. Pick a dedicated multiplayer server solution such as \[Nakama]\[]. Nakama has a \[Dart package]\[]. If you expect that your game requires a low tick rate of synchronization, continue reading. This recipe demonstrates how to use the \[\`cloud\_firestore\` package]\[] to implement multiplayer capabilities in your game. This recipe doesn't require a server. It uses two or more clients sharing game state using Cloud Firestore. \[\`cloud\_firestore\` package]: https\://pub.dev/packages/cloud\_firestore \[Dart package]: https\://pub.dev/packages/nakama \[Nakama]: https\://heroiclabs.com/nakama/ ## 1. Prepare your game for multiplayer Write your game code to allow changing the game state in response to both local events and remote events. A local event could be a player action or some game logic. A remote event could be a world update coming from the server. !\[Screenshot of the card game]\(/assets/images/docs/cookbook/multiplayer-card-game.jpg){:.site-mobile-screenshot .site-illustration} To simplify this cookbook recipe, start with the \[\`card\`]\[] template that you'll find in the \[\`flutter/games\` repository]\[]. Run the following command to clone that repository: \`\`\`console git clone https\://github.com/flutter/games.git \`\`\` Open the project in \`templates/card\`. :::note You can ignore this step and follow the recipe with your own game project. Adapt the code at appropriate places. ::: \[\`card\`]: https\://github.com/flutter/games/tree/main/templates/card#readme \[\`flutter/games\` repository]: https\://github.com/flutter/games ## 2. Install Firestore \[Cloud Firestore]\[] is a horizontally scaling, NoSQL document database in the cloud. It includes built-in live synchronization. This is perfect for our needs. It keeps the game state updated in the cloud database, so every player sees the same state. If you want a quick, 15-minute primer on Cloud Firestore, check out the following video: To add Firestore to your Flutter project, follow the first two steps of the \[Get started with Cloud Firestore]\[] guide: \* \[Create a Cloud Firestore database]\[] \* \[Set up your development environment]\[] The desired outcomes include: \* A Firestore database ready in the cloud, in \*\*Test mode\*\* \* A generated \`firebase\_options.dart\` file \* The appropriate plugins added to your \`pubspec.yaml\` You \*don't\* need to write any Dart code in this step. As soon as you understand the step of writing Dart code in that guide, return to this recipe. \[Cloud Firestore]: https\://cloud.google.com/firestore/ \[Create a Cloud Firestore database]: https\://firebase.google.com/docs/firestore/quickstart#create \[Get started with Cloud Firestore]: https\://firebase.google.com/docs/firestore/quickstart \[Set up your development environment]: https\://firebase.google.com/docs/firestore/quickstart#set\_up\_your\_development\_environment ## 3. Initialize Firestore 1. Open \`lib/main.dart\` and import the plugins, as well as the \`firebase\_options.dart\` file that was generated by \`flutterfire configure\` in the previous step.

<!--?code-excerpt "lib/main.dart (imports)"?-->

\`\`\`dart import 'package:cloud\_firestore/cloud\_firestore.dart'; import 'package:firebase\_core/firebase\_core.dart'; import 'firebase\_options.dart'; \`\`\` 2. Add the following code just above the call to \`runApp()\` in \`lib/main.dart\`:

<!--?code-excerpt "lib/main.dart (initializeApp)"?-->

\`\`\`dart WidgetsFlutterBinding.ensureInitialized(); await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform); \`\`\` This ensures that Firebase is initialized on game startup. 3. Add the Firestore instance to the app. That way, any widget can access this instance. Widgets can also react to the instance missing, if needed. To do this with the \`card\` template, you can use the \`provider\` package (which is already installed as a dependency). Replace the boilerplate \`runApp(MyApp())\` with the following:

<!--?code-excerpt "lib/main.dart (runApp)"?-->

\`\`\`dart runApp(Provider.value(value: FirebaseFirestore.instance, child: MyApp())); \`\`\` Put the provider above \`MyApp\`, not inside it. This enables you to test the app without Firebase. :::note In case you are \*not\* working with the \`card\` template, you must either \[install the \`provider\` package]\[] or use your own method of accessing the \`FirebaseFirestore\` instance from various parts of your codebase. ::: \[install the \`provider\` package]: https\://pub.dev/packages/provider/install ## 4. Create a Firestore controller class Though you can talk to Firestore directly, you should write a dedicated controller class to make the code more readable and maintainable. How you implement the controller depends on your game and on the exact design of your multiplayer experience. For the case of the \`card\` template, you could synchronize the contents of the two circular playing areas. It's not enough for a full multiplayer experience, but it's a good start. !\[Screenshot of the card game, with arrows pointing to playing areas]\(/assets/images/docs/cookbook/multiplayer-areas.jpg){:.site-mobile-screenshot .site-illustration} To create a controller, copy, then paste the following code into a new file called \`lib/multiplayer/firestore\_controller.dart\`.

<!--?code-excerpt "lib/multiplayer/firestore_controller.dart"?-->

\`\`\`dart import 'dart:async'; import 'package:cloud\_firestore/cloud\_firestore.dart'; import 'package:flutter/foundation.dart'; import 'package:logging/logging.dart'; import '../game\_internals/board\_state.dart'; import '../game\_internals/playing\_area.dart'; import '../game\_internals/playing\_card.dart'; class FirestoreController { static final \_log = Logger('FirestoreController'); final FirebaseFirestore instance; final BoardState boardState; /// For now, there is only one match. But in order to be ready /// for match-making, put it in a Firestore collection called matches. late final DocumentReference> \_matchRef = instance .collection('matches') .doc('match\_1'); late final DocumentReference> \_areaOneRef = \_matchRef .collection('areas') .doc('area\_one') .withConverter>( fromFirestore: \_cardsFromFirestore, toFirestore: \_cardsToFirestore, ); late final DocumentReference> \_areaTwoRef = \_matchRef .collection('areas') .doc('area\_two') .withConverter>( fromFirestore: \_cardsFromFirestore, toFirestore: \_cardsToFirestore, ); late final StreamSubscription \_areaOneFirestoreSubscription; late final StreamSubscription \_areaTwoFirestoreSubscription; late final StreamSubscription \_areaOneLocalSubscription; late final StreamSubscription \_areaTwoLocalSubscription; FirestoreController({required this.instance, required this.boardState}) { // Subscribe to the remote changes (from Firestore). \_areaOneFirestoreSubscription = \_areaOneRef.snapshots().listen((snapshot) { \_updateLocalFromFirestore(boardState.areaOne, snapshot); }); \_areaTwoFirestoreSubscription = \_areaTwoRef.snapshots().listen((snapshot) { \_updateLocalFromFirestore(boardState.areaTwo, snapshot); }); // Subscribe to the local changes in game state. \_areaOneLocalSubscription = boardState.areaOne.playerChanges.listen((\_) { \_updateFirestoreFromLocalAreaOne(); }); \_areaTwoLocalSubscription = boardState.areaTwo.playerChanges.listen((\_) { \_updateFirestoreFromLocalAreaTwo(); }); \_log.fine('Initialized'); } void dispose() { \_areaOneFirestoreSubscription.cancel(); \_areaTwoFirestoreSubscription.cancel(); \_areaOneLocalSubscription.cancel(); \_areaTwoLocalSubscription.cancel(); \_log.fine('Disposed'); } /// Takes the raw JSON snapshot coming from Firestore and attempts to /// convert it into a list of \[PlayingCard]s. List \_cardsFromFirestore( DocumentSnapshot> snapshot, SnapshotOptions? options, ) { final data = snapshot.data()?\['cards'] as List?; if (data == null) { \_log.info('No data found on Firestore, returning empty list'); return \[]; } try { return data .cast>() .map(PlayingCard.fromJson) .toList(); } catch (e) { throw FirebaseControllerException( 'Failed to parse data from Firestore: $e', ); } } /// Takes a list of \[PlayingCard]s and converts it into a JSON object /// that can be saved into Firestore. Map \_cardsToFirestore( List cards, SetOptions? options, ) { return {'cards': cards.map((c) => c.toJson()).toList()}; } /// Updates Firestore with the local state of \[area]. Future \_updateFirestoreFromLocal( PlayingArea area, DocumentReference> ref, ) async { try { \_log.fine('Updating Firestore with local data (${area.cards}) ...'); await ref.set(area.cards); \_log.fine('... done updating.'); } catch (e) { throw FirebaseControllerException( 'Failed to update Firestore with local data (${area.cards}): $e', ); } } /// Sends the local state of \`boardState.areaOne\` to Firestore. void \_updateFirestoreFromLocalAreaOne() { \_updateFirestoreFromLocal(boardState.areaOne, \_areaOneRef); } /// Sends the local state of \`boardState.areaTwo\` to Firestore. void \_updateFirestoreFromLocalAreaTwo() { \_updateFirestoreFromLocal(boardState.areaTwo, \_areaTwoRef); } /// Updates the local state of \[area] with the data from Firestore. void \_updateLocalFromFirestore( PlayingArea area, DocumentSnapshot> snapshot, ) { \_log.fine('Received new data from Firestore (${snapshot.data()})'); final cards = snapshot.data() ?? \[]; if (listEquals(cards, area.cards)) { \_log.fine('No change'); } else { \_log.fine('Updating local data with Firestore data ($cards)'); area.replaceWith(cards); } } } class FirebaseControllerException implements Exception { final String message; FirebaseControllerException(this.message); @override String toString() => 'FirebaseControllerException: $message'; } \`\`\` Notice the following features of this code: \* The controller's constructor takes a \`BoardState\`. This enables the controller to manipulate the local state of the game. \* The controller subscribes to both local changes to update Firestore and to remote changes to update the local state and UI. \* The fields \`\_areaOneRef\` and \`\_areaTwoRef\` are Firebase document references. They describe where the data for each area resides, and how to convert between the local Dart objects (\`List\`) and remote JSON objects (\`Map\`). The Firestore API lets us subscribe to these references with \`.snapshots()\`, and write to them with \`.set()\`. ## 5. Use the Firestore controller 1. Open the file responsible for starting the play session: \`lib/play\_session/play\_session\_screen.dart\` in the case of the \`card\` template. You instantiate the Firestore controller from this file. 2. Import Firebase and the controller:

<!--?code-excerpt "lib/play_session/play_session_screen.dart (imports)"?-->

\`\`\`dart import 'package:cloud\_firestore/cloud\_firestore.dart'; import '../multiplayer/firestore\_controller.dart'; \`\`\` 3. Add a nullable field to the \`\_PlaySessionScreenState\` class to contain a controller instance:

<!--?code-excerpt "lib/play_session/play_session_screen.dart (controller)"?-->

\`\`\`dart FirestoreController? \_firestoreController; \`\`\` 4. In the \`initState()\` method of the same class, add code that tries to read the FirebaseFirestore instance and, if successful, constructs the controller. You added the \`FirebaseFirestore\` instance to \`main.dart\` in the \*Initialize Firestore\* step.

<!--?code-excerpt "lib/play_session/play_session_screen.dart (init-state)"?-->

\`\`\`dart final firestore = context.read(); if (firestore == null) { \_log.warning( "Firestore instance wasn't provided. " 'Running without \_firestoreController.', ); } else { \_firestoreController = FirestoreController( instance: firestore, boardState: \_boardState, ); } \`\`\` 5. Dispose of the controller using the \`dispose()\` method of the same class.

<!--?code-excerpt "lib/play_session/play_session_screen.dart (dispose)"?-->

\`\`\`dart \_firestoreController?.dispose(); \`\`\` ## 6. Test the game 1. Run the game on two separate devices or in 2 different windows on the same device. 2. Watch how adding a card to an area on one device makes it appear on the other one. 3. Open the \[Firebase web console]\[] and navigate to your project's Firestore Database. 4. Watch how it updates the data in real time. You can even edit the data in the console and see all running clients update. !\[Screenshot of the Firebase Firestore data view]\(/assets/images/docs/cookbook/multiplayer-firebase-data.png) \[Firebase web console]: https\://console.firebase.google.com/ ### Troubleshooting The most common issues you might encounter when testing Firebase integration include the following: \* \*\*The game crashes when trying to reach Firebase.\*\* \* Firebase integration hasn't been properly set up. Revisit \*Step 2\* and make sure to run \`flutterfire configure\` as part of that step. \* \*\*The game doesn't communicate with Firebase on macOS.\*\* \* By default, macOS apps don't have internet access. Enable \[internet entitlement]\[] first. \[internet entitlement]: /data-and-backend/networking#macos ## 7. Next steps At this point, the game has near-instant and dependable synchronization of state across clients. It lacks actual game rules: what cards can be played when, and with what results. This depends on the game itself and is left to you to try. !\[An illustration of two mobile phones and a two-way arrow between them]\(/assets/images/docs/cookbook/multiplayer-two-mobiles.jpg){:.site-illustration} At this point, the shared state of the match only includes the two playing areas and the cards within them. You can save other data into \`\_matchRef\`, too, like who the players are and whose turn it is. If you're unsure where to start, follow \[a Firestore codelab or two]\[] to familiarize yourself with the API. At first, a single match should suffice for testing your multiplayer game with colleagues and friends. As you approach the release date, think about authentication and match-making. Thankfully, Firebase provides a \[built-in way to authenticate users]\[] and the Firestore database structure can handle multiple matches. Instead of a single \`match\_1\`, you can populate the matches collection with as many records as needed. !\[Screenshot of the Firebase Firestore data view with additional matches]\(/assets/images/docs/cookbook/multiplayer-firebase-match.png) An online match can start in a "waiting" state, with only the first player present. Other players can see the "waiting" matches in some kind of lobby. Once enough players join a match, it becomes "active". Once again, the exact implementation depends on the kind of online experience you want. The basics remain the same: a large collection of documents, each representing one active or potential match. \[a Firestore codelab or two]: https\://codelabs.developers.google.com/?product=flutter\&text=firestore \[built-in way to authenticate users]: https\://firebase.google.com/docs/auth/flutter/start

----
url: https://docs.flutter.dev/add-to-app/multiple-flutters.md
----

\# Multiple Flutter screens or views > How to integrate multiple instances of Flutter engine, screens, or views to your application. ## Scenarios If you're integrating Flutter into an existing app, or gradually migrating an existing app to use Flutter, you might find yourself wanting to add multiple Flutter instances to the same project. In particular, this can be useful in the following scenarios: \* An application where the integrated Flutter screen is not a leaf node of the navigation graph, and the navigation stack might be a hybrid mixture of native -> Flutter -> native -> Flutter. \* A screen where multiple partial screen Flutter views might be integrated and visible at once. The advantage of using multiple Flutter instances is that each instance is independent and maintains its own internal navigation stack, UI, and application states. This simplifies the overall application code's responsibility for state keeping and improves modularity. More details on the scenarios motivating the usage of multiple Flutters can be found at \[flutter.dev/go/multiple-flutters]\[]. Flutter is optimized for this scenario, with a low incremental memory cost (\~180kB) for adding additional Flutter instances. This fixed cost reduction allows the multiple Flutter instance pattern to be used more liberally in your add-to-app integration. ## Components The primary API for adding multiple Flutter instances on both Android and iOS is based on a new \`FlutterEngineGroup\` class (\[Android API]\[], \[iOS API]\[]) to construct \`FlutterEngine\`s, rather than the \`FlutterEngine\` constructors used previously. Whereas the \`FlutterEngine\` API was direct and easier to consume, the \`FlutterEngine\` spawned from the same \`FlutterEngineGroup\` have the performance advantage of sharing many of the common, reusable resources such as the GPU context, font metrics, and isolate group snapshot, leading to a faster initial rendering latency and lower memory footprint. \* \`FlutterEngine\`s spawned from \`FlutterEngineGroup\` can be used to connect to UI classes like \[\`FlutterActivity\`]\[] or \[\`FlutterViewController\`]\[] in the same way as normally constructed cached \`FlutterEngine\`s. \* The first \`FlutterEngine\` spawned from the \`FlutterEngineGroup\` doesn't need to continue surviving in order for subsequent \`FlutterEngine\`s to share resources as long as there's at least 1 living \`FlutterEngine\` at all times. \* Creating the very first \`FlutterEngine\` from a \`FlutterEngineGroup\` has the same \[performance characteristics]\[] as constructing a \`FlutterEngine\` using the constructors did previously. \* When all \`FlutterEngine\`s from a \`FlutterEngineGroup\` are destroyed, the next \`FlutterEngine\` created has the same performance characteristics as the very first engine. \* The \`FlutterEngineGroup\` itself doesn't need to live beyond all of the spawned engines. Destroying the \`FlutterEngineGroup\` doesn't affect existing spawned \`FlutterEngine\`s but does remove the ability to spawn additional \`FlutterEngine\`s that share resources with existing spawned engines. ## Communication Communication between Flutter instances is handled using \[platform channels]\[] (or \[Pigeon]\[]) through the host platform. To see our roadmap on communication, or other planned work on enhancing multiple Flutter instances, check out \[Issue 72009]\[]. ## Samples You can find a sample demonstrating how to use \`FlutterEngineGroup\` on both Android and iOS on \[GitHub]\[]. \[GitHub]: https\://github.com/flutter/samples/tree/main/add\_to\_app/multiple\_flutters \[\`FlutterActivity\`]: https\://api.flutter.dev/javadoc/io/flutter/embedding/android/FlutterActivity.html \[\`FlutterViewController\`]: https\://api.flutter.dev/ios-embedder/interface\_flutter\_view\_controller.html \[performance characteristics]: /add-to-app/performance \[flutter.dev/go/multiple-flutters]: /go/multiple-flutters \[Issue 72009]: https\://github.com/flutter/flutter/issues/72009 \[Pigeon]: https\://pub.dev/packages/pigeon \[platform channels]: /platform-integration/platform-channels \[Android API]: https\://cs.opensource.google/flutter/engine/+/main:shell/platform/android/io/flutter/embedding/engine/FlutterEngineGroup.java \[iOS API]: https\://cs.opensource.google/flutter/engine/+/main:shell/platform/darwin/ios/framework/Headers/FlutterEngineGroup.h

----
url: https://docs.flutter.dev/reference/supported-platforms.md
----

\# Supported deployment platforms > The platforms that Flutter supports by platform version. As of Flutter 3.47, Flutter supports deploying apps on the following combinations of hardware architectures and operating system versions. These combinations are called \_platforms\_. Flutter categorizes platforms as follows: \* \*\*Supported\*\*: The platforms and versions that the Flutter team supports. \* \*\*CI-tested\*\*: The Flutter team tests these platforms on every commit. \* \*\*Unsupported\*\*: The Flutter team doesn't test or support these platforms. Based on these categories, Flutter supports deploying to the following platforms. ## Mobile platforms ## Desktop platforms :::warning macOS Intel (x64) deprecation As Apple phases out Intel-based Macs, Flutter is phasing out support for Intel (x64) hardware. For details on the timeline and impact, refer to the \[macOS Intel deprecation strategy]\[]. \[macOS Intel deprecation strategy]: https\://flutter.dev/go/macos-intel-deprecation ::: ## Web platforms ## Previous Flutter versions To learn about support for previous versions of Flutter, see the following resources: \* \[Flutter SDK release notes]\[]. Find breaking changes and support updates listed by release. \* \[Supported platforms page history]\[supported-platforms-history]. To see historical support matrices, view previous versions of this page. \[Flutter SDK release notes]: /release/release-notes \[supported-platforms-history]: https\://github.com/flutter/website/commits/main/sites/docs/src/content/reference/supported-platforms.md

----
url: https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.41.0.md
----

\# DevTools 2.41.0 release notes > Release notes for Dart and Flutter DevTools version 2.41.0. The 2.41.0 release of the Dart and Flutter DevTools includes the following changes among other general improvements. To learn more about DevTools, check out the \[DevTools overview]\(/tools/devtools/overview). ## General updates \* Persist filter settings across sessions. - \[#8447]\(https\://github.com/flutter/devtools/pull/8447), \[#8456]\(https\://github.com/flutter/devtools/pull/8456) \[#8470]\(https\://github.com/flutter/devtools/pull/8470) ## Inspector updates \* Added an option to the new Inspector's settings to allow auto-refreshing the widget tree after a hot-reload. - \[#8483]\(https\://github.com/flutter/devtools/pull/8483) ## Network profiler updates \* Added a filter text field to the top-level Network profiler controls. - \[#8469]\(https\://github.com/flutter/devtools/pull/8469) !\[Network filter field]\(/assets/images/docs/tools/devtools/release-notes/images-2.41.0/network\_filter.png "Network filter field") ## Logging updates \* Fetch log details immediately upon receiving logs so that log data is not lost due to lazy loading. - \[#8421]\(https\://github.com/flutter/devtools/pull/8421) \* Reduce initial page load time. - \[#8500]\(https\://github.com/flutter/devtools/pull/8500) \* Added support for displaying metadata, such as log severity, category, zone, and isolate - \[#8419]\(https\://github.com/flutter/devtools/pull/8419), \[#8439]\(https\://github.com/flutter/devtools/pull/8439), \[#8441]\(https\://github.com/flutter/devtools/pull/8441). It is now also possible to search and filter by these metadata values. - \[#8473]\(https\://github.com/flutter/devtools/pull/8473) !\[Logging metadata display]\(/assets/images/docs/tools/devtools/release-notes/images-2.41.0/log\_metadata.png "Logging metadata display") \* Add a filter text field to the top-level Logging controls. - \[#8427]\(https\://github.com/flutter/devtools/pull/8427) !\[Logging filter]\(/assets/images/docs/tools/devtools/release-notes/images-2.41.0/log\_filter.png "Logging filter") \* Added support for filtering by log severity / levels. - \[#8433]\(https\://github.com/flutter/devtools/pull/8433) !\[Log level filter]\(/assets/images/docs/tools/devtools/release-notes/images-2.41.0/log\_level\_filter.png "Log level filter") \* Added a setting to set the log retention limit. - \[#8493]\(https\://github.com/flutter/devtools/pull/8493) \* Added a button to toggle the log details display between raw text and JSON. - \[#8445]\(https\://github.com/flutter/devtools/pull/8445) \* Fixed a bug where logs would get out of order after midnight. - \[#8420]\(https\://github.com/flutter/devtools/pull/8420) \* Automatically scroll logs table to the bottom on the initial load. - \[#8437]\(https\://github.com/flutter/devtools/pull/8437) ## VS Code Sidebar updates \* The legacy \`postMessage\` version of the VS Code sidebar has been removed in favor of the DTD-powered version. Trying to access the legacy sidebar will show a message advising to update your Dart VS Code extension. The Dart VS Code extension was the only user of the legacy sidebar and migrated off in v3.96. ## Full commit history To find a complete list of changes in this release, check out the \[DevTools git log]\(https\://github.com/flutter/devtools/tree/v2.41.0).

----
url: https://docs.flutter.dev/deployment/flavors-linux
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Set up Flutter flavors for Linux](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

This guide shows you how to create Flutter flavors for Linux desktop apps.

## Overview

[#](#overview)

A Flutter flavor represents a collection of settings that define how a specific version of your app builds and runs. For example, a flavor can determine the window title, application ID, API endpoint, asset set, and logging configuration for a build.

On Linux, Flutter uses [CMake](https://cmake.org/) to configure and build the native desktop runner. When you run `flutter run` or `flutter build` with the `--flavor` flag, Flutter writes the flavor name into `linux/flutter/ephemeral/generated_config.cmake` as the `FLUTTER_APP_FLAVOR` variable, and isolates the build outputs in a flavor-specific directory.

The following table illustrates the build directories that Flutter creates when a project defines two flavors (`staging`, `production`) and two build modes (`debug`, `release`):

| Flavor     | Build mode | Output directory                                |
| ---------- | ---------- | ----------------------------------------------- |
| staging    | debug      | `build/linux/<arch>/staging/debug/bundle/`      |
| production | debug      | `build/linux/<arch>/production/debug/bundle/`   |
| staging    | release    | `build/linux/<arch>/staging/release/bundle/`    |
| production | release    | `build/linux/<arch>/production/release/bundle/` |

## Configure CMake for flavors (optional)

[#](#configure-cmake)

Passing `--flavor` works on Linux without any project configuration. Existing Flutter projects support flavors with no template updates or configuration changes required. When you pass `--flavor`, Flutter isolates the build directory, populates `appFlavor`, and filters flavor-specific assets automatically.

Complete the following optional steps only if you want to differentiate the native runner per flavor—for example, its GTK application ID or window title. This example configures two flavors called `staging` and `production`.

1. Create a new Flutter project called `flavors_example`:

   ```
   $ flutter create flavors_example
   $ cd flavors_example
   ```

2. Configure flavor-specific application IDs in `linux/CMakeLists.txt`:

   Open `linux/CMakeLists.txt` and find the `add_subdirectory(${FLUTTER_MANAGED_DIR})` line. Set the `APPLICATION_ID` per flavor immediately after that line:

   linux/CMakeLists.txt

   cmake

   ```
   # Flutter library and tool build rules.
   set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter")
   add_subdirectory(${FLUTTER_MANAGED_DIR})

   if(DEFINED FLUTTER_APP_FLAVOR)
     if(FLUTTER_APP_FLAVOR STREQUAL "staging")
       set(APPLICATION_ID "com.example.staging")
     elseif(FLUTTER_APP_FLAVOR STREQUAL "production")
       set(APPLICATION_ID "com.example.production")
     endif()
   endif()
   ```

   `APPLICATION_ID` must be configured after `add_subdirectory(${FLUTTER_MANAGED_DIR})` because `FLUTTER_APP_FLAVOR` only becomes available at this point, and before `add_subdirectory("runner")` where `APPLICATION_ID` is consumed.

3. Verify that your flavors run correctly:

   Run the `staging` flavor:

   ```
   $ flutter run -d linux --flavor staging
   ```

   Run the `production` flavor:

   ```
   $ flutter run -d linux --flavor production
   ```

## Launch a flavor

[#](#launch-a-flavor)

After you configure flavors for your app, run, build, or test a specific flavor using the `--flavor` flag with the Flutter CLI.

### Run a flavor in debug mode

[#](#run-a-flavor)

To run a specific flavor during development, pass the `--flavor` option to `flutter run`:

```
$ flutter run -d linux --flavor <flavor_name>
```

Replace `<flavor_name>` with the name of your flavor (for example, `staging` or `production`).

### Build a release binary

[#](#build-a-flavor)

To build a release executable for a specific flavor, pass the `--flavor` option to `flutter build`:

```
$ flutter build linux --flavor <flavor_name>
```

Flutter outputs the compiled bundle to `build/linux/<arch>/<flavor_name>/release/bundle/`.

### Run tests with a flavor

[#](#test-a-flavor)

The `--flavor` option also works with `flutter test` and `flutter drive`, allowing you to run unit, widget, or integration tests against a specific flavor configuration:

```
$ flutter test --flavor <flavor_name>
```

   ```
   import 'package:flutter/services.dart';
   ```

2. **Read the flavor value:**

   Use the `appFlavor` constant in your application logic (often in `main()`) to handle flavor-specific configuration:

   dart

   ```
   void main() {
     if (appFlavor == 'production') {
       Config.apiUrl = 'https://api.example.com';
     } else if (appFlavor == 'staging') {
       Config.apiUrl = 'https://staging.api.example.com';
     }
   ​
     runApp(const MyApp());
   }
   ```

## Customize configurations

[#](#customize-configurations)

After adding flavors, you can customize native settings and assets for each configuration.

### Create distinct window titles

[#](#create-distinct-window-titles)

To help distinguish between different flavors at runtime, customize the native window title for each flavor.

1. In `linux/runner/CMakeLists.txt`, pass `FLUTTER_APP_FLAVOR` as a preprocessor definition directly after the existing `APPLICATION_ID` definition:

   linux/runner/CMakeLists.txt

   cmake

   ```
   # Add preprocessor definitions for the application ID.
   add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}")

   if(DEFINED FLUTTER_APP_FLAVOR AND NOT FLUTTER_APP_FLAVOR STREQUAL "")
     add_definitions(-DFLUTTER_APP_FLAVOR="${FLUTTER_APP_FLAVOR}")
   endif()
   ```

2. In `linux/runner/my_application.cc`, update the window title in the `my_application_activate` function:

   linux/runner/my\_application.cc

   c

   ```
     const gchar* window_title = "flavors_example";
   #ifdef FLUTTER_APP_FLAVOR
     if (g_strcmp0(FLUTTER_APP_FLAVOR, "staging") == 0) {
       window_title = "Staging App";
     } else if (g_strcmp0(FLUTTER_APP_FLAVOR, "production") == 0) {
       window_title = "Production App";
     }
   #endif
   ```

   Then update `my_application_activate` to use `window_title` instead of the hardcoded string literals:

   linux/runner/my\_application.cc

   c

   ```
       gtk_header_bar_set_title(header_bar, window_title);
       // ...
       gtk_window_set_title(window, window_title);
   ```

### Configure app icons

[#](#configure-app-icons)

On Linux, application icons are not embedded directly in the compiled executable binary. Instead, the desktop environment resolves app icons through `.desktop` launcher files and installed icon themes.

To provide different icons or launcher entries for each flavor, configure your Linux package format—such as Snap, Flatpak, or Debian packages—to install distinct desktop entries and icon assets. To learn more about packaging Linux apps, refer to [Build and release a Linux desktop app](/deployment/linux).

### Bundle assets by flavor

[#](#bundle-assets)

If you have assets that only apply to a specific flavor, configure Flutter to only bundle those assets when building that flavor. This prevents unused assets from increasing your application bundle size.

To bundle assets conditionally, add the `flavors` list to an asset entry in `pubspec.yaml`:

pubspec.yaml

yaml

```
flutter:
  assets:
    - assets/common/
    - path: assets/staging/
      flavors:
        - staging
    - path: assets/production/
      flavors:
        - production
```

To learn more, consult the [`assets` field](/tools/pubspec#assets) in [Flutter pubspec options](/tools/pubspec).

### Set a default flavor

[#](#set-default-flavor)

To specify a flavor to use when running or building without the `--flavor` flag, add the `default-flavor` property to `pubspec.yaml`:

pubspec.yaml

yaml

```
flutter:
  default-flavor: staging
```

To learn more, consult the [`default-flavor` field](/tools/pubspec#default-flavor) in [Flutter pubspec options](/tools/pubspec).

## More information

[#](#more-information)

For more information on flavors and desktop deployment, consult the following resources:

* [Set up Flutter flavors for Android](/deployment/flavors)
* [Set up Flutter flavors for iOS and macOS](/deployment/flavors-ios)
* [Set up Flutter flavors for Windows](/deployment/flavors-windows)
* [Build and release a Linux desktop app](/deployment/linux)

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-09-10. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/deployment/flavors-linux.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/deployment/flavors-linux\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/deployment/flavors-linux.md "Report an issue with this page").

----
url: https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.39.0
----

The 2.39.0 release of the Dart and Flutter DevTools includes the following changes among other general improvements. To learn more about DevTools, check out the [DevTools overview](/tools/devtools/overview).

## General updates

[#](#general-updates)

* Changed table columns to be sortable by default. - [#8175](https://github.com/flutter/devtools/pull/8175)
* Updated DevTools screen icons to match what is used in the Flutter-supported IDEs. - [#8181](https://github.com/flutter/devtools/pull/8181)

## Memory updates

[#](#memory-updates)

* Enabled offline analysis of memory snapshots, as well as support for viewing memory data when an app disconnects. For example, this may happen when an app unexpectedly crashes or hits an out-of-memory issue. - [#7843](https://github.com/flutter/devtools/pull/7843), [#8093](https://github.com/flutter/devtools/pull/8093), [#8096](https://github.com/flutter/devtools/pull/8096)

* Fixed issue where the memory chart could cause the connected application to hit an out of memory exception while allocating large, short-lived objects repeatedly. - [#8209](https://github.com/flutter/devtools/pull/8209)

## App size tool updates

[#](#app-size-tool-updates)

* Added UI polish to the file import views. [#8232](https://github.com/flutter/devtools/pull/8232)

## Full commit history

[#](#full-commit-history)

To find a complete list of changes in this release, check out the [DevTools git log](https://github.com/flutter/devtools/tree/v2.39.0).

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/release-notes/release-notes-2.39.0.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.39.0\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/release-notes/release-notes-2.39.0.md "Report an issue with this page").

----
url: https://docs.flutter.dev/resources/games-toolkit.md
----

\# Casual Games Toolkit > Learn about free & open source multiplatform 2D game development in Flutter. The Flutter Casual Games Toolkit pulls together new and existing resources so you can accelerate development of games on mobile platforms. This page outlines where you can find these available resources. ## Why Flutter for games? The Flutter framework can create performant apps for six target platforms from the desktop to mobile devices to the web. With Flutter's benefits of cross-platform development, performance, and open source licensing, it makes a great choice for games. Casual games fall into two categories: turn-based games and real-time games. You might be familiar with both types of games, though perhaps you didn't think about them in quite this way. \_Turn-based games\_ cover games meant for a mass market with simple rules and gameplay. This includes board games, card games, puzzles, and strategy games. These games respond to simple user input, like tapping on a card or entering a number or letter. These games are well suited for Flutter. \_Real-time games\_ cover games with a series of actions that require real-time responses. These include endless runner games, racing games, and so on. You might want to create a game with advanced features like collision detection, camera views, game loops, and the like. These types of games could use an open source game engine like the \[Flame game engine]\[] built using Flutter. ## What's included in the toolkit The Casual Games Toolkit provides the following free resources. \* A repository that includes three game templates that provide a starting point for building a casual game. 1. A \[base game template]\[basic-template] that includes the basics for: \* Main menu \* Navigation \* Settings \* Level selection \* Player progress \* Play session management \* Sound \* Themes 1. A \[card game template]\[card-template] that includes everything in the base template plus: \* Drag and drop \* Game state management \* Multiplayer integration hooks 1. An \[endless runner template]\[runner-template] created in partnership with the open source game engine, Flame. It implements: \* A FlameGame base template \* Player steering \* Collision detection \* Parallax effects \* Spawning \* Different visual effects 1. A sample game built on top of the endless runner template, called SuperDash. You can play the game on iOS, Android, or \[web]\[], \[view the open source code repo]\[], or \[read how the game was created in 6 weeks]\[]. \* Developer guides for integrating needed services. \* A link to a \[Flame Discord]\[game-discord] channel. If you have a Discord account, use this \[direct link]\[discord-direct]. The included game templates and cookbook recipes make certain choices to accelerate development. They include specific packages, like \`provider\`, \`google\_mobile\_ads\`, \`in\_app\_purchase\`, \`audioplayers\`, \`crashlytics\`, and \`games\_services\`. If you prefer other packages, you can change the code to use them. The Flutter team understands that monetization might be a future consideration. Cookbook recipes for advertising and in-app purchases have been added. As explained on the \[Games]\[] page, you can leverage up to $900 in offers when you integrate Google services, such as \[Cloud, Firebase]\[], and \[Ads]\[], into your game. :::important You must connect your Firebase and GCP accounts to use credits for Firebase services and verify your business email during sign up to earn an additional $100 on top of the normal $300 credit. For the Ads offer, \[check your region's eligibility]\[]. ::: ## Get started Are you ready? To get started: 1. If you haven't done so, \[install Flutter]\[]. 1. \[Clone the games repo]\[game-repo]. 1. Review the \`README\` file for the first type of game you want to create. \* \[basic game]\[basic-template-readme] \* \[card game]\[card-template-readme] \* \[runner game]\[runner-template-readme] 1. \[Join the Flame community on Discord]\[game-discord] (use the \[direct link]\[discord-direct] if you already have a Discord account). 1. Review the codelabs and cookbook recipes. \* Build a \[multiplayer game]\[multiplayer-recipe] with Cloud Firestore. \* Build a \[word puzzle]\[] with Flutter. \* Make your games more engaging with \[leaderboards and achievements]\[leaderboard-recipe]. \* Monetize your games with \[in-game ads]\[ads-recipe] and \[in-app purchases]\[iap-recipe]. \* Add user authentication flow to your game with \[Firebase Authentication]\[firebase-auth]. \* Collect analytics about crashes and errors inside your game with \[Firebase Crashlytics]\[firebase-crashlytics]. 1. Set up accounts on AdMob, Firebase, and Cloud, as needed. 1. Write your game! 1. Deploy to both the Google Play and Apple stores. \[word puzzle]: https\://codelabs.developers.google.com/codelabs/flutter-word-puzzle ## Example games For Google I/O 2022, both the Flutter team and Very Good Ventures created new games. \* VGV created the \[I/O Pinball game]\[pinball-game] using the Flame engine. To learn about this game, read \[I/O Pinball Powered by Flutter and Firebase]\[] on the Flutter blog and \[play the game]\[pinball-game] in your browser. \* The Flutter team created \[I/O Flip]\[flip-game], a virtual \[CCG]. To learn more about I/O Flip, check out \[How It's Made: I/O FLIP adds a twist to a classic card game with generative AI]\[flip-blog] on the Google Developers blog and \[play the game]\[flip-game] in your browser. ## Other resources Once you feel ready to go beyond these games templates, investigate other resources that our community recommended.

| Feature                     | Resources                                                                                                                                                                                                    |
| --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Animation and sprites       | \[Special effects]\[] \[Spriter Pro]\[] \[rive]\[]                                                                                                                                                           |
| App review                  | \[app\_review]\[]                                                                                                                                                                                            |
| Audio                       | \[audioplayers]\[] \[flutter\_soloud]\[]                                                                                                                                                                     |
| Authentication              | \[User Authentication using Firebase]\[firebase-auth]                                                                                                                                                        |
| Cloud services              | \[Add Firebase to your Flutter game]\[]                                                                                                                                                                      |
| Debugging                   | \[Firebase Crashlytics overview]\[firebase-crashlytics] \[firebase\_crashlytics]\[]                                                                                                                          |
| Drivers                     | \[win32\_gamepad]\[]                                                                                                                                                                                         |
| Game assets and asset tools | \[CraftPix]\[] \[Game Developer Studio]\[] \[GIMP]\[]                                                                                                                                                        |
| Game engines                | \[Flame]\[flame-pkg] \[Bonfire]\[bonfire-pkg] \[forge2d]\[]                                                                                                                                                  |
| Game features               | \[Add achievements and leaderboards to your game]\[leaderboard-recipe] \[Add multiplayer support to your game]\[multiplayer-recipe]                                                                          |
| Game services integration   | \[games\_services]\[game-svc-pkg]                                                                                                                                                                            |
| Level editor                | \[Tiled]\[]                                                                                                                                                                                                  |
| Monetization                | \[Add advertising to your Flutter game]\[ads-recipe] \[Add AdMob ads to a Flutter app]\[] \[Add in-app purchases to your Flutter app]\[iap-recipe] \[Gaming UX and Revenue Optimizations for Apps]\[] (PDF)  |
| Persistence                 | \[shared\_preferences]\[] \[sqflite]\[] \[cbl\_flutter]\[] (Couchbase Lite)                                                                                                                                  |
| Special effects             | \[Paint API]\[] \[Special effects]\[]                                                                                                                                                                        |
| User Experience             | \[Best practices for optimizing Flutter web loading speed]\[]                                                                                                                                                |

\[Ads]: https\://ads.google.com/intl/en\_us/home/flutter/#!/ \[Air Hockey]: https\://play.google.com/store/apps/details?id=com.ignacemaes.airhockey \[CCG]: https\://en.wikipedia.org/wiki/Collectible\_card\_game \[Cloud, Firebase]: https\://cloud.google.com/free \[Flame game engine]: https\://flame-engine.org/ \[Games]: https\://flutter.dev/games \[I/O Pinball Powered by Flutter and Firebase]: https\://flutter.dev/blog/i-o-pinball-powered-by-flutter-and-firebase \[install Flutter]: /install \[Tomb Toad]: https\://play.google.com/store/apps/details?id=com.crescentmoongames.tombtoad \[basic-template-readme]: https\://github.com/flutter/games/blob/main/templates/basic/README.md \[basic-template]: https\://github.com/flutter/games/tree/main/templates/basic \[card-template-readme]: https\://github.com/flutter/games/blob/main/templates/card/README.md \[card-template]: https\://github.com/flutter/games/tree/main/templates/card \[check your region's eligibility]: https\://www\.google.com/intl/en/ads/coupons/terms/flutter/ \[discord-direct]: https\://discord.com/login?redirect\_to=%2Fchannels%2F509714518008528896%2F788415774938103829 \[firebase\_crashlytics]: https\://pub.dev/packages/firebase\_crashlytics \[flame-pkg]: https\://pub.dev/packages/flame \[flip-blog]: https\://developers.googleblog.com/2023/05/how-its-made-io-flip-adds-twist-to.html \[flip-game]: https\://flip.withgoogle.com/#/ \[game-discord]: https\://discord.gg/qUyQFVbV45 \[game-repo]: https\://github.com/flutter/games \[pinball-game]: https\://pinball.flutter.dev/#/ \[runner-template-readme]: https\://github.com/flutter/games/blob/main/templates/endless\_runner/README.md \[runner-template]: https\://github.com/flutter/games/tree/main/templates/endless\_runner \[Add AdMob ads to a Flutter app]: https\://codelabs.developers.google.com/codelabs/admob-ads-in-flutter \[firebase-crashlytics]: https\://firebase.google.com/docs/crashlytics/get-started?platform=flutter \[ads-recipe]: /cookbook/plugins/google-mobile-ads \[iap-recipe]: https\://codelabs.developers.google.com/codelabs/flutter-in-app-purchases#0 \[leaderboard-recipe]: /cookbook/games/achievements-leaderboard \[multiplayer-recipe]: /cookbook/games/firestore-multiplayer \[firebase-auth]: https\://firebase.google.com/codelabs/firebase-auth-in-flutter-apps#0 \[bonfire-pkg]: https\://pub.dev/packages/bonfire \[CraftPix]: https\://craftpix.net \[Add Firebase to your Flutter game]: https\://firebase.google.com/docs/flutter/setup \[GIMP]: https\://www\.gimp.org \[Game Developer Studio]: https\://www\.gamedeveloperstudio.com \[Gaming UX and Revenue Optimizations for Apps]: https\://flutter.dev/go/games-revenue \[Paint API]: https\://api.flutter.dev/flutter/dart-ui/Paint-class.html \[Special effects]: /cookbook/effects \[Spriter Pro]: https\://store.steampowered.com/app/332360/Spriter\_Pro/ \[app\_review]: https\://pub.dev/packages/app\_review \[audioplayers]: https\://pub.dev/packages/audioplayers \[cbl\_flutter]: https\://pub.dev/packages/cbl\_flutter \[firebase\_crashlytics]: https\://pub.dev/packages/firebase\_crashlytics \[forge2d]: https\://pub.dev/packages/forge2d \[game-svc-pkg]: https\://pub.dev/packages/games\_services \[rive]: https\://pub.dev/packages/rive \[shared\_preferences]: https\://pub.dev/packages/shared\_preferences \[sqflite]: https\://pub.dev/packages/sqflite \[win32\_gamepad]: https\://pub.dev/packages/win32\_gamepad \[read how the game was created in 6 weeks]: https\://blog.flutter.dev/how-we-built-the-new-super-dash-demo-in-flutter-and-flame-in-just-six-weeks-9c7aa2a5ad31 \[view the open source code repo]: https\://github.com/flutter/super\_dash \[web]: https\://superdash.flutter.dev/ \[Tiled]: https\://www\.mapeditor.org/ \[flutter\_soloud]: https\://pub.dev/packages/flutter\_soloud \[Best practices for optimizing Flutter web loading speed]: https\://blog.flutter.dev/best-practices-for-optimizing-flutter-web-loading-speed-7cc0df14ce5c ## Other resources Check out the following videos: \* \[Building multiplatform games with Flutter]\[gdc-talk], a talk given at the \[Game Developer Conference (GDC)]\[] 2024. \* \[How to build a physics-based game with Flutter and Flame's Forge2D]\[forge2d-video], from Google I/O 2024. \[Game Developer Conference (GDC)]: https\://gdconf.com/ \[forge2d-video]: https\://www\.youtube.com/watch?v=nsnQJrYHHNQ \[gdc-talk]: https\://www\.youtube.com/watch?v=7mG\_sW40tsw

----
url: https://docs.flutter.dev/cookbook/plugins/picture-using-camera.md
----

\# Take a picture using the camera > How to use a camera plugin on mobile.

<!--?code-excerpt path-base="cookbook/plugins/picture_using_camera/"?-->

Many apps require working with the device's cameras to take photos and videos. Flutter provides the \[\`camera\`]\[] plugin for this purpose. The \`camera\` plugin provides tools to get a list of the available cameras, display a preview coming from a specific camera, and take photos or videos. :::note The \[\`camera\_android\_camerax\`]\[] plugin, built on top of the \[CameraX]\[] Android library, improves image resolution with automatic selection of the resolution based on the device's capability. This plugin also helps deal with \_device quirks\_, defined as camera hardware that might not work as expected. For more information, check out the Google I/O 2024 talk, \[Building picture perfect camera experiences in Flutter with CameraX]\[camerax-video]. ::: \[\`camera\_android\_camerax\`]: https\://pub.dev/packages/camera\_android\_camerax \[CameraX]: https\://developer.android.com/training/camerax \[camerax-video]: https\://www\.youtube.com/watch?v=d1sRCa5k2Sg\&t=1s This recipe demonstrates how to use the \`camera\` plugin to display a preview, take a photo, and display it using the following steps: 1. Add the required dependencies. 2. Get a list of the available cameras. 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. ## 1. Add the required dependencies To complete this recipe, you need to add three dependencies to your app: \[\`camera\`]\[] : Provides tools to work with the cameras on the device. \[\`path\_provider\`]\[] : Finds the correct paths to store images. \[\`path\`]\[] : Creates paths that work on any platform. To add the packages as dependencies, run \`flutter pub add\`: \`\`\`console $ flutter pub add camera path\_provider path \`\`\` :::tip On iOS, the following lines must be added inside \`ios/Runner/Info.plist\` to access the camera and microphone: \`\`\`xml NSCameraUsageDescription Explanation on why the camera access is needed. NSMicrophoneUsageDescription Explanation on why the microphone access is needed. \`\`\` ::: ## 2. Get a list of the available cameras Next, get a list of available cameras using the \`camera\` plugin.

<!--?code-excerpt "lib/main.dart (init)"?-->

\`\`\`dart // Ensure that plugin services are initialized so that \`availableCameras()\` // can be called before \`runApp()\` WidgetsFlutterBinding.ensureInitialized(); // Obtain a list of the available cameras on the device. final cameras = await availableCameras(); // 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, use the following steps 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. 1. Create a \`StatefulWidget\` with a companion \`State\` class. 2. Add a variable to the \`State\` class to store the \`CameraController\`. 3. Add a variable to the \`State\` class to store the \`Future\` returned from \`CameraController.initialize()\`. 4. Create and initialize the controller in the \`initState()\` method. 5. Dispose of the controller in the \`dispose()\` method.

<!--?code-excerpt "lib/main_step3.dart (controller)" remove="ignore:"?-->

\`\`\`dart // A screen that allows users to take a picture using a given camera. class TakePictureScreen extends StatefulWidget { const TakePictureScreen({super.key, required this.camera}); final CameraDescription camera; @override TakePictureScreenState createState() => TakePictureScreenState(); } class TakePictureScreenState extends State { late CameraController \_controller; late Future \_initializeControllerFuture; @override void initState() { super.initState(); // To display the current output from the Camera, // create a CameraController. \_controller = CameraController( // Get a specific camera from the list of available cameras. widget.camera, // Define the resolution to use. ResolutionPreset.medium, ); // Next, initialize the controller. This returns a Future. \_initializeControllerFuture = \_controller.initialize(); } @override void dispose() { // 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. return Container(); } } \`\`\` :::warning If you don't initialize the \`CameraController\`, you \*cannot\* use the camera to display a preview and take pictures. ::: ## 4. Use a \`CameraPreview\` to display the camera's feed Next, use the \`CameraPreview\` widget from the \`camera\` package to display a preview of the camera's feed. :::note 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\`. ::: Use a \[\`FutureBuilder\`]\[] for exactly this purpose.

<!--?code-excerpt "lib/main.dart (FutureBuilder)" replace="/body: //g;/^\),$/)/g"?-->

\`\`\`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. FutureBuilder( future: \_initializeControllerFuture, builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.done) { // If the Future is complete, display the preview. return CameraPreview(\_controller); } else { // Otherwise, display a loading indicator. return const Center(child: CircularProgressIndicator()); } }, ) \`\`\` ## 5. Take a picture with the \`CameraController\` You can use the \`CameraController\` to take pictures using the \[\`takePicture()\`]\[] method, which returns an \[\`XFile\`]\[], a cross-platform, simplified \`File\` abstraction. On both Android and IOS, the new image is stored in their respective cache directories, and the \`path\` to that location is returned in the \`XFile\`. In this example, create a \`FloatingActionButton\` that takes a picture using the \`CameraController\` when a user taps on the button. Taking a picture requires 2 steps: 1. Ensure that the camera is initialized. 2. Use the controller to take a picture and ensure that it returns a \`Future\`. It is good practice to wrap these operations in a \`try / catch\` block in order to handle any errors that might occur.

<!--?code-excerpt "lib/main_step5.dart (FAB)" replace="/^floatingActionButton: //g;/^\),$/)/g"?-->

\`\`\`dart FloatingActionButton( // Provide an onPressed callback. onPressed: () async { // Take the Picture in a try / catch block. If anything goes wrong, // catch the error. try { // Ensure that the camera is initialized. await \_initializeControllerFuture; // Attempt to take a picture and then get the location // where the image file is saved. final image = await \_controller.takePicture(); } catch (e) { // If an error occurs, log the error to the console. print(e); } }, child: const Icon(Icons.camera\_alt), ) \`\`\` ## 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 the path created in the previous step.

<!--?code-excerpt "lib/image_file.dart (ImageFile)" replace="/^return\ //g"?-->

\`\`\`dart Image.file(File('path/to/my/picture.png')); \`\`\` ## Complete example

<!--?code-excerpt "lib/main.dart"?-->

\`\`\`dart import 'dart:async'; import 'dart:io'; import 'package:camera/camera.dart'; import 'package:material\_ui/material\_ui.dart'; Future main() async { // Ensure that plugin services are initialized so that \`availableCameras()\` // can be called before \`runApp()\` WidgetsFlutterBinding.ensureInitialized(); // Obtain a list of the available cameras on the device. final cameras = await availableCameras(); // 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. camera: firstCamera, ), ), ); } // A screen that allows users to take a picture using a given camera. class TakePictureScreen extends StatefulWidget { const TakePictureScreen({super.key, required this.camera}); final CameraDescription camera; @override TakePictureScreenState createState() => TakePictureScreenState(); } class TakePictureScreenState extends State { late CameraController \_controller; late Future \_initializeControllerFuture; @override void initState() { super.initState(); // To display the current output from the Camera, // create a CameraController. \_controller = CameraController( // Get a specific camera from the list of available cameras. widget.camera, // Define the resolution to use. ResolutionPreset.medium, ); // Next, initialize the controller. This returns a Future. \_initializeControllerFuture = \_controller.initialize(); } @override void dispose() { // Dispose of the controller when the widget is disposed. \_controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const 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. body: FutureBuilder( future: \_initializeControllerFuture, builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.done) { // If the Future is complete, display the preview. return CameraPreview(\_controller); } else { // Otherwise, display a loading indicator. return const Center(child: CircularProgressIndicator()); } }, ), floatingActionButton: FloatingActionButton( // Provide an onPressed callback. onPressed: () async { // Take the Picture in a try / catch block. If anything goes wrong, // catch the error. try { // Ensure that the camera is initialized. await \_initializeControllerFuture; // Attempt to take a picture and get the file \`image\` // where it was saved. final image = await \_controller.takePicture(); if (!context.mounted) return; // If the picture was taken, display it on a new screen. await Navigator.of(context).push( MaterialPageRoute( builder: (context) => DisplayPictureScreen( // Pass the automatically generated path to // the DisplayPictureScreen widget. imagePath: image.path, ), ), ); } catch (e) { // If an error occurs, log the error to the console. print(e); } }, child: const Icon(Icons.camera\_alt), ), ); } } // A widget that displays the picture taken by the user. class DisplayPictureScreen extends StatelessWidget { final String imagePath; const DisplayPictureScreen({super.key, required this.imagePath}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const 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. body: Image.file(File(imagePath)), ); } } \`\`\` \[\`camera\`]: https\://pub.dev/packages/camera \[\`FutureBuilder\`]: https\://api.flutter.dev/flutter/widgets/FutureBuilder-class.html \[\`path\`]: https\://pub.dev/packages/path \[\`path\_provider\`]: https\://pub.dev/packages/path\_provider \[\`takePicture()\`]: https\://pub.dev/documentation/camera/latest/camera/CameraController/takePicture.html \[\`XFile\`]: https\://pub.dev/documentation/cross\_file/latest/cross\_file/XFile-class.html

----
url: https://docs.flutter.dev/ui/adaptive-responsive/input
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top User input & accessibility](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

It isn't enough to just adapt how your app looks, you also have to support a variety of user inputs. The mouse and keyboard introduce input types beyond those found on a touch device, like scroll wheel, right-click, hover interactions, tab traversal, and keyboard shortcuts.

Some of these features work by default on Material widgets. But, if you've created a custom widget, you might need to implement them directly.

Some features that encompass a well-designed app, also help users who work with assistive technologies. For example, aside from being **good app design**, some features, like tab traversal and keyboard shortcuts, are *critical for users who work with assistive devices*. In addition to the standard advice for [creating accessible apps](/ui/accessibility), this page covers info for creating apps that are both adaptive *and* accessible.

## Scroll wheel for custom widgets

[#](#scroll-wheel-for-custom-widgets)

Scrolling widgets like `ScrollView` or `ListView` support the scroll wheel by default, and because almost every scrollable custom widget is built using one of these, it works with those as well.

If you need to implement custom scroll behavior, you can use the [`Listener`](https://api.flutter.dev/flutter/widgets/Listener-class.html) widget, which lets you customize how your UI reacts to the scroll wheel.

dart

```
return Listener(
  onPointerSignal: (event) {
    if (event is PointerScrollEvent) print(event.scrollDelta.dy);
  },
  child: ListView(),
);
```

## Tab traversal and focus interactions

[#](#tab-traversal-and-focus-interactions)

Users with physical keyboards expect that they can use the tab key to quickly navigate an application, and users with motor or vision differences often rely completely on keyboard navigation.

There are two considerations for tab interactions: how focus moves from widget to widget, known as traversal, and the visual highlight shown when a widget is focused.

Most built-in components, like buttons and text fields, support traversal and highlights by default. If you have your own widget that you want included in traversal, you can use the [`FocusableActionDetector`](https://api.flutter.dev/flutter/widgets/FocusableActionDetector-class.html) widget to create your own controls. The [`FocusableActionDetector`](https://api.flutter.dev/flutter/widgets/FocusableActionDetector-class.html) widget is helpful for combining focus, mouse input, and shortcuts together in one widget. You can create a detector that defines actions and key bindings, and provides callbacks for handling focus and hover highlights.

dart

```
class _BasicActionDetectorState extends State<BasicActionDetector> {
  bool _hasFocus = false;
  @override
  Widget build(BuildContext context) {
    return FocusableActionDetector(
      onFocusChange: (value) => setState(() => _hasFocus = value),
      actions: <Type, Action<Intent>>{
        ActivateIntent: CallbackAction<Intent>(
          onInvoke: (intent) {
            print('Enter or Space was pressed!');
            return null;
          },
        ),
      },
      child: Stack(
        clipBehavior: Clip.none,
        children: [
          const FlutterLogo(size: 100),
          // Position focus in the negative margin for a cool effect
          if (_hasFocus)
            Positioned(
              left: -4,
              top: -4,
              bottom: -4,
              right: -4,
              child: _roundedBorder(),
            ),
        ],
      ),
    );
  }
}
```

### Controlling traversal order

[#](#controlling-traversal-order)

To get more control over the order that widgets are focused on when the user tabs through, you can use [`FocusTraversalGroup`](https://api.flutter.dev/flutter/widgets/FocusTraversalGroup-class.html) to define sections of the tree that should be treated as a group when tabbing.

For example, you might to tab through all the fields in a form before tabbing to the submit button:

dart

```
return Column(
  children: [
    FocusTraversalGroup(child: MyFormWithMultipleColumnsAndRows()),
    SubmitButton(),
  ],
);
```

Flutter has several built-in ways to traverse widgets and groups, defaulting to the `ReadingOrderTraversalPolicy` class. This class usually works well, but it's possible to modify this using another predefined `TraversalPolicy` class or by creating a custom policy.

## Keyboard accelerators

[#](#keyboard-accelerators)

In addition to tab traversal, desktop and web users are accustomed to having various keyboard shortcuts bound to specific actions. Whether it's the `Delete` key for quick deletions or `Control+N` for a new document, be sure to consider the different accelerators your users expect. The keyboard is a powerful input tool, so try to squeeze as much efficiency from it as you can. Your users will appreciate it!

Keyboard accelerators can be accomplished in a few ways in Flutter, depending on your goals.

If you have a single widget like a `TextField` or a `Button` that already has a focus node, you can wrap it in a [`KeyboardListener`](https://api.flutter.dev/flutter/widgets/KeyboardListener-class.html) or a [`Focus`](https://api.flutter.dev/flutter/widgets/Focus-class.html) widget and listen for keyboard events:

dart

```
  @override
  Widget build(BuildContext context) {
    return Focus(
      onKeyEvent: (node, event) {
        if (event is KeyDownEvent) {
          print(event.logicalKey);
        }
        return KeyEventResult.ignored;
      },
      child: ConstrainedBox(
        constraints: const BoxConstraints(maxWidth: 400),
        child: const TextField(
          decoration: InputDecoration(border: OutlineInputBorder()),
        ),
      ),
    );
  }
}
```

To apply a set of keyboard shortcuts to a large section of the tree, use the [`Shortcuts`](https://api.flutter.dev/flutter/widgets/Shortcuts-class.html) widget:

dart

```
// Define a class for each type of shortcut action you want
class CreateNewItemIntent extends Intent {
  const CreateNewItemIntent();
}
​
Widget build(BuildContext context) {
  return Shortcuts(
    // Bind intents to key combinations
    shortcuts: const <ShortcutActivator, Intent>{
      SingleActivator(LogicalKeyboardKey.keyN, control: true):
          CreateNewItemIntent(),
    },
    child: Actions(
      // Bind intents to an actual method in your code
      actions: <Type, Action<Intent>>{
        CreateNewItemIntent: CallbackAction<CreateNewItemIntent>(
          onInvoke: (intent) => _createNewItem(),
        ),
      },
      // Your sub-tree must be wrapped in a focusNode, so it can take focus.
      child: Focus(autofocus: true, child: Container()),
    ),
  );
}
```

The [`Shortcuts`](https://api.flutter.dev/flutter/widgets/Shortcuts-class.html) widget is useful because it only allows shortcuts to be fired when this widget tree or one of its children has focus and is visible.

The final option is a global listener. This listener can be used for always-on, app-wide shortcuts or for panels that can accept shortcuts whenever they're visible (regardless of their focus state). Adding global listeners is easy with [`HardwareKeyboard`](https://api.flutter.dev/flutter/services/HardwareKeyboard-class.html):

dart

```
@override
void initState() {
  super.initState();
  HardwareKeyboard.instance.addHandler(_handleKey);
}
​
@override
void dispose() {
  HardwareKeyboard.instance.removeHandler(_handleKey);
  super.dispose();
}
```

To check key combinations with the global listener, you can use the `HardwareKeyboard.instance.logicalKeysPressed` set. For example, a method like the following can check whether any of the provided keys are being held down:

dart

```
static bool isKeyDown(Set<LogicalKeyboardKey> keys) {
  return keys
      .intersection(HardwareKeyboard.instance.logicalKeysPressed)
      .isNotEmpty;
}
```

Putting these two things together, you can fire an action when `Shift+N` is pressed:

dart

```
bool _handleKey(KeyEvent event) {
  bool isShiftDown = isKeyDown({
    LogicalKeyboardKey.shiftLeft,
    LogicalKeyboardKey.shiftRight,
  });
​
  if (isShiftDown && event.logicalKey == LogicalKeyboardKey.keyN) {
    _createNewItem();
    return true;
  }
​
  return false;
}
```

One note of caution when using the static listener, is that you often need to disable it when the user is typing in a field or when the widget it's associated with is hidden from view. Unlike with `Shortcuts` or `KeyboardListener`, this is your responsibility to manage. This can be especially important when you're binding a Delete/Backspace accelerator for `Delete`, but then have child `TextFields` that the user might be typing in.

## Mouse enter, exit, and hover for custom widgets

[#](#custom-widgets)

On desktop, it's common to change the mouse cursor to indicate the functionality about the content the mouse is hovering over. For example, you typically see a hand cursor when you hover over a button, or an `I` cursor when you hover over text.

Flutter's Material buttons handle basic focus states for standard button and text cursors. (A notable exception is if you change the default styling of the Material buttons to set the `overlayColor` to transparent.)

Implement a focus state for any custom buttons or gesture detectors in your app. If you change the default Material button styles, test for keyboard focus states and implement your own, if needed.

To change the cursor from within your custom widgets, use [`MouseRegion`](https://api.flutter.dev/flutter/widgets/MouseRegion-class.html):

dart

```
// Show hand cursor
return MouseRegion(
  cursor: SystemMouseCursors.click,
  // Request focus when clicked
  child: GestureDetector(
    onTap: () {
      Focus.of(context).requestFocus();
      _submit();
    },
    child: Logo(showBorder: hasFocus),
  ),
);
```

`MouseRegion` is also useful for creating custom rollover and hover effects:

dart

```
return MouseRegion(
  onEnter: (_) => setState(() => _isMouseOver = true),
  onExit: (_) => setState(() => _isMouseOver = false),
  onHover: (e) => print(e.localPosition),
  child: Container(
    height: 500,
    color: _isMouseOver ? Colors.blue : Colors.black,
  ),
);
```

For an example that changes the button style to outline the button when it has focus, check out the [button code for the Wonderous app](https://github.com/gskinnerTeam/flutter-wonderous-app/blob/8a29d6709668980340b1b59c3d3588f123edd4d8/lib/ui/common/controls/buttons.dart#L143). The app modifies the [`FocusNode.hasFocus`](https://api.flutter.dev/flutter/widgets/FocusNode/hasFocus.html) property to check whether the button has focus and, if so, adds an outline.

## Visual density

[#](#visual-density)

You might consider enlarging the "hit area" of a widget to accommodate a touch screen, for example.

Different input devices offer various levels of precision, which necessitate differently-sized hit areas. Flutter's `VisualDensity` class makes it easy to adjust the density of your views across the entire application, for example, by making a button larger (and therefore easier to tap) on a touch device.

When you change the `VisualDensity` for your `MaterialApp`, `MaterialComponents` that support it animate their densities to match. By default, both horizontal and vertical densities are set to 0.0, but you can set the densities to any negative or positive value that you want. By switching between different densities, you can easily adjust your UI.

To set a custom visual density, inject the density into your `MaterialApp` theme:

dart

```
double densityAmt = touchMode ? 0.0 : -1.0;
VisualDensity density = VisualDensity(
  horizontal: densityAmt,
  vertical: densityAmt,
);
return MaterialApp(
  theme: ThemeData(visualDensity: density),
  home: MainAppScaffold(),
  debugShowCheckedModeBanner: false,
);
```

To use `VisualDensity` inside your own views, you can look it up:

dart

```
VisualDensity density = Theme.of(context).visualDensity;
```

Not only does the container react automatically to changes in density, it also animates when it changes. This ties together your custom components, along with the built-in components, for a smooth transition effect across the app.

As shown, `VisualDensity` is unit-less, so it can mean different things to different views. In the following example, 1 density unit equals 6 pixels, but this is totally up to you to decide. The fact that it is unit-less makes it quite versatile, and it should work in most contexts.

It's worth noting that the Material generally use a value of around 4 logical pixels for each visual density unit. For more information about the supported components, see the [`VisualDensity`](https://pub.dev/documentation/material_ui/latest/material_ui/VisualDensity-class.html) API. For more information about density principles in general, see the [Material Design guide](https://m2.material.io/design/layout/applying-density.html#usage).

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-09-29. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/ui/adaptive-responsive/input.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/ui/adaptive-responsive/input\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/ui/adaptive-responsive/input.md "Report an issue with this page").

----
url: https://docs.flutter.dev/ai/genui/input-events.md
----

\# Input and events > How input and events are handled in GenUI applications. This guide explains how user interactions are handled within the GenUI package, from the initial widget interaction to the AI agent receiving the event. :::experimental The \`genui\` package is in alpha and is likely to change. ::: ## Overview In the GenUI architecture, the UI is driven by the AI, but user interactions (like clicking a button or submitting a form) must be communicated back to the AI agent. This allows the agent to update the UI or perform actions in response to user input. The flow of an event is as follows: 1. Interaction: User interacts with a widget; for example, the user taps a button. 2. Capture: The widget implementation dispatches a \`UiEvent\`. 3. Processing: The framework adds context (such as a \`surfaceId\` or data model values) and forwards the event. 4. Transmission: The Flutter widget generates the event, adds the appropriate context, and routes it to the AI through the \`ContentGenerator\`, which forwards it to the AI agent. ## Defining events ### Protocol level The A2UI protocol defines an \`action\` message used to report events. An \`action\` contains: \* \`name\`: The name of the action (defined by the AI when generating the component). \* \`surfaceId\`: The ID of the UI surface where the event occurred. \* \`sourceComponentId\`: The ID of the component that triggered the event. \* \`context\`: A JSON object containing data relevant to the event. \* \`timestamp\`: When the event occurred. ### Dart implementation In \[\`package:genui\`]\[], user events are represented by the \`UiEvent\` extension type and its concrete implementation \`UserActionEvent\`. \[\`package:genui\`]: https\://pub.dev/packages/genui The following structures are defined in \`lib/src/model/ui\_models.dart\`: \`\`\`dart title="lib/src/model/ui\_models.dart" /// A data object that represents a user interaction event in the UI. extension type UiEvent.fromMap(JsonMap \_json) { ... } /// A UI event that represents a user action. extension type UserActionEvent.fromMap(JsonMap \_json) implements UiEvent { UserActionEvent({ String? surfaceId, required String name, required String sourceComponentId, JsonMap? context, // ... }) : ... } \`\`\` ## Capturing events in widgets Widgets in GenUI are defined in a \`Catalog\`, which includes information about what events the widget can send to the AI. The AI can then send information about how to communicate those events back. When you implement a custom widget (or use the standard widgets), you use the \`dispatchEvent\` method in \`CatalogItemContext\` to dispatch events. ### Example: Button implementation {: #button-example} The following example shows how a \`Button\` widget typically captures a tap and dispatches an event. It retrieves the action definition (provided by the AI) from its properties, resolves any data bindings in the context, and sends the event. \`\`\`dart // Inside a CatalogItem widgetBuilder: widgetBuilder: (itemContext) { // 1. Extract action data from the component properties. final buttonData = \_ButtonData.fromMap(itemContext.data as JsonMap); final JsonMap actionData = buttonData.action; final actionName = actionData\['name'] as String; // 2. Extract context definition (which data to send back). final List contextDefinition = (actionData\['context'] as List?) ?? \[]; return ElevatedButton( onPressed: () { // 3. Resolve the context values from the data model. final JsonMap resolvedContext = resolveContext( itemContext.dataContext, contextDefinition, ); // 4. Dispatch the event. itemContext.dispatchEvent( UserActionEvent( name: actionName, sourceComponentId: itemContext.id, context: resolvedContext, ), ); }, child: /\* ... \*/ ); }, \`\`\` ## Event processing pipeline Once \`dispatchEvent\` is called, the event travels through the GenUI core layers. ### Surface The \`Surface\` widget (in \`lib/src/core/surface.dart\`) wraps the rendered widgets. It provides the dispatchEvent callback implementation. When \`\_dispatchEvent\` is called: 1. It automatically injects the \`surfaceId\` into the event, ensuring the AI knows which surface the interaction came from. 2. It delegates handling to the \`SurfaceHost\` (implemented by \`SurfaceController\`). \`\`\`dart // Surface implementation details void \_dispatchEvent(UiEvent event) { // ... final Map eventMap = { ...event.toMap(), surfaceIdKey: widget.surfaceId, // Inject surfaceId }; final UiEvent newEvent = UserActionEvent.fromMap(eventMap); widget.host.handleUiEvent(newEvent); } \`\`\` ### SurfaceController The \`SurfaceController\` (in \`lib/src/core/surface\_controller.dart\`) is the central hub for managing UI state. When \`handleUiEvent\` is called, it does the following: 1. Verifies the event type. 2. Wraps the event in the \`action\` JSON envelope required by the protocol. 3. Emits a \`UserUiInteractionMessage\` on its \`onSubmit\` stream. \`\`\`dart // SurfaceController implementation details @override void handleUiEvent(UiEvent event) { if (event is! UserActionEvent) return; // Wrap in protocol 'action' envelope final String eventJsonString = jsonEncode({'action': event.toMap()}); // Emit for listeners (like Conversation) \_onSubmit.add(UserUiInteractionMessage.text(eventJsonString)); } \`\`\` ## Transmission to AI The final step sends the event to the AI Agent. This is typically handled by \`Conversation\` (in \`lib/src/facade/conversation.dart\`). The \`Conversation\` listens to the \`onSubmit\` stream from the message processor. \`\`\`dart // Conversation constructor \_userEventSubscription = surfaceController.onSubmit.listen(sendRequest); \`\`\` When an event is received, the \`sendRequest\` method: 1. Wraps the \`UserUiInteractionMessage\` back to the developer's client code. 2. The custom integration or predefined transport adapter forwards the message to the LLM agent network transport. The AI Agent receives this JSON message, processes the user action, and might stream back new \`surfaceUpdate\` or \`dataModelUpdate\` messages to modify the UI, or some other action, completing the full interaction loop.

----
url: https://docs.flutter.dev/perf/deferred-components
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Deferred components for Android and web](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

## Introduction

[#](#introduction)

With Flutter, Android and web apps have the capability to download deferred components (additional code and assets) while the app is already running. This is helpful if you have a large app and only want to install components if and when they are needed by the user.

While Flutter supports deferred loading on Android and the web, the implementations differ. Both require [Dart's deferred imports](https://dart.dev/language/libraries#lazily-loading-a-library).

* Android's [dynamic feature modules](https://developer.android.com/guide/playcore/feature-delivery) deliver the deferred components packaged as Android modules.

  When building for Android, though you can defer loading modules, you must build the entire app and upload that app as a single [Android App Bundle](https://developer.android.com/guide/app-bundle) (AAB). Flutter doesn't support dispatching partial updates without re-uploading new Android App Bundles for the entire application.

  Flutter performs deferred loading when you compile your Android app in [release or profile mode](/testing/build-modes), but debug mode treats all deferred components as regular imports.

* The web creates deferred components as separate `*.js` files.

For a deeper dive into the technical details of how this feature works, see [Deferred Components](https://github.com/flutter/flutter/wiki/Deferred-Components) on the [Flutter wiki](https://github.com/flutter/flutter/tree/main/docs).

## How to set your Android project up for deferred components

[#](#how-to-set-your-android-project-up-for-deferred-components)

The following instructions explain how to set up your Android app for deferred loading.

### Step 1: Dependencies and initial project setup

[#](#step-1-dependencies-and-initial-project-setup)

1. Add Play Core to the Android app's build.gradle dependencies. In `android/app/build.gradle` add the following:

   * [Kotlin](#219-tab-panel)
   * [Groovy](#220-tab-panel)

   android/app/build.gradle.kts

   kotlin

   ```
   ...
   dependencies {
     ...
     implementation("com.google.android.play:core:1.8.0")
     ...
   }
   ```

   android/app/build.gradle

   groovy

   ```
   ...
   dependencies {
     ...
     implementation "com.google.android.play:core:1.8.0"
     ...
   }
   ```

2. If using the Google Play Store as the distribution model for dynamic features, the app must support `SplitCompat` and provide an instance of a `PlayStoreDeferredComponentManager`. Both of these tasks can be accomplished by setting the `android:name` property on the application in `android/app/src/main/AndroidManifest.xml` to `io.flutter.embedding.android.FlutterPlayStoreSplitApplication`:

   xml

   ```
   <manifest ...
     <application
        android:name="io.flutter.embedding.android.FlutterPlayStoreSplitApplication"
           ...
     </application>
   </manifest>
   ```

   `io.flutter.app.FlutterPlayStoreSplitApplication` handles both of these tasks for you. If you use `FlutterPlayStoreSplitApplication`, you can skip to step 1.3.

   If your Android application is large or complex, you might want to separately support `SplitCompat` and provide the `PlayStoreDynamicFeatureManager` manually.

   To support `SplitCompat`, there are three methods (as detailed in the [Android docs](https://developer.android.com/guide/playcore/feature-delivery#declare_splitcompatapplication_in_the_manifest)), any of which are valid:

   * Make your application class extend `SplitCompatApplication`:

     java

     ```
     public class MyApplication extends SplitCompatApplication {
         ...
     }
     ```

   * Call `SplitCompat.install(this);` in the `attachBaseContext()` method:

     java

     ```
     @Override
     protected void attachBaseContext(Context base) {
         super.attachBaseContext(base);
         // Emulates installation of future on demand modules using SplitCompat.
         SplitCompat.install(this);
     }
     ```

   * Declare `SplitCompatApplication` as the application subclass and add the Flutter compatibility code from `FlutterApplication` to your application class:

     xml

     ```
     <application
         ...
         android:name="com.google.android.play.core.splitcompat.SplitCompatApplication">
     </application>
     ```

   The embedder relies on an injected `DeferredComponentManager` instance to handle install requests for deferred components. Provide a `PlayStoreDeferredComponentManager` into the Flutter embedder by adding the following code to your app initialization:

   java

   ```
   import io.flutter.embedding.engine.dynamicfeatures.PlayStoreDeferredComponentManager;
   import io.flutter.FlutterInjector;
   ...
   PlayStoreDeferredComponentManager deferredComponentManager = new
     PlayStoreDeferredComponentManager(this, null);
   FlutterInjector.setInstance(new FlutterInjector.Builder()
       .setDeferredComponentManager(deferredComponentManager).build());
   ```

3. Opt into deferred components by adding the `deferred-components` entry to the app's `pubspec.yaml` under the `flutter` entry:

   yaml

   ```
   ...
   flutter:
     ...
     deferred-components:
     ...
   ```

   The `flutter` tool looks for the `deferred-components` entry in the `pubspec.yaml` to determine whether the app should be built as deferred or not. This can be left empty for now unless you already know the components desired and the Dart deferred libraries that go into each. You will fill in this section later in [step 3.3](#step-3.3) once `gen_snapshot` produces the loading units.

### Step 2: Implementing deferred Dart libraries

[#](#step-2-implementing-deferred-dart-libraries)

Next, implement deferred loaded Dart libraries in your app's Dart code. The implementation doesn't need to be feature complete yet. The example in the rest of this page adds a new simple deferred widget as a placeholder. You can also convert existing code to be deferred by modifying the imports and guarding usages of deferred code behind `loadLibrary()` `Futures`.

1. Create a new Dart library. For example, create a new `DeferredBox` widget that can be downloaded at runtime. This widget can be of any complexity but, for the purposes of this guide, create a simple box as a stand-in. To create a simple blue box widget, create `box.dart` with the following contents:

   box.dart

   dart

   ```
   import 'package:material_ui/material_ui.dart';
   ​
   /// A simple blue 30x30 box.
   class DeferredBox extends StatelessWidget {
     const DeferredBox({super.key});
   ​
     @override
     Widget build(BuildContext context) {
       return Container(height: 30, width: 30, color: Colors.blue);
     }
   }
   ```

2. Import the new Dart library with the `deferred` keyword in your app and call `loadLibrary()` (see [lazily loading a library](https://dart.dev/language/libraries#lazily-loading-a-library)). The following example uses `FutureBuilder` to wait for the `loadLibrary` `Future` (created in `initState`) to complete and display a `CircularProgressIndicator` as a placeholder. When the `Future` completes, it returns the `DeferredBox` widget. `SomeWidget` can then be used in the app as normal and won't ever attempt to access the deferred Dart code until it has successfully loaded.

   dart

   ```
   import 'package:material_ui/material_ui.dart';
   ​
   import 'box.dart' deferred as box;
   ​
   class SomeWidget extends StatefulWidget {
     const SomeWidget({super.key});
   ​
     @override
     State<SomeWidget> createState() => _SomeWidgetState();
   }
   ​
   class _SomeWidgetState extends State<SomeWidget> {
     late Future<void> _libraryFuture;
   ​
     @override
     void initState() {
       super.initState();
       _libraryFuture = box.loadLibrary();
     }
   ​
     @override
     Widget build(BuildContext context) {
       return FutureBuilder<void>(
         future: _libraryFuture,
         builder: (context, snapshot) {
           if (snapshot.connectionState == ConnectionState.done) {
             if (snapshot.hasError) {
               return Text('Error: ${snapshot.error}');
             }
             return box.DeferredBox();
           }
           return const CircularProgressIndicator();
         },
       );
     }
   }
   ```

   The `loadLibrary()` function returns a `Future<void>` that completes successfully when the code in the library is available for use and completes with an error otherwise. All usage of symbols from the deferred library should be guarded behind a completed `loadLibrary()` call. All imports of the library must be marked as `deferred` for it to be compiled appropriately to be used in a deferred component. If a component has already been loaded, additional calls to `loadLibrary()` complete quickly (but not synchronously). The `loadLibrary()` function can also be called early to trigger a pre-load to help mask the loading time.

   You can find another example of deferred import loading in [Flutter Gallery's `lib/deferred_widget.dart`](https://github.com/flutter/gallery/blob/main/lib/deferred_widget.dart).

### Step 3: Building the app

[#](#step-3-building-the-app)

Use the following `flutter` command to build a deferred components app:

```
$ flutter build appbundle
```

This command assists you by validating that your project is properly set up to build deferred components apps. By default, the build fails if the validator detects any issues and guides you through suggested changes to fix them.

1. []()

   The `flutter build appbundle` command runs the validator and attempts to build the app with `gen_snapshot` instructed to produce split AOT shared libraries as separate SO files. On the first run, the validator will likely fail as it detects issues; the tool makes recommendations for how to set up the project and fix these issues.

   The validator is split into two sections: prebuild and post-gen\_snapshot validation. This is because any validation referencing loading units can't be performed until `gen_snapshot` completes and produces a final set of loading units.

   The validator detects any new, changed, or removed loading units generated by `gen_snapshot`. The current generated loading units are tracked in your `<projectDirectory>/deferred_components_loading_units.yaml` file. This file should be checked into source control to ensure that changes to the loading units by other developers can be caught.

   The validator also checks for the following in the `android` directory:

   * **`<projectDir>/android/app/src/main/res/values/strings.xml`**\
     An entry for every deferred component mapping the key `${componentName}Name` to `${componentName}`. This string resource is used by the `AndroidManifest.xml` of each feature module to define the `dist:title property`. For example:

     xml

     ```
     <?xml version="1.0" encoding="utf-8"?>
     <resources>
       ...
       <string name="boxComponentName">boxComponent</string>
     </resources>
     ```

   * **`<projectDir>/android/<componentName>`**\
     An Android dynamic feature module for each deferred component exists and contains a `build.gradle` and `src/main/AndroidManifest.xml` file. This only checks for existence and doesn't validate the contents of these files. If a file doesn't exist, it generates a default recommended one.

   * **`<projectDir>/android/app/src/main/res/values/AndroidManifest.xml`**\
     Contains a meta-data entry that encodes the mapping between loading units and component name the loading unit is associated with. This mapping is used by the embedder to convert Dart's internal loading unit id to the name of a deferred component to install. For example:

     xml

     ```
     ...
     <application
         android:label="MyApp"
         android:name="io.flutter.app.FlutterPlayStoreSplitApplication"
         android:icon="@mipmap/ic_launcher">
         ...
         <meta-data android:name="io.flutter.embedding.engine.deferredcomponents.DeferredComponentManager.loadingUnitMapping" android:value="2:boxComponent"/>
     </application>
     ...
     ```

   The `gen_snapshot` validator won't run until the prebuild validator passes.

2. or each of these checks, the tool produces the modified or new files needed to pass the check. These files are placed in the `<projectDir>/build/android_deferred_components_setup_files` directory. It is recommended that the changes be applied by copying and overwriting the same files in the project's `android` directory. Before overwriting, the current project state should be committed to source control and the recommended changes should be reviewed to be appropriate. The tool won't make any changes to your `android/` directory automatically.

3. []()

   Once the available loading units are generated and logged in `<projectDirectory>/deferred_components_loading_units.yaml`, it is possible to fully configure the pubspec's `deferred-components` section so that the loading units are assigned to deferred components as desired. To continue with the box example, the generated `deferred_components_loading_units.yaml` file would contain:

   yaml

   ```
   loading-units:
     - id: 2
       libraries:
         - package:MyAppName/box.Dart
   ```

   The loading unit id ('2' in this case) is used internally by Dart, and can be ignored. The base loading unit (id '1') is not listed and contains everything not explicitly contained in another loading unit.

   You can now add the following to `pubspec.yaml`:

   yaml

   ```
   ...
   flutter:
     ...
     deferred-components:
       - name: boxComponent
         libraries:
           - package:MyAppName/box.Dart
     ...
   ```

   To assign a loading unit to a deferred component, add any Dart library in the loading unit into the libraries section of the feature module. Keep the following guidelines in mind:

   * Loading units shouldn't be included in more than one component.

   * Including one Dart library from a loading unit indicates that the entire loading unit is assigned to the deferred component.

   * All loading units not assigned to a deferred component are included in the base component, which always exists implicitly.

   * Loading units assigned to the same deferred component are downloaded, installed, and shipped together.

   * The base component is implicit and need not be defined in the pubspec.

4. Assets can also be included by adding an assets section in the deferred component configuration:

   yaml

   ```
     deferred-components:
       - name: boxComponent
         libraries:
           - package:MyAppName/box.Dart
         assets:
           - assets/image.jpg
           - assets/picture.png
             # wildcard directory
           - assets/gallery/
   ```

   An asset can be included in multiple deferred components, but installing both components results in a replicated asset. Assets-only components can also be defined by omitting the libraries section. These assets-only components must be installed with the [`DeferredComponent`](https://api.flutter.dev/flutter/services/DeferredComponent-class.html) utility class in services rather than `loadLibrary()`. Since Dart libraries are packaged together with assets, if a Dart library is loaded with `loadLibrary()`, any assets in the component are loaded as well. However, installing by component name and the services utility won't load any Dart libraries in the component.

   You are free to include assets in any component, as long as they are installed and loaded when they are first referenced, though typically, assets and the Dart code that uses those assets are best packed in the same component.

5. Manually add all deferred components that you defined in `pubspec.yaml` into the `android/settings.gradle` file as includes. For example, if there are three deferred components defined in the pubspec named, `boxComponent`, `circleComponent`, and `assetComponent`, ensure that `android/settings.gradle` contains the following:

   * [Kotlin](#221-tab-panel)
   * [Groovy](#222-tab-panel)

   android/settings.gradle.kts

   kotlin

   ```
   include(":app", ":boxComponent", ":circleComponent", ":assetComponent")
   ...
   ```

   android/settings.gradle

   groovy

   ```
   include ':app', ':boxComponent', ':circleComponent', ':assetComponent'
   ...
   ```

6. Repeat steps [3.1](#step-3.1) through 3.6 (this step) until all validator recommendations are handled and the tool runs without further recommendations.

   When successful, this command outputs an `app-release.aab` file in `build/app/outputs/bundle/release`.

   A successful build doesn't always mean the app was built as intended. It is up to you to ensure that all loading units and Dart libraries are included in the way you intended. For example, a common mistake is accidentally importing a Dart library without the `deferred` keyword, resulting in a deferred library being compiled as part of the base loading unit. In this case, the Dart library would load properly because it is always present in the base, and the library wouldn't be split off. This can be checked by examining the `deferred_components_loading_units.yaml` file to verify that the generated loading units are described as intended.

   When adjusting the deferred components configurations, or making Dart changes that add, modify, or remove loading units, you should expect the validator to fail. Follow steps [3.1](#step-3.1) through 3.6 (this step) to apply any recommended changes to continue the build.

### Running the app locally

[#](#running-the-app-locally)

Once your app has successfully built an AAB file, use Android's [`bundletool`](https://developer.android.com/studio/command-line/bundletool) to perform local testing with the `--local-testing` flag.

To run the AAB file on a test device, download the bundletool jar executable from [github.com/google/bundletool/releases](https://github.com/google/bundletool/releases) and run:

```
$ java -jar bundletool.jar build-apks --bundle=<your_app_project_dir>/build/app/outputs/bundle/release/app-release.aab --output=<your_temp_dir>/app.apks --local-testing

$ java -jar bundletool.jar install-apks --apks=<your_temp_dir>/app.apks
```

Where `<your_app_project_dir>` is the path to your app's project directory and `<your_temp_dir>` is any temporary directory used to store the outputs of bundletool. This unpacks your AAB file into an APK file and installs it on the device. All available Android dynamic features are loaded onto the device locally and installation of deferred components is emulated.

Before running `build-apks` again, remove the existing app APK file:

```
$ rm <your_temp_dir>/app.apks
```

Changes to the Dart codebase require either incrementing the Android build ID or uninstalling and reinstalling the app, as Android won't update the feature modules unless it detects a new version number.

### Releasing to the Google Play Store

[#](#releasing-to-the-google-play-store)

The built AAB file can be uploaded directly to the Play store as normal. When `loadLibrary()` is called, the needed Android module containing the Dart AOT library and assets is downloaded by the Flutter engine using the Play store's delivery feature.

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-09-29. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/perf/deferred-components.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/perf/deferred-components\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/perf/deferred-components.md "Report an issue with this page").

----
url: https://docs.flutter.dev/ui/adaptive-responsive/safearea-mediaquery.md
----

\# SafeArea & MediaQuery > Learn how to use SafeArea and MediaQuery to create an adaptive app. This page discusses how and when to use the \`SafeArea\` and \`MediaQuery\` widgets. ## SafeArea When running your app on the latest devices, you might encounter bits of the UI being blocked by cutouts on the device's screen. You can fix this with the \[\`SafeArea\`]\[] widget, which insets its child widget to avoid intrusions (like notches and camera cutouts), as well as operating system UI (such as the status bar on Android), or by rounded corners of the physical display. If you don't want this behavior, the \`SafeArea\` widget allows you to disable padding on any of its four sides. By default, all four sides are enabled. It's generally recommended to wrap the body of a \`Scaffold\` widget in \`SafeArea\` as a good place to start, but you don't always need to put it this high in the \`Widget\` tree. For example, if you purposefully want your app to stretch under the cutouts, you can move the \`SafeArea\` to wrap whatever content makes sense, and let the rest of the app take up the full screen. Using \`SafeArea\` ensures that your app content won't be cut off by physical display features or operating system UI, and sets your app up for success even as new devices with different shapes and styles of cutouts enter the market. How does \`SafeArea\` do so much in a small amount of code? Behind the scenes it uses the \`MediaQuery\` object. \[\`SafeArea\`]: https\://api.flutter.dev/flutter/widgets/SafeArea-class.html ## MediaQuery As discussed in the \[SafeArea]\(#safearea) section, \`MediaQuery\` is a powerful widget for creating adaptive apps. Sometimes you'll use \`MediaQuery\` directly, and sometimes you'll use \`SafeArea\`, which uses \`MediaQuery\` behind the scenes. \`MediaQuery\` provides lots of information, including the app's current window size. It exposes accessibility settings like high contrast mode and text scaling, or if the user is using an accessibility service like TalkBack or VoiceOver. \`MediaQuery\` also contains info about the features of your device's display, such as having a hinge or a fold. \`SafeArea\` uses the data from \`MediaQuery\` to figure out how much to inset its child \`Widget\`. Specifically, it uses the \`MediaQuery\` padding property, which is basically the amount of the display that's partially obscured by system UI, display notches, or status bar. So, why not use \`MediaQuery\` directly? The answer is that \`SafeArea\` does one clever thing that makes it beneficial to use over just raw \`MediaQueryData\`. Specifically, it modifies the \`MediaQuery\` exposed to \`SafeArea\`'s children to make it appear as if the padding added to \`SafeArea\` doesn't exist. This means that you can nest \`SafeArea\`s, and only the topmost one will apply the padding needed to avoid the notches as system UI. As your app grows and you move widgets around, you don't have to worry about having too much padding applied if you have multiple \`SafeArea\`s, whereas you would have issues if using \`MediaQueryData.padding\` directly. You \_can\_ wrap the body of a \`Scaffold\` widget with a \`SafeArea\`, but you don't \_have\_ to put it this high in the widget tree. The \`SafeArea\` just needs to wrap the contents that would cause information loss if cut off by the hardware features mentioned earlier. For example, if you purposefully want your app to stretch under the cutouts, you can move the \`SafeArea\` to wrap whatever content makes sense, and let the rest of the app take up the full screen. A side note is that this is what the \`AppBar\` widget does by default, which is how it goes underneath the system status bar. This is also why wrapping the body of a \`Scaffold\` in a \`SafeArea\` is recommended, instead of wrapping the whole \`Scaffold\` itself. \`SafeArea\` ensures that your app content won't be cut off in a generic way and sets your app up for success even as new devices with different shapes and styles of cutouts enter the market.

----
url: https://docs.flutter.dev/data-and-backend/state-mgmt/options
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Approaches to state management](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

State management is a complex topic. If you feel that some of your questions haven't been answered, or that the approach described on these pages is not viable for your use cases, you are probably right.

Learn more from the following resources, many of which have been contributed by the Flutter community.

## General overview

[#](#general-overview)

Things to review before selecting an approach.

* [Introduction to state management](/data-and-backend/state-mgmt/intro), which is the beginning of this very section (for those of you who arrived directly to this *Options* page and missed the previous pages)
* [Pragmatic State Management in Flutter](https://www.youtube.com/watch?v=d_m5csmrf7I), a video from Google I/O 2019
* [Flutter Architecture Samples](https://fluttersamples.com/), by Brian Egan

## Built-in approaches

[#](#built-in-approaches)

### `setState`

[#](#setstate)

The low-level approach to use for widget-specific, ephemeral state.

* [Adding interactivity to your Flutter app](/ui/interactivity), a Flutter tutorial
* [Basic state management in Google Flutter](https://medium.com/@agungsurya/basic-state-management-in-google-flutter-6ee73608f96d), by Agung Surya

[]()

### `ValueNotifier` and `InheritedNotifier`

[#](#valuenotifier-and-inheritednotifier)

An approach using only Flutter provided APIs to update state and notify the UI of changes.

* [State Management using ValueNotifier and InheritedNotifier](https://www.hungrimind.com/articles/flutter-state-management), by Tadas Petra

[]()

### `InheritedWidget` and `InheritedModel`

[#](#inheritedwidget-and-inheritedmodel)

The low-level approach used to communicate between ancestors and children in the widget tree. This is what `package:provider` and many other approaches use under the hood.

The following instructor-led video workshop covers how to use `InheritedWidget`:

[Watch on YouTube in a new tab: "How to manage application state using inherited widgets"](https://www.youtube.com/watch/LFcGPS6cGrY)

Other useful docs include:

* [InheritedWidget docs](https://api.flutter.dev/flutter/widgets/InheritedWidget-class.html)
* [Managing Flutter Application State With InheritedWidgets](https://blog.flutter.dev/managing-flutter-application-state-with-inheritedwidgets-1140452befe1), by Hans Muller
* [Inheriting Widgets](https://medium.com/@mehmetf_71205/inheriting-widgets-b7ac56dbbeb1), by Mehmet Fidanboylu
* [Widget - State - Context - InheritedWidget](https://flutteris.com/blog/en/widget-state-context-inheritedwidget), by Didier Bolelens

## Community-provided packages

[#](#community-provided-packages)

Depending on the complexity of your app and preferences of your team, you might find adopting a state management package useful. State management packages often help reduce boilerplate code, provide specialized debugging tools, and can help enable a clearer and consistent application architecture.

The Flutter community offers a wide variety of state management packages. The best choice for your app often depends on the app's complexity, your team's preferences, and the specific problems you need to solve.

To begin exploring the available options, check out the [`#state-management`](https://pub.dev/packages?q=topic%3Astate-management) topic on the pub.dev site and refine the search to find packages that match your needs.

[State management packages open\_in\_new](https://pub.dev/packages?q=topic%3Astate-management)

[Explore the variety of state-management packages built by and for the Flutter community.](https://pub.dev/packages?q=topic%3Astate-management)

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-07-31. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/data-and-backend/state-mgmt/options.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/data-and-backend/state-mgmt/options\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/data-and-backend/state-mgmt/options.md "Report an issue with this page").

----
url: https://docs.flutter.dev/platform-integration/platform-channels
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Writing custom platform-specific code](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

This guide describes how to use custom platform-specific code with Flutter.

## Overview

[#](#overview)

You can use platform-specific code in your Flutter app. A few common ways to do this include:

* Use Flutter's platform channel APIs to pass messages between Flutter and your desired platforms. For more information, see [Call platform-specific code using platform channels](#example).

* Use the `Pigeon` package to generate type-safe platform-specific code. For more information, see [Call platform-specific code using the Pigeon package](#pigeon).

Flutter supports the following platforms and platform-specific languages:

* **Android**: Kotlin, Java
* **iOS**: Swift, Objective-C
* **Windows**: C++
* **macOS**: Objective-C
* **Linux**: C

## Architectural overview of platform channels

[#](#architecture)

Messages are passed between the client (UI) and host (platform) using platform channels as illustrated in this diagram:

In the preceding diagram, messages and responses are passed asynchronously through channels to ensure the user interface remains responsive. On the client side, [`MethodChannel` for Flutter](https://api.flutter.dev/flutter/services/MethodChannel-class.html) enables sending messages that correspond to method calls. On the platform side, [`MethodChannel` for Android](https://api.flutter.dev/javadoc/io/flutter/plugin/common/MethodChannel.html) and [`FlutterMethodChannel` for iOS](https://api.flutter.dev/ios-embedder/interface_flutter_method_channel.html) enable receiving method calls and sending back a result. These classes allow you to develop a platform plugin with very little *boilerplate* code.

## Data types support

[#](#codec)

The standard platform channel APIs and the Pigeon package use a standard message codec called [`StandardMessageCodec`](https://api.flutter.dev/flutter/services/StandardMessageCodec-class.html) that supports efficient binary serialization of simple JSON-like values, such as booleans, numbers, Strings, byte buffers, Lists, and Maps. The serialization and deserialization of these values to and from messages happens automatically when you send and receive values.

The following table shows how Dart values are received on the platform side and vice versa:

* [Kotlin](#177-tab-panel)
* [Java](#178-tab-panel)
* [Swift](#179-tab-panel)
* [Obj-C](#180-tab-panel)
* [C++](#181-tab-panel)
* [C](#182-tab-panel)

| Dart              | Kotlin        |
| ----------------- | ------------- |
| `null`            | `null`        |
| `bool`            | `Boolean`     |
| `int` (<=32 bits) | `Int`         |
| `int` (>32 bits)  | `Long`        |
| `double`          | `Double`      |
| `String`          | `String`      |
| `Uint8List`       | `ByteArray`   |
| `Int32List`       | `IntArray`    |
| `Int64List`       | `LongArray`   |
| `Float32List`     | `FloatArray`  |
| `Float64List`     | `DoubleArray` |
| `List`            | `List`        |
| `Map`             | `HashMap`     |

| Dart              | Java                  |
| ----------------- | --------------------- |
| `null`            | `null`                |
| `bool`            | `java.lang.Boolean`   |
| `int` (<=32 bits) | `java.lang.Integer`   |
| `int` (>32 bits)  | `java.lang.Long`      |
| `double`          | `java.lang.Double`    |
| `String`          | `java.lang.String`    |
| `Uint8List`       | `byte[]`              |
| `Int32List`       | `int[]`               |
| `Int64List`       | `long[]`              |
| `Float32List`     | `float[]`             |
| `Float64List`     | `double[]`            |
| `List`            | `java.util.ArrayList` |
| `Map`             | `java.util.HashMap`   |

| Dart              | Swift                                     |
| ----------------- | ----------------------------------------- |
| `null`            | `nil` (`NSNull` when nested)              |
| `bool`            | `NSNumber(value: Bool)`                   |
| `int` (<=32 bits) | `NSNumber(value: Int32)`                  |
| `int` (>32 bits)  | `NSNumber(value: Int)`                    |
| `double`          | `NSNumber(value: Double)`                 |
| `String`          | `String`                                  |
| `Uint8List`       | `FlutterStandardTypedData(bytes: Data)`   |
| `Int32List`       | `FlutterStandardTypedData(int32: Data)`   |
| `Int64List`       | `FlutterStandardTypedData(int64: Data)`   |
| `Float32List`     | `FlutterStandardTypedData(float32: Data)` |
| `Float64List`     | `FlutterStandardTypedData(float64: Data)` |
| `List`            | `Array`                                   |
| `Map`             | `Dictionary`                              |

| Dart              | Objective-C                                      |
| ----------------- | ------------------------------------------------ |
| `null`            | `nil` (`NSNull` when nested)                     |
| `bool`            | `NSNumber numberWithBool:`                       |
| `int` (<=32 bits) | `NSNumber numberWithInt:`                        |
| `int` (>32 bits)  | `NSNumber numberWithLong:`                       |
| `double`          | `NSNumber numberWithDouble:`                     |
| `String`          | `NSString`                                       |
| `Uint8List`       | `FlutterStandardTypedData typedDataWithBytes:`   |
| `Int32List`       | `FlutterStandardTypedData typedDataWithInt32:`   |
| `Int64List`       | `FlutterStandardTypedData typedDataWithInt64:`   |
| `Float32List`     | `FlutterStandardTypedData typedDataWithFloat32:` |
| `Float64List`     | `FlutterStandardTypedData typedDataWithFloat64:` |
| `List`            | `NSArray`                                        |
| `Map`             | `NSDictionary`                                   |

| Dart              | C++                                                        |
| ----------------- | ---------------------------------------------------------- |
| `null`            | `EncodableValue()`                                         |
| `bool`            | `EncodableValue(bool)`                                     |
| `int` (<=32 bits) | `EncodableValue(int32_t)`                                  |
| `int` (>32 bits)  | `EncodableValue(int64_t)`                                  |
| `double`          | `EncodableValue(double)`                                   |
| `String`          | `EncodableValue(std::string)`                              |
| `Uint8List`       | `EncodableValue(std::vector<uint8_t>)`                     |
| `Int32List`       | `EncodableValue(std::vector<int32_t>)`                     |
| `Int64List`       | `EncodableValue(std::vector<int64_t>)`                     |
| `Float32List`     | `EncodableValue(std::vector<float>)`                       |
| `Float64List`     | `EncodableValue(std::vector<double>)`                      |
| `List`            | `EncodableValue(std::vector<EncodableValue>)`              |
| `Map`             | `EncodableValue(std::map<EncodableValue, EncodableValue>)` |

| Dart          | C (GObject)                 |
| ------------- | --------------------------- |
| `null`        | `FlValue()`                 |
| `bool`        | `FlValue(bool)`             |
| `int`         | `FlValue(int64_t)`          |
| `double`      | `FlValue(double)`           |
| `String`      | `FlValue(gchar*)`           |
| `Uint8List`   | `FlValue(uint8_t*)`         |
| `Int32List`   | `FlValue(int32_t*)`         |
| `Int64List`   | `FlValue(int64_t*)`         |
| `Float32List` | `FlValue(float*)`           |
| `Float64List` | `FlValue(double*)`          |
| `List`        | `FlValue(FlValue)`          |
| `Map`         | `FlValue(FlValue, FlValue)` |

## Call platform-specific code using platform channels

[#](#example)

The following code demonstrates how to call a platform-specific API to retrieve and display the current battery level. It uses the Android `BatteryManager` API, the iOS `device.batteryLevel` API, the Windows `GetSystemPowerStatus` API, and the Linux `UPower` API with a single platform message, `getBatteryLevel()`.

The example adds the platform-specific code inside the main app itself. If you want to reuse the platform-specific code for multiple apps, the project creation step is slightly different (see [developing packages](/packages-and-plugins/developing-packages#plugin)), but the platform channel code is still written in the same way.

### Step 1: Create a new app project

[#](#example-project)

Start by creating a new app:

* In a terminal run: `flutter create batterylevel`

By default, our template supports writing Android code using Kotlin, or iOS code using Swift. To use Java or Objective-C, use the `-i` and/or `-a` flags:

* In a terminal run: `flutter create -i objc -a java batterylevel`

### Step 2: Create the Flutter platform client

[#](#example-client)

The app's `State` class holds the current app state. Extend that to hold the current battery state.

First, construct the channel. Use a `MethodChannel` with a single platform method that returns the battery level.

The client and host sides of a channel are connected through a channel name passed in the channel constructor. All channel names used in a single app must be unique; prefix the channel name with a unique 'domain prefix', for example: `samples.flutter.dev/battery`.

dart

```
import 'dart:async';
​
import 'package:flutter/services.dart';
import 'package:material_ui/material_ui.dart';
```

dart

```
class _MyHomePageState extends State<MyHomePage> {
  static const platform = MethodChannel('samples.flutter.dev/battery');
  // Get battery level.
```

Next, invoke a method on the method channel, specifying the concrete method to call using the `String` identifier `getBatteryLevel`. The call might fail—for example, if the platform doesn't support the platform API (such as when running in a simulator), so wrap the `invokeMethod` call in a try-catch statement.

Use the returned result to update the user interface state in `_batteryLevel` inside `setState`.

dart

```
// Get battery level.
String _batteryLevel = 'Unknown battery level.';
​
Future<void> _getBatteryLevel() async {
  String batteryLevel;
  try {
    final result = await platform.invokeMethod<int>('getBatteryLevel');
    batteryLevel = 'Battery level at $result % .';
  } on PlatformException catch (e) {
    batteryLevel = "Failed to get battery level: '${e.message}'.";
  }
​
  setState(() {
    _batteryLevel = batteryLevel;
  });
}
```

Finally, replace the `build` method from the template to contain a small user interface that displays the battery state in a string, and a button for refreshing the value.

dart

```
@override
Widget build(BuildContext context) {
  return Material(
    child: Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.spaceEvenly,
        children: [
          ElevatedButton(
            onPressed: _getBatteryLevel,
            child: const Text('Get Battery Level'),
          ),
          Text(_batteryLevel),
        ],
      ),
    ),
  );
}
```

### Step 3: Add an Android platform-specific implementation

[#](#step-3-add-an-android-platform-specific-implementation)

* [Kotlin](#183-tab-panel)
* [Java](#184-tab-panel)

Start by opening the Android host portion of your Flutter app in Android Studio:

1. Start Android Studio

2. Select the menu item **File > Open...**

3. Navigate to the directory holding your Flutter app, and select the **android** folder inside it. Click **OK**.

4. Open the file `MainActivity.kt` located in the **kotlin** folder in the Project view.

Inside the `configureFlutterEngine()` method, create a `MethodChannel` and call `setMethodCallHandler()`. Make sure to use the same channel name as was used on the Flutter client side.

MainActivity.kt

kotlin

```
import androidx.annotation.NonNull
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel
​
class MainActivity: FlutterActivity() {
  private val CHANNEL = "samples.flutter.dev/battery"
​
  override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
    super.configureFlutterEngine(flutterEngine)
    MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler {
      call, result ->
      // This method is invoked on the main thread.
      // TODO
    }
  }
}
```

Add the Android Kotlin code that uses the Android battery APIs to retrieve the battery level. This code is exactly the same as you would write in a native Android app.

First, add the needed imports at the top of the file:

MainActivity.kt

kotlin

```
import android.content.Context
import android.content.ContextWrapper
import android.content.Intent
import android.content.IntentFilter
import android.os.BatteryManager
import android.os.Build.VERSION
import android.os.Build.VERSION_CODES
```

Next, add the following method in the `MainActivity` class, below the `configureFlutterEngine()` method:

MainActivity.kt

kotlin

```
  private fun getBatteryLevel(): Int {
    val batteryLevel: Int
    if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {
      val batteryManager = getSystemService(Context.BATTERY_SERVICE) as BatteryManager
      batteryLevel = batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY)
    } else {
      val intent = ContextWrapper(applicationContext).registerReceiver(null, IntentFilter(Intent.ACTION_BATTERY_CHANGED))
      batteryLevel = intent!!.getIntExtra(BatteryManager.EXTRA_LEVEL, -1) * 100 / intent.getIntExtra(BatteryManager.EXTRA_SCALE, -1)
    }
​
    return batteryLevel
  }
```

Finally, complete the `setMethodCallHandler()` method added earlier. You need to handle a single platform method, `getBatteryLevel()`, so test for that in the `call` argument. The implementation of this platform method calls the Android code written in the previous step, and returns a response for both the success and error cases using the `result` argument. If an unknown method is called, report that instead.

Remove the following code:

MainActivity.kt

kotlin

```
    MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler {
      call, result ->
      // This method is invoked on the main thread.
      // TODO
    }
```

And replace with the following:

MainActivity.kt

kotlin

```
    MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler {
      // This method is invoked on the main thread.
      call, result ->
      if (call.method == "getBatteryLevel") {
        val batteryLevel = getBatteryLevel()
​
        if (batteryLevel != -1) {
          result.success(batteryLevel)
        } else {
          result.error("UNAVAILABLE", "Battery level not available.", null)
        }
      } else {
        result.notImplemented()
      }
    }
```

Start by opening the Android host portion of your Flutter app in Android Studio:

1. Start Android Studio

2. Select the menu item **File > Open...**

3. Navigate to the directory holding your Flutter app, and select the **android** folder inside it. Click **OK**.

4. Open the `MainActivity.java` file located in the **java** folder in the Project view.

Next, create a `MethodChannel` and set a `MethodCallHandler` inside the `configureFlutterEngine()` method. Make sure to use the same channel name as was used on the Flutter client side.

MainActivity.java

java

```
import androidx.annotation.NonNull;
import io.flutter.embedding.android.FlutterActivity;
import io.flutter.embedding.engine.FlutterEngine;
import io.flutter.plugin.common.MethodChannel;
​
public class MainActivity extends FlutterActivity {
  private static final String CHANNEL = "samples.flutter.dev/battery";
​
  @Override
  public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) {
    super.configureFlutterEngine(flutterEngine);
    new MethodChannel(flutterEngine.getDartExecutor().getBinaryMessenger(), CHANNEL)
        .setMethodCallHandler(
          (call, result) -> {
            // This method is invoked on the main thread.
            // TODO
          }
        );
  }
}
```

Add the Android Java code that uses the Android battery APIs to retrieve the battery level. This code is exactly the same as you would write in a native Android app.

First, add the needed imports at the top of the file:

MainActivity.java

java

```
import android.content.ContextWrapper;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.BatteryManager;
import android.os.Build.VERSION;
import android.os.Build.VERSION_CODES;
import android.os.Bundle;
```

Then add the following as a new method in the activity class, below the `configureFlutterEngine()` method:

MainActivity.java

java

```
  private int getBatteryLevel() {
    int batteryLevel = -1;
    if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {
      BatteryManager batteryManager = (BatteryManager) getSystemService(BATTERY_SERVICE);
      batteryLevel = batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY);
    } else {
      Intent intent = new ContextWrapper(getApplicationContext()).
          registerReceiver(null, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
      batteryLevel = (intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1) * 100) /
          intent.getIntExtra(BatteryManager.EXTRA_SCALE, -1);
    }
​
    return batteryLevel;
  }
```

Finally, complete the `setMethodCallHandler()` method added earlier. You need to handle a single platform method, `getBatteryLevel()`, so test for that in the `call` argument. The implementation of this platform method calls the Android code written in the previous step, and returns a response for both the success and error cases using the `result` argument. If an unknown method is called, report that instead.

Remove the following code:

MainActivity.java

java

```
      new MethodChannel(flutterEngine.getDartExecutor().getBinaryMessenger(), CHANNEL)
        .setMethodCallHandler(
          (call, result) -> {
            // This method is invoked on the main thread.
            // TODO
          }
      );
```

And replace with the following:

MainActivity.java

java

```
      new MethodChannel(flutterEngine.getDartExecutor().getBinaryMessenger(), CHANNEL)
        .setMethodCallHandler(
          (call, result) -> {
            // This method is invoked on the main thread.
            if (call.method.equals("getBatteryLevel")) {
              int batteryLevel = getBatteryLevel();
​
              if (batteryLevel != -1) {
                result.success(batteryLevel);
              } else {
                result.error("UNAVAILABLE", "Battery level not available.", null);
              }
            } else {
              result.notImplemented();
            }
          }
      );
```

You should now be able to run the app on Android. If using the Android Emulator, set the battery level in the Extended Controls panel accessible from the **...** button in the toolbar.

### Step 4: Add an iOS platform-specific implementation

[#](#step-4-add-an-ios-platform-specific-implementation)

* [Swift](#185-tab-panel)
* [Objective-C](#186-tab-panel)

Start by opening the iOS host portion of your Flutter app in Xcode:

1. Start Xcode.

2. Select the menu item **File > Open...**.

3. Navigate to the directory holding your Flutter app, and select the **ios** folder inside it. Click **OK**.

Add support for Swift in the standard template setup that uses Objective-C:

1. **Expand Runner > Runner** in the Project navigator.

2. Open the file `AppDelegate.swift` located under **Runner > Runner** in the Project navigator.

Override the `application:didFinishLaunchingWithOptions:` function and create a `FlutterMethodChannel` tied to the channel name `samples.flutter.dev/battery`:

AppDelegate.swift

swift

```
@main
@objc class AppDelegate: FlutterAppDelegate, FlutterImplicitEngineDelegate {
​
  func didInitializeImplicitFlutterEngine(_ engineBridge: FlutterImplicitEngineBridge) {
    GeneratedPluginRegistrant.register(with: engineBridge.pluginRegistry)
​
    let batteryChannel = FlutterMethodChannel(
      name: "samples.flutter.dev/battery",
      binaryMessenger: engineBridge.applicationRegistrar.messenger()
    )
​
    batteryChannel.setMethodCallHandler({
      [weak self] (call: FlutterMethodCall, result: FlutterResult) -> Void in
      // This method is invoked on the UI thread.
      // Handle battery messages.
    })
  }
}
```

Next, add the iOS Swift code that uses the iOS battery APIs to retrieve the battery level. This code is exactly the same as you would write in a native iOS app.

Add the following as a new method at the bottom of `AppDelegate.swift`:

AppDelegate.swift

swift

```
private func receiveBatteryLevel(result: FlutterResult) {
  let device = UIDevice.current
  device.isBatteryMonitoringEnabled = true
  if device.batteryState == UIDevice.BatteryState.unknown {
    result(FlutterError(code: "UNAVAILABLE",
                        message: "Battery level not available.",
                        details: nil))
  } else {
    result(Int(device.batteryLevel * 100))
  }
}
```

Finally, complete the `setMethodCallHandler()` method added earlier. You need to handle a single platform method, `getBatteryLevel()`, so test for that in the `call` argument. The implementation of this platform method calls the iOS code written in the previous step. If an unknown method is called, report that instead.

AppDelegate.swift

swift

```
batteryChannel.setMethodCallHandler({
  [weak self] (call: FlutterMethodCall, result: FlutterResult) -> Void in
  // This method is invoked on the UI thread.
  guard call.method == "getBatteryLevel" else {
    result(FlutterMethodNotImplemented)
    return
  }
  self?.receiveBatteryLevel(result: result)
})
```

Start by opening the iOS host portion of the Flutter app in Xcode:

1. Start Xcode.

2. Select the menu item **File > Open...**.

3. Navigate to the directory holding your Flutter app, and select the **ios** folder inside it. Click **OK**.

4. Make sure the Xcode projects builds without errors.

5. Open the file `AppDelegate.m`, located under **Runner > Runner** in the Project navigator.

Create a `FlutterMethodChannel` and add a handler inside the `application didFinishLaunchingWithOptions:` method. Make sure to use the same channel name as was used on the Flutter client side.

AppDelegate.m

objc

```
#import <Flutter/Flutter.h>
#import "GeneratedPluginRegistrant.h"
​
@implementation AppDelegate
​
- (void)didInitializeImplicitFlutterEngine:(NSObject<FlutterImplicitEngineBridge>*)engineBridge {
  [GeneratedPluginRegistrant registerWithRegistry:engineBridge.pluginRegistry];
​
  FlutterMethodChannel* batteryChannel = [FlutterMethodChannel
                                        methodChannelWithName:@"samples.flutter.dev/battery"
                                        binaryMessenger:engineBridge.applicationRegistrar.messenger];
​
  [batteryChannel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) {
    // This method is invoked on the UI thread.
    // TODO
  }];
}
@end
```

Next, add the iOS ObjectiveC code that uses the iOS battery APIs to retrieve the battery level. This code is exactly the same as you would write in a native iOS app.

Add the following method in the `AppDelegate` class, just before `@end`:

AppDelegate.m

objc

```
- (int)getBatteryLevel {
  UIDevice* device = UIDevice.currentDevice;
  device.batteryMonitoringEnabled = YES;
  if (device.batteryState == UIDeviceBatteryStateUnknown) {
    return -1;
  } else {
    return (int)(device.batteryLevel * 100);
  }
}
```

Finally, complete the `setMethodCallHandler()` method added earlier. You need to handle a single platform method, `getBatteryLevel()`, so test for that in the `call` argument. The implementation of this platform method calls the iOS code written in the previous step, and returns a response for both the success and error cases using the `result` argument. If an unknown method is called, report that instead.

AppDelegate.m

objc

```
__weak typeof(self) weakSelf = self;
[batteryChannel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) {
  // This method is invoked on the UI thread.
  if ([@"getBatteryLevel" isEqualToString:call.method]) {
    int batteryLevel = [weakSelf getBatteryLevel];
​
    if (batteryLevel == -1) {
      result([FlutterError errorWithCode:@"UNAVAILABLE"
                                 message:@"Battery level not available."
                                 details:nil]);
    } else {
      result(@(batteryLevel));
    }
  } else {
    result(FlutterMethodNotImplemented);
  }
}];
```

You should now be able to run the app on iOS. If using the iOS Simulator, note that it doesn't support battery APIs, and the app displays 'Battery level not available'.

### Step 5: Add a Windows platform-specific implementation

[#](#step-5-add-a-windows-platform-specific-implementation)

Start by opening the Windows host portion of your Flutter app in Visual Studio:

1. Run `flutter build windows` in your project directory once to generate the Visual Studio solution file.

2. Start Visual Studio.

3. Select **Open a project or solution**.

4. Navigate to the directory holding your Flutter app, then into the **build** folder, then the **windows** folder, then select the `batterylevel.sln` file. Click **Open**.

Add the C++ implementation of the platform channel method:

1. Expand **batterylevel > Source Files** in the Solution Explorer.

2. Open the file `flutter_window.cpp`.

First, add the necessary includes to the top of the file, just after `#include "flutter_window.h"`:

flutter\_window\.cpp

cpp

```
#include <flutter/event_channel.h>
#include <flutter/event_sink.h>
#include <flutter/event_stream_handler_functions.h>
#include <flutter/method_channel.h>
#include <flutter/standard_method_codec.h>
#include <windows.h>
​
#include <memory>
```

Edit the `FlutterWindow::OnCreate` method and create a `flutter::MethodChannel` tied to the channel name `samples.flutter.dev/battery`:

flutter\_window\.cpp

cpp

```
bool FlutterWindow::OnCreate() {
  // ...
  RegisterPlugins(flutter_controller_->engine());
​
  flutter::MethodChannel<> channel(
      flutter_controller_->engine()->messenger(), "samples.flutter.dev/battery",
      &flutter::StandardMethodCodec::GetInstance());
  channel.SetMethodCallHandler(
      [](const flutter::MethodCall<>& call,
         std::unique_ptr<flutter::MethodResult<>> result) {
        // TODO
      });
​
  SetChildContent(flutter_controller_->view()->GetNativeWindow());
  return true;
}
```

Next, add the C++ code that uses the Windows battery APIs to retrieve the battery level. This code is exactly the same as you would write in a native Windows application.

Add the following as a new function at the top of `flutter_window.cpp` just after the `#include` section:

flutter\_window\.cpp

cpp

```
static int GetBatteryLevel() {
  SYSTEM_POWER_STATUS status;
  if (GetSystemPowerStatus(&status) == 0 || status.BatteryLifePercent == 255) {
    return -1;
  }
  return status.BatteryLifePercent;
}
```

Finally, complete the `setMethodCallHandler()` method added earlier. You need to handle a single platform method, `getBatteryLevel()`, so test for that in the `call` argument. The implementation of this platform method calls the Windows code written in the previous step. If an unknown method is called, report that instead.

Remove the following code:

flutter\_window\.cpp

cpp

```
  channel.SetMethodCallHandler(
      [](const flutter::MethodCall<>& call,
         std::unique_ptr<flutter::MethodResult<>> result) {
        // TODO
      });
```

And replace with the following:

flutter\_window\.cpp

cpp

```
  channel.SetMethodCallHandler(
      [](const flutter::MethodCall<>& call,
         std::unique_ptr<flutter::MethodResult<>> result) {
        if (call.method_name() == "getBatteryLevel") {
          int battery_level = GetBatteryLevel();
          if (battery_level != -1) {
            result->Success(battery_level);
          } else {
            result->Error("UNAVAILABLE", "Battery level not available.");
          }
        } else {
          result->NotImplemented();
        }
      });
```

You should now be able to run the application on Windows. If your device doesn't have a battery, it displays 'Battery level not available'.

### Step 6: Add a macOS platform-specific implementation

[#](#step-6-add-a-macos-platform-specific-implementation)

Start by opening the macOS host portion of your Flutter app in Xcode:

1. Start Xcode.

2. Select the menu item **File > Open...**.

3. Navigate to the directory holding your Flutter app, and select the **macos** folder inside it. Click **OK**.

Add the Swift implementation of the platform channel method:

1. **Expand Runner > Runner** in the Project navigator.

2. Open the file `MainFlutterWindow.swift` located under **Runner > Runner** in the Project navigator.

First, add the necessary import to the top of the file, just after `import FlutterMacOS`:

MainFlutterWindow\.swift

swift

```
import IOKit.ps
```

Create a `FlutterMethodChannel` tied to the channel name `samples.flutter.dev/battery` in the `awakeFromNib` method:

MainFlutterWindow\.swift

swift

```
  override func awakeFromNib() {
    // ...
    self.setFrame(windowFrame, display: true)
​
    let batteryChannel = FlutterMethodChannel(
      name: "samples.flutter.dev/battery",
      binaryMessenger: flutterViewController.engine.binaryMessenger)
    batteryChannel.setMethodCallHandler { (call, result) in
      // This method is invoked on the UI thread.
      // Handle battery messages.
    }
​
    RegisterGeneratedPlugins(registry: flutterViewController)
​
    super.awakeFromNib()
  }
}
```

Next, add the macOS Swift code that uses the IOKit battery APIs to retrieve the battery level. This code is exactly the same as you would write in a native macOS app.

Add the following as a new method at the bottom of `MainFlutterWindow.swift`:

MainFlutterWindow\.swift

swift

```
private func getBatteryLevel() -> Int? {
  let info = IOPSCopyPowerSourcesInfo().takeRetainedValue()
  let sources: Array<CFTypeRef> = IOPSCopyPowerSourcesList(info).takeRetainedValue() as Array
  if let source = sources.first {
    let description =
      IOPSGetPowerSourceDescription(info, source).takeUnretainedValue() as! [String: AnyObject]
    if let level = description[kIOPSCurrentCapacityKey] as? Int {
      return level
    }
  }
  return nil
}
```

Finally, complete the `setMethodCallHandler` method added earlier. You need to handle a single platform method, `getBatteryLevel()`, so test for that in the `call` argument. The implementation of this platform method calls the macOS code written in the previous step. If an unknown method is called, report that instead.

MainFlutterWindow\.swift

swift

```
batteryChannel.setMethodCallHandler { (call, result) in
  switch call.method {
  case "getBatteryLevel":
    guard let level = getBatteryLevel() else {
      result(
        FlutterError(
          code: "UNAVAILABLE",
          message: "Battery level not available",
          details: nil))
     return
    }
    result(level)
  default:
    result(FlutterMethodNotImplemented)
  }
}
```

You should now be able to run the application on macOS. If your device doesn't have a battery, it displays 'Battery level not available'.

### Step 7: Add a Linux platform-specific implementation

[#](#step-7-add-a-linux-platform-specific-implementation)

For this example you need to install the `upower` developer headers. This is likely available from your distribution, for example with:

```
sudo apt install libupower-glib-dev
```

Start by opening the Linux host portion of your Flutter app in the editor of your choice. The instructions below are for Visual Studio Code with the "C/C++" and "CMake" extensions installed, but can be adjusted for other IDEs.

1. Launch Visual Studio Code.

2. Open the **linux** directory inside your project.

3. Choose **Yes** in the prompt asking: `Would you like to configure project "linux"?`. This enables C++ autocomplete.

4. Open the file `runner/my_application.cc`.

First, add the necessary includes to the top of the file, just after `#include <flutter_linux/flutter_linux.h>`:

runner/my\_application.cc

c

```
#include <math.h>
#include <upower.h>
```

Add an `FlMethodChannel` to the `_MyApplication` struct:

runnner/my\_application.cc

c

```
struct _MyApplication {
  GtkApplication parent_instance;
  char** dart_entrypoint_arguments;
  FlMethodChannel* battery_channel;
};
```

Make sure to clean it up in `my_application_dispose`:

runner/my\_application.cc

c

```
static void my_application_dispose(GObject* object) {
  MyApplication* self = MY_APPLICATION(object);
  g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev);
  g_clear_object(&self->battery_channel);
  G_OBJECT_CLASS(my_application_parent_class)->dispose(object);
}
```

Edit the `my_application_activate` method and initialize `battery_channel` using the channel name `samples.flutter.dev/battery`, just after the call to `fl_register_plugins`:

runner/my\_application.cc

c

```
static void my_application_activate(GApplication* application) {
  // ...
  fl_register_plugins(FL_PLUGIN_REGISTRY(self->view));
​
  g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new();
  self->battery_channel = fl_method_channel_new(
      fl_engine_get_binary_messenger(fl_view_get_engine(view)),
      "samples.flutter.dev/battery", FL_METHOD_CODEC(codec));
  fl_method_channel_set_method_call_handler(
      self->battery_channel, battery_method_call_handler, self, nullptr);
​
  gtk_widget_grab_focus(GTK_WIDGET(self->view));
}
```

Next, add the C code that uses the Linux battery APIs to retrieve the battery level. This code is exactly the same as you would write in a native Linux application.

Add the following as a new function at the top of `my_application.cc` just after the `G_DEFINE_TYPE` line:

runner/my\_application.cc

c

```
static FlMethodResponse* get_battery_level() {
  // Find the first available battery and report that.
  g_autoptr(UpClient) up_client = up_client_new();
  g_autoptr(GPtrArray) devices = up_client_get_devices2(up_client);
  if (devices->len == 0) {
    return FL_METHOD_RESPONSE(fl_method_error_response_new(
        "UNAVAILABLE", "Device does not have a battery.", nullptr));
  }
​
  UpDevice* device = UP_DEVICE(g_ptr_array_index(devices, 0));
  double percentage = 0;
  g_object_get(device, "percentage", &percentage, nullptr);
​
  g_autoptr(FlValue) result =
      fl_value_new_int(static_cast<int64_t>(round(percentage)));
  return FL_METHOD_RESPONSE(fl_method_success_response_new(result));
}
```

Finally, add the `battery_method_call_handler` function referenced in the earlier call to `fl_method_channel_set_method_call_handler`. You need to handle a single platform method, `getBatteryLevel`, so test for that in the `method_call` argument. The implementation of this function calls the Linux code written in the previous step. If an unknown method is called, report that instead.

Add the following code after the `get_battery_level` function:

runner/my\_application.cpp

cpp

```
static void battery_method_call_handler(FlMethodChannel* channel,
                                        FlMethodCall* method_call,
                                        gpointer user_data) {
  g_autoptr(FlMethodResponse) response = nullptr;
  if (strcmp(fl_method_call_get_name(method_call), "getBatteryLevel") == 0) {
    response = get_battery_level();
  } else {
    response = FL_METHOD_RESPONSE(fl_method_not_implemented_response_new());
  }
​
  g_autoptr(GError) error = nullptr;
  if (!fl_method_call_respond(method_call, response, &error)) {
    g_warning("Failed to send response: %s", error->message);
  }
}
```

You should now be able to run the application on Linux. If your device doesn't have a battery, it displays 'Battery level not available'.

## Call platform-specific code using the Pigeon package

[#](#pigeon)

You can use the [`Pigeon`](https://pub.dev/packages/pigeon) package as an alternative to Flutter's platform channel APIs to generate code that sends messages in a structured, type-safe manner. The workflow for Pigeon looks like this:

* The Flutter app sends structured type-safe messages to its *host*, the non-Dart portion of the app, over a platform channel.

* The *host* listens on the platform channel, and receives the message. It then calls into any number of platform-specific APIs using the native programming language and sends a response back to the *client*, the Flutter portion of the app.

Using this package eliminates the need to match strings between host and client for the names and data types of messages. It supports nested classes, grouping messages into APIs, generation of asynchronous wrapper code, and sending messages in either direction. The generated code is readable and guarantees there are no conflicts between multiple clients of different versions.

With Pigeon, the messaging protocol is defined in a subset of Dart that then generates messaging code for Android, iOS, macOS, or Windows. For example:

pigeon\_source.dart

dart

```
import 'package:pigeon/pigeon.dart';
​
class SearchRequest {
  final String query;
​
  SearchRequest({required this.query});
}
​
class SearchReply {
  final String result;
​
  SearchReply({required this.result});
}
​
@HostApi()
abstract class Api {
  @async
  SearchReply search(SearchRequest request);
}
```

use\_pigeon.dart

dart

```
import 'generated_pigeon.dart';
​
Future<void> onClick() async {
  SearchRequest request = SearchRequest(query: 'test');
  Api api = SomeApi();
  SearchReply reply = await api.search(request);
  print('reply: ${reply.result}');
}
```

You can find a complete example and more information on the [`pigeon`](https://pub.dev/packages/pigeon) page on pub.dev.

## Channels and platform threading

[#](#channels-and-platform-threading)

When invoking channels on the platform side destined for Flutter, invoke them on the platform's main thread. When invoking channels in Flutter destined for the platform side, either invoke them from any `Isolate` that is the root `Isolate`, *or* that is registered as a background `Isolate`. The handlers for the platform side can execute on the platform's main thread or they can execute on a background thread if using a Task Queue. You can invoke the platform side handlers asynchronously and on any thread.

### Use plugins and channels from a background isolate

[#](#using-plugins-and-channels-from-background-isolates)

Plugins and channels can be used by any `Isolate`, but that `Isolate` has to be a root `Isolate` (the one created by Flutter) or registered as a background `Isolate` for a root `Isolate`.

The following example shows how to register a background `Isolate` in order to use a plugin from a background `Isolate`.

dart

```
import 'package:flutter/services.dart';
import 'package:shared_preferences/shared_preferences.dart';
​
void _isolateMain(RootIsolateToken rootIsolateToken) async {
  BackgroundIsolateBinaryMessenger.ensureInitialized(rootIsolateToken);
  SharedPreferences sharedPreferences = await SharedPreferences.getInstance();
  print(sharedPreferences.getBool('isDebug'));
}
​
void main() {
  RootIsolateToken rootIsolateToken = RootIsolateToken.instance!;
  Isolate.spawn(_isolateMain, rootIsolateToken);
}
```

### Execute channel handlers on a background thread (Android)

[#](#executing-channel-handlers-on-background-threads)

In order for a channel's platform side handler to execute on a background thread on an Android app, you must use the Task Queue API.

* [Kotlin](#187-tab-panel)
* [Java](#188-tab-panel)

kotlin

```
override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
  val taskQueue =
      flutterPluginBinding.binaryMessenger.makeBackgroundTaskQueue()
  channel = MethodChannel(flutterPluginBinding.binaryMessenger,
                          "com.example.foo",
                          StandardMethodCodec.INSTANCE,
                          taskQueue)
  channel.setMethodCallHandler(this)
}
```

java

```
@Override
public void onAttachedToEngine(@NonNull FlutterPluginBinding binding) {
  BinaryMessenger messenger = binding.getBinaryMessenger();
  BinaryMessenger.TaskQueue taskQueue =
      messenger.makeBackgroundTaskQueue();
  channel =
      new MethodChannel(
          messenger,
          "com.example.foo",
          StandardMethodCodec.INSTANCE,
          taskQueue);
  channel.setMethodCallHandler(this);
}
```

### Execute channel handlers on a background thread (iOS)

[#](#execute-channel-handlers-on-a-background-thread-ios)

In order for a channel's platform side handler to execute on a background thread on an iOS app, you must use the Task Queue API.

* [Swift](#189-tab-panel)
* [Objective-C](#190-tab-panel)

swift

```
public static func register(with registrar: FlutterPluginRegistrar) {
  let taskQueue = registrar.messenger().makeBackgroundTaskQueue?()
  let channel = FlutterMethodChannel(name: "com.example.foo",
                                     binaryMessenger: registrar.messenger(),
                                     codec: FlutterStandardMethodCodec.sharedInstance(),
                                     taskQueue: taskQueue)
  let instance = MyPlugin()
  registrar.addMethodCallDelegate(instance, channel: channel)
}
```

objc

```
+ (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar>*)registrar {
  NSObject<FlutterTaskQueue>* taskQueue =
      [[registrar messenger] makeBackgroundTaskQueue];
  FlutterMethodChannel* channel =
      [FlutterMethodChannel methodChannelWithName:@"com.example.foo"
                                  binaryMessenger:[registrar messenger]
                                            codec:[FlutterStandardMethodCodec sharedInstance]
                                        taskQueue:taskQueue];
  MyPlugin* instance = [[MyPlugin alloc] init];
  [registrar addMethodCallDelegate:instance channel:channel];
}
```

### Jump to the UI thread (Android)

[#](#jumping-to-the-ui-thread-in-android)

To comply with channels' UI thread requirement, you might need to jump from a background thread to Android's UI thread to execute a channel method. In Android, you can accomplish this by `post()`ing a `Runnable` to Android's UI thread `Looper`, which causes the `Runnable` to execute on the main thread at the next opportunity.

* [Kotlin](#191-tab-panel)
* [Java](#192-tab-panel)

kotlin

```
Handler(Looper.getMainLooper()).post {
  // Call the desired channel message here.
}
```

java

```
new Handler(Looper.getMainLooper()).post(new Runnable() {
  @Override
  public void run() {
    // Call the desired channel message here.
  }
});
```

### Jump to the main thread (iOS)

[#](#jumping-to-the-main-thread-in-ios)

To comply with channel's main thread requirement, you might need to jump from a background thread to iOS's main thread to execute a channel method. You can accomplish this in iOS by executing a [block](https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/WorkingwithBlocks/WorkingwithBlocks.html) on the main [dispatch queue](https://developer.apple.com/documentation/dispatch/dispatchqueue):

* [Objective-C](#193-tab-panel)
* [Swift](#194-tab-panel)

objc

```
dispatch_async(dispatch_get_main_queue(), ^{
  // Call the desired channel message here.
});
```

swift

```
DispatchQueue.main.async {
  // Call the desired channel message here.
}
```

## Supplementals

[#](#supplementals)

### Common channels and codecs

[#](#codec2)

The following is a list of some common platform channel APIs that you can use to write platform-specific code:

* [`MethodChannel`](https://api.flutter.dev/flutter/services/MethodChannel-class.html) for Flutter: A named channel that you can use to communicate with platform plugins using asynchronous method calls. By default this channel uses the [`StandardMessageCodec`](https://api.flutter.dev/flutter/services/StandardMessageCodec-class.html) codec. This channel is not type safe, which means calling and receiving messages depends on the host and client declaring the same arguments and data types in order for messages to work.

* [`BasicMessageChannel`](https://api.flutter.dev/flutter/services/BasicMessageChannel-class.html) for Flutter: A named channel that supports basic, asynchronous message passing, using a supported message codec. Not type safe.

* [Engine Embedder APIs](https://api.dart.dev/index.html#more-documentation) for Platforms: These platform-specific APIs contain platform-specific channel APIs.

You can create your own codec or use an existing one. The following is a list of some existing codecs that you can use with platform-specific code:

* [`StandardMessageCodec`](https://api.flutter.dev/flutter/services/StandardMessageCodec-class.html): A commonly used message codec that encodes and decodes a wide range of data types into a platform-agnostic binary format for transmission across platform channels. The serialization and deserialization of values to and from messages happens automatically when you send and receive values. For a list of supported data types, see [Platform channel data types support](#codec).

* [`BinaryCodec`](https://api.flutter.dev/flutter/services/BinaryCodec-class.html): A message codec that passes raw binary data between the Dart side of your Flutter app and the native platform side. It does not perform any higher-level encoding or decoding of data structures.

* [`StringCodec`](https://api.flutter.dev/flutter/services/StringCodec-class.html): A message codec that encodes and decodes strings, using UTF-8 encoding.

* [`JSONMessageCodec`](https://api.flutter.dev/flutter/services/JSONMessageCodec-class.html): A message codec that encodes and decodes JSON-formatted data, using UTF-8 encoding.

* [`FirestoreMessageCodec`](https://github.com/firebase/flutterfire/blob/master/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/method_channel/utils/firestore_message_codec.dart): A message codec that handles the exchange of messages sent across the platform channel between your Flutter app and the native Firebase Firestore SDKs (on Android and iOS).

### Separate platform-specific code from UI code

[#](#separate)

If you expect to use your platform-specific code in multiple Flutter apps, you might consider separating the code into a platform plugin located in a directory outside your main application. See [developing packages](/packages-and-plugins/developing-packages) for details.

### Publish platform-specific code as a package

[#](#publish)

To share your platform-specific code with other developers in the Flutter ecosystem, see [publishing packages](/packages-and-plugins/developing-packages#publish).

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-09-29. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/platform-integration/platform-channels.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/platform-integration/platform-channels\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/platform-integration/platform-channels.md "Report an issue with this page").

----
url: https://docs.flutter.dev/index.md
----

\# Flutter documentation > Get started with Flutter. Widgets, examples, updates, and API docs to help you write your first Flutter app.

Set up your environment and start building. Dip into the rich set of Flutter widgets available in the SDK. Bookmark the API reference docs for the Flutter framework. Browse tutorials, sample code, workshops, and recipes. View the many videos on the Flutter YouTube channel. Learn how to build with and integrate powerful AI tools.

\*\*To see changes to the site since our last release, see \[What's new]\[].\*\* \[What's new]: /release/whats-new ## New to Flutter? Ready to build beautiful, multiplatform apps from a single codebase? First, \[set up your Flutter environment]\[], then follow the \[Flutter learning pathway]\[]. \[set up your Flutter environment]: /install/quick \[Flutter learning pathway]: /learn/pathway ### Docs Coming from another platform? Check out Flutter for: \[Android]\[], \[SwiftUI]\[], \[UIKit]\[], \[React Native]\[], and \[Xamarin.Forms]\[] developers. \[Building layouts]\[] : Learn how to create layouts in Flutter, where everything is a \[widget]\(/resources/glossary#widget). \[Understanding constraints]\[] : Once you understand that "Constraints flow down. Sizes flow up. Parents set positions", then you are well on your way to understanding Flutter's layout model. \[Adding interactivity to your Flutter app]\[interactivity] : Learn how to add a stateful widget to your app. \[FAQ]\[] : Get the answers to frequently asked questions. \[Android]: /flutter-for/android-devs \[Building layouts]: /ui/layout \[FAQ]: /resources/faq \[interactivity]: /ui/interactivity \[SwiftUI]: /flutter-for/swiftui-devs \[UIKit]: /flutter-for/uikit-devs \[React Native]: /flutter-for/react-native-devs \[Understanding constraints]: /ui/layout/constraints \[Xamarin.Forms]: /flutter-for/xamarin-forms-devs ### Videos

Check out the latest in Dart and Flutter

For more of the latest on Dart and Flutter, check out the technical sessions from \[Google I/O]\[io-wrap-up], like these!Also, we launched a new 4-video series at Google I/O called the Full-stack developer guide:You can find the 4 videos in this series, as well as a wrap-up video, on the \[Google I/O wrap-up]\[io-wrap-up] blog post. \[io-wrap-up]: https\://blog.flutter.dev/thats-a-wrap-everything-flutter-at-google-i-o-2026-f316e57186e3 To learn about all of the Flutter video series, visit our \[videos]\[] page. We release new videos almost every week! [Check out the Flutter YouTube channel](https://www.youtube.com/@flutterdev) \[videos]: /resources/videos

----
url: https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.35.0.md
----

\# DevTools 2.35.0 release notes > Release notes for Dart and Flutter DevTools version 2.35.0. The 2.35.0 release of the Dart and Flutter DevTools includes the following changes among other general improvements. To learn more about DevTools, check out the \[DevTools overview]\(/tools/devtools). ## General updates \* Changed to a single button for starting and stopping recording on the Network screen and CPU profiler screen. - \[#7573]\(https\://github.com/flutter/devtools/pull/7573) !\[A screen shot of the CPU profiler tab, with the new recording button.]\(/assets/images/docs/tools/devtools/release-notes/images-2.35.0/profiler\_recording.png) !\[A screen shot of the network tab, with the new recording button.]\(/assets/images/docs/tools/devtools/release-notes/images-2.35.0/network\_recording.png) ## Inspector updates \* Add a preference for the default inspector view - \[#6949]\(https\://github.com/flutter/devtools/pull/6949) ## Memory updates \* Replaced total size with reachable size in snapshot list. - \[#7493]\(https\://github.com/flutter/devtools/pull/7493) ## Debugger updates \* During a hot-restart, \`pause\_isolates\_on\_start\` and only \`resume\` the app once breakpoints are set. - \[#7234]\(https\://github.com/flutter/devtools/pull/7234) ## Network profiler updates \* Added text selection in text viewer for requests and responses. - \[#7596]\(https\://github.com/flutter/devtools/pull/7596) \* Added a JSON copy experience to the JSON viewer. - \[#7596]\(https\://github.com/flutter/devtools/pull/7596) !\[The new JSON copy experience in the JSON viewer]\(/assets/images/docs/tools/devtools/release-notes/images-2.35.0/json\_viewer\_copy.png) \* Fixed a bug where stopping and starting network recording listed requests that happened while not recording. - \[#7626]\(https\://github.com/flutter/devtools/pull/7626) ## Deep links tool updates \* Improve layout for narrow screens. - \[#7524]\(https\://github.com/flutter/devtools/pull/7524) \* Add error handling for missing schemes and domains - \[#7559]\(https\://github.com/flutter/devtools/pull/7559) ## VS Code Sidebar updates \* Added a DevTools section with a list of tools and extensions that are available without a debug session. - \[#7598]\(https\://github.com/flutter/devtools/pull/7598), \[#7604]\(https\://github.com/flutter/devtools/pull/7604) ## DevTools Extension updates \* Support DevTools extensions that do not require a running app, and detect them from the user's IDE workspace. - \[#7612]\(https\://github.com/flutter/devtools/pull/7612) \* Deprecate the \`DevToolsExtension.requiresRunningApplication\` field in favor of the new optional \`requiresConnection\` field that can be added to an extension's \`config.yaml\` file. - \[#7611]\(https\://github.com/flutter/devtools/pull/7611), \[#7602]\(https\://github.com/flutter/devtools/pull/7602) \* Detect extensions for all types of run targets in a package. - \[#7533]\(https\://github.com/flutter/devtools/pull/7533), \[#7535]\(https\://github.com/flutter/devtools/pull/7535) ## Full commit history To find a complete list of changes in this release, check out the \[DevTools git log]\(https\://github.com/flutter/devtools/tree/v2.35.0).

----
url: https://docs.flutter.dev/reference/security-false-positives
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Security false positives](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

## Introduction

[#](#introduction)

We occasionally receive false reports of security vulnerabilities in Dart and Flutter applications, generated by tools that were built for other kinds of applications (for example, those written with Java or C++). This document provides information on reports that we believe are incorrect and explains why the concerns are misplaced.

## Common concerns

[#](#common-concerns)

### Shared objects should use fortified functions

[#](#shared-objects-should-use-fortified-functions)

> The shared object does not have any fortified functions. Fortified functions provides buffer overflow checks against glibc's commons insecure functions like `strcpy`, `gets` etc. Use the compiler option `-D_FORTIFY_SOURCE=2` to fortify functions.

When this refers to compiled Dart code (such as the `libapp.so` file in Flutter applications), this advice is misguided because Dart code doesn't directly invoke libc functions; all Dart code goes through the Dart standard library.

(In general, MobSF gets false positives here because it checks for any use of functions with a `_chk` suffix, but since Dart doesn't use these functions at all, it doesn't have calls with or without the suffix, and therefore MobSF treats the code as containing non-fortified calls.)

### Shared objects should use RELRO

[#](#shared-objects-should-use-relro)

> no RELRO found for `libapp.so` binaries

Dart doesn't use the normal Procedure Linkage Table (PLT) or Global Offsets Table (GOT) mechanisms at all, so the Relocation Read-Only (RELRO) technique doesn't really make much sense for Dart.

Dart's equivalent of GOT is the pool pointer, which unlike GOT, is located in a randomized location and is therefore much harder to exploit.

In principle, you can create vulnerable code when using Dart FFI, but normal use of Dart FFI wouldn't be prone to these issues either, assuming it's used with C code that itself uses RELRO appropriately.

### Shared objects should use stack canary values

[#](#shared-objects-should-use-stack-canary-values)

> no canary are found for `libapp.so` binaries

> This shared object does not have a stack canary value added to the stack. Stack canaries are used to detect and prevent exploits from overwriting return address. Use the option -fstack-protector-all to enable stack canaries.

Dart doesn't generate stack canaries because, unlike C++, Dart doesn't have stack-allocated arrays (the primary source of stack smashing in C/C++).

When writing pure Dart (without using `dart:ffi`), you already have much stronger isolation guarantees than any C++ mitigation can provide, simply because pure Dart code is a managed language where things like buffer overruns don't exist.

In principle, you can create vulnerable code when using Dart FFI, but normal use of Dart FFI would not be prone to these issues either, assuming it's used with C code that itself uses stack canary values appropriately.

### Code should avoid using the `_sscanf`, `_strlen`, and `_fopen` APIs

[#](#code-should-avoid-using-the-sscanf-strlen-and-fopen-apis)

> The binary may contain the following insecure API(s) `_sscanf` , `_strlen` , `_fopen`.

The tools that report these issues tend to be overly-simplistic in their scans; for example, finding custom functions with these names and assuming they refer to the standard library functions. Many of Flutter's third-party dependencies have functions with similar names that trip these checks. It's possible that some occurrences are valid concerns, but it's impossible to tell from the output of these tools due to the sheer number of false positives.

### Code should use `calloc` (instead of `_malloc`) for memory allocations

[#](#code-should-use-calloc-instead-of-malloc-for-memory-allocations)

> The binary may use `_malloc` function instead of `calloc`.

Memory allocation is a nuanced topic, where trade-offs have to be made between performance and resilience to vulnerabilities. Merely using `malloc` is not automatically indicative of a security vulnerability. While we welcome concrete reports (see below) for cases where using `calloc` would be preferable, in practice it would be inappropriate to uniformly replace all `malloc` calls with `calloc`.

### The iOS binary has a Runpath Search Path (`@rpath`) set

[#](#the-ios-binary-has-a-runpath-search-path-rpath-set)

> The binary has Runpath Search Path (`@rpath`) set. In certain cases an attacker can abuse this feature to run arbitrary executable for code execution and privilege escalation. Remove the compiler option `-rpath` to remove `@rpath`.

When the app is being built, Runpath Search Path refers to the paths the linker searches to find dynamic libraries (dylibs) used by the app. By default, iOS apps have this set to `@executable_path/Frameworks`, which means the linker should search for dylibs in the `Frameworks` directory relative to the app binary inside the app bundle. The `Flutter.framework` engine, like most embedded frameworks or dylibs, is correctly copied into this directory. When the app runs, it loads the library binary.

Flutter apps use the default iOS build setting (`LD_RUNPATH_SEARCH_PATHS=@executable_path/Frameworks`).

Vulnerabilities involving `@rpath` don't apply in mobile settings, as attackers don't have access to the file system and can't arbitrarily swap out these frameworks. Even if an attacker somehow *could* swap out the framework with a malicious one, the app would crash on launch due to codesigning violations.

### CBC with PKCS5/PKCS7 padding vulnerability

[#](#cbc-with-pkcs5pkcs7-padding-vulnerability)

We have received vague reports that there is a "CBC with PKCS5/PKCS7 padding vulnerability" in some Flutter packages.

As far as we can tell, this is triggered by the HLS implementation in ExoPlayer (the `com.google.android.exoplayer2.source.hls.Aes128DataSource` class). HLS is Apple's streaming format, which defines the type of encryption that must be used for DRM; this isn't a vulnerability, as DRM doesn't protect the user's machine or data but instead merely provides obfuscation to limit the user's ability to fully use their software and hardware.

### Apps can read and write to external storage

[#](#apps-can-read-and-write-to-external-storage)

> App can read/write to External Storage. Any App can read data written to External Storage.

> As with data from any untrusted source, you should perform input validation when handling data from external storage. We strongly recommend that you not store executables or class files on external storage prior to dynamic loading. If your app does retrieve executable files from external storage, the files should be signed and cryptographically verified prior to dynamic loading.

We have received reports that some vulnerability scanning tools interpret the ability for image picker plugins to read and write to external storage as a threat.

Reading images from local storage is the purpose of these plugins; this is not a vulnerability.

### Apps delete data using file.delete()

[#](#apps-delete-data-using-file-delete)

> When you delete a file using file. delete, only the reference to the file is removed from the file system table. The file still exists on disk until other data overwrites it, leaving it vulnerable to recovery.

Some vulnerability scanning tools interpret the deletion of temporary files after a camera plugin records data from the device's camera as a security vulnerability. Because the video is recorded by the user and is stored on the user's hardware, there is no actual risk.

## Obsolete concerns

[#](#obsolete-concerns)

This section contains valid messages that might be seen with older versions of Dart and Flutter, but should no longer be seen with more recent versions. If you see these messages with old versions of Dart or Flutter, upgrade to the latest stable version. If you see these with the current stable version, please report them (see the section at the end of this document).

### The stack should have its NX bit set

[#](#the-stack-should-have-its-nx-bit-set)

> The shared object does not have NX bit set. NX bit offer protection against exploitation of memory corruption vulnerabilities by marking memory page as non-executable. Use option `--noexecstack` or `-z noexecstack` to mark stack as non executable.

(The message from MobSF is misleading; it's looking for whether the stack is marked as non-executable, not the shared object.)

In older versions of Dart and Flutter there was a bug where the ELF generator didn't emit the `gnustack` segment with the `~X` permission, but this is now fixed.

## Reporting real concerns

[#](#reporting-real-concerns)

While automated vulnerability scanning tools report false positives such as the examples above, we can't rule out that there are real issues that deserve closer attention. Should you find an issue that you believe is a legitimate security vulnerability, we would greatly appreciate if you would report it:

* [Flutter security policy](/security)
* [Dart security policy](https://dart.dev/security)

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/reference/security-false-positives.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/reference/security-false-positives\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/reference/security-false-positives.md "Report an issue with this page").

----
url: https://docs.flutter.dev/app-architecture/guide.md
----

\# Guide to app architecture > The recommended way to architect a Flutter app. The following pages demonstrate how to build an app using best practices. The recommendations in this guide can be applied to most apps, making them easier to scale, test, and maintain. However, they're guidelines, not steadfast rules, and you should adapt them to your unique requirements. This section provides a high-level overview of how Flutter applications can be architected. It explains the layers of an application, along with the classes that exist within each layer. The section after this provides concrete code samples and walks through a Flutter application that's implemented these recommendations. ## Overview of project structure \[Separation-of-concerns]\[] is the most important principle to follow when designing your Flutter app. Your Flutter application should split into two broad layers, the UI layer and the Data layer. Each layer is further split into different components, each of which has distinct responsibilities, a well-defined interface, boundaries and dependencies. This guide recommends you split your application into the following components: \* Views \* View models \* Repositories \* Services ### MVVM If you've encountered the \[Model-View-ViewModel architectural pattern]\[] (MVVM), this will be familiar. MVVM is an architectural pattern that separates a feature of an application into three parts: the \`Model\`, the \`ViewModel\` and the \`View\`. Views and view models make up the UI layer of an application. Repositories and services represent the data of an application, or the model layer of MVVM. Each of these components is defined in the next section. Every feature in an application will contain one view to describe the UI and one view model to handle logic, one or more repositories as the sources of truth for your application data, and zero or more services that interact with external APIs, like client servers and platform plugins. A single feature of an application might require all of the following objects: Each of these objects and the arrows that connect them will be explained thoroughly by the end of this page. Throughout this guide, the following simplified version of that diagram will be used as an anchor. :::note Apps with complex logic might also have a logic layer that sits in between the UI layer and data layer. This logic layer is commonly called the \*domain layer\*. The domain layer contains additional components often called \*interactors\* or \*use-cases\*. The domain layer is covered later in this guide. ::: \[Model-View-ViewModel architectural pattern]: https\://en.wikipedia.org/wiki/Model–view–viewmodel ## UI layer An application's UI layer is responsible for interacting with the user. It displays an application's data to the user and receives user input, such as tap events and form inputs. The UI reacts to data changes or user input. When the UI receives new data from a Repository, it should re-render to display that new data. When the user interacts with the UI, it should change to reflect that interaction. The UI layer is made up of two architectural components, based on the MVVM design pattern: \* \*\*Views\*\* describe how to present application data to the user. Specifically, they refer to \*compositions of widgets\* that make a feature. For instance, a view is often (but not always) a screen that has a \`Scaffold\` widget, along with all of the widgets below it in the widget tree. Views are also responsible for passing events to the view model in response to user interactions. \* \*\*View models\*\* contain the logic that converts app data into \*UI State\*, because data from repositories is often formatted differently from the data that needs to be displayed. For example, you might need to combine data from multiple repositories, or you might want to filter a list of data records. Views and view models should have a one-to-one relationship. In the simplest terms, a view model manages the UI state and the view displays that state. Using views and view models, your UI layer can maintain state during configuration changes (such as screen rotations), and you can test the logic of your UI independently of Flutter widgets. :::note 'View' is an abstract term, and one view doesn't equal one widget. Widgets are composable, and several can be combined to create one view. Therefore, view models don't have a one-to-one relationship with widgets, but rather a one-to-one relationship with a \*collection\* of widgets. ::: A feature of an application is user centric, and therefore defined by the UI layer. Every instance of a paired \*view\* and \*view model\* defines one feature in your app. This is often a screen in your app, but it doesn't have to be. For example, consider logging in and out. Logging in is generally done on a specific screen whose only purpose is to provide the user with a way to log in. In the application code, the login screen would be made up of a \`LoginViewModel\` class and a \`LoginView\` class. On the other hand, logging out of an app is generally not done on a dedicated screen. The ability to log out is generally presented to the user as a button in a menu, a user account screen, or any number of different locations. It's often presented in multiple locations. In such scenarios, you might have a \`LogoutViewModel\` and a \`LogoutView\` which only contains a single button that can be dropped into other widgets. ### Views In Flutter, views are the widget classes of your application. Views are the primary method of rendering UI, and shouldn't contain any business logic. They should be passed all data they need to render from the view model. The only logic a view should contain is: \* Simple if-statements to show and hide widgets based on a flag or nullable field in the view model \* Animation logic \* Layout logic based on device information, like screen size or orientation. \* Simple routing logic All logic related to data should be handled in the view model. ### View models A view model exposes the application data necessary to render a view. In the architecture design described on this page, most of the logic in your Flutter application lives in view models. A view model's main responsibilities include: \* Retrieving application data from repositories and transforming it into a format suitable for presentation in the view. For example, it might filter, sort, or aggregate data. \* Maintaining the current state needed in the view, so that the view can rebuild without losing data. For example, it might contain boolean flags to conditionally render widgets in the view, or a field that tracks which section of a carousel is active on screen. \* Exposes callbacks (called \*\*commands\*\*) to the view that can be attached to an event handler, like a button press or form submission. Commands are named for the \[command pattern]\[], and are Dart functions that allow views to execute complex logic without knowledge of its implementation. Commands are written as members of the view model class to be called by the gesture handlers in the view class. You can find examples of views, view models, and commands on the \[UI layer]\[] portion of the \[App architecture case study]\[]. For a gentle introduction to MVVM in Flutter, check out the \[state management fundamentals]\[]. \[UI layer]: /app-architecture/case-study/ui-layer \[App architecture case study]: /app-architecture/case-study \[state management fundamentals]: /data-and-backend/state-mgmt/intro ## Data layer The data layer of an app handles your business data and logic. Two pieces of architecture make up the data layer: services and repositories. These pieces should have well-defined inputs and outputs to simplify their reusability and testability. Using MVVM language, services and repositories make up your \*model layer\*. ### Repositories \[Repository]\[] classes are the source of truth for your model data. They're responsible for polling data from services, and transforming that raw data into \*\*domain models\*\*. Domain models represent the data that the application needs, formatted in a way that your view model classes can consume. There should be a repository class for each different type of data handled in your app. Repositories handle the business logic associated with services, such as: \* Caching \* Error handling \* Retry logic \* Refreshing data \* Polling services for new data \* Refreshing data based on user actions Repositories output application data as domain models. For example, a social media app might have a \`UserProfileRepository\` class that exposes a \`Stream\`, which emits a new value whenever the user signs in or out. The models output by repositories are consumed by view models. Repositories and view models have a many-to-many relationship. A view model can use many repositories to get the data it needs, and a repository can be used by many view models. Repositories should never be aware of each other. If your application has business logic that needs data from two repositories, you should combine the data in the view model or in the domain layer, especially if your repository-to-view-model relationship is complex. #### Managing app-wide session state Because repositories are the single source of truth for application data, they are also the ideal place to manage \*\*app-wide lifecycle state\*\*—state that needs to be shared across multiple view models but shouldn't persist beyond the current application session. Examples of app-wide lifecycle state include an active user session, in-memory data caches, or transient application settings. Because view models and repositories have a many-to-many relationship, multiple view models can depend on the same repository instance (typically managed through a service locator or dependency injection container). This allows distinct features to reactively observe and modify the same shared state through streams and methods exposed by the repository, without violating the clean one-to-one boundary between a view and its view model. ### Services Services are in the lowest layer of your application. They wrap API endpoints and expose asynchronous response objects, such as \`Future\` and \`Stream\` objects. They're only used to isolate data-loading, and they hold no state. Your app should have one service class per data source. Examples of endpoints that services might wrap include: \* The underlying platform, like iOS and Android APIs \* REST endpoints \* Local files As a rule of thumb, services are most helpful when the necessary data lives outside of your application's Dart code - which is true of each of the preceding examples. Services and repositories have a many-to-many relationship. A single Repository can use several services, and a service can be used by multiple repositories. ## Optional: Domain layer As your app grows and adds features, you might need to abstract away logic that adds too much complexity to your view models. These classes are often called interactors or \*\*use-cases\*\*. Use-cases are responsible for making interactions between the UI and Data layers simpler and more reusable. They take data from repositories and make it suitable for the UI layer. Use-cases are primarily used to encapsulate business logic that would otherwise live in the view model and meets one or more of the following conditions: 1. Requires merging data from multiple repositories 2. Is exceedingly complex 3. The logic will be reused by different view models This layer is optional because not all applications or features within an application have these requirements. If you suspect your application would benefit from this additional layer, consider the pros and cons: | Pros | Cons | |--------------------------------------------------------------------------|--------------------------------------------------------------------------------------------| | ✅ Avoid code duplication in view models | ❌ Increases complexity of your architecture, adding more classes and higher cognitive load | | ✅ Improve testability by separating complex business logic from UI logic | ❌ Testing requires additional mocks | | ✅ Improve code readability in view models | ❌ Adds additional boilerplate to your code | {:.table .table-striped} ### Data access with use-cases Another consideration when adding a Domain layer is whether view models will continue to have access to repository data directly, or if you'll enforce view models to go through use-cases to get their data. Put another way, will you add use-cases as you need them? Perhaps when you notice repeated logic in your view models? Or, will you create a use-case each time a view model needs data, even if the logic in the use-case is simple? If you choose to do the latter, it intensifies the earlier outlined pros and cons. Your application code will be extremely modular and testable, but it also adds a significant amount of unnecessary overhead. A good approach is to add use-cases only when needed. If you find that your view models are accessing data through use-cases most of the time, you can always refactor your code to utilize use-cases exclusively. The example app used later in this guide has use-cases for some features, but also has view models that interact with repositories directly. A complex feature might ultimately end up looking like this: This method of adding use-cases is defined by the following rules: \* Use-cases depend on repositories \* Use-cases and repositories have a many-to-many relationship \* View models depend on one or more use-cases \*and\* one or more repositories This method of using use-cases ends up looking less like a layered lasagna, and more like a plated dinner with two mains (UI and data layers) and a side (domain layer). Use-cases are just utility classes that have well-defined inputs and outputs. This approach is flexible and extendable, but it requires greater diligence to maintain order. \[Separation-of-concerns]: https\://en.wikipedia.org/wiki/Separation\_of\_concerns \[command pattern]: https\://en.wikipedia.org/wiki/Command\_pattern \[Repository]: https\://martinfowler.com/eaaCatalog/repository.html ## Feedback As this section of the website is evolving, we \[welcome your feedback]\[]! \[welcome your feedback]: https\://google.qualtrics.com/jfe/form/SV\_4T0XuR9Ts29acw6?page="guide"

----
url: https://docs.flutter.dev/cookbook/lists/long-lists
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Work with long lists](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

The standard [`ListView`](https://api.flutter.dev/flutter/widgets/ListView-class.html) 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`](https://api.flutter.dev/flutter/widgets/ListView/ListView.builder.html) constructor.

In contrast to the default `ListView` constructor, which requires creating all items at once, the `ListView.builder()` constructor creates items as they're scrolled onto the screen.

## 1. Create a data source

[#](#1-create-a-data-source)

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, generate a list of 10,000 Strings using the [`List.generate`](https://api.flutter.dev/flutter/dart-core/List/List.generate.html) constructor.

dart

```
List<String>.generate(10000, (i) => 'Item $i'),
```

## 2. Convert the data source into widgets

[#](#2-convert-the-data-source-into-widgets)

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

```
ListView.builder(
  itemCount: items.length,
  prototypeItem: ListTile(title: Text(items.first)),
  itemBuilder: (context, index) {
    return ListTile(title: Text(items[index]));
  },
)
```

## Interactive example

[#](#interactive-example)

```
import 'package:flutter/material.dart';

void main() {
  runApp(
    MyApp(
      items: List<String>.generate(10000, (i) => 'Item $i'),
    ),
  );
}

class MyApp extends StatelessWidget {
  final List<String> items;

  const MyApp({super.key, required this.items});

  @override
  Widget build(BuildContext context) {
    const title = 'Long List';

    return MaterialApp(
      title: title,
      home: Scaffold(
        appBar: AppBar(title: const Text(title)),
        body: ListView.builder(
          itemCount: items.length,
          prototypeItem: ListTile(title: Text(items.first)),
          itemBuilder: (context, index) {
            return ListTile(title: Text(items[index]));
          },
        ),
      ),
    );
  }
}
```

## Children's extent

[#](#childrens-extent)

To specify each item's extent, you can use either [`prototypeItem`](https://api.flutter.dev/flutter/widgets/ListView/prototypeItem.html), [`itemExtent`](https://api.flutter.dev/flutter/widgets/ListView/itemExtent.html), or [`itemExtentBuilder`](https://api.flutter.dev/flutter/widgets/ListView/itemExtentBuilder.html).

Specifying either is more efficient than letting the children determine their own extent because the scrolling machinery can make use of the foreknowledge of the children's extent to save work, for example when the scroll position changes drastically.

Use [`prototypeItem`](https://api.flutter.dev/flutter/widgets/ListView/prototypeItem.html) or [`itemExtent`](https://api.flutter.dev/flutter/widgets/ListView/itemExtent.html) if your list has items of fixed size.

Use [`itemExtentBuilder`](https://api.flutter.dev/flutter/widgets/ListView/itemExtentBuilder.html) if your list has items of different sizes.

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/lists/long-lists.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/cookbook/lists/long-lists\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/lists/long-lists.md "Report an issue with this page").

----
url: https://docs.flutter.dev/perf/app-size
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Measuring your app's size](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

Many developers are concerned with the size of their compiled app. As the APK, app bundle, or IPA version of a Flutter app is self-contained and holds all the code and assets needed to run the app, its size can be a concern. The larger an app, the more space it requires on a device, the longer it takes to download, and it might break the limit of useful features like Android instant apps.

## Debug builds are not representative

[#](#debug-builds-are-not-representative)

By default, launching your app with `flutter run`, or by clicking the **Play** button in your IDE, generates a *debug* build of the Flutter app. The app size of a debug build is large due to the debugging overhead that allows for hot reload and source-level debugging. As such, it is not representative of a production app end users download.

## Checking the total size

[#](#checking-the-total-size)

A default release build, such as one created by `flutter build apk` or `flutter build ios`, is built to conveniently assemble your upload package to the Play Store and App Store. As such, they're also not representative of your end-users' download size. The stores generally reprocess and split your upload package to target the specific downloader and the downloader's hardware, such as filtering for assets targeting the phone's DPI, filtering native libraries targeting the phone's CPU architecture.

### Estimating total size

[#](#estimating-total-size)

To get the closest approximate size on each platform, use the following instructions.

#### Android

[#](#android)

Follow the Google [Play Console's instructions](https://support.google.com/googleplay/android-developer/answer/9302563?hl=en) for checking app download and install sizes.

Produce an upload package for your application:

```
flutter build appbundle
```

Log into your [Google Play Console](https://play.google.com/apps/publish/). Upload your application binary by drag dropping the .aab file.

View the application's download and install size in the **Android vitals** -> **App size** tab.

The download size is calculated based on an XXXHDPI (\~640dpi) device on an arm64-v8a architecture. Your end users' download sizes might vary depending on their hardware.

The top tab has a toggle for download size and install size. The page also contains optimization tips further below.

#### iOS

[#](#ios)

Create an [Xcode App Size Report](https://developer.apple.com/documentation/xcode/reducing_your_app_s_size#3458589).

First, by configuring the app version and build as described in the [iOS create build archive instructions](/deployment/ios#update-the-apps-build-and-version-numbers).

Then:

1. Run `flutter build ipa --export-method development`.
2. Run `open build/ios/archive/*.xcarchive` to open the archive in Xcode.
3. Click **Distribute App**.
4. Select a method of distribution. **Development** is the simplest if you don't intend to distribute the application.
5. In **App Thinning**, select 'all compatible device variants'.
6. Select **Strip Swift symbols**.

Sign and export the IPA. The exported directory contains `App Thinning Size Report.txt` with details about your projected application size on different devices and versions of iOS.

The App Size Report for the default demo app in Flutter 1.17 shows:

```
Variant: Runner-7433FC8E-1DF4-4299-A7E8-E00768671BEB.ipa
Supported variant descriptors: [device: iPhone12,1, os-version: 13.0] and [device: iPhone11,8, os-version: 13.0]
App + On Demand Resources size: 5.4 MB compressed, 13.7 MB uncompressed
App size: 5.4 MB compressed, 13.7 MB uncompressed
On Demand Resources size: Zero KB compressed, Zero KB uncompressed
```

In this example, the app has an approximate download size of 5.4 MB and an approximate installation size of 13.7 MB on an iPhone12,1 ([Model ID / Hardware number](https://en.wikipedia.org/wiki/List_of_iOS_devices#Models) for iPhone 11) and iPhone11,8 (iPhone XR) running iOS 13.0.

To measure an iOS app exactly, you have to upload a release IPA to Apple's App Store Connect ([instructions](/deployment/ios)) and obtain the size report from there. IPAs are commonly larger than APKs as explained in [How big is the Flutter engine?](/resources/faq#how-big-is-the-flutter-engine), a section in the Flutter [FAQ](/resources/faq).

## Breaking down the size

[#](#breaking-down-the-size)

Starting in Flutter version 1.22 and DevTools version 0.9.1, a size analysis tool is included to help developers understand the breakdown of the release build of their application.

The size analysis tool is invoked by passing the `--analyze-size` flag when building:

* `flutter build apk --analyze-size`
* `flutter build appbundle --analyze-size`
* `flutter build ios --analyze-size`
* `flutter build linux --analyze-size`
* `flutter build macos --analyze-size`
* `flutter build windows --analyze-size`

This build is different from a standard release build in two ways.

1. The tool compiles Dart in a way that records code size usage of Dart packages.
2. The tool displays a high level summary of the size breakdown in the terminal, and leaves a `*-code-size-analysis_*.json` file for more detailed analysis in DevTools.

In addition to analyzing a single build, two builds can also be diffed by loading two `*-code-size-analysis_*.json` files into DevTools. Check out the [DevTools documentation](/tools/devtools/app-size) for details.

Through the summary, you can get a quick idea of the size usage per category (such as asset, native code, Flutter libraries, etc). The compiled Dart native library is further broken down by package for quick analysis.

### Deeper analysis in DevTools

[#](#deeper-analysis-in-devtools)

The `*-code-size-analysis_*.json` file produced above can be further analyzed in deeper detail in DevTools where a tree or a treemap view can break down the contents of the application into the individual file level and up to function level for the Dart AOT artifact.

This can be done by `dart devtools`, selecting `Open app size tool` and uploading the JSON file.

For further information on using the DevTools app size tool, check out the [DevTools documentation](/tools/devtools/app-size).

## Reducing app size

[#](#reducing-app-size)

When building a release version of your app, consider using the `--split-debug-info` tag. This tag can dramatically reduce code size. For an example of using this tag, see [Obfuscating Dart code](/deployment/obfuscate).

Some other things you can do to make your app smaller are:

* Remove unused resources
* Minimize resource imported from libraries
* Compress PNG and JPEG files

Another way to reduce app size is by using platform-specific code. The Dart compiler removes code that is unreachable on the target platform. For example, if you have code that is specific to Windows, you can wrap it in a check using the `Platform` class from `dart:io`, like `if (Platform.isWindows)`. When you build the app for Android, the compiler sees that this check is always false and removes the Windows-specific code from the release build.

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/perf/app-size.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/perf/app-size\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/perf/app-size.md "Report an issue with this page").

----
url: https://docs.flutter.dev/tools/devtools/deep-links
----

The deep link view validates any deep links that are defined in your app.

To use this feature, open DevTools, click into the **Deep Links** tab, and import a Flutter project that contains deep links.

This tool helps you identify and troubleshoot any errors in your mobile deep link setup, from website configuration to manifest files. DevTools provides instructions on how to fix any issues, making the implementation process easier.

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-07-31. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/deep-links.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/tools/devtools/deep-links\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/deep-links.md "Report an issue with this page").

----
url: https://docs.flutter.dev/packages-and-plugins/background-processes.md
----

\# Background processes > Where to find more information on implementing background processes in Flutter. Have you ever wanted to execute Dart code in the background—even if your app wasn't the currently active app? Perhaps you wanted to implement a process that watches the time, or that catches camera movement. In Flutter, you can execute Dart code in the background. The mechanism for this feature involves setting up an isolate. \_Isolates\_ are Dart's model for multithreading, though an isolate differs from a conventional thread in that it doesn't share memory with the main program. You'll set up your isolate for background execution using callbacks and a callback dispatcher. Additionally, the \[WorkManager] plugin enables persistent background processing that keeps tasks scheduled through app restarts and system reboots. For more information and a geofencing example that uses background execution of Dart code, see the Flutter blog post by Ben Konyi, \[Executing Dart in the Background with Flutter Plugins and Geofencing]\[background-processes]. At the end of this article, you'll find links to example code, and relevant documentation for Dart, iOS, and Android. \[background-processes]: https\://blog.flutter.dev/executing-dart-in-the-background-with-flutter-plugins-and-geofencing-2b3e40a1a124 \[WorkManager]: https\://pub.dev/packages/workmanager

----
url: https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.27.0
----

The 2.27.0 release of the Dart and Flutter DevTools includes the following changes among other general improvements. To learn more about DevTools, check out the [DevTools overview](https://docs.flutter.dev/tools/devtools).

## Network profiler updates

[#](#network-profiler-updates)

* Fix a crash with large text in network requests and responses - [#6254](https://github.com/flutter/devtools/pull/6254)

## Full commit history

[#](#full-commit-history)

To find a complete list of changes since the previous release, check out [the diff on GitHub](https://github.com/flutter/devtools/compare/v2.26.1...v2.27.0).

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/release-notes/release-notes-2.27.0.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.27.0\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/release-notes/release-notes-2.27.0.md "Report an issue with this page").

----
url: https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.33.0.md
----

\# DevTools 2.33.0 release notes > Release notes for Dart and Flutter DevTools version 2.33.0. The 2.33.0 release of the Dart and Flutter DevTools includes the following changes among other general improvements. To learn more about DevTools, check out the \[DevTools overview]\(/tools/devtools). ## General updates \* Improved overall usability by making the DevTools UI more dense. This significantly improves the user experience when using DevTools embedded in an IDE. - \[#7030]\(https\://github.com/flutter/devtools/pull/7030) \* Removed the "Dense mode" setting. - \[#7086]\(https\://github.com/flutter/devtools/pull/7086) \* Added support for filtering with regular expressions in the Logging, Network, and CPU profiler pages. - \[#7027]\(https\://github.com/flutter/devtools/pull/7027) \* Add a DevTools server interaction for getting the DTD URI. - \[#7054]\(https\://github.com/flutter/devtools/pull/7054), \[#7164]\(https\://github.com/flutter/devtools/pull/7164) \* Enabled expression evaluation with scope for the web, allowing evaluation of inspected widgets. - \[#7144]\(https\://github.com/flutter/devtools/pull/7144) \* Update \`package:vm\_service\` constraint to \`^14.0.0\`. - \[#6953]\(https\://github.com/flutter/devtools/pull/6953) \* Onboarding DevTools to \[\`package:unified\_analytics\`]\(https\://pub.dev/packages/unified\_analytics) for unified telemetry logging across Flutter and Dart tooling. - \[#7084]\(https\://github.com/flutter/devtools/pull/7084) ## Debugger updates \* Fixed off by one error causing profiler hits to be rendered on the wrong lines. - \[#7178]\(https\://github.com/flutter/devtools/pull/7178) \* Improved contrast of line numbers when displaying code coverage hits in dark mode. - \[#7178]\(https\://github.com/flutter/devtools/pull/7178) \* Improved contrast of profiling details when displaying profiler hits in dark mode. - \[#7178]\(https\://github.com/flutter/devtools/pull/7178) \* Fixed syntax highlighting for comments when the source file uses \`\r\n\` line endings \[#7190]\(https\://github.com/flutter/devtools/pull/7190) \* Re-establish breakpoints after a hot-restart. - \[#7205]\(https\://github.com/flutter/devtools/pull/7205) ## VS Code Sidebar updates \* Do not show DevTools release notes in the Flutter sidebar. - \[#7166]\(https\://github.com/flutter/devtools/pull/7166) ## DevTools Extension updates \* Added support for connecting to the Dart Tooling Daemon from the simulated DevTools environment. - \[#7133]\(https\://github.com/flutter/devtools/pull/7133) \* Added help buttons to the VM Service and DTD connection text fields in the simulated DevTools environment. - \[#7133]\(https\://github.com/flutter/devtools/pull/7133) \* Fixed an issue with not detecting extensions for test files in subdirectories. - \[#7174]\(https\://github.com/flutter/devtools/pull/7174) \* Added an example of creating an extension for a pure Dart package. - \[#7196]\(https\://github.com/flutter/devtools/pull/7196) \* Updated the \`README.md\` and \`example/README.md\` with more complete documentation. - \[#7237]\(https\://github.com/flutter/devtools/pull/7237), \[#7261]\(https\://github.com/flutter/devtools/pull/7261) \* Added a \`devtools\_extensions validate\` command to validate extension requirements during development. - \[#7257]\(https\://github.com/flutter/devtools/pull/7257) ## Full commit history To find a complete list of changes in this release, check out the \[DevTools git log]\(https\://github.com/flutter/devtools/tree/v2.33.0).

----
url: https://docs.flutter.dev/ui/accessibility/ui-design-and-styling
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top UI design & styling](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

To create an accessible app, design your UI with accessibility in mind. This page covers key aspects of accessible UI design and styling.

## Large fonts

[#](#large-fonts)

Both Android and iOS contain system settings to configure the desired font sizes used by apps. Flutter text widgets respect this OS setting when determining font sizes.

Font sizes are calculated automatically by Flutter based on the OS setting. However, as a developer you should make sure your layout has enough room to render all its contents when the font sizes are increased. For example, you can test all parts of your app on a small-screen device configured to use the largest font setting.

To adjust font sizes: on iOS, go to Settings > Accessibility > Display & Text Size; on Android, go to Settings > Font size.

### Example

[#](#example)

The following two screenshots show the standard Flutter app template rendered with the default iOS font setting, and with the largest font setting selected in iOS accessibility settings.

Default font setting

Largest accessibility font setting

## Sufficient contrast

[#](#sufficient-contrast)

Sufficient color contrast makes text and images easier to read. Along with benefitting users with various visual impairments, sufficient color contrast helps all users when viewing an interface on devices in extreme lighting conditions, such as when exposed to direct sunlight or on a display with low brightness.

The [W3C recommends](https://www.w3.org/TR/UNDERSTANDING-WCAG20/visual-audio-contrast-contrast.html):

* At least 4.5:1 for small text (below 18 point regular or 14 point bold)
* At least 3.0:1 for large text (18 point and above regular or 14 point and above bold)

You can test contrast using Flutter's [Accessibility Guideline API](https://api.flutter.dev/flutter/flutter_test/AccessibilityGuideline-class.html). For more details on testing, check out the [accessibility testing page](/ui/accessibility/accessibility-testing/).

## Tap target size

[#](#tap-target-size)

Controls that are too small are hard for many people to interact with and select. Ensure that interactive elements have a large enough tap target to be easily pressed by users.

Both [Android](https://developer.android.com/guide/topics/ui/accessibility/apps#large-controls) and [iOS](https://developer.apple.com/design/human-interface-guidelines/accessibility#Mobility) recommend a minimum tap target size of 48x48 dp and 44x44 pts respectively.

The [W3C](https://www.w3.org/WAI/WCAG21/Understanding/target-size.html) recommends a minimum target size of 44 by 44 CSS pixels.

You can test tap target size using Flutter's [Accessibility Guideline API](https://api.flutter.dev/flutter/flutter_test/AccessibilityGuideline-class.html). For more details on testing, check out the [accessibility testing page](/ui/accessibility/accessibility-testing/).

## Other accessibility features

[#](#other-accessibility-features)

You can check the [AccessibilityFeatures](https://api.flutter.dev/flutter/dart-ui/AccessibilityFeatures-class.html) class for additional accessibility features that may be enabled by the platform, such as bold text, high contrast, and inverted colors.

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/ui/accessibility/ui-design-and-styling.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/ui/accessibility/ui-design-and-styling\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/ui/accessibility/ui-design-and-styling.md "Report an issue with this page").

----
url: https://docs.flutter.dev/resources/bug-reports
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Create useful bug reports](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

The instructions in this document detail the current steps required to provide the most actionable bug reports for crashes and other bad behavior. Each step is optional but will greatly improve how quickly issues are diagnosed and addressed. We appreciate your effort in sending us as much feedback as possible.

## Create an issue on GitHub

[#](#create-an-issue-on-github)

* To report a Flutter crash or bug, [create an issue in the flutter/flutter project](https://github.com/flutter/flutter/issues/new/choose).
* To report a problem with the website, [create an issue in the flutter/website project](https://github.com/flutter/website/issues/new/choose).

## Provide a minimal reproducible code sample

[#](#provide-a-minimal-reproducible-code-sample)

Create a minimal Flutter app that shows the problem you are facing, and paste it into the GitHub issue.

To create it you can use `flutter create bug` command and update the `main.dart` file.

Alternatively, you can use [DartPad](https://dartpad.dev), which is capable of creating and running small Flutter apps.

If your problem goes out of what can be placed in a single file, for example you have a problem with native channels, you can upload the full code of the reproduction into a separate repository and link it.

## Provide some Flutter diagnostics

[#](#provide-some-flutter-diagnostics)

* Run `flutter doctor -v` in your project directory and paste the results into the GitHub issue:

```
[✓] Flutter (Channel stable, 1.22.3, on Mac OS X 10.15.7 19H2, locale en-US)
    • Flutter version 1.22.3 at /Users/me/projects/flutter
    • Framework revision 8874f21e79 (5 days ago), 2020-10-29 14:14:35 -0700
    • Engine revision a1440ca392
    • Dart version 2.10.3

[✓] Android toolchain - develop for Android devices (Android SDK version 29.0.2)
    • Android SDK at /Users/me/Library/Android/sdk
    • Platform android-30, build-tools 29.0.2
    • Java binary at: /Applications/Android Studio.app/Contents/jre/jdk/Contents/Home/bin/java
    • Java version OpenJDK Runtime Environment (build 1.8.0_242-release-1644-b3-6222593)
    • All Android licenses accepted.

[✓] Xcode - develop for iOS and macOS (Xcode 12.2)
    • Xcode at /Applications/Xcode.app/Contents/Developer
    • Xcode 12.2, Build version 12B5035g
    • CocoaPods version 1.9.3

[✓] Android Studio (version 4.0)
    • Android Studio at /Applications/Android Studio.app/Contents
    • Flutter plugin version 50.0.1
    • Dart plugin version 193.7547
    • Java version OpenJDK Runtime Environment (build 1.8.0_242-release-1644-b3-6222593)

[✓] VS Code (version 1.50.1)
    • VS Code at /Applications/Visual Studio Code.app/Contents
    • Flutter extension version 3.13.2

[✓] Connected device (1 available)
    • iPhone (mobile) • 00000000-0000000000000000 • ios • iOS 14.0
```

## Run the command in verbose mode

[#](#run-the-command-in-verbose-mode)

Follow these steps only if your issue is related to the `flutter` tool.

* All Flutter commands accept the `--verbose` flag. If attached to the issue, the output from this command might aid in diagnosing the problem.
* Attach the results of the command to the GitHub issue.

## Provide the most recent logs

[#](#provide-the-most-recent-logs)

* Logs for the currently connected device are accessed using `flutter logs`.
* If the crash is reproducible, clear the logs (⌘ + k on Mac), reproduce the crash and copy the newly generated logs into a file attached to the bug report.
* If you are getting exceptions thrown by the framework, include all the output between and including the dashed lines of the first such exception.

## Provide the crash report

[#](#provide-the-crash-report)

* When the iOS simulator crashes, a crash report is generated in `~/Library/Logs/DiagnosticReports/`.
* When an iOS device crashes, a crash report is generated in `~/Library/Logs/CrashReporter/MobileDevice`.
* Find the report corresponding to the crash (usually the latest) and attach it to the GitHub issue.

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/resources/bug-reports.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/resources/bug-reports\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/resources/bug-reports.md "Report an issue with this page").

----
url: https://docs.flutter.dev/cookbook/forms/retrieve-input.md
----

\# Retrieve the value of a text field > How to retrieve text from a text field.

<!--?code-excerpt path-base="cookbook/forms/retrieve_input"?-->

In this recipe, learn how to retrieve the text a user has entered into a text field using the following steps: 1. Create a \`TextEditingController\`. 1. Supply the \`TextEditingController\` to a \`TextField\`. 1. Display the current value of the text field. ## 1. Create a \`TextEditingController\` To retrieve the text a user has entered into a text field, create a \[\`TextEditingController\`]\[] and supply it to a \`TextField\` or \`TextFormField\`. :::important Call \`dispose()\` on the \`TextEditingController\` when you've finished using it. This ensures that you discard any resources used by the object. :::

<!--?code-excerpt "lib/starter.dart (Starter)" remove="return Container();"?-->

\`\`\`dart // Define a custom Form widget. class MyCustomForm extends StatefulWidget { const MyCustomForm({super.key}); @override State createState() => \_MyCustomFormState(); } // Define a corresponding State class. // This class holds the data related to the Form. class \_MyCustomFormState extends State { // 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. myController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { // Fill this out in the next step. } } \`\`\` ## 2. Supply the \`TextEditingController\` to a \`TextField\` Now that you have a \`TextEditingController\`, wire it up to a text field using the \`controller\` property:

<!--?code-excerpt "lib/step2.dart (TextFieldController)"?-->

\`\`\`dart return TextField(controller: myController); \`\`\` ## 3. Display the current value of the text field After supplying the \`TextEditingController\` to the text field, begin reading values. Use the \[\`text\`]\[] property provided by the \`TextEditingController\` to retrieve the \`String\` that the user has entered into the text field. The following code displays an alert dialog with the current value of the text field when the user taps a floating action button.

<!--?code-excerpt "lib/step3.dart (FloatingActionButton)" replace="/^floatingActionButton\: //g"?-->

\`\`\`dart FloatingActionButton( // When the user presses the button, show an alert dialog containing // the text that the user has entered into the text field. onPressed: () { showDialog( context: context, builder: (context) { return AlertDialog( // Retrieve the text that the user has entered by // using the TextEditingController. content: Text(myController.text), ); }, ); }, tooltip: 'Show me the value!', child: const Icon(Icons.text\_fields), ), \`\`\` ## Interactive example

<!--?code-excerpt "lib/main.dart"?-->

\`\`\`dartpad title="Flutter retrieve input hands-on example in DartPad" run="true" import 'package:flutter/material.dart'; void main() => runApp(const MyApp()); class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return const MaterialApp( title: 'Retrieve Text Input', home: MyCustomForm(), ); } } // Define a custom Form widget. class MyCustomForm extends StatefulWidget { const MyCustomForm({super.key}); @override State createState() => \_MyCustomFormState(); } // Define a corresponding State class. // This class holds the data related to the Form. class \_MyCustomFormState extends State { // 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. myController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('Retrieve Text Input')), body: Padding( padding: const EdgeInsets.all(16), child: TextField(controller: myController), ), floatingActionButton: FloatingActionButton( // When the user presses the button, show an alert dialog containing // the text that the user has entered into the text field. onPressed: () { showDialog( context: context, builder: (context) { return AlertDialog( // Retrieve the text that the user has entered by using the // TextEditingController. content: Text(myController.text), ); }, ); }, tooltip: 'Show me the value!', child: const Icon(Icons.text\_fields), ), ); } } \`\`\` \[\`text\`]: https\://api.flutter.dev/flutter/widgets/TextEditingController/text.html \[\`TextEditingController\`]: https\://api.flutter.dev/flutter/widgets/TextEditingController-class.html

----
url: https://docs.flutter.dev/ui/widgets/scrolling
----

Scroll multiple widgets as children of the parent.

[Carousel​View](https://pub.dev/documentation/material_ui/latest/material_ui/CarouselView-class.html)

[A Material carousel widget that presents a scrollable list of items, each of which can dynamically change size based on the chosen layout.](https://pub.dev/documentation/material_ui/latest/material_ui/CarouselView-class.html)

[Custom​Scroll​View](https://api.flutter.dev/flutter/widgets/CustomScrollView-class.html)

[A ScrollView that creates custom scroll effects using slivers.](https://api.flutter.dev/flutter/widgets/CustomScrollView-class.html)

[Draggable​Scrollable​Sheet](https://api.flutter.dev/flutter/widgets/DraggableScrollableSheet-class.html)

[A container for a Scrollable that responds to drag gestures by resizing the scrollable until a limit is reached, and then scrolling.](https://api.flutter.dev/flutter/widgets/DraggableScrollableSheet-class.html)

[Grid​View](https://api.flutter.dev/flutter/widgets/GridView-class.html)

[A grid list consists of a repeated pattern of cells arrayed in a vertical and horizontal layout. The GridView widget implements this component.](https://api.flutter.dev/flutter/widgets/GridView-class.html)

[List​View](https://api.flutter.dev/flutter/widgets/ListView-class.html)

[A scrollable, linear list of widgets. ListView is the most commonly used scrolling widget. It displays its children one after another in the scroll direction....](https://api.flutter.dev/flutter/widgets/ListView-class.html)

[Nested​Scroll​View](https://api.flutter.dev/flutter/widgets/NestedScrollView-class.html)

[A scrolling view inside of which can be nested other scrolling views, with their scroll positions being intrinsically linked.](https://api.flutter.dev/flutter/widgets/NestedScrollView-class.html)

[Notification​Listener](https://api.flutter.dev/flutter/widgets/NotificationListener-class.html)

[A widget that listens for Notifications bubbling up the tree.](https://api.flutter.dev/flutter/widgets/NotificationListener-class.html)

[Page​View](https://api.flutter.dev/flutter/widgets/PageView-class.html)

[A scrollable list that works page by page.](https://api.flutter.dev/flutter/widgets/PageView-class.html)

[Refresh​Indicator](https://pub.dev/documentation/material_ui/latest/material_ui/RefreshIndicator-class.html)

[A Material Design pull-to-refresh wrapper for scrollables.](https://pub.dev/documentation/material_ui/latest/material_ui/RefreshIndicator-class.html)

[Reorderable​List​View](https://pub.dev/documentation/material_ui/latest/material_ui/ReorderableListView-class.html)

[A list whose items the user can interactively reorder by dragging.](https://pub.dev/documentation/material_ui/latest/material_ui/ReorderableListView-class.html)

[Scroll​Configuration](https://api.flutter.dev/flutter/widgets/ScrollConfiguration-class.html)

[Controls how Scrollable widgets behave in a subtree.](https://api.flutter.dev/flutter/widgets/ScrollConfiguration-class.html)

[Scrollable](https://api.flutter.dev/flutter/widgets/Scrollable-class.html)

[Scrollable implements the interaction model for a scrollable widget, including gesture recognition, but does not have an opinion about how the viewport, which actually displays...](https://api.flutter.dev/flutter/widgets/Scrollable-class.html)

[Scrollbar](https://pub.dev/documentation/material_ui/latest/material_ui/Scrollbar-class.html)

[A Material Design scrollbar. A scrollbar indicates which portion of a Scrollable widget is actually visible.](https://pub.dev/documentation/material_ui/latest/material_ui/Scrollbar-class.html)

[Single​Child​Scroll​View](https://api.flutter.dev/flutter/widgets/SingleChildScrollView-class.html)

[A box in which a single widget can be scrolled. This widget is useful when you have a single box that will normally be entirely...](https://api.flutter.dev/flutter/widgets/SingleChildScrollView-class.html)

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-09-29. [Report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/ui/widgets/scrolling "Report an issue with this page").

----
url: https://docs.flutter.dev/install/quick
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Set up and test drive Flutter](#site-content-title)

   ```
   $ sudo apt-get update -y && sudo apt-get upgrade -y
   $ sudo apt-get install -y curl git unzip xz-utils zip libglu1-mesa
   ```

3. ### Download and install Visual Studio Code

   To quickly install Flutter, then edit and debug your apps, [install and set up Visual Studio Code](https://code.visualstudio.com/docs/setup/setup-overview).

   You can instead install and use any other Code OSS-based editor that supports VS Code extensions. If you choose to do so, for the rest of this article, assume VS Code refers to the editor of your choice.

1) ### Install git

   **If you already have git installed, skip to the next step: Download and install Visual Studio Code.**

   There are a few ways to install git on your Mac, but the way we recommend is by using XCode. This will be important when you target your builds for iOS or macOS.

   ```
   $ xcode-select --install
   ```

   ```
   $ sudo apt-get update -y && sudo apt-get upgrade -y
   $ sudo apt-get install -y curl git unzip xz-utils zip libglu1-mesa
   ```

      Go to **View** > **Command Palette** or press `Cmd/Ctrl` + `Shift` + `P`.

   2. In the command palette, type `flutter`.

   3. Select **Flutter: New Project**.

   4. VS Code prompts you to locate the Flutter SDK on your computer. Select **Download SDK**.

   5. When the **Select Folder for Flutter SDK** dialog displays, choose where you want to install Flutter.

   6. Click **Clone Flutter**.

      While downloading Flutter, VS Code displays this pop-up notification:

      ```
      Downloading the Flutter SDK. This may take a few minutes.
      ```

      This download takes a few minutes. If you suspect that the download has hung, click **Cancel** then start the installation again.

   7. Click **Add SDK to PATH**.

      When successful, a notification displays:

      ```
      The Flutter SDK was added to your PATH
      ```

   8. VS Code might display a Google Analytics notice.

      If you agree, click **OK**.

   9. To ensure that Flutter is available in all terminals:

      1. Close, then reopen all terminal windows.
      2. Restart VS Code.

4. ### Troubleshoot installation issues

   If you encounter any issues during installation, check out [Flutter installation troubleshooting](/install/troubleshoot).

## Test drive Flutter

[#](#test-drive)

Now that you've set up VS Code and Flutter, it's time to create an app and try out Flutter development!

1. ### Create a new Flutter app

   1. Open the command palette in VS Code.

      Go to **View** > **Command Palette** or press `Cmd/Ctrl` + `Shift` + `P`.

   2. In the command palette, start typing `flutter:`.

      VS Code should surface commands from the Flutter plugin.

   3. Select the **Flutter: New Project** command.

      Your OS or VS Code might ask for access to your documents, agree to continue to the next step.

   4. Choose the **Application** template.

      VS Code should prompt you with **Which Flutter template?**. Choose **Application** to bootstrap a simple counter app.

   5. Create or select the parent directory for your new app's folder.

      A file dialog should appear.

      1. Select or create the parent directory where you want the project to be created.
      2. To confirm your selection, click **Select a folder to create the project in**.

   6. Enter a name for your app.

      VS Code should prompt you to enter a name for your new app. Enter `trying_flutter` or a similar `lowercase_with_underscores` name. To confirm your selection, press `Enter`.

   7. Wait for project initialization to complete.

      Task progress is often surfaced as a notification in the bottom right and can also be accessed from the **Output** panel.

   8. Open the `lib` directory, then the `main.dart` file.

      If you're curious about what each portion of the code does, check out the preceding comments throughout the file.

2. ### Run your app on the web

   While Flutter apps can run on many platforms, try running your new app on the web.

   1. Open the command palette in VS Code.

      Go to **View** > **Command Palette** or press `Cmd/Ctrl` + `Shift` + `P`.

   2. In the command palette, start typing `flutter:`.

      VS Code should surface commands from the Flutter plugin.

   3. Select the **Flutter: Select Device** command.

   4. From the **Select Device** prompt, select **Chrome**.

   5. Run or start debugging your app.

      Go to **Run** > **Start Debugging** or press `F5`.

      `flutter run` is used to build and start your app, then a new instance of Chrome should open and start running your newly created app.

3. ### Try hot reload

   Flutter offers a fast development cycle with **stateful hot reload**, the ability to reload the code of a live running app without restarting or losing app state.

   You can change your app's source code, run the hot reload command in VS Code, then see the change in your running app.

   1. In the running app, try adding to the counter a few times by clicking the button.

   2. With your app still running, make a change in the `lib/main.dart` file.

      Change the `_counter++` line in the `_incrementCounter` method to instead decrement the `_counter` field.

      dart

      ```
      setState(() {
        // ...
        _counter++;
        _counter--;
      });
      ```

   3. Save your changes (**File** > **Save All**) or click the **Hot Reload** button.

      Flutter updates the running app without losing any existing state. Notice the existing value stayed the same.

   4. Try clicking the button again. Notice the value decreases instead of increases.

4. ### Explore the Flutter sidebar

   The Flutter plugin adds a dedicated sidebar to VS Code for managing Flutter debug sessions and devices, viewing an outline of your code and widgets, as well as accessing the Dart and Flutter DevTools.

   1. If your app isn't running, start debugging it again.

      Go to **Run** > **Start Debugging** or press `F5`.

   2. Open the Flutter sidebar in VS Code.

      Either open it with the Flutter button in the VS Code sidebar or open it from the command palette by running the **Flutter: Focus on Flutter Sidebar View** command.

   3. In the Flutter sidebar, under **DevTools**, click the **Flutter Inspector** button.

      A separate **Widget Inspector** panel should open in VS Code.

      In the widget inspector, you can view your app's widget tree, view the properties and layout of each widget, and more.

   4. In the widget inspector, try clicking the top-level `MyHomePage` widget.

      A view of its properties and layout should open, and the VS Code editor should navigate to and focus the line where the widget was included.

   5. Explore and try out other features in the widget inspector and Flutter sidebar.

## Continue your Flutter journey

[#](#next-steps)

**Congratulations!** Now that you've installed and tried out Flutter, follow the [Flutter learning pathway](/learn/pathway), set up development for an [additional target platform](/platform-integration#setup), or explore some of these resources to continue your Flutter learning journey.

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/install/quick.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/install/quick\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/install/quick.md "Report an issue with this page").

----
url: https://docs.flutter.dev/cookbook/persistence/sqlite.md
----

\# Persist data with SQLite > How to use SQLite to store and retrieve data.

<!--?code-excerpt path-base="cookbook/persistence/sqlite/"?-->

:::note This guide uses the \[sqflite package]\[]. This package only supports apps that run on macOS, iOS, or Android. ::: \[sqflite package]: https\://pub.dev/packages/sqflite If you are writing an app that needs to persist and query large 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 your relational data needs to sync across devices in the cloud, check out \[Choose a database for your Flutter app]\[]. \[Choose a database for your Flutter app]: /data-and-backend/persistence/choose-a-database Flutter apps can make use of the SQLite databases via the \[\`sqflite\`]\[] plugin available on pub.dev. This recipe demonstrates the basics of using \`sqflite\` to insert, read, update, and remove data about various Dogs. If you are new to SQLite and SQL statements, review the \[SQLite Tutorial]\[] to learn the basics before completing this recipe. This recipe uses the following steps: 1. Add the dependencies. 2. Define the \`Dog\` data model. 3. Open the database. 4. Create the \`dogs\` table. 5. Insert a \`Dog\` into the database. 6. Retrieve the list of dogs. 7. Update a \`Dog\` in the database. 7. Delete a \`Dog\` from the database. ## 1. Add the dependencies To work with SQLite databases, import the \`sqflite\` and \`path\` packages. \* 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. To add the packages as a dependency, run \`flutter pub add\`: \`\`\`console $ flutter pub add sqflite path \`\`\` Make sure to import the packages in the file you'll be working in.

<!--?code-excerpt "lib/main.dart (imports)"?-->

\`\`\`dart import 'dart:async'; import 'package:flutter/widgets.dart'; import 'package:path/path.dart'; import 'package:sqflite/sqflite.dart'; \`\`\` ## 2. Define the Dog data model 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.

<!--?code-excerpt "lib/step2.dart"?-->

\`\`\`dart class Dog { final int id; final String name; final int age; const Dog({required this.id, required this.name, required this.age}); } \`\`\` ## 3. Open the database 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 \`getDatabasesPath()\` from the \`sqflite\` package, combined with the \`join\` function from the \`path\` package. 2. Open the database with the \`openDatabase()\` function from \`sqflite\`. :::note In order to use the keyword \`await\`, the code must be placed inside an \`async\` function. You should place all the following table functions inside \`void main() async {}\`. :::

<!--?code-excerpt "lib/step3.dart (openDatabase)"?-->

\`\`\`dart // Avoid errors caused by flutter upgrade. // Importing 'package:flutter/widgets.dart' is required. WidgetsFlutterBinding.ensureInitialized(); // Open the database and store the reference. final 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 // constructed for each platform. join(await getDatabasesPath(), 'doggie\_database.db'), ); \`\`\` ## 4. Create 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 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, see the \[official SQLite Datatypes documentation]\[].

<!--?code-excerpt "lib/main.dart (openDatabase)"?-->

\`\`\`dart final 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 // constructed for each platform. join(await getDatabasesPath(), 'doggie\_database.db'), // When the database is first created, create a table to store dogs. onCreate: (db, version) { // Run the CREATE TABLE statement on the database. return db.execute( 'CREATE TABLE dogs(id INTEGER PRIMARY KEY, name TEXT, age INTEGER)', ); }, // Set the version. This executes the onCreate function and provides a // path to perform database upgrades and downgrades. version: 1, ); \`\`\` ## 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. First, insert a \`Dog\` into the \`dogs\` table. This involves two steps: 1. Convert the \`Dog\` into a \`Map\` 2. Use the \[\`insert()\`]\[] method to store the \`Map\` in the \`dogs\` table.

<!--?code-excerpt "lib/main.dart (Dog)"?-->

\`\`\`dart class Dog { final int id; final String name; final int age; Dog({required this.id, required this.name, required this.age}); // Convert a Dog into a Map. The keys must correspond to the names of the // columns in the database. Map toMap() { return {'id': id, 'name': name, 'age': age}; } // 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}'; } } \`\`\`

<!--?code-excerpt "lib/main.dart (insertDog)"?-->

\`\`\`dart // Define a function that inserts dogs into the database Future insertDog(Dog dog) async { // Get a reference to the database. final db = await database; // 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. await db.insert( 'dogs', dog.toMap(), conflictAlgorithm: ConflictAlgorithm.replace, ); } \`\`\`

<!--?code-excerpt "lib/main.dart (fido)"?-->

\`\`\`dart // Create a Dog and add it to the dogs table var fido = Dog(id: 0, name: 'Fido', age: 35); await insertDog(fido); \`\`\` ## 6. Retrieve the list of Dogs 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 returns a \`List\`. 2. Convert the \`List\` into a \`List\`.

<!--?code-excerpt "lib/main.dart (dogs)"?-->

\`\`\`dart // A method that retrieves all the dogs from the dogs table. Future> dogs() async { // Get a reference to the database. final db = await database; // Query the table for all the dogs. final List> dogMaps = await db.query('dogs'); // Convert the list of each dog's fields into a list of \`Dog\` objects. return \[ for (final {'id': id as int, 'name': name as String, 'age': age as int} in dogMaps) Dog(id: id, name: name, age: age), ]; } \`\`\`

<!--?code-excerpt "lib/main.dart (print)"?-->

\`\`\`dart // 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 inserting information into the database, you might want to update that information at a later time. You can do this by using the \[\`update()\`]\[] 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.

<!--?code-excerpt "lib/main.dart (update)"?-->

\`\`\`dart Future updateDog(Dog dog) async { // Get a reference to the database. final db = await database; // Update the given Dog. await db.update( 'dogs', dog.toMap(), // Ensure that the Dog has a matching id. where: 'id = ?', // Pass the Dog's id as a whereArg to prevent SQL injection. whereArgs: \[dog.id], ); } \`\`\`

<!--?code-excerpt "lib/main.dart (update2)"?-->

\`\`\`dart // Update Fido's age and save it to the database. fido = Dog(id: fido.id, name: fido.name, age: fido.age + 7); await updateDog(fido); // Print the updated results. print(await dogs()); // Prints Fido with age 42. \`\`\` :::warning 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}"\`! ::: ## 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()\`]\[] method from the \`sqflite\` library. 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.

<!--?code-excerpt "lib/main.dart (deleteDog)"?-->

\`\`\`dart Future deleteDog(int id) async { // Get a reference to the database. final db = await database; // Remove the Dog from the database. await db.delete( 'dogs', // Use a \`where\` clause to delete a specific dog. where: 'id = ?', // Pass the Dog's id as a whereArg to prevent SQL injection. whereArgs: \[id], ); } \`\`\` ## Example To run the example: 1. Create a new Flutter project. 2. Add the \`sqflite\` 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\`.

<!--?code-excerpt "lib/main.dart"?-->

\`\`\`dart import 'dart:async'; import 'package:flutter/widgets.dart'; import 'package:path/path.dart'; import 'package:sqflite/sqflite.dart'; void main() async { // Avoid errors caused by flutter upgrade. // Importing 'package:flutter/widgets.dart' is required. WidgetsFlutterBinding.ensureInitialized(); // Open the database and store the reference. final 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 // constructed for each platform. join(await getDatabasesPath(), 'doggie\_database.db'), // When the database is first created, create a table to store dogs. onCreate: (db, version) { // Run the CREATE TABLE statement on the database. return db.execute( 'CREATE TABLE dogs(id INTEGER PRIMARY KEY, name TEXT, age INTEGER)', ); }, // Set the version. This executes the onCreate function and provides a // path to perform database upgrades and downgrades. version: 1, ); // Define a function that inserts dogs into the database Future insertDog(Dog dog) async { // Get a reference to the database. final db = await database; // 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. await db.insert( 'dogs', dog.toMap(), conflictAlgorithm: ConflictAlgorithm.replace, ); } // A method that retrieves all the dogs from the dogs table. Future> dogs() async { // Get a reference to the database. final db = await database; // Query the table for all the dogs. final List> dogMaps = await db.query('dogs'); // Convert the list of each dog's fields into a list of \`Dog\` objects. return \[ for (final {'id': id as int, 'name': name as String, 'age': age as int} in dogMaps) Dog(id: id, name: name, age: age), ]; } Future updateDog(Dog dog) async { // Get a reference to the database. final db = await database; // Update the given Dog. await db.update( 'dogs', dog.toMap(), // Ensure that the Dog has a matching id. where: 'id = ?', // 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. final db = await database; // Remove the Dog from the database. await db.delete( 'dogs', // Use a \`where\` clause to delete a specific dog. where: 'id = ?', // Pass the Dog's id as a whereArg to prevent SQL injection. whereArgs: \[id], ); } // Create a Dog and add it to the dogs table var fido = Dog(id: 0, name: 'Fido', age: 35); await insertDog(fido); // Now, use the method above to retrieve all the dogs. print(await dogs()); // Prints a list that include Fido. // Update Fido's age and save it to the database. fido = Dog(id: fido.id, name: fido.name, age: fido.age + 7); await updateDog(fido); // Print the updated results. print(await dogs()); // Prints Fido with age 42. // Delete Fido from the database. await deleteDog(fido.id); // Print the list of dogs (empty). print(await dogs()); } class Dog { final int id; final String name; final int age; Dog({required this.id, required this.name, required this.age}); // Convert a Dog into a Map. The keys must correspond to the names of the // columns in the database. Map toMap() { return {'id': id, 'name': name, 'age': age}; } // 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}'; } } \`\`\` ## Use relationships between tables :::note This is a more advanced use case and not required for basic usage. ::: In real-world applications, you often need to model relationships between tables. For example, instead of storing all data in a single table, you can separate related data into multiple tables and connect them using foreign keys. \`\`\`sql CREATE TABLE breeds( id INTEGER PRIMARY KEY, name TEXT ); You can execute these statements using the \`db.execute()\` method from the \`sqflite\` package. CREATE TABLE dogs( id INTEGER PRIMARY KEY, name TEXT, age INTEGER, breed\_id INTEGER, FOREIGN KEY (breed\_id) REFERENCES breeds(id) ); \`\`\` In this example, each dog is associated with a breed using the \`breed\_id\` field. This allows you to organize data more efficiently and avoid duplication. \[\`delete()\`]: https\://pub.dev/documentation/sqflite\_common/latest/sqlite\_api/DatabaseExecutor/delete.html \[\`insert()\`]: https\://pub.dev/documentation/sqflite\_common/latest/sqlite\_api/DatabaseExecutor/insert.html \[\`sqflite\`]: https\://pub.dev/packages/sqflite \[SQLite Tutorial]: https\://www\.sqlitetutorial.net/ \[official SQLite Datatypes documentation]: https\://www\.sqlite.org/datatype3.html \[\`update()\`]: https\://pub.dev/documentation/sqflite\_common/latest/sqlite\_api/DatabaseExecutor/update.html

----
url: https://docs.flutter.dev/flutter-for/android-devs.md
----

\# Flutter for Android developers > Learn how to apply Android developer knowledge when building Flutter apps.

<!--?code-excerpt path-base="get-started/flutter-for/android_devs"?-->

This document is meant for Android developers looking to apply their existing Android knowledge to build mobile apps with Flutter. If you understand the fundamentals of the Android framework then you can use this document as a jump start to Flutter development. :::note Android has two native user interface systems, Views (XML based) and Jetpack Compose. Some fundamentals are shared so this document will provide value no matter what. However, if you are coming from Jetpack Compose, check out \[Flutter for Jetpack Compose devs]\[] for detailed information about Jetpack Compose and how samples match up to Flutter examples. To integrate Flutter code into your Android app, see \[Add Flutter to existing app]\[]. ::: Your Android knowledge and skill set are highly valuable when building with Flutter, because Flutter relies on the mobile operating system for numerous capabilities and configurations. Flutter is a new way to build UIs for mobile, but it has a plugin system to communicate with Android (and iOS) for non-UI tasks. If you're an expert with Android, you don't have to relearn everything to use Flutter. This document can be used as a cookbook by jumping around and finding questions that are most relevant to your needs. ## Views ### What is the equivalent of a View in Flutter? :::secondary How is react-style, or \_declarative\_, programming different than the traditional imperative style? For a comparison, see \[Introduction to declarative UI]\[]. ::: In Android, the \`View\` is the foundation of everything that shows up on the screen. Buttons, toolbars, and inputs, everything is a View. In Flutter, the rough equivalent to a \`View\` is a \`Widget\`. Widgets don't map exactly to Android views, but while you're getting acquainted with how Flutter works you can think of them as "the way you declare and construct UI". However, these have a few differences to a \`View\`. To start, widgets have a different lifespan: they are immutable and only exist until they need to be changed. Whenever widgets or their state change, Flutter's framework creates a new tree of widget instances. In comparison, an Android view is drawn once and does not redraw until \`invalidate\` is called. Flutter's widgets are lightweight, in part due to their immutability. Because they aren't views themselves, and aren't directly drawing anything, but rather are a description of the UI and its semantics that get "inflated" into actual view objects under the hood. Flutter includes the \[Material Components]\[] library. These are widgets that implement the \[Material Design guidelines]\[]. Material Design is a flexible design system \[optimized for all platforms]\[], including iOS. But Flutter is flexible and expressive enough to implement any design language. For example, on iOS, you can use the \[Cupertino widgets]\[] to produce an interface that looks like \[Apple's iOS design language]\[]. ### How do I update widgets? In Android, you update your views by directly mutating them. However, in Flutter, \`Widget\`s are immutable and are not updated directly, instead you have to work with the widget's state. This is where the concept of \`Stateful\` and \`Stateless\` widgets comes from. A \`StatelessWidget\` is just what it sounds like—a widget with no state information. \`StatelessWidgets\` are useful when the part of the user interface you are describing does not depend on anything other than the configuration information in the object. For example, in Android, this is similar to placing an \`ImageView\` with your logo. The logo is not going to change during runtime, so use a \`StatelessWidget\` in Flutter. If you want to dynamically change the UI based on data received after making an HTTP call or user interaction then you have to work with \`StatefulWidget\` and tell the Flutter framework that the widget's \`State\` has been updated so it can update that widget. The important thing to note here is at the core both stateless and stateful widgets behave the same. They rebuild every frame, the difference is the \`StatefulWidget\` has a \`State\` object that stores state data across frames and restores it. If you are in doubt, then always remember this rule: if a widget changes (because of user interactions, for example) it's stateful. However, if a widget reacts to change, the containing parent widget can still be stateless if it doesn't itself react to change. The following example shows how to use a \`StatelessWidget\`. A common \`StatelessWidget\` is the \`Text\` widget. If you look at the implementation of the \`Text\` widget you'll find that it subclasses \`StatelessWidget\`.

<!--?code-excerpt "lib/text_widget.dart (text-widget)" replace="/return const //g"?-->

\`\`\`dart Text( 'I like Flutter!', style: TextStyle(fontWeight: FontWeight.bold), ); \`\`\` As you can see, the \`Text\` Widget has no state information associated with it, it renders what is passed in its constructors and nothing more. But, what if you want to make "I Like Flutter" change dynamically, for example when clicking a \`FloatingActionButton\`? To achieve this, wrap the \`Text\` widget in a \`StatefulWidget\` and update it when the user clicks the button. For example:

<!--?code-excerpt "lib/text_widget.dart (stateful-widget)"?-->

\`\`\`dart import 'package:material\_ui/material\_ui.dart'; void main() { runApp(const SampleApp()); } class SampleApp extends StatelessWidget { const SampleApp({super.key}); // This widget is the root of your application. @override Widget build(BuildContext context) { return MaterialApp( title: 'Sample App', theme: ThemeData( colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), ), home: const SampleAppPage(), ); } } class SampleAppPage extends StatefulWidget { const SampleAppPage({super.key}); @override State createState() => \_SampleAppPageState(); } class \_SampleAppPageState extends State { // Default placeholder text. String textToShow = 'I Like Flutter'; void \_updateText() { setState(() { // Update the text. textToShow = 'Flutter is Awesome!'; }); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('Sample App')), body: Center(child: Text(textToShow)), floatingActionButton: FloatingActionButton( onPressed: \_updateText, tooltip: 'Update Text', child: const Icon(Icons.update), ), ); } } \`\`\` ### How do I lay out my widgets? Where is my XML layout file? In Android, you write layouts in XML, but in Flutter you write your layouts with a widget tree. The following example shows how to display a simple widget with padding:

<!--?code-excerpt "lib/layout.dart (simple-widget)"?-->

\`\`\`dart @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('Sample App')), body: Center( child: ElevatedButton( style: ElevatedButton.styleFrom( padding: const EdgeInsets.only(left: 20, right: 30), ), onPressed: () {}, child: const Text('Hello'), ), ), ); } \`\`\` You can view some of the layouts that Flutter has to offer in the \[widget catalog]\[]. ### How do I add or remove a component from my layout? In Android, you call \`addChild()\` or \`removeChild()\` on a parent to dynamically add or remove child views. In Flutter, because widgets are immutable there is no direct equivalent to \`addChild()\`. Instead, you can pass a function to the parent that returns a widget, and control that child's creation with a boolean flag. For example, here is how you can toggle between two widgets when you click on a \`FloatingActionButton\`:

<!--?code-excerpt "lib/layout.dart (toggle-widget)"?-->

\`\`\`dart import 'package:material\_ui/material\_ui.dart'; void main() { runApp(const SampleApp()); } class SampleApp extends StatelessWidget { const SampleApp({super.key}); // This widget is the root of your application. @override Widget build(BuildContext context) { return MaterialApp( title: 'Sample App', theme: ThemeData( colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), ), home: const SampleAppPage(), ); } } class SampleAppPage extends StatefulWidget { const SampleAppPage({super.key}); @override State createState() => \_SampleAppPageState(); } class \_SampleAppPageState extends State { // Default value for toggle. bool toggle = true; void \_toggle() { setState(() { toggle = !toggle; }); } Widget \_getToggleChild() { if (toggle) { return const Text('Toggle One'); } else { return ElevatedButton(onPressed: () {}, child: const Text('Toggle Two')); } } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('Sample App')), body: Center(child: \_getToggleChild()), floatingActionButton: FloatingActionButton( onPressed: \_toggle, tooltip: 'Update Text', child: const Icon(Icons.update), ), ); } } \`\`\` ### How do I animate a widget? In Android, you either create animations using XML, or call the \`animate()\` method on a view. In Flutter, animate widgets using the animation library by wrapping widgets inside an animated widget. In Flutter, use an \`AnimationController\` which is an \`Animation\` that can pause, seek, stop and reverse the animation. It requires a \`Ticker\` that signals when vsync happens, and produces a linear interpolation between 0 and 1 on each frame while it's running. You then create one or more \`Animation\`s and attach them to the controller. For example, you might use \`CurvedAnimation\` to implement an animation along an interpolated curve. In this sense, the controller is the "master" source of the animation progress and the \`CurvedAnimation\` computes the curve that replaces the controller's default linear motion. Like widgets, animations in Flutter work with composition. When building the widget tree you assign the \`Animation\` to an animated property of a widget, such as the opacity of a \`FadeTransition\`, and tell the controller to start the animation. The following example shows how to write a \`FadeTransition\` that fades the widget into a logo when you press the \`FloatingActionButton\`:

<!--?code-excerpt "lib/animation.dart"?-->

\`\`\`dart import 'package:material\_ui/material\_ui.dart'; void main() { runApp(const FadeAppTest()); } class FadeAppTest extends StatelessWidget { const FadeAppTest({super.key}); // This widget is the root of your application. @override Widget build(BuildContext context) { return MaterialApp( title: 'Fade Demo', theme: ThemeData( colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), ), home: const MyFadeTest(title: 'Fade Demo'), ); } } class MyFadeTest extends StatefulWidget { const MyFadeTest({super.key, required this.title}); final String title; @override State createState() => \_MyFadeTest(); } class \_MyFadeTest extends State with TickerProviderStateMixin { late AnimationController controller; late CurvedAnimation curve; @override void initState() { super.initState(); controller = AnimationController( duration: const Duration(milliseconds: 2000), vsync: this, ); curve = CurvedAnimation(parent: controller, curve: Curves.easeIn); } @override void dispose() { controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text(widget.title)), body: Center( child: FadeTransition( opacity: curve, child: const FlutterLogo(size: 100), ), ), floatingActionButton: FloatingActionButton( tooltip: 'Fade', onPressed: () { controller.forward(); }, child: const Icon(Icons.brush), ), ); } } \`\`\` For more information, see \[Animation & Motion widgets]\[], the \[Animations tutorial]\[], and the \[Animations overview]\[]. ### How do I use a Canvas to draw/paint? In Android, you would use the \`Canvas\` and \`Drawable\` to draw images and shapes to the screen. Flutter has a similar \`Canvas\` API as well, since it's based on the same low-level rendering engine, Skia. As a result, painting to a canvas in Flutter is a very familiar task for Android developers. Flutter has two classes that help you draw to the canvas: \`CustomPaint\` and \`CustomPainter\`, the latter of which implements your algorithm to draw to the canvas. To learn how to implement a signature painter in Flutter, see Collin's answer on \[Custom Paint]\[].

<!--?code-excerpt "lib/canvas.dart"?-->

\`\`\`dart import 'package:material\_ui/material\_ui.dart'; void main() => runApp(const MaterialApp(home: DemoApp())); class DemoApp extends StatelessWidget { const DemoApp({super.key}); @override Widget build(BuildContext context) => const Scaffold(body: Signature()); } class Signature extends StatefulWidget { const Signature({super.key}); @override SignatureState createState() => SignatureState(); } class SignatureState extends State { List \_points = \[]; @override Widget build(BuildContext context) { return GestureDetector( onPanUpdate: (details) { setState(() { RenderBox? referenceBox = context.findRenderObject() as RenderBox; Offset localPosition = referenceBox.globalToLocal( details.globalPosition, ); \_points = List.from(\_points)..add(localPosition); }); }, onPanEnd: (details) => \_points.add(null), child: CustomPaint( painter: SignaturePainter(\_points), size: Size.infinite, ), ); } } class SignaturePainter extends CustomPainter { SignaturePainter(this.points); final List points; @override void paint(Canvas canvas, Size size) { var paint = Paint() ..color = Colors.black ..strokeCap = StrokeCap.round ..strokeWidth = 5; for (int i = 0; i < points.length - 1; i++) { if (points\[i] != null && points\[i + 1] != null) { canvas.drawLine(points\[i]!, points\[i + 1]!, paint); } } } @override bool shouldRepaint(SignaturePainter oldDelegate) => oldDelegate.points != points; } \`\`\` \[Custom Paint]: https\://stackoverflow\.com/questions/46241071/create-signature-area-for-mobile-app-in-dart-flutter ### How do I build custom widgets? In Android, you typically subclass \`View\`, or use a pre-existing view, to override and implement methods that achieve the desired behavior. In Flutter, build a custom widget by \[composing]\[] smaller widgets (instead of extending them). It is somewhat similar to implementing a custom \`ViewGroup\` in Android, where all the building blocks are already existing, but you provide a different behavior—for example, custom layout logic. For example, how do you build a \`CustomButton\` that takes a label in the constructor? Create a CustomButton that composes a \`ElevatedButton\` with a label, rather than by extending \`ElevatedButton\`:

<!--?code-excerpt "lib/custom.dart (custom-button)"?-->

\`\`\`dart class CustomButton extends StatelessWidget { final String label; const CustomButton(this.label, {super.key}); @override Widget build(BuildContext context) { return ElevatedButton(onPressed: () {}, child: Text(label)); } } \`\`\` Then use \`CustomButton\`, just as you'd use any other Flutter widget:

<!--?code-excerpt "lib/custom.dart (use-custom-button)"?-->

\`\`\`dart @override Widget build(BuildContext context) { return const Center(child: CustomButton('Hello')); } \`\`\` ## Intents ### What is the equivalent of an Intent in Flutter? In Android, there are two main use cases for \`Intent\`s: navigating between Activities, and communicating with components. Flutter, on the other hand, does not have the concept of intents, although you can still start intents through native integrations, such as the \[\`android\_intent\_plus\`]\[] plugin. Flutter doesn't really have a direct equivalent to activities and fragments; rather, in Flutter you navigate between screens, using a \`Navigator\` and \`Route\`s, all within the same \`Activity\`. A \`Route\` is an abstraction for a "screen" or "page" of an app, and a \`Navigator\` is a widget that manages routes. A route roughly maps to an \`Activity\`, but it does not carry the same meaning. A navigator can push and pop routes to move from screen to screen. Navigators work like a stack on which you can \`push()\` new routes you want to navigate to, and from which you can \`pop()\` routes when you want to "go back". In Android, you declare your activities inside the app's \`AndroidManifest.xml\`. In Flutter, you have a couple options to navigate between pages: \* Specify a \`Map\` of route names. (using \`MaterialApp\`) \* Directly navigate to a route. (using \`WidgetsApp\`) The following example builds a Map.

<!--?code-excerpt "lib/intent.dart (map)"?-->

\`\`\`dart void main() { runApp( MaterialApp( home: const MyAppHome(), // Becomes the route named '/'. routes: { '/a': (context) => const MyPage(title: 'page A'), '/b': (context) => const MyPage(title: 'page B'), '/c': (context) => const MyPage(title: 'page C'), }, ), ); } \`\`\` Navigate to a route by \`push\`ing its name to the \`Navigator\`.

<!--?code-excerpt "lib/intent.dart (push)"?-->

\`\`\`dart Navigator.of(context).pushNamed('/b'); \`\`\` The other popular use-case for \`Intent\`s is to call external components such as a Camera or File picker. For this, you would need to create a native platform integration (or use an \[existing plugin]\[]). To learn how to build a native platform integration, see \[developing packages and plugins]\[]. ### How do I handle incoming intents from external applications in Flutter? Flutter can handle incoming intents from Android by directly talking to the Android layer and requesting the data that was shared. The following example registers a text share intent filter on the native activity that runs our Flutter code, so other apps can share text with our Flutter app. The basic flow implies that we first handle the shared text data on the Android native side (in our \`Activity\`), and then wait until Flutter requests for the data to provide it using a \`MethodChannel\`. First, register the intent filter for all intents in \`AndroidManifest.xml\`: \`\`\`xml \`\`\` Then in \`MainActivity\`, handle the intent, extract the text that was shared from the intent, and hold onto it. When Flutter is ready to process, it requests the data using a platform channel, and it's sent across from the native side: \`\`\`java package com.example.shared; import android.content.Intent; import android.os.Bundle; import androidx.annotation.NonNull; import io.flutter.plugin.common.MethodChannel; import io.flutter.embedding.android.FlutterActivity; import io.flutter.embedding.engine.FlutterEngine; import io.flutter.plugins.GeneratedPluginRegistrant; public class MainActivity extends FlutterActivity { private String sharedText; private static final String CHANNEL = "app.channel.shared.data"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Intent intent = getIntent(); String action = intent.getAction(); String type = intent.getType(); if (Intent.ACTION\_SEND.equals(action) && type != null) { if ("text/plain".equals(type)) { handleSendText(intent); // Handle text being sent } } } @Override public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) { GeneratedPluginRegistrant.registerWith(flutterEngine); new MethodChannel(flutterEngine.getDartExecutor().getBinaryMessenger(), CHANNEL) .setMethodCallHandler( (call, result) -> { if (call.method.contentEquals("getSharedText")) { result.success(sharedText); sharedText = null; } } ); } void handleSendText(Intent intent) { sharedText = intent.getStringExtra(Intent.EXTRA\_TEXT); } } \`\`\` Finally, request the data from the Flutter side when the widget is rendered:

<!--?code-excerpt "lib/request_data.dart"?-->

\`\`\`dart import 'package:flutter/services.dart'; import 'package:material\_ui/material\_ui.dart'; void main() { runApp(const SampleApp()); } class SampleApp extends StatelessWidget { const SampleApp({super.key}); // This widget is the root of your application. @override Widget build(BuildContext context) { return MaterialApp( title: 'Sample Shared App Handler', theme: ThemeData( colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), ), home: const SampleAppPage(), ); } } class SampleAppPage extends StatefulWidget { const SampleAppPage({super.key}); @override State createState() => \_SampleAppPageState(); } class \_SampleAppPageState extends State { static const platform = MethodChannel('app.channel.shared.data'); String dataShared = 'No data'; @override void initState() { super.initState(); getSharedText(); } @override Widget build(BuildContext context) { return Scaffold(body: Center(child: Text(dataShared))); } Future getSharedText() async { var sharedData = await platform.invokeMethod('getSharedText'); if (sharedData != null) { setState(() { dataShared = sharedData as String; }); } } } \`\`\` ### What is the equivalent of startActivityForResult()? The \`Navigator\` class handles routing in Flutter and is used to get a result back from a route that you have pushed on the stack. This is done by \`await\`ing on the \`Future\` returned by \`push()\`. For example, to start a location route that lets the user select their location, you could do the following:

<!--?code-excerpt "lib/intent.dart (push-await)"?-->

\`\`\`dart Object? coordinates = await Navigator.of(context).pushNamed('/location'); \`\`\` And then, inside your location route, once the user has selected their location you can \`pop\` the stack with the result:

<!--?code-excerpt "lib/intent.dart (pop)"?-->

\`\`\`dart Navigator.of(context).pop({'lat': 43.821757, 'long': -79.226392}); \`\`\` ## Async UI ### What is the equivalent of runOnUiThread() in Flutter? Dart has a single-threaded execution model, with support for \`Isolate\`s (a way to run Dart code on another thread), an event loop, and asynchronous programming. Unless you spawn an \`Isolate\`, your Dart code runs in the main UI thread and is driven by an event loop. Flutter's event loop is equivalent to Android's main \`Looper\`—that is, the \`Looper\` that is attached to the main thread. Dart's single-threaded model doesn't mean you need to run everything as a blocking operation that causes the UI to freeze. Unlike Android, which requires you to keep the main thread free at all times, in Flutter, use the asynchronous facilities that the Dart language provides, such as \`async\`/\`await\`, to perform asynchronous work. You might be familiar with the \`async\`/\`await\` paradigm if you've used it in C#, Javascript, or if you have used Kotlin's coroutines. For example, you can run network code without causing the UI to hang by using \`async\`/\`await\` and letting Dart do the heavy lifting:

<!--?code-excerpt "lib/async.dart (load-data)"?-->

\`\`\`dart Future loadData() async { final dataURL = Uri.parse('https\://jsonplaceholder.typicode.com/posts'); final response = await http.get(dataURL); setState(() { widgets = (jsonDecode(response.body) as List) .cast>(); }); } \`\`\` Once the \`await\`ed network call is done, update the UI by calling \`setState()\`, which triggers a rebuild of the widget subtree and updates the data. The following example loads data asynchronously and displays it in a \`ListView\`:

<!--?code-excerpt "lib/async.dart"?-->

\`\`\`dart import 'dart:convert'; import 'package:http/http.dart' as http; import 'package:material\_ui/material\_ui.dart'; void main() { runApp(const SampleApp()); } class SampleApp extends StatelessWidget { const SampleApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( title: 'Sample App', theme: ThemeData( colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), ), home: const SampleAppPage(), ); } } class SampleAppPage extends StatefulWidget { const SampleAppPage({super.key}); @override State createState() => \_SampleAppPageState(); } class \_SampleAppPageState extends State { List> widgets = \[]; @override void initState() { super.initState(); loadData(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('Sample App')), body: ListView\.builder( itemCount: widgets.length, itemBuilder: (context, position) { return getRow(position); }, ), ); } Widget getRow(int i) { return Padding( padding: const EdgeInsets.all(10), child: Text("Row ${widgets\[i]\["title"]}"), ); } Future loadData() async { final dataURL = Uri.parse('https\://jsonplaceholder.typicode.com/posts'); final response = await http.get(dataURL); setState(() { widgets = (jsonDecode(response.body) as List) .cast>(); }); } } \`\`\` Refer to the next section for more information on doing work in the background, and how Flutter differs from Android. ### How do you move work to a background thread? In Android, when you want to access a network resource you would typically move to a background thread and do the work, as to not block the main thread, and avoid ANRs. For example, you might be using an \`AsyncTask\`, a \`LiveData\`, an \`IntentService\`, a \`JobScheduler\` job, or an RxJava pipeline with a scheduler that works on background threads. Since Flutter is single threaded and runs an event loop (like Node.js), you don't have to worry about thread management or spawning background threads. If you're doing I/O-bound work, such as disk access or a network call, then you can safely use \`async\`/\`await\` and you're all set. If, on the other hand, you need to do computationally intensive work that keeps the CPU busy, you want to move it to an \`Isolate\` to avoid blocking the event loop, like you would keep \_any\_ sort of work out of the main thread in Android. For I/O-bound work, declare the function as an \`async\` function, and \`await\` on long-running tasks inside the function:

<!--?code-excerpt "lib/async.dart (load-data)"?-->

\`\`\`dart Future loadData() async { final dataURL = Uri.parse('https\://jsonplaceholder.typicode.com/posts'); final response = await http.get(dataURL); setState(() { widgets = (jsonDecode(response.body) as List) .cast>(); }); } \`\`\` This is how you would typically do network or database calls, which are both I/O operations. On Android, when you extend \`AsyncTask\`, you typically override 3 methods, \`onPreExecute()\`, \`doInBackground()\` and \`onPostExecute()\`. There is no equivalent in Flutter, since you \`await\` on a long-running function, and Dart's event loop takes care of the rest. However, there are times when you might be processing a large amount of data and your UI hangs. In Flutter, use \`Isolate\`s to take advantage of multiple CPU cores to do long-running or computationally intensive tasks. Isolates are separate execution threads that do not share any memory with the main execution memory heap. This means you can't access variables from the main thread, or update your UI by calling \`setState()\`. Unlike Android threads, Isolates are true to their name, and cannot share memory (in the form of static fields, for example). The following example shows, in a simple isolate, how to share data back to the main thread to update the UI.

<!--?code-excerpt "lib/isolates.dart (load-data)"?-->

\`\`\`dart Future loadData() async { ReceivePort receivePort = ReceivePort(); await Isolate.spawn(dataLoader, receivePort.sendPort); // The 'echo' isolate sends its SendPort as the first message. SendPort sendPort = await receivePort.first as SendPort; final msg = await sendReceive( sendPort, 'https\://jsonplaceholder.typicode.com/posts', ) as List; final posts = msg.cast>(); setState(() { widgets = posts; }); } // The entry point for the isolate. static Future dataLoader(SendPort sendPort) async { // Open the ReceivePort for incoming messages. ReceivePort port = ReceivePort(); // Notify any other isolates what port this isolate listens to. sendPort.send(port.sendPort); await for (var msg in port) { String dataUrl = msg\[0] as String; SendPort replyTo = msg\[1] as SendPort; http.Response response = await http.get(Uri.parse(dataUrl)); // Lots of JSON to parse replyTo.send(jsonDecode(response.body)); } } Future sendReceive(SendPort port, Object? msg) { ReceivePort response = ReceivePort(); port.send(\[msg, response.sendPort]); return response.first; } \`\`\` Here, \`dataLoader()\` is the \`Isolate\` that runs in its own separate execution thread. In the isolate you can perform more CPU intensive processing (parsing a big JSON, for example), or perform computationally intensive math, such as encryption or signal processing. You can run the full example below:

<!--?code-excerpt "lib/isolates.dart"?-->

\`\`\`dart import 'dart:async'; import 'dart:convert'; import 'dart:isolate'; import 'package:http/http.dart' as http; import 'package:material\_ui/material\_ui.dart'; void main() { runApp(const SampleApp()); } class SampleApp extends StatelessWidget { const SampleApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( title: 'Sample App', theme: ThemeData( colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), ), home: const SampleAppPage(), ); } } class SampleAppPage extends StatefulWidget { const SampleAppPage({super.key}); @override State createState() => \_SampleAppPageState(); } class \_SampleAppPageState extends State { List> widgets = \[]; @override void initState() { super.initState(); loadData(); } Widget getBody() { bool showLoadingDialog = widgets.isEmpty; if (showLoadingDialog) { return getProgressDialog(); } else { return getListView(); } } Widget getProgressDialog() { return const Center(child: CircularProgressIndicator()); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('Sample App')), body: getBody(), ); } ListView getListView() { return ListView\.builder( itemCount: widgets.length, itemBuilder: (context, position) { return getRow(position); }, ); } Widget getRow(int i) { return Padding( padding: const EdgeInsets.all(10), child: Text("Row ${widgets\[i]\["title"]}"), ); } Future loadData() async { ReceivePort receivePort = ReceivePort(); await Isolate.spawn(dataLoader, receivePort.sendPort); // The 'echo' isolate sends its SendPort as the first message. SendPort sendPort = await receivePort.first as SendPort; final msg = await sendReceive( sendPort, 'https\://jsonplaceholder.typicode.com/posts', ) as List; final posts = msg.cast>(); setState(() { widgets = posts; }); } // The entry point for the isolate. static Future dataLoader(SendPort sendPort) async { // Open the ReceivePort for incoming messages. ReceivePort port = ReceivePort(); // Notify any other isolates what port this isolate listens to. sendPort.send(port.sendPort); await for (var msg in port) { String dataUrl = msg\[0] as String; SendPort replyTo = msg\[1] as SendPort; http.Response response = await http.get(Uri.parse(dataUrl)); // Lots of JSON to parse replyTo.send(jsonDecode(response.body)); } } Future sendReceive(SendPort port, Object? msg) { ReceivePort response = ReceivePort(); port.send(\[msg, response.sendPort]); return response.first; } } \`\`\` ### What is the equivalent of OkHttp on Flutter? Making a network call in Flutter is easy when you use the popular \[\`http\` package]\[]. While the http package doesn't have every feature found in OkHttp, it abstracts away much of the networking that you would normally implement yourself, making it a simple way to make network calls. To add the \`http\` package as a dependency, run \`flutter pub add\`: \`\`\`console $ flutter pub add http \`\`\` To make a network call, call \`await\` on the \`async\` function \`http.get()\`:

<!--?code-excerpt "lib/network.dart"?-->

\`\`\`dart import 'dart:developer' as developer; import 'package:http/http.dart' as http; Future loadData() async { var dataURL = Uri.parse('https\://jsonplaceholder.typicode.com/posts'); http.Response response = await http.get(dataURL); developer.log(response.body); } \`\`\` ### How do I show the progress for a long-running task? In Android you would typically show a \`ProgressBar\` view in your UI while executing a long-running task on a background thread. In Flutter, use a \`ProgressIndicator\` widget. Show the progress programmatically by controlling when it's rendered through a boolean flag. Tell Flutter to update its state before your long-running task starts, and hide it after it ends. In the following example, the build function is separated into three different functions. If \`showLoadingDialog\` is \`true\` (when \`widgets.isEmpty\`), then render the \`ProgressIndicator\`. Otherwise, render the \`ListView\` with the data returned from a network call.

<!--?code-excerpt "lib/progress.dart"?-->

\`\`\`dart import 'dart:convert'; import 'package:http/http.dart' as http; import 'package:material\_ui/material\_ui.dart'; void main() { runApp(const SampleApp()); } class SampleApp extends StatelessWidget { const SampleApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( title: 'Sample App', theme: ThemeData( colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), ), home: const SampleAppPage(), ); } } class SampleAppPage extends StatefulWidget { const SampleAppPage({super.key}); @override State createState() => \_SampleAppPageState(); } class \_SampleAppPageState extends State { List> widgets = \[]; @override void initState() { super.initState(); loadData(); } Widget getBody() { bool showLoadingDialog = widgets.isEmpty; if (showLoadingDialog) { return getProgressDialog(); } else { return getListView(); } } Widget getProgressDialog() { return const Center(child: CircularProgressIndicator()); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('Sample App')), body: getBody(), ); } ListView getListView() { return ListView\.builder( itemCount: widgets.length, itemBuilder: (context, position) { return getRow(position); }, ); } Widget getRow(int i) { return Padding( padding: const EdgeInsets.all(10), child: Text("Row ${widgets\[i]\["title"]}"), ); } Future loadData() async { final dataURL = Uri.parse('https\://jsonplaceholder.typicode.com/posts'); final response = await http.get(dataURL); setState(() { widgets = (jsonDecode(response.body) as List) .cast>(); }); } } \`\`\` ## Project structure & resources ### Where do I store my resolution-dependent image files? While Android treats resources and assets as distinct items, Flutter apps have only assets. All resources that would live in the \`res/drawable-\*\` folders on Android, are placed in an assets folder for Flutter. Flutter follows a simple density-based format like iOS. Assets might be \`1.0x\`, \`2.0x\`, \`3.0x\`, or any other multiplier. Flutter doesn't have \`dp\`s but there are logical pixels, which are basically the same as device-independent pixels. Flutter's \[\`devicePixelRatio\`]\[] expresses the ratio of physical pixels in a single logical pixel. The equivalent to Android's density buckets are: Android density qualifier | Flutter pixel ratio --- | --- \`ldpi\` | \`0.75x\` \`mdpi\` | \`1.0x\` \`hdpi\` | \`1.5x\` \`xhdpi\` | \`2.0x\` \`xxhdpi\` | \`3.0x\` \`xxxhdpi\` | \`4.0x\` Assets are located in any arbitrary folder—Flutter has no predefined folder structure. You declare the assets (with location) in the \`pubspec.yaml\` file, and Flutter picks them up. Assets stored in the native asset folder are accessed on the native side using Android's \`AssetManager\`: \`\`\`kotlin val flutterAssetStream = assetManager.open("flutter\_assets/assets/my\_flutter\_asset.png") \`\`\` Flutter can't access native resources or assets. To add a new image asset called \`my\_icon.png\` to our Flutter project, for example, and deciding that it should live in a folder we arbitrarily called \`images\`, you would put the base image (1.0x) in the \`images\` folder, and all the other variants in sub-folders called with the appropriate ratio multiplier: \`\`\`plaintext images/my\_icon.png // Base: 1.0x image images/2.0x/my\_icon.png // 2.0x image images/3.0x/my\_icon.png // 3.0x image \`\`\` Next, you'll need to declare these images in your \`pubspec.yaml\` file: \`\`\`yaml assets: - images/my\_icon.png \`\`\` You can then access your images using \`AssetImage\`:

<!--?code-excerpt "lib/images.dart (asset-image)"?-->

\`\`\`dart AssetImage('images/my\_icon.png'), \`\`\` or directly in an \`Image\` widget:

<!--?code-excerpt "lib/images.dart (image-asset)"?-->

\`\`\`dart @override Widget build(BuildContext context) { return Image.asset('images/my\_image.png'); } \`\`\` ### Where do I store strings? How do I handle localization? Flutter currently doesn't have a dedicated resources-like system for strings. The best and recommended practice is to hold your strings in a \`.arb\` file as key-value pairs For example:

<!--?code-excerpt "lib/arb_examples.arb"?-->

\`\`\`json { "@@locale": "en", "hello":"Hello {userName}", "@hello":{ "description":"A message with a single parameter", "placeholders":{ "userName":{ "type":"String", "example":"Bob" } } } } \`\`\` Then in your code, you can access your strings as such:

<!--?code-excerpt "lib/localization_examples.dart (access-string)"?-->

\`\`\`dart Text(AppLocalizations.of(context)!.hello('John')); \`\`\` See \[Internationalizing Flutter apps]\[] for more information on this. ### What is the equivalent of a Gradle file? How do I add dependencies? In Android, you add dependencies by adding to your Gradle build script. Flutter uses Dart's own build system, and the Pub package manager. The tools delegate the building of the native Android and iOS wrapper apps to the respective build systems. While there are Gradle files under the \`android\` folder in your Flutter project, only use these if you are adding native dependencies needed for per-platform integration. In general, use \`pubspec.yaml\` to declare external dependencies to use in Flutter. A good place to find Flutter packages is \[pub.dev]\[]. ## Activities and fragments ### What are the equivalent of activities and fragments in Flutter? In Android, an \`Activity\` represents a single focused thing the user can do. A \`Fragment\` represents a behavior or a portion of user interface. Fragments are a way to modularize your code, compose sophisticated user interfaces for larger screens, and help scale your application UI. In Flutter, both of these concepts fall under the umbrella of \`Widget\`s. To learn more about the UI for building Activities and Fragments, see the community-contributed article, \[Flutter for Android Developers: How to design Activity UI in Flutter]\[]. As mentioned in the \[Intents]\[] section, screens in Flutter are represented by \`Widget\`s since everything is a widget in Flutter. Use a \`Navigator\` to move between different \`Route\`s that represent different screens or pages, or perhaps different states or renderings of the same data. ### How do I listen to Android activity lifecycle events? In Android, you can override methods from the \`Activity\` to capture lifecycle methods for the activity itself, or register \`ActivityLifecycleCallbacks\` on the \`Application\`. In Flutter, you have neither concept, but you can instead listen to lifecycle events by hooking into the \`WidgetsBinding\` observer and listening to the \`didChangeAppLifecycleState()\` change event. The observable lifecycle events are: \* \`detached\` — The application is still hosted on a flutter engine but is detached from any host views. \* \`inactive\` — The application is in an inactive state and is not receiving user input. \* \`paused\` — The application is not currently visible to the user, not responding to user input, and running in the background. This is equivalent to \`onPause()\` in Android. \* \`resumed\` — The application is visible and responding to user input. This is equivalent to \`onPostResume()\` in Android. For more details on the meaning of these states, see the \[\`AppLifecycleStatus\` documentation]\[]. As you might have noticed, only a small minority of the Activity lifecycle events are available; while \`FlutterActivity\` does capture almost all the activity lifecycle events internally and send them over to the Flutter engine, they're mostly shielded away from you. Flutter takes care of starting and stopping the engine for you, and there is little reason for needing to observe the activity lifecycle on the Flutter side in most cases. If you need to observe the lifecycle to acquire or release any native resources, you should likely be doing it from the native side, at any rate. Here's an example of how to observe the lifecycle status of the containing activity:

<!--?code-excerpt "lib/lifecycle.dart"?-->

\`\`\`dart import 'package:flutter/widgets.dart'; class LifecycleWatcher extends StatefulWidget { const LifecycleWatcher({super.key}); @override State createState() => \_LifecycleWatcherState(); } class \_LifecycleWatcherState extends State with WidgetsBindingObserver { AppLifecycleState? \_lastLifecycleState; @override void initState() { super.initState(); WidgetsBinding.instance.addObserver(this); } @override void dispose() { WidgetsBinding.instance.removeObserver(this); super.dispose(); } @override void didChangeAppLifecycleState(AppLifecycleState state) { setState(() { \_lastLifecycleState = state; }); } @override Widget build(BuildContext context) { if (\_lastLifecycleState == null) { return const Text( 'This widget has not observed any lifecycle changes.', textDirection: TextDirection.ltr, ); } return Text( 'The most recent lifecycle state this widget observed was: $\_lastLifecycleState.', textDirection: TextDirection.ltr, ); } } void main() { runApp(const Center(child: LifecycleWatcher())); } \`\`\` ## Layouts ### What is the equivalent of a LinearLayout? In Android, a LinearLayout is used to lay your widgets out linearly—either horizontally or vertically. In Flutter, use the Row or Column widgets to achieve the same result. If you notice the two code samples are identical with the exception of the "Row" and "Column" widget. The children are the same and this feature can be exploited to develop rich layouts that can change overtime with the same children.

<!--?code-excerpt "lib/layout.dart (row)"?-->

\`\`\`dart @override Widget build(BuildContext context) { return const Row( mainAxisAlignment: MainAxisAlignment.center, children: \[ Text('Row One'), Text('Row Two'), Text('Row Three'), Text('Row Four'), ], ); } \`\`\`

<!--?code-excerpt "lib/layout.dart (column)"?-->

\`\`\`dart @override Widget build(BuildContext context) { return const Column( mainAxisAlignment: MainAxisAlignment.center, children: \[ Text('Column One'), Text('Column Two'), Text('Column Three'), Text('Column Four'), ], ); } \`\`\` To learn more about building linear layouts, see the community-contributed Medium article \[Flutter for Android Developers: How to design LinearLayout in Flutter]\[]. ### What is the equivalent of a RelativeLayout? A RelativeLayout lays your widgets out relative to each other. In Flutter, there are a few ways to achieve the same result. You can achieve the result of a RelativeLayout by using a combination of Column, Row, and Stack widgets. You can specify rules for the widgets constructors on how the children are laid out relative to the parent. For a good example of building a RelativeLayout in Flutter, see Collin's answer on \[StackOverflow]\[]. ### What is the equivalent of a ScrollView? In Android, use a ScrollView to lay out your widgets—if the user's device has a smaller screen than your content, it scrolls. In Flutter, the easiest way to do this is using the ListView widget. This might seem like overkill coming from Android, but in Flutter a ListView widget is both a ScrollView and an Android ListView.

<!--?code-excerpt "lib/layout.dart (list-view)"?-->

\`\`\`dart @override Widget build(BuildContext context) { return ListView( children: const \[ Text('Row One'), Text('Row Two'), Text('Row Three'), Text('Row Four'), ], ); } \`\`\` ### How do I handle landscape transitions in Flutter? FlutterView handles the config change if AndroidManifest.xml contains: \`\`\`yaml android:configChanges="orientation|screenSize" \`\`\` ## Gesture detection and touch event handling ### How do I add an onClick listener to a widget in Flutter? In Android, you can attach onClick to views such as button by calling the method 'setOnClickListener'. In Flutter there are two ways of adding touch listeners: 1. If the Widget supports event detection, pass a function to it and handle it in the function. For example, the ElevatedButton has an \`onPressed\` parameter:

<!--?code-excerpt "lib/events.dart (on-pressed)"?-->

\`\`\`dart @override Widget build(BuildContext context) { return ElevatedButton( onPressed: () { developer.log('click'); }, child: const Text('Button'), ); } \`\`\` 2. If the Widget doesn't support event detection, wrap the widget in a GestureDetector and pass a function to the \`onTap\` parameter.

<!--?code-excerpt "lib/events.dart (on-tap)"?-->

\`\`\`dart class SampleTapApp extends StatelessWidget { const SampleTapApp({super.key}); @override Widget build(BuildContext context) { return Scaffold( body: Center( child: GestureDetector( onTap: () { developer.log('tap'); }, child: const FlutterLogo(size: 200), ), ), ); } } \`\`\` ### How do I handle other gestures on widgets? Using the GestureDetector, you can listen to a wide range of Gestures such as: \* Tap \* \`onTapDown\` - A pointer that might cause a tap has contacted the screen at a particular location. \* \`onTapUp\` - A pointer that triggers a tap has stopped contacting the screen at a particular location. \* \`onTap\` - A tap has occurred. \* \`onTapCancel\` - The pointer that previously triggered the \`onTapDown\` won't cause a tap. \* Double tap \* \`onDoubleTap\` - The user tapped the screen at the same location twice in quick succession. \* Long press \* \`onLongPress\` - A pointer has remained in contact with the screen at the same location for a long period of time. \* Vertical drag \* \`onVerticalDragStart\` - A pointer has contacted the screen and might begin to move vertically. \* \`onVerticalDragUpdate\` - A pointer in contact with the screen has moved further in the vertical direction. \* \`onVerticalDragEnd\` - A pointer that was previously in contact with the screen and moving vertically is no longer in contact with the screen and was moving at a specific velocity when it stopped contacting the screen. \* Horizontal drag \* \`onHorizontalDragStart\` - A pointer has contacted the screen and might begin to move horizontally. \* \`onHorizontalDragUpdate\` - A pointer in contact with the screen has moved further in the horizontal direction. \* \`onHorizontalDragEnd\` - A pointer that was previously in contact with the screen and moving horizontally is no longer in contact with the screen and was moving at a specific velocity when it stopped contacting the screen. The following example shows a \`GestureDetector\` that rotates the Flutter logo on a double tap:

<!--?code-excerpt "lib/events.dart (sample-app)"?-->

\`\`\`dart class SampleApp extends StatefulWidget { const SampleApp({super.key}); @override State createState() => \_SampleAppState(); } class \_SampleAppState extends State with SingleTickerProviderStateMixin { late AnimationController controller; late CurvedAnimation curve; @override void initState() { super.initState(); controller = AnimationController( vsync: this, duration: const Duration(milliseconds: 2000), ); curve = CurvedAnimation(parent: controller, curve: Curves.easeIn); } @override void dispose() { controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( body: Center( child: GestureDetector( onDoubleTap: () { if (controller.isCompleted) { controller.reverse(); } else { controller.forward(); } }, child: RotationTransition( turns: curve, child: const FlutterLogo(size: 200), ), ), ), ); } } \`\`\` ## Listviews & adapters ### What is the alternative to a ListView in Flutter? The equivalent to a ListView in Flutter is … a ListView! In an Android ListView, you create an adapter and pass it into the ListView, which renders each row with what your adapter returns. However, you have to make sure you recycle your rows, otherwise, you get all sorts of crazy visual glitches and memory issues. Due to Flutter's immutable widget pattern, you pass a list of widgets to your ListView, and Flutter takes care of making sure that scrolling is fast and smooth.

<!--?code-excerpt "lib/listview.dart"?-->

\`\`\`dart import 'package:material\_ui/material\_ui.dart'; void main() { runApp(const SampleApp()); } class SampleApp extends StatelessWidget { const SampleApp({super.key}); // This widget is the root of your application. @override Widget build(BuildContext context) { return MaterialApp( title: 'Sample App', theme: ThemeData( colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), ), home: const SampleAppPage(), ); } } class SampleAppPage extends StatefulWidget { const SampleAppPage({super.key}); @override State createState() => \_SampleAppPageState(); } class \_SampleAppPageState extends State { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('Sample App')), body: ListView(children: \_getListData()), ); } List \_getListData() { List widgets = \[]; for (int i = 0; i < 100; i++) { widgets.add( Padding(padding: const EdgeInsets.all(10), child: Text('Row $i')), ); } return widgets; } } \`\`\` ### How do I know which list item is clicked on? In Android, the ListView has a method to find out which item was clicked, 'onItemClickListener'. In Flutter, use the touch handling provided by the passed-in widgets.

<!--?code-excerpt "lib/list_item_tapped.dart"?-->

\`\`\`dart import 'dart:developer' as developer; import 'package:material\_ui/material\_ui.dart'; void main() { runApp(const SampleApp()); } class SampleApp extends StatelessWidget { const SampleApp({super.key}); // This widget is the root of your application. @override Widget build(BuildContext context) { return MaterialApp( title: 'Sample App', theme: ThemeData( colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), ), home: const SampleAppPage(), ); } } class SampleAppPage extends StatefulWidget { const SampleAppPage({super.key}); @override State createState() => \_SampleAppPageState(); } class \_SampleAppPageState extends State { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('Sample App')), body: ListView(children: \_getListData()), ); } List \_getListData() { List widgets = \[]; for (int i = 0; i < 100; i++) { widgets.add( GestureDetector( onTap: () { developer.log('row tapped'); }, child: Padding( padding: const EdgeInsets.all(10), child: Text('Row $i'), ), ), ); } return widgets; } } \`\`\` ### How do I update ListView's dynamically? On Android, you update the adapter and call \`notifyDataSetChanged\`. In Flutter, if you were to update the list of widgets inside a \`setState()\`, you would quickly see that your data did not change visually. This is because when \`setState()\` is called, the Flutter rendering engine looks at the widget tree to see if anything has changed. When it gets to your \`ListView\`, it performs a \`==\` check, and determines that the two \`ListView\`s are the same. Nothing has changed, so no update is required. For a simple way to update your \`ListView\`, create a new \`List\` inside of \`setState()\`, and copy the data from the old list to the new list. While this approach is simple, it is not recommended for large data sets, as shown in the next example.

<!--?code-excerpt "lib/listview_dynamic.dart"?-->

\`\`\`dart import 'dart:developer' as developer; import 'package:material\_ui/material\_ui.dart'; void main() { runApp(const SampleApp()); } class SampleApp extends StatelessWidget { const SampleApp({super.key}); // This widget is the root of your application. @override Widget build(BuildContext context) { return MaterialApp( title: 'Sample App', theme: ThemeData( colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), ), home: const SampleAppPage(), ); } } class SampleAppPage extends StatefulWidget { const SampleAppPage({super.key}); @override State createState() => \_SampleAppPageState(); } class \_SampleAppPageState extends State { List widgets = \[]; @override void initState() { super.initState(); for (int i = 0; i < 100; i++) { widgets.add(getRow(i)); } } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('Sample App')), body: ListView(children: widgets), ); } Widget getRow(int i) { return GestureDetector( onTap: () { setState(() { widgets = List.from(widgets); widgets.add(getRow(widgets.length)); developer.log('row $i'); }); }, child: Padding(padding: const EdgeInsets.all(10), child: Text('Row $i')), ); } } \`\`\` The recommended, efficient, and effective way to build a list uses a \`ListView\.Builder\`. This method is great when you have a dynamic \`List\` or a \`List\` with very large amounts of data. This is essentially the equivalent of RecyclerView on Android, which automatically recycles list elements for you:

<!--?code-excerpt "lib/listview_builder.dart"?-->

\`\`\`dart import 'dart:developer' as developer; import 'package:material\_ui/material\_ui.dart'; void main() { runApp(const SampleApp()); } class SampleApp extends StatelessWidget { const SampleApp({super.key}); // This widget is the root of your application. @override Widget build(BuildContext context) { return MaterialApp( title: 'Sample App', theme: ThemeData( colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), ), home: const SampleAppPage(), ); } } class SampleAppPage extends StatefulWidget { const SampleAppPage({super.key}); @override State createState() => \_SampleAppPageState(); } class \_SampleAppPageState extends State { List widgets = \[]; @override void initState() { super.initState(); for (int i = 0; i < 100; i++) { widgets.add(getRow(i)); } } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('Sample App')), body: ListView\.builder( itemCount: widgets.length, itemBuilder: (context, position) { return getRow(position); }, ), ); } Widget getRow(int i) { return GestureDetector( onTap: () { setState(() { widgets.add(getRow(widgets.length)); developer.log('row $i'); }); }, child: Padding(padding: const EdgeInsets.all(10), child: Text('Row $i')), ); } } \`\`\` Instead of creating a "ListView", create a \`ListView\.builder\` that takes two key parameters: the initial length of the list, and an \`ItemBuilder\` function. The \`ItemBuilder\` function is similar to the \`getView\` function in an Android adapter; it takes a position, and returns the row you want rendered at that position. Finally, but most importantly, notice that the \`onTap()\` function doesn't recreate the list anymore, but instead \`.add\`s to it. ## Working with text ### How do I set custom fonts on my Text widgets? In Android SDK (as of Android O), you create a Font resource file and pass it into the FontFamily param for your TextView. In Flutter, place the font file in a folder and reference it in the \`pubspec.yaml\` file, similar to how you import images. \`\`\`yaml fonts: - family: MyCustomFont fonts: - asset: fonts/MyCustomFont.ttf - style: italic \`\`\` Then assign the font to your \`Text\` widget:

<!--?code-excerpt "lib/text.dart (custom-font)"?-->

\`\`\`dart @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('Sample App')), body: const Center( child: Text( 'This is a custom font text', style: TextStyle(fontFamily: 'MyCustomFont'), ), ), ); } \`\`\` ### How do I style my Text widgets? Along with fonts, you can customize other styling elements on a \`Text\` widget. The style parameter of a \`Text\` widget takes a \`TextStyle\` object, where you can customize many parameters, such as: \* color \* decoration \* decorationColor \* decorationStyle \* fontFamily \* fontSize \* fontStyle \* fontWeight \* hashCode \* height \* inherit \* letterSpacing \* textBaseline \* wordSpacing ## Form input For more information on using Forms, see \[Retrieve the value of a text field]\[]. ### What is the equivalent of a "hint" on an Input? In Flutter, you can easily show a "hint" or a placeholder text for your input by adding an InputDecoration object to the decoration constructor parameter for the Text Widget.

<!--?code-excerpt "lib/form.dart (input-hint)" replace="/return const //g;/;//g"?-->

\`\`\`dart Center( child: TextField(decoration: InputDecoration(hintText: 'This is a hint')), ) \`\`\` ### How do I show validation errors? Just as you would with a "hint", pass an InputDecoration object to the decoration constructor for the Text widget. However, you don't want to start off by showing an error. Instead, when the user has entered invalid data, update the state, and pass a new \`InputDecoration\` object.

<!--?code-excerpt "lib/validation_errors.dart"?-->

\`\`\`dart import 'package:material\_ui/material\_ui.dart'; void main() { runApp(const SampleApp()); } class SampleApp extends StatelessWidget { const SampleApp({super.key}); // This widget is the root of your application. @override Widget build(BuildContext context) { return MaterialApp( title: 'Sample App', theme: ThemeData( colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), ), home: const SampleAppPage(), ); } } class SampleAppPage extends StatefulWidget { const SampleAppPage({super.key}); @override State createState() => \_SampleAppPageState(); } class \_SampleAppPageState extends State { String? \_errorText; @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('Sample App')), body: Center( child: TextField( onSubmitted: (text) { setState(() { if (!isEmail(text)) { \_errorText = 'Error: This is not an email'; } else { \_errorText = null; } }); }, decoration: InputDecoration( hintText: 'This is a hint', errorText: \_getErrorText(), ), ), ), ); } String? \_getErrorText() { return \_errorText; } bool isEmail(String em) { String emailRegexp = r'^((\[^<>()\[\\]\\\\.,;:\s@\\"]+(\\.\[^<>()\[\\]\\\\.,;:\s@\\"]+)\*)|' r'(\\".+\\"))@((\\\[\[0-9]{1,3}\\.\[0-9]{1,3}\\.\[0-9]{1,3}\\.\[0-9]{1,3}\\])|' r'((\[a-zA-Z\\-0-9]+\\.)+\[a-zA-Z]{2,}))$'; RegExp regExp = RegExp(emailRegexp); return regExp.hasMatch(em); } } \`\`\` ## Flutter plugins ### How do I access the GPS sensor? Use the \[\`geolocator\`]\[] community plugin. ### How do I access the camera? The \[\`image\_picker\`]\[] plugin is popular for accessing the camera. ### How do I log in with Facebook? To Log in with Facebook, use the \[\`flutter\_facebook\_auth\`]\[] community plugin. ### How do I use Firebase features? Most Firebase functions are covered by \[first party plugins]\[]. These plugins are first-party integrations, maintained by the Flutter and Firebase teams: \* \[\`google\_mobile\_ads\`]\[] for Google Mobile Ads for Flutter \* \[\`firebase\_analytics\`]\[] for Firebase Analytics \* \[\`firebase\_auth\`]\[] for Firebase Auth \* \[\`firebase\_database\`]\[] for Firebase RTDB \* \[\`firebase\_storage\`]\[] for Firebase Cloud Storage \* \[\`firebase\_messaging\`]\[] for Firebase Messaging (FCM) \* \[\`firebase\_ui\_auth\`]\[] for quick Firebase Auth integrations (Facebook, Google, Twitter and email) \* \[\`cloud\_firestore\`]\[] for Firebase Cloud Firestore You can also find some third-party Firebase plugins on pub.dev that cover areas not directly covered by the first-party plugins. ### How do I build my own custom native integrations? If there is platform-specific functionality that Flutter or its community Plugins are missing, you can build your own following the \[developing packages and plugins]\[] page. Flutter's plugin architecture, in a nutshell, is much like using an Event bus in Android: you fire off a message and let the receiver process and emit a result back to you. In this case, the receiver is code running on the native side on Android or iOS. ### How do I use the NDK in my Flutter application? If you use the NDK in your current Android application and want your Flutter application to take advantage of your native libraries then it's possible by building a custom plugin. Your custom plugin first talks to your Android app, where you call your \`native\` functions over JNI. Once a response is ready, send a message back to Flutter and render the result. Calling native code directly from Flutter is currently not supported. ## Themes ### How do I theme my app? Out of the box, Flutter comes with a beautiful implementation of Material Design, which takes care of a lot of styling and theming needs that you would typically do. Unlike Android where you declare themes in XML and then assign it to your application using AndroidManifest.xml, in Flutter you declare themes in the top level widget. To take full advantage of Material Components in your app, you can declare a top level widget \`MaterialApp\` as the entry point to your application. MaterialApp is a convenience widget that wraps a number of widgets that are commonly required for applications implementing Material Design. It builds upon a WidgetsApp by adding Material specific functionality. You can also use a \`WidgetsApp\` as your app widget, which provides some of the same functionality, but is not as rich as \`MaterialApp\`. To customize the colors and styles of any child components, pass a \`ThemeData\` object to the \`MaterialApp\` widget. For example, in the code below, the color scheme from seed is set to deepPurple and text selection color is red.

<!--?code-excerpt "lib/theme.dart (theme)"?-->

\`\`\`dart import 'package:material\_ui/material\_ui.dart'; class SampleApp extends StatelessWidget { const SampleApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( title: 'Sample App', theme: ThemeData( colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), textSelectionTheme: const TextSelectionThemeData( selectionColor: Colors.red, ), ), home: const SampleAppPage(), ); } } \`\`\` ## Homescreen widgets ### How do I create a homescreen widget? Android homescreen widgets cannot be created fully using Flutter. They must use either Jetpack Glance(preferred method) or XML layout code. Using the third-party package, \[home\_widget]\[], you can wire a homescreen widget to Dart code, embed a Flutter component (as an image) in a host widget, and share data to/from Flutter to the homescreen widget. To provide a richer and more engaging experience, it's recommended to add widget previews to include in the widget picker. For devices running Android 15 and above, generated widget previews allowing the user to see a dynamic and personalized version of the target widget, giving them a glimpse of how it will accurately on their home screen. For more information about the Generated Widget Previews and the fallback options for older devices, check the \[Add generated previews to your widget picker]\[] documentation page. ## Databases and local storage ### How do I access Shared Preferences? In Android, you can store a small collection of key-value pairs using the SharedPreferences API. In Flutter, access this functionality using the \[Shared\_Preferences plugin]\[]. This plugin wraps the functionality of both Shared Preferences and NSUserDefaults (the iOS equivalent).

<!--?code-excerpt "lib/shared_prefs.dart"?-->

\`\`\`dart import 'dart:async'; import 'package:material\_ui/material\_ui.dart'; import 'package:shared\_preferences/shared\_preferences.dart'; void main() { runApp( const MaterialApp( home: Scaffold( body: Center( child: ElevatedButton( onPressed: \_incrementCounter, child: Text('Increment Counter'), ), ), ), ), ); } Future \_incrementCounter() async { SharedPreferences prefs = await SharedPreferences.getInstance(); int counter = (prefs.getInt('counter') ?? 0) + 1; await prefs.setInt('counter', counter); } \`\`\` ### How do I access SQLite in Flutter? In Android, you use SQLite to store structured data that you can query using SQL. In Flutter, for macOS, Android, or iOS, access this functionality using the \[SQFlite]\[] plugin. ## Debugging ### What tools can I use to debug my app in Flutter? Use the \[DevTools]\[] suite for debugging Flutter or Dart apps. DevTools includes support for profiling, examining the heap, inspecting the widget tree, logging diagnostics, debugging, observing executed lines of code, debugging memory leaks and memory fragmentation. For more information, check out the \[DevTools]\[] documentation. ## Notifications ### How do I set up push notifications? In Android, you use Firebase Cloud Messaging to set up push notifications for your app. In Flutter, access this functionality using the \[Firebase Messaging]\[] plugin. For more information on using the Firebase Cloud Messaging API, see the \[\`firebase\_messaging\`]\[] plugin documentation. \[Flutter for Jetpack Compose devs]: /flutter-for/compose-devs \[Add Flutter to existing app]: /add-to-app \[Animation & Motion widgets]: /ui/widgets/animation \[Animations tutorial]: /ui/animations/tutorial \[Animations overview]: /ui/animations \[\`AppLifecycleStatus\` documentation]: https\://api.flutter.dev/flutter/dart-ui/AppLifecycleState.html \[Apple's iOS design language]: https\://developer.apple.com/design/resources/ \[\`cloud\_firestore\`]: https\://pub.dev/packages/cloud\_firestore \[composing]: /resources/architectural-overview#composition \[Cupertino widgets]: /ui/widgets/cupertino \[developing packages and plugins]: /packages-and-plugins/developing-packages \[\`devicePixelRatio\`]: https\://api.flutter.dev/flutter/dart-ui/FlutterView/devicePixelRatio.html \[DevTools]: /tools/devtools \[existing plugin]: https\://pub.dev/flutter/ \[\`flutter\_facebook\_auth\`]: https\://pub.dev/packages/flutter\_facebook\_auth \[\`google\_mobile\_ads\`]: https\://pub.dev/packages/google\_mobile\_ads \[\`firebase\_analytics\`]: https\://pub.dev/packages/firebase\_analytics \[\`firebase\_auth\`]: https\://pub.dev/packages/firebase\_auth \[\`firebase\_database\`]: https\://pub.dev/packages/firebase\_database \[\`firebase\_messaging\`]: https\://pub.dev/packages/firebase\_messaging \[\`firebase\_storage\`]: https\://pub.dev/packages/firebase\_storage \[\`firebase\_ui\_auth\`]: https\://pub.dev/packages/firebase\_ui\_auth \[Firebase Messaging]: https\://github.com/firebase/flutterfire/tree/master/packages/firebase\_messaging \[first party plugins]: https\://pub.dev/flutter/packages?q=firebase \[Flutter for Android Developers: How to design LinearLayout in Flutter]: https\://proandroiddev.com/flutter-for-android-developers-how-to-design-linearlayout-in-flutter-5d819c0ddf1a \[Flutter for Android Developers: How to design Activity UI in Flutter]: https\://burhanrashid52.com/flutter-for-android-developers-how-to-design-activity-ui-in-flutter/ \[\`geolocator\`]: https\://pub.dev/packages/geolocator \[\`http\` package]: https\://pub.dev/packages/http \[\`image\_picker\`]: https\://pub.dev/packages/image\_picker \[Intents]: #what-is-the-equivalent-of-an-intent-in-flutter \[intl package]: https\://pub.dev/packages/intl \[Introduction to declarative UI]: /flutter-for/declarative \[Material Components]: https\://m3.material.io/develop/flutter \[Material Design guidelines]: https\://m3.material.io/styles \[optimized for all platforms]: https\://m3.material.io/develop \[\`android\_intent\_plus\`]: https\://pub.dev/packages/android\_intent\_plus \[pub.dev]: https\://pub.dev/flutter/packages/ \[Retrieve the value of a text field]: /cookbook/forms/retrieve-input \[Shared\_Preferences plugin]: https\://pub.dev/packages/shared\_preferences \[SQFlite]: https\://pub.dev/packages/sqflite \[StackOverflow]: https\://stackoverflow\.com/questions/44396075/equivalent-of-relativelayout-in-flutter \[widget catalog]: /ui/widgets/layout \[Internationalizing Flutter apps]: /ui/internationalization \[home\_widget]: https\://pub.dev/packages/home\_widget \[Add generated previews to your widget picker]: https\://developer.android.com/develop/ui/compose/glance/generated-previews

----
url: https://docs.flutter.dev/data-and-backend/google-apis.md
----

\# Google APIs > How to use Google APIs with Flutter.

<!--?code-excerpt path-base="googleapis/"?-->

The \[Google APIs package]\[] exposes dozens of Google services that you can use from Dart projects. This page describes how to use APIs that interact with end-user data by using Google authentication. Examples of user-data APIs include \[Calendar]\[], \[Gmail]\[], \[YouTube]\[], and Firebase. :::note The only APIs you should use directly from your Flutter project are those that access user data using Google authentication. APIs that require \[service accounts]\[] \*\*should not\*\* be used directly from a Flutter application. Doing so requires shipping service credentials as part of your application, which is not secure. To use these APIs, we recommend creating an intermediate service. ::: To add authentication to Firebase explicitly, check out the \[Add a user authentication flow to a Flutter app using FirebaseUI]\[fb-lab] codelab and the \[Get Started with Firebase Authentication on Flutter]\[fb-auth] docs. \[fb-lab]: https\://firebase.google.com/codelabs/firebase-auth-in-flutter-apps \[Calendar]: https\://pub.dev/documentation/googleapis/latest/calendar\_v3/calendar\_v3-library.html \[fb-auth]: https\://firebase.google.com/docs/auth/flutter/start \[Gmail]: https\://pub.dev/documentation/googleapis/latest/gmail\_v1/gmail\_v1-library.html \[Google APIs package]: https\://pub.dev/packages/googleapis \[service accounts]: https\://cloud.google.com/iam/docs/service-account-overview \[YouTube]: https\://pub.dev/documentation/googleapis/latest/youtube\_v3/youtube\_v3-library.html ## Overview To use Google APIs, follow these steps: 1. Pick the desired API 1. Enable the API 1. Authenticate and determine the current user 1. Obtain an authenticated HTTP client 1. Create and use the desired API class ## 1. Pick the desired API The documentation for \[\`package:googleapis\`]\[] lists each API as a separate Dart library\&emdash;in a \`name\_version\` format. Check out \[\`youtube\_v3\`]\[] as an example. Each library might provide many types, but there is one \_root\_ class that ends in \`Api\`. For YouTube, it's \[\`YouTubeApi\`]\[]. Not only is the \`Api\` class the one you need to instantiate (see step 3), but it also exposes the scopes that represent the permissions needed to use the API. For example, the \[Constants section]\[] of the \`YouTubeApi\` class lists the available scopes. To request access to read (but not write) an end-user's YouTube data, authenticate the user with \[\`youtubeReadonlyScope\`]\[].

<!--?code-excerpt "lib/main.dart (youtube-import)"?-->

\`\`\`dart // Provides the \`YouTubeApi\` class. import 'package:googleapis/youtube/v3.dart'; \`\`\` \[Constants section]: https\://pub.dev/documentation/googleapis/latest/youtube\_v3/YouTubeApi-class.html#constants \[\`package:googleapis\`]: https\://pub.dev/documentation/googleapis \[\`youtube\_v3\`]: https\://pub.dev/documentation/googleapis/latest/youtube\_v3/youtube\_v3-library.html \[\`YouTubeApi\`]: https\://pub.dev/documentation/googleapis/latest/youtube\_v3/YouTubeApi-class.html \[\`youtubeReadonlyScope\`]: https\://pub.dev/documentation/googleapis/latest/youtube\_v3/YouTubeApi/youtubeReadonlyScope-constant.html ## 2. Enable the API To use Google APIs you must have a Google account and a Google project. You also need to enable your desired API. This example enables \[YouTube Data API v3]\[]. For details, see the \[getting started instructions]\[]. \[getting started instructions]: https\://cloud.google.com/apis/docs/getting-started \[YouTube Data API v3]: https\://console.cloud.google.com/apis/library/youtube.googleapis.com ## 3. Authenticate and determine the current user Use the \[google\_sign\_in]\[gsi-pkg] package to authenticate users with their Google identity. Configure sign in for each platform you want to support.

<!--?code-excerpt "lib/main.dart (google-import)"?-->

\`\`\`dart /// Provides the \`GoogleSignIn\` class. import 'package:google\_sign\_in/google\_sign\_in.dart'; \`\`\` The package's functionality is accessed through a static instance of the \[\`GoogleSignIn\`]\[] class. Before interacting with the instance, the \`initialize\` method must be called and allowed to complete.

<!--?code-excerpt "lib/main.dart (init)"?-->

\`\`\`dart final GoogleSignIn \_googleSignIn = GoogleSignIn.instance; @override void initState() { super.initState(); \_googleSignIn.initialize(); // ··· } \`\`\` Once initialization is complete but before user authentication, listen to authentication events to determine if a user signed in.

<!--?code-excerpt "lib/main.dart (post-init)" plaster="none"?-->

\`\`\`dart highlightLines=1,7,9-12 GoogleSignInAccount? \_currentUser; @override void initState() { super.initState(); \_googleSignIn.initialize().then((\_) { \_googleSignIn.authenticationEvents.listen((event) { setState(() { \_currentUser = switch (event) { GoogleSignInAuthenticationEventSignIn() => event.user, \_ => null, }; }); }); }); } \`\`\` Once you're listening to any relevant authentication events, you can attempt to authenticate a previously signed-in user. \`\`\`dart highlightLines=5-6 void initState() { super.initState(); \_googleSignIn.initialize().then((\_) { // ... // Attempt to authenticate a previously signed in user. \_googleSignIn.attemptLightweightAuthentication(); }); } \`\`\` To also allow for new users to authenticate, follow the instructions provided by \[\`package:google\_sign\_in\`]\[gsi-pkg]. Once a user has been authenticated, you must obtain an authenticated HTTP client. \[gsi-pkg]: https\://pub.dev/packages/google\_sign\_in \[\`GoogleSignIn\`]: https\://pub.dev/documentation/google\_sign\_in/latest/google\_sign\_in/GoogleSignIn-class.html ## 4. Obtain an authenticated HTTP client Once you have a signed-in user, request the relevant client authorization tokens using \[\`authorizationForScopes\`]\[] for the API scopes that your app requires.

<!--?code-excerpt "lib/main.dart (scope-authorize)"?-->

\`\`\`dart const relevantScopes = \[YouTubeApi.youtubeReadonlyScope]; final authorization = await currentUser.authorizationClient .authorizationForScopes(relevantScopes); \`\`\` :::note If your scopes require user interaction, you'll need to use \[\`authorizeScopes\`]\[] from an interaction handler instead of \`authorizationForScopes\`. ::: Once you have the relevant authorization tokens, use the \[\`authClient\`]\[] extension from \[\`package:extension\_google\_sign\_in\_as\_googleapis\_auth\`]\[] to set up an authenticated HTTP client with the relevant credentials applied.

<!--?code-excerpt "lib/main.dart (auth-import)"?-->

\`\`\`dart import 'package:extension\_google\_sign\_in\_as\_googleapis\_auth/extension\_google\_sign\_in\_as\_googleapis\_auth.dart'; \`\`\`

<!--?code-excerpt "lib/main.dart (auth-client)"?-->

\`\`\`dart final authenticatedClient = authorization!.authClient( scopes: relevantScopes, ); \`\`\` \[\`authorizationForScopes\`]: https\://pub.dev/documentation/google\_sign\_in/latest/google\_sign\_in/GoogleSignInAuthorizationClient/authorizationForScopes.html \[\`authorizeScopes\`]: https\://pub.dev/documentation/google\_sign\_in/latest/google\_sign\_in/GoogleSignInAuthorizationClient/authorizeScopes.html \[\`authClient\`]: https\://pub.dev/documentation/extension\_google\_sign\_in\_as\_googleapis\_auth/latest/extension\_google\_sign\_in\_as\_googleapis\_auth/GoogleApisGoogleSignInAuth/authClient.html \[\`package:extension\_google\_sign\_in\_as\_googleapis\_auth\`]: https\://pub.dev/packages/extension\_google\_sign\_in\_as\_googleapis\_auth ## 5. Create and use the desired API class Use the API to create the desired API type and call methods. For instance:

<!--?code-excerpt "lib/main.dart (playlist)"?-->

\`\`\`dart final youTubeApi = YouTubeApi(authenticatedClient); final favorites = await youTubeApi.playlistItems.list( \['snippet'], playlistId: 'LL', // Liked List ); \`\`\` ## More information You might want to check out the following: \* The \[\`extension\_google\_sign\_in\_as\_googleapis\_auth\` example]\[auth-ex] is a working implementation of the concepts described on this page. \[auth-ex]: https\://pub.dev/packages/extension\_google\_sign\_in\_as\_googleapis\_auth/example

----
url: https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.28.1.md
----

\# DevTools 2.28.1 release notes > Release notes for Dart and Flutter DevTools version 2.28.1. The 2.28.1 release of the Dart and Flutter DevTools includes the following changes among other general improvements. To learn more about DevTools, check out the \[DevTools overview]\(https\://docs.flutter.dev/tools/devtools). ## General updates \* Added support for DevTools extensions. This means if you are debugging an app that depends on \`package:foo\`, and \`package:foo\` provides a DevTools extension, you will see a "Foo" tab display in DevTools that you can use to debug your app. To provide a DevTools extension for your pub package, check out the getting started guide for \[package:devtools\_extensions]\(https\://pub.dev/packages/devtools\_extensions)! !\[Example DevTools extension]\(/assets/images/docs/tools/devtools/release-notes/images-2.28.1/example\_devtools\_extension.png "Example DevTools extension for package:foo\_package") \* Fixed theming bug in isolate selector - \[#6403]\(https\://github.com/flutter/devtools/pull/6403) \* Fixed isolate bug where main isolate was not reselecting on hot restart - \[#6436]\(https\://github.com/flutter/devtools/pull/6436) \* Show the hot reload button for Dart server apps that support hot reload - \[#6341]\(https\://github.com/flutter/devtools/pull/6341) \* Fixed exceptions on hot restart - \[#6451]\(https\://github.com/flutter/devtools/pull/6451), \[#6450]\(https\://github.com/flutter/devtools/pull/6450) ## Inspector updates \* Fixed bug where inspector service calls were done on the selected isolate, instead of the main isolate - \[#6434]\(https\://github.com/flutter/devtools/pull/6434) ## Logging updates \* Improved responsiveness of the top bar on the Logging view - \[#6281]\(https\://github.com/flutter/devtools/pull/6281) \* Added the ability to copy filtered logs - \[#6260]\(https\://github.com/flutter/devtools/pull/6260) !\[The copy button on the Logging view to the right of the filter tool]\(/assets/images/docs/tools/devtools/release-notes/images-2.28.1/logger\_copy.png "The Logging view copy button") ## Full commit history To find a complete list of changes in this release, check out the \[DevTools git log]\(https\://github.com/flutter/devtools/tree/v2.28.1).

----
url: https://docs.flutter.dev/testing/plugins-in-tests.md
----

\# Plugins in Flutter tests > Adding plugin as part of your Flutter tests. :::note To learn how to avoid crashes from a plugin when testing your Flutter app, read on. To learn how to test your plugin code, check out \[Testing plugins]\[]. ::: \[Testing plugins]: /testing/testing-plugins Almost all \[Flutter plugins]\[] have two parts: \* Dart code, which provides the API your code calls. \* Code written in a platform-specific (or "host") language, such as Kotlin or Swift, which implements those APIs. In fact, the native (or host) language code distinguishes a plugin package from a standard package. \[Flutter plugins]: /packages-and-plugins/using-packages Building and registering the host portion of a plugin is part of the Flutter application build process, so plugins only work when your code is running in your application, such as with \`flutter run\` or when running \[integration tests]\[]. When running \[Dart unit tests]\[] or \[widget tests]\[], the host code isn't available. If the code you are testing calls any plugins, this often results in errors like the following: \`\`\`console MissingPluginException(No implementation found for method someMethodName on channel some\_channel\_name) \`\`\` \[Dart unit tests]: /cookbook/testing/unit/introduction \[integration tests]: /cookbook/testing/integration/introduction \[widget tests]: https\://api.flutter.dev/flutter/flutter\_test/flutter\_test-library.html :::note Plugin implementations that \[only use Dart]\[] will work in unit tests. This is an implementation detail of the plugin, however, so tests shouldn't rely on it. ::: \[only use Dart]: /packages-and-plugins/developing-packages#dart-only-platform-implementations When unit testing code that uses plugins, there are several options to avoid this exception. The following solutions are listed in order of preference. ## Wrap the plugin In most cases, the best approach is to wrap plugin calls in your own API, and provide a way of \[mocking]\[] your own API in tests. This has several advantages: \* If the plugin API changes, you won't need to update your tests. \* You are only testing your own code, so your tests can't fail due to behavior of a plugin you're using. \* You can use the same approach regardless of how the plugin is implemented, or even for non-plugin package dependencies. \[mocking]: /cookbook/testing/unit/mocking ## Mock the plugin's public API If the plugin's API is already based on class instances, you can mock it directly, with the following caveats: \* This won't work if the plugin uses non-class functions or static methods. \* Tests will need to be updated when the plugin API changes. ## Mock the plugin's platform interface If the plugin is a \[federated plugin]\[], it will include a platform interface that allows registering implementations of its internal logic. You can register a mock of that platform interface implementation instead of the public API with the following caveats: \* This won't work if the plugin isn't federated. \* Your tests will include part of the plugin's code, so plugin behavior could cause problems for your tests. For instance, if a plugin writes files as part of an internal cache, your test behavior might change based on whether you had run the test previously. \* Tests might need to be updated when the platform interface changes. An example of when this might be necessary is mocking the implementation of a plugin used by a package that you rely on, rather than your own code, so you can't change how it's called. However, if possible, you should mock the dependency that uses the plugin instead. \[federated plugin]: /packages-and-plugins/developing-packages#federated-plugins ## Mock the platform channel If the plugin uses \[platform channels]\[], you can mock the platform channels using \[\`TestDefaultBinaryMessenger\`]\[]. This should only be used if, for some reason, none of the methods above are available, as it has several drawbacks: \* Only implementations that use platform channels can be mocked. This means that if some implementations don't use platform channels, your tests will unexpectedly use real implementations when run on some platforms. \* Platform channels are usually internal implementation details of plugins. They might change substantially even in a bugfix update to a plugin, breaking your tests unexpectedly. \* Platform channels might differ in each implementation of a federated plugin. For instance, you might set up mock platform channels to make tests pass on a Windows machine, then find that they fail if run on macOS or Linux. \* Platform channels aren't strongly typed. For example, method channels often use dictionaries and you have to read the plugin's implementation to know what the key strings and value types are. Because of these limitations, \`TestDefaultBinaryMessenger\` is mainly useful in the internal tests of plugin implementations, rather than tests of code using plugins. You might also want to check out \[Testing plugins]\[]. \[platform channels]: /platform-integration/platform-channels \[\`TestDefaultBinaryMessenger\`]: https\://api.flutter.dev/flutter/flutter\_test/TestDefaultBinaryMessenger-class.html \[Testing plugins]: /testing/testing-plugins

----
url: https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.53.0.md
----

\# DevTools 2.53.0 release notes # DevTools 2.53.0 release notes The 2.53.0 release of the Dart and Flutter DevTools includes the following changes among other general improvements. To learn more about DevTools, check out the \[DevTools overview]\(/tools/devtools/overview). ## General updates - Switched default compiler for DevTools to \`dart2wasm\`. - \[#9530]\(https\://github.com/flutter/devtools/pull/9530) ## Performance updates - Increased profile data limit from 64MB to 2GB, fixing issue where panel wouldn't load for large profiles. - \[#9540]\(https\://github.com/flutter/devtools/pull/9540) ## Advanced developer mode updates - Fixed issue preventing CPU profiles from loading when "advanced developer mode" was enabled. - \[#9528]\(https\://github.com/flutter/devtools/pull/9528) ## Full commit history To find a complete list of changes in this release, check out the \[DevTools git log]\(https\://github.com/flutter/devtools/tree/v2.53.0).

----
url: https://docs.flutter.dev/platform-integration/windows/setup
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Set up Windows development](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

Learn how to set up your development environment to run, build, and deploy Flutter apps for the Windows desktop platform.

## Set up tooling

[#](#set-up-tooling)

With [Visual Studio](https://visualstudio.microsoft.com/), you can run Flutter apps on Windows as well as compile and debug native C and C++ code.

Note that **Visual Studio** is an IDE separate from **Visual Studio *Code*** and only supported on Windows.

1. ### Install Visual Studio

   If you haven't done so already, follow the Microsoft guide to [install and set up Visual Studio](https://visualstudio.microsoft.com/).

   If you've already installed Visual Studio, [update it to the latest version](https://learn.microsoft.com/en-us/visualstudio/install/update-visual-studio).

2. ### Set up Visual Studio workloads

   When the Visual Studio installer prompts you to choose workloads, select and install the **Desktop development with C++** workload.

   If you already installed Visual Studio, follow the Microsoft guide to [Modify Visual Studio workloads](https://learn.microsoft.com/en-us/visualstudio/install/modify-visual-studio).

## Validate your setup

[#](#validate-setup)

1. ### Check for toolchain issues

   To check for any issues with your Windows development setup, run the `flutter doctor` command in your preferred terminal:

   ```
   $ flutter doctor -v
   ```

   If you see any errors or tasks to complete under the **Windows version** and **Visual Studio - develop Windows apps** sections, complete and resolve them, then run `flutter doctor -v` again to verify any changes.

2. ### Check for Windows devices

   To ensure Flutter can find and connect to your Windows device correctly, run `flutter devices` in your preferred terminal:

   ```
   $ flutter devices
   ```

   If you've set everything up correctly, there should be at least one entry with the platform marked as **windows**.

3. ### Troubleshoot setup issues

   If you need help resolving any setup issues, check out [installation and setup troubleshooting](/install/troubleshoot). Depending on your issue, also check out Microsoft's guide on [Visual Studio troubleshooting](https://learn.microsoft.com/en-us/troubleshoot/developer/visualstudio/installation/troubleshoot-installation-issues).

   If you still have issues or questions, reach out on one of the Flutter [community](https://flutter.dev/community) channels.

## Start developing for Windows

[#](#start-developing)

Congratulations! Now that you've set up Windows desktop development for Flutter, you can continue your Flutter learning journey while testing on Windows or begin expanding integration with Windows.

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-09-10. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/platform-integration/windows/setup.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/platform-integration/windows/setup\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/platform-integration/windows/setup.md "Report an issue with this page").

----
url: https://docs.flutter.dev/perf/ui-performance.md
----

\# Flutter performance profiling > Diagnosing UI performance issues in Flutter. ## Overview App performance encompasses various aspects, from raw speed and I/O throughput to the smoothness of the user interface. While this page primarily focuses on UI smoothness (lack of stutter or jank), the tools described here can often be used to diagnose other performance issues as well. Flutter offers several tools for performance analysis. Here are a few of them: \* \*\*The Performance Overlay\*\*: Displays a simplified set of metrics directly within your running app. To learn more, see the sections in this topic. \* \*\*The Performance View\*\*: A web-based interface that connects to your app and displays detailed performance metrics. Part of the DevTools utility. To learn more, see \[Use the Performance View]\[]. \* \*\*Performance tracing within Dart\*\*: Add tracing directly into your app's Dart code, using the \`dart:developer package\`, and then track your app's performance in the DevTools utility. To learn more, see \[Tracing Dart code]\[]. \* \*\*Benchmarking\*\*: You can measure and track your app's performance by writing benchmark tests. The Flutter Driver library provides support for benchmarking. Using this integration test framework, you can generate metrics that track jank, download size, battery efficiency, and startup time. For more information, check out \[Integration testing]\[]. \* \*\*Widget rebuild profiler (IntelliJ for Android Studio)\*\*: Jank often arises from unnecessary UI rebuilds. If you are using IntelliJ for Android Studio, the Widget Rebuild Profiler helps pinpoint and fix these issues by showing widget rebuild counts for the current screen and frame. For more information, see \[Show performance data]\[]. Flutter aims to provide 60 frames per second (fps) performance, or 120 fps on devices that support it. To achieve the 60fps, each frame must render approximately every 16ms to avoid jank. Jank occurs when frames take significantly longer to render and are dropped, resulting in a visible stutter in animations. For example, if a frame occasionally takes 10 times longer than usual to render, it will likely be dropped, causing the animation to appear jerky. \[Use the Performance View]: /tools/devtools/performance \[Tracing Dart code]: /testing/code-debugging#trace-dart-code-performance \[Show performance data]: /tools/android-studio#show-performance-data \[Integration testing]: /testing/integration-tests ## Connect to a physical device Almost all performance debugging for Flutter applications should be conducted on a physical Android or iOS device, with your Flutter application running in \[profile mode]\[]. Using debug mode, or running apps on simulators or emulators, is generally not indicative of the final behavior of release mode builds. \_You should consider checking performance on the slowest device that your users might reasonably use.\_ :::secondary Why you should run on a real device \* Simulators and emulators don't use the same hardware, so their performance characteristics are different—some operations are faster on simulators than real devices, and some are slower. \* Debug mode enables additional checks (such as asserts) that don't run in profile or release builds, and these checks can be expensive. \* Debug mode also executes code in a different way than release mode. The debug build compiles the Dart code "just in time" (JIT) as the app runs, but profile and release builds are pre-compiled to native instructions (also called "ahead of time", or AOT) before the app is loaded onto the device. JIT can cause the app to pause for JIT compilation, which itself can cause jank. ::: ## Run in profile mode Flutter's profile mode compiles and launches your application almost identically to release mode, but with just enough additional functionality to allow debugging performance problems. For example, profile mode provides tracing information to the profiling tools. :::note Dart/Flutter DevTools can't connect to a Flutter web app running in profile mode. Use Chrome DevTools to \[generate timeline events]\[] for a web app. ::: Launch the app in profile mode as follows: \* In VS Code, open your \`launch.json\` file, and set the \`flutterMode\` property to \`profile\` (when done profiling, change it back to \`release\` or \`debug\`): \`\`\`json "configurations": \[ { "name": "Flutter", "request": "launch", "type": "dart", "flutterMode": "profile" } ] \`\`\` \* In Android Studio and IntelliJ, use the \*\*Run > Flutter Run main.dart in Profile Mode\*\* menu item. \* From the command line, use the \`--profile\` flag: \`\`\`console $ flutter run --profile \`\`\` For more information on the different modes, see \[Flutter's build modes]\[]. You'll begin by opening DevTools and viewing the performance overlay, as discussed in the next section. \[Flutter's build modes]: /testing/build-modes \[generate timeline events]: https\://developers.google.com/web/tools/chrome-devtools/evaluate-performance/performance-reference ## Launch DevTools DevTools provides features like profiling, examining the heap, displaying code coverage, enabling the performance overlay, and a step-by-step debugger. DevTools' \[Timeline view]\[] allows you to investigate the UI performance of your application on a frame-by-frame basis. Once your app is running in profile mode, \[launch DevTools]\[]. \[Timeline view]: /tools/devtools/performance \[launch DevTools]: /tools/devtools ## Display the performance overlay {:#displaying-the-performance-overlay} You can toggle the display of the performance overlay as follows: \* \*\*DevTools Performance view\*\*: The easiest way to enable the PerformanceOverlay widget is from the \[Performance view]\[] in \[DevTools]\[]. Simply click the \*\*Performance Overlay\*\* button to toggle the overlay on your running app. \* \*\*command line\*\*: Toggle the performance overlay using the \*\*P\*\* key from the command line. \* \*\*programmatically\*\*: To enable the overlay programmatically, see \[Performance overlay]\[], a section in the \[Debugging Flutter apps programmatically]\[] page. \[Performance overlay]: /testing/code-debugging#add-performance-overlay \[Debugging Flutter apps programmatically]: /testing/code-debugging []()## Observe the performance overlay {:#performance-overlay} The performance overlay displays statistics in two graphs that show where time is being spent in your app. If the UI is janky (skipping frames), these graphs help you figure out why. The graphs display on top of your running app, but they aren't drawn like a normal widget—the Flutter engine itself paints the overlay and only minimally impacts performance. Each graph represents the last 300 frames for that thread. This section describes how to enable the performance overlay and use it to diagnose the cause of jank in your application. The following screenshot shows the performance overlay running on the Flutter Gallery example: !\[Screenshot of overlay showing zero jank]\(/assets/images/docs/tools/devtools/performance-overlay-green.png)\
Performance overlay showing the raster thread (top), and UI thread (bottom).\
The vertical green bars represent the current frame. ### Review the graphs {:#interpreting-the-graphs} The top graph (marked "GPU") shows the time spent by the raster thread, the bottom one graph shows the time spent by the UI thread. The white lines across the graphs show 16ms increments along the vertical axis; if the graph ever goes over one of these lines then you are running at less than 60Hz. The horizontal axis represents frames. The graph is only updated when your application paints, so if it's idle the graph stops moving. The overlay should always be viewed in \[profile mode]\[], since \[debug mode]\[] performance is intentionally sacrificed in exchange for expensive asserts that are intended to aid development, and thus the results are misleading. Each frame should be created and displayed within 1/60th of a second (approximately 16ms). A frame exceeding this limit (in either graph) fails to display, resulting in jank, and a vertical red bar appears in one or both of the graphs. If a red bar appears in the UI graph, the Dart code is too expensive. If a red vertical bar appears in the GPU graph, the scene is too complicated to render quickly. !\[Screenshot of performance overlay showing jank with red bars]\(/assets/images/docs/tools/devtools/performance-overlay-jank.png)\
The vertical red bars indicate that the current frame is expensive to both render and paint.\
When both graphs display red, start by diagnosing the UI thread. ### Review the threads {:#flutters-threads} Flutter uses several threads to do its work, though only two of the threads are shown in the overlay. All of your Dart code runs on the UI thread. Although you have no direct access to any other thread, your actions on the UI thread have performance consequences on other threads. \*\*Platform thread\*\* : The platform's main thread. Plugin code runs here. For more information, see the \[UIKit]\[] documentation for iOS, or the \[MainThread]\[] documentation for Android. \_This thread is not shown in the performance overlay.\_ \*\*UI thread\*\* : The UI thread executes Dart code in the Dart VM. This thread includes code that you wrote, and code executed by Flutter's framework on your app's behalf. When your app creates and displays a scene, the UI thread creates a \_layer tree\_, a lightweight object containing device-agnostic painting commands, and sends the layer tree to the raster thread to be rendered on the device. \_Don't block this thread!\_ Shown in the bottom row of the performance overlay. \*\*Raster thread\*\* : The raster thread takes the layer tree and displays it by talking to the GPU (graphic processing unit). You cannot directly access the raster thread or its data but, if this thread is slow, it's a result of something you've done in the Dart code. Skia and Impeller, the graphics libraries, run on this thread. Shown in the top row of the performance overlay. Note that while the raster thread rasterizes for the GPU, the thread itself runs on the CPU. \*\*I/O thread\*\* : Performs expensive tasks (mostly I/O) that would otherwise block either the UI or raster threads. \_This thread is not shown in the performance overlay.\_ For links to more information and videos, see \[The Framework architecture]\[] in the \[Flutter wiki]\[], and the community article, \[The Layer Cake]\[]. \[debug mode]: /testing/build-modes#debug \[Flutter wiki]: https\://github.com/flutter/flutter/tree/main/docs \[UIKit]: https\://developer.apple.com/documentation/uikit \[The Layer Cake]: https\://medium.com/flutter-community/the-layer-cake-widgets-elements-renderobjects-7644c3142401 \[The Framework architecture]: https\://github.com/flutter/flutter/blob/main/docs/about/The-Framework-architecture.md \[MainThread]: https\://developer.android.com/reference/android/support/annotation/MainThread ## Identify problems ### Review the UI graph {:#identifying-problems-in-the-ui-graph} If the performance overlay shows red in the UI graph, start by profiling the Dart VM, even if the GPU graph also shows red. ### Review the GPU graph {:#identifying-problems-in-the-gpu-graph} Sometimes a scene results in a layer tree that is easy to construct, but expensive to render on the raster thread. When this happens, the UI graph has no red, but the GPU graph shows red. In this case, you'll need to figure out what your code is doing that is causing rendering code to be slow. Specific kinds of workloads are more difficult for the GPU. They might involve unnecessary calls to \[\`saveLayer\`]\[], intersecting opacities with multiple objects, and clips or shadows in specific situations. If you suspect that the source of the slowness is during an animation, click the \*\*Slow Animations\*\* button in the Flutter inspector to slow animations down by 5x. If you want more control on the speed, you can also do this \[programmatically]\[]. Is the slowness on the first frame, or on the whole animation? If it's the whole animation, is clipping causing the slow down? Maybe there's an alternative way of drawing the scene that doesn't use clipping. For example, overlay opaque corners onto a square instead of clipping to a rounded rectangle. If it's a static scene that's being faded, rotated, or otherwise manipulated, a \[\`RepaintBoundary\`]\[] might help. \[programmatically]: /testing/code-debugging#debug-animation-issues #### Checking for offscreen layers The \[\`saveLayer\`]\[] method is one of the most expensive methods in the Flutter framework. It's useful when applying post-processing to the scene, but it can slow your app and should be avoided if you don't need it. Even if you don't call \`saveLayer\` explicitly, implicit calls might happen on your behalf, for example when specifying \[\`Clip.antiAliasWithSaveLayer\`]\[] (typically as a \`clipBehavior\`). For example, perhaps you have a group of objects with opacities that are rendered using \`saveLayer\`. In this case, it's probably more performant to apply an opacity to each individual widget, rather than a parent widget higher up in the widget tree. The same goes for other potentially expensive operations, such as clipping or shadows. :::note Opacity, clipping, and shadows are not, in themselves, a bad idea. However, applying them to the top of the widget tree might cause extra calls to \`saveLayer\`, and needless processing. ::: When you encounter calls to \`saveLayer\`, ask yourself these questions: \* Does the app need this effect? \* Can any of these calls be eliminated? \* Can I apply the same effect to an individual element instead of a group? \[\`Clip.antiAliasWithSaveLayer\`]: https\://api.flutter.dev/flutter/dart-ui/Clip.html #### Checking for non-cached images Caching an image with \[\`RepaintBoundary\`]\[] is good, \_when it makes sense\_. One of the most expensive operations, from a resource perspective, is rendering a texture using an image file. First, the compressed image is fetched from persistent storage. The image is decompressed into host memory (GPU memory), and transferred to device memory (RAM). In other words, image I/O can be expensive. The cache provides snapshots of complex hierarchies so they are easier to render in subsequent frames. \_Because raster cache entries are expensive to construct and take up loads of GPU memory, cache images only where absolutely necessary.\_ ## Other resources The following resources provide more information on using Flutter's tools and debugging in Flutter: \* \[Debugging]\[] \* \[Performance view]\[] \* \[Flutter inspector]\[] \* \[Flutter inspector talk]\[], presented at DartConf 2018 \* \[Why Flutter Uses Dart]\[], an article on Hackernoon \* \[Why Flutter uses Dart]\[video], a video on the Flutter channel \* \[DevTools]\[devtools]: performance tooling for Dart and Flutter apps \* \[Flutter API]\[] docs, particularly the \[\`PerformanceOverlay\`]\[] class, and the \[\`dart:developer\`]\[] package \[\`PerformanceOverlay\`]: https\://api.flutter.dev/flutter/widgets/PerformanceOverlay-class.html \[\`RepaintBoundary\`]: https\://api.flutter.dev/flutter/widgets/RepaintBoundary-class.html \[\`saveLayer\`]: https\://api.flutter.dev/flutter/dart-ui/Canvas/saveLayer.html \[\`dart:developer\`]: https\://api.flutter.dev/flutter/dart-developer/dart-developer-library.html \[Debugging]: /testing/debugging \[devtools]: /tools/devtools \[Flutter API]: https\://api.flutter.dev \[Flutter inspector talk]: https\://www\.youtube.com/watch?v=JIcmJNT9DNI \[Flutter inspector]: /tools/devtools/inspector \[Performance view]: /tools/devtools/performance \[profile mode]: /testing/build-modes#profile \[video]: https\://www\.youtube.com/watch?v=5F-6n\_2XWR8 \[Why Flutter Uses Dart]: https\://hackernoon.com/why-flutter-uses-dart-dd635a054ebf

----
url: https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.22.2.md
----

\# DevTools 2.22.2 release notes > Release notes for Dart and Flutter DevTools version 2.22.2. The 2.22.2 release of the Dart and Flutter DevTools includes the following changes among other general improvements. To learn more about DevTools, check out the \[DevTools overview]\(https\://docs.flutter.dev/tools/devtools). ## General updates - Prevent crashes if there is no main isolate - \[#5232]\(https\://github.com/flutter/devtools/pull/5232) ## CPU profiler updates - Display stack frame URI inline with method name to ensure the URI is always visible in deeply nested trees - \[#5181]\(https\://github.com/flutter/devtools/pull/5181) !\[inline uri]\(/assets/images/docs/tools/devtools/release-notes/images-2.22.2/5181.png "inline uri") - Add the ability to filter by method name or source URI - \[#5204]\(https\://github.com/flutter/devtools/pull/5204) ## Memory updates - Change filter default to show only project and 3rd party dependencies - \[#5201]\(https\://github.com/flutter/devtools/pull/5201). !\[filter default]\(/assets/images/docs/tools/devtools/release-notes/images-2.22.2/5201.png "filter default") - Support expression evaluation in console for running application - \[#5248]\(https\://github.com/flutter/devtools/pull/5248). !\[evaluation]\(/assets/images/docs/tools/devtools/release-notes/images-2.22.2/5248.png "evaluation") - Add column \`Persisted\` for memory diffing - \[#5290]\(https\://github.com/flutter/devtools/pull/5290) !\[persisted]\(/assets/images/docs/tools/devtools/release-notes/images-2.22.2/5290.png "persisted") ## Debugger updates - Add support for browser navigation history when navigating using the File Explorer - \[#4906]\(https\://github.com/flutter/devtools/pull/4906) - Designate positional fields for \`Record\` types with the getter syntax beginning at \`$1\` - \[#5272]\(https\://github.com/flutter/devtools/pull/5272) - Fix variable inspection for \`Map\` and \`List\` instances - \[#5320]\(https\://github.com/flutter/devtools/pull/5320) !\[map and list]\(/assets/images/docs/tools/devtools/release-notes/images-2.22.2/5320.png "map and list") - Fix variable inspection for \`Set\` instances - \[#5323]\(https\://github.com/flutter/devtools/pull/5323) !\[set]\(/assets/images/docs/tools/devtools/release-notes/images-2.22.2/5323.png "set") ## Network profiler updates - Improve reliability and performance of the Network tab - \[#5056]\(https\://github.com/flutter/devtools/pull/5056) ## Full commit history To find a complete list of changes since the previous release, check out \[the diff on GitHub]\(https\://github.com/flutter/devtools/compare/v2.21.1...v2.22.2).

----
url: https://docs.flutter.dev/learn/pathway/tutorial/widget-fundamentals.md
----

\# Create widgets > Learn about stateless widgets and how to build your own. Learn to create custom widgets and use the most common SDK widgets like Container, Center, and Text. title: What you'll accomplish items: - title: Create a custom StatelessWidget icon: widgets - title: Make widgets reusable with constructor parameters icon: tune - title: Style widgets using Container and BoxDecoration icon: palette --- ### Before you start This app relies on a bit of game logic that isn't UI-related, and thus is outside the scope of this tutorial. Before you move on, you need to add this logic to your app. 1. Download the following Dart file and save it as \`lib/game.dart\` in your project directory. 1. To enable access to the types defined in the \`game.dart\` library, add an import to it from your \`lib/main.dart\` file: \`\`\`dart title="main.dart" highlightLines=3 import 'package:material\_ui/material\_ui.dart'; import 'game.dart'; \`\`\` :::note Game logic note The \`legalGuesses\` and \`legalWords\` lists in \`lib/game.dart\` only contain a small sample of five-letter words (such as \`aback\`, \`abase\`, and \`abate\`) for brevity. When testing your app later in this tutorial, only words defined in \`lib/game.dart\` are accepted as valid guesses. ::: ### Anatomy of a stateless widget A \`Widget\` is a Dart class that extends one of the Flutter widget classes, in this case \[\`StatelessWidget\`]\[]. Open your \`main.dart\` file and add this code below the \`MainApp\` class, which defines a new widget called \`Tile\`.

<!--?code-excerpt "fwe/birdle/lib/step2a_main.dart (Tile)"?-->

\`\`\`dart class Tile extends StatelessWidget { const Tile(this.letter, this.hitType, {super.key}); final String letter; final HitType hitType; @override Widget build(BuildContext context) { return Container(); } } \`\`\` \[\`StatelessWidget\`]: https\://api.flutter.dev/flutter/widgets/StatelessWidget-class.html #### Constructor The \`Tile\` class has a \[constructor]\[] that defines what data needs to be passed into the widget to render the widget. In this case, the constructor accepts two parameters: - A \`String\` representing the guessed letter of the tile. - A \`HitType\` \[enum value]\[] represent the guess result and used to determine the color of the tile. For example, \`HitType.hit\` results in a green tile. Passing data into widget constructors is at the core of making widgets reusable. \[constructor]: https\://dart.dev/language/constructors \[enum value]: https\://dart.dev/language/enums #### Build method Finally, there's the all important \`build\` method, which must be defined on every widget, and will always return another widget.

<!--?code-excerpt "fwe/birdle/lib/step2b_main.dart (Tile)"?-->

\`\`\`dart class Tile extends StatelessWidget { const Tile(this.letter, this.hitType, {super.key}); final String letter; final HitType hitType; @override Widget build(BuildContext context) { // TODO: Replace Container with widgets. return Container(); } } \`\`\` ### Use the custom widget When the app is finished, there will be 25 instances of this widget on the screen. For now, display just a single tile in the center of the screen so you can see styling updates as you make them. In the \`MainApp.build\` method, replace the \`Text\` widget with your \`Tile\` widget:

<!--?code-excerpt "fwe/birdle/lib/step2_main.dart (MainApp)"?-->

\`\`\`dart class MainApp extends StatelessWidget { const MainApp({super.key}); @override Widget build(BuildContext context) { return const MaterialApp( home: Scaffold( body: Center( child: Tile('A', HitType.hit), // NEW ), ), ); } } \`\`\` At the moment, your app appears blank because the \`Tile\` widget returns an empty \`Container\`, which doesn't render anything by default. ### The \`Container\` widget The \`Tile\` widget consists of three of the most common core widgets: \`Container\`, \`Center\`, and \`Text\`. \[\`Container\`]\[] is a convenience widget that wraps several core styling widgets, such as \[\`Padding\`]\[], \[\`ColoredBox\`]\[], \[\`SizedBox\`]\[], and \[\`DecoratedBox\`]\[]. Because the finished UI contains 25 \`Tile\` widgets in neat columns and rows, it should have an explicit size. Set the width and height properties on the \`Container\`. (You could also do this with a \`SizedBox\` widget, but you'll use more properties of the \`Container\` next.)

<!--?code-excerpt "fwe/birdle/lib/step2c_main.dart (Tile)"?-->

\`\`\`dart class Tile extends StatelessWidget { const Tile(this.letter, this.hitType, {super.key}); final String letter; final HitType hitType; @override Widget build(BuildContext context) { // NEW return Container( width: 60, height: 60, // TODO: Add needed widgets ); } } \`\`\` \[\`Container\`]: https\://api.flutter.dev/flutter/widgets/Container-class.html \[\`Padding\`]: https\://api.flutter.dev/flutter/widgets/Padding-class.html \[\`ColoredBox\`]: https\://api.flutter.dev/flutter/widgets/ColoredBox-class.html \[\`SizedBox\`]: https\://api.flutter.dev/flutter/widgets/SizedBox-class.html \[\`DecoratedBox\`]: https\://api.flutter.dev/flutter/widgets/DecoratedBox-class.html ### BoxDecoration Next, add a \[\`Border\`]\[] to the box with the following code:

<!--?code-excerpt "fwe/birdle/lib/step2d_main.dart (Tile)"?-->

\`\`\`dart class Tile extends StatelessWidget { const Tile(this.letter, this.hitType, {super.key}); final String letter; final HitType hitType; @override Widget build(BuildContext context) { // NEW return Container( width: 60, height: 60, decoration: BoxDecoration( border: Border.all(color: Colors.grey.shade300), // TODO: add background color ), ); } } \`\`\` \`BoxDecoration\` is an object that knows how to add any number of decorations to a widget, from background color to borders to box shadows and more. In this case, you added a border. When you hot reload, a lightly colored border appears around the tile. When this game is complete, the color of the tile will depend on the user's guess. The tile will be green when the user has guessed correctly, yellow when the letter is correct but the position is incorrect, and gray if the guess is wrong in both respects. The following figure shows all three possibilities. To achieve this in UI, use a \[switch expression]\[] to set the \`color\` of the \`BoxDecoration\`.

<!--?code-excerpt "fwe/birdle/lib/step2e_main.dart (Tile)"?-->

\`\`\`dart class Tile extends StatelessWidget { const Tile(this.letter, this.hitType, {super.key}); final String letter; final HitType hitType; @override Widget build(BuildContext context) { return Container( width: 60, height: 60, decoration: BoxDecoration( border: Border.all(color: Colors.grey.shade300), color: switch (hitType) { HitType.hit => Colors.green, HitType.partial => Colors.yellow, HitType.miss => Colors.grey, \_ => Colors.white, }, // TODO: add children ), ); } } \`\`\` \[\`Border\`]: https\://api.flutter.dev/flutter/widgets/Container-class.html \[switch expression]: https\://dart.dev/language/branches#switch-expressions ### Child widgets Finally, add the \`Center\` and \`Text\` widgets to the \`Container.child\` property. Most widgets in the Flutter SDK have a \`child\` or \`children\` property that's meant to be passed a widget or a list of widgets, respectively. It's the best practice to use the same naming convention in your own custom widgets.

<!--?code-excerpt "fwe/birdle/lib/step2f_main.dart (Tile)"?-->

\`\`\`dart class Tile extends StatelessWidget { const Tile(this.letter, this.hitType, {super.key}); final String letter; final HitType hitType; @override Widget build(BuildContext context) { return Container( width: 60, height: 60, decoration: BoxDecoration( border: Border.all(color: Colors.grey.shade300), color: switch (hitType) { HitType.hit => Colors.green, HitType.partial => Colors.yellow, HitType.miss => Colors.grey, \_ => Colors.white, }, ), child: Center( child: Text( letter.toUpperCase(), style: Theme.of(context).textTheme.titleLarge, ), ), ); } } \`\`\` Hot reload, and a single green tile containing the letter \*\*A\*\* appears in the center of your app. To test different colors, try replacing the Tile widget in MainApp with one of the following examples, then hot reload:

<!--?code-excerpt "fwe/birdle/lib/step2_main.dart (TileUsage)"?-->

\`\`\`dart // main.dart line \~16 // green Tile('A', HitType.hit); // grey Tile('A', HitType.miss); // yellow Tile('A', HitType.partial); \`\`\` Soon, this small box will be one of many widgets on the screen. In the next lesson, you'll build the full 5x5 grid of tiles for the game board. ### Review title: What you accomplished subtitle: Here's a summary of what you built and learned in this lesson. completed: true items: - title: Built a custom StatelessWidget icon: widgets details: >- You created a new \`Tile\` widget by extending \`StatelessWidget\`. Every widget has a constructor to accept data and a \`build\` method that returns other widgets. This pattern is fundamental to building user interfaces with Flutter. - title: Made widgets reusable with constructor parameters icon: tune details: >- By accepting \`letter\` and \`hitType\` as constructor parameters, your \`Tile\` widget can display different content and colors. Passing data through constructors is how you can create flexible, reusable components. - title: Styled widgets using Container and BoxDecoration icon: palette details: >- You used \`Container\` to set the widget's size and \`BoxDecoration\` to add borders and background colors. Then to conditional style the tile's color, you used a switch expression on the \`hitType\` value. ### Test yourself - question: "What must every Flutter widget's \`build\` method return?" options: - text: A String describing the widget. correct: false explanation: "The \`build\` method returns a widget, not a String." - text: Another widget. correct: true explanation: "The \`build\` method always returns another widget, which forms part of the widget tree." - text: A boolean indicating success or failure. correct: false explanation: Widgets don't indicate success; they return other widgets to be rendered. - text: Null if there's nothing to display. correct: false explanation: "The \`build\` method cannot return null; it must return a valid widget." - question: Which object is used to add decorations like borders, background colors, and shadows to a Container? options: - text: ThemeData correct: false explanation: ThemeData is for app-wide styling, not individual container decorations. - text: TextStyle correct: false explanation: TextStyle is for text formatting, not container decorations. - text: BoxDecoration correct: true explanation: BoxDecoration can add borders, background colors, gradients, shadows, and more to a Container. - text: EdgeInsets correct: false explanation: EdgeInsets is for specifying padding or margin, not visual decorations.

----
url: https://docs.flutter.dev/tools/devtools/vscode.md
----

\# Run DevTools from VS Code > Learn how to launch and use DevTools from VS Code. ## Add the VS Code extensions To use the DevTools from VS Code, you need the \[Dart extension]\[]. If you're debugging Flutter applications, you should also install the \[Flutter extension]\[]. ## Start an application to debug {: #run-and-debug} Start a debug session for your application by opening the root folder of your project (the one containing \`pubspec.yaml\`) in VS Code and clicking \*\*Run > Start Debugging\*\* (\`F5\`). ## Launch DevTools Once the debug session is active and the application has started, the \*\*Open DevTools\*\* commands become available in the VS Code command palette (\`F1\`): !\[Screenshot showing Open DevTools commands]\(/assets/images/docs/tools/vs-code/vscode\_command.png){:width="100%"} The chosen tool will be opened embedded inside VS Code. !\[Screenshot showing DevTools embedded in VS Code]\(/assets/images/docs/tools/vs-code/vscode\_embedded.png){:width="100%"} You can choose to have DevTools always opened in a browser with the \`dart.embedDevTools\` setting, and control whether it opens as a full window or in a new column next to your current editor with the \`dart.devToolsLocation\` setting. A full list of Dart/Flutter settings are available on \[dartcode.org]\(https\://dartcode.org/docs/settings/) or in the \[VS Code settings editor]\(https\://code.visualstudio.com/docs/getstarted/settings#\_settings-editor). Some recommendation settings for Dart/Flutter in VS Code can also be found on \[dartcode.org]\(https\://dartcode.org/docs/recommended-settings/). You can also see whether DevTools is running and launch it in a browser from the language status area (the \`{}\` icon next to \*\*Dart\*\* in the status bar). !\[Screenshot showing DevTools in the VS Code language status area]\(/assets/images/docs/tools/vs-code/vscode\_status\_bar.png){:width="100%"} \[Dart extension]: https\://marketplace.visualstudio.com/items?itemName=Dart-Code.dart-code \[Flutter extension]: https\://marketplace.visualstudio.com/items?itemName=Dart-Code.flutter

----
url: https://docs.flutter.dev/ui/interactivity/gestures/drag-outside
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Drag outside an app](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

You might want to implement drag and drop somewhere in your app.

You have a couple potential approaches that you can take. One directly uses Flutter widgets and the other uses a package ([super\_drag\_and\_drop](https://pub.dev/packages/super_drag_and_drop)), available on [pub.dev](https://pub.dev).

## Create draggable widgets within your app

[#](#create-draggable-widgets-within-your-app)

If you want to implement drag and drop within your application, you can use the [`Draggable`](https://api.flutter.dev/flutter/widgets/Draggable-class.html) widget. For insight into this approach, see the [Drag a UI element within an app](/cookbook/effects/drag-a-widget) recipe.

An advantage of using `Draggable` and `DragTarget` is that you can supply Dart code to decide whether to accept a drop.

For more information, check out the [`Draggable` widget of the week](https://youtu.be/q4x2G_9-Mu0?si=T4679e90U2yrloCs) video.

## Implement drag and drop between apps

[#](#implement-drag-and-drop-between-apps)

If you want to implement drag and drop within your application and *also* between your application and another (possibly non-Flutter) app, check out the [super\_drag\_and\_drop](https://pub.dev/packages/super_drag_and_drop) package.

To avoid implementing two styles of drag and drop, one for drags outside of the app and another for dragging inside the app, you can supply [local data](https://pub.dev/documentation/super_drag_and_drop/latest/super_drag_and_drop/DragItem/localData.html) to the package to perform drags within your app.

Another difference between this approach and using `Draggable` directly, is that you must tell the package up front what data your app accepts because the platform APIs need a synchronous response, which doesn't allow an asynchronous response from the framework.

An advantage of using this approach is that it works across desktop, mobile, *and* web.

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/ui/interactivity/gestures/drag-outside.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/ui/interactivity/gestures/drag-outside\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/ui/interactivity/gestures/drag-outside.md "Report an issue with this page").

----
url: https://docs.flutter.dev/learn/pathway/quick-install
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[school Flutter learning pathway](/learn/pathway)

Quick install

[1Install Flutter](/learn/pathway/quick-install)

Introduction to Flutter UI

[2Tutorial introduction](/learn/pathway/tutorial) [3Create a Flutter app](/learn/pathway/tutorial/create-an-app) [4Widget fundamentals](/learn/pathway/tutorial/widget-fundamentals) [5Layout widgets on a screen](/learn/pathway/tutorial/layout) [6DevTools](/learn/pathway/tutorial/devtools) [7Handle user input](/learn/pathway/tutorial/user-input) [8Learn about stateful widgets](/learn/pathway/tutorial/stateful-widget) [9Add implicit animations](/learn/pathway/tutorial/implicit-animations)

State in Flutter apps

[10The state management project](/learn/pathway/tutorial/set-up-state-project) [11Make Http Requests](/learn/pathway/tutorial/http-requests) [12Use `ChangeNotifier` to update app state](/learn/pathway/tutorial/change-notifier) [13Use `ListenableBuilder` to update app UI](/learn/pathway/tutorial/listenable-builder)

Flutter UI 102

[14Advanced UI features](/learn/pathway/tutorial/advanced-ui) [15Adaptive layouts](/learn/pathway/tutorial/adaptive-layout) [16Scrolling and slivers](/learn/pathway/tutorial/slivers) [17Stack based navigation](/learn/pathway/tutorial/navigation)

How Flutter works

[18How Flutter works](/learn/pathway/how-flutter-works)

   ```
   $ sudo apt-get update -y && sudo apt-get upgrade -y
   $ sudo apt-get install -y curl git unzip xz-utils zip libglu1-mesa
   ```

3. ### Download and install Visual Studio Code

   To quickly install Flutter, then edit and debug your apps, [install and set up Visual Studio Code](https://code.visualstudio.com/docs/setup/setup-overview).

   You can instead install and use any other Code OSS-based editor that supports VS Code extensions. If you choose to do so, for the rest of this article, assume VS Code refers to the editor of your choice.

1) ### Install git

   **If you already have git installed, skip to the next step: Download and install Visual Studio Code.**

   There are a few ways to install git on your Mac, but the way we recommend is by using XCode. This will be important when you target your builds for iOS or macOS.

   ```
   $ xcode-select --install
   ```

   ```
   $ sudo apt-get update -y && sudo apt-get upgrade -y
   $ sudo apt-get install -y curl git unzip xz-utils zip libglu1-mesa
   ```

      Go to **View** > **Command Palette** or press `Cmd/Ctrl` + `Shift` + `P`.

   2. In the command palette, type `flutter`.

   3. Select **Flutter: New Project**.

   4. VS Code prompts you to locate the Flutter SDK on your computer. Select **Download SDK**.

   5. When the **Select Folder for Flutter SDK** dialog displays, choose where you want to install Flutter.

   6. Click **Clone Flutter**.

      While downloading Flutter, VS Code displays this pop-up notification:

      ```
      Downloading the Flutter SDK. This may take a few minutes.
      ```

      This download takes a few minutes. If you suspect that the download has hung, click **Cancel** then start the installation again.

   7. Click **Add SDK to PATH**.

      When successful, a notification displays:

      ```
      The Flutter SDK was added to your PATH
      ```

   8. VS Code might display a Google Analytics notice.

      If you agree, click **OK**.

   9. To ensure that Flutter is available in all terminals:

      1. Close, then reopen all terminal windows.
      2. Restart VS Code.

4. ### Troubleshoot installation issues

   If you encounter any issues during installation, check out [Flutter installation troubleshooting](/install/troubleshoot).

## Start learning!

[#](#start-learning)

Installing the Flutter SDK automatically installs Dart as well. You can now start building Flutter apps!

If you're new to programming, start with the Dart Getting Started tutorial to learn the fundamentals of the Dart programming language. This tutorial was designed to lead into the Flutter Getting Started tutorial.

[Start learning](https://dart.dev/learn/tutorial)

If you're already familiar with programming, you can skip the Dart tutorial and jump straight into the first Flutter lesson.

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/learn/pathway/quick-install.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/learn/pathway/quick-install\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/learn/pathway/quick-install.md "Report an issue with this page").

----
url: https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.39.0.md
----

\# DevTools 2.39.0 release notes > Release notes for Dart and Flutter DevTools version 2.39.0. The 2.39.0 release of the Dart and Flutter DevTools includes the following changes among other general improvements. To learn more about DevTools, check out the \[DevTools overview]\(/tools/devtools/overview). ## General updates \* Changed table columns to be sortable by default. - \[#8175]\(https\://github.com/flutter/devtools/pull/8175) \* Updated DevTools screen icons to match what is used in the Flutter-supported IDEs. - \[#8181]\(https\://github.com/flutter/devtools/pull/8181) ## Memory updates \* Enabled offline analysis of memory snapshots, as well as support for viewing memory data when an app disconnects. For example, this may happen when an app unexpectedly crashes or hits an out-of-memory issue. - \[#7843]\(https\://github.com/flutter/devtools/pull/7843), \[#8093]\(https\://github.com/flutter/devtools/pull/8093), \[#8096]\(https\://github.com/flutter/devtools/pull/8096) \* Fixed issue where the memory chart could cause the connected application to hit an out of memory exception while allocating large, short-lived objects repeatedly. - \[#8209]\(https\://github.com/flutter/devtools/pull/8209) ## App size tool updates \* Added UI polish to the file import views. \[#8232]\(https\://github.com/flutter/devtools/pull/8232) ## Full commit history To find a complete list of changes in this release, check out the \[DevTools git log]\(https\://github.com/flutter/devtools/tree/v2.39.0).

----
url: https://docs.flutter.dev/testing/common-errors
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Common Flutter errors](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

## Introduction

[#](#introduction)

This page explains several frequently-encountered Flutter framework errors (including layout errors) and gives suggestions on how to resolve them. This is a living document with more errors to be added in future revisions, and your contributions are welcomed. Feel free to [open an issue](https://github.com/flutter/website/issues/new/choose) or [submit a pull request](https://github.com/flutter/website/pulls) to make this page more useful to you and the Flutter community.

## A solid red or grey screen when running your app

[#](#a-solid-red-or-grey-screen-when-running-your-app)

Typically called a "red (or grey) screen of death", this is sometimes how Flutter lets you know that there's an error.

The red screen can appear when the app runs in debug or profile mode. The grey screen can appear when the app runs in release mode.

Generally, these errors occur when there's an uncaught exception (and you might need another try-catch block), or when there is some rendering error, such as an overflow error.

The following articles provide some useful insights on debugging this sort of error:

* [Flutter errors demystified](https://medium.com/@hpatilabhi10/flutter-errors-demystified-red-screen-errors-vs-debug-console-errors-acb3b8ed2625) by Abishek
* [Understanding and addressing the grey screen in Flutter](https://medium.com/@LordChris/understanding-and-addressing-the-grey-screen-in-flutter-5e72c31f408f) by Christopher Nwosu-Madueke
* [Flutter stuck on white screen](https://www.dhiwise.com/post/flutter-stuck-on-white-screen-understanding-and-fixing) by Kesar Bhimani

## 'A RenderFlex overflowed…'

[#](#a-renderflex-overflowed)

RenderFlex overflow is one of the most frequently encountered Flutter framework errors, and you've probably run into it already.

**What does the error look like?**

When it happens, yellow and black stripes appear, indicating the area of overflow in the app UI. In addition, an error message displays in the debug console:

```
The following assertion was thrown during layout:
A RenderFlex overflowed by 1146 pixels on the right.

The relevant error-causing widget was

    Row      lib/errors/renderflex_overflow_column.dart:23

The overflowing RenderFlex has an orientation of Axis.horizontal.
The edge of the RenderFlex that is overflowing has been marked in the rendering
with a yellow and black striped pattern. This is usually caused by the contents
being too big for the RenderFlex.
(Additional lines of this message omitted)
```

**How might you run into this error?**

The error often occurs when a `Column` or `Row` has a child widget that isn't constrained in its size. For example, the code snippet below demonstrates a common scenario:

dart

```
Widget build(BuildContext context) {
  return Row(
    children: [
      const Icon(Icons.message),
      Column(
        mainAxisSize: MainAxisSize.min,
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Text('Title', style: Theme.of(context).textTheme.headlineMedium),
          const Text(
            'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed '
            'do eiusmod tempor incididunt ut labore et dolore magna '
            'aliqua. Ut enim ad minim veniam, quis nostrud '
            'exercitation ullamco laboris nisi ut aliquip ex ea '
            'commodo consequat.',
          ),
        ],
      ),
    ],
  );
}
```

In the above example, the `Column` tries to be wider than the space the `Row` (its parent) can allocate to it, causing an overflow error. Why does the `Column` try to do that? To understand this layout behavior, you need to know how the Flutter framework performs layout:

"*To perform layout, Flutter walks the render tree in a depth-first traversal and **passes down size constraints** from parent to child… Children respond by **passing up a size** to their parent object within the constraints the parent established.*" – [Flutter architectural overview](/resources/architectural-overview#layout-and-rendering)

In this case, the `Row` widget doesn't constrain the size of its children, nor does the `Column` widget. Lacking constraints from its parent widget, the second `Text` widget tries to be as wide as all the characters it needs to display. The self-determined width of the `Text` widget then gets adopted by the `Column`, which clashes with the maximum amount of horizontal space its parent, the `Row` widget, can provide.

**How to fix it?**

Well, you need to make sure the `Column` won't attempt to be wider than it can be. To achieve this, you need to constrain its width. One way to do it is to wrap the `Column` in an `Expanded` widget:

dart

```
return const Row(
  children: [
    Icon(Icons.message),
    Expanded(
      child: Column(
        // code omitted
      ),
    ),
  ],
);
```

Another way is to wrap the `Column` in a `Flexible` widget and specify a `flex` factor. In fact, the `Expanded` widget is equivalent to the `Flexible` widget with a `flex` factor of 1.0, as [its source code](https://github.com/flutter/flutter/blob/c8e42b47f5ea8b5ff7bf2f2b0a2a8e765f1aa51d/packages/flutter/lib/src/widgets/basic.dart#L5166-L5174) shows. To further understand how to use the `Flex` widget in Flutter layouts, check out [this 90-second Widget of the Week video](https://www.youtube.com/watch?v=CI7x0mAZiY0) on the `Flexible` widget.

**Further information:**

The resources linked below provide further information about this error.

* [Flexible (Flutter Widget of the Week)](https://www.youtube.com/watch?v=CI7x0mAZiY0)
* [How to debug layout issues with the Flutter Inspector](https://blog.flutter.dev/how-to-debug-layout-issues-with-the-flutter-inspector-87460a7b9db#738b)
* [Understanding constraints](/ui/layout/constraints)

## 'RenderBox was not laid out'

[#](#renderbox-was-not-laid-out)

While this error is pretty common, it's often a side effect of a primary error occurring earlier in the rendering pipeline.

**What does the error look like?**

The message shown by the error looks like this:

```
RenderBox was not laid out:
RenderViewport#5a477 NEEDS-LAYOUT NEEDS-PAINT NEEDS-COMPOSITING-BITS-UPDATE
```

**How might you run into this error?**

Usually, the issue is related to violation of box constraints, and it needs to be solved by providing more information to Flutter about how you'd like to constrain the widgets in question. You can learn more about how constraints work in Flutter on the [Understanding constraints](/ui/layout/constraints) page.

The `RenderBox was not laid out` error is often caused by one of two other errors:

* 'Vertical viewport was given unbounded height'
* 'An InputDecorator...cannot have an unbounded width'

[]()

## 'Vertical viewport was given unbounded height'

[#](#vertical-viewport-was-given-unbounded-height)

This is another common layout error you could run into while creating a UI in your Flutter app.

**What does the error look like?**

The message shown by the error looks like this:

```
The following assertion was thrown during performResize():
Vertical viewport was given unbounded height.

Viewports expand in the scrolling direction to fill their container.
In this case, a vertical viewport was given an unlimited amount of
vertical space in which to expand. This situation typically happens when a
scrollable widget is nested inside another scrollable widget.
(Additional lines of this message omitted)
```

**How might you run into this error?**

The error is often caused when a `ListView` (or other kinds of scrollable widgets such as `GridView`) is placed inside a `Column`. A `ListView` takes all the vertical space available to it, unless it's constrained by its parent widget. However, a `Column` doesn't impose any constraint on its children's height by default. The combination of the two behaviors leads to the failure of determining the size of the `ListView`.

dart

```
Widget build(BuildContext context) {
  return Center(
    child: Column(
      children: <Widget>[
        const Text('Header'),
        ListView(
          children: const <Widget>[
            ListTile(leading: Icon(Icons.map), title: Text('Map')),
            ListTile(leading: Icon(Icons.subway), title: Text('Subway')),
          ],
        ),
      ],
    ),
  );
}
```

**How to fix it?**

To fix this error, specify how tall the `ListView` should be. To make it as tall as the remaining space in the `Column`, wrap it using an `Expanded` widget (as shown in the following example). Otherwise, specify an absolute height using a `SizedBox` widget or a relative height using a `Flexible` widget.

dart

```
Widget build(BuildContext context) {
  return Center(
    child: Column(
      children: <Widget>[
        const Text('Header'),
        Expanded(
          child: ListView(
            children: const <Widget>[
              ListTile(leading: Icon(Icons.map), title: Text('Map')),
              ListTile(leading: Icon(Icons.subway), title: Text('Subway')),
            ],
          ),
        ),
      ],
    ),
  );
}
```

**Further information:**

The resources linked below provide further information about this error.

* [How to debug layout issues with the Flutter Inspector](https://blog.flutter.dev/how-to-debug-layout-issues-with-the-flutter-inspector-87460a7b9db#738b)
* [Understanding constraints](/ui/layout/constraints)

## 'An InputDecorator...cannot have an unbounded width'

[#](#an-inputdecorator-cannot-have-an-unbounded-width)

The error message suggests that it's also related to box constraints, which are important to understand to avoid many of the most common Flutter framework errors.

**What does the error look like?**

The message shown by the error looks like this:

```
The following assertion was thrown during performLayout():
An InputDecorator, which is typically created by a TextField, cannot have an
unbounded width.
This happens when the parent widget does not provide a finite width constraint.
For example, if the InputDecorator is contained by a `Row`, then its width must
be constrained. An `Expanded` widget or a SizedBox can be used to constrain the
width of the InputDecorator or the TextField that contains it.
(Additional lines of this message omitted)
```

**How might you run into the error?**

This error occurs, for example, when a `Row` contains a `TextFormField` or a `TextField` but the latter has no width constraint.

dart

```
Widget build(BuildContext context) {
  return MaterialApp(
    home: Scaffold(
      appBar: AppBar(title: const Text('Unbounded Width of the TextField')),
      body: const Row(children: [TextField()]),
    ),
  );
}
```

**How to fix it?**

As suggested by the error message, fix this error by constraining the text field using either an `Expanded` or `SizedBox` widget. The following example demonstrates using an `Expanded` widget:

dart

```
Widget build(BuildContext context) {
  return MaterialApp(
    home: Scaffold(
      appBar: AppBar(title: const Text('Unbounded Width of the TextField')),
      body: Row(children: [Expanded(child: TextFormField())]),
    ),
  );
}
```

## 'Incorrect use of ParentData widget'

[#](#incorrect-use-of-parentdata-widget)

This error is about missing an expected parent widget.

**What does the error look like?**

The message shown by the error looks like this:

```
The following assertion was thrown while looking for parent data:
Incorrect use of ParentDataWidget.
(Some lines of this message omitted)
Usually, this indicates that at least one of the offending ParentDataWidgets
listed above is not placed directly inside a compatible ancestor widget.
```

**How might you run into the error?**

While Flutter's widgets are generally flexible in how they can be composed together in a UI, a small subset of those widgets expect specific parent widgets. When this expectation can't be satisfied in your widget tree, you're likely to encounter this error.

Here is an *incomplete* list of widgets that expect specific parent widgets within the Flutter framework. Feel free to submit a PR (using the doc icon in the top right corner of the page) to expand this list.

| Widget                                |  Expected parent widget(s) |
| :------------------------------------ | -------------------------: |
| `Flexible`                            | `Row`, `Column`, or `Flex` |
| `Expanded` (a specialized `Flexible`) | `Row`, `Column`, or `Flex` |
| `Positioned`                          |                    `Stack` |
| `TableCell`                           |                    `Table` |

**How to fix it?**

The fix should be obvious once you know which parent widget is missing.

## 'setState called during build'

[#](#setstate-called-during-build)

The `build` method in your Flutter code isn't a good place to call `setState`, either directly or indirectly.

**What does the error look like?**

When the error occurs, the following message is displayed in the console:

```
The following assertion was thrown building DialogPage(dirty, dependencies:
[_InheritedTheme, _LocalizationsScope-[GlobalKey#59a8e]],
state: _DialogPageState#f121e):
setState() or markNeedsBuild() called during build.

This Overlay widget cannot be marked as needing to build because the framework
is already in the process of building widgets.
(Additional lines of this message omitted)
```

**How might you run into the error?**

In general, this error occurs when the `setState` method is called within the `build` method.

A common scenario where this error occurs is when attempting to trigger a `Dialog` from within the `build` method. This is often motivated by the need to immediately show information to the user, but `setState` should never be called from a `build` method.

The following snippet seems to be a common culprit of this error:

dart

```
Widget build(BuildContext context) {
  // Don't do this.
  showDialog<void>(
    context: context,
    builder: (context) {
      return const AlertDialog(title: Text('Alert Dialog'));
    },
  );
​
  return const Center(
    child: Column(children: <Widget>[Text('Show Material Dialog')]),
  );
}
```

This code doesn't make an explicit call to `setState`, but it's called by `showDialog`. The `build` method isn't the right place to call `showDialog` because `build` can be called by the framework for every frame, for example, during an animation.

**How to fix it?**

One way to avoid this error is to use the `Navigator` API to trigger the dialog as a route. In the following example, there are two pages. The second page has a dialog to be displayed upon entry. When the user requests the second page by clicking a button on the first page, the `Navigator` pushes two routes–one for the second page and another for the dialog.

dart

```
class FirstScreen extends StatelessWidget {
  const FirstScreen({super.key});
​
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('First Screen')),
      body: Center(
        child: ElevatedButton(
          child: const Text('Launch screen'),
          onPressed: () {
            // Navigate to the second screen using a named route.
            Navigator.pushNamed(context, '/second');
            // Immediately show a dialog upon loading the second screen.
            Navigator.push(
              context,
              PageRouteBuilder<void>(
                barrierDismissible: true,
                opaque: false,
                pageBuilder: (_, anim1, anim2) => const MyDialog(),
              ),
            );
          },
        ),
      ),
    );
  }
}
```

## `The ScrollController is attached to multiple scroll views`

[#](#the-scrollcontroller-is-attached-to-multiple-scroll-views)

This error can occur when multiple scrolling widgets (such as `ListView`) appear on the screen at the same time. It's more likely for this error to occur on a web or desktop app, than a mobile app since it's rare to encounter this scenario on mobile.

For more information and to learn how to fix, check out the following video on [`PrimaryScrollController`](https://api.flutter.dev/flutter/widgets/PrimaryScrollController-class.html):

[Watch on YouTube in a new tab: "PrimaryScrollController | Decoding Flutter"](https://www.youtube.com/watch/33_0ABjFJUU)

## References

[#](#references)

To learn more about how to debug errors, especially layout errors in Flutter, check out the following resources:

* [How to debug layout issues with the Flutter Inspector](https://blog.flutter.dev/how-to-debug-layout-issues-with-the-flutter-inspector-87460a7b9db#738b)
* [Understanding constraints](/ui/layout/constraints)
* [Flutter architectural overview](/resources/architectural-overview#layout-and-rendering)

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-09-02. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/testing/common-errors.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/testing/common-errors\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/testing/common-errors.md "Report an issue with this page").

----
url: https://docs.flutter.dev/install/add-to-path.md
----

\# Add Flutter to your PATH > Learn how to add Flutter to your PATH after downloading the Flutter SDK. Learn how to add Flutter to your \`PATH\` environment variable after downloading the SDK. Adding Flutter to your \`PATH\` allows you to use the \`flutter\` and \`dart\` command-line tools in terminals and IDEs. :::tip If you haven't downloaded Flutter yet, follow \[Set up and test drive Flutter]\[] instead. :::

\[Set up and test drive Flutter]: /install/quick ## Windows To run \`flutter\` and \`dart\` commands in a terminal on Windows, add the Flutter SDK's \`bin\` directory to the \`Path\` environment variable. 1.

### Determine your Flutter SDK installation location

Copy the absolute path to the directory that you downloaded and extracted the Flutter SDK into. 1.

### Navigate to the environment variables settings

1\. Press `Windows` + `Pause`. If your keyboard lacks a `Pause` key, try `Windows` + `Fn` + `B`. The \*\*System > About\*\* dialog opens. 1. Click \*\*Advanced System Settings\*\* > \*\*Advanced\*\* > \*\*Environment Variables...\*\*. The \*\*Environment Variables\*\* dialog opens. 1.

### Add the Flutter SDK bin to your path

1\. In the \*\*User variables for (username)\*\* section of the \*\*Environment Variables\*\* dialog, look for the \*\*Path\*\* entry. 1. If the \*\*Path\*\* entry exists, double-click it. The \*\*Edit Environment Variable\*\* dialog should open. 1. Double-click inside an empty row. 1. Type the path to the \`bin\` directory of your Flutter installation. For example, if you downloaded Flutter into a \`develop\flutter\` folder inside your user directory, you'd type the following: \`\`\`plaintext %USERPROFILE%\develop\flutter\bin \`\`\` 1. Click the Flutter entry you added to select it. 1. Click \*\*Move Up\*\* until the Flutter entry sits at the top of the list. 1. To confirm your changes, click \*\*OK\*\* three times. {: type="a"} 1. If the entry doesn't exist, click \*\*New\...\*\*. The \*\*Edit Environment Variable\*\* dialog should open. 1. In the \*\*Variable Name\*\* box, type \`Path\`. 1. In the \*\*Variable Value\*\* box, type the path to the \`bin\` directory of your Flutter installation. For example, if you downloaded Flutter into a \`develop\flutter\` folder inside your user directory, you'd type the following: \`\`\`plaintext %USERPROFILE%\develop\flutter\bin \`\`\` 1. To confirm your changes, click \*\*OK\*\* three times. {: type="a"} 1.

### Apply your changes

To apply this change and get access to the \`flutter\` tool, close and reopen all open command prompts, sessions in your terminal apps, and IDEs. 1.

### Validate your setup

To ensure you successfully added the SDK to your \`PATH\`, open command prompt or your preferred terminal app, then try running the \`flutter\` and \`dart\` tools. \`\`\`console $ flutter --version $ dart --version \`\`\` If either command isn't found, check out \[Flutter installation troubleshooting]\[troubleshoot]. {: .steps} \[troubleshoot]: /install/troubleshoot ## macOS To run \`flutter\` and \`dart\` commands in a terminal on macOS, add the Flutter SDK's \`bin\` directory to the \`PATH\` environment variable. :::note The following steps assume you're using the \[default shell]\[zsh-mac] on macOS, Zsh. If you use another shell besides Zsh, check out \[this tutorial on setting your PATH]\[other-path]. ::: 1.

### Determine your Flutter SDK installation location

Copy the absolute path to the directory that you downloaded and extracted the Flutter SDK into. 1.

### Open or create the Zsh environment variable file

If it exists, open the \[Zsh environment variable file]\[zsh-files] \`\~/.zprofile\` in your preferred text editor. If it doesn't exist, create the \`\~/.zprofile\` file. 1.

### Add the Flutter SDK bin to your path

At the end of your \`\~/.zprofile\` file, use the built-in \`export\` command to update the \`PATH\` variable to include the \`bin\` directory of your Flutter installation. Replace \`\` with the path to your Flutter SDK installation. \`\`\`bash export PATH="/bin:$PATH" \`\`\` For example, if you downloaded Flutter into a \`develop/flutter\` folder inside your user directory, you'd add the following to the file: \`\`\`bash export PATH="$HOME/develop/flutter/bin:$PATH" \`\`\` 1.

### Save your changes

Save, then close, the \`\~/.zprofile\` file you edited. 1.

### Apply your changes

To apply this change and get access to the \`flutter\` tool, close and reopen all open Zsh sessions in your terminal apps and IDEs. 1.

### Validate your setup

To ensure you successfully added the SDK to your \`PATH\`, open a Zsh session in your preferred terminal, then try running the \`flutter\` and \`dart\` tools. \`\`\`console $ flutter --version $ dart --version \`\`\` If either command isn't found, check out \[Flutter installation troubleshooting]\[troubleshoot]. {: .steps} \[zsh-mac]: https\://support.apple.com/en-us/102360 \[zsh-files]: https\://zsh.sourceforge.io/Intro/intro\_3.html \[other-path]: https\://www\.cyberciti.biz/faq/unix-linux-adding-path/ \[troubleshoot]: /install/troubleshoot ## Linux To run \`flutter\` and \`dart\` commands in a terminal on Linux, add the Flutter SDK's \`bin\` directory to the \`PATH\` environment variable. 1.

### Determine your Flutter SDK installation location

Copy the absolute path to the directory that you downloaded and extracted the Flutter SDK into. 1.

### Determine your default shell

If you don't know what shell you use, check which shell starts when you open a new console window. \`\`\`console $ echo $SHELL \`\`\` 1.

### Add the Flutter SDK bin to your path

To add the \`bin\` directory of your Flutter installation to your \`PATH\`: 1. Expand the instructions for your default shell. 1. Copy the provided command. 1. Replace \`\` with the path to your Flutter SDK install. 1. Run the edited command in your preferred terminal with that shell.

***

Expand for `bash` instructions

\`\`\`console $ echo 'export PATH="/bin:$PATH"' >> \~/.bashrc \`\`\` For example, if you downloaded Flutter into a \`develop/flutter\` folder inside your user directory, you'd run the following: \`\`\`console $ echo 'export PATH="$HOME/develop/flutter/bin:$PATH"' >> \~/.bashrc \`\`\`

Expand for `zsh` instructions

\`\`\`console $ echo 'export PATH="/bin:$PATH"' >> \~/.zshenv \`\`\` For example, if you downloaded Flutter into a \`develop/flutter\` folder inside your user directory, you'd run the following: \`\`\`console $ echo 'export PATH="$HOME/develop/flutter/bin:$PATH"' >> \~/.zshenv \`\`\`

Expand for `fish` instructions

\`\`\`console $ fish\_add\_path -U -p /bin \`\`\` For example, if you downloaded Flutter into a \`develop/flutter\` folder inside your user directory, you'd run the following: \`\`\`console $ fish\_add\_path -U -p \~/develop/flutter/bin \`\`\`

Expand for `csh` instructions

\`\`\`console $ echo 'setenv PATH "/bin:$PATH"' >> \~/.cshrc \`\`\` For example, if you downloaded Flutter into a \`develop/flutter\` folder inside your user directory, you'd run the following: \`\`\`console $ echo 'setenv PATH "$HOME/develop/flutter/bin:$PATH"' >> \~/.cshrc \`\`\`

Expand for `tcsh` instructions

\`\`\`console $ echo 'setenv PATH "/bin:$PATH"' >> \~/.tcshrc \`\`\` For example, if you downloaded Flutter into a \`develop/flutter\` folder inside your user directory, you'd run the following: \`\`\`console $ echo 'setenv PATH "$HOME/develop/flutter/bin:$PATH"' >> \~/.tcshrc \`\`\`

Expand for `ksh` instructions

\`\`\`console $ echo 'export PATH="/bin:$PATH"' >> \~/.profile \`\`\` For example, if you downloaded Flutter into a \`develop/flutter\` folder inside your user directory, you'd run the following: \`\`\`console $ echo 'export PATH="$HOME/develop/flutter/bin:$PATH"' >> \~/.profile \`\`\`

Expand for `sh` instructions

\`\`\`console $ echo 'export PATH="/bin:$PATH"' >> \~/.profile \`\`\` For example, if you downloaded Flutter into a \`develop/flutter\` folder inside your user directory, you'd run the following: \`\`\`console $ echo 'export PATH="$HOME/develop/flutter/bin:$PATH"' >> \~/.profile \`\`\` 1.

### Apply your changes

To apply this change and get access to the \`flutter\` tool, close and reopen all open shell sessions in your terminal apps and IDEs. 1.

### Validate your setup

To ensure you successfully added the SDK to your \`PATH\`, open your preferred terminal with your default shell, then try running the \`flutter\` and \`dart\` tools. \`\`\`console $ flutter --version $ dart --version \`\`\` If either command isn't found, check out \[Flutter installation troubleshooting]\[troubleshoot]. {: .steps} \[troubleshoot]: /install/troubleshoot ## ChromeOS To run \`flutter\` and \`dart\` commands in a terminal on chromeOS, add the Flutter SDK's \`bin\` directory to the \`PATH\` environment variable. :::note The following steps assume you've already \[turned on Linux support]\[chromeos-linux] and that you're using Bash or the default shell on ChromeOS. If you're using a different shell besides the default or Bash, follow the \[add to path instructions for Linux]\[linux-path]{: target="\_blank"} instead. ::: 1.

### Determine your Flutter SDK installation location

Copy the absolute path to the directory that you downloaded and extracted the Flutter SDK into. 1.

### Add the Flutter SDK bin to your path

To add the \`bin\` directory of your Flutter installation to your \`PATH\`: 1. Copy the following command. 1. Replace \`\` with the path to your Flutter SDK install. 1. Run the edited command in your preferred terminal. \`\`\`console $ echo 'export PATH=":$PATH"' >> \~/.bash\_profile \`\`\` For example, if you downloaded Flutter into a \`develop/flutter\` folder inside your user directory, you'd run the following: \`\`\`console $ echo 'export PATH="$HOME/develop/flutter/bin:$PATH"' >> \~/.bash\_profile \`\`\` 1.

### Apply your changes

To apply this change and get access to the \`flutter\` tool, close and reopen all open Zsh sessions in your terminal apps and IDEs. 1.

### Validate your setup

To ensure you successfully added the SDK to your \`PATH\`, open a Zsh session in your preferred terminal, then try running the \`flutter\` and \`dart\` tools. \`\`\`console $ flutter --version $ dart --version \`\`\` If either command isn't found, check out \[Flutter installation troubleshooting]\[troubleshoot]. {: .steps} \[chromeos-linux]: https\://support.google.com/chromebook/answer/9145439 \[linux-path]: /install/add-to-path#linux \[troubleshoot]: /install/troubleshoot

----
url: https://docs.flutter.dev/learn/pathway/tutorial/implicit-animations
----

Flutter provides a rich set of animation APIs, and the simplest way to start using them is with **implicit animations**. "Implicit animations" refers to a group of widgets that automatically animate changes to their properties without you needing to manage any intermediate behavior.

auto\_awesome Discover implicit animations in Flutter

animation Animate property changes with AnimatedContainer

timeline Customize timing with duration and curves

In this lesson, you'll learn about one of the most common and versatile implicit animation widgets: [`AnimatedContainer`](https://api.flutter.dev/flutter/widgets/AnimatedContainer-class.html). With just two additional lines of code, the background color of each `Tile` animates to a new color in about half a second.

***

## Steps

1### Convert `Container` to `AnimatedContainer`keyboard\_arrow\_up

Currently, the `Tile.build` method returns a `Container` to display a letter. When the `hitType` changes, like from `HitType.none` to `HitType.hit`, the background color of the tile changes instantly. For example, from white to green in the case of `HitType.none` to `HitType.hit`.

For reference, here's the current implementation of the `Tile` widget:

dart

```
class Tile extends StatelessWidget {
  const Tile(this.letter, this.hitType, {super.key});
​
  final String letter;
  final HitType hitType;
​
  @override
  Widget build(BuildContext context) {
    return Container(
      height: 60,
      width: 60,
      decoration: BoxDecoration(
        border: Border.all(color: Colors.grey.shade300),
        color: switch (type) {
          HitType.hit => Colors.green,
          HitType.partial => Colors.yellow,
          HitType.miss => Colors.grey,
          _ => Colors.white,
        },
      ),
      child: Center(
        child: Text(
          letter.toUpperCase(),
          style: Theme.of(context).textTheme.titleLarge,
        ),
      ),
    );
  }
}
```

To make the color change animate smoothly, replace the `Container` widget with an `AnimatedContainer`.

An `AnimatedContainer` is like a `Container`, but it automatically animates changes to its properties over a specified `duration`. When properties such as `color`, `height`, `width`, `decoration`, or `alignment` change, `AnimatedContainer` interpolates between the old and new values, creating a smooth transition.

Modify your `Tile` widget as follows:

dart

```
class Tile extends StatelessWidget {
  const Tile(this.letter, this.hitType, {super.key});
​
  final String letter;
  final HitType hitType;
​
  @override
  Widget build(BuildContext context) {
    return AnimatedContainer(
      duration: Duration(milliseconds: 500),
      height: 60,
      width: 60,
      decoration: BoxDecoration(
        border: Border.all(color: Colors.grey.shade300),
        color: switch (hitType) {
          HitType.hit => Colors.green,
          HitType.partial => Colors.yellow,
          HitType.miss => Colors.grey,
          _ => Colors.white,
        },
      ),
      child: Center(
        child: Text(
          letter.toUpperCase(),
          style: Theme.of(context).textTheme.titleLarge,
        ),
      ),
    );
  }
}
```

**`duration`** is a required property that specifies how long the animation should take. In this example, passing `Duration(milliseconds: 500)` means the color transition will take half a second. You can also specify seconds, minutes, and many other units of time.

Now, when the `hitType` changes and the `Tile` widget rebuilds (because `setState` was called in `GamePage`), the color of the tile smoothly animates from its old color to the new one over the specified duration.

2### Adjust the animation curvekeyboard\_arrow\_up

To add a bit of customization to an implicit animation, you can pass it a different [`Curve`](https://api.flutter.dev/flutter/animation/Curves-class.html). Different curves change the speed of the animation at different points throughout the animation.

For example, the default curve for Flutter animations is `Curves.linear`. This gif shows how the animation curve behaves:

Compare that to `Curve.bounceIn`, another common curve:

To change the `Curve` of this animation, update the code to the following:

dart

```
class Tile extends StatelessWidget {
  const Tile(this.letter, this.hitType, {super.key});
​
  final String letter;
  final HitType hitType;
​
  @override
  Widget build(BuildContext context) {
    return AnimatedContainer(
      duration: Duration(milliseconds: 500),
      curve: Curves.bounceIn, // NEW
      height: 60,
      width: 60,
      decoration: BoxDecoration(
        border: Border.all(color: Colors.grey.shade300),
        color: switch (hitType) {
          HitType.hit => Colors.green,
          HitType.partial => Colors.yellow,
          HitType.miss => Colors.grey,
          _ => Colors.white,
        },
      ),
      child: Center(
        child: Text(
          letter.toUpperCase(),
          style: Theme.of(context).textTheme.titleLarge,
        ),
      ),
    );
  }
}
```

There are many different curves provided by the Flutter SDK, so feel free to try them out by passing different types to the `curve` parameter.

Implicit animations like `AnimatedContainer` are powerful because you just tell the widget what the new state should be, and it handles the "how" of the animation.

For complex, custom animations, you can write your own animated widgets. If you're curious, try it out in the [animations tutorial](/ui/animations/tutorial).

3### Reviewkeyboard\_arrow\_up

auto\_awesome Discovered implicit animations keyboard\_arrow\_up

Implicit animations are widgets that automatically animate changes to their properties. You specify the new state, and the widget handles the animation for you without requiring manual animation management.

animation Animated the tiles with AnimatedContainer keyboard\_arrow\_up

By replacing `Container` with `AnimatedContainer` and adding a `duration`, your tiles now smoothly transition between colors. With just two lines of code, you added professional polish to your app!

timeline Customized timing with duration and curves keyboard\_arrow\_up

The `duration` property controls how long the animation takes, while `curve` changes the animation's feel. You tried `Curves.decelerate`, but you can also try other values like `Curves.easeIn`, `Curves.bounceOut`, or `Curves.elasticIn`.

celebration Completed the Birdle game keyboard\_arrow\_up

You've built a complete Wordle-style game with custom widgets, dynamic layouts, user input, state management, and smooth animations. You now have the foundational skills to build your own Flutter apps!

4### Test yourselfkeyboard\_arrow\_up

### Implicit Animations Quiz

1 / 2

**What widget can you use to automatically animate changes to properties like color, size, and decoration?**

1. Container

   Not quite.

   Container doesn't animate; property changes happen instantly.

2. AnimatedContainer

   That's right!

   AnimatedContainer automatically animates changes to its properties over the specified duration.

3. AnimationController

   Not quite.

   AnimationController is for explicit animations; AnimatedContainer is simpler for basic animations.

4. TransitionContainer

   Not quite.

   There is no TransitionContainer widget; use AnimatedContainer for implicit animations.

**What does the `duration` property control in an AnimatedContainer?**

1. How long the widget stays on screen before disappearing.

   Not quite.

   Duration controls animation time, not widget visibility.

2. How long the animation takes to transition from the old value to the new value.

   That's right!

   The duration specifies the time over which the property change is animated.

3. The delay before the animation starts.

   Not quite.

   Duration is about animation length; delays require separate configuration.

4. How many times the animation repeats.

   Not quite.

   Implicit animations run once per state change; repetition requires explicit animation controllers.

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/learn/pathway/tutorial/implicit-animations.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/learn/pathway/tutorial/implicit-animations\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/learn/pathway/tutorial/implicit-animations.md "Report an issue with this page").

----
url: https://docs.flutter.dev/ui/accessibility/web-accessibility
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Web accessibility](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

## Background

[#](#background)

Flutter supports web accessibility by translating its internal Semantics tree into an accessible HTML DOM structure that screen readers can understand. Since Flutter renders its UI on a single canvas, it needs a special layer to expose the UI's meaning and structure to web browsers.

## Opt-in web accessibility

[#](#opt-in-web-accessibility)

### Invisible button

[#](#invisible-button)

For performance reasons, Flutter's web accessibility is not on by default. To turn on accessibility, the user needs to press an invisible button with `aria-label="Enable accessibility"`. After pressing the button, the DOM tree will reflect all accessibility information for the widgets.

### Turn on accessibility mode in code

[#](#turn-on-accessibility-mode-in-code)

An alternative approach is to turn on accessibility mode by adding the following code when running an app.

dart

```
import 'package:flutter/semantics.dart';
​
void main() {
  runApp(const MyApp());
  if (kIsWeb) {
    SemanticsBinding.instance.ensureSemantics();
  }
}
```

## Enhancing Accessibility with Semantic Roles

[#](#enhancing-accessibility-with-semantic-roles)

### What are Semantic Roles?

[#](#what-are-semantic-roles)

Semantic roles define the purpose of a UI element, helping screen readers and other assistive tools interpret and present your application effectively to users. For example, a role can indicate if a widget is a button, a link, to users. For example, a role can indicate whether a widget is a button, a link, a heading, a slider, or part of a table.

While Flutter's standard widgets often provide these semantics automatically, a custom component without a clearly defined role can be incomprehensible to a screen reader user.

By assigning appropriate roles, you ensure that:

* Screen readers can announce the type and purpose of elements correctly.
* Users can navigate your application more effectively using assistive technologies.
* Your application adheres to web accessibility standards, improving usability.

### Using `SemanticsRole` in Flutter for web

[#](#using-semanticsrole-in-flutter-for-web)

Flutter provides the [`Semantics` widget](https://api.flutter.dev/flutter/widgets/Semantics-class.html) with the [`SemanticsRole` enum](https://api.flutter.dev/flutter/dart-ui/SemanticsRole.html) to allow developers to assign specific roles to their widgets. When your Flutter web app is rendered, these Flutter-specific roles are translated into corresponding ARIA roles in the web page's HTML structure.

**1. Automatic Semantics from Standard Widgets**

Many standard Flutter widgets, like `TabBar`, `MenuAnchor`, and `Table`, automatically include semantic information along with their roles. Whenever possible, prefer using these standard widgets as they handle many accessibility aspects out-of-the-box.

**2. Explicitly adding or overriding roles**

For custom components or when the default semantics aren't sufficient, use the `Semantics` widget to define the role:

Here's an example of how you might explicitly define a list and its items:

dart

```
import 'package:material_ui/material_ui.dart';
import 'package:flutter/semantics.dart';
​
​
class MyCustomListWidget extends StatelessWidget {
  const MyCustomListWidget({Key? key}) : super(key: key);
​
  @override
  Widget build(BuildContext context) {
    // This example shows how to explicitly assign list and listitem roles
    // when building a custom list structure.
    return Semantics(
      role: SemanticsRole.list,
      explicitChildNodes: true,
      child: Column(
        children: <Widget>[
          Semantics(
            role: SemanticsRole.listItem,
            child: const Padding(
              padding: EdgeInsets.all(8.0),
              child: Text('Content of the first custom list item.'),
            ),
          ),
          Semantics(
            role: SemanticsRole.listItem,
            child: const Padding(
              padding: EdgeInsets.all(8.0),
              child: Text('Content of the second custom list item.'),
            ),
          ),
        ],
      ),
    );
  }
}
```

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-09-29. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/ui/accessibility/web-accessibility.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/ui/accessibility/web-accessibility\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/ui/accessibility/web-accessibility.md "Report an issue with this page").

----
url: https://docs.flutter.dev/tools/devtools/console.md
----

\# Use the Debug console > Learn how to use the DevTools console. The DevTools Debug console allows you to watch an application's standard output (\`stdout\`), evaluate expressions for a paused or running app in debug mode, and analyze inbound and outbound references for objects. :::note This page is up to date for DevTools 2.23.0. ::: The Debug console is available from the \[Inspector]\[], \[Debugger]\[], and \[Memory]\[] views. \[Inspector]: /tools/devtools/inspector \[Debugger]: /tools/devtools/debugger \[Memory]: /tools/devtools/memory ## Watch application output The console shows the application's standard output (\`stdout\`): !\[Screenshot of stdout in Console view]\(/assets/images/docs/tools/devtools/console-stdout.png) ## Explore inspected widgets If you click a widget on the \*\*Inspector\*\* screen, the variable for this widget displays in the \*\*Console\*\*: !\[Screenshot of inspected widget in Console view]\(/assets/images/docs/tools/devtools/console-inspect-widget.png){:width="100%"} ## Evaluate expressions In the console, you can evaluate expressions for a paused or running application, assuming that you are running your app in debug mode: !\[Screenshot showing evaluating an expression in the console]\(/assets/images/docs/tools/devtools/console-evaluate-expressions.png) To assign an evaluated object to a variable, use \`$0\`, \`$1\` (through \`$5\`) in the form of \`var x = $0\`: !\[Screenshot showing how to evaluate variables]\(/assets/images/docs/tools/devtools/console-evaluate-variables.png){:width="100%"} ## Browse heap snapshot To drop a variable to the console from a heap snapshot, do the following: 1. Navigate to \*\*Devtools > Memory > Diff Snapshots\*\*. 1. Record a memory heap snapshot. 1. Click on the context menu \`\[⋮]\` to view the number of \*\*Instances\*\* for the desired \*\*Class\*\*. 1. Select whether you want to store a single instance as a console variable, or whether you want to store \_all\_ currently alive instances in the app. !\[Screenshot showing how to browse the heap snapshots]\(/assets/images/docs/tools/devtools/browse-heap-snapshot.png){:width="100%"} The Console screen displays both live and static inbound and outbound references, as well as field values: !\[Screenshot showing inbound and outbound references in Console]\(/assets/images/docs/tools/devtools/console-references.png){:width="100%"}

----
url: https://docs.flutter.dev/ui/animations/implicit-animations
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Implicit animations](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

With Flutter's [animation library](https://api.flutter.dev/flutter/animation/animation-library.html), you can add motion and create visual effects for the widgets in your UI. One part of the library is an assortment of widgets that manage animations for you. These widgets are collectively referred to as *implicit animations*, or *implicitly animated widgets*, deriving their name from the [`ImplicitlyAnimatedWidget`](https://api.flutter.dev/flutter/widgets/ImplicitlyAnimatedWidget-class.html) class that they implement.

With implicit animations, you don't need to manage an `AnimationController` or `Ticker`. Instead, you pass a `duration` and an optional `curve` to an implicitly animated widget, such as `AnimatedOpacity`. When you update the widget with a new target value, such as after calling `setState()`, the widget automatically animates to that value:

dart

```
import 'package:material_ui/material_ui.dart';
​
class FadeBoxDemo extends StatefulWidget {
  const FadeBoxDemo({super.key});
​
  @override
  State<FadeBoxDemo> createState() => _FadeBoxDemoState();
}
​
class _FadeBoxDemoState extends State<FadeBoxDemo> {
  bool _visible = true;
​
  @override
  Widget build(BuildContext context) {
    return Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        AnimatedOpacity(
          opacity: _visible ? 1 : 0,
          duration: const Duration(milliseconds: 500),
          curve: Curves.easeInOut,
          child: const FlutterLogo(size: 100),
        ),
        const SizedBox(height: 16),
        ElevatedButton(
          onPressed: () => setState(() => _visible = !_visible),
          child: const Text('Toggle opacity'),
        ),
      ],
    );
  }
}
```

**App source:** [implicit animation example](https://github.com/flutter/website/tree/main/examples/animation/implicit)

When no built-in implicitly animated widget covers the property you want to animate, you can build a custom implicit animation with [`TweenAnimationBuilder`](https://api.flutter.dev/flutter/widgets/TweenAnimationBuilder-class.html).

The following set of resources provides many ways to learn about implicit animations in Flutter.

## Documentation

[#](#documentation)

* [Animations in Flutter codelab](https://codelabs.developers.google.com/advanced-flutter-animations)

  Learn about implicit and explicit animations and get hands-on experience adding implicit animations to a complete Flutter app.

* [`AnimatedContainer` sample](/cookbook/animation/animated-container)

  A step-by-step recipe for using the [`AnimatedContainer`](https://api.flutter.dev/flutter/widgets/AnimatedContainer-class.html) implicitly animated widget.

* [Fade a widget in and out](/cookbook/animation/opacity-animation)

  A cookbook recipe showing how to fade a widget using [`AnimatedOpacity`](https://api.flutter.dev/flutter/widgets/AnimatedOpacity-class.html).

* [`TweenAnimationBuilder`](https://api.flutter.dev/flutter/widgets/TweenAnimationBuilder-class.html) API page

  Create custom implicit animations for any property using a `Tween` and a `builder` callback.

* [`ImplicitlyAnimatedWidget`](https://api.flutter.dev/flutter/widgets/ImplicitlyAnimatedWidget-class.html) API page

  All implicit animations extend the `ImplicitlyAnimatedWidget` class.

## Flutter in Focus videos

[#](#flutter-in-focus-videos)

Flutter in Focus videos feature 5-10 minute tutorials with real code that cover techniques that every Flutter dev needs to know from top to bottom. The following videos cover topics that are relevant to implicit animations.

[Watch on YouTube in a new tab: "Flutter implicit animation basics"](https://www.youtube.com/watch/IVTjpW3W33s)

[Watch on YouTube in a new tab: "Create custom implicit animations with TweenAnimationBuilder"](https://www.youtube.com/watch/6KiPEqzJIKQ)

## The Boring Show

[#](#the-boring-show)

Watch the Boring Show to follow Google engineers building apps from scratch in Flutter. The following episode covers using implicit animations in a news aggregator app.

[Watch on YouTube in a new tab: "Adding implicit animations to a news application"](https://www.youtube.com/watch/8ehlWchLVlQ)

## Widget of the Week videos

[#](#widget-of-the-week-videos)

A weekly series of short animated videos each showing the important features of one particular widget. In about 60 seconds, you'll see real code for each widget with a demo about how it works. The following Widget of the Week videos cover implicitly animated widgets:

[Watch on YouTube in a new tab: "AnimatedOpacity - Flutter widget of the week"](https://www.youtube.com/watch/QZAvjqOqiLY)

[Watch on YouTube in a new tab: "AnimatedPadding - Flutter widget of the week"](https://www.youtube.com/watch/PY2m0fhGNz4)

[Watch on YouTube in a new tab: "AnimatedPositioned - Flutter widget of the week"](https://www.youtube.com/watch/hC3s2YdtWt8)

[Watch on YouTube in a new tab: "AnimatedSwitcher - Flutter widget of the week"](https://www.youtube.com/watch/2W7POjFb88g)

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-10-02. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/ui/animations/implicit-animations.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/ui/animations/implicit-animations\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/ui/animations/implicit-animations.md "Report an issue with this page").

----
url: https://docs.flutter.dev/app-architecture/case-study/testing.md
----

\# Testing each layer > How to test an app that implements MVVM architecture. ## Testing the UI layer One way to determine whether your architecture is sound is considering how easy (or difficult) the application is to test. Because view models and views have well-defined inputs, their dependencies can easily be mocked or faked, and unit tests are easily written. ### ViewModel unit tests To test the UI logic of the view model, you should write unit tests that don't rely on Flutter libraries or testing frameworks. Repositories are a view model's only dependencies (unless you're implementing \[use-cases]\[]), and writing \`mocks\` or \`fakes\` of the repository is the only setup you need to do. In this example test, a fake called \`FakeBookingRepository\` is used. \`\`\`dart title=home\_screen\_test.dart void main() { group('HomeViewModel tests', () { test('Load bookings', () { // HomeViewModel.\_load is called in the constructor of HomeViewModel. final viewModel = HomeViewModel( bookingRepository: FakeBookingRepository() ..createBooking(kBooking), userRepository: FakeUserRepository(), ); expect(viewModel.bookings.isNotEmpty, true); }); }); } \`\`\` The \[\`FakeBookingRepository\`]\[] class implements \[\`BookingRepository\`]\[]. In the \[data layer section]\[] of this case-study, the \`BookingRepository\` class is explained thoroughly. \`\`\`dart title=fake\_booking\_repository.dart class FakeBookingRepository implements BookingRepository { List bookings = List.empty(growable: true); @override Future> createBooking(Booking booking) async { bookings.add(booking); return Result.ok(null); } // ... } \`\`\` :::note If you're using this architecture with \[use-cases]\[], these would similarly need to be faked. ::: ### View widget tests Once you've written tests for your view model, you've already created the fakes you need to write widget tests as well. The following example shows how the \`HomeScreen\` widget tests are set up using the \`HomeViewModel\` and needed repositories: \`\`\`dart title=home\_screen\_test.dart void main() { group('HomeScreen tests', () { late HomeViewModel viewModel; late MockGoRouter goRouter; late FakeBookingRepository bookingRepository; setUp(() { bookingRepository = FakeBookingRepository() ..createBooking(kBooking); viewModel = HomeViewModel( bookingRepository: bookingRepository, userRepository: FakeUserRepository(), ); goRouter = MockGoRouter(); when(() => goRouter.push(any())).thenAnswer((\_) => Future.value(null)); }); // ... }); } \`\`\` This setup creates the two fake repositories needed, and passes them into a \`HomeViewModel\` object. This class doesn't need to be faked. :::note The code also defines a \`MockGoRouter\`. The router is mocked using \[\`package:mocktail\`]\[], and is outside the scope of this case-study. You can find general testing guidance in \[Flutter's testing documentation]\[]. ::: After the view model and its dependencies are defined, the Widget tree that will be tested needs to be created. In the tests for \`HomeScreen\`, a \`loadWidget\` method is defined. \`\`\`dart title=home\_screen\_test.dart highlightLines=11-23 void main() { group('HomeScreen tests', () { late HomeViewModel viewModel; late MockGoRouter goRouter; late FakeBookingRepository bookingRepository; setUp( // ... ); void loadWidget(WidgetTester tester) async { await testApp( tester, ChangeNotifierProvider.value( value: FakeAuthRepository() as AuthRepository, child: Provider.value( value: FakeItineraryConfigRepository() as ItineraryConfigRepository, child: HomeScreen(viewModel: viewModel), ), ), goRouter: goRouter, ); } // ... }); } \`\`\` This method turns around and calls \`testApp\`, a generalized method used for all widget tests in the compass app. It looks like this: \`\`\`dart title=testing/app.dart void testApp( WidgetTester tester, Widget body, { GoRouter? goRouter, }) async { tester.view\.devicePixelRatio = 1.0; await tester.binding.setSurfaceSize(const Size(1200, 800)); await mockNetworkImages(() async { await tester.pumpWidget( MaterialApp( localizationsDelegates: \[ GlobalWidgetsLocalizations.delegate, GlobalMaterialLocalizations.delegate, AppLocalizationDelegate(), ], theme: AppTheme.lightTheme, home: InheritedGoRouter( goRouter: goRouter ?? MockGoRouter(), child: Scaffold( body: body, ), ), ), ); }); } \`\`\` This function's only job is to create a widget tree that can be tested. The \`loadWidget\` method passes in the unique parts of a widget tree for testing. In this case, that includes the \`HomeScreen\` and its view model, as well as some additional faked repositories that are higher in the widget tree. The most important thing to take away is that view and view model tests only require mocking repositories if your architecture is sound. ## Testing the data layer Similar to the UI layer, the components of the data layer have well-defined inputs and outputs, making both sides fake-able. To write unit tests for any given repository, mock the services that it depends on. The following example shows a unit test for the \`BookingRepository\`. \`\`\`dart title=booking\_repository\_remote\_test.dart void main() { group('BookingRepositoryRemote tests', () { late BookingRepository bookingRepository; late FakeApiClient fakeApiClient; setUp(() { fakeApiClient = FakeApiClient(); bookingRepository = BookingRepositoryRemote( apiClient: fakeApiClient, ); }); test('should get booking', () async { final result = await bookingRepository.getBooking(0); final booking = result.asOk.value; expect(booking, kBooking); }); }); } \`\`\` To learn more about writing mocks and fakes, check out examples in the \[Compass App \`testing\` directory]\[] or read \[Flutter's testing documentation]\[]. \[use-cases]: /app-architecture/guide#optional-domain-layer \[\`FakeBookingRepository\`]: https\://github.com/flutter/samples/blob/main/compass\_app/app/testing/fakes/repositories/fake\_booking\_repository.dart \[\`BookingRepository\`]: https\://github.com/flutter/samples/tree/main/compass\_app/app/lib/data/repositories/booking \[data layer section]: /app-architecture/case-study/data-layer \[\`package:mocktail\`]: https\://pub.dev/packages/mocktail \[Flutter's testing documentation]: /testing/overview \[Compass App \`testing\` directory]: https\://github.com/flutter/samples/tree/main/compass\_app/app/testing ## Feedback As this section of the website is evolving, we \[welcome your feedback]\[]! \[welcome your feedback]: https\://google.qualtrics.com/jfe/form/SV\_4T0XuR9Ts29acw6?page="case-study/testing"

----
url: https://docs.flutter.dev/app-architecture/design-patterns/result.md
----

\# Error handling with Result objects > Improve error handling across classes with Result objects.

<!--?code-excerpt path-base="app-architecture/result"?-->

Dart provides a built-in error handling mechanism with the ability to throw and catch exceptions. As mentioned in the \[Error handling documentation]\[], Dart's exceptions are unhandled exceptions. This means that methods that throw exceptions don’t need to declare them, and calling methods aren't required to catch them either. This can lead to situations where exceptions are not handled properly. In large projects, developers might forget to catch exceptions, and the different application layers and components could throw exceptions that aren’t documented. This can lead to errors and crashes. In this guide, you will learn about this limitation and how to mitigate it using the \_result\_ pattern. ## Error flow in Flutter applications Applications following the \[Flutter architecture guidelines]\[] are usually composed of view models, repositories, and services, among other parts. When a function in one of these components fails, it should communicate the error to the calling component. Typically, that's done with exceptions. For example, an API client service failing to communicate with the remote server might throw an HTTP Error Exception. The calling component, for example a Repository, would have to either capture this exception or ignore it and let the calling view model handle it. This can be observed in the following example. Consider these classes: - A service, \`ApiClientService\`, performs API calls to a remote service. - A repository, \`UserProfileRepository\`, provides the \`UserProfile\` provided by the \`ApiClientService\`. - A view model, \`UserProfileViewModel\`, uses the \`UserProfileRepository\`. The \`ApiClientService\` contains a method, \`getUserProfile\`, that throws exceptions in certain situations: - The method throws an \`HttpException\` if the response code isn’t 200. - The JSON parsing method throws an exception if the response isn't formatted correctly. - The HTTP client might throw an exception due to networking issues. The following code tests for a variety of possible exceptions:

<!--?code-excerpt "lib/no_result.dart (ApiClientService)"?-->

\`\`\`dart class ApiClientService { // ··· Future getUserProfile() async { try { final request = await client.get(\_host, \_port, '/user'); final response = await request.close(); if (response.statusCode == 200) { final stringData = await response.transform(utf8.decoder).join(); return UserProfile.fromJson(jsonDecode(stringData)); } else { throw const HttpException('Invalid response'); } } finally { client.close(); } } } \`\`\` The \`UserProfileRepository\` doesn’t need to handle the exceptions from the \`ApiClientService\`. In this example, it just returns the value from the API Client.

<!--?code-excerpt "lib/no_result.dart (UserProfileRepository)"?-->

\`\`\`dart class UserProfileRepository { // ··· Future getUserProfile() async { return await \_apiClientService.getUserProfile(); } } \`\`\` Finally, the \`UserProfileViewModel\` should capture all exceptions and handle the errors. This can be done by wrapping the call to the \`UserProfileRepository\` with a try-catch:

<!--?code-excerpt "lib/no_result.dart (UserProfileViewModel)"?-->

\`\`\`dart class UserProfileViewModel extends ChangeNotifier { // ··· Future load() async { try { \_userProfile = await userProfileRepository.getUserProfile(); notifyListeners(); } on Exception catch (exception) { // handle exception } } } \`\`\` In reality, a developer might forget to properly capture exceptions and end up with the following code. It compiles and runs, but crashes if one of the exceptions mentioned previously occurs:

<!--?code-excerpt "lib/no_result.dart (UserProfileViewModelNoTryCatch)" replace="/NoTryCatch//g"?-->

\`\`\`dart class UserProfileViewModel extends ChangeNotifier { // ··· Future load() async { \_userProfile = await userProfileRepository.getUserProfile(); notifyListeners(); } } \`\`\` You can attempt to solve this by documenting the \`ApiClientService\`, warning about the possible exceptions it might throw. However, since the view model doesn’t use the service directly, other developers working in the codebase might miss this information. ## Using the result pattern An alternative to throwing exceptions is to wrap the function output in a \`Result\` object. When the function runs successfully, the \`Result\` contains the returned value. However, if the function does not complete successfully, the \`Result\` object contains the error. A \`Result\` is a \[\`sealed\`]\[] class that can either subclass \`Ok\` or the \`Error\` class. Return the successful value with the subclass \`Ok\`, and the captured error with the subclass \`Error\`. The following code shows a sample \`Result\` class that has been simplified for demo purposes. A full implementation is at the end of this page.

<!--?code-excerpt "lib/simple_result.dart"?-->

\`\`\`dart /// Utility class that simplifies handling errors. /// /// Return a \[Result] from a function to indicate success or failure. /// /// A \[Result] is either an \[Ok] with a value of type \[T] /// or an \[Error] with an \[Exception]. /// /// Use \[Result.ok] to create a successful result with a value of type \[T]. /// Use \[Result.error] to create an error result with an \[Exception]. sealed class Result { const Result(); /// Creates an instance of Result containing a value factory Result.ok(T value) => Ok(value); /// Create an instance of Result containing an error factory Result.error(Exception error) => Error(error); } /// Subclass of Result for values final class Ok extends Result { const Ok(this.value); /// Returned value in result final T value; } /// Subclass of Result for errors final class Error extends Result { const Error(this.error); /// Returned error in result final Exception error; } \`\`\` In this example, the \`Result\` class uses a generic type \`T\` to represent any return value, which can be a primitive Dart type like \`String\` or an \`int\` or a custom class like \`UserProfile\`. ### Creating a \`Result\` object For functions using the \`Result\` class to return values, instead of a value, the function returns a \`Result\` object containing the value. For example, in the \`ApiClientService\`, \`getUserProfile\` is changed to return a \`Result\`:

<!--?code-excerpt "lib/main.dart (ApiClientService1)"?-->

\`\`\`dart class ApiClientService { // ··· Future> getUserProfile() async { // ··· } } \`\`\` Instead of returning the \`UserProfile\` directly, it returns a \`Result\` object containing a \`UserProfile\`. To facilitate using the \`Result\` class, it contains two named constructors, \`Result.ok\` and \`Result.error\`. Use them to construct the \`Result\` depending on desired output. As well, capture any exceptions thrown by the code and wrap them into the \`Result\` object. For example, here the \`getUserProfile()\` method has been changed to use the \`Result\` class:

<!--?code-excerpt "lib/main.dart (ApiClientService2)"?-->

\`\`\`dart class ApiClientService { // ··· Future> getUserProfile() async { try { final request = await client.get(\_host, \_port, '/user'); final response = await request.close(); if (response.statusCode == 200) { final stringData = await response.transform(utf8.decoder).join(); return Result.ok(UserProfile.fromJson(jsonDecode(stringData))); } else { return const Result.error(HttpException('Invalid response')); } } on Exception catch (exception) { return Result.error(exception); } finally { client.close(); } } } \`\`\` The original return statement was replaced with a statement that returns the value using \`Result.ok\`. The \`throw HttpException()\` was replaced with a statement that returns \`Result.error(HttpException())\`, wrapping the error into a \`Result\`. As well, the method is wrapped with a \`try-catch\` block to capture any exceptions thrown by the Http client or the JSON parser into a \`Result.error\`. The repository class also needs to be modified, and instead of returning a \`UserProfile\` directly, now it returns a \`Result\`.

<!--?code-excerpt "lib/main.dart (getUserProfile1)" replace="/1//g"?-->

\`\`\`dart Future> getUserProfile() async { return await \_apiClientService.getUserProfile(); } \`\`\` ### Unwrapping the Result object Now the view model doesn't receive the \`UserProfile\` directly, but instead it receives a \`Result\` containing a \`UserProfile\`. This forces the developer implementing the view model to unwrap the \`Result\` to obtain the \`UserProfile\`, and avoids having uncaught exceptions.

<!--?code-excerpt "lib/main.dart (UserProfileViewModel)"?-->

\`\`\`dart class UserProfileViewModel extends ChangeNotifier { // ··· UserProfile? userProfile; Exception? error; Future load() async { final result = await userProfileRepository.getUserProfile(); switch (result) { case Ok(): userProfile = result.value; case Error(): error = result.error; } notifyListeners(); } } \`\`\` The \`Result\` class is implemented using a \`sealed\` class, meaning it can only be of type \`Ok\` or \`Error\`. This allows the code to evaluate the result with a \[switch result or expression]\[]. In the \`Ok\` case, obtain the value using the \`value\` property. In the \`Error\` case, obtain the error object using the \`error\` property. ## Improving control flow Wrapping code in a \`try-catch\` block ensures that thrown exceptions are caught and not propagated to other parts of the code. Consider the following code.

<!--?code-excerpt "lib/no_result.dart (UserProfileRepository2)" replace="/2//g"?-->

\`\`\`dart class UserProfileRepository { // ··· Future getUserProfile() async { try { return await \_apiClientService.getUserProfile(); } catch (e) { try { return await \_databaseService.createTemporaryUser(); } catch (e) { throw Exception('Failed to get user profile'); } } } } \`\`\` In this method, the \`UserProfileRepository\` attempts to obtain the \`UserProfile\` using the \`ApiClientService\`. If it fails, it tries to create a temporary user in a \`DatabaseService\`. Because either service method can fail, the code must catch the exceptions in both cases. This can be improved using the \`Result\` pattern:

<!--?code-excerpt "lib/main.dart (getUserProfile)"?-->

\`\`\`dart Future> getUserProfile() async { final apiResult = await \_apiClientService.getUserProfile(); if (apiResult is Ok) { return apiResult; } final databaseResult = await \_databaseService.createTemporaryUser(); if (databaseResult is Ok) { return databaseResult; } return Result.error(Exception('Failed to get user profile')); } \`\`\` In this code, if the \`Result\` object is an \`Ok\` instance, then the function returns that object; otherwise, it returns \`Result.Error\`. ## Putting it all together In this guide, you have learned how to use a \`Result\` class to return result values. The key takeaways are: - \`Result\` classes force the calling method to check for errors, reducing the amount of bugs caused by uncaught exceptions. - \`Result\` classes help improve control flow compared to try-catch blocks. - \`Result\` classes are \`sealed\` and can only return \`Ok\` or \`Error\` instances, allowing the code to unwrap them with a switch statement. Below you can find the full \`Result\` class as implemented in the \[Compass App example]\[] for the \[Flutter architecture guidelines]\[]. :::note Check \[pub.dev]\[] for different ready-to-use implementations of the \`Result\` class, such as the \[\`result\_dart\`]\[], \[\`result\_type\`]\[], and \[\`multiple\_result\`]\[] packages. :::

<!--?code-excerpt "lib/result.dart (Result)"?-->

\`\`\`dart /// Utility class that simplifies handling errors. /// /// Return a \[Result] from a function to indicate success or failure. /// /// A \[Result] is either an \[Ok] with a value of type \[T] /// or an \[Error] with an \[Exception]. /// /// Use \[Result.ok] to create a successful result with a value of type \[T]. /// Use \[Result.error] to create an error result with an \[Exception]. /// /// Evaluate the result using a switch statement: /// \`\`\`dart /// switch (result) { /// case Ok(): { /// print(result.value); /// } /// case Error(): { /// print(result.error); /// } /// } /// \`\`\` sealed class Result { const Result(); /// Creates a successful \[Result], completed with the specified \[value]. const factory Result.ok(T value) = Ok.\_; /// Creates an error \[Result], completed with the specified \[error]. const factory Result.error(Exception error) = Error.\_; } /// A successful \[Result] with a returned \[value]. final class Ok extends Result { const Ok.\_(this.value); /// The returned value of this result. final T value; @override String toString() => 'Result<$T>.ok($value)'; } /// An error \[Result] with a resulting \[error]. final class Error extends Result { const Error.\_(this.error); /// The resulting error of this result. final Exception error; @override String toString() => 'Result<$T>.error($error)'; } \`\`\` \[Error handling documentation]: https\://dart.dev/language/error-handling \[Flutter architecture guidelines]: /app-architecture \[Compass App example]: https\://github.com/flutter/samples/tree/main/compass\_app \[pub.dev]: https\://pub.dev \[\`result\_dart\`]: https\://pub.dev/packages/result\_dart \[\`result\_type\`]: https\://pub.dev/packages/result\_type \[\`multiple\_result\`]: https\://pub.dev/packages/multiple\_result \[\`sealed\`]: https\://dart.dev/language/class-modifiers#sealed \[switch result or expression]: https\://dart.dev/language/branches#switch-statements

----
url: https://docs.flutter.dev/platform-integration/desktop.md
----

\# Desktop support for Flutter > General information about Flutter support for desktop apps. Flutter provides support for compiling a native Windows, macOS, or Linux desktop app. Flutter's desktop support also extends to plugins—you can install existing plugins that support the Windows, macOS, or Linux platforms, or you can create your own. :::note This page covers developing apps for all desktop platforms. Once you've read this, you can dive into specific platform information at the following links: \* \[Building Windows apps with Flutter]\[] \* \[Building macOS apps with Flutter]\[] \* \[Building Linux apps with Flutter]\[] ::: \[Building Windows apps with Flutter]: /platform-integration/windows/building \[Building macOS apps with Flutter]: /platform-integration/macos/building \[Building Linux apps with Flutter]: /platform-integration/linux/building ## Create a new project You can use the following steps to create a new project with desktop support. ### Set up desktop devtools Consult the guide for your target desktop environment: \* \[Install Linux desktop devtools]\[Linux-devtools] \* \[Install macOS desktop devtools]\[macOS-devtools] \* \[Install Windows desktop devtools]\[Windows-devtools] \[Linux-devtools]: /platform-integration/linux/setup#set-up-tooling \[macOS-devtools]: /platform-integration/macos/setup#set-up-tooling \[Windows-devtools]: /platform-integration/windows/setup#set-up-tooling If \`flutter doctor\` finds problems or missing components for a platform that you don't want to develop for, you can ignore those warnings. Or you can disable the platform altogether using the \`flutter config\` command, for example: \`\`\`console $ flutter config --no-enable-ios \`\`\` Other available flags: \* \`--no-enable-windows-desktop\` \* \`--no-enable-linux-desktop\` \* \`--no-enable-macos-desktop\` \* \`--no-enable-web\` \* \`--no-enable-android\` \* \`--no-enable-ios\` After enabling desktop support, restart your IDE so that it can detect the new device. ### Create and run Creating a new project with desktop support is no different from \[creating a new Flutter project]\[] for other platforms. Once you've configured your environment for desktop support, you can create and run a desktop application either in the IDE or from the command line. \[creating a new Flutter project]: /reference/create-new-app #### Using an IDE After you've configured your environment to support desktop, make sure you restart the IDE if it was already running. Create a new application in your IDE and it automatically creates iOS, Android, web, and desktop versions of your app. From the device pulldown, select \*\*windows (desktop)\*\*, \*\*macOS (desktop)\*\*, or \*\*linux (desktop)\*\* and run your application to see it launch on the desktop. #### From the command line To create a new application that includes desktop support (in addition to mobile and web support), run the following commands, substituting \`my\_app\` with the name of your project: \`\`\`console $ flutter create my\_app $ cd my\_app \`\`\` To launch your application from the command line, enter one of the following commands from the top of the package: \`\`\`console C:\\> flutter run -d windows $ flutter run -d macos $ flutter run -d linux \`\`\` :::note If you do not supply the \`-d\` flag, \`flutter run\` lists the available targets to choose from. ::: ## Build a release app To generate a release build, run one of the following commands: \`\`\`console PS C:\\> flutter build windows $ flutter build macos $ flutter build linux \`\`\` ## Add desktop support to an existing Flutter app To add desktop support to an existing Flutter project, run the following command in a terminal from the root project directory: \`\`\`console $ flutter create --platforms=windows,macos,linux . \`\`\` This adds the necessary desktop files and directories to your existing Flutter project. To add only specific desktop platforms, change the \`platforms\` list to include only the platform(s) you want to add. ## Plugin support Flutter on the desktop supports using and creating plugins. To use a plugin that supports desktop, follow the steps for plugins in \[using packages]\[]. Flutter automatically adds the necessary native code to your project, as with any other platform. ### Writing a plugin When you start building your own plugins, you'll want to keep federation in mind. Federation is the ability to define several different packages, each targeted at a different set of platforms, brought together into a single plugin for ease of use by developers. For example, the Windows implementation of the \`url\_launcher\` is really \`url\_launcher\_windows\`, but a Flutter developer can simply add the \`url\_launcher\` package to their \`pubspec.yaml\` as a dependency and the build process pulls in the correct implementation based on the target platform. Federation is handy because different teams with different expertise can build plugin implementations for different platforms. You can add a new platform implementation to any endorsed federated plugin on pub.dev, so long as you coordinate this effort with the original plugin author. For more information, including information about endorsed plugins, see the following resources: \* \[Developing packages and plugins]\[], particularly the \[Federated plugins]\[] section. \* \[How to write a Flutter web plugin, part 2]\[], covers the structure of federated plugins and contains information applicable to desktop plugins. \* \[Modern Flutter Plugin Development]\[] covers recent enhancements to Flutter's plugin support. \[using packages]: /packages-and-plugins/using-packages \[Developing packages and plugins]: /packages-and-plugins/developing-packages \[Federated plugins]: /packages-and-plugins/developing-packages#federated-plugins \[How to write a Flutter web plugin, part 2]: https\://blog.flutter.dev/how-to-write-a-flutter-web-plugin-part-2-afdddb69ece6 \[Modern Flutter Plugin Development]: https\://blog.flutter.dev/modern-flutter-plugin-development-4c3ee015cf5a []()## Samples You can run the following samples as desktop apps, as well as download and inspect the source code to learn more about Flutter desktop support. Wonderous app \[running app]\[wonderous-app], \[repo]\[wonderous-repo] : A showcase app that uses Flutter to create a highly expressive user interface. Wonderous focuses on delivering an accessible and high-quality user experience while including engaging interactions and novel animations. To run Wonderous as a desktop app, clone the project and follow the instructions provided in the \[README]\[wonderous-readme]. Flokk \[announcement blogpost]\[gskinner-flokk-blogpost], \[repo]\[gskinner-flokk-repo] : A Google contacts manager that integrates with GitHub and Twitter. It syncs with your Google account, imports your contacts, and allows you to manage them. \[Photo Search app]\[] : A sample application built as a desktop application that uses desktop-supported plugins. \[wonderous-app]: https\://wonderous.app//web \[wonderous-repo]: https\://github.com/gskinnerTeam/flutter-wonderous-app \[wonderous-readme]: https\://github.com/gskinnerTeam/flutter-wonderous-app#wonderous \[Photo Search app]: https\://github.com/flutter/samples/tree/main/desktop\_photo\_search \[gskinner-flokk-repo]: https\://github.com/gskinnerTeam/flokk \[gskinner-flokk-blogpost]: https\://blog.gskinner.com/archives/2020/09/flokk-how-we-built-a-desktop-app-using-flutter.html

----
url: https://docs.flutter.dev/app-architecture/design-patterns/key-value-data.md
----

\# Persistent storage architecture: Key-value data > Save application data to a user's on-device key-value store.

<!--?code-excerpt path-base="app-architecture/todo_data_service"?-->

Most Flutter applications, no matter how small or big they are, require storing data on the user’s device at some point, such as API keys, user preferences or data that should be available offline. In this recipe, you will learn how to integrate persistent storage for key-value data in a Flutter application that uses the recommended \[Flutter architecture design]\[]. If you aren’t familiar with storing data to disk at all, you can read the \[Store key-value data on disk]\[] recipe. Key-value stores are often used for saving simple data, such as app configuration, and in this recipe you’ll use it to save Dark Mode preferences. If you want to learn how to store complex data on a device, you’ll likely want to use SQL. In that case, take a look at the cookbook recipe that follows this one called \[Persistent storage architecture: SQL]\[]. ## Example application: App with theme selection The example application consists of a single screen with an app bar at the top, a list of items, and a text field input at the bottom. In the \`AppBar\`, a \`Switch\` allows users to change between dark and light theme modes. This setting is applied immediately and it’s stored in the device using a key-value data storage service. The setting is restored when the user starts the application again. :::note The full, runnable source-code for this example is available in \[\`/examples/app-architecture/todo\_data\_service/\`]\[]. ::: ## Storing theme selection key-value data This functionality follows the recommended Flutter architecture design pattern, with a presentation and a data layer. - The presentation layer contains the \`ThemeSwitch\` widget and the \`ThemeSwitchViewModel\`. - The data layer contains the \`ThemeRepository\` and the \`SharedPreferencesService\`. ### Theme selection presentation layer The \`ThemeSwitch\` is a \`StatelessWidget\` that contains a \`Switch\` widget. The state of the switch is represented by the public field \`isDarkMode\` in the \`ThemeSwitchViewModel\`. When the user taps the switch, the code executes the command \`toggle\` in the view model.

<!--?code-excerpt "lib/ui/theme_config/widgets/theme_switch.dart (ThemeSwitch)"?-->

\`\`\`dart class ThemeSwitch extends StatelessWidget { const ThemeSwitch({super.key, required this.viewmodel}); final ThemeSwitchViewModel viewmodel; @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.symmetric(horizontal: 16.0), child: Row( children: \[ const Text('Dark Mode'), ListenableBuilder( listenable: viewmodel, builder: (context, \_) { return Switch( value: viewmodel.isDarkMode, onChanged: (\_) { viewmodel.toggle.execute(); }, ); }, ), ], ), ); } } \`\`\` The \`ThemeSwitchViewModel\` implements a view model as described in the MVVM pattern. This view model contains the state of the \`ThemeSwitch\` widget, represented by the boolean variable \`\_isDarkMode\`. The view model uses the \`ThemeRepository\` to store and load the dark mode setting. It contains two different command actions: \`load\`, which loads the dark mode setting from the repository, and \`toggle\`, which switches the state between dark mode and light mode. It exposes the state through the \`isDarkMode\` getter. The \`\_load\` method implements the \`load\` command. This method calls \`ThemeRepository.isDarkMode\` to obtain the stored setting and calls \`notifyListeners()\` to refresh the UI. The \`\_toggle\` method implements the \`toggle\` command. This method calls \`ThemeRepository.setDarkMode\` to store the new dark mode setting. As well, it changes the local state of \`\_isDarkMode\` then calls \`notifyListeners()\` to update the UI.

<!--?code-excerpt "lib/ui/theme_config/viewmodel/theme_switch_viewmodel.dart (ThemeSwitchViewModel)"?-->

\`\`\`dart class ThemeSwitchViewModel extends ChangeNotifier { ThemeSwitchViewModel(this.\_themeRepository) { load = Command0(\_load)..execute(); toggle = Command0(\_toggle); } final ThemeRepository \_themeRepository; bool \_isDarkMode = false; /// If true show dark mode bool get isDarkMode => \_isDarkMode; late final Command0 load; late final Command0 toggle; /// Load the current theme setting from the repository Future> \_load() async { final result = await \_themeRepository.isDarkMode(); if (result is Ok) { \_isDarkMode = result.value; } notifyListeners(); return result; } /// Toggle the theme setting Future> \_toggle() async { \_isDarkMode = !\_isDarkMode; final result = await \_themeRepository.setDarkMode(\_isDarkMode); notifyListeners(); return result; } } \`\`\` ### Theme selection data layer Following the architecture guidelines, the data layer is split into two parts: the \`ThemeRepository\` and the \`SharedPreferencesService\`. The \`ThemeRepository\` is the single source of truth for all the theming configuration settings, and handles any possible errors coming from the service layer. In this example, the \`ThemeRepository\` also exposes the dark mode setting through an observable \`Stream\`. This allows other parts of the application to subscribe to changes in the dark mode setting. The \`ThemeRepository\` depends on \`SharedPreferencesService\`. The repository obtains the stored value from the service, and stores it when it changes. The \`setDarkMode()\` method passes the new value to the \`StreamController\`, so that any component listening to the \`observeDarkMode\` stream

<!--?code-excerpt "lib/data/repositories/theme_repository.dart (ThemeRepository)"?-->

\`\`\`dart class ThemeRepository { ThemeRepository(this.\_service); final \_darkModeController = StreamController.broadcast(); final SharedPreferencesService \_service; /// Get if dark mode is enabled Future> isDarkMode() async { try { final value = await \_service.isDarkMode(); return Result.ok(value); } on Exception catch (e) { return Result.error(e); } } /// Set dark mode Future> setDarkMode(bool value) async { try { await \_service.setDarkMode(value); \_darkModeController.add(value); return Result.ok(null); } on Exception catch (e) { return Result.error(e); } } /// Stream that emits theme config changes. /// ViewModels should call \[isDarkMode] to get the current theme setting. Stream observeDarkMode() => \_darkModeController.stream; } \`\`\` The \`SharedPreferencesService\` wraps the \`SharedPreferences\` plugin functionality, and calls to the \`setBool()\` and \`getBool()\` methods to store the dark mode setting, hiding this third-party dependency from the rest of the application :::note A third-party dependency is a way to refer to packages and plugins developed by other programmers outside of your organization. :::

<!--?code-excerpt "lib/data/services/shared_preferences_service.dart (SharedPreferencesService)"?-->

\`\`\`dart class SharedPreferencesService { static const String \_kDarkMode = 'darkMode'; Future setDarkMode(bool value) async { final prefs = await SharedPreferences.getInstance(); await prefs.setBool(\_kDarkMode, value); } Future isDarkMode() async { final prefs = await SharedPreferences.getInstance(); return prefs.getBool(\_kDarkMode) ?? false; } } \`\`\` ## Putting it all together In this example, the \`ThemeRepository\` and \`SharedPreferencesService\` are created in the \`main()\` method and passed to the \`MainApp\` as constructor argument dependency.

<!--?code-excerpt "lib/main.dart (MainTheme)"?-->

\`\`\`dart void main() { // ··· runApp( MainApp( themeRepository: ThemeRepository(SharedPreferencesService()), // ··· ), ); } \`\`\` Then, when the \`ThemeSwitch\` is created, also create \`ThemeSwitchViewModel\` and pass the \`ThemeRepository\` as dependency.

<!--?code-excerpt "lib/main.dart (AddThemeSwitch)"?-->

\`\`\`dart ThemeSwitch( viewmodel: ThemeSwitchViewModel(widget.themeRepository), ), \`\`\` The example application also includes the \`MainAppViewModel\` class, which listens to changes in the \`ThemeRepository\` and exposes the dark mode setting to the \`MaterialApp\` widget.

<!--?code-excerpt "lib/main_app_viewmodel.dart (MainAppViewModel)"?-->

\`\`\`dart class MainAppViewModel extends ChangeNotifier { MainAppViewModel(this.\_themeRepository) { \_subscription = \_themeRepository.observeDarkMode().listen((isDarkMode) { \_isDarkMode = isDarkMode; notifyListeners(); }); \_load(); } final ThemeRepository \_themeRepository; StreamSubscription? \_subscription; bool \_isDarkMode = false; bool get isDarkMode => \_isDarkMode; Future \_load() async { final result = await \_themeRepository.isDarkMode(); if (result is Ok) { \_isDarkMode = result.value; } notifyListeners(); } @override void dispose() { \_subscription?.cancel(); super.dispose(); } } \`\`\`

<!--?code-excerpt "lib/main.dart (ListenableBuilder)" replace="/^return //g;/},$/},\n  child: \/\/...\n)/g"?-->

\`\`\`dart ListenableBuilder( listenable: \_viewModel, builder: (context, child) { return MaterialApp( theme: \_viewModel.isDarkMode ? ThemeData.dark() : ThemeData.light(), home: child, ); }, child: //... ) \`\`\` \[Flutter architecture design]: /app-architecture \[Store key-value data on disk]: /cookbook/persistence/key-value \[Persistent Storage Architecture: SQL]: /app-architecture/design-patterns/sql \[\`/examples/app-architecture/todo\_data\_service/\`]: https\://github.com/flutter/website/tree/main/examples/app-architecture/todo\_data\_service/

----
url: https://docs.flutter.dev/data-and-backend/state-mgmt/ephemeral-vs-app
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Differentiate between ephemeral state and app state](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

*This doc introduces app state, ephemeral state, and how you might manage each in a Flutter app.*

In the broadest possible sense, the state of an app is everything that exists in memory when the app is running. This includes the app's assets, all the variables that the Flutter framework keeps about the UI, animation state, textures, fonts, and so on. While this broadest possible definition of state is valid, it's not very useful for architecting an app.

First, you don't even manage some state (like textures). The framework handles those for you. So a more useful definition of state is "whatever data you need in order to rebuild your UI at any moment in time". Second, the state that you *do* manage yourself can be separated into two conceptual types: ephemeral state and app state.

## Ephemeral state

[#](#ephemeral-state)

Ephemeral state (sometimes called *UI state* or *local state*) is the state you can neatly contain in a single widget.

This is, intentionally, a vague definition, so here are a few examples.

* current page in a [`PageView`](https://api.flutter.dev/flutter/widgets/PageView-class.html)
* current progress of a complex animation
* current selected tab in a `BottomNavigationBar`

Other parts of the widget tree seldom need to access this kind of state. There is no need to serialize it, and it doesn't change in complex ways.

In other words, there is no need to use state management techniques (ScopedModel, Redux, etc.) on this kind of state. All you need is a `StatefulWidget`.

Below, you see how the currently selected item in a bottom navigation bar is held in the `_index` field of the `_MyHomepageState` class. In this example, `_index` is ephemeral state.

dart

```
class MyHomepage extends StatefulWidget {
  const MyHomepage({super.key});
​
  @override
  State<MyHomepage> createState() => _MyHomepageState();
}
​
class _MyHomepageState extends State<MyHomepage> {
  int _index = 0;
​
  @override
  Widget build(BuildContext context) {
    return BottomNavigationBar(
      currentIndex: _index,
      onTap: (newIndex) {
        setState(() {
          _index = newIndex;
        });
      },
      // ... items ...
    );
  }
}
```

Here, using `setState()` and a field inside the StatefulWidget's State class is completely natural. No other part of your app needs to access `_index`. The variable only changes inside the `MyHomepage` widget. And, if the user closes and restarts the app, you don't mind that `_index` resets to zero.

## App state

[#](#app-state)

State that is not ephemeral, that you want to share across many parts of your app, and that you want to keep between user sessions, is what we call application state (sometimes also called shared state).

Examples of application state:

* User preferences
* Login info
* Notifications in a social networking app
* The shopping cart in an e-commerce app
* Read/unread state of articles in a news app

For managing app state, you'll want to research your options. Your choice depends on the complexity and nature of your app, your team's previous experience, and many other aspects. Read on.

## There is no clear-cut rule

[#](#there-is-no-clear-cut-rule)

To be clear, you *can* use `State` and `setState()` to manage all of the state in your app. In fact, the Flutter team does this in many simple app samples (including the starter app that you get with every `flutter create`).

It goes the other way, too. For example, you might decide that—in the context of your particular app—the selected tab in a bottom navigation bar is *not* ephemeral state. You might need to change it from outside the class, keep it between sessions, and so on. In that case, the `_index` variable is app state.

There is no clear-cut, universal rule to distinguish whether a particular variable is ephemeral or app state. Sometimes, you'll have to refactor one into another. For example, you'll start with some clearly ephemeral state, but as your application grows in features, it might need to be moved to app state.

For that reason, take the following diagram with a large grain of salt:

When asked about React's setState versus Redux's store, the author of Redux, Dan Abramov, replied:

> "The rule of thumb is: [Do whatever is less awkward](https://github.com/reduxjs/redux/issues/1287#issuecomment-175351978)."

In summary, there are two conceptual types of state in any Flutter app. Ephemeral state can be implemented using `State` and `setState()`, and is often local to a single widget. The rest is your app state. Both types have their place in any Flutter app, and the split between the two depends on your own preference and the complexity of the app.

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-07-31. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/data-and-backend/state-mgmt/ephemeral-vs-app.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/data-and-backend/state-mgmt/ephemeral-vs-app\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/data-and-backend/state-mgmt/ephemeral-vs-app.md "Report an issue with this page").

----
url: https://docs.flutter.dev/perf/metrics
----

* Startup time to the first frame

  * Check the time when [WidgetsBinding.instance.firstFrameRasterized](https://api.flutter.dev/flutter/widgets/WidgetsBinding/firstFrameRasterized.html) is true.
  * See the [perf dashboard](https://flutter-flutter-perf.skia.org/e/?queries=sub_result%3DtimeToFirstFrameRasterizedMicros).

* Frame buildDuration, rasterDuration, and totalSpan

  * See [`FrameTiming`](https://api.flutter.dev/flutter/dart-ui/FrameTiming-class.html) in the API docs.

* Statistics of frame `buildDuration` (`*_frame_build_time_millis`)

  * We recommend monitoring four stats: average, 90th percentile, 99th percentile, and worst frame build time.
  * See, for example, [metrics](https://flutter-flutter-perf.skia.org/e/?queries=sub_result%3D90th_percentile_frame_build_time_millis%26sub_result%3D99th_percentile_frame_build_time_millis%26sub_result%3Daverage_frame_build_time_millis%26sub_result%3Dworst_frame_build_time_millis%26test%3Dflutter_gallery__transition_perf) for the `flutter_gallery__transition_perf` test.

* Statistics of frame `rasterDuration` (`*_frame_build_time_millis`)

  * We recommend monitoring four stats: average, 90th percentile, 99th percentile, and worst frame build time.
  * See, for example, [metrics](https://flutter-flutter-perf.skia.org/e/?queries=sub_result%3D90th_percentile_frame_rasterizer_time_millis%26sub_result%3D99th_percentile_frame_rasterizer_time_millis%26sub_result%3Daverage_frame_rasterizer_time_millis%26sub_result%3Dworst_frame_rasterizer_time_millis%26test%3Dflutter_gallery__transition_perf) for the `flutter_gallery__transition_perf` test.

* CPU/GPU usage (a good approximation for energy use)

  * The usage is currently only available through trace events. See [profiling\_summarizer.dart](https://github.com/flutter/flutter/blob/main/packages/flutter_driver/lib/src/driver/profiling_summarizer.dart).
  * See [metrics](https://flutter-flutter-perf.skia.org/e/?queries=sub_result%3Daverage_cpu_usage%26sub_result%3Daverage_gpu_usage%26test%3Dsimple_animation_perf_ios) for the `simple_animation_perf_ios` test.

* release\_size\_bytes to approximately measure the size of a Flutter app

  * See the [basic\_material\_app\_android](https://github.com/flutter/flutter/blob/main/dev/devicelab/bin/tasks/basic_material_app_android__compile.dart), [basic\_material\_app\_ios](https://github.com/flutter/flutter/blob/main/dev/devicelab/bin/tasks/basic_material_app_ios__compile.dart), [hello\_world\_android](https://github.com/flutter/flutter/blob/main/dev/devicelab/bin/tasks/hello_world_android__compile.dart), [hello\_world\_ios](https://github.com/flutter/flutter/blob/main/dev/devicelab/bin/tasks/hello_world_ios__compile.dart), [flutter\_gallery\_android](https://github.com/flutter/flutter/blob/main/dev/devicelab/bin/tasks/flutter_gallery_android__compile.dart), and [flutter\_gallery\_ios](https://github.com/flutter/flutter/blob/main/dev/devicelab/bin/tasks/flutter_gallery_ios__compile.dart) tests.
  * See [metrics](https://flutter-flutter-perf.skia.org/e/?queries=sub_result%3Drelease_size_bytes%26test%3Dbasic_material_app_android__compile%26test%3Dbasic_material_app_ios__compile%26test%3Dhello_world_android__compile%26test%3Dhello_world_ios__compile%26test%3Dflutter_gallery_ios__compile%26test%3Dflutter_gallery_android__compile) in the dashboard.
  * For info on how to measure the size more accurately, see the [app size](/perf/app-size) page.

For a complete list of performance metrics Flutter measures per commit, visit the following sites, click **Query**, and filter the **test** and **sub\_result** fields:

* <https://flutter-flutter-perf.skia.org/e/>
* <https://flutter-engine-perf.skia.org/e/>

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/perf/metrics.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/perf/metrics\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/perf/metrics.md "Report an issue with this page").

----
url: https://docs.flutter.dev/testing/build-modes
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Flutter's build modes](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

The Flutter tooling supports three modes when compiling your app, and a headless mode for testing. You choose a compilation mode depending on where you are in the development cycle. Are you debugging your code? Do you need profiling information? Are you ready to deploy your app?

A quick summary for when to use which mode is as follows:

* Use [debug](#debug) mode during development, when you want to use [hot reload](/tools/hot-reload).
* Use [profile](#profile) mode when you want to analyze performance.
* Use [release](#release) mode when you are ready to release your app.

The rest of the page details these modes.

* To learn about the headless testing mode, refer to the engine wiki's docs on [Flutter's build modes](https://github.com/flutter/flutter/blob/main/docs/engine/Flutter's-modes.md).
* To learn how to detect the build mode, check out the [Check for Debug/Release Mode in Flutter Apps](https://retroportalstudio.medium.com/check-for-debug-release-mode-in-flutter-apps-d8d545f20da3) blog post.

## Debug

[#](#debug)

In *debug mode*, the app is set up for debugging on the physical device, emulator, or simulator.

Debug mode for mobile apps mean that:

* [Assertions](https://dart.dev/language/error-handling#assert) are enabled.
* Service extensions are enabled.
* Compilation is optimized for fast development and run cycles (but not for execution speed, binary size, or deployment).
* Debugging is enabled, and tools supporting source level debugging (such as [DevTools](/tools/devtools)) can connect to the process.

Debug mode for a web app means that:

* The build is *not* minified and tree shaking has *not* been performed.
* The app is compiled with the [dartdevc](https://dart.dev/tools/dartdevc) compiler for easier debugging.

By default, `flutter run` compiles to debug mode. Your IDE supports this mode. Android Studio, for example, provides a **Run > Debug...** menu option, as well as a green bug icon overlaid with a small triangle on the project page.

## Release

[#](#release)

Use *release mode* for deploying the app, when you want maximum optimization and minimal footprint size. For mobile, release mode (which is not supported on the simulator or emulator), means that:

* Assertions are disabled.
* Debugging information is stripped out.
* Debugging is disabled.
* Compilation is optimized for fast startup, fast execution, and small package sizes.
* Service extensions are disabled.

Release mode for a web app means that:

* The build is minified and tree shaking has been performed.
* The app is compiled with the [dart2js](https://dart.dev/tools/dart2js) compiler for best performance.

The command `flutter run --release` compiles to release mode. Your IDE supports this mode. Android Studio, for example, provides a **Run > Run...** menu option, as well as a triangular green run button icon on the project page. You can compile to release mode for a specific target with `flutter build <target>`. For a list of supported targets, use `flutter help build`.

For more information, see the docs on releasing [iOS](/deployment/ios) and [Android](/deployment/android) apps.

## Profile

[#](#profile)

In *profile mode*, some debugging ability is maintained—enough to profile your app's performance. Profile mode is disabled on the emulator and simulator, because their behavior is not representative of real performance. On mobile, profile mode is similar to release mode, with the following differences:

* Some service extensions, such as the one that enables the performance overlay, are enabled.
* Tracing is enabled, and tools supporting source-level debugging (such as [DevTools](/tools/devtools)) can connect to the process.

Profile mode for a web app means that:

* The build is *not* minified but tree shaking has been performed.
* The app is compiled with the [dart2js](https://dart.dev/tools/dart2js) compiler.
* DevTools can't connect to a Flutter web app running in profile mode. Use Chrome DevTools to [generate timeline events](https://developers.google.com/web/tools/chrome-devtools/evaluate-performance/performance-reference) for a web app.

Your IDE supports this mode. Android Studio, for example, provides a **Run > Profile...** menu option. The command `flutter run --profile` compiles to profile mode.

For more information on the build modes, see [Flutter's build modes](https://github.com/flutter/flutter/blob/main/docs/engine/Flutter's-modes.md).

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/testing/build-modes.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/testing/build-modes\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/testing/build-modes.md "Report an issue with this page").

----
url: https://docs.flutter.dev/cookbook/design/themes.md
----

\# Use themes to share colors and font styles > How to share colors and font styles throughout an app using Themes.

<!--?code-excerpt path-base="cookbook/design/themes"?-->

:::note This recipe uses Flutter's support for \[Material 3]\[] and the \[google\_fonts]\[] package. ::: \[Material 3]: /ui/design/material \[google\_fonts]: https\://pub.dev/packages/google\_fonts To share colors and font styles throughout an app, use themes. You can define app-wide themes. You can extend a theme to change a theme style for one component. Each theme defines the colors, type style, and other parameters applicable for the type of Material component. Flutter applies styling in the following order: 1. Styles applied to the specific widget. 1. Themes that override the immediate parent theme. 1. Main theme for the entire app. After you define a \`Theme\`, use it within your own widgets. Flutter's Material widgets use your theme to set the background colors and font styles for app bars, buttons, checkboxes, and more. ## Create an app theme To share a \`Theme\` across your entire app, set the \`theme\` property to your \`MaterialApp\` constructor. This property takes a \[\`ThemeData\`]\[] instance. If you don't specify a theme in the constructor, Flutter creates a default theme for you.

<!--?code-excerpt "lib/main.dart (MaterialApp)" replace="/return //g"?-->

\`\`\`dart MaterialApp( title: appName, theme: ThemeData( // Define the default brightness and colors. colorScheme: ColorScheme.fromSeed( seedColor: Colors.purple, // ··· brightness: Brightness.dark, ), // Define the default \`TextTheme\`. Use this to specify the default // text styling for headlines, titles, bodies of text, and more. textTheme: TextTheme( displayLarge: const TextStyle( fontSize: 72, fontWeight: FontWeight.bold, ), // ··· titleLarge: GoogleFonts.oswald( fontSize: 30, fontStyle: FontStyle.italic, ), bodyMedium: GoogleFonts.merriweather(), displaySmall: GoogleFonts.pacifico(), ), ), home: const MyHomePage(title: appName), ); \`\`\` Most instances of \`ThemeData\` set values for the following two properties. These properties affect the entire app. 1. \[\`colorScheme\`]\[] defines the colors. 1. \[\`textTheme\`]\[] defines text styling. \[\`colorScheme\`]: https\://pub.dev/documentation/material\_ui/latest/material\_ui/ThemeData/colorScheme.html \[\`textTheme\`]: https\://pub.dev/documentation/material\_ui/latest/material\_ui/ThemeData/textTheme.html To learn what colors, fonts, and other properties, you can define, check out the \[\`ThemeData\`]\[] documentation. ## Apply a theme To apply your new theme, use the \`Theme.of(context)\` method when specifying a widget's styling properties. These can include, but are not limited to, \`style\` and \`color\`. The \`Theme.of(context)\` method looks up the widget tree and retrieves the nearest \`Theme\` in the tree. If you have a standalone \`Theme\`, that's applied. If not, Flutter applies the app's theme. In the following example, the \`Container\` constructor uses this technique to set its \`color\`.

<!--?code-excerpt "lib/main.dart (Container)" replace="/^child: //g"?-->

\`\`\`dart Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12), color: Theme.of(context).colorScheme.primary, child: Text( 'Text with a background color', // ··· style: Theme.of(context).textTheme.bodyMedium! .copyWith(color: Theme.of(context).colorScheme.onPrimary), ), ), \`\`\` ## Override a theme To override the overall theme in part of an app, wrap that section of the app in a \`Theme\` widget. You can override a theme in two ways: 1. Create a unique \`ThemeData\` instance. 2. Extend the parent theme. ### Set a unique \`ThemeData\` instance If you want a component of your app to ignore the overall theme, create a \`ThemeData\` instance. Pass that instance to the \`Theme\` widget.

<!--?code-excerpt "lib/main.dart (Theme)"?-->

\`\`\`dart Theme( // Create a unique theme with \`ThemeData\`. data: ThemeData(colorScheme: ColorScheme.fromSeed(seedColor: Colors.pink)), child: FloatingActionButton(onPressed: () {}, child: const Icon(Icons.add)), ); \`\`\` ### Extend the parent theme Instead of overriding everything, consider extending the parent theme. To extend a theme, use the \[\`copyWith()\`]\[] method.

<!--?code-excerpt "lib/main.dart (ThemeCopyWith)"?-->

\`\`\`dart Theme( // Find and extend the parent theme using \`copyWith\`. // To learn more, check out the section on \`Theme.of\`. data: Theme.of(context) .copyWith(colorScheme: ColorScheme.fromSeed(seedColor: Colors.pink)), child: const FloatingActionButton(onPressed: null, child: Icon(Icons.add)), ); \`\`\` ## Watch a video on \`Theme\` To learn more, watch this short Widget of the Week video on the \`Theme\` widget: ## Try an interactive example

<!--?code-excerpt "lib/main.dart (FullApp)"?-->

\`\`\`dartpad title="Flutter themes hands-on example in DartPad" run="true" import 'package:flutter/material.dart'; // Include the Google Fonts package to provide more text format options // https\://pub.dev/packages/google\_fonts import 'package:google\_fonts/google\_fonts.dart'; void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { const appName = 'Custom Themes'; return MaterialApp( title: appName, theme: ThemeData( // Define the default brightness and colors. colorScheme: ColorScheme.fromSeed( seedColor: Colors.purple, // TRY THIS: Change to "Brightness.light" // and see that all colors change // to better contrast a light background. brightness: Brightness.dark, ), // Define the default \`TextTheme\`. Use this to specify the default // text styling for headlines, titles, bodies of text, and more. textTheme: TextTheme( displayLarge: const TextStyle( fontSize: 72, fontWeight: FontWeight.bold, ), // TRY THIS: Change one of the GoogleFonts // to "lato", "poppins", or "lora". // The title uses "titleLarge" // and the middle text uses "bodyMedium". titleLarge: GoogleFonts.oswald( fontSize: 30, fontStyle: FontStyle.italic, ), bodyMedium: GoogleFonts.merriweather(), displaySmall: GoogleFonts.pacifico(), ), ), home: const MyHomePage(title: appName), ); } } class MyHomePage extends StatelessWidget { final String title; const MyHomePage({super.key, required this.title}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text( title, style: Theme.of(context).textTheme.titleLarge! .copyWith(color: Theme.of(context).colorScheme.onSecondary), ), backgroundColor: Theme.of(context).colorScheme.secondary, ), body: Center( child: Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12), color: Theme.of(context).colorScheme.primary, child: Text( 'Text with a background color', // TRY THIS: Change the Text value // or change the Theme.of(context).textTheme // to "displayLarge" or "displaySmall". style: Theme.of(context).textTheme.bodyMedium! .copyWith(color: Theme.of(context).colorScheme.onPrimary), ), ), ), floatingActionButton: Theme( data: Theme.of(context).copyWith( // TRY THIS: Change the seedColor to "Colors.red" or // "Colors.blue". colorScheme: ColorScheme.fromSeed( seedColor: Colors.pink, brightness: Brightness.dark, ), ), child: FloatingActionButton( onPressed: () {}, child: const Icon(Icons.add), ), ), ); } } \`\`\` \[\`copyWith()\`]: https\://pub.dev/documentation/material\_ui/latest/material\_ui/ThemeData/copyWith.html \[\`ThemeData\`]: https\://pub.dev/documentation/material\_ui/latest/material\_ui/ThemeData-class.html

----
url: https://docs.flutter.dev/platform-integration/ios/app-extensions
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Adding iOS app extensions](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

This guide shows you how to use iOS app extensions with a Flutter app.

## Overview

[#](#overview)

[iOS app extensions](https://developer.apple.com/app-extensions/) allow you to expand functionality outside of your iOS app. Your app could appear as a home screen widget, or you can make portions of your app available within other apps.

In the following example, when a user selects a photo to share in the iOS Photo app, a Flutter app called `Example App With Extension` is displayed in the Photo apps share sheet:

## Add an iOS app extension to your Flutter app

[#](#add-extension)

If you want to integrate your Flutter app with the iOS operating system, you can add iOS app extensions to your Flutter project. For a seamless workflow, the following steps show how to add a [Share](https://developer.apple.com/library/archive/documentation/General/Conceptual/ExtensibilityPG/Share.html) app extension to a new Flutter app called `example_app_with_extension`, but you can always start with an existing project.

1. In the console, create a new Flutter project called `example_app_with_extension`.

   ```
   $ flutter create example_app_with_extension
   ```

2. In the console, open the Xcode workspace for the `example_app_with_extension` project.

   ```
   $ cd example_app_with_extension && open ios/Runner.xcworkspace
   ```

3. In Xcode, add an app extension called `Share` and call it `ShareExtension`.

   * In the Xcode menu bar, select **File** > **New** > **Target**.

   * Add **Share Extension**.

   * In the **Name field**, enter **ShareExtension**.

   * Click **Finish**.

   * In the **Activate … Scheme** dialog box that appears, select **Activate**.

4. In Xcode, change the order of the build process.

   * Open the **project navigator** (**View** > **Navigators** > **Project**).

   * In the **project navigator**, at the top, select **Runner**.

   * In the main window under **TARGETS**, select **Runner**.

   * Open the **Build Phases** tab.

   * Drag **Embed Foundation Extensions** above **Run Script**.

5. Make sure your **Minimum Deployments** iOS value is properly set and matches in both **Runner** and **ShareExtension**

   * Open the **project navigator** (**View** > **Navigators** > **Project**).

   * In the **project navigator**, at the top, select **Runner**.

   * In the main window under **TARGETS**, select **Runner**.

   * On the **General** tab check your **Minimum Deployments** dropdown value to match the one you have on **ShareExtension** > **General** tab.

6. In the console, run the following command to rebuild your iOS app:

   ```
   $ flutter build ios --config-only
   ```

7. [Test your app with the simulator](#test-extensions).

When you add a new app extension, Xcode generates sample code based on the template you selected. For more information about the generated code and WidgetKit, see [Apple's app extension documentation](https://developer.apple.com/app-extensions/).

## Test an iOS app extension

[#](#test-extensions)

After you've added an app extension to your Flutter project, you can test it, using a simulator or physical device. If you are testing you extension in debug mode, you must use the iOS simulator.

The following steps assume you're using the sample application and Share extension from [Adding iOS app extensions](#add-extension).

* [Simulator](#209-tab-panel)
* [Physical device](#210-tab-panel)

1. In Xcode, [add an app extension to your project](#add-extension).

2. In the console, use the following command to run your iOS app:

   ```
   $ flutter run
   ```

3. In the simulator, test your app extension.

   * Launch an app that supports the Share extension, such as the Photos app.

   * Select a photo, tap the share button, then tap on the share extension icon of your app.

1) Add an app extension to your project.

2) In the console, run your Flutter app in release mode:

   ```
   $ flutter run --release
   ```

3) On your device, test your app extension.

   * Launch an app that supports the Share extension, such as the Photos app.

   * Select a photo, tap the share button, then tap on the share extension icon of your app.

## Additional ways to interact with iOS app extensions

[#](#interact-app-extensions)

Flutter apps interact with iOS app extensions using the same techniques as UIKit or SwiftUI apps. The containing app and the app extension don't communicate directly. The containing app might not be running while the device user interacts with the extension. The app and your extension can read and write to shared resources or use higher-level APIs to communicate with each other.

### Use higher-level APIs

[#](#using-higher-level-apis)

Some extensions have APIs. For example, the [Core Spotlight](https://developer.apple.com/documentation/corespotlight) framework indexes your app, allowing users to search from Spotlight and Safari. The [WidgetKit](https://developer.apple.com/documentation/widgetkit) framework can trigger an update of your home screen widget.

To simplify how your app communicates with extensions, Flutter plugins wrap these APIs. To find plugins that wrap extension APIs, check out [Leveraging Apple's System APIs and Frameworks](/platform-integration/ios/apple-frameworks) or search [pub.dev](https://pub.dev/packages).

### Share resources

[#](#sharing-resources)

To share resources between your Flutter app and your app extension, put the `Runner` app target and the extension target in the same [App Group](https://developer.apple.com/documentation/xcode/configuring-app-groups).

To add a target to an App Group:

1. Open the target settings in Xcode.

2. Navigate to the **Signing & Capabilities** tab.

3. Select **+ Capability** then **App Groups**.

4. Choose which App Group you want to add the target from one of two options:

   1. Select an App Group from the list.
   2. Click **+** to add a new App Group.

When two targets belong to the same App Group, they can read from and write to the same source. Choose one of the following sources for your data.

* **Key/value:** Use the [`shared_preference_app_group`](https://pub.dev/packages/shared_preference_app_group) plugin to read or write to `UserDefaults` within the same App Group.
* **File:** Use the App Group container path from the [`path_provider`](https://pub.dev/packages/path_provider) plugin to [read and write files](/cookbook/persistence/reading-writing-files).
* **Database:** Use the App Group container path from the [`path_provider`](https://pub.dev/packages/path_provider) plugin to create a database with the [`sqflite`](https://pub.dev/packages/sqflite) plugin.

### Schedule background updates

[#](#background-updates)

Background tasks provide a means to update your extension through code regardless of the status of your app.

To schedule background work from your Flutter app, use the [`workmanager`](https://pub.dev/packages/workmanager) plugin.

### Add deep linking

[#](#deep-linking)

You might want to direct users from an app extension to a specific page in your Flutter app. To open a specific route in your app, you can use [Deep Linking](/ui/navigation/deep-linking).

### Add a scrollable list

[#](#advanced-scrolling-behavior)

By default, flutter view does not handle scroll gestures in a [Share](https://developer.apple.com/library/archive/documentation/General/Conceptual/ExtensibilityPG/Share.html) extension. To support a scrollable list in the Share extension, follow [the instructions on GitHub](https://github.com/flutter/flutter/issues/164670#issuecomment-2762124121).

### Open a Flutter app in an iOS app extension

[#](#creating-app-extension-uis-with-flutter)

You can open your Flutter app directly in some iOS app extensions, such as the [Share](https://developer.apple.com/library/archive/documentation/General/Conceptual/ExtensibilityPG/Share.html) extension, with a `FlutterViewController`.

In the following example, a Flutter app called `Example App With Extension` is opened in the Share extension, which lets users share content between apps:

Use the following steps to display a Flutter app inside of a [Share](https://developer.apple.com/library/archive/documentation/General/Conceptual/ExtensibilityPG/Share.html) app extension. In this example the app extension scheme is called `ShareExtension`, the Flutter app scheme is called `Runner`, and the Flutter app is called `Example App With Extension`:

1. [Add an extension to your Flutter app](#add-extension) if you haven't already done so.

2. In the console, navigate to your Flutter project directory and then open your project in Xcode with the following command:

   ```
   open ios/Runner.xcworkspace
   ```

3. In Xcode, disable user script sandboxing.

   * Open the **project navigator** (**View** > **Navigators** > **Project**).

   * In the main window under **TARGETS**, select **ShareExtension**.

   * Open the **Build Settings** tab.

   * Navigate to **Build Options**.

   * Set **User Script Sandboxing** as **No**.

4. In Xcode, add the pre-action to the `ShareExtension` scheme.

   * Open the **Manage Schemes** window (**Product** > **Scheme** > **Manage Schemes**).

   * Select the **ShareExtension** scheme and edit it.

   * Expand the **Build** tab.

   * Select **Pre-actions**.

   * Click **+** and select **New Run Script Action**.

   * In the **Provide build settings from** drop-down list, select **ShareExtension**.

   * In the **Shell** text field, enter:

     ```
     /bin/sh "$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh" prepare
     ```

   * Click **Close**.

5. In Xcode, share the build configurations.

   * Open the **project navigator** (**View** > **Navigators** > **Project**).

   * In the main window under **PROJECT**, select **Runner**.

   * Open the **Info** tab.

   * Expand **Configuration**.

   * Expand **Debug** and update the value for **ShareExtension** to match the value for **Runner**.

   * Repeat the previous step for **Profile**, and **Release**.

   * When you are finished, make sure that the configurations look similar to the following:

6. (Optional) In Xcode, replace any storyboard files with an extension class, if needed.

   * Open the **project navigator** (**View** > **Navigators** > **Project**).

   * Select **Runner** > **ShareExtension** > **Info**.

   * Expand **Information Property List**.

   * Delete the **NSExtensionMainStoryboard** key.

   * Add the **NSExtensionPrincipalClass** key.

   * Add one of these values for the `NSExtensionPrincipalClass` key:

     * (Swift) **ShareExtension.ShareViewController**
     * (Objective-C) **ShareViewController**

7. In Xcode, update the `ShareViewController` to use the `FlutterViewController`.

   * Open the **project navigator** (**View** > **Navigators** > **Project**).

   * Select **Runner** > **ShareExtension** > **ShareViewController**.

   * Update `ShareViewController` to use the `FlutterViewController` class:

* [UIKit-Swift](#211-tab-panel)
* [UIKit-ObjC](#212-tab-panel)

ShareViewController.swift

swift

```
import UIKit
import Flutter
​
class ShareViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        showFlutter()
    }
​
    func showFlutter() {
        let flutterEngine = FlutterEngine(name: "my flutter engine")
        flutterEngine.run()
        let flutterViewController = FlutterViewController(engine: flutterEngine, nibName: nil, bundle: nil)
        addChild(flutterViewController)
        view.addSubview(flutterViewController.view)
        flutterViewController.view.frame = view.bounds
    }
​
    override func viewDidDisappear(_ animated: Bool) {
        super.viewDidDisappear(animated)
        self.extensionContext?.cancelRequest(
            withError: NSError(domain: Bundle.main.bundleIdentifier!, code: 0))
    }
}
```

ShareViewController.h

objc

```
@import Flutter;
@import UIKit;
​
@interface ShareViewController : UIViewController
​
@end
```

ShareViewController.m

objc

```
#import "ShareViewController.h"
@import Flutter;
​
@implementation ShareViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    [self showFlutter];
}
​
- (void)showFlutter {
    FlutterEngine *flutterEngine = [[FlutterEngine alloc] initWithName:@"my flutter engine"];
    [flutterEngine run];
    FlutterViewController *flutterViewController =
            [[FlutterViewController alloc] initWithEngine:flutterEngine nibName:nil bundle:nil];
    [self addChildViewController:flutterViewController];
    [self.view addSubview:flutterViewController.view];
    flutterViewController.view.frame = self.view.bounds;
}
​
- (void)viewDidDisappear:(BOOL)animated {
    [super viewDidDisappear:animated];
    [self.extensionContext cancelRequestWithError:[NSError errorWithDomain:NSBundle.mainBundle.bundleIdentifier code:0 userInfo:nil]];
}
@end
```

8. [Test your app with the simulator](#test-extensions).

### Register plugins

[#](#register-plugins)

Use the following steps to register plugins for an app extension. In this example, the app extension scheme is called `ShareExtension`, the Flutter app scheme is called `Runner`, and the Flutter app is called `Example App With Extension`:

1. [Add an extension to your Flutter app](#add-extension) if you haven't already done so.

2. In Xcode, add `GeneratedPluginRegistrant.m` to the app extension target.

   * Open the **project navigator** (**View** > **Navigators** > **Project**).

   * In the main window under **TARGETS**, select **ShareExtension**.

   * Open the **Build Phases** tab.

   * Expand **Compile Sources**.

   * Click **+**.

   * From the list in the *Choose item to add* dialog box, select **GeneratedPluginRegistrant.m**.

   * Click **Add**.

3. (Swift only) In Xcode, update the `SWIFT_OBJC_BRIDGING_HEADER` build setting.

   * Open the **project navigator** (**View** > **Navigators** > **Project**).

   * In the main window under **TARGETS**, select **ShareExtension**.

   * Open the **Build Settings** tab.

   * Select the **All** filter.

   * Navigate to **Swift Compiler - General** and change the value for the **Objective-C Bridging Header** key to **Runner/Runner-Bridging-Header.h**.

4. In Xcode, update the code for `ShareViewController` to register `GeneratedPluginRegistrant.h`.

   * Open the **project navigator** (**View** > **Navigators** > **Project**).

   * Select **Runner** > **ShareExtension** > **ShareViewController**.

   * Update the `ShareViewController` file to use the `GeneratedPluginRegistrant.h`:

* [UIKit-Swift](#213-tab-panel)
* [UIKit-ObjC](#214-tab-panel)

ShareViewController.swift

swift

```
// Add this inside `showFlutter()` at the top
GeneratedPluginRegistrant.register(with: flutterEngine)
```

ShareViewController.m

objc

```
// Add this import at the top
#import "GeneratedPluginRegistrant.h"
```

ShareViewController.m

objc

```
// Add this after [flutterEngine run]
[GeneratedPluginRegistrant registerWithRegistry:flutterEngine];
```

5. (Xcode) [Test your app with the simulator](#test-extensions).

## Constraints

[#](#constraints)

* You must use an iOS simulator to test your extension in debug mode.

* Flutter doesn't fully support running app extensions in debug mode on physical devices when used to build extension UIs because a physical device might run out of memory.

* iOS app extensions have limited memory. It is advisable to only modify an app extension's UI if the app extension supports at least 100MB of memory.

## Call Dart code / render Flutter content in iOS app extensions

[#](#call-dart-code-render-flutter-content-in-ios-app-extensions)

The [home\_widget](https://pub.dev/packages/home_widget) package provides a large amount of functionality, which includes allowing the following:

* [Respond to user input](https://docs.page/abausg/home_widget/features/interactive-widgets) in app extensions using Dart Code.

* [Render Flutter widgets](https://docs.page/abausg/home_widget/features/render-flutter-widgets) in an app extension as an image.

* [Save and retrieve data](https://docs.page/abausg/home_widget/usage/sync-data) from `UserDefaults` on iOS.

## Other resources

[#](#other-resources)

For step-by-step instruction for using app extensions with your Flutter iOS app, check out the [Adding a Home Screen Widget to your Flutter app](https://codelabs.developers.google.com/flutter-home-screen-widgets) codelab.

To learn more about the various ways you can add a Flutter Screen to an iOS app, see [Adding a Flutter Screen to an iOS app](/add-to-app/ios/add-flutter-screen).

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-06-08. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/platform-integration/ios/app-extensions.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/platform-integration/ios/app-extensions\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/platform-integration/ios/app-extensions.md "Report an issue with this page").

----
url: https://docs.flutter.dev/perf/appendix
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top More thoughts about performance](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

## What is performance?

[#](#what-is-performance)

Performance is a set of quantifiable properties of a performer.

In this context, performance isn't the execution of an action itself; it's how well something or someone performs. Therefore, we use the adjective *performant*.

While the *how well* part can, in general, be described in natural languages, in our limited scope, the focus is on something that is quantifiable as a real number. Real numbers include integers and 0/1 binaries as special cases. Natural language descriptions are still very important. For example, a news article that heavily criticizes Flutter's performance by just using words without any numbers (a quantifiable value) could still be meaningful, and it could have great impacts. The limited scope is chosen only because of our limited resources.

The required quantity to describe performance is often referred to as a metric.

To navigate through countless performance issues and metrics, you can categorize based on performers.

For example, most of the content on this website is about the Flutter app performance, where the performer is a Flutter app. Infra performance is also important to Flutter, where the performers are build bots and CI task runners: they heavily affect how fast Flutter can incorporate code changes, to improve the app's performance.

Here, the scope was intentionally broadened to include performance issues other than just app performance issues because they can share many tools regardless of who the performers are. For example, Flutter app performance and infra performance might share the same dashboard and similar alert mechanisms.

Broadening the scope also allows performers to be included that traditionally are easy to ignore. Document performance is such an example. The performer could be an API doc of the SDK, and a metric could be: the percentage of readers who find the API doc useful.

## Why is performance important?

[#](#why-is-performance-important)

Answering this question is not only crucial for validating the work in performance, but also for guiding the performance work in order to be more useful. The answer to "why is performance important?" often is also the answer to "how is performance useful?"

Simply speaking, performance is important and useful because, in the scope, performance must have quantifiable properties or metrics. This implies:

1. A performance report is easy to consume.
2. Performance has little ambiguity.
3. Performance is comparable and convertible.
4. Performance is fair.

Not that non-performance, or non-measurable issues or descriptions are not important. They're meant to highlight the scenarios where performance can be more useful.

### 1. A performance report is easy to consume

[#](#1-a-performance-report-is-easy-to-consume)

Performance metrics are numbers. Reading a number is much easier than reading a passage. For example, it probably takes an engineer 1 second to consume the performance rating as a number from 1 to 5. It probably takes the same engineer at least 1 minute to read the full, 500-word feedback summary.

If there are many numbers, it's easy to summarize or visualize them for quick consumption. For example, you can quickly consume millions of numbers by looking at its histogram, average, quantiles, and so on. If a metric has a history of thousands of data points, then you can easily plot a timeline to read its trend.

On the other hand, having *n* number of 500-word texts almost guarantees an *n*-time cost to consume those texts. It would be a daunting task to analyze thousands of historical descriptions, each having 500 words.

### 2. Performance has little ambiguity

[#](#2-performance-has-little-ambiguity)

Another advantage of having performance as a set of numbers is its unambiguity. When you want an animation to have a performance of 20 ms per frame or 50 fps, there's little room for different interpretations about the numbers. On the other hand, to describe the same animation in words, someone might call it good, while someone else might complain that it's bad. Similarly, the same word or phrase could be interpreted differently by different people. You might interpret an OK frame rate to be 60 fps, while someone else might interpret it to be 30 fps.

Numbers can still be noisy. For example, the measured time per frame might be a true computation time of this frame, plus a random amount of time (noise) that CPU/GPU spends on some unrelated work. Hence, the metric fluctuates. Nevertheless, there's no ambiguity of what the number means. And, there are also rigorous theory and testing tools to handle such noise. For example, you could take multiple measurements to estimate the distribution of a random variable, or you could take the average of many measurements to eliminate the noise by [the law of large numbers](https://en.wikipedia.org/wiki/Law_of_large_numbers).

### 3. Performance is comparable and convertible

[#](#3-performance-is-comparable-and-convertible)

Performance numbers not only have unambiguous meanings, but they also have unambiguous comparisons. For example, there's no doubt that 5 is greater than 4. On the other hand, it might be subjective to figure out whether excellent is better or worse than superb. Similarly, could you figure out whether epic is better than legendary? Actually, the phrase *strongly exceeds expectations* could be better than *superb* in someone's interpretation. It only becomes unambiguous and comparable after a definition that maps strongly exceeds expectations to 4 and superb to 5.

Numbers are also easily convertible using formulas and functions. For example, 60 fps can be converted to 16.67 ms per frame. A frame's rendering time *x* (ms) can be converted to a binary indicator `isSmooth = [x <= 16] = (x <= 16 ? 1 :0)`. Such conversion can be compounded or chained, so you can get a large variety of quantities using a single measurement without any added noise or ambiguity. The converted quantity can then be used for further comparisons and consumption. Such conversions are almost impossible if you're dealing with natural languages.

### 4. Performance is fair

[#](#4-performance-is-fair)

If issues rely on verbose words to be discovered, then an unfair advantage is given to people who are more verbose (more willing to chat or write) or those who are closer to the development team, who have a larger bandwidth and lower cost for chatting or face-to-face meetings.

By having the same metrics to detect problems no matter how far away or how silent the users are, we can treat all issues fairly. That, in turn, allows us to focus on the right issues that have greater impact.

### How to make performance useful

[#](#how-to-make-performance-useful)

The following summarizes the 4 points discussed here, from a slightly different perspective:

1. Make performance metrics easy to consume. Do not overwhelm the readers with a lot of numbers (or words). If there are many numbers, then try to summarize them into a smaller set of numbers (for example, summarize many numbers into a single average number). Only notify readers when the numbers change significantly (for example, automatic alerts on spikes or regressions).

2. Make performance metrics as unambiguous as possible. Define the unit that the number is using. Precisely describe how the number is measured. Make the number easily reproducible. When there's a lot of noise, try to show the full distribution, or eliminate the noise as much as possible by aggregating many noisy measurements.

3. Make it easy to compare performance. For example, provide a timeline to compare the current version with the old version. Provide ways and tools to convert one metric to another. For example, if we can convert both memory increase and fps drops into the number of users dropped or revenue lost in dollars, then we can compare them and make an informed trade-off.

4. Make performance metrics monitor a population that is as wide as possible, so no one is left behind.

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/perf/appendix.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/perf/appendix\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/perf/appendix.md "Report an issue with this page").

----
url: https://docs.flutter.dev/learn/pathway/how-flutter-works
----

1## Flutter's architecturekeyboard\_arrow\_up

[Watch on YouTube in a new tab: "How Flutter Works"](https://www.youtube.com/watch/0Xn1QhNtPkQ)

Welcome to the first episode of "How Flutter Works," a six-part series designed to explore what happens to your Dart code after you hand it off to the Flutter framework. This episode takes a high-level look at Flutter's architecture, covering declarative code, multi-platform frameworks, and the role of Dart. This video is perfect for anyone beginning to research Flutter who wants to understand the big picture.

2## Widgets and the three treeskeyboard\_arrow\_up

[Watch on YouTube in a new tab: "The three trees"](https://www.youtube.com/watch/xiW3ahr4CRU)

Dive into Flutter's architecture by exploring its three primary trees: `Widget`, `Element`, and `RenderObject`. Discover how widgets provide the declarative API for Flutter developers and see how elements glue widgets to the rendering layer. You'll also learn about the role of render objects in translating widget values into painting calls.

3## State objects and their lifecyclekeyboard\_arrow\_up

[Watch on YouTube in a new tab: "The state class"](https://www.youtube.com/watch/FP737UMx7ss)

Episode 3 of "How Flutter Works" dives deep into the `State` class, a critical piece behind every `StatefulWidget`. Follow the full lifecycle of a `State` object—from `initState`, where you initialize resources, to `dispose`, where you clean up. Along the way, explore important methods including `didChangeDependencies`, `didUpdateWidget`, and the all-important `build` method. By the end of the episode, you'll understand how `State` objects track, respond to, and manage changes in your Flutter apps—and how the `State` lifecycle enables efficient UI updates.

This episode also peels back the curtain on how Flutter recurses down the widget tree after a `setState` call, building out only the parts of your app that need to change. You'll learn why `const` constructors matter for performance, why `setState` closures must be synchronous, and how elements (not widgets themselves) manage the actual rebuild process. If you're curious how Flutter keeps apps fast and responsive, or you just want to really understand what happens behind the scenes, this episode is packed with the essential foundations.

4## The widgets that actually renderkeyboard\_arrow\_up

[Watch on YouTube in a new tab: "The RenderObjectWidget"](https://www.youtube.com/watch/zcJlHVVM84I)

Ever wonder how your Flutter app actually renders to the screen? This video dives into `RenderObjectWidget`—the only type of widget in Flutter that creates something visual. While stateless and stateful widgets help structure your app, it's render object widgets that turn your UI code into real pixels.

You'll learn how Flutter builds the `Widget`, `Element`, and `RenderObject` trees, why many common widgets don't directly render anything, and how Flutter uses `RenderObjectWidget` to create and update render objects that power your UI.

5## A day in the life of a render objectkeyboard\_arrow\_up

[Watch on YouTube in a new tab: "A day in the life of a RenderObject"](https://www.youtube.com/watch/EuG12bebwac)

In Episode 5 of "How Flutter Works," Craig walks through a full day in the life of a `RenderObject`. Building on the concepts from Episode 4, this video explains the core responsibilities of render objects: layout, painting, hit testing, and accessibility. Craig demystifies how constraints flow down the render tree, how sizes come back up, and how parent render objects set their children's positions. He also breaks down key methods like `layout`, `paint`, and `describeSemanticsConfiguration`, showing how they fit together to keep your UI responsive and accurate.

6## The Flutter engine and embedderskeyboard\_arrow\_up

[Watch on YouTube in a new tab: "The Flutter Engine and Embedders"](https://www.youtube.com/watch/Y2aBMjWVv2Y)

In Episode 6 of "How Flutter Works," Craig takes us beneath the Dart code to explore the Flutter engine and embedders. This episode explains how Flutter mobile apps rely on native Android and iOS code to launch and operate, how the Flutter engine connects your Dart code to the host platform, and how embedders facilitate communication between the two. Craig also highlights the structure of a newly generated Flutter project, dives into how threads are managed in a Flutter app, and explains the roles of platform channels and the [Pigeon](https://pub.dev/packages/pigeon) package.

You'll also learn why the Flutter engine is written in C++ rather than Dart, how it evolved from a fork of Chrome, and how it uses Skia or Impeller to render each frame. The episode wraps up by looking ahead to Flutter's future architecture improvements, which aim to simplify native interop even further. If you want a clear mental model of how Flutter apps work under the hood, this is the perfect way to connect all the layers together.

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/learn/pathway/how-flutter-works.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/learn/pathway/how-flutter-works\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/learn/pathway/how-flutter-works.md "Report an issue with this page").

----
url: https://docs.flutter.dev/cookbook/effects/staggered-menu-animation
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Create a staggered menu animation](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

A single app screen might contain multiple animations. Playing all of the animations at the same time can be overwhelming. Playing the animations one after the other can take too long. A better option is to stagger the animations. Each animation begins at a different time, but the animations overlap to create a shorter duration. In this recipe, you build a drawer menu with animated content that is staggered and has a button that pops in at the bottom.

The following animation shows the app's behavior:

## Create the menu without animations

[#](#create-the-menu-without-animations)

The drawer menu displays a list of titles, followed by a Get started button at the bottom of the menu.

Define a stateful widget called `Menu` that displays the list and button in static locations.

dart

```
class Menu extends StatefulWidget {
  const Menu({super.key});
​
  @override
  State<Menu> createState() => _MenuState();
}
​
class _MenuState extends State<Menu> {
  static const _menuTitles = [
    'Declarative Style',
    'Premade Widgets',
    'Stateful Hot Reload',
    'Native Performance',
    'Great Community',
  ];
​
  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.white,
      child: Stack(
        fit: StackFit.expand,
        children: [_buildFlutterLogo(), _buildContent()],
      ),
    );
  }
​
  Widget _buildFlutterLogo() {
    // TODO: We'll implement this later.
    return Container();
  }
​
  Widget _buildContent() {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        const SizedBox(height: 16),
        ..._buildListItems(),
        const Spacer(),
        _buildGetStartedButton(),
      ],
    );
  }
​
  List<Widget> _buildListItems() {
    final listItems = <Widget>[];
    for (var i = 0; i < _menuTitles.length; ++i) {
      listItems.add(
        Padding(
          padding: const EdgeInsets.symmetric(horizontal: 36, vertical: 16),
          child: Text(
            _menuTitles[i],
            textAlign: TextAlign.left,
            style: const TextStyle(fontSize: 24, fontWeight: FontWeight.w500),
          ),
        ),
      );
    }
    return listItems;
  }
​
  Widget _buildGetStartedButton() {
    return SizedBox(
      width: double.infinity,
      child: Padding(
        padding: const EdgeInsets.all(24),
        child: ElevatedButton(
          style: ElevatedButton.styleFrom(
            shape: const StadiumBorder(),
            backgroundColor: Colors.blue,
            padding: const EdgeInsets.symmetric(horizontal: 48, vertical: 14),
          ),
          onPressed: () {},
          child: const Text(
            'Get Started',
            style: TextStyle(color: Colors.white, fontSize: 22),
          ),
        ),
      ),
    );
  }
}
```

## Prepare for animations

[#](#prepare-for-animations)

Control of the animation timing requires an `AnimationController`.

Add the `SingleTickerProviderStateMixin` to the `MenuState` class. Then, declare and instantiate an `AnimationController`.

dart

```
class _MenuState extends State<Menu> with SingleTickerProviderStateMixin {
  late AnimationController _staggeredController;
​
  @override
  void initState() {
    super.initState();
​
    _staggeredController = AnimationController(vsync: this);
  }
​
  @override
  void dispose() {
    _staggeredController.dispose();
    super.dispose();
  }
}
```

The length of the delay before every animation is up to you. Define the animation delays, individual animation durations, and the total animation duration.

dart

```
class _MenuState extends State<Menu> with SingleTickerProviderStateMixin {
  static const _initialDelayTime = Duration(milliseconds: 50);
  static const _itemSlideTime = Duration(milliseconds: 250);
  static const _staggerTime = Duration(milliseconds: 50);
  static const _buttonDelayTime = Duration(milliseconds: 150);
  static const _buttonTime = Duration(milliseconds: 500);
  final Duration _animationDuration =
      _initialDelayTime +
      (_staggerTime * _menuTitles.length) +
      _buttonDelayTime +
      _buttonTime;
}
```

In this case, all the animations are delayed by 50 ms. After that, list items begin to appear. Each list item's appearance is delayed by 50 ms after the previous list item begins to slide in. Each list item takes 250 ms to slide from right to left. After the last list item begins to slide in, the button at the bottom waits another 150 ms to pop in. The button animation takes 500 ms.

With each delay and animation duration defined, the total duration is calculated so that it can be used to calculate the individual animation times.

The desired animation times are shown in the following diagram:

To animate a value during a subsection of a larger animation, Flutter provides the `Interval` class. An `Interval` takes a start time percentage and an end time percentage. That `Interval` can then be used to animate a value between those start and end times, instead of using the entire animation's start and end times. For example, given an animation that takes 1 second, an interval from 0.2 to 0.5 would start at 200 ms (20%) and end at 500 ms (50%).

Declare and calculate each list item's `Interval` and the bottom button `Interval`.

dart

```
class _MenuState extends State<Menu> with SingleTickerProviderStateMixin {
  final List<Interval> _itemSlideIntervals = [];
  late Interval _buttonInterval;
​
  @override
  void initState() {
    super.initState();
​
    _createAnimationIntervals();
​
    _staggeredController = AnimationController(
      vsync: this,
      duration: _animationDuration,
    );
  }
​
  void _createAnimationIntervals() {
    for (var i = 0; i < _menuTitles.length; ++i) {
      final startTime = _initialDelayTime + (_staggerTime * i);
      final endTime = startTime + _itemSlideTime;
      _itemSlideIntervals.add(
        Interval(
          startTime.inMilliseconds / _animationDuration.inMilliseconds,
          endTime.inMilliseconds / _animationDuration.inMilliseconds,
        ),
      );
    }
​
    final buttonStartTime =
        Duration(milliseconds: _menuTitles.length * 50) + _buttonDelayTime;
    final buttonEndTime = buttonStartTime + _buttonTime;
    _buttonInterval = Interval(
      buttonStartTime.inMilliseconds / _animationDuration.inMilliseconds,
      buttonEndTime.inMilliseconds / _animationDuration.inMilliseconds,
    );
  }
}
```

## Animate the list items and button

[#](#animate-the-list-items-and-button)

The staggered animation plays as soon as the menu becomes visible.

Start the animation in `initState()`.

dart

```
@override
void initState() {
  super.initState();
​
  _createAnimationIntervals();
​
  _staggeredController = AnimationController(
    vsync: this,
    duration: _animationDuration,
  )..forward();
}
```

Each list item slides from right to left and fades in at the same time.

Use the list item's `Interval` and an `easeOut` curve to animate the opacity and translation values for each list item.

dart

```
List<Widget> _buildListItems() {
  final listItems = <Widget>[];
  for (var i = 0; i < _menuTitles.length; ++i) {
    listItems.add(
      AnimatedBuilder(
        animation: _staggeredController,
        builder: (context, child) {
          final animationPercent = Curves.easeOut.transform(
            _itemSlideIntervals[i].transform(_staggeredController.value),
          );
          final opacity = animationPercent;
          final slideDistance = (1.0 - animationPercent) * 150;
​
          return Opacity(
            opacity: opacity,
            child: Transform.translate(
              offset: Offset(slideDistance, 0),
              child: child,
            ),
          );
        },
        child: Padding(
          padding: const EdgeInsets.symmetric(horizontal: 36, vertical: 16),
          child: Text(
            _menuTitles[i],
            textAlign: TextAlign.left,
            style: const TextStyle(fontSize: 24, fontWeight: FontWeight.w500),
          ),
        ),
      ),
    );
  }
  return listItems;
}
```

Use the same approach to animate the opacity and scale of the bottom button. This time, use an `elasticOut` curve to give the button a springy effect.

dart

```
Widget _buildGetStartedButton() {
  return SizedBox(
    width: double.infinity,
    child: Padding(
      padding: const EdgeInsets.all(24),
      child: AnimatedBuilder(
        animation: _staggeredController,
        builder: (context, child) {
          final animationPercent = Curves.elasticOut.transform(
            _buttonInterval.transform(_staggeredController.value),
          );
          final opacity = animationPercent.clamp(0.0, 1.0);
          final scale = (animationPercent * 0.5) + 0.5;
​
          return Opacity(
            opacity: opacity,
            child: Transform.scale(scale: scale, child: child),
          );
        },
        child: ElevatedButton(
          style: ElevatedButton.styleFrom(
            shape: const StadiumBorder(),
            backgroundColor: Colors.blue,
            padding: const EdgeInsets.symmetric(horizontal: 48, vertical: 14),
          ),
          onPressed: () {},
          child: const Text(
            'Get Started',
            style: TextStyle(color: Colors.white, fontSize: 22),
          ),
        ),
      ),
    ),
  );
}
```

Congratulations! You have an animated menu where the appearance of each list item is staggered, followed by a bottom button that pops into place.

## Interactive example

[#](#interactive-example)

```
import 'package:flutter/material.dart';

void main() {
  runApp(
    const MaterialApp(
      home: ExampleStaggeredAnimations(),
      debugShowCheckedModeBanner: false,
    ),
  );
}

class ExampleStaggeredAnimations extends StatefulWidget {
  const ExampleStaggeredAnimations({super.key});

  @override
  State<ExampleStaggeredAnimations> createState() =>
      _ExampleStaggeredAnimationsState();
}

class _ExampleStaggeredAnimationsState extends State<ExampleStaggeredAnimations>
    with SingleTickerProviderStateMixin {
  late AnimationController _drawerSlideController;

  @override
  void initState() {
    super.initState();

    _drawerSlideController = AnimationController(
      vsync: this,
      duration: const Duration(milliseconds: 150),
    );
  }

  @override
  void dispose() {
    _drawerSlideController.dispose();
    super.dispose();
  }

  bool _isDrawerOpen() {
    return _drawerSlideController.value == 1.0;
  }

  bool _isDrawerOpening() {
    return _drawerSlideController.status == AnimationStatus.forward;
  }

  bool _isDrawerClosed() {
    return _drawerSlideController.value == 0.0;
  }

  void _toggleDrawer() {
    if (_isDrawerOpen() || _isDrawerOpening()) {
      _drawerSlideController.reverse();
    } else {
      _drawerSlideController.forward();
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.white,
      appBar: _buildAppBar(),
      body: Stack(children: [_buildContent(), _buildDrawer()]),
    );
  }

  PreferredSizeWidget _buildAppBar() {
    return AppBar(
      title: const Text('Flutter Menu', style: TextStyle(color: Colors.black)),
      backgroundColor: Colors.transparent,
      elevation: 0.0,
      automaticallyImplyLeading: false,
      actions: [
        AnimatedBuilder(
          animation: _drawerSlideController,
          builder: (context, child) {
            return IconButton(
              onPressed: _toggleDrawer,
              icon: _isDrawerOpen() || _isDrawerOpening()
                  ? const Icon(Icons.clear, color: Colors.black)
                  : const Icon(Icons.menu, color: Colors.black),
            );
          },
        ),
      ],
    );
  }

  Widget _buildContent() {
    // Put page content here.
    return const SizedBox();
  }

  Widget _buildDrawer() {
    return AnimatedBuilder(
      animation: _drawerSlideController,
      builder: (context, child) {
        return FractionalTranslation(
          translation: Offset(1.0 - _drawerSlideController.value, 0.0),
          child: _isDrawerClosed() ? const SizedBox() : const Menu(),
        );
      },
    );
  }
}

class Menu extends StatefulWidget {
  const Menu({super.key});

  @override
  State<Menu> createState() => _MenuState();
}

class _MenuState extends State<Menu> with SingleTickerProviderStateMixin {
  static const _menuTitles = [
    'Declarative style',
    'Premade widgets',
    'Stateful hot reload',
    'Native performance',
    'Great community',
  ];

  static const _initialDelayTime = Duration(milliseconds: 50);
  static const _itemSlideTime = Duration(milliseconds: 250);
  static const _staggerTime = Duration(milliseconds: 50);
  static const _buttonDelayTime = Duration(milliseconds: 150);
  static const _buttonTime = Duration(milliseconds: 500);
  final Duration _animationDuration =
      _initialDelayTime +
      (_staggerTime * _menuTitles.length) +
      _buttonDelayTime +
      _buttonTime;

  late AnimationController _staggeredController;
  final List<Interval> _itemSlideIntervals = [];
  late Interval _buttonInterval;

  @override
  void initState() {
    super.initState();

    _createAnimationIntervals();

    _staggeredController = AnimationController(
      vsync: this,
      duration: _animationDuration,
    )..forward();
  }

  void _createAnimationIntervals() {
    for (var i = 0; i < _menuTitles.length; ++i) {
      final startTime = _initialDelayTime + (_staggerTime * i);
      final endTime = startTime + _itemSlideTime;
      _itemSlideIntervals.add(
        Interval(
          startTime.inMilliseconds / _animationDuration.inMilliseconds,
          endTime.inMilliseconds / _animationDuration.inMilliseconds,
        ),
      );
    }

    final buttonStartTime =
        Duration(milliseconds: _menuTitles.length * 50) + _buttonDelayTime;
    final buttonEndTime = buttonStartTime + _buttonTime;
    _buttonInterval = Interval(
      buttonStartTime.inMilliseconds / _animationDuration.inMilliseconds,
      buttonEndTime.inMilliseconds / _animationDuration.inMilliseconds,
    );
  }

  @override
  void dispose() {
    _staggeredController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.white,
      child: Stack(
        fit: StackFit.expand,
        children: [_buildFlutterLogo(), _buildContent()],
      ),
    );
  }

  Widget _buildFlutterLogo() {
    return const Positioned(
      right: -100,
      bottom: -30,
      child: Opacity(opacity: 0.2, child: FlutterLogo(size: 400)),
    );
  }

  Widget _buildContent() {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        const SizedBox(height: 16),
        ..._buildListItems(),
        const Spacer(),
        _buildGetStartedButton(),
      ],
    );
  }

  List<Widget> _buildListItems() {
    final listItems = <Widget>[];
    for (var i = 0; i < _menuTitles.length; ++i) {
      listItems.add(
        AnimatedBuilder(
          animation: _staggeredController,
          builder: (context, child) {
            final animationPercent = Curves.easeOut.transform(
              _itemSlideIntervals[i].transform(_staggeredController.value),
            );
            final opacity = animationPercent;
            final slideDistance = (1.0 - animationPercent) * 150;

            return Opacity(
              opacity: opacity,
              child: Transform.translate(
                offset: Offset(slideDistance, 0),
                child: child,
              ),
            );
          },
          child: Padding(
            padding: const EdgeInsets.symmetric(horizontal: 36, vertical: 16),
            child: Text(
              _menuTitles[i],
              textAlign: TextAlign.left,
              style: const TextStyle(fontSize: 24, fontWeight: FontWeight.w500),
            ),
          ),
        ),
      );
    }
    return listItems;
  }

  Widget _buildGetStartedButton() {
    return SizedBox(
      width: double.infinity,
      child: Padding(
        padding: const EdgeInsets.all(24),
        child: AnimatedBuilder(
          animation: _staggeredController,
          builder: (context, child) {
            final animationPercent = Curves.elasticOut.transform(
              _buttonInterval.transform(_staggeredController.value),
            );
            final opacity = animationPercent.clamp(0.0, 1.0);
            final scale = (animationPercent * 0.5) + 0.5;

            return Opacity(
              opacity: opacity,
              child: Transform.scale(scale: scale, child: child),
            );
          },
          child: ElevatedButton(
            style: ElevatedButton.styleFrom(
              shape: const StadiumBorder(),
              backgroundColor: Colors.blue,
              padding: const EdgeInsets.symmetric(horizontal: 48, vertical: 14),
            ),
            onPressed: () {},
            child: const Text(
              'Get started',
              style: TextStyle(color: Colors.white, fontSize: 22),
            ),
          ),
        ),
      ),
    );
  }
}
```

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/effects/staggered-menu-animation.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/cookbook/effects/staggered-menu-animation\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/effects/staggered-menu-animation.md "Report an issue with this page").

----
url: https://docs.flutter.dev/tools/android-studio
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Android Studio and IntelliJ](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

* [Android Studio and IntelliJ]()
* [Visual Studio Code](/tools/vs-code)

[]()

## Installation and setup

[#](#setup)

Android Studio and IntelliJ IDEA offer a complete, IDE experience once you install the Flutter plugin.

To install the latest version of the following IDEs, follow their instructions:

* [Android Studio](https://developer.android.com/studio/install)
* [IntelliJ IDEA Community](https://www.jetbrains.com/idea/download/)
* [IntelliJ IDEA Ultimate](https://www.jetbrains.com/idea/download/)

### Install the Flutter plugin

[#](#install-plugin)

* [Windows](#18-tab-panel)
* [macOS](#19-tab-panel)
* [Linux](#20-tab-panel)

1. Go to **File** > **Settings**.

   You can also press `Ctrl` + `Alt` + `S`.

   The **Preferences** dialog opens.

2. From the list at the left, select **Plugins**.

3. From the top of this panel, select **Marketplace**.

4. Type `flutter` in the plugin search field.

5. Select the **Flutter** plugin.

6. Click **Install**.

7. Click **Yes** when prompted to install the plugin.

8. Click **Restart** when prompted.

1) Start Android Studio or IntelliJ.

2) From the macOS menu bar, go to **Android Studio** (or **IntelliJ**) > **Settings...**.

   You can also press `Cmd` + `,`.

   The **Preferences** dialog opens.

3) From the list at the left, select **Plugins**.

4) From the top of this panel, select **Marketplace**.

5) Type `flutter` in the plugin search field.

6) Select the **Flutter** plugin.

7) Click **Install**.

8) Click **Yes** when prompted to install the plugin.

9) Click **Restart** when prompted.

1. Go to **File** > **Settings**.

   You can also press `Ctrl` + `Alt` + `S`.

   The **Preferences** dialog opens.

2. From the list at the left, select **Plugins**.

3. From the top of this panel, select **Marketplace**.

4. Type `flutter` in the plugin search field.

5. Select the **Flutter** plugin.

6. Click **Install**.

7. Click **Yes** when prompted to install the plugin.

8. Click **Restart** when prompted.

### Updating the plugins

[#](#updating)

Updates to the plugins are shipped on a regular basis. You should be prompted in the IDE when an update is available.

To check for updates manually:

1. Open preferences (**Android Studio > Check for Updates** on macOS, **Help > Check for Updates** on Linux).
2. If `dart` or `flutter` are listed, update them.

## Creating projects

[#](#creating-projects)

You can create a new project in one of several ways.

### Creating a new project

[#](#creating-a-new-project)

Creating a new Flutter project from the Flutter starter app template differs between Android Studio and IntelliJ.

**In Android Studio:**

1. In the IDE, click **New Flutter Project** from the **Welcome** window or **File > New > New Flutter Project** from the main IDE window.
2. Specify the **Flutter SDK path** and click **Next**.
3. Enter your desired **Project name**, **Description**, and **Project location**.
4. If you might publish this app, [set the company domain](#set-the-company-domain).
5. Click **Finish**.

**In IntelliJ:**

1. In the IDE, click **New Project** from the **Welcome** window or **File > New > Project** from the main IDE window.
2. Select **Flutter** from the **Generators** list in the left panel
3. Specify the **Flutter SDK path** and click **Next**.
4. Enter your desired **Project name**, **Description**, and **Project location**.
5. If you might publish this app, [set the company domain](#set-the-company-domain).
6. Click **Finish**.

#### Set the company domain

[#](#set-the-company-domain)

When creating a new app, some Flutter IDE plugins ask for an organization name in reverse domain order, something like `com.example`. Along with the name of the app, this is used as the package name for Android, and the Bundle ID for iOS when the app is released. If you think you might ever release this app, it is better to specify these now. They cannot be changed once the app is released. Your organization name should be unique.

### Opening a project from existing source code

[#](#opening-a-project-from-existing-source-code)

To open an existing Flutter project:

1. In the IDE, click **Open** from the **Welcome** window, or **File > Open** from the main IDE window.

2. Browse to the directory holding your existing Flutter source code files.

3. Click **Open**.

## Editing code and viewing issues

[#](#editing-code-and-viewing-issues)

The Flutter plugin performs code analysis that enables the following:

* Syntax highlighting.
* Code completions based on rich type analysis.
* Navigating to type declarations (**Navigate > Declaration**), and finding type usages (**Edit > Find > Find Usages**).
* Viewing all current source code problems (**View > Tool Windows > Dart Analysis**). Any analysis issues are shown in the Dart Analysis pane:

## Running and debugging

[#](#running-and-debugging)

Running and debugging are controlled from the main toolbar:

### Selecting a target

[#](#selecting-a-target)

When a Flutter project is open in the IDE, you should see a set of Flutter-specific buttons on the right-hand side of the toolbar.

1. Locate the **Flutter Target Selector** drop-down button. This shows a list of available targets.
2. Select the target you want your app to be started on. When you connect devices, or start simulators, additional entries appear.

### Run app without breakpoints

[#](#run-app-without-breakpoints)

1. Click the **Play icon** in the toolbar, or invoke **Run > Run**. The bottom **Run** pane shows logs output.

### Run app with breakpoints

[#](#run-app-with-breakpoints)

1. If desired, set breakpoints in your source code.

2. Click the **Debug icon** in the toolbar, or invoke **Run > Debug**.

   * The bottom **Debugger** pane shows Stack Frames and Variables.
   * The bottom **Console** pane shows detailed logs output.
   * Debugging is based on a default launch configuration. To customize this, click the drop-down button to the right of the device selector, and select **Edit configuration**.

## Fast edit and refresh development cycle

[#](#fast-edit-and-refresh-development-cycle)

Flutter offers a best-in-class developer cycle enabling you to see the effect of your changes almost instantly with the *Stateful Hot Reload* feature. To learn more, check out [Hot reload](/tools/hot-reload).

### Show performance data

[#](#show-performance-data)

To view the performance data, including the widget rebuild information, start the app in **Debug** mode, and then open the Performance tool window using **View > Tool Windows > Flutter Performance**.

To see the stats about which widgets are being rebuilt, and how often, click **Show widget rebuild information** in the **Performance** pane. The exact count of the rebuilds for this frame displays in the second column from the right. For a high number of rebuilds, a yellow spinning circle displays. The column to the far right shows how many times a widget was rebuilt since entering the current screen. For widgets that aren't rebuilt, a solid grey circle displays. Otherwise, a grey spinning circle displays.

The purpose of this feature is to make you aware when widgets are rebuilding—you might not realize that this is happening when just looking at the code. If widgets are rebuilding that you didn't expect, it's probably a sign that you should refactor your code by splitting up large build methods into multiple widgets.

This tool can help you debug at least four common performance issues:

1. The whole screen (or large pieces of it) are built by a single StatefulWidget, causing unnecessary UI building. Split up the UI into smaller widgets with smaller `build()` functions.

2. Offscreen widgets are being rebuilt. This can happen, for example, when a ListView is nested in a tall Column that extends offscreen. Or when the RepaintBoundary is not set for a list that extends offscreen, causing the whole list to be redrawn.

3. The `build()` function for an AnimatedBuilder draws a subtree that does not need to be animated, causing unnecessary rebuilds of static objects.

4. An Opacity widget is placed unnecessarily high in the widget tree. Or, an Opacity animation is created by directly manipulating the opacity property of the Opacity widget, causing the widget itself and its subtree to rebuild.

You can click on a line in the table to navigate to the line in the source where the widget is created. As the code runs, the spinning icons also display in the code pane to help you visualize which rebuilds are happening.

Note that numerous rebuilds doesn't necessarily indicate a problem. Typically you should only worry about excessive rebuilds if you have already run the app in profile mode and verified that the performance is not what you want.

And remember, *the widget rebuild information is only available in a debug build*. Test the app's performance on a real device in a profile build, but debug performance issues in a debug build.

## Editing tips for Flutter code

[#](#editing-tips-for-flutter-code)

If you have additional tips we should share, [let us know](https://github.com/flutter/website/issues/new)!

### Assists & quick fixes

[#](#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 (`Alt`+`Enter` on Linux and Windows, `Option`+`Return` on macOS), as illustrated here:

Quick Fixes are similar, only they are shown with a piece of code has an error and they can assist in correcting it. They are indicated with a red lightbulb.

#### Wrap with new widget assist

[#](#wrap-with-new-widget-assist)

This can be used when you have a widget that you want to wrap in a surrounding widget, for example if you want to wrap a widget in a `Row` or `Column`.

#### Wrap widget list with new widget assist

[#](#wrap-widget-list-with-new-widget-assist)

Similar to the assist above, but for wrapping an existing list of widgets rather than an individual widget.

#### Convert child to children assist

[#](#convert-child-to-children-assist)

Changes a child argument to a children argument, and wraps the argument value in a list.

### Live templates

[#](#live-templates)

Live templates can be used to speed up entering typical code structures. They are invoked by typing their prefix, and then selecting it in the code completion window:

The Flutter plugin includes the following templates:

* Prefix `stless`: Create a new subclass of `StatelessWidget`.
* Prefix `stful`: Create a new subclass of `StatefulWidget` and its associated State subclass.
* Prefix `stanim`: Create a new subclass of `StatefulWidget` and its associated State subclass, including a field initialized with an `AnimationController`.

You can also define custom templates in **Settings > Editor > Live Templates**.

### Keyboard shortcuts

[#](#keyboard-shortcuts)

**Hot reload**

On Linux (keymap *Default for XWin*) and Windows the keyboard shortcuts are `Control`+`Alt`+`;` and `Control`+`Backslash`.

On macOS (keymap *Mac OS X 10.5+ copy*) the keyboard shortcuts are `Command`+`Option` and `Command`+`Backslash`.

Keyboard mappings can be changed in the IDE Preferences/Settings: Select *Keymap*, then enter *flutter* into the search box in the upper right corner. Right click the binding you want to change and *Add Keyboard Shortcut*.

### Hot reload vs. hot restart

[#](#hot-reload-vs-hot-restart)

Hot reload works by injecting updated source code files into the running Dart VM (Virtual Machine). This includes not only adding new classes, but also adding methods and fields to existing classes, and changing existing functions. A few types of code changes cannot be hot reloaded though:

* Global variable initializers
* Static field initializers
* The `main()` method of the app

For these changes you can fully restart your application, without having to end your debugging session. To perform a hot restart, don't click the Stop button, simply re-click the Run button (if in a run session) or Debug button (if in a debug session), or shift-click the 'hot reload' button.

## Editing Android code in Android Studio with full IDE support

[#](#android-ide)

Opening the root directory of a Flutter project doesn't expose all the Android files to the IDE. Flutter apps contain a subdirectory named `android`. If you open this subdirectory as its own separate project in Android Studio, the IDE will be able to fully support editing and refactoring all Android files (like Gradle scripts).

If you already have the entire project opened as a Flutter app in Android Studio, there are two equivalent ways to open the Android files on their own for editing in the IDE. Before trying this, make sure that you're on the latest version of Android Studio and the Flutter plugins.

* In the ["project view"](https://developer.android.com/studio/projects/#ProjectView), you should see a subdirectory immediately under the root of your flutter app named `android`. Right click on it, then select **Flutter > Open Android module in Android Studio**.
* OR, you can open any of the files under the `android` subdirectory for editing. You should then see a "Flutter commands" banner at the top of the editor with a link labeled **Open for Editing in Android Studio**. Click that link.

For both options, Android Studio gives you the option to use separate windows or to replace the existing window with the new project when opening a second project. Either option is fine.

If you don't already have the Flutter project opened in Android studio, you can open the Android files as their own project from the start:

1. Click **Open an existing Android Studio Project** on the Welcome splash screen, or **File > Open** if Android Studio is already open.
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 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.

## Editing Android code in IntelliJ IDEA

[#](#edit-android-code)

To enable editing of Android code in IntelliJ IDEA, you need to configure the location of the Android SDK:

1. In **Preferences > Plugins**, enable **Android Support** if you haven't already.
2. Right-click the **android** folder in the Project view, and select **Open Module Settings**.
3. In the **Sources** tab, locate the **Language level** field, and select level 8 or later.
4. In the **Dependencies** tab, locate the **Module SDK** field, and select an Android SDK. If no SDK is listed, click **New** and specify the location of the Android SDK. Make sure to select an Android SDK matching the one used by Flutter (as reported by `flutter doctor`).
5. Click **OK**.

## Flutter Property Editor

[#](#property-editor)

The Flutter Property Editor is a powerful tool provided by the [Flutter plugin](https://plugins.jetbrains.com/plugin/9212-flutter) that lets you view and modify widget properties directly from its visual interface.

### How to open the Flutter Property Editor in Android Studio and IntelliJ

[#](#how-to-open-the-flutter-property-editor-in-android-studio-and-intellij)

1. Click on the Flutter Property Editor **icon** in the Android Studio or IntelliJ sidebar.
2. The Flutter Property Editor will load in the side panel.
3. Please refer to the Flutter Property Editor [documentation](/tools/property-editor) for a detailed usage guide.

## Troubleshooting

[#](#troubleshooting)

### Known issues and feedback

[#](#known-issues-and-feedback)

Important known issues that might impact your experience are documented in the [Flutter plugin README](https://github.com/flutter/flutter-intellij/blob/master/README.md) file.

Known bugs and feature requests are tracked in the issue trackers:

* [Flutter plugin issue tracker](https://github.com/flutter/flutter-intellij/issues)
* [Dart plugin issue tracker](https://github.com/flutter/dart-intellij-third-party/issues)

We welcome feedback, both on bugs/issues and feature requests. Prior to filing new issues:

* Do a quick search in the issue trackers to see if the issue is already tracked.
* Make sure you have [updated](#updating) to the most recent version of the plugin.

When filing new issues, include the output of [`flutter doctor`](/resources/bug-reports#provide-some-flutter-diagnostics).

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/android-studio.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/tools/android-studio\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/android-studio.md "Report an issue with this page").

----
url: https://docs.flutter.dev/add-to-app/macos/add-flutter-screen
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Add a Flutter screen to an macOS app](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

This guide describes how to add a single Flutter screen to an existing macOS app.

## Start a FlutterEngine and FlutterViewController

[#](#start-a-flutterengine-and-flutterviewcontroller)

To launch a Flutter screen from an existing macOS app, you start a [`FlutterEngine`](https://api.flutter.dev/macos-embedder/interface_flutter_engine.html) and a [`FlutterViewController`](https://api.flutter.dev/macos-embedder/interface_flutter_view_controller.html).

The `FlutterEngine` might have the same lifespan as your `FlutterViewController` or outlive your `FlutterViewController`.

See [Loading sequence and performance](/add-to-app/performance) for more analysis on the latency and memory trade-offs of pre-warming an engine.

### Create a FlutterEngine

[#](#create-a-flutterengine)

Where you create a `FlutterEngine` depends on your host app.

* [SwiftUI](#113-tab-panel)
* [AppKit-Swift](#114-tab-panel)

In this example, we create a `FlutterEngine` object inside a SwiftUI [`Observable`](https://developer.apple.com/documentation/observation/observable) object called `FlutterDependencies`. Pre-warm the engine by calling `run()`, and then inject this object into a `ContentView` using the `environment()` view modifier.

MyApp.swift

swift

```
import SwiftUI
import FlutterMacOS
// The following library connects plugins with macOS platform code to this app.
import FlutterPluginRegistrant
​
@Observable
class FlutterDependencies {
 let flutterEngine = FlutterEngine(name: "my flutter engine", project: nil)
 init() {
   // Runs the default Dart entrypoint with a default Flutter route.
   flutterEngine.run(withEntrypoint: nil)
   // Connects plugins with macOS platform code to this app.
   RegisterGeneratedPlugins(registry: self.flutterEngine)
 }
}
​
@main
struct MyApp: App {
   // flutterDependencies will be injected through the view environment.
   @State var flutterDependencies = FlutterDependencies()
   var body: some Scene {
     WindowGroup {
       ContentView()
         .environment(flutterDependencies)
     }
   }
}
```

As an example, we demonstrate creating a `FlutterEngine`, exposed as a property, on app startup in the app delegate.

AppDelegate.swift

swift

```
import Cocoa
import FlutterMacOS
// The following library connects plugins with macOS platform code to this app.
import FlutterPluginRegistrant
​
@main
class AppDelegate: FlutterAppDelegate {
  lazy var flutterEngine = FlutterEngine(name: "my flutter engine", project: nil)
​
  override func applicationDidFinishLaunching(_ aNotification: Notification) {
    flutterEngine.run(withEntrypoint: nil)
    RegisterGeneratedPlugins(registry: self.flutterEngine)
  }
}
```

### Show a FlutterViewController with your FlutterEngine

[#](#show-a-flutterviewcontroller-with-your-flutterengine)

* [SwiftUI](#115-tab-panel)
* [AppKit-Swift](#116-tab-panel)

The following example shows a generic `ContentView` with a [`NavigationLink`](https://developer.apple.com/documentation/swiftui/navigationlink) hooked to a flutter screen. First, create a `FlutterViewControllerRepresentable` to represent the `FlutterViewController`. The `FlutterViewController` constructor takes the pre-warmed `FlutterEngine` as an argument, which is injected through the view environment.

ContentView\.swift

swift

```
import SwiftUI
import FlutterMacOS
​
struct FlutterViewControllerRepresentable: NSViewControllerRepresentable {
  // Flutter dependencies are passed in through the view environment.
  @Environment(FlutterDependencies.self) var flutterDependencies
​
  func makeNSViewController(context: Context) -> FlutterViewController {
    return FlutterViewController(
      engine: flutterDependencies.flutterEngine,
      nibName: nil,
      bundle: nil
    )
  }
​
  func updateNSViewController(_ nsViewController: FlutterViewController, context: Context) {}
}
​
struct ContentView: View {
  var body: some View {
    NavigationStack {
      NavigationLink("My Flutter Feature") {
        FlutterViewControllerRepresentable()
      }
    }
  }
}
```

Now, you have a Flutter screen embedded in your macOS app.

The following example shows a generic `ViewController` with an `NSButton` hooked to present a [`FlutterViewController`](https://api.flutter.dev/macos-embedder/interface_flutter_view_controller.html). The `FlutterViewController` uses the `FlutterEngine` instance created in the `AppDelegate`.

ViewController.swift

swift

```
import Cocoa
import FlutterMacOS
​
class ViewController: NSViewController {
​
  override func viewDidLoad() {
    super.viewDidLoad()
​
    // Make a button to call the showFlutter function when pressed.
    let button =  NSButton(title: "Show Flutter!", target: self, action: #selector(showFlutter))
    button.frame = CGRect(x: 202, y: 187, width: 160.0, height: 40.0)
    self.view.addSubview(button)
  }
​
  @objc func showFlutter() {
    let flutterEngine = (NSApplication.shared.delegate as! AppDelegate).flutterEngine
    let flutterViewController =
        FlutterViewController(engine: flutterEngine, nibName: nil, bundle: nil)
    self.addChild(flutterViewController)
    flutterViewController.view.frame = self.view.bounds
    presentAsModalWindow(flutterViewController)
  }
}
```

Now, you have a Flutter screen embedded in your macOS app.

### *Alternatively* - Create a FlutterViewController with an implicit FlutterEngine

[#](#alternatively-create-a-flutterviewcontroller-with-an-implicit-flutterengine)

As an alternative to the previous example, you can let the `FlutterViewController` implicitly create its own `FlutterEngine` without pre-warming one ahead of time.

This is not usually recommended because creating a `FlutterEngine` on-demand could introduce a noticeable latency between when the `FlutterViewController` is presented and when it renders its first frame. This could, however, be useful if the Flutter screen is rarely shown, when there are no good heuristics to determine when the Dart VM should be started, and when Flutter doesn't need to persist state between view controllers.

To let the `FlutterViewController` present without an existing `FlutterEngine`, omit the `FlutterEngine` construction, and create the `FlutterViewController` without an engine reference.

* [SwiftUI](#117-tab-panel)
* [AppKit-Swift](#118-tab-panel)

ContentView\.swift

swift

```
// Existing code omitted.
func makeNSViewController(context: Context) -> FlutterViewController {
  return FlutterViewController()
}
```

ViewController.swift

swift

```
// Existing code omitted.
func showFlutter() {
  let flutterViewController = FlutterViewController()
  self.addChild(flutterViewController)
  flutterViewController.view.frame = self.view.bounds
  presentAsModalWindow(flutterViewController)
}
```

See [Loading sequence and performance](/add-to-app/performance) for more explorations on latency and memory usage.

## Using the FlutterAppDelegate

[#](#using-the-flutterappdelegate)

Letting your application's `UIApplicationDelegate` subclass `FlutterAppDelegate` is recommended but not required.

The `FlutterAppDelegate` performs functions such as:

* Forwarding application callbacks such as [`openURLs`](https://developer.apple.com/documentation/appkit/nsapplicationdelegate/application\(_:open:\)) to plugins such as [google\_sign\_in](https://pub.dev/packages/google_sign_in).

### Creating a FlutterAppDelegate subclass

[#](#creating-a-flutterappdelegate-subclass)

Creating a subclass of the `FlutterAppDelegate` in UIKit apps was shown in the [Start a FlutterEngine and FlutterViewController section](/add-to-app/macos/add-flutter-screen/#start-a-flutterengine-and-flutterviewcontroller). In a SwiftUI app, you can create a subclass of the `FlutterAppDelegate` and annotate it with the [`Observable()`](https://developer.apple.com/documentation/observation/observable\(\)) macro as follows:

MyApp.swift

swift

```
import SwiftUI
import FlutterMacOS
​
@Observable
class AppDelegate: FlutterAppDelegate {
  let flutterEngine = FlutterEngine(name: "my flutter engine", project: nil)
​
  override func applicationDidFinishLaunching(_ aNotification: Notification) {
    // Runs the default Dart entrypoint with a default Flutter route.
    flutterEngine.run(withEntrypoint: nil)
    // Used to connect plugins (only if you have plugins
    // with macOS platform code).
    RegisterGeneratedPlugins(registry: self.flutterEngine)
  }
}
​
@main
struct MyApp: App {
  // Use this property wrapper to tell SwiftUI
  // it should use the AppDelegate class for the application delegate
  @NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
​
  var body: some Scene {
    WindowGroup {
      ContentView()
    }
  }
}
```

Then, in your view, the `AppDelegate` is accessible through the view environment.

ContentView\.swift

swift

```
import SwiftUI
import FlutterMacOS
​
struct FlutterViewControllerRepresentable: NSViewControllerRepresentable {
  // Access the AppDelegate through the view environment.
  @Environment(AppDelegate.self) var appDelegate
​
  func makeNSViewController(context: Context) -> FlutterViewController {
    return FlutterViewController(
      engine: appDelegate.flutterEngine,
      nibName: nil,
      bundle: nil
    )
  }
​
  func updateNSViewController(_ nsViewController: FlutterViewController, context: Context) {}
}
​
struct ContentView: View {
  var body: some View {
    NavigationStack {
      NavigationLink("My Flutter Feature") {
        FlutterViewControllerRepresentable()
      }
    }
  }
}
```

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-18. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/add-to-app/macos/add-flutter-screen.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/add-to-app/macos/add-flutter-screen\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/add-to-app/macos/add-flutter-screen.md "Report an issue with this page").

----
url: https://docs.flutter.dev/tools/devtools/cpu-profiler
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Use the CPU profiler view](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

The CPU profiler view allows you to record and profile a session from your Dart or Flutter application. The profiler can help you solve performance problems or generally understand your app's CPU activity. The Dart VM collects CPU samples (a snapshot of the CPU call stack at a single point in time) and sends the data to DevTools for visualization. By aggregating many CPU samples together, the profiler can help you understand where the CPU spends most of its time.

## CPU profiler

[#](#cpu-profiler)

Start recording a CPU profile by clicking **Record**. When you are done recording, click **Stop**. At this point, CPU profiling data is pulled from the VM and displayed in the profiler views (Call tree, Bottom up, Method table, and Flame chart).

To load all available CPU samples without manually recording and stopping, you can click **Load all CPU samples**, which pulls all CPU samples that the VM has recorded and stored in its sample buffer and then displays those CPU samples in the profiler view.

By default, the VM's sample buffer is used as a ring buffer, meaning that once it has become full, new samples start overwriting the oldest ones in the buffer. To make the VM instead discard all samples collected after the sample buffer has become full, pass the `--profile-startup` flag to `dart run` or `flutter run`.

### Bottom up

[#](#bottom-up)

This table provides a bottom-up representation of a CPU profile. This means that each top-level method, or root, in the bottom-up table is actually the top method in the call stack for one or more CPU samples. In other words, each top-level method in a bottom-up table is a leaf node from the top-down table (the call tree). In this table, a method can be expanded to show its *callers*.

This view is useful for identifying expensive *methods* in a CPU profile. When a root node in this table has a high *self* time, that means that many CPU samples in this profile ended with that method on top of the call stack.

See the [Guidelines](#guidelines) section below to learn how to enable the blue and green vertical lines seen in this image.

Tooltips can help you understand the values in each column:

* **Total time**

  For top-level methods in the bottom-up tree

(stack frames that were at the top of at least one CPU sample), this is the time the method spent executing its own code, as well as the code for any methods that it called.

* **Self time**

  For top-level methods in the bottom-up tree

(stack frames that were at the top of at least one CPU sample), this is the time the method spent executing only its own code.\
\
For children methods in the bottom-up tree (the callers), this is the self time of the top-level method (the callee) when called through the child method (the caller).

**Table element** (self time)

### Call tree

[#](#call-tree)

This table provides a top-down representation of a CPU profile. This means that each top-level method in the call tree is a root of one or more CPU samples. In this table, a method can be expanded to show its *callees*.

This view is useful for identifying expensive *paths* in a CPU profile. When a root node in this table has a high *total* time, that means that many CPU samples in this profile started with that method on the bottom of the call stack.

See the [Guidelines](#guidelines) section below to learn how to enable the blue and green vertical lines seen in this image.

Tooltips can help you understand the values in each column:

* **Total time**

  Time that a method spent executing its own code as well as

the code for any methods it called.

* **Self time**

  Time the method spent executing only its own code.

### Method table

[#](#method-table)

The method table provides CPU statistics for each method contained in a CPU profile. In the table on the left, all available methods are listed with their **total** and **self** time.

**Total** time is the combined time that a method spent **anywhere** on the call stack, or in other words, the time a method spent executing its own code and any code for methods that it called.

**Self** time is the combined time that a method spent on top of the call stack, or in other words, the time a method spent executing only its own code.

Selecting a method from the table on the left shows the call graph for that method. The call graph shows a method's callers and callees and their respective caller / callee percentages.

### Flame chart

[#](#flame-chart)

The flame chart view is a graphical representation of the [Call tree](#call-tree). This is a top-down view of a CPU profile, so in this chart, the top-most method calls the one below it. The width of each flame chart element represents the amount of time that a method spent on the call stack.

Like the Call tree, this view is useful for identifying expensive paths in a CPU profile.

The help menu, which can be opened by clicking the `?` icon next to the search bar, provides information about how to navigate and zoom within the chart and a color-coded legend.

### CPU sampling rate

[#](#cpu-sampling-rate)

DevTools sets a rate at which the VM collects CPU samples: 1 sample / 250 μs (microseconds). This is selected by default on the CPU profiler page as "Cpu sampling rate: medium". This rate can be modified using the selector at the top of the page.

The **low**, **medium**, and **high** sampling rates are 1,000 Hz, 4,000 Hz, and 20,000 Hz, respectively. It's important to know the trade-offs of modifying this setting.

A profile that was recorded with a **higher** sampling rate yields a more fine-grained CPU profile with more samples. This might affect performance of your app since the VM is being interrupted more often to collect samples. This also causes the VM's CPU sample buffer to overflow more quickly. The VM has limited space where it can store CPU sample information. At a higher sampling rate, the space fills up and begins to overflow sooner than it would have if a lower sampling rate was used. This means that you might not have access to CPU samples from the beginning of the recorded profile, depending on whether the buffer overflows during the time of recording.

A profile that was recorded with a lower sampling rate yields a more coarse-grained CPU profile with fewer samples. This affects your app's performance less, but you might have access to less information about what the CPU was doing during the time of the profile. The VM's sample buffer also fills more slowly, so you can see CPU samples for a longer period of app run time. This means that you have a better chance of viewing CPU samples from the beginning of the recorded profile.

### Filtering

[#](#filtering)

When viewing a CPU profile, you can filter the data by library, method name, or [`UserTag`](https://api.flutter.dev/flutter/dart-developer/UserTag-class.html).

## Guidelines

[#](#guidelines)

When looking at a call tree or bottom up view, sometimes the trees can be very deep. To help with viewing parent-child relationships in a deep tree, enable the **Display guidelines** option. This adds vertical guidelines between parent and child in the tree.

## Other resources

[#](#other-resources)

To learn how to use DevTools to analyze the CPU usage of a compute-intensive Mandelbrot app, check out a guided [CPU Profiler View tutorial](https://medium.com/@fluttergems/mastering-dart-flutter-devtools-cpu-profiler-view-part-6-of-8-31e24eae6bf8). Also, learn how to analyze CPU usage when the app uses isolates for parallel computing.

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-07-31. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/cpu-profiler.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/tools/devtools/cpu-profiler\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/cpu-profiler.md "Report an issue with this page").

----
url: https://docs.flutter.dev/reference/create-new-app
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Create a new Flutter app](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

This page provides step-by-step instructions on how to bootstrap a new Flutter app in your preferred development environment.

To create a new Flutter app, first [set up Flutter](/install), then choose your preferred environment and follow the corresponding instructions.

[Add to app open\_in\_new](/add-to-app#get-started)

[Create a new Flutter module to embed in an existing app.](/add-to-app#get-started)

## VS Code

[#](#vs-code)

To create a Flutter app with [VS Code](https://code.visualstudio.com/) and other Code OSS-based editors, you first need to [install Flutter](/install) and [set up VS Code](/tools/vs-code#installation-and-setup) for Flutter development. Then follow these steps:

1. ### Launch VS Code

   Open VS Code or your preferred Code OSS-based editor.

2. ### Open the command palette

   Go to **View** > **Command Palette** or press `Cmd/Ctrl` + `Shift` + `P`.

3. ### Find the Flutter commands

   In the command palette, start typing `flutter:`. VS Code should surface commands from the Flutter plugin.

4. ### Run the new project command

   Select the **Flutter: New Project** command. Your OS or VS Code might ask for access to your documents, agree to continue to the next step.

5. ### Choose a template

   VS Code should prompt you with **Which Flutter template?**. Depending on what type of Flutter project you want to create, choose the corresponding template. For a new Flutter app, choose **Application**.

6. ### Select a project location

   A file dialog should appear. Select or create the parent directory where you want the project to be created. Don't create the project folder itself, the Flutter tool does so. To confirm your selection, click **Select a folder to create the project in**.

7. ### Enter a project name

   VS Code should prompt you to enter a name for your new project. Enter a name for your app that follows the `lowercase_with_underscores` naming convention, following the [Effective Dart](https://dart.dev/effective-dart/style#do-name-packages-and-file-system-entities-using-lowercase-with-underscores) guidelines. To confirm your selection, press `Enter`.

8. ### Wait for project initialization

   Based on the information you entered, VS Code uses `flutter create` to bootstrap your app. Progress is often surfaced as a notification in the bottom right and can also be accessed from the **Output** panel.

9. ### Run your app

   Your new app should now be created and open in VS Code. To try your new app, follow the steps to [run and debug](/tools/vs-code#running-and-debugging) in VS Code.

You've successfully created a new Flutter app in VS Code! If you need more help with developing Flutter in VS Code, check out the [VS Code for Flutter reference](/tools/vs-code).

## Android Studio

[#](#android-studio)

To create a Flutter app with Android Studio, you first need to [install Flutter](/install) and [set up Android Studio](/tools/android-studio#installation-and-setup) for Flutter development. Then follow these steps:

1. ### Launch Android Studio

   Open Android Studio with the Dart and Flutter plugins installed.

2. ### Begin project creation

   If you're on the IDE welcome dialog that says **Welcome to Android Studio**, find and click the **New Flutter Project** button in the center.

   If you already have a project open, either close it or go to **File** > **New** > **New Flutter Project...**.

3. ### Choose a project type

   In the **New Project** dialog, under **Generators** in the left panel, select **Flutter**.

4. ### Verify Flutter SDK setup

   At the top of the right panel, ensure the **Flutter SDK path** value matches the location of the Flutter SDK you'd like to develop with. If not, update it by choosing or specifying the correct one.

5. ### Configure your project

   Click **Next** to continue to project configuration. Multiple configuration options should appear.

   In the **Project name** field, enter a name for your app that follows the `lowercase_with_underscores` naming convention, following the [Effective Dart](https://dart.dev/effective-dart/style#do-name-packages-and-file-system-entities-using-lowercase-with-underscores) guidelines.

   If you're not creating an application, select another template from the **Project type** dropdown.

   If you're creating an app that you might publish in the future, set the **Organization** field [to your company domain](/tools/android-studio#set-the-company-domain).

   The other fields can be kept as is or configured according to your project's needs.

6. ### Finish project creation

   Once you've completed the configuration of your project, click **Create** to begin project initialization.

7. ### Wait for workspace initialization

   Android Studio will now initialize your workspace, bootstrap your project file structure, and retrieve your app's dependencies. This might take a while and can be tracked at the bottom of the window.

8. ### Run your app

   Your new app should now be created and open in Android Studio. To try your new app, follow the steps to [run and debug](/tools/android-studio#running-and-debugging) in Android Studio.

You've successfully created a new Flutter app in Android Studio! If you need more help with developing Flutter in Android Studio, check out the [Android Studio for Flutter reference](/tools/android-studio).

## IntelliJ

[#](#intellij)

To create a Flutter app with IntelliJ or other JetBrains IDEs, you first need to [install Flutter](/install) and [set up IntelliJ](/tools/android-studio#installation-and-setup) for Flutter development. Then follow these steps:

1. ### Launch IntelliJ

   Open IntelliJ IDEA or your preferred IntelliJ-based IDE by JetBrains that has the Dart and Flutter plugins installed.

2. ### Begin project creation

   If you're on the IDE welcome dialog that says **Welcome to IntelliJ IDEA**, find and click the **New Project** button in the upper right corner.

   If you already have a project open, either close it or go to **File** > **New** > **New Project...**.

3. ### Choose a project type

   In the **New Project** dialog, under **Generators** in the left panel, select **Flutter**.

4. ### Verify Flutter SDK setup

   At the top of the right panel, ensure the **Flutter SDK path** value matches the location of the Flutter SDK you'd like to develop with. If not, update it by choosing or specifying the correct one.

5. ### Configure your project

   Click **Next** to continue to project configuration. Multiple configuration options should appear.

   In the **Project name** field, enter a name for your app that follows the `lowercase_with_underscores` naming convention, following the [Effective Dart](https://dart.dev/effective-dart/style#do-name-packages-and-file-system-entities-using-lowercase-with-underscores) guidelines.

   If you're not creating an application, select another template from the **Project type** dropdown.

   If you're creating an app that you might publish in the future, set the **Organization** field \[to your company domain]\[ij-set-org].

   The other fields can be kept as is or configured according to your project's needs.

6. ### Finish project creation

   Once you've completed the configuration of your project, click **Create** to begin project initialization.

7. ### Wait for workspace initialization

   IntelliJ will now initialize your workspace, bootstrap your project file structure, and retrieve your app's dependencies. This might take a while and can be tracked at the bottom of the window.

8. ### Run your app

   Your new app should now be created and open in IntelliJ. To try your new app, follow the steps to [run and debug](/tools/android-studio#running-and-debugging) in IntelliJ.

You've successfully created a new Flutter app in IntelliJ! If you need more help with developing Flutter in IntelliJ, check out the [IntelliJ for Flutter reference](/tools/android-studio).

[]()

## Antigravity

[#](#antigravity)

To create a Flutter app with Antigravity, you first need to install and set up Antigravity as described on the [Get started with AI](/ai/get-started). Then follow these steps:

1. ### Open Antigravity and create a Workspace

   Launch the Antigravity IDE and create a new, sandboxed workspace for your project using the Agent Manager.

2. ### Use the Agent Manager

   Switch to the Agent Manager interface, which is where you interact with the AI agents by using chat.

3. ### Write detailed prompts

   Communicate your app requirements to the agent using detailed, structured prompts, much like providing instructions to a junior developer. For example, "Create a new Flutter project named my\_app. Add a home screen with a list of items and a floating action button".

4. ### Review and approve the plan

   The AI agent will generate a detailed implementation plan, including folder structure, dependencies, and steps. You can review this plan and click **Proceed** or provide feedback to make changes.

5. ### Authorize actions

   The agent will ask for permission before running terminal commands (like `flutter pub add` or `flutter create`) or accessing the browser/emulator. Review and **Accept** these actions to allow the agent to build the app.

6. ### Iterate and refine

   Once the initial app is generated, you can provide further prompts to add features, refine the UI, implement logic, or add persistence (for example, "Add local storage using shared\_preferences").

7. ### Test the app

   Use the integrated emulator or connect a physical device to test the app. The agent can even run tests and provide video walkthroughs of the functionality.

8. ### Verify code in the editor

   You can switch to the standard code editor view at any time to inspect the generated Dart and Flutter files, ensuring the code quality meets your standards.

## Terminal

[#](#terminal)

To create a Flutter app in your terminal, you first need to install and [set up Flutter](/install). Then follow these steps:

1. ### Open your terminal

   Open your preferred method to access the command line, such as Terminal on macOS or PowerShell on Windows.

2. ### Navigate to the desired directory

   Ensure your current working directory is the desired parent directory for your new app. Don't create the project folder, the `flutter` tool will do so.

3. ### Configure project creation

   In your terminal, type out the `flutter create` command and pass in any desired flags and options to configure your project. For example, to create an app with a minimal `main.dart` file, you can add the `--empty` option:

   ```
   $ flutter create --empty
   ```

   To learn about the available creation options, run `flutter create --help` in another terminal window.

4. ### Enter a project name

   As the only non-option argument to `flutter create`, specify the directory and default name for your application. The name should follow the `lowercase_with_underscores` naming convention, following the [Effective Dart](https://dart.dev/effective-dart/style#do-name-packages-and-file-system-entities-using-lowercase-with-underscores) guidelines.

   For example, if you wanted to create an app named `my_app`:

   ```
   $ flutter create my_app
   ```

5. ### Execute the configured command

   To create a project with your specified configuration, run the command you built in the previous step.

6. ### Wait for project initialization

   The `flutter` tool will now bootstrap your project's file structure and retrieve any necessary dependencies. This might take a while.

7. ### Navigate into the project directory

   Now that your project has been created, you can navigate to it in your terminal or your preferred editor. For example, with a bash shell and a project named `my_app`:

   ```
   $ cd my_app
   ```

8. ### Run your app

   To try your new app, run the `flutter run` command in your terminal and respond to its prompts to select an output device.

You've successfully created a new Flutter app in your terminal! If you need help configuring your project or with the `flutter` CLI tool, check out the [Flutter CLI reference](/reference/flutter-cli).

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-08-28. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/reference/create-new-app.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/reference/create-new-app\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/reference/create-new-app.md "Report an issue with this page").

----
url: https://docs.flutter.dev/platform-integration/android/sensitive-content.md
----

\# Protect your app's sensitive content > Learn how to protect sensitive content in your Flutter app. The \[\`SensitiveContent\`] widget allows you to prevent screens that contain sensitive content (such as passwords) from being projected. To learn more, check out the following two-minute Widget of the Week video: ## About the \`SensitiveContent\` widget You can use the \`SensitiveContent\` widget in your app to set the content sensitivity of a child \`Widget\` to one of the following \[\`ContentSensitivity\`] values: \`notSensitive\`, \`sensitive\`, or \`autoSensitive\`. Your chosen mode determines if the device screen should be obscured (blacked out) during media projection to protect sensitive data. You can have as many \`SensitiveContent\` widgets in your app as you wish, but if \_any\_ one of those widgets has a \`sensitive\` content value, then the entire screen is obscured during media projection. Thus, for most use cases, using multiple \`SensitiveContent\` widgets provides no advantage over having one \`SensitiveContent\` widget in your app’s widget tree. This feature is available on Android API 35+ and has no effect on lower API versions or other platforms. :::note The \`autoSensitive\` value isn't supported as of Flutter 3.35 and behaves the same as \`notSensitive\`. See \[Issue #160879]\[] for more information. ::: \[Issue #160879]: https\://github.com/flutter/flutter/issues/160879 ## Using the \`SensitiveContent\` widget Given some content that you want to protect from media screen share (for example, a \`MySensitiveContent()\` widget), you can wrap it with the \`SensitiveContent\` widget as shown in the following example: \`\`\`dart class MyWidget extends StatelessWidget { ... Widget build(BuildContext context) { return SensitiveContent( sensitivity: ContentSensitivity.sensitive, child: MySensitiveContent(), ); } } \`\`\` When running on Android API 34 and below, the screen won't be obscured during media projection. The widget will exist in the tree but has no other effect, and you don't need to avoid usages of \`SensitiveContent\` on platforms that don't support this feature. ## For more information For more information, visit the \[\`SensitiveContent\`]\[] and \[\`ContentSensitivity\`]\[] API docs. \[\`SensitiveContent\`]: https\://api.flutter.dev/flutter/widgets/SensitiveContent-class.html \[\`ContentSensitivity\`]: https\://api.flutter.dev/flutter/services/ContentSensitivity.html

----
url: https://docs.flutter.dev/deployment/flavors-ios
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Set up Flutter flavors for iOS and macOS](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

This guide shows you how to create Flutter flavors for an iOS or macOS app.

## Overview

[#](#overview)

A Flutter flavor is basically a collection of settings that define how a specific version of your app should be built and run. For example, a flavor could determine which icon, app name, API key, feature flag, and logging level is associated with a specific version of your app.

If you want to create Flutter flavors for an iOS app, you'll need to do so in Xcode. Xcode does not have a concept called "flavor". Instead, you'll need to set up something called a scheme and attach custom configurations to it.

The following illustrates an example of two Flutter flavors (staging, production) as Xcode schemes with custom Xcode configurations assigned to them:

| Scheme     | Configurations for the scheme                         |
| ---------- | ----------------------------------------------------- |
| staging    | Debug-staging Profile-staging Release-staging         |
| production | Debug-production Profile-production Releae-production |

## Configure Xcode schemes

[#](#configure-xcode-schemes)

The following steps show how to configure two Xcode schemes called `staging` and `production` for your Flutter iOS project. You can also use these steps to set up a macOS project by replacing any reference to `iOS` with `macOS`.

For a seamless workflow, we've started with a new Flutter project called `flavors_example`, but you can always start with an existing project.

1. Create a new Flutter project called `flavors_example`:

   console

   ```
   $ flutter create flavors_example
   ```

2. Open the default Xcode workspace for the iOS version of the `flavors_example` project:

   console

   ```
   $ cd flavors_example && open ios/Runner.xcworkspace
   ```

3. Open the `flavors_example` project in the Xcode project navigator:

   * Open the **project navigator** (**View** > **Navigators** > **Project**).

   * In the **project navigator**, at the top, select **Runner**.

4. Create schemes in Xcode:

   * Open the **New Scheme** window (**Product > Scheme > New Scheme**).

   * In the **Target** field, select **Runner**.

   * In the **Name** box, enter `staging`.

   * Click **Okay** to add the new scheme.

   * Repeat the previous steps for a scheme called `production`.

   * When finished, check to make sure that you have the following schemes:

5. Create configurations for the schemes in Xcode:

   * In the **project navigator**, select **Runner**.

   * In the main window under **PROJECT**, select **Runner**.

   * Open the **Info tab** if it isn’t open.

   * Go to the **Configurations** section and add new `Debug` configurations. Always use lowercase for the appended flavor name (such as `staging` or `production`) so the Flutter CLI recognizes it:

     * Click **+**, select **Duplicate "Debug" configuration**, and name the new configuration `Debug-staging`.
     * Click **+**, select **Duplicate "Debug" configuration**, and name the new configuration `Debug-production`.

   * Repeat the previous step for the `Release` configurations and the `Profile` configurations.

   * When finished, check to make sure that you have the following configurations:

     Your configurations should be based on `Debug.xcconfig` and `Release.xcconfig` (not `Pods-Runner.xcconfig`). By default, both Profile and Release configurations use `Release.xcconfig`. You can check this by expanding the configuration names in Xcode.

6. Assign the configurations to the schemes in Xcode:

   * Open the **Manage Schemes** window (**Product > Scheme > Manage Schemes**).

   * Select the `staging` scheme and edit it.

   * In the following tabs, update the **Build Configuration** field as follows:

     * **Run**: `Debug-staging`
     * **Test**: `Debug-staging`
     * **Profile**: `Profile-staging`
     * **Analyze**: `Debug-staging`
     * **Archive**: `Release-staging`

   * Click **Close**.

   * Repeat the previous steps for the `production` scheme.

7. If you are working with a pre-existing Flutter project that has at least one Podfile, update it. For details, refer to [Update Podfiles](#update-podfiles).

8. To make sure that you've set up everything correctly, run your app on the new schemes in Xcode. The app won't have any visual differences yet because the configuration settings haven't changed, but you should verify that it runs without errors.

   * Select the `staging` scheme (**Product > Schemes > staging**).

   * To the right of `staging` in the toolbar, select the iOS device you want to test against. In the following example, the device is `iPhone 16 Pro`.

   * Run the app scheme (**Product > Run**).

   * Repeat the previous steps for the `production` scheme.

9. If everything runs, you're ready to customize your configurations. For details, refer to [Customize configurations](#customize-configurations).

## Launch an Xcode scheme

[#](#launch-an-xcode-scheme)

After you've created the schemes for an iOS app in Xcode, you can launch a specific scheme through Xcode or Flutter. You can also use these steps to launch a macOS project by replacing any reference to `iOS` with `macOS`.

### Use the flavor flag (Flutter CLI)

[#](#use-the-flavor-flag-flutter-cli)

You can launch an Xcode scheme in `Debug` mode with the Flutter CLI using the following steps:

1. In your IDE, start the iOS simulator.

2. In the console, navigate to the `flavors_example` directory and enter the following command:

   console

   ```
   $ flutter run --flavor <xcode_scheme_name>
   ```

   * `<xcode_scheme_name>`: Replace this with the name of your Xcode scheme (for example, `staging` or `production`).

   Example:

   console

   ```
   $ flutter run --flavor staging
   ```

### Access the current flavor

[#](#access-the-current-flavor)

1. **Import the services library.** To access the `appFlavor` constant, add the following import to your Dart file:

   dart

   ```
   import 'package:flutter/services.dart';
   ```

2. **Check the flavor value.** Use the `appFlavor` constant in your application logic (often in `main()`) to handle flavor-specific configurations:

   dart

   ```
   void main() {
     // appFlavor will match the name of the Xcode scheme
     if (appFlavor == 'production') {
       // Logic for production environment
       Config.apiUrl = 'https://api.flavors_example.com';
     } else if (appFlavor == 'staging') {
       // Logic for staging environment
       Config.apiUrl = 'https://staging.api.flavors_example.com';
     }
   ​
     runApp(const MyApp());
   }
   ```

### Use the run command (Xcode)

[#](#use-the-run-command-xcode)

You can launch a specific scheme in Xcode using the following steps:

1. Select the scheme you want to test (**Product > Schemes > Choose scheme**).

2. Next to the scheme name in the toolbar, select the device you want to test against.

3. Run the scheme for your app (**Product > Run**).

## Customize configurations

[#](#customize-configurations)

After you've added Xcode schemes, you can customize them for your iOS app. You can also use these steps to configure a macOS project by replacing any reference to `iOS` with `macOS`.

### Create distinct app display names

[#](#create_a_distinct_app_display_name)

If you have multiple schemes, a distinct app name can quickly identify which scheme your deployed app is using.

The following steps show how to add distinct app display names in Xcode for two schemes called `staging` and `production` in a project called `flavors_example`.

1. Create user-defined settings in Xcode:

   * Open the **project navigator** (**View > Navigators > Project**).

   * In the **project navigator**, at the top, select **Runner**.

   * In the main window under **TARGETS**, select **Runner**.

   * Open the **Build Settings** tab.

   * To the left of the Basic tab, click **+** and select **Add User-Defined Setting**.

   * Create a setting named `APP_DISPLAY_NAME`.

   * Expand the **APP\_DISPLAY\_NAME** setting.

   * Assign the following values to the following keys:

     * **Debug-production**: `Flavors prod`
     * **Debug-staging**: `Flavors staging`
     * **Profile-production**: `Flavors prod`
     * **Profile-staging**: `Flavors staging`
     * **Release-production**: `Flavors prod`
     * **Release-staging**: `Flavors staging`

2. Update `Info.plist` in Xcode:

   * In the project navigator, select **Runner > Runner > Info** to open `flavor_test/ios/Runner/Info.plist`.

   * Under **Information Property List**, find the following key and update the value for it:

     * **Key**: `CFBundleDisplayName`
     * **Value**: `$(APP_DISPLAY_NAME)`

3. Launch the app for each scheme (`staging`, `production`) and check to make sure that the app display name has changed for each. To learn how to launch a scheme, refer to [Launch an Xcode scheme](#launch-an-xcode-scheme).

### Create distinct icons

[#](#create-distinct-icons)

If you have multiple schemes, a distinct icon for each configuration can help you quickly identify which scheme your deployed app is using.

The following steps show how to add a distinct icon in Xcode for two schemes called `staging` and `production` in an iOS project called `flavors_example`.

1. Prepare your icons:

   * Design your staging icon and production icon in the design tool of your choice.

   * Generate versions of the staging icon and production icon in the sizes that you need. Save them in PNG format.

2. Add the icons to your Xcode project:

   * Open the **project navigator** (**View > Navigators > Project**).

   * In the **project navigator**, select **Runner > Runner > Assets** to open the **Assets** window.

   * Complete the following steps for the staging icon:

     * Click **+ > iOS > iOS App icon**.

     * Name the icon `AppIcon-staging`.

     * Drag your staging icons into the **AppIcon-staging** window and make sure the icons are assigned to the correct sizes.

   * Repeat the previous step for the production icon.

3. Connect the icons to your schemes:

   * Open the **project navigator**.

   * In the main window under **TARGETS**, select **Runner**.

   * Open the **General** tab if it's not already open.

   * Go to the **Apps Icons and Launch Screen** section and expand it.

   * To the right of the **App icon** field, click **+** and update the fields as follows:

     * **Debug-staging**: `AppIcon-staging`
     * **Profile-staging**: `AppIcon-staging`
     * **Release-staging**: `AppIcon-staging`
     * **Debug-production**: `AppIcon-production`
     * **Profile-production**: `AppIcon-production`
     * **Release-production**: `AppIcon-production`

4. Launch the app for each scheme (`staging`, `production`) and check to make sure that the app icon has changed for each. To learn how to launch a scheme, refer to [Launch an Xcode scheme](#launch-an-xcode-scheme).

### Add distinct bundle identifiers

[#](#add-distinct-bundle-identifiers)

A bundle identifier is a unique identifier for your application on Apple's platforms. If you are using multiple Xcode schemes as Flutter flavors, you can have Apple treat each scheme as a separate application. To do this, you need to assign a different bundle identifier to each scheme. This allows you to test new features or bug fixes in one version of the app (for example `staging`) without affecting another version of the app (for example, `production`).

The following steps show how to set a unique bundle identifier for two Xcode schemes called `staging` and `production` in an iOS project called `flavors_example`.

1. In Xcode, open the **project navigator** (**View > Navigators > Project**).

2. In the main window under **TARGETS**, select **Runner**.

3. Open the **Build Settings** tab.

4. Navigate to the **Packaging** section.

5. Expand the **Product Bundle Identifier** setting to view the different build configurations.

6. For each scheme's build configuration, set the desired bundle identifier. For example:

   * Debug-staging, Profile-staging, Release-staging:

     `com.example.flavorsExample.staging`

   * Debug, Profile, Release, Debug-production, Profile-production, Release-production:

     `com.example.flavorsExample`

7. Ensure that these bundle identifiers are included in your App ID and your App ID is [registered in your Apple Developer account](https://developer.apple.com/help/account/identifiers/register-an-app-id/).

### Bundle assets

[#](#bundle-assets)

If you have assets that are only used in a specific flavor in your app, you can configure them to only be bundled into your app when launching that flavor. This prevents your app bundle size from being bloated by unused assets. To bundle assets for each flavor, add the `flavors` subfield to the `assets` field in your project's pubspec. For details, refer to the [`assets` field](/tools/pubspec#assets) in [Flutter pubspec options](/tools/pubspec).

### Update Podfiles

[#](#update-podfiles)

If you are creating new Xcode schemes for a Flutter iOS project and you have an iOS Podfile in an existing Flutter project, you must update the Flutter iOS Podfile to match the changes you made in Xcode.

The following steps show how to update your iOS Podfile to include two new Xcode schemes called `staging` and `production` in a Flutter project called `flavors_example`. You can also use these steps to update a macOS project by replacing any reference to `iOS` with `macOS`.

1. In your IDE, open the `ios/Podfile` file.

2. Make the following updates and save your changes:

   flavors\_example/ios/Podfile

   ruby

   ```
   project 'Runner', {
     ...
     'Debug' => :debug,
     'Debug-staging' => :debug,
     'Debug-production' => :debug,
     'Profile' => :release,
     'Profile-staging' => :release,
     'Profile-production' => :release,
     'Release' => :release,
     'Release-staging' => :release,
     'Release-production' => :release,
     ...
   ```

### Add unique build settings

[#](#add-unique-build-settings)

You can use [build settings](https://developer.apple.com/documentation/xcode/build-settings-reference/) to govern your iOS build process from compilation and linking to debugging and distribution. One way that you can use build settings with Flutter flavors is to assign those build settings to Xcode build configurations. For example, you might want to assign different API URLs to `Debug-staging` and `Debug-production`. The following configurations show example build settings:

debug-staging-settings.xcconfig

```
# Debug-staging build settings
API_BASE_URL = staging.flavors.com/api
```

debug-production-settings.xcconfig

```
# Debug-production build settings
API_BASE_URL = flavors.com/api
```

To learn how to add additional build settings for a specific build configuration, consult Apple's [Adding a build configuration file to your project](https://developer.apple.com/documentation/xcode/adding-a-build-configuration-file-to-your-project).

### Add additional customizations

[#](#add-additional-customizations)

This document contains a few common Xcode scheme configurations, but there are many more that you can apply. To learn about them, consult [Customizing the build schemes for a project](https://developer.apple.com/documentation/xcode/customizing-the-build-schemes-for-a-project).

## More information

[#](#more-information)

To learn about creating and using flavors, consult the following resources:

* [Set up Flutter flavors for Android](/deployment/flavors)
* [Set up Flutter flavors for Linux](/deployment/flavors-linux)
* [Set up Flutter flavors for Windows](/deployment/flavors-windows)
* [How to set up Flutter & Firebase with Multiple Flavors using the FlutterFire CLI](https://codewithandrea.com/articles/flutter-firebase-multiple-flavors-flutterfire-cli/)
* [Build flavors in Flutter (Android and iOS) with different Firebase projects per flavor Flutter Ready to Go](https://medium.com/@animeshjain/build-flavors-in-flutter-android-and-ios-with-different-firebase-projects-per-flavor-27c5c5dac10b)

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-09-29. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/deployment/flavors-ios.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/deployment/flavors-ios\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/deployment/flavors-ios.md "Report an issue with this page").

----
url: https://docs.flutter.dev/tools/sdk
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Flutter SDK overview](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

The Flutter SDK has the packages and command-line tools that you need to develop Flutter apps across platforms. To get the Flutter SDK, see [Install](/install).

## What's in the Flutter SDK

[#](#whats-in-the-flutter-sdk)

The following is available through the Flutter SDK:

* [Dart SDK](https://dart.dev/tools/sdk)
* Heavily optimized, mobile-first 2D rendering engine with excellent support for text
* Modern react-style framework
* Rich set of widgets implementing Material Design and iOS styles
* APIs for unit and integration tests
* Interop and plugin APIs to connect to the system and 3rd-party SDKs
* Headless test runner for running tests on Windows, Linux, and Mac
* [Flutter DevTools](/tools/devtools) for testing, debugging, and profiling your app
* `flutter` and `dart` command-line tools for creating, building, testing, and compiling your apps

Note: For more information about the Flutter SDK, see its [README file](https://github.com/flutter/flutter/blob/main/README.md).

## `flutter` command-line tool

[#](#flutter-command-line-tool)

The [`flutter` CLI tool](/reference/flutter-cli) (`flutter/bin/flutter`) is how developers (or IDEs on behalf of developers) interact with Flutter.

## `dart` command-line tool

[#](#dart-command-line-tool)

The [`dart` CLI tool](https://dart.dev/tools/dart-tool) is available with the Flutter SDK at `flutter/bin/dart`.

## SDK support for Flutter developer tools

[#](#sdk-support-for-flutter-developer-tools)

The IDE tooling for Flutter (Android Studio and Intellij plugins, VS Code extensions) supports Flutter SDK versions going back two years. This means that while the tools might still function with SDKs older than two years, they will no longer provide fixes for issues specific to these older versions.

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/sdk.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/tools/sdk\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/sdk.md "Report an issue with this page").

----
url: https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.8.0
----

The 2.8.0 release of the Dart and Flutter DevTools includes the following changes among other general improvements. To learn more about DevTools, check out the [DevTools overview](https://docs.flutter.dev/tools/devtools).

## General updates

[#](#general-updates)

* Improvements for initial page load time - [#3325](https://github.com/flutter/devtools/pull/3325)

* Performance improvements for connecting DevTools to a device, particularly impactful for low-memory devices - [#3468](https://github.com/flutter/devtools/pull/3468)

* For users on Flutter 2.8.0 or greater (or Dart 2.15.0 or greater), DevTools should now be launched via the `dart devtools` command instead of running `pub global activate devtools`. DevTools 2.8.0 will be the last version of DevTools shipped on pub, and all future versions of DevTools will be shipped as part of the Dart SDK. If you see this warning, be sure to open DevTools via `dart devtools` instead of from pub:

## Performance updates

[#](#performance-updates)

* Added a new "Enhance Tracing" feature to help users diagnose UI jank stemming from expensive Build, Layout, and Paint operations.

  The expected workflow is as such:

  1. User is investigating UI jank in the performance page
  2. User notices a long Build, Layout, and/or Paint event
  3. User turns on the respective tracking toggle in the "Enhance Tracing" feature
  4. User reproduces the UI jank in their app
  5. User looks at the new set of Timeline events, which should now have additional child events for widgets built, render objects laid out, and/or render objects painted

* Added new "More debugging options" feature to allow for disabling rendering layers for Clip, Opacity, and Physical Shapes.

  The expected workflow is as such:

  1. User is investigating UI jank in the performance page
  2. User notices a lot of janky frames and suspects it could be due to excessive use of clipping, opacity, or physical shapes.
  3. User turns off the respective render layer toggle in the "More debugging options" feature
  4. User reproduces the UI jank in their app
  5. If the UI jank is reduced with a rendering layer turned off, the user should try to optimize their app to use less clipping/opacity/physical shape effects. If the UI jank is not reduced, the user now knows that the performance problem is not due to these UI effects.

## Debugger updates

[#](#debugger-updates)

* Replaced the "Libraries" pane with a "File Explorer" pane - [#3448](https://github.com/flutter/devtools/pull/3448). The "File Explorer" pane has two components:

  1. A tree view of the libraries present in your application. You can use the File Explorer to find and open a library, or you can use the existing `Ctrl` / `Cmd` + `P` keyboard shortcut to search for a file.
  2. A new "Outline" view that shows the structure of the selected library. This view will show classes, members, methods, etc., and when an item is selected, the source view will jump to the respective line of code for the selected item.

* Performance improvements to expression evaluation auto complete - [#3463](https://github.com/flutter/devtools/pull/3463)

* Fixed a bug with keyboard shortcuts - [#3458](https://github.com/flutter/devtools/pull/3458)

* UI polish - [#3421](https://github.com/flutter/devtools/pull/3421), [#3449](https://github.com/flutter/devtools/pull/3449)

## Full commit history

[#](#full-commit-history)

To find a complete list of changes since the previous release, check out [the diff on GitHub](https://github.com/flutter/devtools/compare/v2.7.0...v2.8.0).

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/release-notes/release-notes-2.8.0.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.8.0\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/release-notes/release-notes-2.8.0.md "Report an issue with this page").

----
url: https://docs.flutter.dev/flutter-for/xamarin-forms-devs.md
----

\# Flutter for Xamarin.Forms developers > Learn how to apply Xamarin.Forms developer knowledge when building Flutter apps.

<!--?code-excerpt path-base="get-started/flutter-for/xamarin_devs"?-->

This document is meant for Xamarin.Forms developers looking to apply their existing knowledge to build mobile apps with Flutter. If you understand the fundamentals of the Xamarin.Forms framework, then you can use this document as a jump start to Flutter development. Your Android and iOS knowledge and skill set are valuable when building with Flutter, because Flutter relies on the native operating system configurations, similar to how you would configure your native Xamarin.Forms projects. The Flutter Frameworks is also similar to how you create a single UI, that is used on multiple platforms. This document can be used as a cookbook by jumping around and finding questions that are most relevant to your needs. ## Project setup ### How does the app start? For each platform in Xamarin.Forms, you call the \`LoadApplication\` method, which creates a new application and starts your app. \`\`\`csharp LoadApplication(new App()); \`\`\` In Flutter, the default main entry point is \`main\` where you load your Flutter app.

<!--?code-excerpt "lib/main.dart (main)"?-->

\`\`\`dart void main() { runApp(const MyApp()); } \`\`\` In Xamarin.Forms, you assign a \`Page\` to the \`MainPage\` property in the \`Application\` class. \`\`\`csharp public class App : Application { public App() { MainPage = new ContentPage { Content = new Label { Text = "Hello World", HorizontalOptions = LayoutOptions.Center, VerticalOptions = LayoutOptions.Center } }; } } \`\`\` In Flutter, "everything is a widget", even the application itself. The following example shows \`MyApp\`, a simple application \`Widget\`.

<!--?code-excerpt "lib/main.dart (my-app)"?-->

\`\`\`dart class MyApp extends StatelessWidget { /// This widget is the root of your application. const MyApp({super.key}); @override Widget build(BuildContext context) { return const Center( child: Text('Hello World!', textDirection: TextDirection.ltr), ); } } \`\`\` ### How do you create a page? Xamarin.Forms has many types of pages; \`ContentPage\` is the most common. In Flutter, you specify an application widget that holds your root page. You can use a \[\`MaterialApp\`]\[] widget, which supports \[Material Design]\[], or you can use a \[\`CupertinoApp\`]\[] widget, which supports an iOS-style app, or you can use the lower level \[\`WidgetsApp\`]\[], which you can customize in any way you want. \[\`CupertinoApp\`]: https\://pub.dev/documentation/cupertino\_ui/latest/cupertino\_ui/CupertinoApp-class.html \[\`MaterialApp\`]: https\://pub.dev/documentation/material\_ui/latest/material\_ui/MaterialApp-class.html \[\`WidgetsApp\`]: https\://api.flutter.dev/flutter/widgets/WidgetsApp-class.html The following code defines the home page, a stateful widget. In Flutter, all widgets are immutable, but two types of widgets are supported: \_Stateful\_ and \_Stateless\_. Examples of a stateless widget are titles, icons, or images. The following example uses \`MaterialApp\`, which holds its root page in the \`home\` property.

<!--?code-excerpt "lib/page.dart (my-app)"?-->

\`\`\`dart class MyApp extends StatelessWidget { /// This widget is the root of your application. const MyApp({super.key}); @override Widget build(BuildContext context) { return const MaterialApp( title: 'Flutter Demo', home: MyHomePage(title: 'Flutter Demo Home Page'), ); } } \`\`\` From here, your actual first page is another \`Widget\`, in which you create your state. A \_Stateful\_ widget, such as \`MyHomePage\` below, consists of two parts. The first part, which is itself immutable, creates a \`State\` object that holds the state of the object. The \`State\` object persists over the life of the widget.

<!--?code-excerpt "lib/page.dart (my-home-page)"?-->

\`\`\`dart class MyHomePage extends StatefulWidget { const MyHomePage({super.key, required this.title}); final String title; @override State createState() => \_MyHomePageState(); } \`\`\` The \`State\` object implements the \`build()\` method for the stateful widget. When the state of the widget tree changes, call \`setState()\`, which triggers a build of that portion of the UI. Make sure to call \`setState()\` only when necessary, and only on the part of the widget tree that has changed, or it can result in poor UI performance.

<!--?code-excerpt "lib/page.dart (my-home-page-state)"?-->

\`\`\`dart class \_MyHomePageState extends State { int \_counter = 0; void \_incrementCounter() { setState(() { \_counter++; }); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( // Take the value from the MyHomePage object that was created by // the App.build method, and use it to set the appbar title. title: Text(widget.title), ), body: Center( // Center is a layout widget. It takes a single child and positions it // in the middle of the parent. child: Column( mainAxisAlignment: MainAxisAlignment.center, children: \[ const Text('You have pushed the button this many times:'), Text( '$\_counter', style: Theme.of(context).textTheme.headlineMedium, ), ], ), ), floatingActionButton: FloatingActionButton( onPressed: \_incrementCounter, tooltip: 'Increment', child: const Icon(Icons.add), ), ); } } \`\`\` In Flutter, the UI (also known as widget tree), is immutable, meaning you can't change its state once it's built. You change fields in your \`State\` class, then call \`setState()\` to rebuild the entire widget tree again. This way of generating UI is different from Xamarin.Forms, but there are many benefits to this approach. ## Views ### What is the equivalent of a Page or Element in Flutter? :::secondary How is react-style, or \_declarative\_, programming different from the traditional imperative style? For a comparison, see \[Introduction to declarative UI]\[]. ::: \`ContentPage\`, \`TabbedPage\`, \`FlyoutPage\` are all types of pages you might use in a Xamarin.Forms application. These pages would then hold \`Element\`s to display the various controls. In Xamarin.Forms an \`Entry\` or \`Button\` are examples of an \`Element\`. In Flutter, almost everything is a widget. A \`Page\`, called a \`Route\` in Flutter, is a widget. Buttons, progress bars, and animation controllers are all widgets. When building a route, you create a widget tree. Flutter includes the \[Material Components]\[] library. These are widgets that implement the \[Material Design guidelines]\[]. Material Design is a flexible design system \[optimized for all platforms]\[], including iOS. But Flutter is flexible and expressive enough to implement any design language. For example, on iOS, you can use the \[Cupertino widgets]\[] to produce an interface that looks like \[Apple's iOS design language]\[]. ### How do I update widgets? In Xamarin.Forms, each \`Page\` or \`Element\` is a stateful class, that has properties and methods. You update your \`Element\` by updating a property, and this is propagated down to the native control. In Flutter, \`Widget\`s are immutable and you can't directly update them by changing a property, instead you have to work with the widget's state. This is where the concept of Stateful vs Stateless widgets comes from. A \`StatelessWidget\` is just what it sounds like— a widget with no state information. \`StatelessWidgets\` are useful when the part of the user interface you are describing doesn't depend on anything other than the configuration information in the object. For example, in Xamarin.Forms, this is similar to placing an \`Image\` with your logo. The logo is not going to change during runtime, so use a \`StatelessWidget\` in Flutter. If you want to dynamically change the UI based on data received after making an HTTP call or a user interaction, then you have to work with \`StatefulWidget\` and tell the Flutter framework that the widget's \`State\` has been updated, so it can update that widget. The important thing to note here is at the core both stateless and stateful widgets behave the same. They rebuild every frame, the difference is the \`StatefulWidget\` has a \`State\` object that stores state data across frames and restores it. If you are in doubt, then always remember this rule: if a widget changes (because of user interactions, for example) it's stateful. However, if a widget reacts to change, the containing parent widget can still be stateless if it doesn't itself react to change. The following example shows how to use a \`StatelessWidget\`. A common \`StatelessWidget\` is the \`Text\` widget. If you look at the implementation of the \`Text\` widget you'll find it subclasses \`StatelessWidget\`.

<!--?code-excerpt "lib/views.dart (text)" replace="/return //g"?-->

\`\`\`dart const Text( 'I like Flutter!', style: TextStyle(fontWeight: FontWeight.bold), ); \`\`\` As you can see, the \`Text\` widget has no state information associated with it, it renders what is passed in its constructors and nothing more. But, what if you want to make "I Like Flutter" change dynamically, for example, when clicking a \`FloatingActionButton\`? To achieve this, wrap the \`Text\` widget in a \`StatefulWidget\` and update it when the user clicks the button, as shown in the following example:

<!--?code-excerpt "lib/views_stateful.dart"?-->

\`\`\`dart import 'package:material\_ui/material\_ui.dart'; void main() { runApp(const SampleApp()); } class SampleApp extends StatelessWidget { /// This widget is the root of your application. const SampleApp({super.key}); @override Widget build(BuildContext context) { return const MaterialApp(title: 'Sample App', home: SampleAppPage()); } } class SampleAppPage extends StatefulWidget { const SampleAppPage({super.key}); @override State createState() => \_SampleAppPageState(); } class \_SampleAppPageState extends State { /// Default placeholder text String textToShow = 'I Like Flutter'; void \_updateText() { setState(() { // Update the text textToShow = 'Flutter is Awesome!'; }); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('Sample App')), body: Center(child: Text(textToShow)), floatingActionButton: FloatingActionButton( onPressed: \_updateText, tooltip: 'Update Text', child: const Icon(Icons.update), ), ); } } \`\`\` ### How do I lay out my widgets? What is the equivalent of an XAML file? In Xamarin.Forms, most developers write layouts in XAML, though sometimes in C#. In Flutter, you write your layouts with a widget tree in code. The following example shows how to display a simple widget with padding:

<!--?code-excerpt "lib/padding.dart (padding)"?-->

\`\`\`dart @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('Sample App')), body: Center( child: ElevatedButton( style: ElevatedButton.styleFrom( padding: const EdgeInsets.only(left: 20, right: 30), ), onPressed: () {}, child: const Text('Hello'), ), ), ); } \`\`\` You can view the layouts that Flutter has to offer in the \[widget catalog]\[]. ### How do I add or remove an Element from my layout? In Xamarin.Forms, you had to remove or add an \`Element\` in code. This involved either setting the \`Content\` property or calling \`Add()\` or \`Remove()\` if it was a list. In Flutter, because widgets are immutable there is no direct equivalent. Instead, you can pass a function to the parent that returns a widget, and control that child's creation with a boolean flag. The following example shows how to toggle between two widgets when the user clicks the \`FloatingActionButton\`:

<!--?code-excerpt "lib/views.dart (add-remove-element)"?-->

\`\`\`dart class SampleApp extends StatelessWidget { /// This widget is the root of your application. const SampleApp({super.key}); @override Widget build(BuildContext context) { return const MaterialApp(title: 'Sample App', home: SampleAppPage()); } } class SampleAppPage extends StatefulWidget { const SampleAppPage({super.key}); @override State createState() => \_SampleAppPageState(); } class \_SampleAppPageState extends State { /// Default value for toggle bool toggle = true; void \_toggle() { setState(() { toggle = !toggle; }); } Widget \_getToggleChild() { if (toggle) { return const Text('Toggle One'); } return CupertinoButton(onPressed: () {}, child: const Text('Toggle Two')); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('Sample App')), body: Center(child: \_getToggleChild()), floatingActionButton: FloatingActionButton( onPressed: \_toggle, tooltip: 'Update Text', child: const Icon(Icons.update), ), ); } } \`\`\` ### How do I animate a widget? In Xamarin.Forms, you create simple animations using ViewExtensions that include methods such as \`FadeTo\` and \`TranslateTo\`. You would use these methods on a view to perform the required animations. \`\`\`xml \`\`\` Then in code behind, or a behavior, this would fade in the image, over a 1-second period. \`\`\`csharp myImage.FadeTo(0, 1000); \`\`\` In Flutter, you animate widgets using the animation library by wrapping widgets inside an animated widget. Use an \`AnimationController\`, which is an \`Animation\` that can pause, seek, stop and reverse the animation. It requires a \`Ticker\` that signals when vsync happens, and produces a linear interpolation between 0 and 1 on each frame while it's running. You then create one or more\`Animation\`s and attach them to the controller. For example, you might use \`CurvedAnimation\` to implement an animation along an interpolated curve. In this sense, the controller is the "master" source of the animation progress and the \`CurvedAnimation\` computes the curve that replaces the controller's default linear motion. Like widgets, animations in Flutter work with composition. When building the widget tree, you assign the \`Animation\` to an animated property of a widget, such as the opacity of a \`FadeTransition\`, and tell the controller to start the animation. The following example shows how to write a \`FadeTransition\` that fades the widget into a logo when you press the \`FloatingActionButton\`:

<!--?code-excerpt "lib/animation.dart"?-->

\`\`\`dart import 'package:material\_ui/material\_ui.dart'; void main() { runApp(const FadeAppTest()); } class FadeAppTest extends StatelessWidget { /// This widget is the root of your application. const FadeAppTest({super.key}); @override Widget build(BuildContext context) { return const MaterialApp( title: 'Fade Demo', home: MyFadeTest(title: 'Fade Demo'), ); } } class MyFadeTest extends StatefulWidget { const MyFadeTest({super.key, required this.title}); final String title; @override State createState() => \_MyFadeTest(); } class \_MyFadeTest extends State with TickerProviderStateMixin { late final AnimationController controller; late final CurvedAnimation curve; @override void initState() { super.initState(); controller = AnimationController( duration: const Duration(milliseconds: 2000), vsync: this, ); curve = CurvedAnimation(parent: controller, curve: Curves.easeIn); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text(widget.title)), body: Center( child: FadeTransition( opacity: curve, child: const FlutterLogo(size: 100), ), ), floatingActionButton: FloatingActionButton( onPressed: () { controller.forward(); }, tooltip: 'Fade', child: const Icon(Icons.brush), ), ); } } \`\`\` For more information, see \[Animation & Motion widgets]\[], the \[Animations tutorial]\[], and the \[Animations overview]\[]. ### How do I draw/paint on the screen? Xamarin.Forms never had a built-in way to draw directly on the screen. Many would use SkiaSharp, if they needed a custom image drawn. In Flutter, you have direct access to the Skia Canvas and can easily draw on screen. Flutter has two classes that help you draw to the canvas: \`CustomPaint\` and \`CustomPainter\`, the latter of which implements your algorithm to draw to the canvas. To learn how to implement a signature painter in Flutter, see Collin's answer on \[Custom Paint]\[]. \[Custom Paint]: https\://stackoverflow\.com/questions/46241071/create-signature-area-for-mobile-app-in-dart-flutter

<!--?code-excerpt "lib/draw.dart"?-->

\`\`\`dart import 'package:material\_ui/material\_ui.dart'; void main() { runApp(const MaterialApp(home: DemoApp())); } class DemoApp extends StatelessWidget { const DemoApp({super.key}); @override Widget build(BuildContext context) => const Scaffold(body: Signature()); } class Signature extends StatefulWidget { const Signature({super.key}); @override SignatureState createState() => SignatureState(); } class SignatureState extends State { List \_points = \[]; void \_onPanUpdate(DragUpdateDetails details) { setState(() { final RenderBox referenceBox = context.findRenderObject() as RenderBox; final Offset localPosition = referenceBox.globalToLocal( details.globalPosition, ); \_points = List.from(\_points)..add(localPosition); }); } @override Widget build(BuildContext context) { return GestureDetector( onPanUpdate: \_onPanUpdate, onPanEnd: (details) => \_points.add(null), child: CustomPaint( painter: SignaturePainter(\_points), size: Size.infinite, ), ); } } class SignaturePainter extends CustomPainter { const SignaturePainter(this.points); final List points; @override void paint(Canvas canvas, Size size) { final Paint paint = Paint() ..color = Colors.black ..strokeCap = StrokeCap.round ..strokeWidth = 5; for (int i = 0; i < points.length - 1; i++) { if (points\[i] != null && points\[i + 1] != null) { canvas.drawLine(points\[i]!, points\[i + 1]!, paint); } } } @override bool shouldRepaint(SignaturePainter oldDelegate) => oldDelegate.points != points; } \`\`\` ### Where is the widget's opacity? On Xamarin.Forms, all \`VisualElement\`s have an Opacity. In Flutter, you need to wrap a widget in an \[\`Opacity\` widget]\[] to accomplish this. ### How do I build custom widgets? In Xamarin.Forms, you typically subclass \`VisualElement\`, or use a pre-existing \`VisualElement\`, to override and implement methods that achieve the desired behavior. In Flutter, build a custom widget by \[composing]\[] smaller widgets (instead of extending them). It is somewhat similar to implementing a custom control based off a \`Grid\` with numerous \`VisualElement\`s added in, while extending with custom logic. For example, how do you build a \`CustomButton\` that takes a label in the constructor? Create a CustomButton that composes a \`ElevatedButton\` with a label, rather than by extending \`ElevatedButton\`:

<!--?code-excerpt "lib/custom_button.dart (custom-button)"?-->

\`\`\`dart class CustomButton extends StatelessWidget { const CustomButton(this.label, {super.key}); final String label; @override Widget build(BuildContext context) { return ElevatedButton(onPressed: () {}, child: Text(label)); } } \`\`\` Then use \`CustomButton\`, just as you'd use any other Flutter widget:

<!--?code-excerpt "lib/custom_button.dart (use-custom-button)"?-->

\`\`\`dart @override Widget build(BuildContext context) { return const Center(child: CustomButton('Hello')); } \`\`\` ## Navigation ### How do I navigate between pages? In Xamarin.Forms, the \`NavigationPage\` class provides a hierarchical navigation experience where the user is able to navigate through pages, forwards and backwards. Flutter has a similar implementation, using a \`Navigator\` and \`Routes\`. A \`Route\` is an abstraction for a \`Page\` of an app, and a \`Navigator\` is a \[widget]\[] that manages routes. A route roughly maps to a \`Page\`. The navigator works in a similar way to the Xamarin.Forms \`NavigationPage\`, in that it can \`push()\` and \`pop()\` routes depending on whether you want to navigate to, or back from, a view. To navigate between pages, you have a couple options: \* Specify a \`Map\` of route names. (\`MaterialApp\`) \* Directly navigate to a route. (\`WidgetsApp\`) The following example builds a \`Map\`.

<!--?code-excerpt "lib/navigation.dart (main)"?-->

\`\`\`dart void main() { runApp( MaterialApp( home: const MyAppHome(), // becomes the route named '/' routes: { '/a': (context) => const MyPage(title: 'page A'), '/b': (context) => const MyPage(title: 'page B'), '/c': (context) => const MyPage(title: 'page C'), }, ), ); } \`\`\` Navigate to a route by pushing its name to the \`Navigator\`.

<!--?code-excerpt "lib/navigation.dart (push-named)"?-->

\`\`\`dart Navigator.of(context).pushNamed('/b'); \`\`\` The \`Navigator\` is a stack that manages your app's routes. Pushing a route to the stack moves to that route. Popping a route from the stack, returns to the previous route. This is done by awaiting on the \`Future\` returned by \`push()\`. \`async\`/\`await\` is very similar to the .NET implementation and is explained in more detail in \[Async UI]\[]. For example, to start a \`location\` route that lets the user select their location, you might do the following:

<!--?code-excerpt "lib/navigation.dart (await)"?-->

\`\`\`dart Object? coordinates = await Navigator.of(context).pushNamed('/location'); \`\`\` And then, inside your 'location' route, once the user has selected their location, pop the stack with the result:

<!--?code-excerpt "lib/navigation.dart (pop-location)"?-->

\`\`\`dart Navigator.of(context).pop({'lat': 43.821757, 'long': -79.226392}); \`\`\` ### How do I navigate to another app? In Xamarin.Forms, to send the user to another application, you use a specific URI scheme, using \`Device.OpenUrl("mailto://")\`. To implement this functionality in Flutter, create a native platform integration, or use an \[existing plugin]\[], such as\[\`url\_launcher\`]\[], available with many other packages on \[pub.dev]\[]. ## Async UI ### What is the equivalent of Device.BeginOnMainThread() in Flutter? Dart has a single-threaded execution model, with support for \`Isolate\`s (a way to run Dart codes on another thread), an event loop, and asynchronous programming. Unless you spawn an \`Isolate\`, your Dart code runs in the main UI thread and is driven by an event loop. Dart's single-threaded model doesn't mean you need to run everything as a blocking operation that causes the UI to freeze. Much like Xamarin.Forms, you need to keep the UI thread free. You would use \`async\`/\`await\` to perform tasks, where you must wait for the response. In Flutter, use the asynchronous facilities that the Dart language provides, also named \`async\`/\`await\`, to perform asynchronous work. This is very similar to C# and should be very easy to use for any Xamarin.Forms developer. For example, you can run network code without causing the UI to hang by using \`async\`/\`await\` and letting Dart do the heavy lifting:

<!--?code-excerpt "lib/data.dart (load-data)"?-->

\`\`\`dart Future loadData() async { final Uri dataURL = Uri.parse('https\://jsonplaceholder.typicode.com/posts'); final http.Response response = await http.get(dataURL); setState(() { data = (jsonDecode(response.body) as List).cast>(); }); } \`\`\` Once the awaited network call is done, update the UI by calling \`setState()\`, which triggers a rebuild of the widget subtree and updates the data. The following example loads data asynchronously and displays it in a \`ListView\`:

<!--?code-excerpt "lib/data.dart"?-->

\`\`\`dart import 'dart:convert'; import 'package:http/http.dart' as http; import 'package:material\_ui/material\_ui.dart'; void main() { runApp(const SampleApp()); } class SampleApp extends StatelessWidget { const SampleApp({super.key}); @override Widget build(BuildContext context) { return const MaterialApp(title: 'Sample App', home: SampleAppPage()); } } class SampleAppPage extends StatefulWidget { const SampleAppPage({super.key}); @override State createState() => \_SampleAppPageState(); } class \_SampleAppPageState extends State { List> data = \[]; @override void initState() { super.initState(); loadData(); } Future loadData() async { final Uri dataURL = Uri.parse('https\://jsonplaceholder.typicode.com/posts'); final http.Response response = await http.get(dataURL); setState(() { data = (jsonDecode(response.body) as List).cast>(); }); } Widget getRow(int index) { return Padding( padding: const EdgeInsets.all(10), child: Text('Row ${data\[index]\['title']}'), ); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('Sample App')), body: ListView\.builder( itemCount: data.length, itemBuilder: (context, index) { return getRow(index); }, ), ); } } \`\`\` Refer to the next section for more information on doing work in the background, and how Flutter differs from Android. ### How do you move work to a background thread? Since Flutter is single threaded and runs an event loop, you don't have to worry about thread management or spawning background threads. This is very similar to Xamarin.Forms. If you're doing I/O-bound work, such as disk access or a network call, then you can safely use \`async\`/\`await\` and you're all set. If, on the other hand, you need to do computationally intensive work that keeps the CPU busy, you want to move it to an \`Isolate\` to avoid blocking the event loop, like you would keep \_any\_ sort of work out of the main thread. This is similar to when you move things to a different thread via \`Task.Run()\` in Xamarin.Forms. For I/O-bound work, declare the function as an \`async\` function, and \`await\` on long-running tasks inside the function:

<!--?code-excerpt "lib/data.dart (load-data)"?-->

\`\`\`dart Future loadData() async { final Uri dataURL = Uri.parse('https\://jsonplaceholder.typicode.com/posts'); final http.Response response = await http.get(dataURL); setState(() { data = (jsonDecode(response.body) as List).cast>(); }); } \`\`\` This is how you would typically do network or database calls, which are both I/O operations. However, there are times when you might be processing a large amount of data and your UI hangs. In Flutter, use \`Isolate\`s to take advantage of multiple CPU cores to do long-running or computationally intensive tasks. Isolates are separate execution threads that do not share any memory with the main execution memory heap. This is a difference between \`Task.Run()\`. This means you can't access variables from the main thread, or update your UI by calling \`setState()\`. The following example shows, in a simple isolate, how to share data back to the main thread to update the UI.

<!--?code-excerpt "lib/isolates.dart (simple-isolate)"?-->

\`\`\`dart Future loadData() async { final ReceivePort receivePort = ReceivePort(); await Isolate.spawn(dataLoader, receivePort.sendPort); // The 'echo' isolate sends its SendPort as the first message final SendPort sendPort = await receivePort.first as SendPort; final List> msg = await sendReceive( sendPort, 'https\://jsonplaceholder.typicode.com/posts', ); setState(() { data = msg; }); } // The entry point for the isolate static Future dataLoader(SendPort sendPort) async { // Open the ReceivePort for incoming messages. final ReceivePort port = ReceivePort(); // Notify any other isolates what port this isolate listens to. sendPort.send(port.sendPort); await for (final dynamic msg in port) { final String url = msg\[0] as String; final SendPort replyTo = msg\[1] as SendPort; final Uri dataURL = Uri.parse(url); final http.Response response = await http.get(dataURL); // Lots of JSON to parse replyTo.send(jsonDecode(response.body) as List>); } } Future>> sendReceive(SendPort port, String msg) { final ReceivePort response = ReceivePort(); port.send(\[msg, response.sendPort]); return response.first as Future>>; } \`\`\` Here, \`dataLoader()\` is the \`Isolate\` that runs in its own separate execution thread. In the isolate, you can perform more CPU intensive processing (parsing a big JSON, for example), or perform computationally intensive math, such as encryption or signal processing. You can run the full example below:

<!--?code-excerpt "lib/isolates.dart"?-->

\`\`\`dart import 'dart:async'; import 'dart:convert'; import 'dart:isolate'; import 'package:http/http.dart' as http; import 'package:material\_ui/material\_ui.dart'; void main() { runApp(const SampleApp()); } class SampleApp extends StatelessWidget { const SampleApp({super.key}); @override Widget build(BuildContext context) { return const MaterialApp(title: 'Sample App', home: SampleAppPage()); } } class SampleAppPage extends StatefulWidget { const SampleAppPage({super.key}); @override State createState() => \_SampleAppPageState(); } class \_SampleAppPageState extends State { List> data = \[]; @override void initState() { super.initState(); loadData(); } bool get showLoadingDialog => data.isEmpty; Future loadData() async { final ReceivePort receivePort = ReceivePort(); await Isolate.spawn(dataLoader, receivePort.sendPort); // The 'echo' isolate sends its SendPort as the first message final SendPort sendPort = await receivePort.first as SendPort; final List> msg = await sendReceive( sendPort, 'https\://jsonplaceholder.typicode.com/posts', ); setState(() { data = msg; }); } // The entry point for the isolate static Future dataLoader(SendPort sendPort) async { // Open the ReceivePort for incoming messages. final ReceivePort port = ReceivePort(); // Notify any other isolates what port this isolate listens to. sendPort.send(port.sendPort); await for (final dynamic msg in port) { final String url = msg\[0] as String; final SendPort replyTo = msg\[1] as SendPort; final Uri dataURL = Uri.parse(url); final http.Response response = await http.get(dataURL); // Lots of JSON to parse replyTo.send(jsonDecode(response.body) as List>); } } Future>> sendReceive(SendPort port, String msg) { final ReceivePort response = ReceivePort(); port.send(\[msg, response.sendPort]); return response.first as Future>>; } Widget getBody() { if (showLoadingDialog) { return getProgressDialog(); } return getListView(); } Widget getProgressDialog() { return const Center(child: CircularProgressIndicator()); } ListView getListView() { return ListView\.builder( itemCount: data.length, itemBuilder: (context, index) { return getRow(index); }, ); } Widget getRow(int index) { return Padding( padding: const EdgeInsets.all(10), child: Text('Row ${data\[index]\['title']}'), ); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('Sample App')), body: getBody(), ); } } \`\`\` ### How do I make network requests? In Xamarin.Forms you would use \`HttpClient\`. Making a network call in Flutter is easy when you use the popular \[\`http\` package]\[]. This abstracts away a lot of the networking that you might normally implement yourself, making it simple to make network calls. To use the \`http\` package, add it to your dependencies in \`pubspec.yaml\`: \`\`\`yaml dependencies: http: ^1.4.0 \`\`\` To make a network request, call \`await\` on the \`async\` function \`http.get()\`:

<!--?code-excerpt "lib/data.dart (load-data)"?-->

\`\`\`dart Future loadData() async { final Uri dataURL = Uri.parse('https\://jsonplaceholder.typicode.com/posts'); final http.Response response = await http.get(dataURL); setState(() { data = (jsonDecode(response.body) as List).cast>(); }); } \`\`\` ### How do I show the progress for a long-running task? In Xamarin.Forms you would typically create a loading indicator, either directly in XAML or through a 3rd party plugin such as AcrDialogs. In Flutter, use a \`ProgressIndicator\` widget. Show the progress programmatically by controlling when it's rendered through a boolean flag. Tell Flutter to update its state before your long-running task starts, and hide it after it ends. In the example below, the build function is separated into three different functions. If \`showLoadingDialog\` is \`true\` (when \`widgets.length == 0\`), then render the \`ProgressIndicator\`. Otherwise, render the \`ListView\` with the data returned from a network call.

<!--?code-excerpt "lib/loading.dart"?-->

\`\`\`dart import 'dart:async'; import 'dart:convert'; import 'package:http/http.dart' as http; import 'package:material\_ui/material\_ui.dart'; void main() { runApp(const SampleApp()); } class SampleApp extends StatelessWidget { const SampleApp({super.key}); @override Widget build(BuildContext context) { return const MaterialApp(title: 'Sample App', home: SampleAppPage()); } } class SampleAppPage extends StatefulWidget { const SampleAppPage({super.key}); @override State createState() => \_SampleAppPageState(); } class \_SampleAppPageState extends State { List> data = \[]; @override void initState() { super.initState(); loadData(); } bool get showLoadingDialog => data.isEmpty; Future loadData() async { final Uri dataURL = Uri.parse('https\://jsonplaceholder.typicode.com/posts'); final http.Response response = await http.get(dataURL); setState(() { data = (jsonDecode(response.body) as List).cast>(); }); } Widget getBody() { if (showLoadingDialog) { return getProgressDialog(); } return getListView(); } Widget getProgressDialog() { return const Center(child: CircularProgressIndicator()); } ListView getListView() { return ListView\.builder( itemCount: data.length, itemBuilder: (context, index) { return getRow(index); }, ); } Widget getRow(int index) { return Padding( padding: const EdgeInsets.all(10), child: Text('Row ${data\[index]\['title']}'), ); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('Sample App')), body: getBody(), ); } } \`\`\` ## Project structure & resources ### Where do I store my image files? Xamarin.Forms has no platform independent way of storing images, you had to place images in the iOS \`xcasset\` folder, or on Android in the various \`drawable\` folders. While Android and iOS treat resources and assets as distinct items, Flutter apps have only assets. All resources that would live in the \`Resources/drawable-\*\` folders on Android, are placed in an assets' folder for Flutter. Flutter follows a simple density-based format like iOS. Assets might be \`1.0x\`, \`2.0x\`, \`3.0x\`, or any other multiplier. Flutter doesn't have \`dp\`s but there are logical pixels, which are basically the same as device-independent pixels. Flutter's \[\`devicePixelRatio\`]\[] expresses the ratio of physical pixels in a single logical pixel. The equivalent to Android's density buckets are: | Android density qualifier | Flutter pixel ratio | |---------------------------|---------------------| | \`ldpi\` | \`0.75x\` | | \`mdpi\` | \`1.0x\` | | \`hdpi\` | \`1.5x\` | | \`xhdpi\` | \`2.0x\` | | \`xxhdpi\` | \`3.0x\` | | \`xxxhdpi\` | \`4.0x\` | Assets are located in any arbitrary folder— Flutter has no predefined folder structure. You declare the assets (with location) in the \`pubspec.yaml\` file, and Flutter picks them up. To add a new image asset called \`my\_icon.png\` to our Flutter project, for example, and deciding that it should live in a folder we arbitrarily called \`images\`, you would put the base image (1.0x) in the \`images\` folder, and all the other variants in sub-folders called with the appropriate ratio multiplier: \`\`\`plaintext images/my\_icon.png // Base: 1.0x image images/2.0x/my\_icon.png // 2.0x image images/3.0x/my\_icon.png // 3.0x image \`\`\` Next, you'll need to declare these images in your \`pubspec.yaml\` file: \`\`\`yaml assets: - images/my\_icon.png \`\`\` You can directly access your images in an \`Image.asset\` widget:

<!--?code-excerpt "lib/images.dart (image-asset)"?-->

\`\`\`dart @override Widget build(BuildContext context) { return Image.asset('images/my\_icon.png'); } \`\`\` or using \`AssetImage\`:

<!--?code-excerpt "lib/images.dart (asset-image)"?-->

\`\`\`dart @override Widget build(BuildContext context) { return const Image(image: AssetImage('images/my\_image.png')); } \`\`\` More detailed information can be found in \[Adding assets and images]\[]. ### Where do I store strings? How do I handle localization? Unlike .NET which has \`resx\` files, Flutter doesn't currently have a dedicated system for handling strings. At the moment, the best practice is to declare your copy text in a class as static fields and access them from there. For example:

<!--?code-excerpt "lib/strings.dart (strings-class)"?-->

\`\`\`dart class Strings { static const String welcomeMessage = 'Welcome To Flutter'; } \`\`\` You can access your strings as such:

<!--?code-excerpt "lib/strings.dart (access-string)" replace="/return const //g"?-->

\`\`\`dart Text(Strings.welcomeMessage); \`\`\` By default, Flutter only supports US English for its strings. If you need to add support for other languages, include the \`flutter\_localizations\` package. You might also need to add Dart's \[\`intl\`]\[] package to use i10n machinery, such as date/time formatting. \`\`\`yaml dependencies: flutter\_localizations: sdk: flutter intl: any # Use version of intl from flutter\_localizations. \`\`\` To use the \`flutter\_localizations\` package, specify the \`localizationsDelegates\` and \`supportedLocales\` on the app widget:

<!--?code-excerpt "lib/strings.dart (localization)"?-->

\`\`\`dart import 'package:flutter\_localizations/flutter\_localizations.dart' hide GlobalMaterialLocalizations; import 'package:material\_ui/material\_ui.dart'; class MyWidget extends StatelessWidget { const MyWidget({super.key}); @override Widget build(BuildContext context) { return const MaterialApp( localizationsDelegates: >\[ // Add app-specific localization delegate\[s] here GlobalMaterialLocalizations.delegate, GlobalWidgetsLocalizations.delegate, ], supportedLocales: \[ Locale('en', 'US'), // English Locale('he', 'IL'), // Hebrew // ... other locales the app supports ], ); } } \`\`\` The delegates contain the actual localized values, while the \`supportedLocales\` defines which locales the app supports. The above example uses a \`MaterialApp\`, so it has both a \`GlobalWidgetsLocalizations\` for the base widgets localized values, and a \`MaterialWidgetsLocalizations\` for the Material widgets localizations. If you use \`WidgetsApp\` for your app, you don't need the latter. Note that these two delegates contain "default" values, but you'll need to provide one or more delegates for your own app's localizable copy, if you want those to be localized too. When initialized, the \`WidgetsApp\` (or \`MaterialApp\`) creates a \[\`Localizations\`]\[] widget for you, with the delegates you specify. The current locale for the device is always accessible from the \`Localizations\` widget from the current context (in the form of a \`Locale\` object), or using the \[\`Window\.locale\`]\[]. To access localized resources, use the \`Localizations.of()\` method to access a specific localizations class that is provided by a given delegate. Use the \[\`intl\_translation\`]\[] package to extract translatable copy to \[arb]\[] files for translating, and importing them back into the app for using them with \`intl\`. For further details on internationalization and localization in Flutter, see the \[internationalization guide]\[], which has sample code with and without the \`intl\` package. ### Where is my project file? In Xamarin.Forms you will have a \`csproj\` file. The closest equivalent in Flutter is pubspec.yaml, which contains package dependencies and various project details. Similar to .NET Standard, files within the same directory are considered part of the project. ### What is the equivalent of Nuget? How do I add dependencies? In the .NET ecosystem, native Xamarin projects and Xamarin.Forms projects had access to Nuget and the built-in package management system. Flutter apps contain a native Android app, native iOS app and Flutter app. In Android, you add dependencies by adding to your Gradle build script. In iOS, you add dependencies by adding to your \`Podfile\`. Flutter uses Dart's own build system, and the Pub package manager. The tools delegate the building of the native Android and iOS wrapper apps to the respective build systems. In general, use \`pubspec.yaml\` to declare external dependencies to use in Flutter. A good place to find Flutter packages is on \[pub.dev]\[]. ## Application lifecycle ### How do I listen to application lifecycle events? In Xamarin.Forms, you have an \`Application\` that contains \`OnStart\`, \`OnResume\` and \`OnSleep\`. In Flutter, you can instead listen to similar lifecycle events by hooking into the \`WidgetsBinding\` observer and listening to the \`didChangeAppLifecycleState()\` change event. The observable lifecycle events are: \`inactive\` : The application is in an inactive state and is not receiving user input. This event is iOS only. \`paused\` : The application is not currently visible to the user, is not responding to user input, but is running in the background. \`resumed\` : The application is visible and responding to user input. \`suspending\` : The application is suspended momentarily. This event is Android only. For more details on the meaning of these states, see the \[\`AppLifecycleStatus\` documentation]\[]. \[\`AppLifecycleStatus\` documentation]: https\://api.flutter.dev/flutter/dart-ui/AppLifecycleState.html ## Layouts ### What is the equivalent of a StackLayout? In Xamarin.Forms you can create a \`StackLayout\` with an \`Orientation\` of horizontal or vertical. Flutter has a similar approach, however you would use the \`Row\` or \`Column\` widgets. If you notice the two code samples are identical except the \`Row\` and \`Column\` widget. The children are the same and this feature can be exploited to develop rich layouts that can change overtime with the same children.

<!--?code-excerpt "lib/layouts.dart (row)"?-->

\`\`\`dart @override Widget build(BuildContext context) { return const Row( mainAxisAlignment: MainAxisAlignment.center, children: \[ Text('Row One'), Text('Row Two'), Text('Row Three'), Text('Row Four'), ], ); } \`\`\`

<!--?code-excerpt "lib/layouts.dart (column)"?-->

\`\`\`dart @override Widget build(BuildContext context) { return const Column( mainAxisAlignment: MainAxisAlignment.center, children: \[ Text('Column One'), Text('Column Two'), Text('Column Three'), Text('Column Four'), ], ); \`\`\` ### What is the equivalent of a Grid? The closest equivalent of a \`Grid\` would be a \`GridView\`. This is much more powerful than what you are used to in Xamarin.Forms. A \`GridView\` provides automatic scrolling when the content exceeds its viewable space.

<!--?code-excerpt "lib/layouts.dart (grid)"?-->

\`\`\`dart @override Widget build(BuildContext context) { return GridView\.count( // Create a grid with 2 columns. If you change the scrollDirection to // horizontal, this would produce 2 rows. crossAxisCount: 2, // Generate 100 widgets that display their index in the list. children: List.generate(100, (index) { return Center( child: Text( 'Item $index', style: Theme.of(context).textTheme.headlineMedium, ), ); }), ); } \`\`\` You might have used a \`Grid\` in Xamarin.Forms to implement widgets that overlay other widgets. In Flutter, you accomplish this with the \`Stack\` widget. This sample creates two icons that overlap each other.

<!--?code-excerpt "lib/layouts.dart (stack)"?-->

\`\`\`dart @override Widget build(BuildContext context) { return const Stack( children: \[ Icon(Icons.add\_box, size: 24, color: Colors.black), Positioned( left: 10, child: Icon(Icons.add\_circle, size: 24, color: Colors.black), ), ], ); } \`\`\` ### What is the equivalent of a ScrollView? In Xamarin.Forms, a \`ScrollView\` wraps around a \`VisualElement\`, and if the content is larger than the device screen, it scrolls. In Flutter, the closest match is the \`SingleChildScrollView\` widget. You simply fill the Widget with the content that you want to be scrollable.

<!--?code-excerpt "lib/layouts.dart (scroll-view)"?-->

\`\`\`dart @override Widget build(BuildContext context) { return const SingleChildScrollView(child: Text('Long Content')); } \`\`\` If you have many items you want to wrap in a scroll, even of different \`Widget\` types, you might want to use a \`ListView\`. This might seem like overkill, but in Flutter this is far more optimized and less intensive than a Xamarin.Forms \`ListView\`, which is backing on to platform specific controls.

<!--?code-excerpt "lib/layouts.dart (list-view)"?-->

\`\`\`dart @override Widget build(BuildContext context) { return ListView( children: const \[ Text('Row One'), Text('Row Two'), Text('Row Three'), Text('Row Four'), ], ); } \`\`\` ### How do I handle landscape transitions in Flutter? Landscape transitions can be handled automatically by setting the \`configChanges\` property in the AndroidManifest.xml: \`\`\`xml \`\`\` ## Gesture detection and touch event handling ### How do I add GestureRecognizers to a widget in Flutter? In Xamarin.Forms, \`Element\`s might contain a click event you can attach to. Many elements also contain a \`Command\` that is tied to this event. Alternatively you would use the \`TapGestureRecognizer\`. In Flutter there are two very similar ways: 1. If the widget supports event detection, pass a function to it and handle it in the function. For example, the ElevatedButton has an \`onPressed\` parameter:

<!--?code-excerpt "lib/gestures.dart (elevated-button)"?-->

\`\`\`dart @override Widget build(BuildContext context) { return ElevatedButton( onPressed: () { developer.log('click'); }, child: const Text('Button'), ); } \`\`\` 2. If the widget doesn't support event detection, wrap the widget in a \`GestureDetector\` and pass a function to the \`onTap\` parameter.

<!--?code-excerpt "lib/gestures.dart (gesture-detector)"?-->

\`\`\`dart class SampleApp extends StatelessWidget { const SampleApp({super.key}); @override Widget build(BuildContext context) { return Scaffold( body: Center( child: GestureDetector( onTap: () { developer.log('tap'); }, child: const FlutterLogo(size: 200), ), ), ); } } \`\`\` ### How do I handle other gestures on widgets? In Xamarin.Forms you would add a \`GestureRecognizer\` to the \`View\`. You would normally be limited to \`TapGestureRecognizer\`, \`PinchGestureRecognizer\`, \`PanGestureRecognizer\`, \`SwipeGestureRecognizer\`, \`DragGestureRecognizer\` and \`DropGestureRecognizer\` unless you built your own. In Flutter, using the GestureDetector, you can listen to a wide range of Gestures such as: \* Tap \`onTapDown\` : A pointer that might cause a tap has contacted the screen at a particular location. \`onTapUp\` : A pointer that triggers a tap has stopped contacting the screen at a particular location. \`onTap\` : A tap has occurred. \`onTapCancel\` : The pointer that previously triggered the \`onTapDown\` won't cause a tap. \* Double tap \`onDoubleTap\` : The user tapped the screen at the same location twice in quick succession. \* Long press \`onLongPress\` : A pointer has remained in contact with the screen at the same location for a long period of time. \* Vertical drag \`onVerticalDragStart\` : A pointer has contacted the screen and might begin to move vertically. \`onVerticalDragUpdate\` : A pointer in contact with the screen has moved further in the vertical direction. \`onVerticalDragEnd\` : A pointer that was previously in contact with the screen and moving vertically is no longer in contact with the screen and was moving at a specific velocity when it stopped contacting the screen. \* Horizontal drag \`onHorizontalDragStart\` : A pointer has contacted the screen and might begin to move horizontally. \`onHorizontalDragUpdate\` : A pointer in contact with the screen has moved further in the horizontal direction. \`onHorizontalDragEnd\` : A pointer that was previously in contact with the screen and moving horizontally is no longer in contact with the screen and was moving at a specific velocity when it stopped contacting the screen. The following example shows a \`GestureDetector\` that rotates the Flutter logo on a double tap:

<!--?code-excerpt "lib/gestures.dart (rotating-flutter-detector)"?-->

\`\`\`dart class RotatingFlutterDetector extends StatefulWidget { const RotatingFlutterDetector({super.key}); @override State createState() => \_RotatingFlutterDetectorState(); } class \_RotatingFlutterDetectorState extends State with SingleTickerProviderStateMixin { late final AnimationController controller; late final CurvedAnimation curve; @override void initState() { super.initState(); controller = AnimationController( duration: const Duration(milliseconds: 2000), vsync: this, ); curve = CurvedAnimation(parent: controller, curve: Curves.easeIn); } @override Widget build(BuildContext context) { return Scaffold( body: Center( child: GestureDetector( onDoubleTap: () { if (controller.isCompleted) { controller.reverse(); } else { controller.forward(); } }, child: RotationTransition( turns: curve, child: const FlutterLogo(size: 200), ), ), ), ); } } \`\`\` ## Listviews and adapters ### What is the equivalent to a ListView in Flutter? The equivalent to a \`ListView\` in Flutter is … a \`ListView\`! In a Xamarin.Forms \`ListView\`, you create a \`ViewCell\` and possibly a \`DataTemplateSelector\`and pass it into the \`ListView\`, which renders each row with what your \`DataTemplateSelector\` or \`ViewCell\` returns. However, you often have to make sure you turn on Cell Recycling otherwise you will run into memory issues and slow scrolling speeds. Due to Flutter's immutable widget pattern, you pass a list of widgets to your \`ListView\`, and Flutter takes care of making sure that scrolling is fast and smooth.

<!--?code-excerpt "lib/listview.dart"?-->

\`\`\`dart import 'package:material\_ui/material\_ui.dart'; void main() { runApp(const SampleApp()); } class SampleApp extends StatelessWidget { /// This widget is the root of your application. const SampleApp({super.key}); @override Widget build(BuildContext context) { return const MaterialApp(title: 'Sample App', home: SampleAppPage()); } } class SampleAppPage extends StatelessWidget { const SampleAppPage({super.key}); List \_getListData() { return List.generate( 100, (index) => Padding(padding: const EdgeInsets.all(10), child: Text('Row $index')), ); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('Sample App')), body: ListView(children: \_getListData()), ); } } \`\`\` ### How do I know which list item has been clicked? In Xamarin.Forms, the ListView has an \`ItemTapped\` method to find out which item was clicked. There are many other techniques you might have used such as checking when \`SelectedItem\` or \`EventToCommand\` behaviors change. In Flutter, use the touch handling provided by the passed-in widgets.

<!--?code-excerpt "lib/listview_item_clicked.dart"?-->

\`\`\`dart import 'dart:developer' as developer; import 'package:material\_ui/material\_ui.dart'; void main() { runApp(const SampleApp()); } class SampleApp extends StatelessWidget { // This widget is the root of your application. const SampleApp({super.key}); @override Widget build(BuildContext context) { return const MaterialApp(title: 'Sample App', home: SampleAppPage()); } } class SampleAppPage extends StatefulWidget { const SampleAppPage({super.key}); @override State createState() => \_SampleAppPageState(); } class \_SampleAppPageState extends State { List \_getListData() { return List.generate( 100, (index) => GestureDetector( onTap: () { developer.log('Row $index tapped'); }, child: Padding( padding: const EdgeInsets.all(10), child: Text('Row $index'), ), ), ); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('Sample App')), body: ListView(children: \_getListData()), ); } } \`\`\` ### How do I update a ListView dynamically? In Xamarin.Forms, if you bound the \`ItemsSource\` property to an \`ObservableCollection\`, you would just update the list in your ViewModel. Alternatively, you could assign a new \`List\` to the \`ItemSource\` property. In Flutter, things work a little differently. If you update the list of widgets inside a \`setState()\` method, you would quickly see that your data did not change visually. This is because when \`setState()\` is called, the Flutter rendering engine looks at the widget tree to see if anything has changed. When it gets to your \`ListView\`, it performs a \`==\` check, and determines that the two \`ListView\`s are the same. Nothing has changed, so no update is required. For a simple way to update your \`ListView\`, create a new \`List\` inside of \`setState()\`, and copy the data from the old list to the new list. While this approach is simple, it is not recommended for large data sets, as shown in the next example.

<!--?code-excerpt "lib/dynamic_listview.dart"?-->

\`\`\`dart import 'dart:developer' as developer; import 'package:material\_ui/material\_ui.dart'; void main() { runApp(const SampleApp()); } class SampleApp extends StatelessWidget { /// This widget is the root of your application. const SampleApp({super.key}); @override Widget build(BuildContext context) { return const MaterialApp(title: 'Sample App', home: SampleAppPage()); } } class SampleAppPage extends StatefulWidget { const SampleAppPage({super.key}); @override State createState() => \_SampleAppPageState(); } class \_SampleAppPageState extends State { List widgets = \[]; @override void initState() { super.initState(); for (int i = 0; i < 100; i++) { widgets.add(getRow(i)); } } Widget getRow(int index) { return GestureDetector( onTap: () { setState(() { widgets = List.from(widgets); widgets.add(getRow(widgets.length)); developer.log('Row $index'); }); }, child: Padding( padding: const EdgeInsets.all(10), child: Text('Row $index'), ), ); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('Sample App')), body: ListView(children: widgets), ); } } \`\`\` The recommended, efficient, and effective way to build a list uses a \`ListView\.Builder\`. This method is great when you have a dynamic list or a list with very large amounts of data. This is essentially the equivalent of RecyclerView on Android, which automatically recycles list elements for you:

<!--?code-excerpt "lib/listview_builder.dart"?-->

\`\`\`dart import 'dart:developer' as developer; import 'package:material\_ui/material\_ui.dart'; void main() { runApp(const SampleApp()); } class SampleApp extends StatelessWidget { /// This widget is the root of your application. const SampleApp({super.key}); @override Widget build(BuildContext context) { return const MaterialApp(title: 'Sample App', home: SampleAppPage()); } } class SampleAppPage extends StatefulWidget { const SampleAppPage({super.key}); @override State createState() => \_SampleAppPageState(); } class \_SampleAppPageState extends State { List widgets = \[]; @override void initState() { super.initState(); for (int i = 0; i < 100; i++) { widgets.add(getRow(i)); } } Widget getRow(int index) { return GestureDetector( onTap: () { setState(() { widgets.add(getRow(widgets.length)); developer.log('Row $index'); }); }, child: Padding( padding: const EdgeInsets.all(10), child: Text('Row $index'), ), ); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('Sample App')), body: ListView\.builder( itemCount: widgets.length, itemBuilder: (context, index) { return getRow(index); }, ), ); } } \`\`\` Instead of creating a \`ListView\`, create a \`ListView\.builder\` that takes two key parameters: the initial length of the list, and an item builder function. The item builder function is similar to the \`getView\` function in an Android adapter; it takes a position, and returns the row you want rendered at that position. Finally, but most importantly, notice that the \`onTap()\` function doesn't recreate the list anymore, but instead adds to it. For more information, see \[Your first Flutter app]\[first\_codelab] codelab. ## Working with text ### How do I set custom fonts on my text widgets? In Xamarin.Forms, you would have to add a custom font in each native project. Then, in your \`Element\` you would assign this font name to the \`FontFamily\` attribute using \`filename#fontname\` and just \`fontname\` for iOS. In Flutter, place the font file in a folder and reference it in the \`pubspec.yaml\` file, similar to how you import images. \`\`\`yaml fonts: - family: MyCustomFont fonts: - asset: fonts/MyCustomFont.ttf - style: italic \`\`\` Then assign the font to your \`Text\` widget:

<!--?code-excerpt "lib/strings.dart (custom-font)"?-->

\`\`\`dart @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('Sample App')), body: const Center( child: Text( 'This is a custom font text', style: TextStyle(fontFamily: 'MyCustomFont'), ), ), ); } \`\`\` ### How do I style my text widgets? Along with fonts, you can customize other styling elements on a \`Text\` widget. The style parameter of a \`Text\` widget takes a \`TextStyle\` object, where you can customize many parameters, such as: \* \`color\` \* \`decoration\` \* \`decorationColor\` \* \`decorationStyle\` \* \`fontFamily\` \* \`fontSize\` \* \`fontStyle\` \* \`fontWeight\` \* \`hashCode\` \* \`height\` \* \`inherit\` \* \`letterSpacing\` \* \`textBaseline\` \* \`wordSpacing\` ## Form input ### How do I retrieve user input? Xamarin.Forms \`element\`s allow you to directly query the \`element\` to determine the state of its properties, or whether it's bound to a property in a \`ViewModel\`. Retrieving information in Flutter is handled by specialized widgets and is different from how you are used to. If you have a \`TextField\`or a \`TextFormField\`, you can supply a \[\`TextEditingController\`]\[] to retrieve user input:

<!--?code-excerpt "lib/form.dart"?-->

\`\`\`dart import 'package:material\_ui/material\_ui.dart'; class MyForm extends StatefulWidget { const MyForm({super.key}); @override State createState() => \_MyFormState(); } class \_MyFormState extends State { /// Create a text controller and use it to retrieve the current value /// of the TextField. final TextEditingController myController = TextEditingController(); @override void dispose() { // Clean up the controller when disposing of the widget. myController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('Retrieve Text Input')), body: Padding( padding: const EdgeInsets.all(16), child: TextField(controller: myController), ), floatingActionButton: FloatingActionButton( // When the user presses the button, show an alert dialog with the // text that the user has typed into our text field. onPressed: () { showDialog( context: context, builder: (context) { return AlertDialog( // Retrieve the text that the user has entered using the // TextEditingController. content: Text(myController.text), ); }, ); }, tooltip: 'Show me the value!', child: const Icon(Icons.text\_fields), ), ); } } \`\`\` You can find more information and the full code listing in \[Retrieve the value of a text field]\[]. ### What is the equivalent of a Placeholder on an Entry? In Xamarin.Forms, some \`Elements\` support a \`Placeholder\` property that you can assign a value to. For example: \`\`\`xml \`\`\` In Flutter, you can easily show a "hint" or a placeholder text for your input by adding an \`InputDecoration\` object to the \`decoration\` constructor parameter for the text widget.

<!--?code-excerpt "lib/input_decoration.dart (hint-text)" replace="/child: //g"?-->

\`\`\`dart TextField(decoration: InputDecoration(hintText: 'This is a hint')), \`\`\` ### How do I show validation errors? With Xamarin.Forms, if you wished to provide a visual hint of a validation error, you would need to create new properties and \`VisualElement\`s surrounding the \`Element\`s that had validation errors. In Flutter, you pass through an InputDecoration object to the decoration constructor for the text widget. However, you don't want to start off by showing an error. Instead, when the user has entered invalid data, update the state, and pass a new \`InputDecoration\` object.

<!--?code-excerpt "lib/validation.dart"?-->

\`\`\`dart import 'package:material\_ui/material\_ui.dart'; void main() { runApp(const SampleApp()); } class SampleApp extends StatelessWidget { /// This widget is the root of your application. const SampleApp({super.key}); @override Widget build(BuildContext context) { return const MaterialApp(title: 'Sample App', home: SampleAppPage()); } } class SampleAppPage extends StatefulWidget { const SampleAppPage({super.key}); @override State createState() => \_SampleAppPageState(); } class \_SampleAppPageState extends State { String? \_errorText; String? \_getErrorText() { return \_errorText; } bool isEmail(String em) { const String emailRegexp = r'^((\[^<>()\[\\]\\\\.,;:\s@\\"]+(\\.\[^<>()\[\\]\\\\.,;:\s@\\"]+)\*)|' r'(\\".+\\"))@((\\\[\[0-9]{1,3}\\.\[0-9]{1,3}\\.\[0-9]{1,3}\\.\[0-9]{1,3}\\])|' r'((\[a-zA-Z\\-0-9]+\\.)+\[a-zA-Z]{2,}))$'; final RegExp regExp = RegExp(emailRegexp); return regExp.hasMatch(em); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('Sample App')), body: Center( child: TextField( onSubmitted: (text) { setState(() { if (!isEmail(text)) { \_errorText = 'Error: This is not an email'; } else { \_errorText = null; } }); }, decoration: InputDecoration( hintText: 'This is a hint', errorText: \_getErrorText(), ), ), ), ); } } \`\`\` ## Flutter plugins ## Interacting with hardware, third party services, and the platform ### How do I interact with the platform, and with platform native code? Flutter doesn't run code directly on the underlying platform; rather, the Dart code that makes up a Flutter app is run natively on the device, "sidestepping" the SDK provided by the platform. That means, for example, when you perform a network request in Dart, it runs directly in the Dart context. You don't use the Android or iOS APIs you normally take advantage of when writing native apps. Your Flutter app is still hosted in a native app's \`ViewController\` or \`Activity\` as a view, but you don't have direct access to this, or the native framework. This doesn't mean Flutter apps can't interact with those native APIs, or with any native code you have. Flutter provides \[platform channels]\[] that communicate and exchange data with the \`ViewController\` or \`Activity\` that hosts your Flutter view. Platform channels are essentially an asynchronous messaging mechanism that bridges the Dart code with the host \`ViewController\` or \`Activity\` and the iOS or Android framework it runs on. You can use platform channels to execute a method on the native side, or to retrieve some data from the device's sensors, for example. In addition to directly using platform channels, you can use a variety of pre-made \[plugins]\[] that encapsulate the native and Dart code for a specific goal. For example, you can use a plugin to access the camera roll and the device camera directly from Flutter, without having to write your own integration. Plugins are found on \[pub.dev]\[], Dart and Flutter's open source package repository. Some packages might support native integrations on iOS, or Android, or both. If you can't find a plugin on pub.dev that fits your needs, you can \[write your own]\[], and \[publish it on pub.dev]\[]. ### How do I access the GPS sensor? Use the \[\`geolocator\`]\[] community plugin. ### How do I access the camera? The \[\`camera\`]\[] plugin is popular for accessing the camera. ### How do I log in with Facebook? To log in with Facebook, use the \[\`flutter\_facebook\_auth\`]\[] community plugin. ### How do I use Firebase features? Most Firebase functions are covered by \[first party plugins]\[]. These plugins are first-party integrations, maintained by the Flutter and Firebase teams: \* \[\`google\_mobile\_ads\`]\[] for Google Mobile Ads for Flutter \* \[\`firebase\_analytics\`]\[] for Firebase Analytics \* \[\`firebase\_auth\`]\[] for Firebase Auth \* \[\`firebase\_database\`]\[] for Firebase RTDB \* \[\`firebase\_storage\`]\[] for Firebase Cloud Storage \* \[\`firebase\_messaging\`]\[] for Firebase Messaging (FCM) \* \[\`firebase\_ui\_auth\`]\[] for quick Firebase Auth integrations (Facebook, Google, Twitter and email) \* \[\`cloud\_firestore\`]\[] for Firebase Cloud Firestore You can also find some third-party Firebase plugins on pub.dev that cover areas not directly covered by the first-party plugins. ### How do I build my own custom native integrations? If there is platform-specific functionality that Flutter or its community plugins are missing, you can build your own following the \[developing packages and plugins]\[] page. Flutter's plugin architecture, in a nutshell, is much like using an Event bus in Android: you fire off a message and let the receiver process and emit a result back to you. In this case, the receiver is code running on the native side on Android or iOS. ## Themes (Styles) ### How do I theme my app? Flutter comes with a beautiful, built-in implementation of Material Design, which handles much of the styling and theming needs that you would typically do. Xamarin.Forms does have a global \`ResourceDictionary\` where you can share styles across your app. Alternatively, there is Theme support currently in preview. In Flutter, you declare themes in the top level widget. To take full advantage of Material Components in your app, you can declare a top level widget \`MaterialApp\` as the entry point to your application. \`MaterialApp\` is a convenience widget that wraps a number of widgets that are commonly required for applications implementing Material Design. It builds upon a \`WidgetsApp\` by adding Material-specific functionality. You can also use a \`WidgetsApp\` as your app widget, which provides some of the same functionality, but is not as rich as \`MaterialApp\`. To customize the colors and styles of any child components, pass a \`ThemeData\` object to the \`MaterialApp\` widget. For example, in the following code, the color scheme from seed is set to deepPurple and text selection color is red.

<!--?code-excerpt "lib/theme.dart (theme)"?-->

\`\`\`dart class SampleApp extends StatelessWidget { /// This widget is the root of your application. const SampleApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( title: 'Sample App', theme: ThemeData( colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), textSelectionTheme: const TextSelectionThemeData( selectionColor: Colors.red, ), ), home: const SampleAppPage(), ); } } \`\`\` ## Databases and local storage ### How do I access shared preferences or UserDefaults? Xamarin.Forms developers will likely be familiar with the \`Xam.Plugins.Settings\` plugin. In Flutter, access equivalent functionality using the \[\`shared\_preferences\`]\[] plugin. This plugin wraps the functionality of both \`UserDefaults\` and the Android equivalent, \`SharedPreferences\`. ### How do I access SQLite in Flutter? In Xamarin.Forms most applications would use the \`sqlite-net-pcl\` plugin to access SQLite databases. In Flutter, on macOS, Android, and iOS, access this functionality using the \[\`sqflite\`]\[] plugin. ## Debugging ### What tools can I use to debug my app in Flutter? Use the \[DevTools]\[] suite for debugging Flutter or Dart apps. DevTools includes support for profiling, examining the heap, inspecting the widget tree, logging diagnostics, debugging, observing executed lines of code, debugging memory leaks and memory fragmentation. For more information, check out the \[DevTools]\[] documentation. ## Notifications ### How do I set up push notifications? In Android, you use Firebase Cloud Messaging to set up push notifications for your app. In Flutter, access this functionality using the \[\`firebase\_messaging\`]\[] plugin. For more information on using the Firebase Cloud Messaging API, see the \[\`firebase\_messaging\`]\[] plugin documentation. \[Adding assets and images]: /ui/assets/assets-and-images \[Animation & Motion widgets]: /ui/widgets/animation \[Animations overview]: /ui/animations \[Animations tutorial]: /ui/animations/tutorial \[Apple's iOS design language]: https\://developer.apple.com/design/resources/ \[arb]: https\://github.com/google/app-resource-bundle \[Async UI]: #async-ui \[\`cloud\_firestore\`]: https\://pub.dev/packages/cloud\_firestore \[composing]: /resources/architectural-overview#composition \[Cupertino widgets]: /ui/widgets/cupertino \[\`devicePixelRatio\`]: https\://api.flutter.dev/flutter/dart-ui/FlutterView/devicePixelRatio.html \[developing packages and plugins]: /packages-and-plugins/developing-packages \[DevTools]: /tools/devtools \[existing plugin]: https\://pub.dev/flutter \[\`google\_mobile\_ads\`]: https\://pub.dev/packages/google\_mobile\_ads \[\`firebase\_analytics\`]: https\://pub.dev/packages/firebase\_analytics \[\`firebase\_auth\`]: https\://pub.dev/packages/firebase\_auth \[\`firebase\_database\`]: https\://pub.dev/packages/firebase\_database \[\`firebase\_messaging\`]: https\://pub.dev/packages/firebase\_messaging \[\`firebase\_storage\`]: https\://pub.dev/packages/firebase\_storage \[first party plugins]: https\://pub.dev/flutter/packages?q=firebase \[\`flutter\_facebook\_auth\`]: https\://pub.dev/packages/flutter\_facebook\_auth \[\`firebase\_ui\_auth\`]: https\://pub.dev/packages/firebase\_ui\_auth \[\`geolocator\`]: https\://pub.dev/packages/geolocator \[\`camera\`]: https\://pub.dev/packages/camera \[\`http\` package]: https\://pub.dev/packages/http \[internationalization guide]: /ui/internationalization \[\`intl\`]: https\://pub.dev/packages/intl \[\`intl\_translation\`]: https\://pub.dev/packages/intl\_translation \[Introduction to declarative UI]: /flutter-for/declarative \[\`Localizations\`]: https\://api.flutter.dev/flutter/widgets/Localizations-class.html \[Material Components]: /ui/widgets/material \[Material Design]: https\://m3.material.io/styles \[Material Design guidelines]: https\://m3.material.io/styles \[\`Opacity\` widget]: https\://api.flutter.dev/flutter/widgets/Opacity-class.html \[optimized for all platforms]: https\://m2.material.io/design/platform-guidance/cross-platform-adaptation.html#cross-platform-guidelines \[platform channels]: /platform-integration/platform-channels \[plugins]: /packages-and-plugins/using-packages \[pub.dev]: https\://pub.dev \[publish it on pub.dev]: /packages-and-plugins/developing-packages#publish \[Retrieve the value of a text field]: /cookbook/forms/retrieve-input \[\`shared\_preferences\`]: https\://pub.dev/packages/shared\_preferences \[\`sqflite\`]: https\://pub.dev/packages/sqflite \[\`TextEditingController\`]: https\://api.flutter.dev/flutter/widgets/TextEditingController-class.html \[\`url\_launcher\`]: https\://pub.dev/packages/url\_launcher \[widget]: /resources/architectural-overview#widgets \[widget catalog]: /ui/widgets/layout \[\`Window\.locale\`]: https\://api.flutter.dev/flutter/dart-ui/Window/locale.html \[first\_codelab]: https\://codelabs.developers.google.com/codelabs/flutter-codelab-first \[write your own]: /packages-and-plugins/developing-packages

----
url: https://docs.flutter.dev/add-to-app/multiple-flutters
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Multiple Flutter screens or views](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

## Scenarios

[#](#scenarios)

If you're integrating Flutter into an existing app, or gradually migrating an existing app to use Flutter, you might find yourself wanting to add multiple Flutter instances to the same project. In particular, this can be useful in the following scenarios:

* An application where the integrated Flutter screen is not a leaf node of the navigation graph, and the navigation stack might be a hybrid mixture of native -> Flutter -> native -> Flutter.
* A screen where multiple partial screen Flutter views might be integrated and visible at once.

The advantage of using multiple Flutter instances is that each instance is independent and maintains its own internal navigation stack, UI, and application states. This simplifies the overall application code's responsibility for state keeping and improves modularity. More details on the scenarios motivating the usage of multiple Flutters can be found at [flutter.dev/go/multiple-flutters](/go/multiple-flutters).

Flutter is optimized for this scenario, with a low incremental memory cost (\~180kB) for adding additional Flutter instances. This fixed cost reduction allows the multiple Flutter instance pattern to be used more liberally in your add-to-app integration.

## Components

[#](#components)

The primary API for adding multiple Flutter instances on both Android and iOS is based on a new `FlutterEngineGroup` class ([Android API](https://cs.opensource.google/flutter/engine/+/main:shell/platform/android/io/flutter/embedding/engine/FlutterEngineGroup.java), [iOS API](https://cs.opensource.google/flutter/engine/+/main:shell/platform/darwin/ios/framework/Headers/FlutterEngineGroup.h)) to construct `FlutterEngine`s, rather than the `FlutterEngine` constructors used previously.

Whereas the `FlutterEngine` API was direct and easier to consume, the `FlutterEngine` spawned from the same `FlutterEngineGroup` have the performance advantage of sharing many of the common, reusable resources such as the GPU context, font metrics, and isolate group snapshot, leading to a faster initial rendering latency and lower memory footprint.

* `FlutterEngine`s spawned from `FlutterEngineGroup` can be used to connect to UI classes like [`FlutterActivity`](https://api.flutter.dev/javadoc/io/flutter/embedding/android/FlutterActivity.html) or [`FlutterViewController`](https://api.flutter.dev/ios-embedder/interface_flutter_view_controller.html) in the same way as normally constructed cached `FlutterEngine`s.

* The first `FlutterEngine` spawned from the `FlutterEngineGroup` doesn't need to continue surviving in order for subsequent `FlutterEngine`s to share resources as long as there's at least 1 living `FlutterEngine` at all times.

* Creating the very first `FlutterEngine` from a `FlutterEngineGroup` has the same [performance characteristics](/add-to-app/performance) as constructing a `FlutterEngine` using the constructors did previously.

* When all `FlutterEngine`s from a `FlutterEngineGroup` are destroyed, the next `FlutterEngine` created has the same performance characteristics as the very first engine.

* The `FlutterEngineGroup` itself doesn't need to live beyond all of the spawned engines. Destroying the `FlutterEngineGroup` doesn't affect existing spawned `FlutterEngine`s but does remove the ability to spawn additional `FlutterEngine`s that share resources with existing spawned engines.

## Communication

[#](#communication)

Communication between Flutter instances is handled using [platform channels](/platform-integration/platform-channels) (or [Pigeon](https://pub.dev/packages/pigeon)) through the host platform. To see our roadmap on communication, or other planned work on enhancing multiple Flutter instances, check out [Issue 72009](https://github.com/flutter/flutter/issues/72009).

## Samples

[#](#samples)

You can find a sample demonstrating how to use `FlutterEngineGroup` on both Android and iOS on [GitHub](https://github.com/flutter/samples/tree/main/add_to_app/multiple_flutters).

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/add-to-app/multiple-flutters.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/add-to-app/multiple-flutters\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/add-to-app/multiple-flutters.md "Report an issue with this page").

----
url: https://docs.flutter.dev/platform-integration/macos/setup.md
----

\# Set up macOS development > Configure your development environment to run, build, and deploy Flutter apps for macOS devices. Learn how to set up your development environment to run, build, and deploy Flutter apps for the macOS desktop platform. :::note If you haven't set up Flutter already, visit and follow \[Install Flutter]\[] first. If you've already installed Flutter, ensure that it's \[up to date]\[]. ::: \[Install Flutter]: /install \[up to date]: /install/upgrade ## Set up tooling {: #set-up-tooling} With Xcode, you can run Flutter apps on macOS as well as compile and debug native Swift and Objective-C code. 1.

### Install Xcode

If you haven't done so already, \[install and set up the latest version of Xcode]\[xcode]. If you've already installed Xcode, update it to the latest version using the same installation method you used originally. 1.

### Set up Xcode command-line tools

To configure the Xcode command-line tools to use the version of Xcode you installed, run the following command in your preferred terminal: \`\`\`console $ sudo sh -c 'xcode-select -s /Applications/Xcode.app/Contents/Developer && xcodebuild -runFirstLaunch' \`\`\` If you downloaded Xcode elsewhere or need to use a different version, replace \`/Applications/Xcode.app\` with the path to your Xcode directory. 1.

### Agree to the Xcode licenses

After you've set up Xcode and configured its command-line tools, agree to the Xcode licenses. 1. Open your preferred terminal. 1. Run the following command to review and sign the Xcode licenses. \`\`\`console $ sudo xcodebuild -license \`\`\` 1. Read and agree to all necessary licenses. Before agreeing to the terms of each license, read each with care. Once you've accepted all the necessary licenses successfully, the command should output how to review the licenses. 1.

### Install CocoaPods

To support \[Flutter plugins]\[] that use native macOS code, install the latest version of \[CocoaPods]\[]. Install CocoaPods following the \[CocoaPods installation guide]\[]. If you've already installed CocoaPods, update it following the \[CocoaPods update guide]\[]. {: .steps} \[xcode]: https\://developer.apple.com/xcode/ \[Flutter plugins]: /packages-and-plugins/developing-packages#types \[CocoaPods]: https\://cocoapods.org/ \[CocoaPods installation guide]: https\://guides.cocoapods.org/using/getting-started.html#installation \[CocoaPods update guide]: https\://guides.cocoapods.org/using/getting-started.html#updating-cocoapods ## Validate your setup {: #validate-setup} 1.

### Check for toolchain issues

To check for any issues with your macOS development setup, run the \`flutter doctor\` command in your preferred terminal: \`\`\`console $ flutter doctor -v \`\`\` If you see any errors or tasks to complete under the \*\*Xcode\*\* section, complete and resolve them, then run \`flutter doctor -v\` again to verify any changes. 1.

### Check for macOS devices

To ensure Flutter can find and connect to your macOS device correctly, run \`flutter devices\` in your preferred terminal: \`\`\`console $ flutter devices \`\`\` If you set everything up correctly, there should be at least one entry with the platform marked as \*\*macos\*\*. 1.

### Troubleshoot setup issues

If you need help resolving any setup issues, check out \[Install and setup troubleshooting]\[]. If you still have issues or questions, reach out on one of the Flutter \[community]\[] channels. {: .steps} \[Install and setup troubleshooting]: /install/troubleshoot \[community]: https\://flutter.dev/community ## Start developing for macOS {: #start-developing} Congratulations! Now that you've set up macOS desktop development for Flutter, you can continue your Flutter learning journey while testing on macOS or begin expanding integration with macOS.

----
url: https://docs.flutter.dev/data-and-backend/serialization/json.md
----

\# JSON and serialization > How to use JSON with Flutter.

<!--?code-excerpt path-base="data-and-backend/json/"?-->

It is hard to think of a mobile app that doesn't need to communicate with a web server or easily store structured data at some point. When making network-connected apps, the chances are that it needs to consume some good old JSON, sooner or later. This guide looks into ways of using JSON with Flutter. It covers which JSON solution to use in different scenarios, and why. :::note Terminology \_Encoding\_ and \_serialization\_ are the same thing—turning a data structure into a string. \_Decoding\_ and \_deserialization\_ are the opposite process—turning a string into a data structure. However, \_serialization\_ also commonly refers to the entire process of translating data structures to and from a more easily readable format. To avoid confusion, this doc uses "serialization" when referring to the overall process, and "encoding" and "decoding" when specifically referring to those processes. ::: ## Which JSON serialization method is right for me? This article covers two general strategies for working with JSON: \* Manual serialization \* Automated serialization using code generation Different projects come with different complexities and use cases. For smaller proof-of-concept projects or quick prototypes, using code generators might be overkill. For apps with several JSON models with more complexity, encoding by hand can quickly become tedious, repetitive, and lend itself to many small errors. ### Use manual serialization for smaller projects Manual JSON decoding refers to using the built-in JSON decoder in \`dart:convert\`. It involves passing the raw JSON string to the \`jsonDecode()\` function, and then looking up the values you need in the resulting \`Map\`. It has no external dependencies or particular setup process, and it's good for a quick proof of concept. Manual decoding does not perform well when your project becomes bigger. Writing decoding logic by hand can become hard to manage and error-prone. If you have a typo when accessing a nonexistent JSON field, your code throws an error during runtime. If you do not have many JSON models in your project and are looking to test a concept quickly, manual serialization might be the way you want to start. For an example of manual encoding, see \[Serializing JSON manually using dart:convert]\[]. :::tip For hands-on practice deserializing JSON and taking advantage of Dart 3's new features, check out the \[Dive into Dart's patterns and records]\[] codelab. ::: ### Use code generation for medium to large projects JSON serialization with code generation means having an external library generate the encoding boilerplate for you. After some initial setup, you run a file watcher that generates the code from your model classes. For example, \[\`json\_serializable\`]\[] and \[\`built\_value\`]\[] 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 might produce visual clutter in your project navigator. You might want to use generated code for JSON serialization when you have a medium or a larger project. To see an example of code generation based JSON encoding, see \[Serializing JSON using code generation libraries]\[]. ## Is there a GSON/​Jackson/​Moshi equivalent in Flutter? The simple answer is no. Such a library would require using runtime \[reflection]\[], which is disabled in Flutter. Runtime reflection interferes with \[tree shaking]\[], which Dart has supported for quite a long time. With tree shaking, you can "shake off" unused code from your release builds. This optimizes the app's size significantly. Since reflection makes all code implicitly used by default, it makes tree shaking difficult. The tools cannot know what parts are unused at runtime, so the redundant code is hard to strip away. App sizes cannot be easily optimized when using reflection. Although you cannot use runtime reflection with Flutter, some libraries give you similarly easy-to-use APIs but are based on code generation instead. This approach is covered in more detail in the \[code generation libraries]\[] section. []()## Serializing JSON manually using dart:convert Basic JSON serialization in Flutter is very simple. Flutter has a built-in \`dart:convert\` library that includes a straightforward JSON encoder and decoder. The following sample JSON implements a simple user model.

<!--?code-excerpt "lib/manual/main.dart (multiline-json)" skip="1" take="4"?-->

\`\`\`json { "name": "John Smith", "email": "john\@example.com" } \`\`\` With \`dart:convert\`, you can serialize this JSON model in two ways. ### Serializing JSON inline By looking at the \[\`dart:convert\`]\[] documentation, you'll see that you can decode the JSON by calling the \`jsonDecode()\` function, with the JSON string as the method argument.

<!--?code-excerpt "lib/manual/main.dart (manual)"?-->

\`\`\`dart final user = jsonDecode(jsonString) as Map; print('Howdy, ${user\['name']}!'); print('We sent the verification link to ${user\['email']}.'); \`\`\` Unfortunately, \`jsonDecode()\` returns a \`dynamic\`, meaning that you do not know the types of the values until runtime. With this approach, you lose most of the statically typed language features: type safety, autocompletion and most importantly, compile-time exceptions. Your code will become instantly more error-prone. For example, whenever you access the \`name\` or \`email\` fields, you could quickly introduce a typo. A typo that the compiler doesn't know about since the JSON lives in a map structure. ### Serializing JSON inside model classes Combat the previously mentioned problems by introducing a plain model class, called \`User\` in this example. Inside the \`User\` class, you'll find: \* A \`User.fromJson()\` constructor, for constructing a new \`User\` instance from a map structure. \* A \`toJson()\` method, which converts a \`User\` instance into a map. With this approach, the \_calling code\_ can have type safety, autocompletion for the \`name\` and \`email\` fields, and compile-time exceptions. If you make typos or treat the fields as \`int\`s instead of \`String\`s, the app won't compile, instead of crashing at runtime. \*\*user.dart\*\*

<!--?code-excerpt "lib/manual/user.dart"?-->

\`\`\`dart class User { final String name; final String email; User(this.name, this.email); User.fromJson(Map json) : name = json\['name'] as String, email = json\['email'] as String; Map toJson() => {'name': name, 'email': email}; } \`\`\` The responsibility of the decoding logic is now moved inside the model itself. With this new approach, you can decode a user easily.

<!--?code-excerpt "lib/manual/main.dart (from-json)"?-->

\`\`\`dart final userMap = jsonDecode(jsonString) as Map; final user = User.fromJson(userMap); print('Howdy, ${user.name}!'); print('We sent the verification link to ${user.email}.'); \`\`\` To encode a user, pass the \`User\` object to the \`jsonEncode()\` function. You don't need to call the \`toJson()\` method, since \`jsonEncode()\` already does it for you.

<!--?code-excerpt "lib/manual/main.dart (json-encode)" skip="1"?-->

\`\`\`dart String json = jsonEncode(user); \`\`\` With this approach, the calling code doesn't have to worry about JSON serialization at all. However, the model class still definitely has to. In a production app, you would want to ensure that the serialization works properly. In practice, the \`User.fromJson()\` and \`User.toJson()\` methods both need to have unit tests in place to verify correct behavior. :::note The cookbook contains \[a more comprehensive worked example of using JSON model classes]\[json background parsing], using an isolate to parse the JSON file on a background thread. This approach is ideal if you need your app to remain responsive while the JSON file is being decoded. ::: However, real-world scenarios are not always that simple. Sometimes JSON API responses are more complex, for example since they contain nested JSON objects that must be parsed through their own model class. It would be nice if there were something that handled the JSON encoding and decoding for you. Luckily, there is! []()## Serializing JSON using code generation libraries Although there are other libraries available, this guide uses \[\`json\_serializable\`]\[], an automated source code generator that generates the JSON serialization boilerplate for you. :::note Choosing a library You might have noticed two \[Flutter Favorite]\[] packages on pub.dev that generate JSON serialization code, \[\`json\_serializable\`]\[] and \[\`built\_value\`]\[]. How do you choose between these packages? The \`json\_serializable\` package allows you to make regular classes serializable by using annotations, whereas the \`built\_value\` package provides a higher-level way of defining immutable value classes that can also be serialized to JSON. ::: Since the serialization code is not handwritten or maintained manually anymore, you minimize the risk of having JSON serialization exceptions at runtime. ### Setting up json\_serializable in a project To include \`json\_serializable\` in your project, you need one regular dependency, and two \_dev dependencies\_. In short, \_dev dependencies\_ are dependencies that are not included in our app source code—they are only used in the development environment. To add the dependencies, run \`flutter pub add\`: \`\`\`console $ flutter pub add json\_annotation dev:build\_runner dev:json\_serializable \`\`\` Run \`flutter pub get\` inside your project root folder (or click \*\*Packages get\*\* in your editor) to make these new dependencies available in your project. ### Creating model classes the json\_serializable way The following shows how to convert the \`User\` class to a \`json\_serializable\` class. For the sake of simplicity, this code uses the simplified JSON model from the previous samples. \*\*user.dart\*\*

<!--?code-excerpt "lib/serializable/user.dart"?-->

\`\`\`dart import 'package:json\_annotation/json\_annotation.dart'; /// This allows the \`User\` class to access private members in /// the generated file. The value for this is \*.g.dart, where /// the star denotes the source file name. part 'user.g.dart'; /// An annotation for the code generator to know that this class needs the /// JSON serialization logic to be generated. @JsonSerializable() class User { User(this.name, this.email); String name; String email; /// A necessary factory constructor for creating a new User instance /// from a map. Pass the map to the generated \`\_$UserFromJson()\` constructor. /// The constructor is named after the source class, in this case, User. factory User.fromJson(Map json) => \_$UserFromJson(json); /// \`toJson\` is the convention for a class to declare support for serialization /// to JSON. The implementation simply calls the private, generated /// helper method \`\_$UserToJson\`. Map toJson() => \_$UserToJson(this); } \`\`\` With this setup, the source code generator generates code for encoding and decoding the \`name\` and \`email\` fields from JSON. If needed, it is also easy to customize the naming strategy. For example, if the API returns objects with \_snake\\\_case\_, and you want to use \_lowerCamelCase\_ in your models, you can use the \`@JsonKey\` annotation with a name parameter: \`\`\`dart /// Tell json\_serializable that "registration\_date\_millis" should be /// mapped to this property. @JsonKey(name: 'registration\_date\_millis') final int registrationDateMillis; \`\`\` It's best if both server and client follow the same naming strategy. \`@JsonSerializable()\` provides \`fieldRename\` enum for totally converting dart fields into JSON keys. Modifying \`@JsonSerializable(fieldRename: FieldRename.snake)\` is equivalent to adding \`@JsonKey(name: '')\` to each field. Sometimes server data is uncertain, so it is necessary to verify and protect data on client. Other commonly used \`@JsonKey\` annotations include: \`\`\`dart /// Tell json\_serializable to use "defaultValue" if the JSON doesn't /// contain this key or if the value is \`null\`. @JsonKey(defaultValue: false) final bool isAdult; /// When \`true\` tell json\_serializable that JSON must contain the key, /// If the key doesn't exist, an exception is thrown. @JsonKey(required: true) final String id; /// When \`true\` tell json\_serializable that generated code should /// ignore this field completely. @JsonKey(ignore: true) final String verificationCode; \`\`\` ### Running the code generation utility When creating \`json\_serializable\` classes the first time, you'll get errors similar to the following: \`\`\`plaintext Target of URI hasn't been generated: 'user.g.dart'. \`\`\` These errors are entirely normal and are simply because the generated code for the model class does not exist yet. To resolve this, run the code generator that generates the serialization boilerplate. There are two ways of running the code generator. #### One-time code generation By running \`dart run build\_runner build --delete-conflicting-outputs\` in the project root, you generate JSON serialization code for your models whenever they are needed. This triggers a one-time build that goes through the source files, picks the relevant ones, and generates the necessary serialization code for them. While this is convenient, it would be nice if you did not have to run the build manually every time you make changes in your model classes. #### Generating code continuously A \_watcher\_ makes our source code generation process more convenient. It watches changes in our project files and automatically builds the necessary files when needed. Start the watcher by running \`dart run build\_runner watch --delete-conflicting-outputs\` in the project root. It is safe to start the watcher once and leave it running in the background. ### Consuming json\_serializable models To decode a JSON string the \`json\_serializable\` way, you do not have actually to make any changes to our previous code.

<!--?code-excerpt "lib/serializable/main.dart (from-json)"?-->

\`\`\`dart final userMap = jsonDecode(jsonString) as Map; final user = User.fromJson(userMap); \`\`\` The same goes for encoding. The calling API is the same as before.

<!--?code-excerpt "lib/serializable/main.dart (json-encode)" skip="1"?-->

\`\`\`dart String json = jsonEncode(user); \`\`\` With \`json\_serializable\`, you can forget any manual JSON serialization in the \`User\` class. The source code generator creates a file called \`user.g.dart\`, that has all the necessary serialization logic. You no longer have to write automated tests to ensure that the serialization works—it's now \_the library's responsibility\_ to make sure the serialization works appropriately. ## Generating code for nested classes You might have code that has nested classes within a class. If that is the case, and you have tried to pass the class in JSON format as an argument to a service (such as Firebase, for example), you might have experienced an \`Invalid argument\` error. Consider the following \`Address\` class:

<!--?code-excerpt "lib/nested/address.dart"?-->

\`\`\`dart import 'package:json\_annotation/json\_annotation.dart'; part 'address.g.dart'; @JsonSerializable() class Address { String street; String city; Address(this.street, this.city); factory Address.fromJson(Map json) => \_$AddressFromJson(json); Map toJson() => \_$AddressToJson(this); } \`\`\` The \`Address\` class is nested inside the \`User\` class:

<!--?code-excerpt "lib/nested/user.dart" replace="/explicitToJson: true//g"?-->

\`\`\`dart import 'package:json\_annotation/json\_annotation.dart'; import 'address.dart'; part 'user.g.dart'; @JsonSerializable() class User { User(this.name, this.address); String name; Address address; factory User.fromJson(Map json) => \_$UserFromJson(json); Map toJson() => \_$UserToJson(this); } \`\`\` Running \`dart run build\_runner build --delete-conflicting-outputs\` in the terminal creates the \`\*.g.dart\` file, but the private \`\_$UserToJson()\` function looks something like the following: \`\`\`dart Map \_$UserToJson(User instance) => { 'name': instance.name, 'address': instance.address, }; \`\`\` All looks fine now, but if you do a print() on the user object:

<!--?code-excerpt "lib/nested/main.dart (print)"?-->

\`\`\`dart Address address = Address('My st.', 'New York'); User user = User('John', address); print(user.toJson()); \`\`\` The result is: \`\`\`json {name: John, address: Instance of 'address'} \`\`\` When what you probably want is output like the following: \`\`\`json {name: John, address: {street: My st., city: New York}} \`\`\` To make this work, pass \`explicitToJson: true\` in the \`@JsonSerializable()\` annotation over the class declaration. The \`User\` class now looks as follows:

<!--?code-excerpt "lib/nested/user.dart"?-->

\`\`\`dart import 'package:json\_annotation/json\_annotation.dart'; import 'address.dart'; part 'user.g.dart'; @JsonSerializable(explicitToJson: true) class User { User(this.name, this.address); String name; Address address; factory User.fromJson(Map json) => \_$UserFromJson(json); Map toJson() => \_$UserToJson(this); } \`\`\` For more information, see \[\`explicitToJson\`]\[] in the \[\`JsonSerializable\`]\[] class for the \[\`json\_annotation\`]\[] package. ## Further references For more information, see the following resources: \* The \[\`dart:convert\`]\[] and \[\`JsonCodec\`]\[] documentation \* The \[\`json\_serializable\`]\[] package on pub.dev \* The \[\`json\_serializable\` examples]\[] on GitHub \* The \[Dive into Dart's patterns and records]\[] codelab \* This ultimate guide about \[how to parse JSON in Dart/Flutter]\[] \[\`built\_value\`]: https\://pub.dev/packages/built\_value \[code generation libraries]: #code-generation \[\`dart:convert\`]: https\://api.dart.dev/dart-convert \[\`explicitToJson\`]: https\://pub.dev/documentation/json\_annotation/latest/json\_annotation/JsonSerializable/explicitToJson.html \[Flutter Favorite]: /packages-and-plugins/favorites \[json background parsing]: /cookbook/networking/background-parsing \[\`JsonCodec\`]: https\://api.dart.dev/dart-convert/JsonCodec-class.html \[\`JsonSerializable\`]: https\://pub.dev/documentation/json\_annotation/latest/json\_annotation/JsonSerializable-class.html \[\`json\_annotation\`]: https\://pub.dev/packages/json\_annotation \[\`json\_serializable\`]: https\://pub.dev/packages/json\_serializable \[\`json\_serializable\` examples]: https\://github.com/google/json\_serializable.dart/blob/master/example/lib/example.dart \[pubspec file]: https\://raw\.githubusercontent.com/google/json\_serializable.dart/master/example/pubspec.yaml \[reflection]: https\://en.wikipedia.org/wiki/Reflection\_(computer\_programming) \[Serializing JSON manually using dart:convert]: #manual-encoding \[Serializing JSON using code generation libraries]: #code-generation \[tree shaking]: https\://en.wikipedia.org/wiki/Tree\_shaking \[Dive into Dart's patterns and records]: https\://codelabs.developers.google.com/codelabs/dart-patterns-records \[how to parse JSON in Dart/Flutter]: https\://codewithandrea.com/articles/parse-json-dart/

----
url: https://docs.flutter.dev/app-architecture/recommendations.md
----

\# Architecture recommendations and resources > Recommendations for building scalable Flutter applications. This page presents architecture best practices, why they matter, and whether we recommend them for your Flutter application. You should treat these recommendations as recommendations, and not steadfast rules, and you should adapt them to your app's unique requirements. The best practices on this page have a priority, which reflects how strongly the Flutter team recommends it. \* \*\*Strongly recommend:\*\* You should always implement this recommendation if you're starting to build a new application. You should strongly consider refactoring an existing app to implement this practice unless doing so would fundamentally clash with your current approach. \* \*\*Recommend\*\*: This practice will likely improve your app. \* \*\*Conditional\*\*: This practice can improve your app in certain circumstances. ## Separation of concerns You should separate your app into a UI layer and a data layer. Within those layers, you should further separate logic into classes by responsibility. ## Handling data Handling data with care makes your code easier to understand, less error prone, and prevents malformed or unexpected data from being created. ## App structure Well organized code benefits both the health of the app itself, and the team working on the code. ## Testing Good testing practices makes your app flexible. It also makes it straightforward and low risk to add new logic and new UI. []()## Recommended resources {:#resources} \* Code and templates \* \[Compass app source code]\[] - Source code of a full-featured, robust Flutter application that implements many of these recommendations. \* \[very\_good\_cli]\[] - A Flutter application template made by the Flutter experts Very Good Ventures. This template generates a similar app structure. \* Documentation \* \[Very Good Engineering architecture documentation]\[] - Very Good Engineering is a documentation site by VGV that has technical articles, demos, and open-sourced projects. It includes documentation on architecting Flutter applications. \* Tooling \* \[Flutter developer tools]\[] - DevTools is a suite of performance and debugging tools for Dart and Flutter. \* \[flutter\_lints]\[] - A package that contains the lints for Flutter apps recommended by the Flutter team. Use this package to encourage good coding practices across a team. \[Compass app source code]: https\://github.com/flutter/samples/tree/main/compass\_app \[very\_good\_cli]: https\://cli.vgv.dev/ \[Very Good Engineering architecture documentation]: https\://engineering.verygood.ventures/architecture/architecture/ \[Flutter developer tools]: /tools/devtools \[flutter\_lints]: https\://pub.dev/packages/flutter\_lints ## Feedback As this section of the website is evolving, we \[welcome your feedback]\[]! \[welcome your feedback]: https\://google.qualtrics.com/jfe/form/SV\_4T0XuR9Ts29acw6?page="recommendations"

----
url: https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.15.0.md
----

\# DevTools 2.15.0 release notes > Release notes for Dart and Flutter DevTools version 2.15.0. The 2.15.0 release of the Dart and Flutter DevTools includes the following changes among other general improvements. To learn more about DevTools, check out the \[DevTools overview]\(https\://docs.flutter.dev/tools/devtools). ## General updates \* The DevTools 2.15 release includes improvements to all tables in DevTools (logging view, network profiler, CPU profiler, and so on) - \[#4175]\(https\://github.com/flutter/devtools/pull/4175) ## Performance updates \* Added outlines to each layer displayed in the Raster Metrics tool - \[#4192]\(https\://github.com/flutter/devtools/pull/4192) !\[raster-metrics-layer-outlines]\(/assets/images/docs/tools/devtools/release-notes/images-2.15.0/image1.png "raster metrics layer outlines") \* Fix a bug with loading offline data - \[#4189]\(https\://github.com/flutter/devtools/pull/4189) ## Network updates \* Added a Json viewer with syntax highlighting for network responses - \[#4167]\(https\://github.com/flutter/devtools/pull/4167) !\[network-response-json-viewer]\(/assets/images/docs/tools/devtools/release-notes/images-2.15.0/image2.png "network response json viewer") \* Added the ability to copy network responses - \[#4190]\(https\://github.com/flutter/devtools/pull/4190) ## Memory updates \* Added the ability to select a different isolate from the DevTools footer - \[#4173]\(https\://github.com/flutter/devtools/pull/4173) \* Made the automatic snapshotting feature a configurable setting - \[#4200]\(https\://github.com/flutter/devtools/pull/4200) ## CPU profiler \* Stop manually truncating source URIs in the profiler tables - \[#4166]\(https\://github.com/flutter/devtools/pull/4166) ## Full commit history To find a complete list of changes since the previous release, check out \[the diff on GitHub]\(https\://github.com/flutter/devtools/compare/v2.14.0...v2.15.0).

----
url: https://docs.flutter.dev/platform-integration/android/platform-views
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Hosting native Android views in your Flutter app with Platform Views](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

Platform views allow you to embed native views in a Flutter app, so you can apply transforms, clips, and opacity to the native view from Dart.

This allows you, for example, to use the native Google Maps from the Android SDK directly inside your Flutter app.

Platform Views on Android have several implementations. They come with tradeoffs both in terms of performance and fidelity.

### Choosing an implementation

[#](#choosing-an-implementation)

The following matrix summarizes the different implementations and their trade-offs:

| Mode                    | Benefits                                                               | Considerations                                                                                                                                                                                                                                                                                                                                                 | Enabler                                                                                                                                                                                                                                                               |
| :---------------------- | :--------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Texture layer**       | • Good Flutter performance • Full widget transforms work               | • Janky during quick scrolling • SurfaceViews lose accessibility and text magnifier breaks • Platform View -> Renders to texture -> Uploads to Impeller -> Impeller composites Flutter content and Platform View content                                                                                                                                       | Default behavior or standard `AndroidView`                                                                                                                                                                                                                            |
| **Hybrid composition**  | • Full native fidelity • Correct accessibility and SurfaceView support | • Causes thread merging of raster & platform, which degrades Flutter FPS • Platform View -> Renders into the Android view hierarchy as usual, Flutter content renders into `ImageReader` -backed `FlutterImageView` s (a background layer, plus an overlay layer for content drawn above a platform view), the Android view hierarchy composites them together | • `PlatformViewLink` with `AndroidViewSurface` • [`AndroidViewController` builds either a TLHC or an HC Platform View](https://github.com/flutter/flutter/blob/main/dev/integration_tests/hybrid_android_views/lib/android_platform_view.dart)                        |
| **HCPP** (Experimental) | • Full fidelity and performance • Solves original sync overhead        | • Requires Android API 34+, Vulkan support, and use of the Impeller rendering engine • Platform View -> Renders to native Android Surface, Impeller renders to native Android Surface, SurfaceFlinger composites the two together                                                                                                                              | • `<meta-data>` in `AndroidManifest.xml` • `--enable-hcpp` local flag • [`AndroidViewController` builds either a TLHC or an HC Platform View](https://github.com/flutter/flutter/blob/main/dev/integration_tests/hybrid_android_views/lib/android_platform_view.dart) |

## Hybrid composition

[#](#hybrid-composition)

Platform views are rendered as they normally are, directly in the Android view hierarchy. Flutter content is rendered into `ImageReader`s and painted by `FlutterImageView`s that sit in the same window as the platform views, so the Android view hierarchy composites them together.

Flutter uses two kinds of these layers. The main Flutter render surface is swapped for a background `FlutterImageView` once a platform view is added. Flutter content that draws on top of a platform view goes into an additional overlay `FlutterImageView`, allocated from a pool and recycled between frames.

## Hybrid composition++ (HCPP)

[#](#hcpp)

HCPP is the latest hybrid composition strategy, designed to solve compositing performance and synchronization issues seen in the original Hybrid Composition mode. It is currently available as an opt-in feature.

### Requirements

[#](#requirements)

* **Android API 34 or later**: Required for native transaction synchronization capabilities.
* **Impeller with the Vulkan backend**: The device must be capable of rendering with Vulkan. Impeller also has an OpenGLES backend, and falls back to it on devices without usable Vulkan support. HCPP is not available in that configuration.

If these requirements are not met on the end-user device, Flutter will automatically fall back to the existing platform view strategy configured for the app.

### Opt in

[#](#opt-in)

Because HCPP acts as a global upgrade for how platform views are backed, it's enabled through configuration rather than standard Dart initialization methods (`initAndroidView`, and so on).

You can enable HCPP using one of the following methods:

1. **Command line flag (run/test)**: Pass the `--enable-hcpp` flag to your `flutter run` or `flutter test` command:

   ```
   $ flutter run --enable-hcpp
   ```

2. **AndroidManifest.xml**: Include a `<meta-data>` tag inside the `<application>` block of your `AndroidManifest.xml`:

   xml

   ```
   <meta-data
       android:name="io.flutter.embedding.android.EnableHcpp"
       android:value="true" />
   ```

### Limitations and known issues

[#](#limitations-and-known-issues)

* **Complex overlay stacking**: Transparent platform views won't display correctly in layout stacks structured as: **Flutter canvas -> Platform View -> Overlay -> Transparent Platform View**, when all four of these layers intersect.

## Texture layer

[#](#texture-layer)

Platform Views are rendered into a texture. Flutter draws the platform views (using the texture). Flutter content is rendered directly into a Surface.

This approach provides:

* good performance for Android Views
* good performance for Flutter rendering
* all transformations work correctly

However, this approach might cause:

* jankiness on quick scrolling (such as a web view)
* broken accessibility for `SurfaceView`s
* broken text magnification unless Flutter is rendered into a `TextureView`

## On the Dart side

[#](#on-the-dart-side)

To create a platform view on Android, use the following steps. First, on the Dart side, create a `Widget` and add one of the following build implementations depending on your chosen strategy.

### Hybrid composition

[#](#hybrid-composition-1)

In your Dart file, for example `native_view_example.dart`, use the following instructions:

1. Add the following imports:

   dart

   ```
   import 'package:flutter/foundation.dart';
   import 'package:flutter/gestures.dart';
   import 'package:flutter/rendering.dart';
   import 'package:flutter/services.dart';
   import 'package:material_ui/material_ui.dart';
   ```

2. Implement a `build` method:

   dart

   ```
   Widget build(BuildContext context) {
     // This is used in the platform side to register the view.
     const String viewType = '<platform-view-type>';
     // Pass parameters to the platform side.
     const Map<String, dynamic> creationParams = <String, dynamic>{};
   ​
     return PlatformViewLink(
       viewType: viewType,
       surfaceFactory: (context, controller) {
         return AndroidViewSurface(
           controller: controller as AndroidViewController,
           gestureRecognizers: const <Factory<OneSequenceGestureRecognizer>>{},
           hitTestBehavior: PlatformViewHitTestBehavior.opaque,
         );
       },
       onCreatePlatformView: (params) {
         return PlatformViewsService.initSurfaceAndroidView(
             id: params.id,
             viewType: viewType,
             layoutDirection: TextDirection.ltr,
             creationParams: creationParams,
             creationParamsCodec: const StandardMessageCodec(),
             onFocus: () {
               params.onFocusChanged(true);
             },
           )
           ..addOnPlatformViewCreatedListener(params.onPlatformViewCreated)
           ..create();
       },
     );
   }
   ```

For more information, visit the following API docs:

* [`PlatformViewLink`](https://api.flutter.dev/flutter/widgets/PlatformViewLink-class.html)
* [`AndroidViewSurface`](https://api.flutter.dev/flutter/widgets/AndroidViewSurface-class.html)
* [`PlatformViewsService`](https://api.flutter.dev/flutter/services/PlatformViewsService-class.html)

### TextureLayerHybridComposition

[#](#texturelayerhybridcomposition)

In your Dart file, for example `native_view_example.dart`, use the following instructions:

1. Add the following imports:

   dart

   ```
   import 'package:flutter/services.dart';
   import 'package:material_ui/material_ui.dart';
   ```

2. Implement a `build` method:

   dart

   ```
   Widget build(BuildContext context) {
     // This is used in the platform side to register the view.
     const String viewType = '<platform-view-type>';
     // Pass parameters to the platform side.
     final Map<String, dynamic> creationParams = <String, dynamic>{};
   ​
     return AndroidView(
       viewType: viewType,
       layoutDirection: TextDirection.ltr,
       creationParams: creationParams,
       creationParamsCodec: const StandardMessageCodec(),
     );
   }
   ```

For more information, visit the [`AndroidView`](https://api.flutter.dev/flutter/widgets/AndroidView-class.html) API page.

## On the platform side

[#](#on-the-platform-side)

On the platform side, use the standard `io.flutter.plugin.platform` package in either Kotlin or Java:

* [Kotlin](#197-tab-panel)
* [Java](#198-tab-panel)

In your native code, implement the following:

Extend `io.flutter.plugin.platform.PlatformView` to provide a reference to the `android.view.View` (for example, `NativeView.kt`):

kotlin

```
package dev.flutter.example
​
import android.content.Context
import android.graphics.Color
import android.view.View
import android.widget.TextView
import io.flutter.plugin.platform.PlatformView
​
internal class NativeView(context: Context, id: Int, creationParams: Map<String?, Any?>?) : PlatformView {
    private val textView: TextView
​
    override fun getView(): View {
        return textView
    }
​
    override fun dispose() {}
​
    init {
        textView = TextView(context)
        textView.textSize = 72f
        textView.setBackgroundColor(Color.rgb(255, 255, 255))
        textView.text = "Rendered on a native Android view (id: $id)"
    }
}
```

Create a factory class that creates an instance of the `NativeView` created earlier (for example, `NativeViewFactory.kt`):

kotlin

```
package dev.flutter.example
​
import android.content.Context
import io.flutter.plugin.common.StandardMessageCodec
import io.flutter.plugin.platform.PlatformView
import io.flutter.plugin.platform.PlatformViewFactory
​
class NativeViewFactory : PlatformViewFactory(StandardMessageCodec.INSTANCE) {
    override fun create(context: Context, viewId: Int, args: Any?): PlatformView {
        val creationParams = args as Map<String?, Any?>?
        return NativeView(context, viewId, creationParams)
    }
}
```

Finally, register the platform view. You can do this in an app or a plugin.

For app registration, modify the app's main activity (for example, `MainActivity.kt`):

kotlin

```
package dev.flutter.example
​
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
​
class MainActivity : FlutterActivity() {
    override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
        super.configureFlutterEngine(flutterEngine)
        flutterEngine
                .platformViewsController
                .registry
                .registerViewFactory("<platform-view-type>",
                                      NativeViewFactory())
    }
}
```

For plugin registration, modify the plugin's main class (for example, `PlatformViewPlugin.kt`):

kotlin

```
package dev.flutter.plugin.example
​
import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.embedding.engine.plugins.FlutterPlugin.FlutterPluginBinding
​
class PlatformViewPlugin : FlutterPlugin {
    override fun onAttachedToEngine(binding: FlutterPluginBinding) {
        binding
                .platformViewRegistry
                .registerViewFactory("<platform-view-type>", NativeViewFactory())
    }
​
    override fun onDetachedFromEngine(binding: FlutterPluginBinding) {}
}
```

In your native code, implement the following:

Extend `io.flutter.plugin.platform.PlatformView` to provide a reference to the `android.view.View` (for example, `NativeView.java`):

java

```
package dev.flutter.example;
​
import android.content.Context;
import android.graphics.Color;
import android.view.View;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import io.flutter.plugin.platform.PlatformView;
import java.util.Map;
​
class NativeView implements PlatformView {
   @NonNull private final TextView textView;
​
    NativeView(@NonNull Context context, int id, @Nullable Map<String, Object> creationParams) {
        textView = new TextView(context);
        textView.setTextSize(72);
        textView.setBackgroundColor(Color.rgb(255, 255, 255));
        textView.setText("Rendered on a native Android view (id: " + id + ")");
    }
​
    @NonNull
    @Override
    public View getView() {
        return textView;
    }
​
    @Override
    public void dispose() {}
}
```

Create a factory class that creates an instance of the `NativeView` created earlier (for example, `NativeViewFactory.java`):

java

```
package dev.flutter.example;
​
import android.content.Context;
import androidx.annotation.Nullable;
import androidx.annotation.NonNull;
import io.flutter.plugin.common.StandardMessageCodec;
import io.flutter.plugin.platform.PlatformView;
import io.flutter.plugin.platform.PlatformViewFactory;
import java.util.Map;
​
class NativeViewFactory extends PlatformViewFactory {
​
  NativeViewFactory() {
    super(StandardMessageCodec.INSTANCE);
  }
​
  @NonNull
  @Override
  public PlatformView create(@NonNull Context context, int id, @Nullable Object args) {
    final Map<String, Object> creationParams = (Map<String, Object>) args;
    return new NativeView(context, id, creationParams);
  }
}
```

Finally, register the platform view. You can do this in an app or a plugin.

For app registration, modify the app's main activity (for example, `MainActivity.java`):

java

```
package dev.flutter.example;
​
import androidx.annotation.NonNull;
import io.flutter.embedding.android.FlutterActivity;
import io.flutter.embedding.engine.FlutterEngine;
​
public class MainActivity extends FlutterActivity {
    @Override
    public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) {
        flutterEngine
            .getPlatformViewsController()
            .getRegistry()
            .registerViewFactory("<platform-view-type>", new NativeViewFactory());
    }
}
```

For plugin registration, modify the plugin's main file (for example, `PlatformViewPlugin.java`):

java

```
package dev.flutter.plugin.example;
​
import androidx.annotation.NonNull;
import io.flutter.embedding.engine.plugins.FlutterPlugin;
​
public class PlatformViewPlugin implements FlutterPlugin {
  @Override
  public void onAttachedToEngine(@NonNull FlutterPluginBinding binding) {
    binding
        .getPlatformViewRegistry()
        .registerViewFactory("<platform-view-type>", new NativeViewFactory());
  }
​
  @Override
  public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) {}
}
```

For more information, visit the API docs for:

* [`FlutterPlugin`](https://api.flutter.dev/javadoc/io/flutter/embedding/engine/plugins/FlutterPlugin.html)
* [`PlatformViewRegistry`](https://api.flutter.dev/javadoc/io/flutter/plugin/platform/PlatformViewRegistry.html)
* [`PlatformViewFactory`](https://api.flutter.dev/javadoc/io/flutter/plugin/platform/PlatformViewFactory.html)
* [`PlatformView`](https://api.flutter.dev/javadoc/io/flutter/plugin/platform/PlatformView.html)

### Manual view invalidation

[#](#manual-view-invalidation)

Certain Android Views don't invalidate themselves when their content changes. Some examples include `SurfaceView` and `SurfaceTexture`. When your Platform View includes these views, you must manually invalidate it after it has been drawn (or, more specifically, after the swap chain is flipped). Invalidate the view by calling `invalidate` on it or on one of its parents.

### Issues

[#](#issues)

Check out the [existing Platform View issues](https://github.com/flutter/flutter/issues?q=is%3Aopen+is%3Aissue+label%3A%22a%3A+platform-views) on GitHub.

## Performance

[#](#performance)

Platform views in Flutter come with performance trade-offs.

In a typical Flutter app, Flutter rasterizes frames on a dedicated raster thread, while platform code, such as plugins and Android views, runs on the platform thread, which is the Android main thread. This separation keeps Flutter rendering fast and fluid, as the platform thread is not blocked by rasterization work.

However, when a platform view is rendered on Android using hybrid composition, Flutter merges the raster thread into the platform thread to ensure correct synchronization between the native Android views and the Flutter canvas. Because of this thread merging, rasterizing complex Flutter widgets alongside a platform view competes with other work on the platform thread, such as OS messages and plugin interactions, potentially causing lower application FPS and frame drops.

Also, prior to Android 10, hybrid composition copied each Flutter frame out of the graphic memory into main memory, and then copied it back to a GPU texture. As this copy happens per frame, the performance of the entire Flutter UI might be impacted. In Android 10 or above, the graphics memory is copied only once.

Hybrid Composition++ (HCPP) minimizes this overhead by using native transaction synchronization on supported devices (Android API 34+ with Vulkan), allowing superior performance without the heavy costs of original hybrid composition.

Virtual display, on the other hand, makes each pixel of the native view flow through additional intermediate graphic buffers, which cost graphic memory and drawing performance. This can cause jank during high-frequency updates like fast scrolling.

For complex cases, there are some techniques that can be used to mitigate these issues.

For example, you can use a placeholder texture while an animation is happening in Dart. In other words, if an animation is slow while a platform view is rendered, then consider taking a screenshot of the native view and rendering it as a texture.

For more information, visit the following API pages:

* [`TextureLayer`](https://api.flutter.dev/flutter/rendering/TextureLayer-class.html)
* [`TextureRegistry`](https://api.flutter.dev/javadoc/io/flutter/view/TextureRegistry.html)
* [`FlutterTextureRegistry`](https://api.flutter.dev/ios-embedder/protocol_flutter_texture_registry-p.html)
* [`FlutterImageView`](https://api.flutter.dev/javadoc/io/flutter/embedding/android/FlutterImageView.html)

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-09-29. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/platform-integration/android/platform-views.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/platform-integration/android/platform-views\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/platform-integration/android/platform-views.md "Report an issue with this page").

----
url: https://docs.flutter.dev/deployment/flavors-windows
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Set up Flutter flavors for Windows](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

This guide shows you how to create Flutter flavors for Windows desktop apps.

## Overview

[#](#overview)

A Flutter flavor represents a collection of settings that define how a specific version of your app builds and runs. For example, a flavor can determine the window title, application icon, API endpoint, asset set, and logging configuration for a build.

On Windows, Flutter uses [CMake](https://cmake.org/) to configure and build the native desktop runner. When you run `flutter run` or `flutter build` with the `--flavor` flag, Flutter writes the flavor name into `windows/flutter/ephemeral/generated_config.cmake` as the `FLUTTER_APP_FLAVOR` variable, and isolates the build outputs in a flavor-specific directory.

The following table illustrates the build directories that Flutter creates when a project defines two flavors (`staging`, `production`) and two build modes (`debug`, `release`):

| Flavor     | Build mode | Output directory                                  |
| ---------- | ---------- | ------------------------------------------------- |
| staging    | debug      | `build/windows/<arch>/staging/runner/Debug/`      |
| production | debug      | `build/windows/<arch>/production/runner/Debug/`   |
| staging    | release    | `build/windows/<arch>/staging/runner/Release/`    |
| production | release    | `build/windows/<arch>/production/runner/Release/` |

## Create and run flavors

[#](#create-and-run-flavors)

Passing `--flavor` works on Windows without any CMake configuration. Existing Flutter projects support flavors with no template updates or configuration changes required. When you pass `--flavor`, Flutter isolates the build directory, populates `appFlavor`, and filters flavor-specific assets automatically.

To try flavors with a sample project, complete the following steps:

1. Create a new Flutter project called `flavors_example`:

   ```
   $ flutter create flavors_example
   $ cd flavors_example
   ```

2. Verify that your flavors run correctly:

   Run the `staging` flavor:

   ```
   $ flutter run -d windows --flavor staging
   ```

   Run the `production` flavor:

   ```
   $ flutter run -d windows --flavor production
   ```

To customize native runner settings for each flavor—such as the window title or application icon—refer to [Customize configurations](#customize-configurations).

## Launch a flavor

[#](#launch-a-flavor)

After you define flavors for your app, run, build, or test a specific flavor using the `--flavor` flag with the Flutter CLI.

### Run a flavor in debug mode

[#](#run-a-flavor)

To run a specific flavor during development, pass the `--flavor` option to `flutter run`:

```
$ flutter run -d windows --flavor <flavor_name>
```

Replace `<flavor_name>` with the name of your flavor (for example, `staging` or `production`).

### Build a release binary

[#](#build-a-flavor)

To build a release executable for a specific flavor, pass the `--flavor` option to `flutter build`:

```
$ flutter build windows --flavor <flavor_name>
```

Flutter outputs the compiled executable to `build/windows/<arch>/<flavor_name>/runner/Release/`.

### Run tests with a flavor

[#](#test-a-flavor)

The `--flavor` option also works with `flutter test` and `flutter drive`, allowing you to run unit, widget, or integration tests against a specific flavor configuration:

```
$ flutter test --flavor <flavor_name>
```

   ```
   import 'package:flutter/services.dart';
   ```

2. **Read the flavor value:**

   Use the `appFlavor` constant in your application logic (often in `main()`) to handle flavor-specific configuration:

   dart

   ```
   void main() {
     if (appFlavor == 'production') {
       Config.apiUrl = 'https://api.example.com';
     } else if (appFlavor == 'staging') {
       Config.apiUrl = 'https://staging.api.example.com';
     }
   ​
     runApp(const MyApp());
   }
   ```

## Customize configurations

[#](#customize-configurations)

To differentiate the native Windows runner per flavor—such as giving each flavor a unique window title or application icon—configure CMake to generate the runner source and resource files using `configure_file`.

### Configure CMake for native settings

[#](#configure-cmake-native)

CMake evaluates `windows/runner/CMakeLists.txt` after `FLUTTER_APP_FLAVOR` is defined. You can inspect this variable to configure flavor-specific values, then use `configure_file` to substitute them into template files for the C++ runner (`main.cpp`) and the Windows resource script (`Runner.rc`).

In `windows/runner/CMakeLists.txt`, add the following block before the `add_executable(${BINARY_NAME} ...)` line:

windows/runner/CMakeLists.txt

cmake

```
# Configure flavor-specific settings.
if(DEFINED FLUTTER_APP_FLAVOR)
  if(FLUTTER_APP_FLAVOR STREQUAL "staging")
    set(RUNNER_APP_ICON "staging.ico")
    set(WINDOW_TITLE "Staging App")
  elseif(FLUTTER_APP_FLAVOR STREQUAL "production")
    set(RUNNER_APP_ICON "production.ico")
    set(WINDOW_TITLE "Production App")
  endif()
else()
  set(RUNNER_APP_ICON "app_icon.ico")
  set(WINDOW_TITLE "${BINARY_NAME}")
endif()

configure_file(
  "${CMAKE_CURRENT_SOURCE_DIR}/main.cpp.in"
  "${CMAKE_CURRENT_SOURCE_DIR}/main.cpp"
  @ONLY
)

configure_file(
  "${CMAKE_CURRENT_SOURCE_DIR}/Runner.rc.in"
  "${CMAKE_CURRENT_SOURCE_DIR}/Runner.rc"
  @ONLY
)
```

### Create distinct window titles

[#](#create-distinct-window-titles)

To display a flavor-specific window title when the app launches:

1. Rename `windows/runner/main.cpp` to `windows/runner/main.cpp.in`.

2. In `windows/runner/main.cpp.in`, find where `window.Create` is called and replace the hardcoded title with `@WINDOW_TITLE@`:

   windows/runner/main.cpp.in

   cpp

   ```
     if (!window.Create(L"@WINDOW_TITLE@", origin, size)) {
       return EXIT_FAILURE;
     }
   ```

   When CMake configures the build, it replaces `@WINDOW_TITLE@` with the window title for the selected flavor.

### Create distinct app icons

[#](#create-distinct-app-icons)

To provide unique desktop icons for each flavor:

1. Prepare your icon files in `.ico` format (for example, `staging.ico` and `production.ico`) and place them in `windows/runner/resources/` alongside the default `app_icon.ico`.

2. Rename `windows/runner/Runner.rc` to `windows/runner/Runner.rc.in`.

3. In `windows/runner/Runner.rc.in`, locate the `IDI_APP_ICON` line and update the icon path to use `@RUNNER_APP_ICON@`:

   windows/runner/Runner.rc.in

   rc

   ```
   IDI_APP_ICON            ICON                    "resources\\@RUNNER_APP_ICON@"
   ```

   You can also update the metadata strings, such as `FileDescription` and `ProductName`, to use `@WINDOW_TITLE@`:

   windows/runner/Runner.rc.in

   rc

   ```
   VALUE "FileDescription", "@WINDOW_TITLE@" "\0"
   VALUE "ProductName",     "@WINDOW_TITLE@" "\0"
   ```

### Bundle assets by flavor

[#](#bundle-assets)

If you have assets that only apply to a specific flavor, configure Flutter to only bundle those assets when building that flavor. This prevents unused assets from increasing your application bundle size.

To bundle assets conditionally, add the `flavors` list to an asset entry in `pubspec.yaml`:

pubspec.yaml

yaml

```
flutter:
  assets:
    - assets/common/
    - path: assets/staging/
      flavors:
        - staging
    - path: assets/production/
      flavors:
        - production
```

To learn more, consult the [`assets` field](/tools/pubspec#assets) in [Flutter pubspec options](/tools/pubspec).

### Set a default flavor

[#](#set-default-flavor)

To specify a flavor to use when running or building without the `--flavor` flag, add the `default-flavor` property to `pubspec.yaml`:

pubspec.yaml

yaml

```
flutter:
  default-flavor: staging
```

To learn more, consult the [`default-flavor` field](/tools/pubspec#default-flavor) in [Flutter pubspec options](/tools/pubspec).

## More information

[#](#more-information)

For more information on flavors and desktop deployment, consult the following resources:

* [Set up Flutter flavors for Android](/deployment/flavors)
* [Set up Flutter flavors for iOS and macOS](/deployment/flavors-ios)
* [Set up Flutter flavors for Linux](/deployment/flavors-linux)
* [Build and release a Windows desktop app](/deployment/windows)

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-09-10. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/deployment/flavors-windows.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/deployment/flavors-windows\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/deployment/flavors-windows.md "Report an issue with this page").

----
url: https://docs.flutter.dev/cookbook/networking/update-data
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Update data over the internet](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

Updating data over the internet is necessary for most apps. The `http` package has got that covered!

This recipe uses the following steps:

1. Add the `http` package.
2. Update data over the internet using the `http` package.
3. Convert the response into a custom Dart object.
4. Get the data from the internet.
5. Update the existing `title` from user input.
6. Update and display the response on screen.

## 1. Add the `http` package

[#](#1-add-the-http-package)

To add the `http` package as a dependency, run `flutter pub add`:

```
$ flutter pub add http
```

Import the `http` package.

dart

```
import 'package:http/http.dart' as http;
import 'package:material_ui/material_ui.dart';
```

If you are deploying to Android, edit your `AndroidManifest.xml` file to add the Internet permission.

xml

```
<!-- Required to fetch data from the internet. -->
<uses-permission android:name="android.permission.INTERNET" />
```

Likewise, if you are deploying to macOS, edit your `macos/Runner/DebugProfile.entitlements` and `macos/Runner/Release.entitlements` files to include the network client entitlement.

xml

```
<!-- Required to fetch data from the internet. -->
<key>com.apple.security.network.client</key>
<true/>
```

## 2. Updating data over the internet using the `http` package

[#](#2-updating-data-over-the-internet-using-the-http-package)

This recipe covers how to update an album title to the [JSONPlaceholder](https://jsonplaceholder.typicode.com/) using the [`http.put()`](https://pub.dev/documentation/http/latest/http/put.html) method.

dart

```
Future<http.Response> updateAlbum(String title) {
  return http.put(
    Uri.parse('https://jsonplaceholder.typicode.com/albums/1'),
    headers: <String, String>{
      'Content-Type': 'application/json; charset=UTF-8',
    },
    body: jsonEncode(<String, String>{'title': title}),
  );
}
```

The `http.put()` method returns a `Future` that contains a `Response`.

* [`Future`](https://api.flutter.dev/flutter/dart-async/Future-class.html) is a core Dart class for working with async operations. A `Future` object represents a potential value or error that will be available at some time in the future.
* The `http.Response` class contains the data received from a successful http call.
* The `updateAlbum()` method takes an argument, `title`, which is sent to the server to update the `Album`.

```
class Album {
  final int id;
  final String title;
​
  const Album({required this.id, required this.title});
​
  factory Album.fromJson(Map<String, dynamic> json) {
    return switch (json) {
      {'id': int id, 'title': String title} => Album(id: id, title: title),
      _ => throw const FormatException('Failed to load album.'),
    };
  }
}
```

### Convert the `http.Response` to an `Album`

[#](#convert-the-http-response-to-an-album)

Now, use the following steps to update the `updateAlbum()` function to return a `Future<Album>`:

1. Convert the response body into a JSON `Map` with the `dart:convert` package.
2. If the server returns an `UPDATED` response with a status code of 200, then convert the JSON `Map` into an `Album` using the `fromJson()` factory method.
3. If the server doesn't return an `UPDATED` response with a status code of 200, then throw an exception. (Even in the case of a "404 Not Found" server response, throw an exception. Do not return `null`. This is important when examining the data in `snapshot`, as shown below.)

dart

```
Future<Album> updateAlbum(String title) async {
  final response = await http.put(
    Uri.parse('https://jsonplaceholder.typicode.com/albums/1'),
    headers: <String, String>{
      'Content-Type': 'application/json; charset=UTF-8',
    },
    body: jsonEncode(<String, String>{'title': title}),
  );
​
  if (response.statusCode == 200) {
    // If the server did return a 200 OK response,
    // then parse the JSON.
    return Album.fromJson(jsonDecode(response.body) as Map<String, dynamic>);
  } else {
    // If the server did not return a 200 OK response,
    // then throw an exception.
    throw Exception('Failed to update album.');
  }
}
```

Hooray! Now you've got a function that updates the title of an album.

### Get the data from the internet

[#](#get-the-data-from-the-internet)

Get the data from internet before you can update it. For a complete example, see the [Fetch data](/cookbook/networking/fetch-data) recipe.

dart

```
Future<Album> fetchAlbum() async {
  final response = await http.get(
    Uri.parse('https://jsonplaceholder.typicode.com/albums/1'),
  );
​
  if (response.statusCode == 200) {
    // If the server did return a 200 OK response,
    // then parse the JSON.
    return Album.fromJson(jsonDecode(response.body) as Map<String, dynamic>);
  } else {
    // If the server did not return a 200 OK response,
    // then throw an exception.
    throw Exception('Failed to load album');
  }
}
```

Ideally, you will use this method to set `_futureAlbum` during `initState` to fetch the data from the internet.

## 4. Update the existing title from user input

[#](#4-update-the-existing-title-from-user-input)

Create a `TextField` to enter a title and a `ElevatedButton` to update the data on server. Also define a `TextEditingController` to read the user input from a `TextField`.

When the `ElevatedButton` is pressed, the `_futureAlbum` is set to the value returned by `updateAlbum()` method.

dart

```
Column(
  mainAxisAlignment: MainAxisAlignment.center,
  children: <Widget>[
    Padding(
      padding: const EdgeInsets.all(8),
      child: TextField(
        controller: _controller,
        decoration: const InputDecoration(hintText: 'Enter Title'),
      ),
    ),
    ElevatedButton(
      onPressed: () {
        setState(() {
          _futureAlbum = updateAlbum(_controller.text);
        });
      },
      child: const Text('Update Data'),
    ),
  ],
);
```

On pressing the **Update Data** button, a network request sends the data in the `TextField` to the server as a `PUT` request. The `_futureAlbum` variable is used in the next step.

## 5. Display the response on screen

[#](#5-display-the-response-on-screen)

To display the data on screen, use the [`FutureBuilder`](https://api.flutter.dev/flutter/widgets/FutureBuilder-class.html) widget. The `FutureBuilder` widget comes with Flutter and makes it easy to work with async data sources. You must provide two parameters:

1. The `Future` you want to work with. In this case, the future returned from the `updateAlbum()` function.
2. A `builder` function that tells Flutter what to render, depending on the state of the `Future`: loading, success, or error.

Note that `snapshot.hasData` only returns `true` when the snapshot contains a non-null data value. This is why the `updateAlbum` function should throw an exception even in the case of a "404 Not Found" server response. If `updateAlbum` returns `null` then `CircularProgressIndicator` will display indefinitely.

dart

```
FutureBuilder<Album>(
  future: _futureAlbum,
  builder: (context, snapshot) {
    if (snapshot.hasData) {
      return Text(snapshot.data!.title);
    } else if (snapshot.hasError) {
      return Text('${snapshot.error}');
    }
​
    return const CircularProgressIndicator();
  },
);
```

## Complete example

[#](#complete-example)

dart

```
import 'dart:async';
import 'dart:convert';
​
import 'package:http/http.dart' as http;
import 'package:material_ui/material_ui.dart';
​
Future<Album> fetchAlbum() async {
  final response = await http.get(
    Uri.parse('https://jsonplaceholder.typicode.com/albums/1'),
  );
​
  if (response.statusCode == 200) {
    // If the server did return a 200 OK response,
    // then parse the JSON.
    return Album.fromJson(jsonDecode(response.body) as Map<String, dynamic>);
  } else {
    // If the server did not return a 200 OK response,
    // then throw an exception.
    throw Exception('Failed to load album');
  }
}
​
Future<Album> updateAlbum(String title) async {
  final response = await http.put(
    Uri.parse('https://jsonplaceholder.typicode.com/albums/1'),
    headers: <String, String>{
      'Content-Type': 'application/json; charset=UTF-8',
    },
    body: jsonEncode(<String, String>{'title': title}),
  );
​
  if (response.statusCode == 200) {
    // If the server did return a 200 OK response,
    // then parse the JSON.
    return Album.fromJson(jsonDecode(response.body) as Map<String, dynamic>);
  } else {
    // If the server did not return a 200 OK response,
    // then throw an exception.
    throw Exception('Failed to update album.');
  }
}
​
class Album {
  final int id;
  final String title;
​
  const Album({required this.id, required this.title});
​
  factory Album.fromJson(Map<String, dynamic> json) {
    return switch (json) {
      {'id': int id, 'title': String title} => Album(id: id, title: title),
      _ => throw const FormatException('Failed to load album.'),
    };
  }
}
​
void main() {
  runApp(const MyApp());
}
​
class MyApp extends StatefulWidget {
  const MyApp({super.key});
​
  @override
  State<MyApp> createState() {
    return _MyAppState();
  }
}
​
class _MyAppState extends State<MyApp> {
  final TextEditingController _controller = TextEditingController();
  late Future<Album> _futureAlbum;
​
  @override
  void initState() {
    super.initState();
    _futureAlbum = fetchAlbum();
  }
​
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Update Data Example',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
      ),
      home: Scaffold(
        appBar: AppBar(title: const Text('Update Data Example')),
        body: Container(
          alignment: Alignment.center,
          padding: const EdgeInsets.all(8),
          child: FutureBuilder<Album>(
            future: _futureAlbum,
            builder: (context, snapshot) {
              if (snapshot.connectionState == ConnectionState.done) {
                if (snapshot.hasData) {
                  return Column(
                    mainAxisAlignment: MainAxisAlignment.center,
                    children: <Widget>[
                      Text(snapshot.data!.title),
                      TextField(
                        controller: _controller,
                        decoration: const InputDecoration(
                          hintText: 'Enter Title',
                        ),
                      ),
                      ElevatedButton(
                        onPressed: () {
                          setState(() {
                            _futureAlbum = updateAlbum(_controller.text);
                          });
                        },
                        child: const Text('Update Data'),
                      ),
                    ],
                  );
                } else if (snapshot.hasError) {
                  return Text('${snapshot.error}');
                }
              }
​
              return const CircularProgressIndicator();
            },
          ),
        ),
      ),
    );
  }
}
```

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-09-29. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/networking/update-data.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/cookbook/networking/update-data\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/networking/update-data.md "Report an issue with this page").

----
url: https://docs.flutter.dev/data-and-backend
----

[Google APIs](/data-and-backend/google-apis)

[How to use Google APIs with Flutter.](/data-and-backend/google-apis)

[Serialization](/data-and-backend/serialization)

[Content covering serialization in Flutter apps.](/data-and-backend/serialization)

[Networking](/data-and-backend/networking)

[Internet network calls in Flutter.](/data-and-backend/networking)

[Persistence](/data-and-backend/persistence)

[Content covering persistence in Flutter apps.](/data-and-backend/persistence)

[State management](/data-and-backend/state-mgmt)

[Content covering state management in Flutter apps.](/data-and-backend/state-mgmt)

[Firebase](/data-and-backend/firebase)

[How to use Firebase and Firestore with Flutter.](/data-and-backend/firebase)

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/data-and-backend/index.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/data-and-backend\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/data-and-backend/index.md "Report an issue with this page").

----
url: https://docs.flutter.dev/perf/deferred-components.md
----

\# Deferred components for Android and web > How to create deferred components for improved download performance.

<!--?code-excerpt path-base="perf/deferred_components"?-->

\## Introduction With Flutter, Android and web apps have the capability to download deferred components (additional code and assets) while the app is already running. This is helpful if you have a large app and only want to install components if and when they are needed by the user. While Flutter supports deferred loading on Android and the web, the implementations differ. Both require \[Dart's deferred imports]\[dart-def-import]. \* Android's \[dynamic feature modules]\[] deliver the deferred components packaged as Android modules. When building for Android, though you can defer loading modules, you must build the entire app and upload that app as a single \[Android App Bundle]\[android-app-bundle] (AAB). Flutter doesn't support dispatching partial updates without re-uploading new Android App Bundles for the entire application. Flutter performs deferred loading when you compile your Android app in \[release or profile mode]\[], but debug mode treats all deferred components as regular imports. \* The web creates deferred components as separate \`\*.js\` files. For a deeper dive into the technical details of how this feature works, see \[Deferred Components]\[] on the \[Flutter wiki]\[]. ## How to set your Android project up for deferred components The following instructions explain how to set up your Android app for deferred loading. ### Step 1: Dependencies and initial project setup

1. Add Play Core to the Android app's build.gradle dependencies. In \`android/app/build.gradle\` add the following: \`\`\`kotlin title="android/app/build.gradle.kts" ... dependencies { ... implementation("com.google.android.play:core:1.8.0") ... } \`\`\` \`\`\`groovy title="android/app/build.gradle" ... dependencies { ... implementation "com.google.android.play:core:1.8.0" ... } \`\`\`

2. If using the Google Play Store as the distribution model for dynamic features, the app must support \`SplitCompat\` and provide an instance of a \`PlayStoreDeferredComponentManager\`. Both of these tasks can be accomplished by setting the \`android:name\` property on the application in \`android/app/src/main/AndroidManifest.xml\` to \`io.flutter.embedding.android.FlutterPlayStoreSplitApplication\`: \`\`\`xml \`\`\` \`io.flutter.app.FlutterPlayStoreSplitApplication\` handles both of these tasks for you. If you use \`FlutterPlayStoreSplitApplication\`, you can skip to step 1.3. If your Android application is large or complex, you might want to separately support \`SplitCompat\` and provide the \`PlayStoreDynamicFeatureManager\` manually. To support \`SplitCompat\`, there are three methods (as detailed in the \[Android docs]\[]), any of which are valid:

   * Make your application class extend \`SplitCompatApplication\`: \`\`\`java public class MyApplication extends SplitCompatApplication { ... } \`\`\`
   * Call \`SplitCompat.install(this);\` in the \`attachBaseContext()\` method: \`\`\`java @Override protected void attachBaseContext(Context base) { super.attachBaseContext(base); // Emulates installation of future on demand modules using SplitCompat. SplitCompat.install(this); } \`\`\`
   * Declare \`SplitCompatApplication\` as the application subclass and add the Flutter compatibility code from \`FlutterApplication\` to your application class: \`\`\`xml \`\`\`

   The embedder relies on an injected \`DeferredComponentManager\` instance to handle install requests for deferred components. Provide a \`PlayStoreDeferredComponentManager\` into the Flutter embedder by adding the following code to your app initialization: \`\`\`java import io.flutter.embedding.engine.dynamicfeatures.PlayStoreDeferredComponentManager; import io.flutter.FlutterInjector; ... PlayStoreDeferredComponentManager deferredComponentManager = new PlayStoreDeferredComponentManager(this, null); FlutterInjector.setInstance(new FlutterInjector.Builder() .setDeferredComponentManager(deferredComponentManager).build()); \`\`\`

3. Opt into deferred components by adding the \`deferred-components\` entry to the app's \`pubspec.yaml\` under the \`flutter\` entry: \`\`\`yaml ... flutter: ... deferred-components: ... \`\`\` The \`flutter\` tool looks for the \`deferred-components\` entry in the \`pubspec.yaml\` to determine whether the app should be built as deferred or not. This can be left empty for now unless you already know the components desired and the Dart deferred libraries that go into each. You will fill in this section later in \[step 3.3]\[] once \`gen\_snapshot\` produces the loading units.

\### Step 2: Implementing deferred Dart libraries Next, implement deferred loaded Dart libraries in your app's Dart code. The implementation doesn't need to be feature complete yet. The example in the rest of this page adds a new simple deferred widget as a placeholder. You can also convert existing code to be deferred by modifying the imports and guarding usages of deferred code behind \`loadLibrary()\` \`Futures\`.

1. Create a new Dart library. For example, create a new \`DeferredBox\` widget that can be downloaded at runtime. This widget can be of any complexity but, for the purposes of this guide, create a simple box as a stand-in. To create a simple blue box widget, create \`box.dart\` with the following contents:
   <!--?code-excerpt "lib/box.dart"?-->
   \`\`\`dart title="box.dart" import 'package:material\_ui/material\_ui.dart'; /// A simple blue 30x30 box. class DeferredBox extends StatelessWidget { const DeferredBox({super.key}); @override Widget build(BuildContext context) { return Container(height: 30, width: 30, color: Colors.blue); } } \`\`\`
2. Import the new Dart library with the \`deferred\` keyword in your app and call \`loadLibrary()\` (see \[lazily loading a library]\[]). The following example uses \`FutureBuilder\` to wait for the \`loadLibrary\` \`Future\` (created in \`initState\`) to complete and display a \`CircularProgressIndicator\` as a placeholder. When the \`Future\` completes, it returns the \`DeferredBox\` widget. \`SomeWidget\` can then be used in the app as normal and won't ever attempt to access the deferred Dart code until it has successfully loaded.
   <!--?code-excerpt "lib/use_deferred_box.dart"?-->
   \`\`\`dart import 'package:material\_ui/material\_ui.dart'; import 'box.dart' deferred as box; class SomeWidget extends StatefulWidget { const SomeWidget({super.key}); @override State createState() => \_SomeWidgetState(); } class \_SomeWidgetState extends State { late Future \_libraryFuture; @override void initState() { super.initState(); \_libraryFuture = box.loadLibrary(); } @override Widget build(BuildContext context) { return FutureBuilder( future: \_libraryFuture, builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.done) { if (snapshot.hasError) { return Text('Error: ${snapshot.error}'); } return box.DeferredBox(); } return const CircularProgressIndicator(); }, ); } } \`\`\` The \`loadLibrary()\` function returns a \`Future\` that completes successfully when the code in the library is available for use and completes with an error otherwise. All usage of symbols from the deferred library should be guarded behind a completed \`loadLibrary()\` call. All imports of the library must be marked as \`deferred\` for it to be compiled appropriately to be used in a deferred component. If a component has already been loaded, additional calls to \`loadLibrary()\` complete quickly (but not synchronously). The \`loadLibrary()\` function can also be called early to trigger a pre-load to help mask the loading time. You can find another example of deferred import loading in \[Flutter Gallery's \`lib/deferred\_widget.dart\`]\[].

\### Step 3: Building the app Use the following \`flutter\` command to build a deferred components app: \`\`\`console $ flutter build appbundle \`\`\` This command assists you by validating that your project is properly set up to build deferred components apps. By default, the build fails if the validator detects any issues and guides you through suggested changes to fix them. :::note You can opt out of building deferred components with the \`--no-deferred-components\` flag. This flag causes all assets defined under deferred components to be treated as if they were defined under the assets section of \`pubspec.yaml\`. All Dart code is compiled into a single shared library and \`loadLibrary()\` calls complete in the next event loop boundary (as soon as possible while being asynchronous). This flag is also equivalent to omitting the \`deferred-components:\` entry in \`pubspec.yaml\`. :::

1. []()The \`flutter build appbundle\` command runs the validator and attempts to build the app with \`gen\_snapshot\` instructed to produce split AOT shared libraries as separate SO files. On the first run, the validator will likely fail as it detects issues; the tool makes recommendations for how to set up the project and fix these issues. The validator is split into two sections: prebuild and post-gen\_snapshot validation. This is because any validation referencing loading units can't be performed until \`gen\_snapshot\` completes and produces a final set of loading units. :::note You can opt to have the tool attempt to build your app without the validator by passing the \`--no-validate-deferred-components\` flag. This can result in unexpected and confusing instructions to resolve failures. This flag is meant to be used in custom implementations that don't rely on the default Play-store-based implementation that the validator checks for. ::: The validator detects any new, changed, or removed loading units generated by \`gen\_snapshot\`. The current generated loading units are tracked in your \`/deferred\_components\_loading\_units.yaml\` file. This file should be checked into source control to ensure that changes to the loading units by other developers can be caught. The validator also checks for the following in the \`android\` directory:

   * \*\*\`/android/app/src/main/res/values/strings.xml\`\*\*\
     An entry for every deferred component mapping the key \`${componentName}Name\` to \`${componentName}\`. This string resource is used by the \`AndroidManifest.xml\` of each feature module to define the \`dist:title property\`. For example: \`\`\`xml
     <!--?xml version="1.0" encoding="utf-8"?-->
     ... boxComponent \`\`\`
   * \*\*\`/android/\`\*\*\
     An Android dynamic feature module for each deferred component exists and contains a \`build.gradle\` and \`src/main/AndroidManifest.xml\` file. This only checks for existence and doesn't validate the contents of these files. If a file doesn't exist, it generates a default recommended one.
   * \*\*\`/android/app/src/main/res/values/AndroidManifest.xml\`\*\*\
     Contains a meta-data entry that encodes the mapping between loading units and component name the loading unit is associated with. This mapping is used by the embedder to convert Dart's internal loading unit id to the name of a deferred component to install. For example: \`\`\`xml ... ... ... \`\`\`

   The \`gen\_snapshot\` validator won't run until the prebuild validator passes.

2. or each of these checks, the tool produces the modified or new files needed to pass the check. These files are placed in the \`/build/android\_deferred\_components\_setup\_files\` directory. It is recommended that the changes be applied by copying and overwriting the same files in the project's \`android\` directory. Before overwriting, the current project state should be committed to source control and the recommended changes should be reviewed to be appropriate. The tool won't make any changes to your \`android/\` directory automatically.

3. []()Once the available loading units are generated and logged in \`/deferred\_components\_loading\_units.yaml\`, it is possible to fully configure the pubspec's \`deferred-components\` section so that the loading units are assigned to deferred components as desired. To continue with the box example, the generated \`deferred\_components\_loading\_units.yaml\` file would contain: \`\`\`yaml loading-units: - id: 2 libraries: - package:MyAppName/box.Dart \`\`\` The loading unit id ('2' in this case) is used internally by Dart, and can be ignored. The base loading unit (id '1') is not listed and contains everything not explicitly contained in another loading unit. You can now add the following to \`pubspec.yaml\`: \`\`\`yaml ... flutter: ... deferred-components: - name: boxComponent libraries: - package:MyAppName/box.Dart ... \`\`\` To assign a loading unit to a deferred component, add any Dart library in the loading unit into the libraries section of the feature module. Keep the following guidelines in mind:

   * Loading units shouldn't be included in more than one component.
   * Including one Dart library from a loading unit indicates that the entire loading unit is assigned to the deferred component.
   * All loading units not assigned to a deferred component are included in the base component, which always exists implicitly.
   * Loading units assigned to the same deferred component are downloaded, installed, and shipped together.
   * The base component is implicit and need not be defined in the pubspec.

4. Assets can also be included by adding an assets section in the deferred component configuration: \`\`\`yaml deferred-components: - name: boxComponent libraries: - package:MyAppName/box.Dart assets: - assets/image.jpg - assets/picture.png # wildcard directory - assets/gallery/ \`\`\` An asset can be included in multiple deferred components, but installing both components results in a replicated asset. Assets-only components can also be defined by omitting the libraries section. These assets-only components must be installed with the \[\`DeferredComponent\`]\[] utility class in services rather than \`loadLibrary()\`. Since Dart libraries are packaged together with assets, if a Dart library is loaded with \`loadLibrary()\`, any assets in the component are loaded as well. However, installing by component name and the services utility won't load any Dart libraries in the component. You are free to include assets in any component, as long as they are installed and loaded when they are first referenced, though typically, assets and the Dart code that uses those assets are best packed in the same component.

5. Manually add all deferred components that you defined in \`pubspec.yaml\` into the \`android/settings.gradle\` file as includes. For example, if there are three deferred components defined in the pubspec named, \`boxComponent\`, \`circleComponent\`, and \`assetComponent\`, ensure that \`android/settings.gradle\` contains the following: \`\`\`kotlin title="android/settings.gradle.kts" include(":app", ":boxComponent", ":circleComponent", ":assetComponent") ... \`\`\` \`\`\`groovy title="android/settings.gradle" include ':app', ':boxComponent', ':circleComponent', ':assetComponent' ... \`\`\`

6. Repeat steps \[3.1]\[] through 3.6 (this step) until all validator recommendations are handled and the tool runs without further recommendations. When successful, this command outputs an \`app-release.aab\` file in \`build/app/outputs/bundle/release\`. A successful build doesn't always mean the app was built as intended. It is up to you to ensure that all loading units and Dart libraries are included in the way you intended. For example, a common mistake is accidentally importing a Dart library without the \`deferred\` keyword, resulting in a deferred library being compiled as part of the base loading unit. In this case, the Dart library would load properly because it is always present in the base, and the library wouldn't be split off. This can be checked by examining the \`deferred\_components\_loading\_units.yaml\` file to verify that the generated loading units are described as intended. When adjusting the deferred components configurations, or making Dart changes that add, modify, or remove loading units, you should expect the validator to fail. Follow steps \[3.1]\[] through 3.6 (this step) to apply any recommended changes to continue the build.

\### Running the app locally Once your app has successfully built an AAB file, use Android's \[\`bundletool\`]\[] to perform local testing with the \`--local-testing\` flag. To run the AAB file on a test device, download the bundletool jar executable from \[github.com/google/bundletool/releases]\[] and run: \`\`\`console $ java -jar bundletool.jar build-apks --bundle=/build/app/outputs/bundle/release/app-release.aab --output=/app.apks --local-testing $ java -jar bundletool.jar install-apks --apks=/app.apks \`\`\` Where \`\` is the path to your app's project directory and \`\` is any temporary directory used to store the outputs of bundletool. This unpacks your AAB file into an APK file and installs it on the device. All available Android dynamic features are loaded onto the device locally and installation of deferred components is emulated. Before running \`build-apks\` again, remove the existing app APK file: \`\`\`console $ rm /app.apks \`\`\` Changes to the Dart codebase require either incrementing the Android build ID or uninstalling and reinstalling the app, as Android won't update the feature modules unless it detects a new version number. ### Releasing to the Google Play Store The built AAB file can be uploaded directly to the Play store as normal. When \`loadLibrary()\` is called, the needed Android module containing the Dart AOT library and assets is downloaded by the Flutter engine using the Play store's delivery feature. \[3.1]: #step-3.1 \[Android docs]: https\://developer.android.com/guide/playcore/feature-delivery#declare\_splitcompatapplication\_in\_the\_manifest \[\`bundletool\`]: https\://developer.android.com/studio/command-line/bundletool \[Deferred Components]: https\://github.com/flutter/flutter/wiki/Deferred-Components \[\`DeferredComponent\`]: https\://api.flutter.dev/flutter/services/DeferredComponent-class.html \[dynamic feature modules]: https\://developer.android.com/guide/playcore/feature-delivery \[Flutter Gallery's \`lib/deferred\_widget.dart\`]: https\://github.com/flutter/gallery/blob/main/lib/deferred\_widget.dart \[Flutter wiki]: https\://github.com/flutter/flutter/tree/main/docs \[github.com/google/bundletool/releases]: https\://github.com/google/bundletool/releases \[lazily loading a library]: https\://dart.dev/language/libraries#lazily-loading-a-library \[release or profile mode]: /testing/build-modes \[step 3.3]: #step-3.3 \[android-app-bundle]: https\://developer.android.com/guide/app-bundle \[dart-def-import]: https\://dart.dev/language/libraries#lazily-loading-a-library

----
url: https://docs.flutter.dev/jobs
----

## Current openings

[#](#current-openings)

The Flutter and Dart teams aren't currently hiring. Thanks for your interest!

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-07-24. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/jobs/index.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/jobs\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/jobs/index.md "Report an issue with this page").

----
url: https://docs.flutter.dev/tools/devtools/cpu-profiler.md
----

\# Use the CPU profiler view > Learn how to use the DevTools CPU profiler view. :::note The CPU profiler view works with Dart CLI and mobile apps only. Use Chrome DevTools to \[analyze performance]\[] of a web app. ::: The CPU profiler view allows you to record and profile a session from your Dart or Flutter application. The profiler can help you solve performance problems or generally understand your app's CPU activity. The Dart VM collects CPU samples (a snapshot of the CPU call stack at a single point in time) and sends the data to DevTools for visualization. By aggregating many CPU samples together, the profiler can help you understand where the CPU spends most of its time. :::note \*\*If you are running a Flutter application, use a profile build to analyze performance.\*\* CPU profiles are not indicative of release performance unless your Flutter application is run in profile mode. ::: ## CPU profiler Start recording a CPU profile by clicking \*\*Record\*\*. When you are done recording, click \*\*Stop\*\*. At this point, CPU profiling data is pulled from the VM and displayed in the profiler views (Call tree, Bottom up, Method table, and Flame chart). To load all available CPU samples without manually recording and stopping, you can click \*\*Load all CPU samples\*\*, which pulls all CPU samples that the VM has recorded and stored in its sample buffer and then displays those CPU samples in the profiler view. By default, the VM's sample buffer is used as a ring buffer, meaning that once it has become full, new samples start overwriting the oldest ones in the buffer. To make the VM instead discard all samples collected after the sample buffer has become full, pass the \`--profile-startup\` flag to \`dart run\` or \`flutter run\`. ### Bottom up This table provides a bottom-up representation of a CPU profile. This means that each top-level method, or root, in the bottom-up table is actually the top method in the call stack for one or more CPU samples. In other words, each top-level method in a bottom-up table is a leaf node from the top-down table (the call tree). In this table, a method can be expanded to show its \_callers\_. This view is useful for identifying expensive \_methods\_ in a CPU profile. When a root node in this table has a high \_self\_ time, that means that many CPU samples in this profile ended with that method on top of the call stack. !\[Screenshot of the Bottom-up view]\(/assets/images/docs/tools/devtools/bottom-up-view\.png) See the \[Guidelines]\(#guidelines) section below to learn how to enable the blue and green vertical lines seen in this image. Tooltips can help you understand the values in each column: \*\*Total time\*\* : For top-level methods in the bottom-up tree (stack frames that were at the top of at least one CPU sample), this is the time the method spent executing its own code, as well as the code for any methods that it called. \*\*Self time\*\* : For top-level methods in the bottom-up tree (stack frames that were at the top of at least one CPU sample), this is the time the method spent executing only its own code.\
\
For children methods in the bottom-up tree (the callers), this is the self time of the top-level method (the callee) when called through the child method (the caller). \*\*Table element\*\* (self time) !\[Screenshot of a bottom-up table]\(/assets/images/docs/tools/devtools/table-element.png) ### Call tree This table provides a top-down representation of a CPU profile. This means that each top-level method in the call tree is a root of one or more CPU samples. In this table, a method can be expanded to show its \_callees\_. This view is useful for identifying expensive \_paths\_ in a CPU profile. When a root node in this table has a high \_total\_ time, that means that many CPU samples in this profile started with that method on the bottom of the call stack. !\[Screenshot of a call tree table]\(/assets/images/docs/tools/devtools/call-tree.png) See the \[Guidelines]\(#guidelines) section below to learn how to enable the blue and green vertical lines seen in this image. Tooltips can help you understand the values in each column: \*\*Total time\*\* : Time that a method spent executing its own code as well as the code for any methods it called. \*\*Self time\*\* : Time the method spent executing only its own code. ### Method table The method table provides CPU statistics for each method contained in a CPU profile. In the table on the left, all available methods are listed with their \*\*total\*\* and \*\*self\*\* time. \*\*Total\*\* time is the combined time that a method spent \*\*anywhere\*\* on the call stack, or in other words, the time a method spent executing its own code and any code for methods that it called. \*\*Self\*\* time is the combined time that a method spent on top of the call stack, or in other words, the time a method spent executing only its own code. !\[Screenshot of a call tree table]\(/assets/images/docs/tools/devtools/method-table.png) Selecting a method from the table on the left shows the call graph for that method. The call graph shows a method's callers and callees and their respective caller / callee percentages. ### Flame chart The flame chart view is a graphical representation of the \[Call tree]\(#call-tree). This is a top-down view of a CPU profile, so in this chart, the top-most method calls the one below it. The width of each flame chart element represents the amount of time that a method spent on the call stack. Like the Call tree, this view is useful for identifying expensive paths in a CPU profile. !\[Screenshot of a flame chart]\(/assets/images/docs/tools/devtools/cpu-flame-chart.png) The help menu, which can be opened by clicking the \`?\` icon next to the search bar, provides information about how to navigate and zoom within the chart and a color-coded legend. !\[Screenshot of flame chart help]\(/assets/images/docs/tools/devtools/flame-chart-help.png){:width="70%"} ### CPU sampling rate DevTools sets a rate at which the VM collects CPU samples: 1 sample / 250 μs (microseconds). This is selected by default on the CPU profiler page as "Cpu sampling rate: medium". This rate can be modified using the selector at the top of the page. !\[Screenshot of cpu sampling rate menu]\(/assets/images/docs/tools/devtools/cpu-sampling-rate-menu.png){:width="70%"} The \*\*low\*\*, \*\*medium\*\*, and \*\*high\*\* sampling rates are 1,000 Hz, 4,000 Hz, and 20,000 Hz, respectively. It's important to know the trade-offs of modifying this setting. A profile that was recorded with a \*\*higher\*\* sampling rate yields a more fine-grained CPU profile with more samples. This might affect performance of your app since the VM is being interrupted more often to collect samples. This also causes the VM's CPU sample buffer to overflow more quickly. The VM has limited space where it can store CPU sample information. At a higher sampling rate, the space fills up and begins to overflow sooner than it would have if a lower sampling rate was used. This means that you might not have access to CPU samples from the beginning of the recorded profile, depending on whether the buffer overflows during the time of recording. A profile that was recorded with a lower sampling rate yields a more coarse-grained CPU profile with fewer samples. This affects your app's performance less, but you might have access to less information about what the CPU was doing during the time of the profile. The VM's sample buffer also fills more slowly, so you can see CPU samples for a longer period of app run time. This means that you have a better chance of viewing CPU samples from the beginning of the recorded profile. ### Filtering When viewing a CPU profile, you can filter the data by library, method name, or \[\`UserTag\`]\[]. !\[Screenshot of filter by tag menu]\(/assets/images/docs/tools/devtools/filter-by-tag.png) \[\`UserTag\`]: https\://api.flutter.dev/flutter/dart-developer/UserTag-class.html ## Guidelines When looking at a call tree or bottom up view, sometimes the trees can be very deep. To help with viewing parent-child relationships in a deep tree, enable the \*\*Display guidelines\*\* option. This adds vertical guidelines between parent and child in the tree. !\[Screenshot of display options]\(/assets/images/docs/tools/devtools/display-options.png) \[analyze performance]: https\://developers.google.com/web/tools/chrome-devtools/evaluate-performance/ ## Other resources To learn how to use DevTools to analyze the CPU usage of a compute-intensive Mandelbrot app, check out a guided \[CPU Profiler View tutorial]\[profiler-tutorial]. Also, learn how to analyze CPU usage when the app uses isolates for parallel computing. \[profiler-tutorial]: https\://medium.com/@fluttergems/mastering-dart-flutter-devtools-cpu-profiler-view-part-6-of-8-31e24eae6bf8

----
url: https://docs.flutter.dev/ui/navigation/deep-linking
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Deep linking](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

Deep links are links that not only open an app, but also take the user to a specific location "deep" inside the app. For example, a deep link from an advertisement for a pair of sneakers might open a shopping app and display the product page for those particular shoes.

Flutter supports deep linking on iOS, Android, and the web. Opening a URL displays that screen in your app. With the following steps, you can launch and display routes by using named routes (either with the [`routes`](https://pub.dev/documentation/material_ui/latest/material_ui/MaterialApp/routes.html) parameter or [`onGenerateRoute`](https://pub.dev/documentation/material_ui/latest/material_ui/MaterialApp/onGenerateRoute.html)), or by using the [`Router`](https://api.flutter.dev/flutter/widgets/Router-class.html) widget.

If you're running the app in a web browser, there's no additional setup required. Route paths are handled in the same way as an iOS or Android deep link. By default, web apps read the deep link path from the url fragment using the pattern: `/#/path/to/app/screen`, but this can be changed by [configuring the URL strategy](/ui/navigation/url-strategies) for your app.

If you are a visual learner, check out the following video:

[Watch on YouTube in a new tab: "Deep linking in Flutter"](https://www.youtube.com/watch/KNAb2XL7k2g)

## Get started

[#](#get-started)

To get started, see our cookbooks for Android and iOS:

[Android](/cookbook/navigation/set-up-app-links)

[iOS](/cookbook/navigation/set-up-universal-links)

## Migrating from plugin-based deep linking

[#](#migrating-from-plugin-based-deep-linking)

If you have written a plugin to handle deep links, as described in [Deep Links and Flutter applications](https://medium.com/flutter-community/deep-links-and-flutter-applications-how-to-handle-them-properly-8c9865af9283) (a free article on Medium), you should opt out the Flutter's default deep link handler. To do this, set `FlutterDeepLinkingEnabled` to false in `Info.plist` *or* `flutter_deeplinking_enabled` to false in `AndroidManifest.xml`.

## Behavior

[#](#behavior)

The behavior varies slightly based on the platform and whether the app is launched and running.

| Platform / Scenario      | Using Navigator                                                     | Using Router                                                                                                                                                                                               |
| ------------------------ | ------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| iOS (not launched)       | App gets initialRoute ("/") and a short time after gets a pushRoute | App gets initialRoute ("/") and a short time after uses the RouteInformationParser to parse the route and call RouterDelegate.setNewRoutePath, which configures the Navigator with the corresponding Page. |
| Android - (not launched) | App gets initialRoute containing the route ("/deeplink")            | App gets initialRoute ("/deeplink") and passes it to the RouteInformationParser to parse the route and call RouterDelegate.setNewRoutePath, which configures the Navigator with the corresponding Pages.   |
| iOS (launched)           | pushRoute is called                                                 | Path is parsed, and the Navigator is configured with a new set of Pages.                                                                                                                                   |
| Android (launched)       | pushRoute is called                                                 | Path is parsed, and the Navigator is configured with a new set of Pages.                                                                                                                                   |

When using the [`Router`](https://api.flutter.dev/flutter/widgets/Router-class.html) widget, your app has the ability to replace the current set of pages when a new deep link is opened while the app is running.

## To learn more

[#](#to-learn-more)

* [Learning Flutter's new navigation and routing system](https://blog.flutter.dev/learning-flutters-new-navigation-and-routing-system-7c9068155ade) provides an introduction to the Router system.
* [Deep dive into Flutter deep linking](https://www.youtube.com/watch?v=6RxuDcs6jVw\&t=3s) video from Google I/O 2023
* [Flutter Deep Linking: The Ultimate Guide](https://codewithandrea.com/articles/flutter-deep-links/), a step-by-step tutorial showing how to implement deep links in Flutter.

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-09-29. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/ui/navigation/deep-linking.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/ui/navigation/deep-linking\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/ui/navigation/deep-linking.md "Report an issue with this page").

----
url: https://docs.flutter.dev/cookbook/networking/delete-data.md
----

\# Delete data on the internet > How to use the http package to delete data on the internet.

<!--?code-excerpt path-base="cookbook/networking/delete_data/"?-->

This recipe covers how to delete data over the internet using the \`http\` package. This recipe uses the following steps: 1. Add the \`http\` package. 2. Delete data on the server. 3. Update the screen. ## 1. Add the \`http\` package To add the \`http\` package as a dependency, run \`flutter pub add\`: \`\`\`console $ flutter pub add http \`\`\` Import the \`http\` package.

<!--?code-excerpt "lib/main.dart (Http)"?-->

\`\`\`dart import 'package:http/http.dart' as http; import 'package:material\_ui/material\_ui.dart'; \`\`\` If you are deploying to Android, edit your \`AndroidManifest.xml\` file to add the Internet permission. \`\`\`xml \`\`\` Likewise, if you are deploying to macOS, edit your \`macos/Runner/DebugProfile.entitlements\` and \`macos/Runner/Release.entitlements\` files to include the network client entitlement. \`\`\`xml com.apple.security.network.client \`\`\` ## 2. Delete data on the server This recipe covers how to delete an album from the \[JSONPlaceholder]\[] using the \`http.delete()\` method. Note that this requires the \`id\` of the album that you want to delete. For this example, use something you already know, for example \`id = 1\`.

<!--?code-excerpt "lib/main_step1.dart (deleteAlbum)"?-->

\`\`\`dart Future deleteAlbum(String id) async { final http.Response response = await http.delete( Uri.parse('https\://jsonplaceholder.typicode.com/albums/$id'), headers: { 'Content-Type': 'application/json; charset=UTF-8', }, ); return response; } \`\`\` The \`http.delete()\` method returns a \`Future\` that contains a \`Response\`. \* \[\`Future\`]\[] is a core Dart class for working with async operations. A Future object represents a potential value or error that will be available at some time in the future. \* The \`http.Response\` class contains the data received from a successful http call. \* The \`deleteAlbum()\` method takes an \`id\` argument that is needed to identify the data to be deleted from the server. ## 3. Update the screen In order to check whether the data has been deleted or not, first fetch the data from the \[JSONPlaceholder]\[] using the \`http.get()\` method, and display it in the screen. (See the \[Fetch Data]\[] recipe for a complete example.) You should now have a \*\*Delete Data\*\* button that, when pressed, calls the \`deleteAlbum()\` method.

<!--?code-excerpt "lib/main.dart (Column)" replace="/return //g"?-->

\`\`\`dart Column( mainAxisAlignment: MainAxisAlignment.center, children: \[ Text(snapshot.data?.title ?? 'Deleted'), ElevatedButton( child: const Text('Delete Data'), onPressed: () { setState(() { \_futureAlbum = deleteAlbum( snapshot.data!.id.toString(), ); }); }, ), ], ); \`\`\` Now, when you click on the \*\*\*Delete Data\*\*\* button, the \`deleteAlbum()\` method is called and the id you are passing is the id of the data that you retrieved from the internet. This means you are going to delete the same data that you fetched from the internet. ### Returning a response from the deleteAlbum() method Once the delete request has been made, you can return a response from the \`deleteAlbum()\` method to notify our screen that the data has been deleted.

<!--?code-excerpt "lib/main.dart (deleteAlbum)"?-->

\`\`\`dart Future deleteAlbum(String id) async { final http.Response response = await http.delete( Uri.parse('https\://jsonplaceholder.typicode.com/albums/$id'), headers: { 'Content-Type': 'application/json; charset=UTF-8', }, ); if (response.statusCode == 200) { // If the server did return a 200 OK response, // then return an empty Album. After deleting, // you'll get an empty JSON \`{}\` response. // Don't return \`null\`, otherwise \`snapshot.hasData\` // will always return false on \`FutureBuilder\`. return Album.empty(); } else { // If the server did not return a "200 OK response", // then throw an exception. throw Exception('Failed to delete album.'); } } \`\`\` \`FutureBuilder()\` now rebuilds when it receives a response. Since the response won't have any data in its body if the request was successful, the \`Album.fromJson()\` method creates an instance of the \`Album\` object with a default value (\`null\` in our case). This behavior can be altered in any way you wish. That's all! Now you've got a function that deletes the data from the internet. ## Complete example

<!--?code-excerpt "lib/main.dart"?-->

\`\`\`dart import 'dart:async'; import 'dart:convert'; import 'package:http/http.dart' as http; import 'package:material\_ui/material\_ui.dart'; Future fetchAlbum() async { final response = await http.get( Uri.parse('https\://jsonplaceholder.typicode.com/albums/1'), ); if (response.statusCode == 200) { // If the server did return a 200 OK response, then parse the JSON. return Album.fromJson(jsonDecode(response.body) as Map); } else { // If the server did not return a 200 OK response, then throw an exception. throw Exception('Failed to load album'); } } Future deleteAlbum(String id) async { final http.Response response = await http.delete( Uri.parse('https\://jsonplaceholder.typicode.com/albums/$id'), headers: { 'Content-Type': 'application/json; charset=UTF-8', }, ); if (response.statusCode == 200) { // If the server did return a 200 OK response, // then return an empty Album. After deleting, // you'll get an empty JSON \`{}\` response. // Don't return \`null\`, otherwise \`snapshot.hasData\` // will always return false on \`FutureBuilder\`. return Album.empty(); } else { // If the server did not return a "200 OK response", // then throw an exception. throw Exception('Failed to delete album.'); } } class Album { int? id; String? title; Album({this.id, this.title}); Album.empty(); factory Album.fromJson(Map json) { return switch (json) { {'id': int id, 'title': String title} => Album(id: id, title: title), \_ => throw const FormatException('Failed to load album.'), }; } } void main() { runApp(const MyApp()); } class MyApp extends StatefulWidget { const MyApp({super.key}); @override State createState() { return \_MyAppState(); } } class \_MyAppState extends State { late Future \_futureAlbum; @override void initState() { super.initState(); \_futureAlbum = fetchAlbum(); } @override Widget build(BuildContext context) { return MaterialApp( title: 'Delete Data Example', theme: ThemeData( colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), ), home: Scaffold( appBar: AppBar(title: const Text('Delete Data Example')), body: Center( child: FutureBuilder( future: \_futureAlbum, builder: (context, snapshot) { // If the connection is done, // check for response data or an error. if (snapshot.connectionState == ConnectionState.done) { if (snapshot.hasData) { return Column( mainAxisAlignment: MainAxisAlignment.center, children: \[ Text(snapshot.data?.title ?? 'Deleted'), ElevatedButton( child: const Text('Delete Data'), onPressed: () { setState(() { \_futureAlbum = deleteAlbum( snapshot.data!.id.toString(), ); }); }, ), ], ); } else if (snapshot.hasError) { return Text('${snapshot.error}'); } } // By default, show a loading spinner. return const CircularProgressIndicator(); }, ), ), ), ); } } \`\`\` \[Fetch Data]: /cookbook/networking/fetch-data \[ConnectionState]: https\://api.flutter.dev/flutter/widgets/ConnectionState-class.html \[\`didChangeDependencies()\`]: https\://api.flutter.dev/flutter/widgets/State/didChangeDependencies.html \[\`Future\`]: https\://api.flutter.dev/flutter/dart-async/Future-class.html \[\`FutureBuilder\`]: https\://api.flutter.dev/flutter/widgets/FutureBuilder-class.html \[JSONPlaceholder]: https\://jsonplaceholder.typicode.com/ \[\`http\`]: https\://pub.dev/packages/http \[\`http.delete()\`]: https\://pub.dev/documentation/http/latest/http/delete.html \[\`http\` package]: https\://pub.dev/packages/http/install \[\`InheritedWidget\`]: https\://api.flutter.dev/flutter/widgets/InheritedWidget-class.html \[Introduction to unit testing]: /cookbook/testing/unit/introduction \[\`initState()\`]: https\://api.flutter.dev/flutter/widgets/State/initState.html \[Mock dependencies using Mockito]: /cookbook/testing/unit/mocking \[JSON and serialization]: /data-and-backend/serialization/json \[\`State\`]: https\://api.flutter.dev/flutter/widgets/State-class.html

----
url: https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.28.5
----

The 2.28.5 release of the Dart and Flutter DevTools includes the following changes among other general improvements. To learn more about DevTools, check out the [DevTools overview](https://docs.flutter.dev/tools/devtools).

This was a cherry-pick release on top of DevTools 2.28.4. To learn about the improvements included in DevTools 2.28.4, please read the [release notes](/tools/devtools/release-notes/release-notes-2.28.4).

## Inspector updates

[#](#inspector-updates)

* Only cache pub root directories added by the user. - [#6897](https://github.com/flutter/devtools/pull/6897)

* Remove Flutter pub root if it was accidentally cached. - [#6911](https://github.com/flutter/devtools/pull/6911)

## DevTools Extension updates

[#](#devtools-extension-updates)

* Fixed a couple bugs preventing Dart server apps from connecting to DevTools extensions. - [#6982](https://github.com/flutter/devtools/pull/6982), [#6993](https://github.com/flutter/devtools/pull/6993)

## Full commit history

[#](#full-commit-history)

To find a complete list of changes in this release, check out the [DevTools git log](https://github.com/flutter/devtools/tree/v2.28.5).

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-11. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/release-notes/release-notes-2.28.5.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.28.5\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/release-notes/release-notes-2.28.5.md "Report an issue with this page").

----
url: https://docs.flutter.dev/learn/pathway/tutorial/layout
----

Learn how to build layouts with common widgets like Scaffold, AppBar, Column, and Row.

[Watch on YouTube in a new tab: "Flutter layout and constraints"](https://www.youtube.com/watch/z8bY3XVAzgI)

web\_asset Structure an app with Scaffold and AppBar

view\_column Arrange widgets using Column and Row

repeat Generate widgets dynamically from data

grid\_view Build a grid layout for the game board

***

## Steps

1### Introductionkeyboard\_arrow\_up

Given that Flutter is a UI toolkit, you'll spend a lot of time creating layouts with Flutter widgets.

In this section, you'll learn how to build layouts with some of the most common layout widgets. This includes high-level widgets like [`Scaffold`](https://pub.dev/documentation/material_ui/latest/material_ui/Scaffold-class.html) and [`AppBar`](https://pub.dev/documentation/material_ui/latest/material_ui/AppBar-class.html), which lay out the structure of a screen, as well as lower-level widgets like [`Column`](https://api.flutter.dev/flutter/widgets/Column-class.html) or [`Row`](https://api.flutter.dev/flutter/widgets/Row-class.html) that lay out widgets vertically or horizontally.

2### `Scaffold` and `AppBar`keyboard\_arrow\_up

Mobile applications often have a bar at the top called an "app bar" that can display a title, navigation controls, and/or actions.

The simplest way to add an app bar to your app is by using two widgets: `Scaffold` and `AppBar`.

`Scaffold` is a convenience widget that provides a Material-style page layout, making it simple to add an app bar, drawer, navigation bar, and more to a page of your app. `AppBar` is, of course, the app bar.

Add an `AppBar` to the `Scaffold` widget within your `MainApp` widget's `build` method. The following code uses an additional layout widget: [`Align`](https://api.flutter.dev/flutter/widgets/Align-class.html). This positions the title to the left, which would be centered by default. The `Text` widget contains the title itself.

Passing an enum or static property directly (like `Alignment.centerLeft`) can also be shortened using [Dart's dot shorthands](https://dart.dev/language/dot-shorthands) syntax, which you can read more about on both the official Dart documentation and the [Flutter shorthands overview](/ui/dot-shorthands).

dart

```
class MainApp extends StatelessWidget {
  const MainApp({super.key});
​
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Align(
            alignment: Alignment.centerLeft,
            child: Text('Birdle'),
          ),
        ),
        body: const Center(
          child: Tile('A', HitType.hit),
        ),
      ),
    );
  }
}
```

#### An updated widget tree

[#](#an-updated-widget-tree)

Considering your app's widget tree gets more important as your app grows. At this point, there's a "branch" in the widget tree for the first time, and it now looks like the following figure:

3### Create a widget for the game page layoutkeyboard\_arrow\_up

Add the following code for a new widget, called `GamePage`, to your `main.dart` file. This widget will eventually display the UI elements needed for the game itself.

lib/main.dart

dart

```
class GamePage extends StatelessWidget {
  GamePage({super.key});
  // This object is part of the game.dart file.
  // It manages wordle logic, and is outside the scope of this tutorial.
  final Game _game = Game();
​
  @override
  Widget build(BuildContext context) {
    // TODO: Replace with screen contents
    return Container();
  }
}
```

Then update your `MainApp` widget to create and display a `GamePage` widget instead of the `Tile` widget.

dart

```
class MainApp extends StatelessWidget {
  const MainApp({super.key});
​
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Align(
            alignment: Alignment.centerLeft,
            child: Text('Birdle'),
          ),
        ),
        body: Center(child: GamePage()),
      ),
    );
  }
}
```

4### Arrange widgets with `Column` and `Row`keyboard\_arrow\_up

The `GamePage` layout contains the grid of tiles that display a user's guesses.

There are a number of ways you can build this layout. The simplest is with the `Column` and `Row` widgets. Each row contains five tiles that represent the five letters in a guess, with five rows total. So you'll need a single `Column` with five `Row` widgets as children, where each row contains five children.

To get started, replace the `Container` in `GamePage.build` with a `Padding` widget with a `Column` widget as its child:

dart

```
class GamePage extends StatelessWidget {
  GamePage({super.key});
  // This manages game logic, and is out of scope for this lesson.
  final Game _game = Game();
​
  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(8.0),
      child: Column(
        spacing: 5.0,
        children: [
          // Add children next.
        ],
      ),
    );
  }
}
```

The `spacing` property puts five pixels between each element on the main axis.

Within `Column.children`, for each element in the `_game.guesses` list, add a `Row` widget as a child.

dart

```
class GamePage extends StatelessWidget {
  GamePage({super.key});
  // This manages game logic, and is out of scope for this lesson.
  final Game _game = Game();
​
  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(8.0),
      child: Column(
        spacing: 5.0,
        children: [
          for (final guess in _game.guesses)
            Row(
              spacing: 5.0,
              children: [
                // We'll add the tiles here later.
              ],
            ),
        ],
      ),
    );
  }
}
```

The `for` loop in the `children` list is called a [collection for element](https://dart.dev/language/collections#for-element), a Dart syntax that allows you to iteratively add items to a collection when it is built at runtime. This syntactic sugar makes it easier for you to work with collections of widgets, providing a declarative alternative to the following:

dart

```
[..._game.guesses.map((guess) => Row(/* ... */))],
```

In this case, it adds five `Row` widgets to the column, one for each guess on the `Game` object.

#### An updated widget tree

[#](#an-updated-widget-tree-1)

The widget tree for this app has expanded significantly in this lesson. Now, it looks more like the following (abridged) figure:

When you reload your app, you should see a 5x5 grid of white squares.

5### Reviewkeyboard\_arrow\_up

web\_asset Structured your app with Scaffold and AppBar keyboard\_arrow\_up

You used `Scaffold` to provide a Material-style page layout and `AppBar` to add a title bar at the top of your app. These high-level widgets give your app a standard, yet polished structure.

view\_column Arranged widgets using Column and Row keyboard\_arrow\_up

`Column` arranges widgets vertically and `Row` arranges them horizontally. These are fundamental layout widgets you'll use constantly in Flutter. The `spacing` property adds consistent gaps between children.

repeat Generated widgets dynamically from data keyboard\_arrow\_up

You used a collection for element to build widgets from a list. This declarative approach lets you build user interfaces that automatically and visually reflect your data, a pattern central to Flutter development.

grid\_view Built the game board grid keyboard\_arrow\_up

By nesting `Row` widgets inside a `Column` and using nested loops, you created a 5x5 grid of `Tile` widgets. Your app now displays the complete game board layout!

6### Test yourselfkeyboard\_arrow\_up

### Layout Quiz

1 / 2

**What is the primary difference between a Column and a Row widget?**

1. Column is for scrolling content; Row is for static content.

   Not quite.

   Both Column and Row are for layout, not scrolling. Use ListView or SingleChildScrollView for scrolling.

2. Column arranges children vertically; Row arranges children horizontally.

   That's right!

   Column lays out its children along the vertical axis, while Row uses the horizontal axis.

3. Column can have unlimited children; Row is limited to two.

   Not quite.

   Both widgets can have any number of children.

4. Column requires a Scaffold parent; Row does not.

   Not quite.

   Neither widget requires a Scaffold as a parent.

**What does the Scaffold widget provide in a Flutter app?**

1. Only a background color for the page.

   Not quite.

   Scaffold provides much more, including structure for app bars, drawers, and more.

2. A Material-style page layout with slots for app bar, body, drawer, and more.

   That's right!

   Scaffold is a convenience widget that provides a standard Material page structure.

3. A way to navigate between different pages.

   Not quite.

   Navigation is handled by Navigator, not Scaffold.

4. Automatic state management for the page.

   Not quite.

   Scaffold doesn't manage state; you use StatefulWidget or state management solutions for that.

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-09-29. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/learn/pathway/tutorial/layout.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/learn/pathway/tutorial/layout\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/learn/pathway/tutorial/layout.md "Report an issue with this page").

----
url: https://docs.flutter.dev/cookbook/gestures/dismissible.md
----

\# Implement swipe to dismiss > How to implement swiping to dismiss or delete.

<!--?code-excerpt path-base="cookbook/gestures/dismissible"?-->

The "swipe to dismiss" pattern is common in many mobile apps. For example, when writing an email app, you might want to allow a user to swipe away email messages to delete them from a list. Flutter makes this task easy by providing the \[\`Dismissible\`]\[] widget. Learn how to implement swipe to dismiss with the following steps: 1. Create a list of items. 2. Wrap each item in a \`Dismissible\` widget. 3. Provide "leave behind" indicators. ## 1. Create a list of items First, create a list of items. For detailed instructions on how to create a list, follow the \[Working with long lists]\[] recipe. ### Create a data source In this example, you want 20 sample items to work with. To keep it simple, generate a list of strings.

<!--?code-excerpt "lib/main.dart (Items)"?-->

\`\`\`dart final items = List.generate(20, (i) => 'Item ${i + 1}'); \`\`\` ### Convert the data source into a list Display each item in the list on screen. Users won't be able to swipe these items away just yet.

<!--?code-excerpt "lib/step1.dart (ListView)" replace="/^body: //g;/^\),$/)/g"?-->

\`\`\`dart ListView\.builder( itemCount: items.length, itemBuilder: (context, index) { return ListTile(title: Text(items\[index])); }, ) \`\`\` ## 2. Wrap each item in a Dismissible widget In this step, give users the ability to swipe an item off the list by using the \[\`Dismissible\`]\[] widget. 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. Update the \`itemBuilder()\` function to return a \`Dismissible\` widget:

<!--?code-excerpt "lib/step2.dart (Dismissible)"?-->

\`\`\`dart itemBuilder: (context, index) { final item = items\[index]; return Dismissible( // Each Dismissible must contain a Key. Keys allow Flutter to // uniquely identify widgets. key: Key(item), // Provide a function that tells the app // what to do after an item has been swiped away. onDismissed: (direction) { // Remove the item from the data source. setState(() { items.removeAt(index); }); // Then show a snackbar. ScaffoldMessenger.of(context) .showSnackBar(SnackBar(content: Text('$item dismissed'))); }, child: ListTile(title: Text(item)), ); }, \`\`\` ## 3. Provide "leave behind" indicators 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, the indicator is a red background. To add the indicator, provide a \`background\` parameter to the \`Dismissible\`. \`\`\`dart diff ScaffoldMessenger.of(context) .showSnackBar(SnackBar(content: Text('$item dismissed'))); }, + // Show a red background as the item is swiped away. + background: Container(color: Colors.red), child: ListTile( title: Text(item), ), \`\`\` ## Interactive example

<!--?code-excerpt "lib/main.dart"?-->

\`\`\`dartpad title="Flutter Swipe to Dismiss hands-on example in DartPad" run="true" import 'package:flutter/material.dart'; void main() { runApp(const MyApp()); } // MyApp is a StatefulWidget. This allows updating the state of the // widget when an item is removed. class MyApp extends StatefulWidget { const MyApp({super.key}); @override MyAppState createState() { return MyAppState(); } } class MyAppState extends State { final items = List.generate(20, (i) => 'Item ${i + 1}'); @override Widget build(BuildContext context) { const title = 'Dismissing Items'; return MaterialApp( title: title, theme: ThemeData( colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), ), home: Scaffold( appBar: AppBar(title: const Text(title)), body: ListView\.builder( itemCount: items.length, itemBuilder: (context, index) { final item = items\[index]; return Dismissible( // Each Dismissible must contain a Key. Keys allow Flutter to // uniquely identify widgets. key: Key(item), // Provide a function that tells the app // what to do after an item has been swiped away. onDismissed: (direction) { // Remove the item from the data source. setState(() { items.removeAt(index); }); // Then show a snackbar. ScaffoldMessenger.of(context) .showSnackBar(SnackBar(content: Text('$item dismissed'))); }, // Show a red background as the item is swiped away. background: Container(color: Colors.red), child: ListTile(title: Text(item)), ); }, ), ), ); } } \`\`\` \[\`Dismissible\`]: https\://api.flutter.dev/flutter/widgets/Dismissible-class.html \[Working with long lists]: /cookbook/lists/long-lists

----
url: https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.34.1.md
----

\# DevTools 2.34.1 release notes > Release notes for Dart and Flutter DevTools version 2.34.1. The 2.34.1 release of the Dart and Flutter DevTools includes the following changes among other general improvements. To learn more about DevTools, check out the \[DevTools overview]\(/tools/devtools). ## General updates \* Fixed an issue preventing DevTools from connecting to Flutter apps that are not launched from Flutter Tools. - \[#6848]\(https\://github.com/flutter/devtools/issues/6848) \* Improved performance of the FlatTable. - \[#7391]\(https\://github.com/flutter/devtools/pull/7391) ## Inspector updates - Fixes an edge case where widgets from other packages could show up in the inspector tree. - \[#7353]\(https\://github.com/flutter/devtools/pull/7353) ## Performance updates \* Add a setting to include CPU samples in the Timeline. - \[#7333]\(https\://github.com/flutter/devtools/pull/7333), \[#7369]\(https\://github.com/flutter/devtools/pull/7369) !\[Timeline settings]\(/assets/images/docs/tools/devtools/release-notes/images-2.34.1/7369-timeline-settings.png "Timeline settings") \* Removed the legacy trace viewer. The legacy trace viewer was replaced with the embedded Perfetto trace viewer in DevTools version 2.21.1, but was available behind a setting to ensure a smooth rollout. This release of DevTools removes the legacy trace viewer entirely. - \[#7316]\(https\://github.com/flutter/devtools/pull/7316) \* Updated the Perfetto trace viewer build. - \[#7445]\(https\://github.com/flutter/devtools/pull/7445), \[#7456]\(https\://github.com/flutter/devtools/pull/7456), \[#7480]\(https\://github.com/flutter/devtools/pull/7480) \* Added a loading message to show when refreshing the timeline. - \[#7463]\(https\://github.com/flutter/devtools/pull/7463) !\[Loading message]\(/assets/images/docs/tools/devtools/release-notes/images-2.34.1/7463-overlay.png "Loading message") ## Memory updates \* Enabled export of snapshots and improved snapshotting performance. - \[#7197]\(https\://github.com/flutter/devtools/pull/7197), \[#7439]\(https\://github.com/flutter/devtools/pull/7439), \[#7449]\(https\://github.com/flutter/devtools/pull/7449) !\[Export snapshot]\(/assets/images/docs/tools/devtools/release-notes/images-2.34.1/7197-export.png "Export snapshot") \* Fixed failures during disconnect in tracing. - \[#7440]\(https\://github.com/flutter/devtools/pull/7440) \* Made class filter shared between the panes \`Profile Memory\` and \`Diff Snapshots\`. - \[#7462]\(https\://github.com/flutter/devtools/pull/7462) ## Network profiler updates \* Improved Network profiler performance. - \[#7266]\(https\://github.com/flutter/devtools/pull/7266) \* Fixed a bug where selected pending requests weren't refreshing the tab once updated. - \[#7266]\(https\://github.com/flutter/devtools/pull/7266) \* Fixed the JSON viewer so multiline strings are visible in their row, and through a tooltip. - \[#7389]\(https\://github.com/flutter/devtools/pull/7389) \* Fixed JsonViewer where all of the expanded sections would snap closed. \[#7367]\(https\://github.com/flutter/devtools/pull/7367) ## Deep Links tool updates \* Automatically populate a list of Flutter projects from the connected IDE. - \[#7415]\(https\://github.com/flutter/devtools/pull/7415), \[#7431]\(https\://github.com/flutter/devtools/pull/7431) ## Full commit history To find a complete list of changes in this release, check out the \[DevTools git log]\(https\://github.com/flutter/devtools/tree/v2.34.1).

----
url: https://docs.flutter.dev/cookbook/networking/fetch-data
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Fetch data from the internet](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

Fetching data from the internet is necessary for most apps. Luckily, Dart and Flutter provide tools, such as the `http` package, for this type of work.

This recipe uses the following steps:

1. Add the `http` package.
2. Make a network request using the `http` package.
3. Convert the response into a custom Dart object.
4. Fetch and display the data with Flutter.

## 1. Add the `http` package

[#](#1-add-the-http-package)

The [`http`](https://pub.dev/packages/http) package provides the simplest way to fetch data from the internet.

To add the `http` package as a dependency, run `flutter pub add`:

```
$ flutter pub add http
```

Import the http package.

dart

```
import 'package:http/http.dart' as http;
import 'package:material_ui/material_ui.dart';
```

If you are deploying to Android, edit your `AndroidManifest.xml` file to add the Internet permission.

xml

```
<!-- Required to fetch data from the internet. -->
<uses-permission android:name="android.permission.INTERNET" />
```

Likewise, if you are deploying to macOS, edit your `macos/Runner/DebugProfile.entitlements` and `macos/Runner/Release.entitlements` files to include the network client entitlement.

xml

```
<!-- Required to fetch data from the internet. -->
<key>com.apple.security.network.client</key>
<true/>
```

## 2. Make a network request

[#](#2-make-a-network-request)

This recipe covers how to fetch a sample album from the [JSONPlaceholder](https://jsonplaceholder.typicode.com/) using the [`http.get()`](https://pub.dev/documentation/http/latest/http/get.html) method.

dart

```
Future<http.Response> fetchAlbum() {
  return http.get(Uri.parse('https://jsonplaceholder.typicode.com/albums/1'));
}
```

The `http.get()` method returns a `Future` that contains a `Response`.

* [`Future`](https://api.flutter.dev/flutter/dart-async/Future-class.html) is a core Dart class for working with async operations. A Future object represents a potential value or error that will be available at some time in the future.
* The `http.Response` class contains the data received from a successful http call.

## 3. Convert the response into a custom Dart object

[#](#3-convert-the-response-into-a-custom-dart-object)

While it's easy to make a network request, working with a raw `Future<http.Response>` isn't very convenient. To make your life easier, convert the `http.Response` into a Dart object.

### Create an `Album` class

[#](#create-an-album-class)

First, create an `Album` class that contains the data from the network request. It includes a factory constructor that creates an `Album` from JSON.

Converting JSON using [pattern matching](https://dart.dev/language/patterns) is only one option. For more information, see the full article on [JSON and serialization](/data-and-backend/serialization/json).

dart

```
class Album {
  final int userId;
  final int id;
  final String title;
​
  const Album({required this.userId, required this.id, required this.title});
​
  factory Album.fromJson(Map<String, dynamic> json) {
    return switch (json) {
      {'userId': int userId, 'id': int id, 'title': String title} => Album(
        userId: userId,
        id: id,
        title: title,
      ),
      _ => throw const FormatException('Failed to load album.'),
    };
  }
}
```

### Convert the `http.Response` to an `Album`

[#](#convert-the-http-response-to-an-album)

Now, use the following steps to update the `fetchAlbum()` function to return a `Future<Album>`:

1. Convert the response body into a JSON `Map` with the `dart:convert` package.
2. If the server does return an OK response with a status code of 200, then convert the JSON `Map` into an `Album` using the `fromJson()` factory method.
3. If the server does not return an OK response with a status code of 200, then throw an exception. (Even in the case of a "404 Not Found" server response, throw an exception. Do not return `null`. This is important when examining the data in `snapshot`, as shown below.)

dart

```
Future<Album> fetchAlbum() async {
  final response = await http.get(
    Uri.parse('https://jsonplaceholder.typicode.com/albums/1'),
    headers: {'Accept': 'application/json'},
  );
​
  if (response.statusCode == 200) {
    // If the server did return a 200 OK response,
    // then parse the JSON.
    return Album.fromJson(jsonDecode(response.body) as Map<String, dynamic>);
  } else {
    // If the server did not return a 200 OK response,
    // then throw an exception.
    throw Exception('Failed to load album');
  }
}
```

Hooray! Now you've got a function that fetches an album from the internet.

## 4. Fetch the data

[#](#4-fetch-the-data)

Call the `fetchAlbum()` method in either the [`initState()`](https://api.flutter.dev/flutter/widgets/State/initState.html) or [`didChangeDependencies()`](https://api.flutter.dev/flutter/widgets/State/didChangeDependencies.html) methods.

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`](https://api.flutter.dev/flutter/widgets/InheritedWidget-class.html) changing, put the call into the `didChangeDependencies()` method. See [`State`](https://api.flutter.dev/flutter/widgets/State-class.html) for more details.

dart

```
class _MyAppState extends State<MyApp> {
  late Future<Album> futureAlbum;
​
  @override
  void initState() {
    super.initState();
    futureAlbum = fetchAlbum();
  }
  // ···
}
```

This Future is used in the next step.

## 5. Display the data

[#](#5-display-the-data)

To display the data on screen, use the [`FutureBuilder`](https://api.flutter.dev/flutter/widgets/FutureBuilder-class.html) widget. The `FutureBuilder` widget comes with Flutter and makes it easy to work with asynchronous data sources.

You must provide two parameters:

1. The `Future` you want to work with. In this case, the future returned from the `fetchAlbum()` function.
2. A `builder` function that tells Flutter what to render, depending on the state of the `Future`: loading, success, or error.

Note that `snapshot.hasData` only returns `true` when the snapshot contains a non-null data value.

Because `fetchAlbum` can only return non-null values, the function should throw an exception even in the case of a "404 Not Found" server response. Throwing an exception sets the `snapshot.hasError` to `true` which can be used to display an error message.

Otherwise, the spinner will be displayed.

dart

```
FutureBuilder<Album>(
  future: futureAlbum,
  builder: (context, snapshot) {
    if (snapshot.hasData) {
      return Text(snapshot.data!.title);
    } else if (snapshot.hasError) {
      return Text('${snapshot.error}');
    }
​
    // By default, show a loading spinner.
    return const CircularProgressIndicator();
  },
)
```

## Why is fetchAlbum() called in initState()?

[#](#why-is-fetchalbum-called-in-initstate)

Although it's convenient, it's not recommended to put an API call in a `build()` method.

Flutter calls the `build()` method every time it needs to change anything in the view, and this happens surprisingly often. The `fetchAlbum()` method, if placed inside `build()`, is repeatedly called on each rebuild causing the app to slow down.

Storing the `fetchAlbum()` result in a state variable ensures that the `Future` is executed only once and then cached for subsequent rebuilds.

## Testing

[#](#testing)

For information on how to test this functionality, see the following recipes:

* [Introduction to unit testing](/cookbook/testing/unit/introduction)
* [Mock dependencies using Mockito](/cookbook/testing/unit/mocking)

## Complete example

[#](#complete-example)

dart

```
import 'dart:async';
import 'dart:convert';
​
import 'package:http/http.dart' as http;
import 'package:material_ui/material_ui.dart';
​
Future<Album> fetchAlbum() async {
  final response = await http.get(
    Uri.parse('https://jsonplaceholder.typicode.com/albums/1'),
    headers: {'Accept': 'application/json'},
  );
​
  if (response.statusCode == 200) {
    // If the server did return a 200 OK response,
    // then parse the JSON.
    return Album.fromJson(jsonDecode(response.body) as Map<String, dynamic>);
  } else {
    // If the server did not return a 200 OK response,
    // then throw an exception.
    throw Exception('Failed to load album');
  }
}
​
class Album {
  final int userId;
  final int id;
  final String title;
​
  const Album({required this.userId, required this.id, required this.title});
​
  factory Album.fromJson(Map<String, dynamic> json) {
    return switch (json) {
      {'userId': int userId, 'id': int id, 'title': String title} => Album(
        userId: userId,
        id: id,
        title: title,
      ),
      _ => throw const FormatException('Failed to load album.'),
    };
  }
}
​
void main() => runApp(const MyApp());
​
class MyApp extends StatefulWidget {
  const MyApp({super.key});
​
  @override
  State<MyApp> createState() => _MyAppState();
}
​
class _MyAppState extends State<MyApp> {
  late Future<Album> futureAlbum;
​
  @override
  void initState() {
    super.initState();
    futureAlbum = fetchAlbum();
  }
​
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Fetch Data Example',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
      ),
      home: Scaffold(
        appBar: AppBar(title: const Text('Fetch Data Example')),
        body: Center(
          child: FutureBuilder<Album>(
            future: futureAlbum,
            builder: (context, snapshot) {
              if (snapshot.hasData) {
                return Text(snapshot.data!.title);
              } else if (snapshot.hasError) {
                return Text('${snapshot.error}');
              }
​
              // By default, show a loading spinner.
              return const CircularProgressIndicator();
            },
          ),
        ),
      ),
    );
  }
​
}
```

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-09-29. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/networking/fetch-data.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/cookbook/networking/fetch-data\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/networking/fetch-data.md "Report an issue with this page").

----
url: https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.54.0
----

The 2.54.0 release of the Dart and Flutter DevTools includes the following changes among other general improvements. To learn more about DevTools, check out the [DevTools overview](/tools/devtools/overview).

## General updates

[#](#general-updates)

* Dropped connections to DTD will now automatically be retried to improve the experience when your machine is resumed from sleep. - [#9587](https://github.com/flutter/devtools/pull/9587)

## Inspector updates

[#](#inspector-updates)

* Added a warning banner that the legacy inspector will be removed in a future release. - [#9572](https://github.com/flutter/devtools/pull/9572)

## Memory updates

[#](#memory-updates)

* Fixed an error preventing users from changing the zoom duration. - [#9573](https://github.com/flutter/devtools/pull/9573)

## Deep links tool updates

[#](#deep-links-tool-updates)

* Added a more informative dialog if the Deep Links tool is unable to find build options for the iOS or Android app. - [#9571](https://github.com/flutter/devtools/pull/9571)
* Fixed null error when parsing universal link settings. - [#9581](https://github.com/flutter/devtools/pull/9581)

## Full commit history

[#](#full-commit-history)

To find a complete list of changes in this release, check out the [DevTools git log](https://github.com/flutter/devtools/tree/v2.54.0).

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/release-notes/release-notes-2.54.0.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.54.0\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/release-notes/release-notes-2.54.0.md "Report an issue with this page").

----
url: https://docs.flutter.dev/flutter-for/react-native-devs
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Flutter for React Native developers](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

This document is for React Native (RN) developers looking to apply their existing RN knowledge to build mobile apps with Flutter. If you understand the fundamentals of the RN framework then you can use this document as a way to get started learning Flutter development.

This document can be used as a cookbook by jumping around and finding questions that are most relevant to your needs.

## Introduction to Dart for JavaScript Developers (ES6)

[#](#introduction-to-dart-for-javascript-developers-es6)

Like React Native, Flutter uses reactive-style views. However, while RN transpiles to native widgets, Flutter compiles all the way to native code. Flutter controls each pixel on the screen, which avoids performance problems caused by the need for a JavaScript bridge.

Dart is an easy language to learn and offers the following features:

* Provides an open-source, scalable programming language for building web, server, and mobile apps.
* Provides an object-oriented, single inheritance language that uses a C-style syntax that is AOT-compiled into native.
* Transcompiles optionally into JavaScript.
* Supports interfaces and abstract classes.

A few examples of the differences between JavaScript and Dart are described below.

### Entry point

[#](#entry-point)

JavaScript doesn't have a pre-defined entry function—you define the entry point.

js

```
// JavaScript
function startHere() {
  // Can be used as entry point
}
```

In Dart, every app must have a top-level `main()` function that serves as the entry point to the app.

dart

```
/// Dart
void main() {}
```

### Printing to the console

[#](#printing-to-the-console)

To print to the console in Dart, use `print()`.

js

```
// JavaScript
console.log('Hello world!');
```

dart

```
/// Dart
print('Hello world!');
```

### Variables

[#](#variables)

Dart is type safe—it uses a combination of static type checking and runtime checks to ensure that a variable's value always matches the variable's static type. Although types are mandatory, some type annotations are optional because Dart performs type inference.

#### Creating and assigning variables

[#](#creating-and-assigning-variables)

In JavaScript, variables cannot be typed.

In [Dart](https://dart.dev/dart-2), variables must either be explicitly typed or the type system must infer the proper type automatically.

js

```
// JavaScript
let name = 'JavaScript';
```

dart

```
/// Dart
/// Both variables are acceptable.
String name = 'dart'; // Explicitly typed as a [String].
var otherName = 'Dart'; // Inferred [String] type.
```

For more information, see [Dart's Type System](https://dart.dev/guides/language/sound-dart).

#### Default value

[#](#default-value)

In JavaScript, uninitialized variables are `undefined`.

In Dart, uninitialized variables have an initial value of `null`. Because numbers are objects in Dart, even uninitialized variables with numeric types have the value `null`.

js

```
// JavaScript
let name; // == undefined
```

dart

```
// Dart
var name; // == null; raises a linter warning
int? x; // == null
```

For more information, see the documentation on [variables](https://dart.dev/language/variables).

### Checking for null or zero

[#](#checking-for-null-or-zero)

In JavaScript, values of 1 or any non-null objects are treated as `true` when using the `==` comparison operator.

js

```
// JavaScript
let myNull = null;
if (!myNull) {
  console.log('null is treated as false');
}
let zero = 0;
if (!zero) {
  console.log('0 is treated as false');
}
```

In Dart, only the boolean value `true` is treated as true.

dart

```
/// Dart
var myNull = potentiallyNull();
if (myNull == null) {
  print('use "== null" to check null');
}
var zero = 0;
if (zero == 0) {
  print('use "== 0" to check zero');
}
```

### Functions

[#](#functions)

Dart and JavaScript functions are generally similar. The primary difference is the declaration.

js

```
// JavaScript
function fn() {
  return true;
}
```

dart

```
/// Dart
/// You can explicitly define the return type.
bool fn() {
  return true;
}
```

For more information, see the documentation on [functions](https://dart.dev/language/functions).

### Asynchronous programming

[#](#asynchronous-programming)

#### Futures

[#](#futures)

Like JavaScript, Dart supports single-threaded execution. In JavaScript, the Promise object represents the eventual completion (or failure) of an asynchronous operation and its resulting value.

Dart uses [`Future`](https://dart.dev/tutorials/language/futures) objects to handle this.

js

```
// JavaScript
class Example {
  _getIPAddress() {
    const url = 'https://httpbin.org/ip';
    return fetch(url)
      .then(response => response.json())
      .then(responseJson => {
        const ip = responseJson.origin;
        return ip;
      });
  }
}
​
function main() {
  const example = new Example();
  example
    ._getIPAddress()
    .then(ip => console.log(ip))
    .catch(error => console.error(error));
}
​
main();
```

dart

```
// Dart
import 'dart:convert';
​
import 'package:http/http.dart' as http;
​
class Example {
  Future<String> _getIPAddress() {
    final url = Uri.https('httpbin.org', '/ip');
    return http.get(url).then((response) {
      final ip = jsonDecode(response.body)['origin'] as String;
      return ip;
    });
  }
}
​
void main() {
  final example = Example();
  example
      ._getIPAddress()
      .then((ip) => print(ip))
      .onError<Exception>((error, _) => print(error));
}
```

For more information, see the documentation on [`Future`](https://dart.dev/tutorials/language/futures) objects.

#### `async` and `await`

[#](#async-and-await)

The `async` function declaration defines an asynchronous function.

In JavaScript, the `async` function returns a `Promise`. The `await` operator is used to wait for a `Promise`.

js

```
// JavaScript
class Example {
  async function _getIPAddress() {
    const url = 'https://httpbin.org/ip';
    const response = await fetch(url);
    const json = await response.json();
    const data = json.origin;
    return data;
  }
}
​
async function main() {
  const example = new Example();
  try {
    const ip = await example._getIPAddress();
    console.log(ip);
  } catch (error) {
    console.error(error);
  }
}
​
main();
```

In Dart, an `async` function returns a `Future`, and the body of the function is scheduled for execution later. The `await` operator is used to wait for a `Future`.

dart

```
// Dart
import 'dart:convert';
​
import 'package:http/http.dart' as http;
​
class Example {
  Future<String> _getIPAddress() async {
    final url = Uri.https('httpbin.org', '/ip');
    final response = await http.get(url);
    final ip = jsonDecode(response.body)['origin'] as String;
    return ip;
  }
}
​
/// An async function returns a `Future`.
/// It can also return `void`, unless you use
/// the `avoid_void_async` lint. In that case,
/// return `Future<void>`.
void main() async {
  final example = Example();
  try {
    final ip = await example._getIPAddress();
    print(ip);
  } catch (error) {
    print(error);
  }
}
```

For more information, see the documentation for [async and await](https://dart.dev/language/async).

## The basics

[#](#the-basics)

### How do I create a Flutter app?

[#](#how-do-i-create-a-flutter-app)

To create an app using React Native, you would run `create-react-native-app` from the command line.

```
$ create-react-native-app <projectname>
```

To create an app in Flutter, do one of the following:

* Use an IDE with the Flutter and Dart plugins installed.
* Use the `flutter create` command from the command line. Make sure that the Flutter SDK is in your PATH.

```
$ flutter create <projectname>
```

For more information, see [Getting started](/install), which walks you through creating a button-click counter app. Creating a Flutter project builds all the files that you need to run a sample app on both Android and iOS devices.

### How do I run my app?

[#](#how-do-i-run-my-app)

In React Native, you would run `npm run` or `yarn run` from the project directory.

You can run Flutter apps in a couple of ways:

* Use the "run" option in an IDE with the Flutter and Dart plugins.
* Use `flutter run` from the project's root directory.

Your app runs on a connected device, the iOS simulator, or the Android emulator.

For more information, see the Flutter [Getting started](/install) documentation.

### How do I import widgets?

[#](#how-do-i-import-widgets)

In React Native, you need to import each required component.

js

```
// React Native
import React from 'react';
import { StyleSheet, Text, View } from 'react-native';
```

In Flutter, to use widgets from the Material Design library, import the `material.dart` package. To use iOS style widgets, import the Cupertino library. To use a more basic widget set, import the Widgets library. Or, you can write your own widget library and import that.

dart

```
import 'package:cupertino_ui/cupertino_ui.dart';
import 'package:flutter/widgets.dart';
import 'package:material_ui/material_ui.dart';
import 'package:my_widgets/my_widgets.dart';
```

Whichever widget package you import, Dart pulls in only the widgets that are used in your app.

For more information, see the [Flutter Widget Catalog](/ui/widgets).

### What is the equivalent of the React Native "Hello world!" app in Flutter?

[#](#what-is-the-equivalent-of-the-react-native-hello-world-app-in-flutter)

In React Native, the `HelloWorldApp` class extends `React.Component` and implements the render method by returning a view component.

js

```
// React Native
import React from 'react';
import { StyleSheet, Text, View } from 'react-native';
​
const App = () => {
  return (
    <View style={styles.container}>
      <Text>Hello world!</Text>
    </View>
  );
};
​
const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff',
    alignItems: 'center',
    justifyContent: 'center'
  }
});
​
export default App;
```

In Flutter, you can create an identical "Hello world!" app using the `Center` and `Text` widgets from the core widget library. The `Center` widget becomes the root of the widget tree and has one child, the `Text` widget.

dart

```
// Flutter
import 'package:material_ui/material_ui.dart';
​
void main() {
  runApp(
    const Center(
      child: Text('Hello, world!', textDirection: TextDirection.ltr),
    ),
  );
}
```

The following images show the Android and iOS UI for the basic Flutter "Hello world!" app.

Android

iOS

Now that you've seen the most basic Flutter app, the next section shows how to take advantage of Flutter's rich widget libraries to create a modern, polished app.

### How do I use widgets and nest them to form a widget tree?

[#](#how-do-i-use-widgets-and-nest-them-to-form-a-widget-tree)

In Flutter, almost everything is a widget.

Widgets are the basic building blocks of an app's user interface. You compose widgets into a hierarchy, called a widget tree. Each widget nests inside a parent widget and inherits properties from its parent. Even the application object itself is a widget. There is no separate "application" object. Instead, the root widget serves this role.

A widget can define:

* A structural element—like a button or menu
* A stylistic element—like a font or color scheme
* An aspect of layout—like padding or alignment

The following example shows the "Hello world!" app using widgets from the Material library. In this example, the widget tree is nested inside the `MaterialApp` root widget.

dart

```
// Flutter
import 'package:material_ui/material_ui.dart';
​
void main() => runApp(const MyApp());
​
class MyApp extends StatelessWidget {
  const MyApp({super.key});
​
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Welcome to Flutter',
      home: Scaffold(
        appBar: AppBar(title: const Text('Welcome to Flutter')),
        body: const Center(child: Text('Hello world')),
      ),
    );
  }
}
```

The following images show "Hello world!" built from Material Design widgets. You get more functionality for free than in the basic "Hello world!" app.

Android

iOS

When writing an app, you'll use two types of widgets: [`StatelessWidget`](https://api.flutter.dev/flutter/widgets/StatelessWidget-class.html) or [`StatefulWidget`](https://api.flutter.dev/flutter/widgets/StatefulWidget-class.html). A `StatelessWidget` is just what it sounds like—a widget with no state. A `StatelessWidget` is created once, and never changes its appearance. A `StatefulWidget` dynamically changes state based on data received, or user input.

The important difference between stateless and stateful widgets is that `StatefulWidget`s have a `State` object that stores state data and carries it over across tree rebuilds, so it's not lost.

In simple or basic apps it's easy to nest widgets, but as the code base gets larger and the app becomes complex, you should break deeply nested widgets into functions that return the widget or smaller classes. Creating separate functions and widgets allows you to reuse the components within the app.

### How do I create reusable components?

[#](#how-do-i-create-reusable-components)

In React Native, you would define a function (or a class) to create a reusable component and then use `props` methods to set or return properties and values of the selected elements. In the example below, the `CustomCard` function is defined and then used inside a parent component.

js

```
// React Native
const CustomCard = ({ index, onPress }) => {
  return (
    <View>
      <Text> Card {index} </Text>
      <Button
        title="Press"
        onPress={() => onPress(index)}
      />
    </View>
  );
};
​
// Usage
<CustomCard onPress={this.onPress} index={item.key} />
```

In Flutter, define a class to create a custom widget and then reuse the widget. You can also define and call a function that returns a reusable widget as shown in the `build` function in the following example.

dart

```
/// Flutter
class CustomCard extends StatelessWidget {
  const CustomCard({super.key, required this.index, required this.onPress});
​
  final int index;
  final void Function() onPress;
​
  @override
  Widget build(BuildContext context) {
    return Card(
      child: Column(
        children: <Widget>[
          Text('Card $index'),
          TextButton(onPressed: onPress, child: const Text('Press')),
        ],
      ),
    );
  }
}
​
class UseCard extends StatelessWidget {
  const UseCard({super.key, required this.index});
​
  final int index;
​
  @override
  Widget build(BuildContext context) {
    /// Usage
    return CustomCard(
      index: index,
      onPress: () {
        print('Card $index');
      },
    );
  }
}
```

In the previous example, the constructor for the `CustomCard` class uses Dart's curly brace syntax `{ }` to indicate [named parameters](https://dart.dev/language/functions#named-parameters).

To require these fields, either remove the curly braces from the constructor, or add `required` to the constructor.

The following screenshots show an example of the reusable `CustomCard` class.

Android

iOS

## Project structure and resources

[#](#project-structure-and-resources)

### Where do I start writing the code?

[#](#where-do-i-start-writing-the-code)

Start with the `lib/main.dart` file. It's autogenerated when you create a Flutter app.

dart

```
// Dart
void main() {
  print('Hello, this is the main function.');
}
```

In Flutter, the entry point file is `{project_name}/lib/main.dart` and execution starts from the `main` function.

### How are files structured in a Flutter app?

[#](#how-are-files-structured-in-a-flutter-app)

When you create a new Flutter project, it builds the following directory structure. You can customize it later, but this is where you start.

* \<project\_name>/

  * android/// Contains Android-specific files.

  * build/// Stores iOS and Android build files.

  * ios/// Contains iOS-specific files.

  * lib/// Contains externally accessible Dart source files.

    * src/// Contains additional source files.
    * main.dart// The Flutter entry point and the start of a new app. This is generated automatically when you create a Flutter project. It's where you start writing your Dart code.

  * test/// Contains automated test files.

  * pubspec.yaml// Contains the metadata for the Flutter app. This is equivalent to the package.json file in React Native.

### Where do I put my resources and assets and how do I use them?

[#](#where-do-i-put-my-resources-and-assets-and-how-do-i-use-them)

A Flutter resource or asset is a file that is bundled and deployed with your app and is accessible at runtime. Flutter apps can include the following asset types:

* Static data such as JSON files
* Configuration files
* Icons and images (JPEG, PNG, GIF, Animated GIF, WebP, Animated WebP, BMP, and WBMP)

Flutter uses the `pubspec.yaml` file, located at the root of your project, to identify assets required by an app.

yaml

```
flutter:
  assets:
    - assets/my_icon.png
    - assets/background.png
```

The `assets` subsection specifies files that should be included with the app. Each asset is identified by an explicit path relative to the `pubspec.yaml` file, where the asset file is located. The order in which the assets are declared does not matter. The actual directory used (`assets` in this case) does not matter. However, while assets can be placed in any app directory, it's a best practice to place them in the `assets` directory.

During a build, Flutter places assets into a special archive called the *asset bundle*, which apps read from at runtime. When an asset's path is specified in the assets' section of `pubspec.yaml`, the build process looks for any files with the same name in adjacent subdirectories. These files are also included in the asset bundle along with the specified asset. Flutter uses asset variants when choosing resolution-appropriate images for your app.

In React Native, you would add a static image by placing the image file in a source code directory and referencing it.

js

```
<Image source={require('./my-icon.png')} />
// OR
<Image
  source={{
    url: 'https://reactnative.dev/img/tiny_logo.png'
  }}
/>
```

In Flutter, add a static image to your app using the `Image.asset` constructor in a widget's build method.

dart

```
Image.asset('assets/background.png');
```

For more information, see [Adding Assets and Images in Flutter](/ui/assets/assets-and-images).

### How do I load images over a network?

[#](#how-do-i-load-images-over-a-network)

In React Native, you would specify the `uri` in the `source` prop of the `Image` component and also provide the size if needed.

In Flutter, use the `Image.network` constructor to include an image from a URL.

dart

```
Image.network('https://docs.flutter.dev/assets/images/docs/owl.jpg');
```

### How do I install packages and package plugins?

[#](#how-do-i-install-packages-and-package-plugins)

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. Packages that contain platform-specific code are known as package plugins.

In React Native, you would use `yarn add {package-name}` or `npm install --save {package-name}` to install packages from the command line.

In Flutter, install a package using the following instructions:

1. To add the `google_sign_in` package as a dependency, run `flutter pub add`:

```
$ flutter pub add google_sign_in
```

2. Install the package from the command line by using `flutter pub get`. If using an IDE, it often runs `flutter pub get` for you, or it might prompt you to do so.
3. Import the package into your app code as shown below:

dart

```
import 'package:material_ui/material_ui.dart';
```

For more information, see [Using Packages](/packages-and-plugins/using-packages) and [Developing Packages & Plugins](/packages-and-plugins/developing-packages).

You can find many packages shared by Flutter developers in the [Flutter packages](https://pub.dev/flutter/) section of [pub.dev](https://pub.dev).

## Flutter widgets

[#](#flutter-widgets)

In Flutter, you build your UI out of widgets that describe what their view should look like given their current configuration and state.

Widgets are often composed of many small, single-purpose widgets that are nested to produce powerful effects. For example, the `Container` widget consists of several widgets responsible for layout, painting, positioning, and sizing. Specifically, the `Container` widget includes the `LimitedBox`, `ConstrainedBox`, `Align`, `Padding`, `DecoratedBox`, and `Transform` widgets. Rather than subclassing `Container` to produce a customized effect, you can compose these and other simple widgets in new and unique ways.

The `Center` widget is another example of how you can control the layout. To center a widget, wrap it in a `Center` widget and then use layout widgets for alignment, row, columns, and grids. These layout widgets do not have a visual representation of their own. Instead, their sole purpose is to control some aspect of another widget's layout. To understand why a widget renders in a certain way, it's often helpful to inspect the neighboring widgets.

For more information, see the [Flutter Technical Overview](/resources/architectural-overview).

For more information about the core widgets from the `Widgets` package, see [Flutter Basic Widgets](/ui/widgets/basics), the [Flutter Widget Catalog](/ui/widgets), or the [Flutter Widget Index](/reference/widgets).

## Views

[#](#views)

### What is the equivalent of the `View` container?

[#](#what-is-the-equivalent-of-the-view-container)

In React Native, `View` is a container that supports layout with `Flexbox`, style, touch handling, and accessibility controls.

In Flutter, you can use the core layout widgets in the `Widgets` library, such as [`Container`](https://api.flutter.dev/flutter/widgets/Container-class.html), [`Column`](https://api.flutter.dev/flutter/widgets/Column-class.html), [`Row`](https://api.flutter.dev/flutter/widgets/Row-class.html), and [`Center`](https://api.flutter.dev/flutter/widgets/Center-class.html). For more information, see the [Layout Widgets](/ui/widgets/layout) catalog.

### What is the equivalent of `FlatList` or `SectionList`?

[#](#what-is-the-equivalent-of-flatlist-or-sectionlist)

A `List` is a scrollable list of components arranged vertically.

In React Native, `FlatList` or `SectionList` are used to render simple or sectioned lists.

js

```
// React Native
<FlatList
  data={[ ... ]}
  renderItem={({ item }) => <Text>{item.key}</Text>}
/>
```

[`ListView`](https://api.flutter.dev/flutter/widgets/ListView-class.html) is Flutter's most commonly used scrolling widget. The default constructor takes an explicit list of children. [`ListView`](https://api.flutter.dev/flutter/widgets/ListView-class.html) is most appropriate for a small number of widgets. For a large or infinite list, use `ListView.builder`, which builds its children on demand and only builds those children that are visible.

dart

```
var data = ['Hello', 'World'];
return ListView.builder(
  itemCount: data.length,
  itemBuilder: (context, index) {
    return Text(data[index]);
  },
);
```

Android

iOS

To learn how to implement an infinite scrolling list, see the official [`infinite_list`](https://github.com/flutter/samples/tree/main/infinite_list) sample.

### How do I use a Canvas to draw or paint?

[#](#how-do-i-use-a-canvas-to-draw-or-paint)

In React Native, canvas components aren't present so third party libraries like `react-native-canvas` are used.

js

```
// React Native
const CanvasComp = () => {
  const handleCanvas = (canvas) => {
    const ctx = canvas.getContext('2d');
    ctx.fillStyle = 'skyblue';
    ctx.beginPath();
    ctx.arc(75, 75, 50, 0, 2 * Math.PI);
    ctx.fillRect(150, 100, 300, 300);
    ctx.stroke();
  };
​
  return (
    <View>
      <Canvas ref={this.handleCanvas} />
    </View>
  );
}
```

In Flutter, you can use the [`CustomPaint`](https://api.flutter.dev/flutter/widgets/CustomPaint-class.html) and [`CustomPainter`](https://api.flutter.dev/flutter/rendering/CustomPainter-class.html) classes to draw to the canvas.

The following example shows how to draw during the paint phase using the `CustomPaint` widget. It implements the abstract class, `CustomPainter`, and passes it to `CustomPaint`'s painter property. `CustomPaint` subclasses must implement the `paint()` and `shouldRepaint()` methods.

dart

```
class MyCanvasPainter extends CustomPainter {
  const MyCanvasPainter();
​
  @override
  void paint(Canvas canvas, Size size) {
    final Paint paint = Paint()..color = Colors.amber;
    canvas.drawCircle(const Offset(100, 200), 40, paint);
    final Paint paintRect = Paint()..color = Colors.lightBlue;
    final Rect rect = Rect.fromPoints(
      const Offset(150, 300),
      const Offset(300, 400),
    );
    canvas.drawRect(rect, paintRect);
  }
​
  @override
  bool shouldRepaint(MyCanvasPainter oldDelegate) => false;
}
​
class MyCanvasWidget extends StatelessWidget {
  const MyCanvasWidget({super.key});
​
  @override
  Widget build(BuildContext context) {
    return const Scaffold(body: CustomPaint(painter: MyCanvasPainter()));
  }
}
```

Android

iOS

## Layouts

[#](#layouts)

### How do I use widgets to define layout properties?

[#](#how-do-i-use-widgets-to-define-layout-properties)

In React Native, most of the layout can be done with the props that are passed to a specific component. For example, you could use the `style` prop on the `View` component in order to specify the flexbox properties. To arrange your components in a column, you would specify a prop such as: `flexDirection: 'column'`.

js

```
// React Native
<View
  style={{
    flex: 1,
    flexDirection: 'column',
    justifyContent: 'space-between',
    alignItems: 'center'
  }}
>
```

In Flutter, the layout is primarily defined by widgets specifically designed to provide layout, combined with control widgets and their style properties.

For example, the [`Column`](https://api.flutter.dev/flutter/widgets/Column-class.html) and [`Row`](https://api.flutter.dev/flutter/widgets/Row-class.html) widgets take an array of children and align them vertically and horizontally respectively. A [`Container`](https://api.flutter.dev/flutter/widgets/Container-class.html) widget takes a combination of layout and styling properties, and a [`Center`](https://api.flutter.dev/flutter/widgets/Center-class.html) widget centers its child widgets.

dart

```
@override
Widget build(BuildContext context) {
  return Center(
    child: Column(
      children: <Widget>[
        Container(color: Colors.red, width: 100, height: 100),
        Container(color: Colors.blue, width: 100, height: 100),
        Container(color: Colors.green, width: 100, height: 100),
      ],
    ),
  );
```

Flutter provides a variety of layout widgets in its core widget library. For example, [`Padding`](https://api.flutter.dev/flutter/widgets/Padding-class.html), [`Align`](https://api.flutter.dev/flutter/widgets/Align-class.html), and [`Stack`](https://api.flutter.dev/flutter/widgets/Stack-class.html).

For a complete list, see [Layout Widgets](/ui/widgets/layout).

Android

iOS

### How do I layer widgets?

[#](#how-do-i-layer-widgets)

In React Native, components can be layered using `absolute` positioning.

Flutter uses the [`Stack`](https://api.flutter.dev/flutter/widgets/Stack-class.html) widget to arrange children widgets in layers. The widgets can entirely or partially overlap the base widget.

The `Stack` widget positions its children relative to the edges of its box. This class is useful if you simply want to overlap several children widgets.

dart

```
@override
Widget build(BuildContext context) {
  return Stack(
    alignment: const Alignment(0.6, 0.6),
    children: <Widget>[
      const CircleAvatar(
        backgroundImage: NetworkImage(
          'https://avatars3.githubusercontent.com/u/14101776?v=4',
        ),
      ),
      Container(color: Colors.black45, child: const Text('Flutter')),
    ],
  );
```

The previous example uses `Stack` to overlay a Container (that displays its `Text` on a translucent black background) on top of a `CircleAvatar`. The Stack offsets the text using the alignment property and `Alignment` coordinates.

Android

iOS

For more information, see the [`Stack`](https://api.flutter.dev/flutter/widgets/Stack-class.html) class documentation.

## Styling

[#](#styling)

### How do I style my components?

[#](#how-do-i-style-my-components)

In React Native, inline styling and `stylesheets.create` are used to style components.

js

```
// React Native
<View style={styles.container}>
  <Text style={{ fontSize: 32, color: 'cyan', fontWeight: '600' }}>
    This is a sample text
  </Text>
</View>
​
const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff',
    alignItems: 'center',
    justifyContent: 'center'
  }
});
```

In Flutter, a `Text` widget can take a `TextStyle` class for its style property. If you want to use the same text style in multiple places, you can create a [`TextStyle`](https://api.flutter.dev/flutter/dart-ui/TextStyle-class.html) class and use it for multiple `Text` widgets.

dart

```
const TextStyle textStyle = TextStyle(
  color: Colors.cyan,
  fontSize: 32,
  fontWeight: FontWeight.w600,
);
​
return const Center(
  child: Column(
    children: <Widget>[
      Text('Sample text', style: textStyle),
      Padding(
        padding: EdgeInsets.all(20),
        child: Icon(
          Icons.lightbulb_outline,
          size: 48,
          color: Colors.redAccent,
        ),
      ),
    ],
  ),
);
```

Android

iOS

### How do I use `Icons` and `Colors`?

[#](#how-do-i-use-icons-and-colors)

React Native doesn't include support for icons so third party libraries are used.

In Flutter, importing the Material library also pulls in the rich set of [Material icons](https://pub.dev/documentation/material_ui/latest/material_ui/Icons-class.html) and [colors](https://pub.dev/documentation/material_ui/latest/material_ui/Colors-class.html).

dart

```
return const Icon(Icons.lightbulb_outline, color: Colors.redAccent);
```

When using the `Icons` class, make sure to set `uses-material-design: true` in the project's `pubspec.yaml` file. This ensures that the `MaterialIcons` font, which displays the icons, is included in your app. In general, if you intend to use the Material library, you should include this line.

yaml

```
name: my_awesome_application
flutter:
  uses-material-design: true
```

Flutter's [Cupertino (iOS-style)](/ui/widgets/cupertino) package provides high fidelity widgets for the current iOS design language. To use the `CupertinoIcons` font, add a dependency for `cupertino_icons` in your project's `pubspec.yaml` file.

yaml

```
name: my_awesome_application
dependencies:
  cupertino_icons: ^1.0.8
```

To globally customize the colors and styles of components, use `ThemeData` to specify default colors for various aspects of the theme. Set the theme property in `MaterialApp` to the `ThemeData` object. The [`Colors`](https://pub.dev/documentation/material_ui/latest/material_ui/Colors-class.html) class provides colors from the Material Design [color palette](https://m2.material.io/design/color/the-color-system.html#color-theme-creation).

The following example sets the color scheme from seed to `deepPurple` and the text selection to `red`.

dart

```
class SampleApp extends StatelessWidget {
  const SampleApp({super.key});
​
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Sample App',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        textSelectionTheme: const TextSelectionThemeData(
          selectionColor: Colors.red,
        ),
      ),
      home: const SampleAppPage(),
    );
  }
}
```

### How do I add style themes?

[#](#how-do-i-add-style-themes)

In React Native, common themes are defined for components in stylesheets and then used in components.

In Flutter, create uniform styling for almost everything by defining the styling in the [`ThemeData`](https://pub.dev/documentation/material_ui/latest/material_ui/ThemeData-class.html) class and passing it to the theme property in the [`MaterialApp`](https://pub.dev/documentation/material_ui/latest/material_ui/MaterialApp-class.html) widget.

dart

```
@override
Widget build(BuildContext context) {
  return MaterialApp(
    theme: ThemeData(primaryColor: Colors.cyan, brightness: Brightness.dark),
    home: const StylingPage(),
  );
}
```

A `Theme` can be applied even without using the `MaterialApp` widget. The [`Theme`](https://pub.dev/documentation/material_ui/latest/material_ui/Theme-class.html) widget takes a `ThemeData` in its `data` parameter and applies the `ThemeData` to all of its children widgets.

dart

```
@override
Widget build(BuildContext context) {
  return Theme(
    data: ThemeData(primaryColor: Colors.cyan, brightness: brightness),
    child: Scaffold(
      backgroundColor: Theme.of(context).primaryColor,
      //...
    ),
  );
}
```

## State management

[#](#state-management)

State is information that can be read synchronously when a widget is built or information that might change during the lifetime of a widget. To manage app state in Flutter, use a [`StatefulWidget`](https://api.flutter.dev/flutter/widgets/StatefulWidget-class.html) paired with a State object.

For more information on ways to approach managing state in Flutter, see [State management](/data-and-backend/state-mgmt).

### The StatelessWidget

[#](#the-statelesswidget)

A `StatelessWidget` in Flutter is a widget that doesn't require a state change— it has no internal state to manage.

Stateless widgets are useful when the part of the user interface you are describing does not depend on anything other than the configuration information in the object itself and the [`BuildContext`](https://api.flutter.dev/flutter/widgets/BuildContext-class.html) in which the widget is inflated.

[`AboutDialog`](https://pub.dev/documentation/material_ui/latest/material_ui/AboutDialog-class.html), [`CircleAvatar`](https://pub.dev/documentation/material_ui/latest/material_ui/CircleAvatar-class.html), and [`Text`](https://api.flutter.dev/flutter/widgets/Text-class.html) are examples of stateless widgets that subclass [`StatelessWidget`](https://api.flutter.dev/flutter/widgets/StatelessWidget-class.html).

dart

```
import 'package:material_ui/material_ui.dart';
​
void main() => runApp(
  const MyStatelessWidget(
    text: 'StatelessWidget Example to show immutable data',
  ),
);
​
class MyStatelessWidget extends StatelessWidget {
  const MyStatelessWidget({super.key, required this.text});
​
  final String text;
​
  @override
  Widget build(BuildContext context) {
    return Center(child: Text(text, textDirection: TextDirection.ltr));
  }
}
```

The previous example uses the constructor of the `MyStatelessWidget` class to pass the `text`, which is marked as `final`. This class extends `StatelessWidget`\&mdash;it contains immutable data.

The `build` method of a stateless widget is typically called in only three situations:

* When the widget is inserted into a tree
* When the widget's parent changes its configuration
* When an [`InheritedWidget`](https://api.flutter.dev/flutter/widgets/InheritedWidget-class.html) it depends on, changes

### The StatefulWidget

[#](#the-statefulwidget)

A [`StatefulWidget`](https://api.flutter.dev/flutter/widgets/StatefulWidget-class.html) is a widget that changes state. Use the `setState` method to manage the state changes for a `StatefulWidget`. A call to `setState()` tells the Flutter framework that something has changed in a state, which causes an app to rerun the `build()` method so that the app can reflect the change.

*State* is information that can be read synchronously when a widget is built and might change during the lifetime of the widget. It's the responsibility of the widget implementer to ensure that the state object is promptly notified when the state changes. Use `StatefulWidget` when a widget can change dynamically. For example, the state of the widget changes by typing into a form, or moving a slider. Or, it can change over time—perhaps a data feed updates the UI.

[`Checkbox`](https://pub.dev/documentation/material_ui/latest/material_ui/Checkbox-class.html), [`Radio`](https://pub.dev/documentation/material_ui/latest/material_ui/Radio-class.html), [`Slider`](https://pub.dev/documentation/material_ui/latest/material_ui/Slider-class.html), [`InkWell`](https://pub.dev/documentation/material_ui/latest/material_ui/InkWell-class.html), [`Form`](https://api.flutter.dev/flutter/widgets/Form-class.html), and [`TextField`](https://pub.dev/documentation/material_ui/latest/material_ui/TextField-class.html) are examples of stateful widgets that subclass [`StatefulWidget`](https://api.flutter.dev/flutter/widgets/StatefulWidget-class.html).

The following example declares a `StatefulWidget` that requires a `createState()` method. This method creates the state object that manages the widget's state, `_MyStatefulWidgetState`.

dart

```
class MyStatefulWidget extends StatefulWidget {
  const MyStatefulWidget({super.key, required this.title});
​
  final String title;
​
  @override
  State<MyStatefulWidget> createState() => _MyStatefulWidgetState();
}
```

The following state class, `_MyStatefulWidgetState`, implements the `build()` method for the widget. When the state changes, for example, when the user toggles the button, `setState()` is called with the new toggle value. This causes the framework to rebuild this widget in the UI.

dart

```
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
  bool showText = true;
  bool toggleState = true;
  Timer? t2;
​
  void toggleBlinkState() {
    setState(() {
      toggleState = !toggleState;
    });
    if (!toggleState) {
      t2 = Timer.periodic(const Duration(milliseconds: 1000), (t) {
        toggleShowText();
      });
    } else {
      t2?.cancel();
    }
  }
​
  void toggleShowText() {
    setState(() {
      showText = !showText;
    });
  }
​
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          children: <Widget>[
            if (showText)
              const Text('This execution will be done before you can blink.'),
            Padding(
              padding: const EdgeInsets.only(top: 70),
              child: ElevatedButton(
                onPressed: toggleBlinkState,
                child: toggleState
                    ? const Text('Blink')
                    : const Text('Stop Blinking'),
              ),
            ),
          ],
        ),
      ),
    );
  }
}
```

### What are the StatefulWidget and StatelessWidget best practices?

[#](#what-are-the-statefulwidget-and-statelesswidget-best-practices)

Here are a few things to consider when designing your widget.

1. Determine whether a widget should be a `StatefulWidget` or a `StatelessWidget`.

In Flutter, widgets are either Stateful or Stateless—depending on whether they depend on a state change.

* If a widget changes—the user interacts with it or a data feed interrupts the UI, then it's *Stateful*.
* If a widget is final or immutable, then it's *Stateless*.

2. Determine which object manages the widget's state (for a `StatefulWidget`).

In Flutter, there are three primary ways to manage state:

* The widget manages its own state
* The parent widget manages the widget's state
* A mix-and-match approach

When deciding which approach to use, consider the following principles:

* If the state in question is user data, for example the checked or unchecked mode of a checkbox, or the position of a slider, then the state is best managed by the parent widget.
* If the state in question is aesthetic, for example an animation, then the widget itself best manages the state.
* When in doubt, let the parent widget manage the child widget's state.

3. Subclass StatefulWidget and State.

The `MyStatefulWidget` class manages its own state—it extends `StatefulWidget`, it overrides the `createState()` method to create the `State` object, and the framework calls `createState()` to build the widget. In this example, `createState()` creates an instance of `_MyStatefulWidgetState`, which is implemented in the next best practice.

dart

```
class MyStatefulWidget extends StatefulWidget {
  const MyStatefulWidget({super.key, required this.title});
​
  final String title;
  @override
  State<MyStatefulWidget> createState() => _MyStatefulWidgetState();
}
​
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
  @override
  Widget build(BuildContext context) {
    //...
  }
}
```

4. Add the StatefulWidget into the widget tree.

Add your custom `StatefulWidget` to the widget tree in the app's build method.

dart

```
class MyStatelessWidget extends StatelessWidget {
  // This widget is the root of your application.
  const MyStatelessWidget({super.key});
​
  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      title: 'Flutter Demo',
      home: MyStatefulWidget(title: 'State Change Demo'),
    );
  }
}
```

Android

iOS

## Props

[#](#props)

In React Native, most components can be customized when they are created with different parameters or properties, called `props`. These parameters can be used in a child component using `this.props`.

js

```
// React Native
const CustomCard = ({ index, onPress }) => {
  return (
    <View>
      <Text> Card {index} </Text>
      <Button
        title='Press'
        onPress={() => onPress(index)}
      />
    </View>
  );
};
​
const App = () => {
  const onPress = (index) => {
    console.log('Card ', index);
  };
​
  return (
    <View>
      <FlatList
        data={[ /* ... */ ]}
        renderItem={({ item }) => (
          <CustomCard onPress={onPress} index={item.key} />
        )}
      />
    </View>
  );
};
```

In Flutter, you assign a local variable or function marked `final` with the property received in the parameterized constructor.

dart

```
/// Flutter
class CustomCard extends StatelessWidget {
  const CustomCard({super.key, required this.index, required this.onPress});
​
  final int index;
  final void Function() onPress;
​
  @override
  Widget build(BuildContext context) {
    return Card(
      child: Column(
        children: <Widget>[
          Text('Card $index'),
          TextButton(onPressed: onPress, child: const Text('Press')),
        ],
      ),
    );
  }
}
​
class UseCard extends StatelessWidget {
  const UseCard({super.key, required this.index});
​
  final int index;
​
  @override
  Widget build(BuildContext context) {
    /// Usage
    return CustomCard(
      index: index,
      onPress: () {
        print('Card $index');
      },
    );
  }
}
```

Android

iOS

## Local storage

[#](#local-storage)

If you don't need to store a lot of data, and it doesn't require structure, you can use `shared_preferences` which allows you to read and write persistent key-value pairs of primitive data types: booleans, floats, ints, longs, and strings.

### How do I store persistent key-value pairs that are global to the app?

[#](#how-do-i-store-persistent-key-value-pairs-that-are-global-to-the-app)

In React Native, you use the `setItem` and `getItem` functions of the `AsyncStorage` component to store and retrieve data that is persistent and global to the app.

js

```
// React Native
const [counter, setCounter] = useState(0)
...
await AsyncStorage.setItem( 'counterkey', json.stringify(++this.state.counter));
AsyncStorage.getItem('counterkey').then(value => {
  if (value != null) {
    setCounter(value);
  }
});
```

In Flutter, use the [`shared_preferences`](https://github.com/flutter/packages/tree/main/packages/shared_preferences/shared_preferences) plugin to store and retrieve key-value data that is persistent and global to the app. The `shared_preferences` plugin wraps `NSUserDefaults` on iOS and `SharedPreferences` on Android, providing a persistent store for simple data.

To add the `shared_preferences` package as a dependency, run `flutter pub add`:

```
$ flutter pub add shared_preferences
```

dart

```
import 'package:shared_preferences/shared_preferences.dart';
```

To implement persistent data, use the setter methods provided by the `SharedPreferences` class. Setter methods are available for various primitive types, such as `setInt`, `setBool`, and `setString`. To read data, use the appropriate getter method provided by the `SharedPreferences` class. For each setter there is a corresponding getter method, for example, `getInt`, `getBool`, and `getString`.

dart

```
Future<void> updateCounter() async {
  final prefs = await SharedPreferences.getInstance();
  int? counter = prefs.getInt('counter');
  if (counter is int) {
    await prefs.setInt('counter', ++counter);
  }
  setState(() {
    _counter = counter;
  });
}
```

## Routing

[#](#routing)

Most apps contain several screens for displaying different types of information. For example, you might have a product screen that displays images where users could tap on a product image to get more information about the product on a new screen.

In Android, new screens are new Activities. In iOS, new screens are new ViewControllers. In Flutter, screens are just Widgets! And to navigate to new screens in Flutter, use the Navigator widget.

### How do I navigate between screens?

[#](#how-do-i-navigate-between-screens)

In React Native, there are three main navigators: StackNavigator, TabNavigator, and DrawerNavigator. Each provides a way to configure and define the screens.

js

```
// React Native
const MyApp = TabNavigator(
  { Home: { screen: HomeScreen }, Notifications: { screen: tabNavScreen } },
  { tabBarOptions: { activeTintColor: '#e91e63' } }
);
const SimpleApp = StackNavigator({
  Home: { screen: MyApp },
  stackScreen: { screen: StackScreen }
});
export default (MyApp1 = DrawerNavigator({
  Home: {
    screen: SimpleApp
  },
  Screen2: {
    screen: drawerScreen
  }
}));
```

In Flutter, there are two main widgets used to navigate between screens:

* A [`Route`](https://api.flutter.dev/flutter/widgets/Route-class.html) is an abstraction for an app screen or page.
* A [`Navigator`](https://api.flutter.dev/flutter/widgets/Navigator-class.html) is a widget that manages routes.

A `Navigator` is defined as a widget that manages a set of child widgets with a stack discipline. The navigator manages a stack of `Route` objects and provides methods for managing the stack, like [`Navigator.push`](https://api.flutter.dev/flutter/widgets/Navigator/push.html) and [`Navigator.pop`](https://api.flutter.dev/flutter/widgets/Navigator/pop.html). A list of routes might be specified in the [`MaterialApp`](https://pub.dev/documentation/material_ui/latest/material_ui/MaterialApp-class.html) widget, or they might be built on the fly, for example, in hero animations. The following example specifies named routes in the `MaterialApp` widget.

dart

```
class NavigationApp extends StatelessWidget {
  // This widget is the root of your application.
  const NavigationApp({super.key});
​
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      //...
      routes: <String, WidgetBuilder>{
        '/a': (context) => const UsualNavScreen(),
        '/b': (context) => const DrawerNavScreen(),
      },
      //...
    );
  }
}
```

To navigate to a named route, the [`Navigator.of()`](https://api.flutter.dev/flutter/widgets/Navigator/of.html) method is used to specify the `BuildContext` (a handle to the location of a widget in the widget tree). The name of the route is passed to the `pushNamed` function to navigate to the specified route.

dart

```
Navigator.of(context).pushNamed('/a');
```

You can also use the push method of `Navigator` which adds the given [`Route`](https://api.flutter.dev/flutter/widgets/Route-class.html) to the history of the navigator that most tightly encloses the given [`BuildContext`](https://api.flutter.dev/flutter/widgets/BuildContext-class.html), and transitions to it. In the following example, the [`MaterialPageRoute`](https://pub.dev/documentation/material_ui/latest/material_ui/MaterialPageRoute-class.html) widget is a modal route that replaces the entire screen with a platform-adaptive transition. It takes a [`WidgetBuilder`](https://api.flutter.dev/flutter/widgets/WidgetBuilder.html) as a required parameter.

dart

```
Navigator.push(
  context,
  MaterialPageRoute<void>(builder: (context) => const UsualNavScreen()),
);
```

### How do I use tab navigation and drawer navigation?

[#](#how-do-i-use-tab-navigation-and-drawer-navigation)

In Material Design apps, there are two primary options for Flutter navigation: tabs and drawers. When there is insufficient space to support tabs, drawers provide a good alternative.

#### Tab navigation

[#](#tab-navigation)

In React Native, `createBottomTabNavigator` and `TabNavigation` are used to show tabs and for tab navigation.

js

```
// React Native
import { createBottomTabNavigator } from 'react-navigation';
​
const MyApp = TabNavigator(
  { Home: { screen: HomeScreen }, Notifications: { screen: tabNavScreen } },
  { tabBarOptions: { activeTintColor: '#e91e63' } }
);
```

Flutter provides several specialized widgets for drawer and tab navigation:

* [`TabController`](https://pub.dev/documentation/material_ui/latest/material_ui/TabController-class.html)

  Coordinates the tab selection between a `TabBar` and a `TabBarView`.

* [`TabBar`](https://pub.dev/documentation/material_ui/latest/material_ui/TabBar-class.html)

  Displays a horizontal row of tabs.

* [`Tab`](https://pub.dev/documentation/material_ui/latest/material_ui/Tab-class.html)

  Creates a material design TabBar tab.

* [`TabBarView`](https://pub.dev/documentation/material_ui/latest/material_ui/TabBarView-class.html)

  Displays the widget that corresponds to the currently selected tab.

dart

```
class _MyAppState extends State<MyApp> with SingleTickerProviderStateMixin {
  late TabController controller = TabController(length: 2, vsync: this);
​
  @override
  Widget build(BuildContext context) {
    return TabBar(
      controller: controller,
      tabs: const <Tab>[
        Tab(icon: Icon(Icons.person)),
        Tab(icon: Icon(Icons.email)),
      ],
    );
  }
}
```

A `TabController` is required to coordinate the tab selection between a `TabBar` and a `TabBarView`. The `TabController` constructor `length` argument is the total number of tabs. A `TickerProvider` is required to trigger the notification whenever a frame triggers a state change. The `TickerProvider` is `vsync`. Pass the `vsync: this` argument to the `TabController` constructor whenever you create a new `TabController`.

The [`TickerProvider`](https://api.flutter.dev/flutter/scheduler/TickerProvider-class.html) is an interface implemented by classes that can vend [`Ticker`](https://api.flutter.dev/flutter/scheduler/Ticker-class.html) objects. Tickers can be used by any object that must be notified whenever a frame triggers, but they're most commonly used indirectly via an [`AnimationController`](https://api.flutter.dev/flutter/animation/AnimationController-class.html). `AnimationController`s need a `TickerProvider` to obtain their `Ticker`. If you are creating an AnimationController from a State, then you can use the [`TickerProviderStateMixin`](https://api.flutter.dev/flutter/widgets/TickerProviderStateMixin-mixin.html) or [`SingleTickerProviderStateMixin`](https://api.flutter.dev/flutter/widgets/SingleTickerProviderStateMixin-mixin.html) classes to obtain a suitable `TickerProvider`.

The [`Scaffold`](https://pub.dev/documentation/material_ui/latest/material_ui/Scaffold-class.html) widget wraps a new `TabBar` widget and creates two tabs. The `TabBarView` widget is passed as the `body` parameter of the `Scaffold` widget. All screens corresponding to the `TabBar` widget's tabs are children to the `TabBarView` widget along with the same `TabController`.

dart

```
class _NavigationHomePageState extends State<NavigationHomePage>
    with SingleTickerProviderStateMixin {
  late TabController controller = TabController(length: 2, vsync: this);
​
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      bottomNavigationBar: Material(
        color: Colors.blue,
        child: TabBar(
          tabs: const <Tab>[
            Tab(icon: Icon(Icons.person)),
            Tab(icon: Icon(Icons.email)),
          ],
          controller: controller,
        ),
      ),
      body: TabBarView(
        controller: controller,
        children: const <Widget>[HomeScreen(), TabScreen()],
      ),
    );
  }
}
```

#### Drawer navigation

[#](#drawer-navigation)

In React Native, import the needed react-navigation packages and then use `createDrawerNavigator` and `DrawerNavigation`.

js

```
// React Native
export default (MyApp1 = DrawerNavigator({
  Home: {
    screen: SimpleApp
  },
  Screen2: {
    screen: drawerScreen
  }
}));
```

In Flutter, we can use the `Drawer` widget in combination with a `Scaffold` to create a layout with a Material Design drawer. To add a `Drawer` to an app, wrap it in a `Scaffold` widget. The `Scaffold` widget provides a consistent visual structure to apps that follow the [Material Design](https://m3.material.io/styles) guidelines. It also supports special Material Design components, such as `Drawers`, `AppBars`, and `SnackBars`.

The `Drawer` widget is a Material Design panel that slides in horizontally from the edge of a `Scaffold` to show navigation links in an application. You can provide a [`ElevatedButton`](https://pub.dev/documentation/material_ui/latest/material_ui/ElevatedButton-class.html), a [`Text`](https://api.flutter.dev/flutter/widgets/Text-class.html) widget, or a list of items to display as the child to the `Drawer` widget. In the following example, the [`ListTile`](https://pub.dev/documentation/material_ui/latest/material_ui/ListTile-class.html) widget provides the navigation on tap.

dart

```
@override
Widget build(BuildContext context) {
  return Drawer(
    elevation: 20,
    child: ListTile(
      leading: const Icon(Icons.change_history),
      title: const Text('Screen2'),
      onTap: () {
        Navigator.of(context).pushNamed('/b');
      },
    ),
  );
}
```

The `Scaffold` widget also includes an `AppBar` widget that automatically displays an appropriate IconButton to show the `Drawer` when a Drawer is available in the `Scaffold`. The `Scaffold` automatically handles the edge-swipe gesture to show the `Drawer`.

dart

```
@override
Widget build(BuildContext context) {
  return Scaffold(
    drawer: Drawer(
      elevation: 20,
      child: ListTile(
        leading: const Icon(Icons.change_history),
        title: const Text('Screen2'),
        onTap: () {
          Navigator.of(context).pushNamed('/b');
        },
      ),
    ),
    appBar: AppBar(title: const Text('Home')),
    body: Container(),
  );
}
```

Android

iOS

## Gesture detection and touch event handling

[#](#gesture-detection-and-touch-event-handling)

To listen for and respond to gestures, Flutter supports taps, drags, and scaling. The gesture system in Flutter has two separate layers. The first layer includes raw pointer events, which describe the location and movement of pointers, (such as touches, mice, and styli movements), across the screen. The second layer includes gestures that describe semantic actions and consist of one or more pointer movements.

### How do I add a click or press listeners to a widget?

[#](#how-do-i-add-a-click-or-press-listeners-to-a-widget)

In React Native, listeners are added to components using `PanResponder` or the `Touchable` components.

js

```
// React Native
<TouchableOpacity
  onPress={() => {
    console.log('Press');
  }}
  onLongPress={() => {
    console.log('Long Press');
  }}
>
  <Text>Tap or Long Press</Text>
</TouchableOpacity>
```

For more complex gestures and combining several touches into a single gesture, [`PanResponder`](https://reactnative.dev/docs/panresponder) is used.

js

```
// React Native
const App = () => {
  const panResponderRef = useRef(null);
​
  useEffect(() => {
    panResponderRef.current = PanResponder.create({
      onMoveShouldSetPanResponder: (event, gestureState) =>
        !!getDirection(gestureState),
      onPanResponderMove: (event, gestureState) => true,
      onPanResponderRelease: (event, gestureState) => {
        const drag = getDirection(gestureState);
      },
      onPanResponderTerminationRequest: (event, gestureState) => true
    });
  }, []);
​
  return (
    <View style={styles.container} {...panResponderRef.current.panHandlers}>
      <View style={styles.center}>
        <Text>Swipe Horizontally or Vertically</Text>
      </View>
    </View>
  );
};
```

In Flutter, to add a click (or press) listener to a widget, use a button or a touchable widget that has an `onPress: field`. Or, add gesture detection to any widget by wrapping it in a [`GestureDetector`](https://api.flutter.dev/flutter/widgets/GestureDetector-class.html).

dart

```
@override
Widget build(BuildContext context) {
  return GestureDetector(
    child: Scaffold(
      appBar: AppBar(title: const Text('Gestures')),
      body: const Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text('Tap, Long Press, Swipe Horizontally or Vertically'),
          ],
        ),
      ),
    ),
    onTap: () {
      print('Tapped');
    },
    onLongPress: () {
      print('Long Pressed');
    },
    onVerticalDragEnd: (value) {
      print('Swiped Vertically');
    },
    onHorizontalDragEnd: (value) {
      print('Swiped Horizontally');
    },
  );
}
```

For more information, including a list of Flutter `GestureDetector` callbacks, see the [GestureDetector class](https://api.flutter.dev/flutter/widgets/GestureDetector-class.html#instance-properties).

Android

iOS

## Making HTTP network requests

[#](#making-http-network-requests)

Fetching data from the internet is common for most apps. And in Flutter, the `http` package provides the simplest way to fetch data from the internet.

### How do I fetch data from API calls?

[#](#how-do-i-fetch-data-from-api-calls)

React Native provides the Fetch API for networking—you make a fetch request and then receive the response to get the data.

js

```
// React Native
const [ipAddress, setIpAddress] = useState('')
​
const _getIPAddress = () => {
  fetch('https://httpbin.org/ip')
    .then(response => response.json())
    .then(responseJson => {
      setIpAddress(responseJson.origin);
    })
    .catch(error => {
      console.error(error);
    });
};
```

Flutter uses the `http` package.

To add the `http` package as a dependency, run `flutter pub add`:

```
$ flutter pub add http
```

Flutter uses the [`dart:io`](https://api.flutter.dev/flutter/dart-io/dart-io-library.html) core HTTP support client. To create an HTTP Client, import `dart:io`.

dart

```
import 'dart:io';
```

The client supports the following HTTP operations: GET, POST, PUT, and DELETE.

dart

```
final Uri url = Uri.parse('https://httpbin.org/ip');
final HttpClient httpClient = HttpClient();
​
Future<void> getIPAddress() async {
  final request = await httpClient.getUrl(url);
  final response = await request.close();
  final responseBody = await response.transform(utf8.decoder).join();
  final ip = jsonDecode(responseBody)['origin'] as String;
  setState(() {
    _ipAddress = ip;
  });
}
```

Android

iOS

## Form input

[#](#form-input)

Text fields allow users to type text into your app so they can be used to build forms, messaging apps, search experiences, and more. Flutter provides two core text field widgets: [`TextField`](https://pub.dev/documentation/material_ui/latest/material_ui/TextField-class.html) and [`TextFormField`](https://pub.dev/documentation/material_ui/latest/material_ui/TextFormField-class.html).

### How do I use text field widgets?

[#](#how-do-i-use-text-field-widgets)

In React Native, to enter text you use a `TextInput` component to show a text input box and then use the callback to store the value in a variable.

js

```
// React Native
const [password, setPassword] = useState('')
...
<TextInput
  placeholder="Enter your Password"
  onChangeText={password => setPassword(password)}
/>
<Button title="Submit" onPress={this.validate} />
```

In Flutter, use the [`TextEditingController`](https://api.flutter.dev/flutter/widgets/TextEditingController-class.html) class to manage a `TextField` widget. Whenever the text field is modified, the controller notifies its listeners.

Listeners read the text and selection properties to learn what the user typed into the field. You can access the text in `TextField` by the `text` property of the controller.

dart

```
final TextEditingController _controller = TextEditingController();
​
@override
Widget build(BuildContext context) {
  return Column(
    children: [
      TextField(
        controller: _controller,
        decoration: const InputDecoration(
          hintText: 'Type something',
          labelText: 'Text Field',
        ),
      ),
      ElevatedButton(
        child: const Text('Submit'),
        onPressed: () {
          showDialog<void>(
            context: context,
            builder: (context) {
              return AlertDialog(
                title: const Text('Alert'),
                content: Text('You typed ${_controller.text}'),
              );
            },
          );
        },
      ),
    ],
  );
}
```

In this example, when a user clicks on the submit button an alert dialog displays the current text entered in the text field. This is achieved using an [`AlertDialog`](https://pub.dev/documentation/material_ui/latest/material_ui/AlertDialog-class.html) widget that displays the alert message, and the text from the `TextField` is accessed by the `text` property of the [`TextEditingController`](https://api.flutter.dev/flutter/widgets/TextEditingController-class.html).

### How do I use Form widgets?

[#](#how-do-i-use-form-widgets)

In Flutter, use the [`Form`](https://api.flutter.dev/flutter/widgets/Form-class.html) widget where [`TextFormField`](https://pub.dev/documentation/material_ui/latest/material_ui/TextFormField-class.html) widgets along with the submit button are passed as children. The `TextFormField` widget has a parameter called [`onSaved`](https://api.flutter.dev/flutter/widgets/FormField/onSaved.html) that takes a callback and executes when the form is saved. A `FormState` object is used to save, reset, or validate each `FormField` that is a descendant of this `Form`. To obtain the `FormState`, you can use `Form.of()` with a context whose ancestor is the `Form`, or pass a `GlobalKey` to the `Form` constructor and call `GlobalKey.currentState()`.

dart

```
@override
Widget build(BuildContext context) {
  return Form(
    key: formKey,
    child: Column(
      children: <Widget>[
        TextFormField(
          validator: (value) {
            if (value != null && value.contains('@')) {
              return null;
            }
            return 'Not a valid email.';
          },
          onSaved: (val) {
            _email = val;
          },
          decoration: const InputDecoration(
            hintText: 'Enter your email',
            labelText: 'Email',
          ),
        ),
        ElevatedButton(onPressed: _submit, child: const Text('Login')),
      ],
    ),
  );
}
```

The following example shows how `Form.save()` and `formKey` (which is a `GlobalKey`), are used to save the form on submit.

dart

```
void _submit() {
  final form = formKey.currentState;
  if (form != null && form.validate()) {
    form.save();
    showDialog<void>(
      context: context,
      builder: (context) {
        return AlertDialog(
          title: const Text('Alert'),
          content: Text('Email: $_email, password: $_password'),
        );
      },
    );
  }
}
```

Android

iOS

## Platform-specific code

[#](#platform-specific-code)

When building a cross-platform app, you want to re-use as much code as possible across platforms. However, scenarios might arise where it makes sense for the code to be different depending on the OS. This requires a separate implementation by declaring a specific platform.

In React Native, the following implementation would be used:

js

```
// React Native
if (Platform.OS === 'ios') {
  return 'iOS';
} else if (Platform.OS === 'android') {
  return 'android';
} else {
  return 'not recognised';
}
```

In Flutter, use the following implementation:

dart

```
final platform = Theme.of(context).platform;
if (platform == TargetPlatform.iOS) {
  return 'iOS';
}
if (platform == TargetPlatform.android) {
  return 'android';
}
if (platform == TargetPlatform.fuchsia) {
  return 'fuchsia';
}
return 'not recognized ';
```

## Debugging

[#](#debugging)

### What tools can I use to debug my app in Flutter?

[#](#what-tools-can-i-use-to-debug-my-app-in-flutter)

Use the [DevTools](/tools/devtools) suite for debugging Flutter or Dart apps.

DevTools includes support for profiling, examining the heap, inspecting the widget tree, logging diagnostics, debugging, observing executed lines of code, debugging memory leaks and memory fragmentation. For more information, check out the [DevTools](/tools/devtools) documentation.

If you're using an IDE, you can debug your application using the IDE's debugger.

### How do I perform a hot reload?

[#](#how-do-i-perform-a-hot-reload)

Flutter's Stateful Hot Reload feature helps you quickly and easily experiment, build UIs, add features, and fix bugs. Instead of recompiling your app every time you make a change, you can hot reload your app instantly. The app is updated to reflect your change, and the current state of the app is preserved.

First, from your preferred IDE, enable autosave and hot reloads on save.

**VS Code**

Add the following to your `.vscode/settings.json` file:

`json "files.autoSave": "afterDelay", "dart.flutterHotReloadOnSave": "all", `**Android Studio and IntelliJ**

\* Open `Settings > Tools > Actions on Save` and select `Configure autosave options`. - Check the option to `Save files if the IDE is idle for X seconds`. - **Recommended:** Set a small delay duration. For example, 2 seconds.

\* Open `Settings > Languages & Frameworks > Flutter`. - Check the option to `Perform hot reload on save`.

In React Native, the shortcut is ⌘R for the iOS Simulator and tapping R twice on Android emulators.

In Flutter, if you are using IntelliJ IDE or Android Studio, you can select Save All (⌘s/ctrl-s), or you can click the Hot Reload button on the toolbar. If you are running the app at the command line using `flutter run`, type `r` in the Terminal window. You can also perform a full restart by typing `R` in the Terminal window.

### How do I access the in-app developer menu?

[#](#how-do-i-access-the-in-app-developer-menu)

In React Native, the developer menu can be accessed by shaking your device: ⌘D for the iOS Simulator or ⌘M for Android emulator.

In Flutter, if you are using an IDE, you can use the IDE tools. If you start your application using `flutter run` you can also access the menu by typing `h` in the terminal window, or type the following shortcuts:

| Action                                      |                   Terminal Shortcut                   | Debug functions and properties          |
| :------------------------------------------ | :---------------------------------------------------: | :-------------------------------------- |
| Widget hierarchy of the app                 |                          `w`                          | debugDumpApp()                          |
| Rendering tree of the app                   |                          `t`                          | debugDumpRenderTree()                   |
| Layers                                      |                          `L`                          | debugDumpLayerTree()                    |
| Accessibility                               | `S` (traversal order) or `U` (inverse hit test order) | debugDumpSemantics()                    |
| To toggle the widget inspector              |                          `i`                          | WidgetsApp. showWidgetInspectorOverride |
| To toggle the display of construction lines |                          `p`                          | debugPaintSizeEnabled                   |
| To simulate different operating systems     |                          `o`                          | defaultTargetPlatform                   |
| To display the performance overlay          |                          `P`                          | WidgetsApp. showPerformanceOverlay      |
| To save a screenshot to flutter. png        |                          `s`                          |                                         |
| To quit                                     |                          `q`                          |                                         |

## Animation

[#](#animation)

Well-designed animation makes a UI feel intuitive, contributes to the look and feel of a polished app, and improves the user experience. Flutter's animation support makes it easy to implement simple and complex animations. The Flutter SDK includes many Material Design widgets that include standard motion effects, and you can easily customize these effects to personalize your app.

In React Native, Animated APIs are used to create animations.

In Flutter, use the [`Animation`](https://api.flutter.dev/flutter/animation/Animation-class.html) class and the [`AnimationController`](https://api.flutter.dev/flutter/animation/AnimationController-class.html) class. `Animation` is an abstract class that understands its current value and its state (completed or dismissed). The `AnimationController` class lets you play an animation forward or in reverse, or stop animation and set the animation to a specific value to customize the motion.

### How do I add a simple fade-in animation?

[#](#how-do-i-add-a-simple-fade-in-animation)

In the React Native example below, an animated component, `FadeInView` is created using the Animated API. The initial opacity state, final state, and the duration over which the transition occurs are defined. The animation component is added inside the `Animated` component, the opacity state `fadeAnim` is mapped to the opacity of the `Text` component that we want to animate, and then, `start()` is called to start the animation.

js

```
// React Native
const FadeInView = ({ style, children }) => {
  const fadeAnim = useRef(new Animated.Value(0)).current;
​
  useEffect(() => {
    Animated.timing(fadeAnim, {
      toValue: 1,
      duration: 10000
    }).start();
  }, []);
​
  return (
    <Animated.View style={{ ...style, opacity: fadeAnim }}>
      {children}
    </Animated.View>
  );
};
    ...
<FadeInView>
  <Text> Fading in </Text>
</FadeInView>
    ...
```

To create the same animation in Flutter, create an [`AnimationController`](https://api.flutter.dev/flutter/animation/AnimationController-class.html) object named `controller` and specify the duration. By default, an `AnimationController` linearly produces values that range from 0.0 to 1.0, during a given duration. The animation controller generates a new value whenever the device running your app is ready to display a new frame. Typically, this rate is around 60 values per second.

When defining an `AnimationController`, you must pass in a `vsync` object. The presence of `vsync` prevents offscreen animations from consuming unnecessary resources. You can use your stateful object as the `vsync` by adding `TickerProviderStateMixin` to the class definition. An `AnimationController` needs a TickerProvider, which is configured using the `vsync` argument on the constructor.

A [`Tween`](https://api.flutter.dev/flutter/animation/Tween-class.html) describes the interpolation between a beginning and ending value or the mapping from an input range to an output range. To use a `Tween` object with an animation, call the `Tween` object's `animate()` method and pass it the `Animation` object that you want to modify.

For this example, a [`FadeTransition`](https://api.flutter.dev/flutter/widgets/FadeTransition-class.html) widget is used and the `opacity` property is mapped to the `animation` object.

To start the animation, use `controller.forward()`. Other operations can also be performed using the controller such as `fling()` or `repeat()`. For this example, the [`FlutterLogo`](https://pub.dev/documentation/material_ui/latest/material_ui/FlutterLogo-class.html) widget is used inside the `FadeTransition` widget.

dart

```
import 'package:material_ui/material_ui.dart';
​
void main() {
  runApp(const Center(child: LogoFade()));
}
​
class LogoFade extends StatefulWidget {
  const LogoFade({super.key});
​
  @override
  State<LogoFade> createState() => _LogoFadeState();
}
​
class _LogoFadeState extends State<LogoFade>
    with SingleTickerProviderStateMixin {
  late Animation<double> animation;
  late AnimationController controller;
​
  @override
  void initState() {
    super.initState();
    controller = AnimationController(
      duration: const Duration(milliseconds: 3000),
      vsync: this,
    );
    final CurvedAnimation curve = CurvedAnimation(
      parent: controller,
      curve: Curves.easeIn,
    );
    animation = Tween(begin: 0.0, end: 1.0).animate(curve);
    controller.forward();
  }
​
  @override
  void dispose() {
    controller.dispose();
    super.dispose();
  }
​
  @override
  Widget build(BuildContext context) {
    return FadeTransition(
      opacity: animation,
      child: const SizedBox(height: 300, width: 300, child: FlutterLogo()),
    );
  }
}
```

Android

iOS

### How do I add swipe animation to cards?

[#](#how-do-i-add-swipe-animation-to-cards)

In React Native, either the `PanResponder` or third-party libraries are used for swipe animation.

In Flutter, to add a swipe animation, use the [`Dismissible`](https://api.flutter.dev/flutter/widgets/Dismissible-class.html) widget and nest the child widgets.

dart

```
return Dismissible(
  key: Key(widget.key.toString()),
  onDismissed: (dismissDirection) {
    cards.removeLast();
  },
  child: Container(
    //...
  ),
);
```

Android

iOS

## React Native and Flutter widget equivalent components

[#](#react-native-and-flutter-widget-equivalent-components)

The following table lists commonly-used React Native components mapped to the corresponding Flutter widget and common widget properties.

| React Native Component                                                | Flutter Widget                                                                                                                   | Description                                                                                                                                                                                                                                     |
| --------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [`Button`](https://reactnative.dev/docs/button)                       | [`ElevatedButton`](https://pub.dev/documentation/material_ui/latest/material_ui/ElevatedButton-class.html)                       | A basic raised button.                                                                                                                                                                                                                          |
|                                                                       | onPressed \[required]                                                                                                            | The callback when the button is tapped or otherwise activated.                                                                                                                                                                                  |
|                                                                       | Child                                                                                                                            | The button's label.                                                                                                                                                                                                                             |
|                                                                       |                                                                                                                                  |                                                                                                                                                                                                                                                 |
| [`Button`](https://reactnative.dev/docs/button)                       | [`TextButton`](https://pub.dev/documentation/material_ui/latest/material_ui/TextButton-class.html)                               | A basic flat button.                                                                                                                                                                                                                            |
|                                                                       | onPressed \[required]                                                                                                            | The callback when the button is tapped or otherwise activated.                                                                                                                                                                                  |
|                                                                       | Child                                                                                                                            | The button's label.                                                                                                                                                                                                                             |
|                                                                       |                                                                                                                                  |                                                                                                                                                                                                                                                 |
| [`ScrollView`](https://reactnative.dev/docs/scrollview)               | [`ListView`](https://api.flutter.dev/flutter/widgets/ListView-class.html)                                                        | A scrollable list of widgets arranged linearly.                                                                                                                                                                                                 |
|                                                                       | children                                                                                                                         | ( \<Widget> \[ ]) List of child widgets to display.                                                                                                                                                                                             |
|                                                                       | controller                                                                                                                       | \[ [`ScrollController`](https://api.flutter.dev/flutter/widgets/ScrollController-class.html) ] An object that can be used to control a scrollable widget.                                                                                       |
|                                                                       | itemExtent                                                                                                                       | \[ double ] If non-null, forces the children to have the given extent in the scroll direction.                                                                                                                                                  |
|                                                                       | scroll Direction                                                                                                                 | \[ [`Axis`](https://api.flutter.dev/flutter/painting/Axis.html) ] The axis along which the scroll view scrolls.                                                                                                                                 |
|                                                                       |                                                                                                                                  |                                                                                                                                                                                                                                                 |
| [`FlatList`](https://reactnative.dev/docs/flatlist)                   | [`ListView.builder`](https://api.flutter.dev/flutter/widgets/ListView/ListView.builder.html)                                     | The constructor for a linear array of widgets that are created on demand.                                                                                                                                                                       |
|                                                                       | itemBuilder \[required]                                                                                                          | \[ [`IndexedWidgetBuilder`](https://api.flutter.dev/flutter/widgets/IndexedWidgetBuilder.html) ] helps in building the children on demand. This callback is called only with indices greater than or equal to zero and less than the itemCount. |
|                                                                       | itemCount                                                                                                                        | \[ int ] improves the ability of the `ListView` to estimate the maximum scroll extent.                                                                                                                                                          |
|                                                                       |                                                                                                                                  |                                                                                                                                                                                                                                                 |
| [`Image`](https://reactnative.dev/docs/image)                         | [`Image`](https://api.flutter.dev/flutter/widgets/Image-class.html)                                                              | A widget that displays an image.                                                                                                                                                                                                                |
|                                                                       | image \[required]                                                                                                                | The image to display.                                                                                                                                                                                                                           |
|                                                                       | Image. asset                                                                                                                     | Several constructors are provided for the various ways that an image can be specified.                                                                                                                                                          |
|                                                                       | width, height, color, alignment                                                                                                  | The style and layout for the image.                                                                                                                                                                                                             |
|                                                                       | fit                                                                                                                              | Inscribing the image into the space allocated during layout.                                                                                                                                                                                    |
|                                                                       |                                                                                                                                  |                                                                                                                                                                                                                                                 |
| [`Modal`](https://reactnative.dev/docs/modal)                         | [`ModalRoute`](https://api.flutter.dev/flutter/widgets/ModalRoute-class.html)                                                    | A route that blocks interaction with previous routes.                                                                                                                                                                                           |
|                                                                       | animation                                                                                                                        | The animation that drives the route's transition and the previous route's forward transition.                                                                                                                                                   |
|                                                                       |                                                                                                                                  |                                                                                                                                                                                                                                                 |
| [`ActivityIndicator`](https://reactnative.dev/docs/activityindicator) | [`CircularProgressIndicator`](https://pub.dev/documentation/material_ui/latest/material_ui/CircularProgressIndicator-class.html) | A widget that shows progress along a circle.                                                                                                                                                                                                    |
|                                                                       | strokeWidth                                                                                                                      | The width of the line used to draw the circle.                                                                                                                                                                                                  |
|                                                                       | backgroundColor                                                                                                                  | The progress indicator's background color. The current theme's `ThemeData.backgroundColor` by default.                                                                                                                                          |
|                                                                       |                                                                                                                                  |                                                                                                                                                                                                                                                 |
| [`ActivityIndicator`](https://reactnative.dev/docs/activityindicator) | [`LinearProgressIndicator`](https://pub.dev/documentation/material_ui/latest/material_ui/LinearProgressIndicator-class.html)     | A widget that shows progress along a line.                                                                                                                                                                                                      |
|                                                                       | value                                                                                                                            | The value of this progress indicator.                                                                                                                                                                                                           |
|                                                                       |                                                                                                                                  |                                                                                                                                                                                                                                                 |
| [`RefreshControl`](https://reactnative.dev/docs/refreshcontrol)       | [`RefreshIndicator`](https://pub.dev/documentation/material_ui/latest/material_ui/RefreshIndicator-class.html)                   | A widget that supports the Material "swipe to refresh" idiom.                                                                                                                                                                                   |
|                                                                       | color                                                                                                                            | The progress indicator's foreground color.                                                                                                                                                                                                      |
|                                                                       | onRefresh                                                                                                                        | A function that's called when a user drags the refresh indicator far enough to demonstrate that they want the app to refresh.                                                                                                                   |
|                                                                       |                                                                                                                                  |                                                                                                                                                                                                                                                 |
| [`View`](https://reactnative.dev/docs/view)                           | [`Container`](https://api.flutter.dev/flutter/widgets/Container-class.html)                                                      | A widget that surrounds a child widget.                                                                                                                                                                                                         |
|                                                                       |                                                                                                                                  |                                                                                                                                                                                                                                                 |
| [`View`](https://reactnative.dev/docs/view)                           | [`Column`](https://api.flutter.dev/flutter/widgets/Column-class.html)                                                            | A widget that displays its children in a vertical array.                                                                                                                                                                                        |
|                                                                       |                                                                                                                                  |                                                                                                                                                                                                                                                 |
| [`View`](https://reactnative.dev/docs/view)                           | [`Row`](https://api.flutter.dev/flutter/widgets/Row-class.html)                                                                  | A widget that displays its children in a horizontal array.                                                                                                                                                                                      |
|                                                                       |                                                                                                                                  |                                                                                                                                                                                                                                                 |
| [`View`](https://reactnative.dev/docs/view)                           | [`Center`](https://api.flutter.dev/flutter/widgets/Center-class.html)                                                            | A widget that centers its child within itself.                                                                                                                                                                                                  |
|                                                                       |                                                                                                                                  |                                                                                                                                                                                                                                                 |
| [`View`](https://reactnative.dev/docs/view)                           | [`Padding`](https://api.flutter.dev/flutter/widgets/Padding-class.html)                                                          | A widget that insets its child by the given padding.                                                                                                                                                                                            |
|                                                                       | padding \[required]                                                                                                              | \[ EdgeInsets ] The amount of space to inset the child.                                                                                                                                                                                         |
|                                                                       |                                                                                                                                  |                                                                                                                                                                                                                                                 |
| [`TouchableOpacity`](https://reactnative.dev/docs/touchableopacity)   | [`GestureDetector`](https://api.flutter.dev/flutter/widgets/GestureDetector-class.html)                                          | A widget that detects gestures.                                                                                                                                                                                                                 |
|                                                                       | onTap                                                                                                                            | A callback when a tap occurs.                                                                                                                                                                                                                   |
|                                                                       | onDoubleTap                                                                                                                      | A callback when a tap occurs at the same location twice in quick succession.                                                                                                                                                                    |
|                                                                       |                                                                                                                                  |                                                                                                                                                                                                                                                 |
| [`TextInput`](https://reactnative.dev/docs/textinput)                 | [`TextInput`](https://api.flutter.dev/flutter/services/TextInput-class.html)                                                     | The interface to the system's text input control.                                                                                                                                                                                               |
|                                                                       | controller                                                                                                                       | \[ [`TextEditingController`](https://api.flutter.dev/flutter/widgets/TextEditingController-class.html) ] used to access and modify text.                                                                                                        |
|                                                                       |                                                                                                                                  |                                                                                                                                                                                                                                                 |
| [`Text`](https://reactnative.dev/docs/text)                           | [`Text`](https://api.flutter.dev/flutter/widgets/Text-class.html)                                                                | The Text widget that displays a string of text with a single style.                                                                                                                                                                             |
|                                                                       | data                                                                                                                             | \[ String ] The text to display.                                                                                                                                                                                                                |
|                                                                       | textDirection                                                                                                                    | \[ [`TextAlign`](https://api.flutter.dev/flutter/dart-ui/TextAlign.html) ] The direction in which the text flows.                                                                                                                               |
|                                                                       |                                                                                                                                  |                                                                                                                                                                                                                                                 |
| [`Switch`](https://reactnative.dev/docs/switch)                       | [`Switch`](https://pub.dev/documentation/material_ui/latest/material_ui/Switch-class.html)                                       | A material design switch.                                                                                                                                                                                                                       |
|                                                                       | value \[required]                                                                                                                | \[ boolean ] Whether this switch is on or off.                                                                                                                                                                                                  |
|                                                                       | onChanged \[required]                                                                                                            | \[ callback ] Called when the user toggles the switch on or off.                                                                                                                                                                                |

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-09-29. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/flutter-for/react-native-devs.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/flutter-for/react-native-devs\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/flutter-for/react-native-devs.md "Report an issue with this page").

----
url: https://docs.flutter.dev/cookbook/navigation/hero-animations
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Animate a widget across screens](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

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 the two screens.

Use the [`Hero`](https://api.flutter.dev/flutter/widgets/Hero-class.html) widget to animate a widget from one screen to the next. This recipe uses the following steps:

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.

## 1. Create two screens showing the same image

[#](#1-create-two-screens-showing-the-same-image)

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; handle animations in the next steps.

dart

```
import 'package:flutter/material.dart';
​
class MainScreen extends StatelessWidget {
  const MainScreen({super.key});
​
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Main Screen')),
      body: GestureDetector(
        onTap: () {
          Navigator.push(
            context,
            MaterialPageRoute<void>(
              builder: (context) {
                return const DetailScreen();
              },
            ),
          );
        },
        child: Image.network('https://picsum.photos/250?image=9'),
      ),
    );
  }
}
​
class DetailScreen extends StatelessWidget {
  const DetailScreen({super.key});
​
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: GestureDetector(
        onTap: () {
          Navigator.pop(context);
        },
        child: Center(
          child: Image.network('https://picsum.photos/250?image=9'),
        ),
      ),
    );
  }
}
```

## 2. Add a `Hero` widget to the first screen

[#](#2-add-a-hero-widget-to-the-first-screen)

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:

* `tag`

  An object that identifies the `Hero`. It must be the same on both screens.

* `child`

  The widget to animate across screens.

dart

```
Hero(
  tag: 'imageHero',
  child: Image.network('https://picsum.photos/250?image=9'),
)
```

## 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, wrap the `Image` on the second screen with a `Hero` widget that has the same `tag` as the `Hero` in the first screen.

After applying the `Hero` widget to the second screen, the animation between screens just works.

dart

```
Hero(
  tag: 'imageHero',
  child: Image.network('https://picsum.photos/250?image=9'),
)
```

## Interactive example

[#](#interactive-example)

```
import 'package:flutter/material.dart';

void main() => runApp(const HeroApp());

class HeroApp extends StatelessWidget {
  const HeroApp({super.key});

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(title: 'Transition Demo', home: MainScreen());
  }
}

class MainScreen extends StatelessWidget {
  const MainScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Main Screen')),
      body: GestureDetector(
        onTap: () {
          Navigator.push(
            context,
            MaterialPageRoute<void>(
              builder: (context) {
                return const DetailScreen();
              },
            ),
          );
        },
        child: Hero(
          tag: 'imageHero',
          child: Image.network('https://picsum.photos/250?image=9'),
        ),
      ),
    );
  }
}

class DetailScreen extends StatelessWidget {
  const DetailScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: GestureDetector(
        onTap: () {
          Navigator.pop(context);
        },
        child: Center(
          child: Hero(
            tag: 'imageHero',
            child: Image.network('https://picsum.photos/250?image=9'),
          ),
        ),
      ),
    );
  }
}
```

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/navigation/hero-animations.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/cookbook/navigation/hero-animations\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/navigation/hero-animations.md "Report an issue with this page").

----
url: https://docs.flutter.dev/ai/best-practices/prompting
----

Assuming you've configured your Flutter app with the Firebase project and configuration you need to use the Firebase AI Logic SDK (which you can learn about in [the README](https://github.com/flutter/demos/tree/main/crossword_companion)), you're all set to start using generative AI. Generative AI is the branch of Machine Learning (ML) that uses a neural network trained on a large set of human language to produce a Large Language Model (LLM). At this point, the best models (like Google Gemini) are trained on what is essentially the entire internet.

At that scale, a model trained with that much data has created models that can interpret human language and produce useful human language outputs. By now I'm sure you've used [the Gemini chat app](https://gemini.google.com/app) (or ChatGPT or Claude or other chat apps), so you know that if you talk to an LLM using vague language, you're likely to get vague, often incorrect, results. If you want to get good results, you'll have to use good prompts.

### Prompt construction

[#](#prompt-construction)

A prompt is the input you provide to an LLM to get the output you want. It will include text as well zero or more files, like images or PDF files. If you're building chat into your app, then the user will be entering the prompts (and [the Flutter AI Toolkit](/ai-toolkit) is useful for building the chat UI). If you're using an LLM to implement the features of your app, like parsing an image for crossword puzzle data, then you're going to be building the prompts yourself. How you build them matters.

As an example, in building the Crossword Companion, the original clue solving prompt looked like this:

dart

```
You are a crossword puzzle solver. Your goal is to solve the puzzle by filling in the grid with the correct answers. Given the current state of the crossword grid and a single clue, provide the answer for that clue. The answer should be a single word, returned in a JSON object that matches the following schema: '{"type": "object", "properties": {"answer": {"type": "string"}}}'.
​
# Puzzle Information
## Grid Layout
The grid is (${grid.width}x${grid.height}):
${_getGridStateAsString(grid)}
​
## Clue
${clue.number} ${clue.direction == ClueDirection.across ? 'Across' : 'Down'}: ${clue.text}
```

This prompt isn't all bad – it has some useful pieces:

* **Persona:** the phrase "You are a crossword puzzle solver" narrows the model's focus
* **Context:** the current state of the puzzle
* **Query:** asking for a solution to a clue
* **Format:** provide the output in JSON so the result could be parsed programmatically

However, because of the two-dimensional nature of the data, this is a hard prompt for some models to solve. The results from Gemini 2.5 Flash (the more efficient of the models available at the time) were inconsistent. The quality of the results from Gemini 2.5 Pro were excellent, but they were slower and more expensive to obtain. Debugging revealed that Pro was essentially solving the entire puzzle every time it was called, responding with just the solution to a single clue.

What was needed was the efficiency of Flash with the quality of Pro. To do that required some work on the prompt:

markdown

```
Your task is to solve the following crossword clue.
​
**Clue:** "${clue.text}"
​
**Constraints:**
- The answer is a **$length-letter** word.
- The current letter pattern is `$pattern`, where `_` represents an unknown letter.
​
Return your answer and confidence score in the required JSON format.
```

This prompt asks to solve the clue, provides the important context, and specifies the output format. Instead of handing in the entire state of the two-dimensional grid, the input was narrowed to the length requirement and a pattern, such as "\_ R \_ Y". These simplifications produce high quality results from Flash that come back quickly enough to make it [fun to watch](https://github.com/flutter/demos/raw/refs/heads/main/crossword_companion/readme/screen-recording.mov).

### Layering your prompts

[#](#layering-your-prompts)

The prompt used to solve the clues is not the only prompt the model sees. It also has the system instruction (also known as the system message or the system prompt) which is set as part of model instance creation. Think of the system instruction as "this is what you do" while the individual prompts are "now do this."

Here is the partial system instruction for the clue solver model (you'll see the rest later):

dart

```
final clueSolverSystemInstruction =
'''
You are an expert crossword puzzle solver.
​
**Follow these rules at all times:**
1.  **Prefer Common Words:** Prioritize common English words and proper nouns. Avoid obscure, archaic, or highly technical terms unless the clue strongly implies them.
2.  **Match the Clue:** Ensure your answer strictly matches the clue's tense, plurality (singular vs. plural), and part of speech.
3.  **Verify Grammatically:** If a clue implies a specific part of speech (e.g., it's a verb, adverb, or plural), it's a good idea to use the `getWordMetadata` tool to verify your candidate answer matches. However, avoid using it for every clue.
4.  **Be Confident:** Provide a confidence score from 0.0 to 1.0 indicating your certainty.
5.  **Trust the Clue Over the Pattern:** The provided letter pattern is only a suggestion based on other potentially incorrect answers. Your primary goal is to find the best word that fits the **clue text**. If you are confident in an answer that contradicts the provided pattern, you should use that answer.
6.  **Format Correctly:** You must return your answer in the specified JSON format.
...
''';
```

Given the model we want to use and the system instruction, we now have everything we need to create an instance:

dart

```
// The model for solving clues.
_clueSolverModel = FirebaseAI.googleAI().generativeModel(
  model: 'gemini-2.5-flash',
  systemInstruction: Content.text(clueSolverSystemInstruction),
  ...
);
```

While the system instruction is often static, the individual prompts are usually created dynamically based on data.

### Parameterizing your prompts

[#](#parameterizing-your-prompts)

Each clue solver prompt is created using the text from the clue, the target length of the answer and the pattern so far given previously solved clues, such as "\_R\_Y":

dart

```
String getSolverPrompt(Clue clue, int length, String pattern) =>
'''
Your task is to solve the following crossword clue.
​
**Clue:** "${clue.text}"
​
**Constraints:**
- The answer is a **$length-letter** word.
- The current letter pattern is `$pattern`, where `_` represents an unknown letter.
​
Return your answer and confidence score in the required JSON format.
''';
```

With the prompt in hand, we can pass it along to the model for our clue answer:

dart

```
final result = await _clueSolverModel.generateContent(
  prompt: getSolverPrompt(clue, length, pattern),
);
```

### Prompt versioning

[#](#prompt-versioning)

This basic app keeps the prompt strings in code. This makes them hard to track down and update. For production apps, it's better to keep your prompts separated from the code, perhaps bundled as Flutter assets. One way to arrange prompt files is to use [the Google dotprompt format](https://google.github.io/dotprompt/getting-started/), which allows you to write `.prompt` files that look like this:

markdown

```
---
model: googleai/gemini-2.5-flash
input:
  schema:
    text: string
output:
  format: json
  schema:
    title?: string, the title of the article if it has one
    summary: string, a 3-sentence summary of the text
    tags?(array, a list of string tag category for the text): string
---
​
Extract the requested information from the given text. If a piece of information is not present, omit that field from the output.
​
Text:
```

To expand a `.prompt` file for use in your Dart and Flutter projects, you can use [the dotprompt\_dart package](https://pub.dev/packages/dotprompt_dart).

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-08-19. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/ai/best-practices/prompting.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/ai/best-practices/prompting\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/ai/best-practices/prompting.md "Report an issue with this page").

----
url: https://docs.flutter.dev/testing/code-debugging.md
----

\# Debug Flutter apps from code > How to enable various debugging tools from your code and at the command line.

<!--?code-excerpt path-base="testing/code_debugging"?-->

This guide describes which debugging features you can enable in your code. For a full list of debugging and profiling tools, check out the \[Debugging]\[] page. ## Add logging to your application The following list contains a few statements that you can use to log the behavior of your application. You can view your logs in DevTools' \[Logging view]\[] or in your system console. \* \[\`print()\`]\[]: Prints a \`stdout\` (standard output) message. Part of the \`dart:io\` library. \* \[\`stderr.method\_to\_invoke()\`]\[]: Prints a \`stderr\` (standard error) message. Replace \`method\_to\_invoke()\` with a method supported by the \`stderr\` property, such as \`writeln()\` or \`write()\`. Often used in a \`try...catch\` block. Part of the \`dart:io\` library.

<!--?code-excerpt "lib/main.dart (stderr)"?-->

\`\`\`dart stderr.writeln('print me'); \`\`\` \* \[\`log()\`]\[]: Includes greater granularity and more information in the logging output. Part of the \`dart:developer\` library. \* \[\`debugPrint()\`]\[]: If too much output results in discarded log lines, use this to keep those lines. Will print messages in release mode unless part of a debug mode check or an assert. Part of the \`foundations\` library. ### Example 1 {:.no\_toc}

<!--?code-excerpt "lib/main.dart (log)"?-->

\`\`\`dart import 'dart:developer' as developer; void main() { developer.log('log me', name: 'my.app.category'); developer.log('log me 1', name: 'my.other.category'); developer.log('log me 2', name: 'my.other.category'); } \`\`\` You can also pass app data to the log call. The convention for this is to use the \`error:\` named parameter on the \`log()\` call, JSON encode the object you want to send, and pass the encoded string to the error parameter. ### Example 2 {:.no\_toc}

<!--?code-excerpt "lib/app_data.dart (pass-data)"?-->

\`\`\`dart import 'dart:convert'; import 'dart:developer' as developer; void main() { var myCustomObject = MyCustomObject(); developer.log( 'log me', name: 'my.app.category', error: jsonEncode(myCustomObject), ); } \`\`\` DevTool's logging view interprets the JSON encoded error parameter as a data object. DevTool renders in the details view for that log entry. ## Set breakpoints You can set breakpoints in DevTools' \[Debugger]\[] or in the built-in debugger of your IDE. To set programmatic breakpoints: 1. Import the \`dart:developer\` package into the relevant file. 1. Insert programmatic breakpoints using the \`debugger()\` statement. This statement takes an optional \`when\` argument. This boolean argument sets a break when the given condition resolves to true. \*\*Example 3\*\* illustrates this. ### Example 3 {:.no\_toc}

<!--?code-excerpt "lib/debugger.dart"?-->

\`\`\`dart import 'dart:developer'; void someFunction(double offset) { debugger(when: offset > 30); // ... } \`\`\` ## Debug app layers using flags Each layer of the Flutter framework provides a function to dump its current state or events to the console using the \`debugPrint\` property. :::note All of the following examples were run as macOS native apps on a MacBook Pro M1. These will differ from any dumps your development machine prints. ::: :::tip Each render object in any tree includes the first five hexadecimal digits of its \[\`hashCode\`]\[]. This hash serves as a unique identifier for that render object. ::: \[\`hashCode\`]: https\://api.flutter.dev/flutter/rendering/TextSelectionPoint/hashCode.html ### Print the widget tree To dump the state of the Widgets library, call the \[\`debugDumpApp()\`]\[] function. 1. Open your source file. 1. Import \`package:flutter/rendering.dart\`. 1. Call the \[\`debugDumpApp()\`]\[] function from within the \`runApp()\` function. You need your app in debug mode. You cannot call this function inside a \`build()\` method when the app is building. 1. If you haven't started your app, debug it using your IDE. 1. If you have started your app, save your source file. Hot reload re-renders your app. #### Example 4: Call \`debugDumpApp()\`

<!--?code-excerpt "lib/dump_app.dart"?-->

\`\`\`dart import 'package:material\_ui/material\_ui.dart'; void main() { runApp(const MaterialApp(home: AppHome())); } class AppHome extends StatelessWidget { const AppHome({super.key}); @override Widget build(BuildContext context) { return Material( child: Center( child: TextButton( onPressed: () { debugDumpApp(); }, child: const Text('Dump Widget Tree'), ), ), ); } } \`\`\` This function recursively calls the \`toStringDeep()\` method starting with the root of the widget tree. It returns a "flattened" tree. \*\*Example 4\*\* produces the following widget tree. It includes: \* All the widgets projected through their various build functions. \* Many widgets that don't appear in your app's source. The framework's widgets' build functions insert them during the build. The following tree, for example, shows \[\`\_InkFeatures\`]\[]. That class implements part of the \[\`Material\`]\[] widget. It doesn't appear anywhere in the code in \*\*Example 4\*\*.

**Expand to view the widget tree for Example 4**

\`\`\`plaintext flutter: WidgetsFlutterBinding - DEBUG MODE flutter: \[root]\(renderObject: RenderView#06beb) flutter: └View-\[GlobalObjectKey FlutterView#7971c] flutter: └\_ViewScope flutter: └\_MediaQueryFromView(state: \_MediaQueryFromViewState#d790c) flutter: └MediaQuery(MediaQueryData(size: Size(800.0, 600.0), devicePixelRatio: 1.0, textScaleFactor: 1.0, platformBrightness: Brightness.dark, padding: EdgeInsets.zero, viewPadding: EdgeInsets.zero, viewInsets: EdgeInsets.zero, systemGestureInsets: EdgeInsets.zero, alwaysUse24HourFormat: false, accessibleNavigation: false, highContrast: false, disableAnimations: false, invertColors: false, boldText: false, navigationMode: traditional, gestureSettings: DeviceGestureSettings(touchSlop: null), displayFeatures: \[])) flutter: └MaterialApp(state: \_MaterialAppState#27fa9) flutter: └ScrollConfiguration(behavior: MaterialScrollBehavior) flutter: └HeroControllerScope flutter: └Focus(state: \_FocusState#d7f97) flutter: └\_FocusInheritedScope flutter: └Semantics(container: false, properties: SemanticsProperties, tooltip: null, renderObject: RenderSemanticsAnnotations#a6464) flutter: └WidgetsApp-\[GlobalObjectKey \_MaterialAppState#27fa9]\(state: \_WidgetsAppState#b5b17) flutter: └RootRestorationScope(state: \_RootRestorationScopeState#6b028) flutter: └UnmanagedRestorationScope flutter: └RestorationScope(dependencies: \[UnmanagedRestorationScope], state: \_RestorationScopeState#d1369) flutter: └UnmanagedRestorationScope flutter: └SharedAppData(state: \_SharedAppDataState#95e82) flutter: └\_SharedAppModel flutter: └Shortcuts(shortcuts: , state: \_ShortcutsState#272dc) flutter: └Focus(debugLabel: "Shortcuts", dependencies: \[\_FocusInheritedScope], state: \_FocusState#a3300) flutter: └\_FocusInheritedScope flutter: └Semantics(container: false, properties: SemanticsProperties, tooltip: null, renderObject: RenderSemanticsAnnotations#db110) flutter: └DefaultTextEditingShortcuts flutter: └Shortcuts(shortcuts: , state: \_ShortcutsState#1d796) flutter: └Focus(debugLabel: "Shortcuts", dependencies: \[\_FocusInheritedScope], state: \_FocusState#0081b) flutter: └\_FocusInheritedScope flutter: └Semantics(container: false, properties: SemanticsProperties, tooltip: null, renderObject: RenderSemanticsAnnotations#0d70e) flutter: └Shortcuts(shortcuts: , state: \_ShortcutsState#56bac) flutter: └Focus(debugLabel: "Shortcuts", dependencies: \[\_FocusInheritedScope], state: \_FocusState#3152e) flutter: └\_FocusInheritedScope flutter: └Semantics(container: false, properties: SemanticsProperties, tooltip: null, renderObject: RenderSemanticsAnnotations#b7eaf) flutter: └Actions(dispatcher: null, actions: {DoNothingIntent: DoNothingAction#0fda1, DoNothingAndStopPropagationIntent: DoNothingAction#17f30, RequestFocusIntent: RequestFocusAction#10bd0, NextFocusIntent: NextFocusAction#60317, PreviousFocusIntent: PreviousFocusAction#2a933, DirectionalFocusIntent: DirectionalFocusAction#a6922, ScrollIntent: \_OverridableContextAction#964fe(defaultAction: ScrollAction#ffb50), PrioritizedIntents: PrioritizedAction#be0e2, VoidCallbackIntent: VoidCallbackAction#805fa}, state: \_ActionsState#bbd25) flutter: └\_ActionsScope flutter: └FocusTraversalGroup(policy: ReadingOrderTraversalPolicy#f1e76, state: \_FocusTraversalGroupState#0c200) flutter: └Focus(debugLabel: "FocusTraversalGroup", focusNode: \_FocusTraversalGroupNode#ffcad(FocusTraversalGroup \[IN FOCUS PATH]), dependencies: \[\_FocusInheritedScope], state: \_FocusState#c7dc2) flutter: └\_FocusInheritedScope flutter: └TapRegionSurface(renderObject: RenderTapRegionSurface#17aba) flutter: └ShortcutRegistrar(state: \_ShortcutRegistrarState#44954) flutter: └\_ShortcutRegistrarScope flutter: └Shortcuts(manager: ShortcutManager#eb38c(shortcuts: {}), shortcuts: {}, state: \_ShortcutsState#f85ac) flutter: └Focus(debugLabel: "Shortcuts", dependencies: \[\_FocusInheritedScope], state: \_FocusState#8c1a7) flutter: └\_FocusInheritedScope flutter: └Semantics(container: false, properties: SemanticsProperties, tooltip: null, renderObject: RenderSemanticsAnnotations#1fc98) flutter: └Localizations(locale: en\_US, delegates: \[DefaultMaterialLocalizations.delegate(en\_US), DefaultCupertinoLocalizations.delegate(en\_US), DefaultWidgetsLocalizations.delegate(en\_US)], state: \_LocalizationsState#ae3a0) flutter: └Semantics(container: false, properties: SemanticsProperties, tooltip: null, textDirection: ltr, renderObject: RenderSemanticsAnnotations#8776e) flutter: └\_LocalizationsScope-\[GlobalKey#61ca6] flutter: └Directionality(textDirection: ltr) flutter: └Title(color: Color(0xff2196f3)) flutter: └CheckedModeBanner("DEBUG") flutter: └Banner("DEBUG", textDirection: ltr, location: topEnd, Color(0xa0b71c1c), text inherit: true, text color: Color(0xffffffff), text size: 10.2, text weight: 900, text height: 1.0x, dependencies: \[Directionality]) flutter: └CustomPaint(renderObject: RenderCustomPaint#c014d) flutter: └DefaultTextStyle(debugLabel: fallback style; consider putting your text in a Material, inherit: true, color: Color(0xd0ff0000), family: monospace, size: 48.0, weight: 900, decoration: double Color(0xffffff00) TextDecoration.underline, softWrap: wrapping at box width, overflow: clip) flutter: └Builder(dependencies: \[MediaQuery]) flutter: └ScaffoldMessenger(dependencies: \[MediaQuery], state: ScaffoldMessengerState#5b36e) flutter: └\_ScaffoldMessengerScope flutter: └DefaultSelectionStyle flutter: └AnimatedTheme(duration: 200ms, state: \_AnimatedThemeState#cd149(ticker inactive, ThemeDataTween(ThemeData#ef3b2 → ThemeData#ef3b2))) flutter: └Theme(ThemeData#ef3b2, dependencies: \[DefaultSelectionStyle]) flutter: └\_InheritedTheme flutter: └CupertinoTheme(brightness: light, primaryColor: MaterialColor(primary value: Color(0xff2196f3)), primaryContrastingColor: Color(0xffffffff), scaffoldBackgroundColor: Color(0xfffafafa), actionTextStyle: TextStyle(inherit: false, color: MaterialColor(primary value: Color(0xff2196f3)), family: .SF Pro Text, size: 17.0, letterSpacing: -0.4, decoration: TextDecoration.none), navActionTextStyle: TextStyle(inherit: false, color: MaterialColor(primary value: Color(0xff2196f3)), family: .SF Pro Text, size: 17.0, letterSpacing: -0.4, decoration: TextDecoration.none)) flutter: └\_InheritedCupertinoTheme flutter: └IconTheme(color: MaterialColor(primary value: Color(0xff2196f3))) flutter: └IconTheme(color: Color(0xdd000000)) flutter: └DefaultSelectionStyle flutter: └FocusScope(debugLabel: "Navigator Scope", AUTOFOCUS, dependencies: \[\_FocusInheritedScope], state: \_FocusScopeState#acbd8) flutter: └Semantics(container: false, properties: SemanticsProperties, tooltip: null, renderObject: RenderSemanticsAnnotations#ab3f0) flutter: └\_FocusInheritedScope flutter: └Navigator-\[GlobalObjectKey \_WidgetsAppState#b5b17]\(dependencies: \[HeroControllerScope, UnmanagedRestorationScope], state: NavigatorState#1395a(tickers: tracking 1 ticker)) flutter: └HeroControllerScope flutter: └Listener(listeners: \[down, up, cancel], behavior: deferToChild, renderObject: RenderPointerListener#34172) flutter: └AbsorbPointer(absorbing: false, renderObject: RenderAbsorbPointer#f8711) flutter: └FocusTraversalGroup(policy: ReadingOrderTraversalPolicy#f1e76, state: \_FocusTraversalGroupState#8d61a) flutter: └Focus(debugLabel: "FocusTraversalGroup", focusNode: \_FocusTraversalGroupNode#dd2b1(FocusTraversalGroup \[IN FOCUS PATH]), dependencies: \[\_FocusInheritedScope], state: \_FocusState#0bb03) flutter: └\_FocusInheritedScope flutter: └Focus(debugLabel: "Navigator", AUTOFOCUS, focusNode: FocusNode#a3309(Navigator \[IN FOCUS PATH]), dependencies: \[\_FocusInheritedScope], state: \_FocusState#d3d07) flutter: └\_FocusInheritedScope flutter: └UnmanagedRestorationScope flutter: └Overlay-\[LabeledGlobalKey#5485a]\(state: OverlayState#5bd52(entries: \[OverlayEntry#fc947(opaque: true; maintainState: false), OverlayEntry#05a32(opaque: false; maintainState: true)])) flutter: └\_Theater(skipCount: 0, dependencies: \[Directionality], renderObject: \_RenderTheater#e86c3) flutter: ├\_OverlayEntryWidget-\[LabeledGlobalKey<\_OverlayEntryWidgetState>#1b37e]\(state: \_OverlayEntryWidgetState#06ab0) flutter: │└TickerMode(state: \_TickerModeState#0b4ac(requested mode: enabled)) flutter: │ └\_EffectiveTickerMode(effective mode: enabled) flutter: │ └\_RenderTheaterMarker flutter: │ └IgnorePointer(ignoring: false, renderObject: RenderIgnorePointer#34c66) flutter: │ └ModalBarrier flutter: │ └BlockSemantics(blocking: true, renderObject: RenderBlockSemantics#97799) flutter: │ └ExcludeSemantics(excluding: true, renderObject: RenderExcludeSemantics#8c4ce) flutter: │ └\_ModalBarrierGestureDetector flutter: │ └RawGestureDetector(state: RawGestureDetectorState#556f6(gestures: \[any tap], behavior: opaque)) flutter: │ └\_GestureSemantics(renderObject: RenderSemanticsGestureHandler#616f1) flutter: │ └Listener(listeners: \[down, panZoomStart], behavior: opaque, renderObject: RenderPointerListener#c2b89) flutter: │ └Semantics(container: false, properties: SemanticsProperties, tooltip: null, renderObject: RenderSemanticsAnnotations#c3b31) flutter: │ └MouseRegion(listeners: , cursor: SystemMouseCursor(basic), renderObject: RenderMouseRegion#53cdb) flutter: │ └ConstrainedBox(BoxConstraints(biggest), renderObject: RenderConstrainedBox#faa51) flutter: └\_OverlayEntryWidget-\[LabeledGlobalKey<\_OverlayEntryWidgetState>#bc0aa]\(state: \_OverlayEntryWidgetState#cbf35) flutter: └TickerMode(state: \_TickerModeState#23e73(requested mode: enabled)) flutter: └\_EffectiveTickerMode(effective mode: enabled) flutter: └\_RenderTheaterMarker flutter: └Semantics(container: false, properties: SemanticsProperties, tooltip: null, sortKey: OrdinalSortKey#135f4(order: 0.0), renderObject: RenderSemanticsAnnotations#5565e) flutter: └\_ModalScope-\[LabeledGlobalKey<\_ModalScopeState>#4fe82]\(state: \_ModalScopeState#4da7d) flutter: └AnimatedBuilder(listenable: ValueNotifier#d87c6(null), state: \_AnimatedState#dde81) flutter: └RestorationScope(dependencies: \[UnmanagedRestorationScope], state: \_RestorationScopeState#78c51) flutter: └UnmanagedRestorationScope flutter: └\_ModalScopeStatus(active) flutter: └Offstage(offstage: false, renderObject: RenderOffstage#5e498) flutter: └PageStorage flutter: └Builder flutter: └Actions(dispatcher: null, actions: {DismissIntent: \_DismissModalAction#6279e}, state: \_ActionsState#48019) flutter: └\_ActionsScope flutter: └PrimaryScrollController(ScrollController#6a546(no clients)) flutter: └FocusScope(debugLabel: "\_ModalScopeState Focus Scope", focusNode: FocusScopeNode#0e2af(\_ModalScopeState Focus Scope \[PRIMARY FOCUS]), dependencies: \[\_FocusInheritedScope], state: \_FocusScopeState#0bac4) flutter: └Semantics(container: false, properties: SemanticsProperties, tooltip: null, renderObject: RenderSemanticsAnnotations#44b4e) flutter: └\_FocusInheritedScope flutter: └RepaintBoundary(renderObject: RenderRepaintBoundary#38f41) flutter: └AnimatedBuilder(listenable: Listenable.merge(\[AnimationController#9d623(⏭ 1.000; paused; for MaterialPageRoute(/))➩ProxyAnimation, kAlwaysDismissedAnimation➩ProxyAnimation➩ProxyAnimation]), dependencies: \[\_InheritedTheme, \_LocalizationsScope-\[GlobalKey#61ca6]], state: \_AnimatedState#47725) flutter: └CupertinoPageTransition(dependencies: \[Directionality]) flutter: └SlideTransition(listenable: kAlwaysDismissedAnimation➩ProxyAnimation➩ProxyAnimation➩Cubic(0.35, 0.91, 0.33, 0.97)ₒₙ/Cubic(0.67, 0.03, 0.65, 0.09)➩Tween(Offset(0.0, 0.0) → Offset(-0.3, 0.0))➩Offset(0.0, 0.0), state: \_AnimatedState#b6162) flutter: └FractionalTranslation(renderObject: RenderFractionalTranslation#fb461) flutter: └SlideTransition(listenable: AnimationController#9d623(⏭ 1.000; paused; for MaterialPageRoute(/))➩ProxyAnimation➩ThreePointCubic ₒₙ/FlippedCurve(ThreePointCubic )➩Tween(Offset(1.0, 0.0) → Offset(0.0, 0.0))➩Offset(0.0, 0.0), state: \_AnimatedState#834bf) flutter: └FractionalTranslation(renderObject: RenderFractionalTranslation#73ea4) flutter: └DecoratedBoxTransition(listenable: AnimationController#9d623(⏭ 1.000; paused; for MaterialPageRoute(/))➩ProxyAnimation➩Cubic(0.35, 0.91, 0.33, 0.97)➩DecorationTween(\_CupertinoEdgeShadowDecoration(colors: null) → \_CupertinoEdgeShadowDecoration(colors: \[Color(0x04000000), Color(0x00000000)]))➩\_CupertinoEdgeShadowDecoration(colors: \[Color(0x04000000), Color(0x00000000)]), state: \_AnimatedState#a7fca) flutter: └DecoratedBox(bg: \_CupertinoEdgeShadowDecoration(colors: \[Color(0x04000000), Color(0x00000000)]), dependencies: \[Directionality, MediaQuery, \_LocalizationsScope-\[GlobalKey#61ca6]], renderObject: RenderDecoratedBox#9965c) flutter: └\_CupertinoBackGestureDetector(dependencies: \[Directionality, MediaQuery], state: \_CupertinoBackGestureDetectorState#ab8cd) flutter: └Stack(alignment: AlignmentDirectional.topStart, fit: passthrough, dependencies: \[Directionality], renderObject: RenderStack#b2b7c) flutter: ├AnimatedBuilder(listenable: ValueNotifier#1a88e(false), state: \_AnimatedState#6e33c) flutter: │└IgnorePointer(ignoring: false, renderObject: RenderIgnorePointer#2b763) flutter: │ └RepaintBoundary-\[GlobalKey#628f4]\(renderObject: RenderRepaintBoundary#5a53b) flutter: │ └Builder flutter: │ └Semantics(container: false, properties: SemanticsProperties, tooltip: null, renderObject: RenderSemanticsAnnotations#f8795) flutter: │ └AppHome flutter: │ └Material(type: canvas, dependencies: \[\_InheritedTheme, \_LocalizationsScope-\[GlobalKey#61ca6]], state: \_MaterialState#7d183) flutter: │ └AnimatedPhysicalModel(duration: 200ms, shape: rectangle, borderRadius: BorderRadius.zero, elevation: 0.0, color: Color(0xfffafafa), animateColor: false, shadowColor: Color(0xff000000), animateShadowColor: true, state: \_AnimatedPhysicalModelState#d479e(ticker inactive)) flutter: │ └PhysicalModel(shape: rectangle, borderRadius: BorderRadius.zero, elevation: 0.0, color: Color(0xfffafafa), shadowColor: Color(0xff000000), renderObject: RenderPhysicalModel#c60b5) flutter: │ └NotificationListener flutter: │ └\_InkFeatures-\[GlobalKey#e9da0 ink renderer]\(renderObject: \_RenderInkFeatures#d8e6d) flutter: │ └AnimatedDefaultTextStyle(duration: 200ms, debugLabel: (englishLike bodyMedium 2014).merge(blackRedwoodCity bodyMedium), inherit: false, color: Color(0xdd000000), family: .AppleSystemUIFont, size: 14.0, weight: 400, baseline: alphabetic, decoration: TextDecoration.none, softWrap: wrapping at box width, overflow: clip, state: \_AnimatedDefaultTextStyleState#12f43(ticker inactive)) flutter: │ └DefaultTextStyle(debugLabel: (englishLike bodyMedium 2014).merge(blackRedwoodCity bodyMedium), inherit: false, color: Color(0xdd000000), family: .AppleSystemUIFont, size: 14.0, weight: 400, baseline: alphabetic, decoration: TextDecoration.none, softWrap: wrapping at box width, overflow: clip) flutter: │ └Center(alignment: Alignment.center, dependencies: \[Directionality], renderObject: RenderPositionedBox#b088f) flutter: │ └TextButton(dirty, dependencies: \[MediaQuery, \_InheritedTheme, \_LocalizationsScope-\[GlobalKey#61ca6]], state: \_ButtonStyleState#687c9) flutter: │ └Semantics(container: true, properties: SemanticsProperties, tooltip: null, renderObject: RenderSemanticsAnnotations#ca411 relayoutBoundary=up1) flutter: │ └\_InputPadding(renderObject: \_RenderInputPadding#60ede relayoutBoundary=up2) flutter: │ └ConstrainedBox(BoxConstraints(56.0<=w<=Infinity, 28.0<=h<=Infinity), renderObject: RenderConstrainedBox#34800 relayoutBoundary=up3) flutter: │ └Material(type: button, color: Color(0x00000000), shadowColor: Color(0xff000000), textStyle.debugLabel: ((englishLike labelLarge 2014).merge(blackRedwoodCity labelLarge)).copyWith, textStyle.inherit: false, textStyle.color: MaterialColor(primary value: Color(0xff2196f3)), textStyle.family: .AppleSystemUIFont, textStyle.size: 14.0, textStyle.weight: 500, textStyle.baseline: alphabetic, textStyle.decoration: TextDecoration.none, shape: RoundedRectangleBorder(BorderSide(width: 0.0, style: none), BorderRadius.circular(4.0)), dependencies: \[\_InheritedTheme, \_LocalizationsScope-\[GlobalKey#61ca6]], state: \_MaterialState#50a4d(tickers: tracking 5 tickers)) flutter: │ └\_MaterialInterior(duration: 200ms, shape: RoundedRectangleBorder(BorderSide(width: 0.0, style: none), BorderRadius.circular(4.0)), elevation: 0.0, color: Color(0x00000000), shadowColor: Color(0xff000000), dependencies: \[Directionality, \_InheritedTheme, \_LocalizationsScope-\[GlobalKey#61ca6]], state: \_MaterialInteriorState#d296d(ticker inactive)) flutter: │ └PhysicalShape(clipper: ShapeBorderClipper, elevation: 0.0, color: Color(0x00000000), shadowColor: Color(0xff000000), renderObject: RenderPhysicalShape#43df6 relayoutBoundary=up4) flutter: │ └\_ShapeBorderPaint(dependencies: \[Directionality]) flutter: │ └CustomPaint(renderObject: RenderCustomPaint#c1a3c relayoutBoundary=up5) flutter: │ └NotificationListener flutter: │ └\_InkFeatures-\[GlobalKey#625bc ink renderer]\(renderObject: \_RenderInkFeatures#54439 relayoutBoundary=up6) flutter: │ └AnimatedDefaultTextStyle(duration: 200ms, debugLabel: ((englishLike labelLarge 2014).merge(blackRedwoodCity labelLarge)).copyWith, inherit: false, color: MaterialColor(primary value: Color(0xff2196f3)), family: .AppleSystemUIFont, size: 14.0, weight: 500, baseline: alphabetic, decoration: TextDecoration.none, softWrap: wrapping at box width, overflow: clip, state: \_AnimatedDefaultTextStyleState#2f29d(ticker inactive)) flutter: │ └DefaultTextStyle(debugLabel: ((englishLike labelLarge 2014).merge(blackRedwoodCity labelLarge)).copyWith, inherit: false, color: MaterialColor(primary value: Color(0xff2196f3)), family: .AppleSystemUIFont, size: 14.0, weight: 500, baseline: alphabetic, decoration: TextDecoration.none, softWrap: wrapping at box width, overflow: clip) flutter: │ └InkWell flutter: │ └\_InkResponseStateWidget(gestures: \[tap], mouseCursor: ButtonStyleButton\_MouseCursor, clipped to BoxShape.rectangle, dirty, dependencies: \[Directionality, MediaQuery, \_InheritedTheme, \_LocalizationsScope-\[GlobalKey#61ca6]], state: \_InkResponseState#0b11d) flutter: │ └\_ParentInkResponseProvider flutter: │ └Actions(dispatcher: null, actions: {ActivateIntent: CallbackAction#018db, ButtonActivateIntent: CallbackAction#ef87a}, state: \_ActionsState#a5eab) flutter: │ └\_ActionsScope flutter: │ └Focus(dependencies: \[\_FocusInheritedScope], state: \_FocusState#5a9de) flutter: │ └\_FocusInheritedScope flutter: │ └Semantics(container: false, properties: SemanticsProperties, tooltip: null, renderObject: RenderSemanticsAnnotations#8ac3e relayoutBoundary=up7) flutter: │ └MouseRegion(listeners: \[enter, exit], cursor: SystemMouseCursor(click), renderObject: RenderMouseRegion#13d4e relayoutBoundary=up8) flutter: │ └Builder(dependencies: \[DefaultSelectionStyle]) flutter: │ └DefaultSelectionStyle flutter: │ └Semantics(container: false, properties: SemanticsProperties, tooltip: null, renderObject: RenderSemanticsAnnotations#d99cc relayoutBoundary=up9) flutter: │ └GestureDetector(startBehavior: start, dependencies: \[MediaQuery]) flutter: │ └RawGestureDetector(state: RawGestureDetectorState#b8d93(gestures: \[tap], excludeFromSemantics: true, behavior: opaque)) flutter: │ └Listener(listeners: \[down, panZoomStart], behavior: opaque, renderObject: RenderPointerListener#a4c3b relayoutBoundary=up10) flutter: │ └Builder(dependencies: \[IconTheme]) flutter: │ └IconTheme(color: MaterialColor(primary value: Color(0xff2196f3))) flutter: │ └Padding(padding: EdgeInsets(8.0, 0.0, 8.0, 0.0), dependencies: \[Directionality], renderObject: RenderPadding#18a87 relayoutBoundary=up11) flutter: │ └Align(alignment: Alignment.center, widthFactor: 1.0, heightFactor: 1.0, dependencies: \[Directionality], renderObject: RenderPositionedBox#fb8a8 relayoutBoundary=up12) flutter: │ └Text("Dump Widget Tree", dependencies: \[DefaultSelectionStyle, DefaultTextStyle, MediaQuery]) flutter: │ └RichText(softWrap: wrapping at box width, maxLines: unlimited, text: "Dump Widget Tree", dependencies: \[Directionality, \_LocalizationsScope-\[GlobalKey#61ca6]], renderObject: RenderParagraph#d15aa relayoutBoundary=up13) flutter: └PositionedDirectional(dependencies: \[Directionality]) flutter: └Positioned(left: 0.0, top: 0.0, bottom: 0.0, width: 20.0) flutter: └Listener(listeners: \[down], behavior: translucent, renderObject: RenderPointerListener#d884c) flutter: flutter: \`\`\` When the button changes from being pressed to being released, this invokes the \`debugDumpApp()\` function. It also coincides with the \[\`TextButton\`]\[] object calling \[\`setState()\`]\[] and thus marking itself dirty. This explains why a Flutter marks a specific object as "dirty". When you review the widget tree, look for a line that resembles the following: \`\`\`plaintext └TextButton(dirty, dependencies: \[MediaQuery, \_InheritedTheme, \_LocalizationsScope-\[GlobalKey#5880d]], state: \_ButtonStyleState#ab76e) \`\`\` If you write your own widgets, override the \[\`debugFillProperties()\`]\[widget-fill] method to add information. Add \[DiagnosticsProperty]\[] objects to the method's argument and call the superclass method. The \`toString\` method uses this function to fill in the widget's description. ### Print the render tree When debugging a layout issue, the Widgets layer's tree might lack detail. The next level of debugging might require a render tree. To dump the render tree: 1. Open your source file. 1. Call the \[\`debugDumpRenderTree()\`]\[] function. You can call this any time except during a layout or paint phase. Consider calling it from a \[frame callback]\[] or an event handler. 1. If you haven't started your app, debug it using your IDE. 1. If you have started your app, save your source file. Hot reload re-renders your app. #### Example 5: Call \`debugDumpRenderTree()\`

<!--?code-excerpt "lib/dump_render_tree.dart"?-->

\`\`\`dart import 'package:material\_ui/material\_ui.dart'; void main() { runApp(const MaterialApp(home: AppHome())); } class AppHome extends StatelessWidget { const AppHome({super.key}); @override Widget build(BuildContext context) { return Material( child: Center( child: TextButton( onPressed: () { debugDumpRenderTree(); }, child: const Text('Dump Render Tree'), ), ), ); } } \`\`\` When debugging layout issues, look at the \`size\` and \`constraints\` fields. The constraints flow down the tree and the sizes flow back up.

**Expand to view the render tree for Example 5**

\`\`\`plaintext flutter: RenderView#02c80 flutter: │ debug mode enabled - macos flutter: │ view size: Size(800.0, 600.0) (in physical pixels) flutter: │ device pixel ratio: 1.0 (physical pixels per logical pixel) flutter: │ configuration: Size(800.0, 600.0) at 1.0x (in logical pixels) flutter: │ flutter: └─child: RenderSemanticsAnnotations#fe6b5 flutter: │ needs compositing flutter: │ creator: Semantics ← \_FocusInheritedScope ← Focus ← flutter: │ HeroControllerScope ← ScrollConfiguration ← MaterialApp ← flutter: │ MediaQuery ← \_MediaQueryFromView ← \_ViewScope ← flutter: │ View-\[GlobalObjectKey FlutterView#6cffa] ← \[root] flutter: │ parentData: flutter: │ constraints: BoxConstraints(w=800.0, h=600.0) flutter: │ size: Size(800.0, 600.0) flutter: │ flutter: └─child: RenderSemanticsAnnotations#6edef flutter: │ needs compositing flutter: │ creator: Semantics ← \_FocusInheritedScope ← Focus ← Shortcuts ← flutter: │ \_SharedAppModel ← SharedAppData ← UnmanagedRestorationScope ← flutter: │ RestorationScope ← UnmanagedRestorationScope ← flutter: │ RootRestorationScope ← WidgetsApp-\[GlobalObjectKey flutter: │ \_MaterialAppState#5c303] ← Semantics ← ⋯ flutter: │ parentData: (can use size) flutter: │ constraints: BoxConstraints(w=800.0, h=600.0) flutter: │ size: Size(800.0, 600.0) flutter: │ flutter: └─child: RenderSemanticsAnnotations#e8ce8 flutter: │ needs compositing flutter: │ creator: Semantics ← \_FocusInheritedScope ← Focus ← Shortcuts ← flutter: │ DefaultTextEditingShortcuts ← Semantics ← \_FocusInheritedScope flutter: │ ← Focus ← Shortcuts ← \_SharedAppModel ← SharedAppData ← flutter: │ UnmanagedRestorationScope ← ⋯ flutter: │ parentData: (can use size) flutter: │ constraints: BoxConstraints(w=800.0, h=600.0) flutter: │ size: Size(800.0, 600.0) flutter: │ flutter: └─child: RenderSemanticsAnnotations#fc545 flutter: │ needs compositing flutter: │ creator: Semantics ← \_FocusInheritedScope ← Focus ← Shortcuts ← flutter: │ Semantics ← \_FocusInheritedScope ← Focus ← Shortcuts ← flutter: │ DefaultTextEditingShortcuts ← Semantics ← \_FocusInheritedScope flutter: │ ← Focus ← ⋯ flutter: │ parentData: (can use size) flutter: │ constraints: BoxConstraints(w=800.0, h=600.0) flutter: │ size: Size(800.0, 600.0) flutter: │ flutter: └─child: RenderTapRegionSurface#ff857 flutter: │ needs compositing flutter: │ creator: TapRegionSurface ← \_FocusInheritedScope ← Focus ← flutter: │ FocusTraversalGroup ← \_ActionsScope ← Actions ← Semantics ← flutter: │ \_FocusInheritedScope ← Focus ← Shortcuts ← Semantics ← flutter: │ \_FocusInheritedScope ← ⋯ flutter: │ parentData: (can use size) flutter: │ constraints: BoxConstraints(w=800.0, h=600.0) flutter: │ size: Size(800.0, 600.0) flutter: │ behavior: deferToChild flutter: │ flutter: └─child: RenderSemanticsAnnotations#fe316 flutter: │ needs compositing flutter: │ creator: Semantics ← \_FocusInheritedScope ← Focus ← Shortcuts ← flutter: │ \_ShortcutRegistrarScope ← ShortcutRegistrar ← TapRegionSurface flutter: │ ← \_FocusInheritedScope ← Focus ← FocusTraversalGroup ← flutter: │ \_ActionsScope ← Actions ← ⋯ flutter: │ parentData: (can use size) flutter: │ constraints: BoxConstraints(w=800.0, h=600.0) flutter: │ size: Size(800.0, 600.0) flutter: │ flutter: └─child: RenderSemanticsAnnotations#fa55c flutter: │ needs compositing flutter: │ creator: Semantics ← Localizations ← Semantics ← flutter: │ \_FocusInheritedScope ← Focus ← Shortcuts ← flutter: │ \_ShortcutRegistrarScope ← ShortcutRegistrar ← TapRegionSurface flutter: │ ← \_FocusInheritedScope ← Focus ← FocusTraversalGroup ← ⋯ flutter: │ parentData: (can use size) flutter: │ constraints: BoxConstraints(w=800.0, h=600.0) flutter: │ size: Size(800.0, 600.0) flutter: │ flutter: └─child: RenderCustomPaint#4b256 flutter: │ needs compositing flutter: │ creator: CustomPaint ← Banner ← CheckedModeBanner ← Title ← flutter: │ Directionality ← \_LocalizationsScope-\[GlobalKey#4a3aa] ← flutter: │ Semantics ← Localizations ← Semantics ← \_FocusInheritedScope ← flutter: │ Focus ← Shortcuts ← ⋯ flutter: │ parentData: (can use size) flutter: │ constraints: BoxConstraints(w=800.0, h=600.0) flutter: │ size: Size(800.0, 600.0) flutter: │ painter: null flutter: │ foregroundPainter: BannerPainter#1bfd7(Instance of flutter: │ '\_SystemFontsNotifier') flutter: │ flutter: └─child: RenderSemanticsAnnotations#f470f flutter: │ needs compositing flutter: │ creator: Semantics ← FocusScope ← DefaultSelectionStyle ← flutter: │ IconTheme ← IconTheme ← \_InheritedCupertinoTheme ← flutter: │ CupertinoTheme ← \_InheritedTheme ← Theme ← AnimatedTheme ← flutter: │ DefaultSelectionStyle ← \_ScaffoldMessengerScope ← ⋯ flutter: │ parentData: (can use size) flutter: │ constraints: BoxConstraints(w=800.0, h=600.0) flutter: │ size: Size(800.0, 600.0) flutter: │ flutter: └─child: RenderPointerListener#f59c8 flutter: │ needs compositing flutter: │ creator: Listener ← HeroControllerScope ← flutter: │ Navigator-\[GlobalObjectKey flutter: │ \_WidgetsAppState#0d73a] ← \_FocusInheritedScope ← Semantics ← flutter: │ FocusScope ← DefaultSelectionStyle ← IconTheme ← IconTheme ← flutter: │ \_InheritedCupertinoTheme ← CupertinoTheme ← \_InheritedTheme ← ⋯ flutter: │ parentData: (can use size) flutter: │ constraints: BoxConstraints(w=800.0, h=600.0) flutter: │ size: Size(800.0, 600.0) flutter: │ behavior: deferToChild flutter: │ listeners: down, up, cancel flutter: │ flutter: └─child: RenderAbsorbPointer#c91bd flutter: │ needs compositing flutter: │ creator: AbsorbPointer ← Listener ← HeroControllerScope ← flutter: │ Navigator-\[GlobalObjectKey flutter: │ \_WidgetsAppState#0d73a] ← \_FocusInheritedScope ← Semantics ← flutter: │ FocusScope ← DefaultSelectionStyle ← IconTheme ← IconTheme ← flutter: │ \_InheritedCupertinoTheme ← CupertinoTheme ← ⋯ flutter: │ parentData: (can use size) flutter: │ constraints: BoxConstraints(w=800.0, h=600.0) flutter: │ size: Size(800.0, 600.0) flutter: │ absorbing: false flutter: │ ignoringSemantics: null flutter: │ flutter: └─child: \_RenderTheater#07897 flutter: │ needs compositing flutter: │ creator: \_Theater ← flutter: │ Overlay-\[LabeledGlobalKey#49a93] ← flutter: │ UnmanagedRestorationScope ← \_FocusInheritedScope ← Focus ← flutter: │ \_FocusInheritedScope ← Focus ← FocusTraversalGroup ← flutter: │ AbsorbPointer ← Listener ← HeroControllerScope ← flutter: │ Navigator-\[GlobalObjectKey flutter: │ \_WidgetsAppState#0d73a] ← ⋯ flutter: │ parentData: (can use size) flutter: │ constraints: BoxConstraints(w=800.0, h=600.0) flutter: │ size: Size(800.0, 600.0) flutter: │ skipCount: 0 flutter: │ textDirection: ltr flutter: │ flutter: ├─onstage 1: RenderIgnorePointer#3b659 flutter: │ │ creator: IgnorePointer ← \_RenderTheaterMarker ← flutter: │ │ \_EffectiveTickerMode ← TickerMode ← flutter: │ │ \_OverlayEntryWidget-\[LabeledGlobalKey<\_OverlayEntryWidgetState>#a47f4] flutter: │ │ ← \_Theater ← Overlay-\[LabeledGlobalKey#49a93] ← flutter: │ │ UnmanagedRestorationScope ← \_FocusInheritedScope ← Focus ← flutter: │ │ \_FocusInheritedScope ← Focus ← ⋯ flutter: │ │ parentData: not positioned; offset=Offset(0.0, 0.0) (can use flutter: │ │ size) flutter: │ │ constraints: BoxConstraints(w=800.0, h=600.0) flutter: │ │ size: Size(800.0, 600.0) flutter: │ │ ignoring: false flutter: │ │ ignoringSemantics: null flutter: │ │ flutter: │ └─child: RenderBlockSemantics#7586c flutter: │ │ creator: BlockSemantics ← ModalBarrier ← IgnorePointer ← flutter: │ │ \_RenderTheaterMarker ← \_EffectiveTickerMode ← TickerMode ← flutter: │ │ \_OverlayEntryWidget-\[LabeledGlobalKey<\_OverlayEntryWidgetState>#a47f4] flutter: │ │ ← \_Theater ← Overlay-\[LabeledGlobalKey#49a93] ← flutter: │ │ UnmanagedRestorationScope ← \_FocusInheritedScope ← Focus ← ⋯ flutter: │ │ parentData: (can use size) flutter: │ │ constraints: BoxConstraints(w=800.0, h=600.0) flutter: │ │ blocks semantics of earlier render objects below the common flutter: │ │ boundary flutter: │ │ size: Size(800.0, 600.0) flutter: │ │ blocking: true flutter: │ │ flutter: │ └─child: RenderExcludeSemantics#c1d3f flutter: │ │ creator: ExcludeSemantics ← BlockSemantics ← ModalBarrier ← flutter: │ │ IgnorePointer ← \_RenderTheaterMarker ← \_EffectiveTickerMode ← flutter: │ │ TickerMode ← flutter: │ │ \_OverlayEntryWidget-\[LabeledGlobalKey<\_OverlayEntryWidgetState>#a47f4] flutter: │ │ ← \_Theater ← Overlay-\[LabeledGlobalKey#49a93] ← flutter: │ │ UnmanagedRestorationScope ← \_FocusInheritedScope ← ⋯ flutter: │ │ parentData: (can use size) flutter: │ │ constraints: BoxConstraints(w=800.0, h=600.0) flutter: │ │ size: Size(800.0, 600.0) flutter: │ │ excluding: true flutter: │ │ flutter: │ └─child: RenderSemanticsGestureHandler#70b16 flutter: │ │ creator: \_GestureSemantics ← RawGestureDetector ← flutter: │ │ \_ModalBarrierGestureDetector ← ExcludeSemantics ← flutter: │ │ BlockSemantics ← ModalBarrier ← IgnorePointer ← flutter: │ │ \_RenderTheaterMarker ← \_EffectiveTickerMode ← TickerMode ← flutter: │ │ \_OverlayEntryWidget-\[LabeledGlobalKey<\_OverlayEntryWidgetState>#a47f4] flutter: │ │ ← \_Theater ← ⋯ flutter: │ │ parentData: (can use size) flutter: │ │ constraints: BoxConstraints(w=800.0, h=600.0) flutter: │ │ size: Size(800.0, 600.0) flutter: │ │ behavior: opaque flutter: │ │ gestures: flutter: │ │ flutter: │ └─child: RenderPointerListener#1f34a flutter: │ │ creator: Listener ← \_GestureSemantics ← RawGestureDetector ← flutter: │ │ \_ModalBarrierGestureDetector ← ExcludeSemantics ← flutter: │ │ BlockSemantics ← ModalBarrier ← IgnorePointer ← flutter: │ │ \_RenderTheaterMarker ← \_EffectiveTickerMode ← TickerMode ← flutter: │ │ \_OverlayEntryWidget-\[LabeledGlobalKey<\_OverlayEntryWidgetState>#a47f4] flutter: │ │ ← ⋯ flutter: │ │ parentData: (can use size) flutter: │ │ constraints: BoxConstraints(w=800.0, h=600.0) flutter: │ │ size: Size(800.0, 600.0) flutter: │ │ behavior: opaque flutter: │ │ listeners: down, panZoomStart flutter: │ │ flutter: │ └─child: RenderSemanticsAnnotations#73467 flutter: │ │ creator: Semantics ← Listener ← \_GestureSemantics ← flutter: │ │ RawGestureDetector ← \_ModalBarrierGestureDetector ← flutter: │ │ ExcludeSemantics ← BlockSemantics ← ModalBarrier ← flutter: │ │ IgnorePointer ← \_RenderTheaterMarker ← \_EffectiveTickerMode ← flutter: │ │ TickerMode ← ⋯ flutter: │ │ parentData: (can use size) flutter: │ │ constraints: BoxConstraints(w=800.0, h=600.0) flutter: │ │ size: Size(800.0, 600.0) flutter: │ │ flutter: │ └─child: RenderMouseRegion#560dc flutter: │ │ creator: MouseRegion ← Semantics ← Listener ← \_GestureSemantics ← flutter: │ │ RawGestureDetector ← \_ModalBarrierGestureDetector ← flutter: │ │ ExcludeSemantics ← BlockSemantics ← ModalBarrier ← flutter: │ │ IgnorePointer ← \_RenderTheaterMarker ← \_EffectiveTickerMode ← ⋯ flutter: │ │ parentData: (can use size) flutter: │ │ constraints: BoxConstraints(w=800.0, h=600.0) flutter: │ │ size: Size(800.0, 600.0) flutter: │ │ behavior: opaque flutter: │ │ listeners: flutter: │ │ cursor: SystemMouseCursor(basic) flutter: │ │ flutter: │ └─child: RenderConstrainedBox#01e8c flutter: │ creator: ConstrainedBox ← MouseRegion ← Semantics ← Listener ← flutter: │ \_GestureSemantics ← RawGestureDetector ← flutter: │ \_ModalBarrierGestureDetector ← ExcludeSemantics ← flutter: │ BlockSemantics ← ModalBarrier ← IgnorePointer ← flutter: │ \_RenderTheaterMarker ← ⋯ flutter: │ parentData: (can use size) flutter: │ constraints: BoxConstraints(w=800.0, h=600.0) flutter: │ size: Size(800.0, 600.0) flutter: │ additionalConstraints: BoxConstraints(biggest) flutter: │ flutter: ├─onstage 2: RenderSemanticsAnnotations#8187b flutter: ╎ │ needs compositing flutter: ╎ │ creator: Semantics ← \_RenderTheaterMarker ← \_EffectiveTickerMode flutter: ╎ │ ← TickerMode ← flutter: ╎ │ \_OverlayEntryWidget-\[LabeledGlobalKey<\_OverlayEntryWidgetState>#8cd54] flutter: ╎ │ ← \_Theater ← Overlay-\[LabeledGlobalKey#49a93] ← flutter: ╎ │ UnmanagedRestorationScope ← \_FocusInheritedScope ← Focus ← flutter: ╎ │ \_FocusInheritedScope ← Focus ← ⋯ flutter: ╎ │ parentData: not positioned; offset=Offset(0.0, 0.0) (can use flutter: ╎ │ size) flutter: ╎ │ constraints: BoxConstraints(w=800.0, h=600.0) flutter: ╎ │ size: Size(800.0, 600.0) flutter: ╎ │ flutter: ╎ └─child: RenderOffstage#f211d flutter: ╎ │ needs compositing flutter: ╎ │ creator: Offstage ← \_ModalScopeStatus ← UnmanagedRestorationScope flutter: ╎ │ ← RestorationScope ← AnimatedBuilder ← flutter: ╎ │ \_ModalScope-\[LabeledGlobalKey<\_ModalScopeState>#db401] flutter: ╎ │ ← Semantics ← \_RenderTheaterMarker ← \_EffectiveTickerMode ← flutter: ╎ │ TickerMode ← flutter: ╎ │ \_OverlayEntryWidget-\[LabeledGlobalKey<\_OverlayEntryWidgetState>#8cd54] flutter: ╎ │ ← \_Theater ← ⋯ flutter: ╎ │ parentData: (can use size) flutter: ╎ │ constraints: BoxConstraints(w=800.0, h=600.0) flutter: ╎ │ size: Size(800.0, 600.0) flutter: ╎ │ offstage: false flutter: ╎ │ flutter: ╎ └─child: RenderSemanticsAnnotations#9436c flutter: ╎ │ needs compositing flutter: ╎ │ creator: Semantics ← FocusScope ← PrimaryScrollController ← flutter: ╎ │ \_ActionsScope ← Actions ← Builder ← PageStorage ← Offstage ← flutter: ╎ │ \_ModalScopeStatus ← UnmanagedRestorationScope ← flutter: ╎ │ RestorationScope ← AnimatedBuilder ← ⋯ flutter: ╎ │ parentData: (can use size) flutter: ╎ │ constraints: BoxConstraints(w=800.0, h=600.0) flutter: ╎ │ size: Size(800.0, 600.0) flutter: ╎ │ flutter: ╎ └─child: RenderRepaintBoundary#f8f28 flutter: ╎ │ needs compositing flutter: ╎ │ creator: RepaintBoundary ← \_FocusInheritedScope ← Semantics ← flutter: ╎ │ FocusScope ← PrimaryScrollController ← \_ActionsScope ← Actions flutter: ╎ │ ← Builder ← PageStorage ← Offstage ← \_ModalScopeStatus ← flutter: ╎ │ UnmanagedRestorationScope ← ⋯ flutter: ╎ │ parentData: (can use size) flutter: ╎ │ constraints: BoxConstraints(w=800.0, h=600.0) flutter: ╎ │ layer: OffsetLayer#e73b7 flutter: ╎ │ size: Size(800.0, 600.0) flutter: ╎ │ metrics: 66.7% useful (1 bad vs 2 good) flutter: ╎ │ diagnosis: insufficient data to draw conclusion (less than five flutter: ╎ │ repaints) flutter: ╎ │ flutter: ╎ └─child: RenderFractionalTranslation#c3a54 flutter: ╎ │ needs compositing flutter: ╎ │ creator: FractionalTranslation ← SlideTransition ← flutter: ╎ │ CupertinoPageTransition ← AnimatedBuilder ← RepaintBoundary ← flutter: ╎ │ \_FocusInheritedScope ← Semantics ← FocusScope ← flutter: ╎ │ PrimaryScrollController ← \_ActionsScope ← Actions ← Builder ← ⋯ flutter: ╎ │ parentData: (can use size) flutter: ╎ │ constraints: BoxConstraints(w=800.0, h=600.0) flutter: ╎ │ size: Size(800.0, 600.0) flutter: ╎ │ translation: Offset(0.0, 0.0) flutter: ╎ │ transformHitTests: false flutter: ╎ │ flutter: ╎ └─child: RenderFractionalTranslation#7fcf2 flutter: ╎ │ needs compositing flutter: ╎ │ creator: FractionalTranslation ← SlideTransition ← flutter: ╎ │ FractionalTranslation ← SlideTransition ← flutter: ╎ │ CupertinoPageTransition ← AnimatedBuilder ← RepaintBoundary ← flutter: ╎ │ \_FocusInheritedScope ← Semantics ← FocusScope ← flutter: ╎ │ PrimaryScrollController ← \_ActionsScope ← ⋯ flutter: ╎ │ parentData: (can use size) flutter: ╎ │ constraints: BoxConstraints(w=800.0, h=600.0) flutter: ╎ │ size: Size(800.0, 600.0) flutter: ╎ │ translation: Offset(0.0, 0.0) flutter: ╎ │ transformHitTests: true flutter: ╎ │ flutter: ╎ └─child: RenderDecoratedBox#713ec flutter: ╎ │ needs compositing flutter: ╎ │ creator: DecoratedBox ← DecoratedBoxTransition ← flutter: ╎ │ FractionalTranslation ← SlideTransition ← FractionalTranslation flutter: ╎ │ ← SlideTransition ← CupertinoPageTransition ← AnimatedBuilder ← flutter: ╎ │ RepaintBoundary ← \_FocusInheritedScope ← Semantics ← FocusScope flutter: ╎ │ ← ⋯ flutter: ╎ │ parentData: (can use size) flutter: ╎ │ constraints: BoxConstraints(w=800.0, h=600.0) flutter: ╎ │ size: Size(800.0, 600.0) flutter: ╎ │ ├─decoration: \_CupertinoEdgeShadowDecoration flutter: ╎ │ colors: Color(0x04000000), Color(0x00000000) flutter: ╎ │ flutter: ╎ │ configuration: ImageConfiguration(bundle: flutter: ╎ │ PlatformAssetBundle#164ca(), devicePixelRatio: 1.0, locale: flutter: ╎ │ en\_US, textDirection: TextDirection.ltr, platform: macOS) flutter: ╎ │ flutter: ╎ └─child: RenderStack#83b13 flutter: ╎ │ needs compositing flutter: ╎ │ creator: Stack ← \_CupertinoBackGestureDetector ← flutter: ╎ │ DecoratedBox ← DecoratedBoxTransition ← FractionalTranslation ← flutter: ╎ │ SlideTransition ← FractionalTranslation ← SlideTransition ← flutter: ╎ │ CupertinoPageTransition ← AnimatedBuilder ← RepaintBoundary ← flutter: ╎ │ \_FocusInheritedScope ← ⋯ flutter: ╎ │ parentData: (can use size) flutter: ╎ │ constraints: BoxConstraints(w=800.0, h=600.0) flutter: ╎ │ size: Size(800.0, 600.0) flutter: ╎ │ alignment: AlignmentDirectional.topStart flutter: ╎ │ textDirection: ltr flutter: ╎ │ fit: passthrough flutter: ╎ │ flutter: ╎ ├─child 1: RenderIgnorePointer#ad50f flutter: ╎ │ │ needs compositing flutter: ╎ │ │ creator: IgnorePointer ← AnimatedBuilder ← Stack ← flutter: ╎ │ │ \_CupertinoBackGestureDetector ← DecoratedBox ← flutter: ╎ │ │ DecoratedBoxTransition ← FractionalTranslation ← flutter: ╎ │ │ SlideTransition ← FractionalTranslation ← SlideTransition ← flutter: ╎ │ │ CupertinoPageTransition ← AnimatedBuilder ← ⋯ flutter: ╎ │ │ parentData: not positioned; offset=Offset(0.0, 0.0) (can use flutter: ╎ │ │ size) flutter: ╎ │ │ constraints: BoxConstraints(w=800.0, h=600.0) flutter: ╎ │ │ size: Size(800.0, 600.0) flutter: ╎ │ │ ignoring: false flutter: ╎ │ │ ignoringSemantics: null flutter: ╎ │ │ flutter: ╎ │ └─child: RenderRepaintBoundary#29754 flutter: ╎ │ │ needs compositing flutter: ╎ │ │ creator: RepaintBoundary-\[GlobalKey#75409] ← IgnorePointer ← flutter: ╎ │ │ AnimatedBuilder ← Stack ← flutter: ╎ │ │ \_CupertinoBackGestureDetector ← DecoratedBox ← flutter: ╎ │ │ DecoratedBoxTransition ← FractionalTranslation ← flutter: ╎ │ │ SlideTransition ← FractionalTranslation ← SlideTransition ← flutter: ╎ │ │ CupertinoPageTransition ← ⋯ flutter: ╎ │ │ parentData: (can use size) flutter: ╎ │ │ constraints: BoxConstraints(w=800.0, h=600.0) flutter: ╎ │ │ layer: OffsetLayer#fa835 flutter: ╎ │ │ size: Size(800.0, 600.0) flutter: ╎ │ │ metrics: 90.9% useful (1 bad vs 10 good) flutter: ╎ │ │ diagnosis: this is an outstandingly useful repaint boundary and flutter: ╎ │ │ should definitely be kept flutter: ╎ │ │ flutter: ╎ │ └─child: RenderSemanticsAnnotations#95566 flutter: ╎ │ │ creator: Semantics ← Builder ← RepaintBoundary-\[GlobalKey#75409] flutter: ╎ │ │ ← IgnorePointer ← AnimatedBuilder ← Stack ← flutter: ╎ │ │ \_CupertinoBackGestureDetector ← DecoratedBox ← flutter: ╎ │ │ DecoratedBoxTransition ← FractionalTranslation ← flutter: ╎ │ │ SlideTransition ← FractionalTranslation ← ⋯ flutter: ╎ │ │ parentData: (can use size) flutter: ╎ │ │ constraints: BoxConstraints(w=800.0, h=600.0) flutter: ╎ │ │ size: Size(800.0, 600.0) flutter: ╎ │ │ flutter: ╎ │ └─child: RenderPhysicalModel#bc9d7 flutter: ╎ │ │ creator: PhysicalModel ← AnimatedPhysicalModel ← Material ← flutter: ╎ │ │ AppHome ← Semantics ← Builder ← flutter: ╎ │ │ RepaintBoundary-\[GlobalKey#75409] ← IgnorePointer ← flutter: ╎ │ │ AnimatedBuilder ← Stack ← flutter: ╎ │ │ \_CupertinoBackGestureDetector ← DecoratedBox ← ⋯ flutter: ╎ │ │ parentData: (can use size) flutter: ╎ │ │ constraints: BoxConstraints(w=800.0, h=600.0) flutter: ╎ │ │ size: Size(800.0, 600.0) flutter: ╎ │ │ elevation: 0.0 flutter: ╎ │ │ color: Color(0xfffafafa) flutter: ╎ │ │ shadowColor: Color(0xfffafafa) flutter: ╎ │ │ shape: BoxShape.rectangle flutter: ╎ │ │ borderRadius: BorderRadius.zero flutter: ╎ │ │ flutter: ╎ │ └─child: \_RenderInkFeatures#ac819 flutter: ╎ │ │ creator: \_InkFeatures-\[GlobalKey#d721e ink renderer] ← flutter: ╎ │ │ NotificationListener ← PhysicalModel flutter: ╎ │ │ ← AnimatedPhysicalModel ← Material ← AppHome ← Semantics ← flutter: ╎ │ │ Builder ← RepaintBoundary-\[GlobalKey#75409] ← IgnorePointer ← flutter: ╎ │ │ AnimatedBuilder ← Stack ← ⋯ flutter: ╎ │ │ parentData: (can use size) flutter: ╎ │ │ constraints: BoxConstraints(w=800.0, h=600.0) flutter: ╎ │ │ size: Size(800.0, 600.0) flutter: ╎ │ │ flutter: ╎ │ └─child: RenderPositionedBox#dc1df flutter: ╎ │ │ creator: Center ← DefaultTextStyle ← AnimatedDefaultTextStyle ← flutter: ╎ │ │ \_InkFeatures-\[GlobalKey#d721e ink renderer] ← flutter: ╎ │ │ NotificationListener ← PhysicalModel flutter: ╎ │ │ ← AnimatedPhysicalModel ← Material ← AppHome ← Semantics ← flutter: ╎ │ │ Builder ← RepaintBoundary-\[GlobalKey#75409] ← ⋯ flutter: ╎ │ │ parentData: (can use size) flutter: ╎ │ │ constraints: BoxConstraints(w=800.0, h=600.0) flutter: ╎ │ │ size: Size(800.0, 600.0) flutter: ╎ │ │ alignment: Alignment.center flutter: ╎ │ │ textDirection: ltr flutter: ╎ │ │ widthFactor: expand flutter: ╎ │ │ heightFactor: expand flutter: ╎ │ │ flutter: ╎ │ └─child: RenderSemanticsAnnotations#a0a4b relayoutBoundary=up1 flutter: ╎ │ │ creator: Semantics ← TextButton ← Center ← DefaultTextStyle ← flutter: ╎ │ │ AnimatedDefaultTextStyle ← \_InkFeatures-\[GlobalKey#d721e ink flutter: ╎ │ │ renderer] ← NotificationListener ← flutter: ╎ │ │ PhysicalModel ← AnimatedPhysicalModel ← Material ← AppHome ← flutter: ╎ │ │ Semantics ← ⋯ flutter: ╎ │ │ parentData: offset=Offset(329.0, 286.0) (can use size) flutter: ╎ │ │ constraints: BoxConstraints(0.0<=w<=800.0, 0.0<=h<=600.0) flutter: ╎ │ │ semantic boundary flutter: ╎ │ │ size: Size(142.0, 28.0) flutter: ╎ │ │ flutter: ╎ │ └─child: \_RenderInputPadding#4672f relayoutBoundary=up2 flutter: ╎ │ │ creator: \_InputPadding ← Semantics ← TextButton ← Center ← flutter: ╎ │ │ DefaultTextStyle ← AnimatedDefaultTextStyle ← flutter: ╎ │ │ \_InkFeatures-\[GlobalKey#d721e ink renderer] ← flutter: ╎ │ │ NotificationListener ← PhysicalModel flutter: ╎ │ │ ← AnimatedPhysicalModel ← Material ← AppHome ← ⋯ flutter: ╎ │ │ parentData: (can use size) flutter: ╎ │ │ constraints: BoxConstraints(0.0<=w<=800.0, 0.0<=h<=600.0) flutter: ╎ │ │ size: Size(142.0, 28.0) flutter: ╎ │ │ flutter: ╎ │ └─child: RenderConstrainedBox#425d6 relayoutBoundary=up3 flutter: ╎ │ │ creator: ConstrainedBox ← \_InputPadding ← Semantics ← TextButton flutter: ╎ │ │ ← Center ← DefaultTextStyle ← AnimatedDefaultTextStyle ← flutter: ╎ │ │ \_InkFeatures-\[GlobalKey#d721e ink renderer] ← flutter: ╎ │ │ NotificationListener ← PhysicalModel flutter: ╎ │ │ ← AnimatedPhysicalModel ← Material ← ⋯ flutter: ╎ │ │ parentData: offset=Offset(0.0, 0.0) (can use size) flutter: ╎ │ │ constraints: BoxConstraints(0.0<=w<=800.0, 0.0<=h<=600.0) flutter: ╎ │ │ size: Size(142.0, 28.0) flutter: ╎ │ │ additionalConstraints: BoxConstraints(56.0<=w<=Infinity, flutter: ╎ │ │ 28.0<=h<=Infinity) flutter: ╎ │ │ flutter: ╎ │ └─child: RenderPhysicalShape#8e171 relayoutBoundary=up4 flutter: ╎ │ │ creator: PhysicalShape ← \_MaterialInterior ← Material ← flutter: ╎ │ │ ConstrainedBox ← \_InputPadding ← Semantics ← TextButton ← flutter: ╎ │ │ Center ← DefaultTextStyle ← AnimatedDefaultTextStyle ← flutter: ╎ │ │ \_InkFeatures-\[GlobalKey#d721e ink renderer] ← flutter: ╎ │ │ NotificationListener ← ⋯ flutter: ╎ │ │ parentData: (can use size) flutter: ╎ │ │ constraints: BoxConstraints(56.0<=w<=800.0, 28.0<=h<=600.0) flutter: ╎ │ │ size: Size(142.0, 28.0) flutter: ╎ │ │ elevation: 0.0 flutter: ╎ │ │ color: Color(0x00000000) flutter: ╎ │ │ shadowColor: Color(0x00000000) flutter: ╎ │ │ clipper: ShapeBorderClipper flutter: ╎ │ │ flutter: ╎ │ └─child: RenderCustomPaint#eea46 relayoutBoundary=up5 flutter: ╎ │ │ creator: CustomPaint ← \_ShapeBorderPaint ← PhysicalShape ← flutter: ╎ │ │ \_MaterialInterior ← Material ← ConstrainedBox ← \_InputPadding ← flutter: ╎ │ │ Semantics ← TextButton ← Center ← DefaultTextStyle ← flutter: ╎ │ │ AnimatedDefaultTextStyle ← ⋯ flutter: ╎ │ │ parentData: (can use size) flutter: ╎ │ │ constraints: BoxConstraints(56.0<=w<=800.0, 28.0<=h<=600.0) flutter: ╎ │ │ size: Size(142.0, 28.0) flutter: ╎ │ │ painter: null flutter: ╎ │ │ foregroundPainter: \_ShapeBorderPainter#ac724() flutter: ╎ │ │ flutter: ╎ │ └─child: \_RenderInkFeatures#b19a7 relayoutBoundary=up6 flutter: ╎ │ │ creator: \_InkFeatures-\[GlobalKey#87971 ink renderer] ← flutter: ╎ │ │ NotificationListener ← CustomPaint ← flutter: ╎ │ │ \_ShapeBorderPaint ← PhysicalShape ← \_MaterialInterior ← flutter: ╎ │ │ Material ← ConstrainedBox ← \_InputPadding ← Semantics ← flutter: ╎ │ │ TextButton ← Center ← ⋯ flutter: ╎ │ │ parentData: (can use size) flutter: ╎ │ │ constraints: BoxConstraints(56.0<=w<=800.0, 28.0<=h<=600.0) flutter: ╎ │ │ size: Size(142.0, 28.0) flutter: ╎ │ │ flutter: ╎ │ └─child: RenderSemanticsAnnotations#4d1b3 relayoutBoundary=up7 flutter: ╎ │ │ creator: Semantics ← \_FocusInheritedScope ← Focus ← \_ActionsScope flutter: ╎ │ │ ← Actions ← \_ParentInkResponseProvider ← flutter: ╎ │ │ \_InkResponseStateWidget ← InkWell ← DefaultTextStyle ← flutter: ╎ │ │ AnimatedDefaultTextStyle ← \_InkFeatures-\[GlobalKey#87971 ink flutter: ╎ │ │ renderer] ← NotificationListener ← ⋯ flutter: ╎ │ │ parentData: (can use size) flutter: ╎ │ │ constraints: BoxConstraints(56.0<=w<=800.0, 28.0<=h<=600.0) flutter: ╎ │ │ size: Size(142.0, 28.0) flutter: ╎ │ │ flutter: ╎ │ └─child: RenderMouseRegion#e5b3f relayoutBoundary=up8 flutter: ╎ │ │ creator: MouseRegion ← Semantics ← \_FocusInheritedScope ← Focus ← flutter: ╎ │ │ \_ActionsScope ← Actions ← \_ParentInkResponseProvider ← flutter: ╎ │ │ \_InkResponseStateWidget ← InkWell ← DefaultTextStyle ← flutter: ╎ │ │ AnimatedDefaultTextStyle ← \_InkFeatures-\[GlobalKey#87971 ink flutter: ╎ │ │ renderer] ← ⋯ flutter: ╎ │ │ parentData: (can use size) flutter: ╎ │ │ constraints: BoxConstraints(56.0<=w<=800.0, 28.0<=h<=600.0) flutter: ╎ │ │ size: Size(142.0, 28.0) flutter: ╎ │ │ behavior: opaque flutter: ╎ │ │ listeners: enter, exit flutter: ╎ │ │ cursor: SystemMouseCursor(click) flutter: ╎ │ │ flutter: ╎ │ └─child: RenderSemanticsAnnotations#deb9b relayoutBoundary=up9 flutter: ╎ │ │ creator: Semantics ← DefaultSelectionStyle ← Builder ← flutter: ╎ │ │ MouseRegion ← Semantics ← \_FocusInheritedScope ← Focus ← flutter: ╎ │ │ \_ActionsScope ← Actions ← \_ParentInkResponseProvider ← flutter: ╎ │ │ \_InkResponseStateWidget ← InkWell ← ⋯ flutter: ╎ │ │ parentData: (can use size) flutter: ╎ │ │ constraints: BoxConstraints(56.0<=w<=800.0, 28.0<=h<=600.0) flutter: ╎ │ │ size: Size(142.0, 28.0) flutter: ╎ │ │ flutter: ╎ │ └─child: RenderPointerListener#2017a relayoutBoundary=up10 flutter: ╎ │ │ creator: Listener ← RawGestureDetector ← GestureDetector ← flutter: ╎ │ │ Semantics ← DefaultSelectionStyle ← Builder ← MouseRegion ← flutter: ╎ │ │ Semantics ← \_FocusInheritedScope ← Focus ← \_ActionsScope ← flutter: ╎ │ │ Actions ← ⋯ flutter: ╎ │ │ parentData: (can use size) flutter: ╎ │ │ constraints: BoxConstraints(56.0<=w<=800.0, 28.0<=h<=600.0) flutter: ╎ │ │ size: Size(142.0, 28.0) flutter: ╎ │ │ behavior: opaque flutter: ╎ │ │ listeners: down, panZoomStart flutter: ╎ │ │ flutter: ╎ │ └─child: RenderPadding#8455f relayoutBoundary=up11 flutter: ╎ │ │ creator: Padding ← IconTheme ← Builder ← Listener ← flutter: ╎ │ │ RawGestureDetector ← GestureDetector ← Semantics ← flutter: ╎ │ │ DefaultSelectionStyle ← Builder ← MouseRegion ← Semantics ← flutter: ╎ │ │ \_FocusInheritedScope ← ⋯ flutter: ╎ │ │ parentData: (can use size) flutter: ╎ │ │ constraints: BoxConstraints(56.0<=w<=800.0, 28.0<=h<=600.0) flutter: ╎ │ │ size: Size(142.0, 28.0) flutter: ╎ │ │ padding: EdgeInsets(8.0, 0.0, 8.0, 0.0) flutter: ╎ │ │ textDirection: ltr flutter: ╎ │ │ flutter: ╎ │ └─child: RenderPositionedBox#80b8d relayoutBoundary=up12 flutter: ╎ │ │ creator: Align ← Padding ← IconTheme ← Builder ← Listener ← flutter: ╎ │ │ RawGestureDetector ← GestureDetector ← Semantics ← flutter: ╎ │ │ DefaultSelectionStyle ← Builder ← MouseRegion ← Semantics ← ⋯ flutter: ╎ │ │ parentData: offset=Offset(8.0, 0.0) (can use size) flutter: ╎ │ │ constraints: BoxConstraints(40.0<=w<=784.0, 28.0<=h<=600.0) flutter: ╎ │ │ size: Size(126.0, 28.0) flutter: ╎ │ │ alignment: Alignment.center flutter: ╎ │ │ textDirection: ltr flutter: ╎ │ │ widthFactor: 1.0 flutter: ╎ │ │ heightFactor: 1.0 flutter: ╎ │ │ flutter: ╎ │ └─child: RenderParagraph#59bc2 relayoutBoundary=up13 flutter: ╎ │ │ creator: RichText ← Text ← Align ← Padding ← IconTheme ← Builder flutter: ╎ │ │ ← Listener ← RawGestureDetector ← GestureDetector ← Semantics ← flutter: ╎ │ │ DefaultSelectionStyle ← Builder ← ⋯ flutter: ╎ │ │ parentData: offset=Offset(0.0, 6.0) (can use size) flutter: ╎ │ │ constraints: BoxConstraints(0.0<=w<=784.0, 0.0<=h<=600.0) flutter: ╎ │ │ size: Size(126.0, 16.0) flutter: ╎ │ │ textAlign: start flutter: ╎ │ │ textDirection: ltr flutter: ╎ │ │ softWrap: wrapping at box width flutter: ╎ │ │ overflow: clip flutter: ╎ │ │ locale: en\_US flutter: ╎ │ │ maxLines: unlimited flutter: ╎ │ ╘═╦══ text ═══ flutter: ╎ │ ║ TextSpan: flutter: ╎ │ ║ debugLabel: ((englishLike labelLarge 2014).merge(blackRedwoodCity flutter: ╎ │ ║ labelLarge)).copyWith flutter: ╎ │ ║ inherit: false flutter: ╎ │ ║ color: MaterialColor(primary value: Color(0xff2196f3)) flutter: ╎ │ ║ family: .AppleSystemUIFont flutter: ╎ │ ║ size: 14.0 flutter: ╎ │ ║ weight: 500 flutter: ╎ │ ║ baseline: alphabetic flutter: ╎ │ ║ decoration: TextDecoration.none flutter: ╎ │ ║ "Dump Render Tree" flutter: ╎ │ ╚═══════════ flutter: ╎ └─child 2: RenderPointerListener#db4b5 flutter: ╎ creator: Listener ← Positioned ← PositionedDirectional ← Stack ← flutter: ╎ \_CupertinoBackGestureDetector ← DecoratedBox ← flutter: ╎ DecoratedBoxTransition ← FractionalTranslation ← flutter: ╎ SlideTransition ← FractionalTranslation ← SlideTransition ← flutter: ╎ CupertinoPageTransition ← ⋯ flutter: ╎ parentData: top=0.0; bottom=0.0; left=0.0; width=20.0; flutter: ╎ offset=Offset(0.0, 0.0) (can use size) flutter: ╎ constraints: BoxConstraints(w=20.0, h=600.0) flutter: ╎ size: Size(20.0, 600.0) flutter: ╎ behavior: translucent flutter: ╎ listeners: down flutter: ╎ flutter: └╌no offstage children flutter: \`\`\` In the render tree for \*\*Example 5\*\*: \* The \`RenderView\`, or window size, limits all render objects up to and including \[\`RenderPositionedBox\`]\[]\`#dc1df\` render object to the size of the screen. This example sets the size to \`Size(800.0, 600.0)\` \* The \`constraints\` property of each render object limits the size of each child. This property takes the \[\`BoxConstraints\`]\[] render object as a value. Starting with the \`RenderSemanticsAnnotations#fe6b5\`, the constraint equals \`BoxConstraints(w=800.0, h=600.0)\`. \* The \[\`Center\`]\[] widget created the \`RenderPositionedBox#dc1df\` render object under the \`RenderSemanticsAnnotations#8187b\` subtree. \* Each child under this render object has \`BoxConstraints\` with both minimum and maximum values. For example, \`RenderSemanticsAnnotations#a0a4b\` uses \`BoxConstraints(0.0<=w<=800.0, 0.0<=h<=600.0)\`. \* All children of the \`RenderPhysicalShape#8e171\` render object use \`BoxConstraints(BoxConstraints(56.0<=w<=800.0, 28.0<=h<=600.0))\`. \* The child \`RenderPadding#8455f\` sets a \`padding\` value of \`EdgeInsets(8.0, 0.0, 8.0, 0.0)\`. This sets a left and right padding of 8 to all subsequent children of this render object. They now have new constraints: \`BoxConstraints(40.0<=w<=784.0, 28.0<=h<=600.0)\`. This object, which the \`creator\` field tells us is probably part of the \[\`TextButton\`]\[]'s definition, sets a minimum width of 88 pixels on its contents and a specific height of 36.0. This is the \`TextButton\` class implementing the Material Design guidelines regarding button dimensions. \`RenderPositionedBox#80b8d\` render object loosens the constraints again to center the text within the button. The \[\`RenderParagraph\`]\[]#59bc2 render object picks its size based on its contents. If you follow the sizes back up the tree, you see how the size of the text influences the width of all the boxes that form the button. All parents take their child's dimensions to size themselves. Another way to notice this is by looking at the \`relayoutBoundary\` attribute of in the descriptions of each box. This tells you how many ancestors depend on this element's size. For example, the innermost \`RenderPositionedBox\` line has a \`relayoutBoundary=up13\`. This means that when Flutter marks the \`RenderConstrainedBox\` as dirty, it also marks box's 13 ancestors as dirty because the new dimensions might affect those ancestors. To add information to the dump if you write your own render objects, override \[\`debugFillProperties()\`]\[render-fill]. Add \[DiagnosticsProperty]\[] objects to the method's argument then call the superclass method. ### Print the layer tree To debug a compositing issue, use \[\`debugDumpLayerTree()\`]\[]. #### Example 6: Call \`debugDumpLayerTree()\`

<!--?code-excerpt "lib/dump_layer_tree.dart"?-->

\`\`\`dart import 'package:material\_ui/material\_ui.dart'; void main() { runApp(const MaterialApp(home: AppHome())); } class AppHome extends StatelessWidget { const AppHome({super.key}); @override Widget build(BuildContext context) { return Material( child: Center( child: TextButton( onPressed: () { debugDumpLayerTree(); }, child: const Text('Dump Layer Tree'), ), ), ); } } \`\`\`

**Expand to view the output of layer tree for Example 6**

\`\`\`plaintext flutter: TransformLayer#214da flutter: │ owner: RenderView#ebaaf flutter: │ creator: \[root] flutter: │ engine layer: TransformEngineLayer#535de flutter: │ handles: 1 flutter: │ offset: Offset(0.0, 0.0) flutter: │ transform: flutter: │ \[0] 1.0,0.0,0.0,0.0 flutter: │ \[1] 0.0,1.0,0.0,0.0 flutter: │ \[2] 0.0,0.0,1.0,0.0 flutter: │ \[3] 0.0,0.0,0.0,1.0 flutter: │ flutter: ├─child 1: OffsetLayer#0f766 flutter: │ │ creator: RepaintBoundary ← \_FocusInheritedScope ← Semantics ← flutter: │ │ FocusScope ← PrimaryScrollController ← \_ActionsScope ← Actions flutter: │ │ ← Builder ← PageStorage ← Offstage ← \_ModalScopeStatus ← flutter: │ │ UnmanagedRestorationScope ← ⋯ flutter: │ │ engine layer: OffsetEngineLayer#1768d flutter: │ │ handles: 2 flutter: │ │ offset: Offset(0.0, 0.0) flutter: │ │ flutter: │ ├─child 1: PictureLayer#dd023 flutter: │ │ handles: 1 flutter: │ │ paint bounds: Rect.fromLTRB(0.0, 0.0, 800.0, 600.0) flutter: │ │ picture: \_NativePicture#36f94 flutter: │ │ raster cache hints: isComplex = false, willChange = false flutter: │ │ flutter: │ └─child 2: OffsetLayer#4cfc8 flutter: │ │ creator: RepaintBoundary-\[GlobalKey#bd5d9] ← IgnorePointer ← flutter: │ │ AnimatedBuilder ← Stack ← flutter: │ │ \_CupertinoBackGestureDetector ← DecoratedBox ← flutter: │ │ DecoratedBoxTransition ← FractionalTranslation ← flutter: │ │ SlideTransition ← FractionalTranslation ← SlideTransition ← flutter: │ │ CupertinoPageTransition ← ⋯ flutter: │ │ engine layer: OffsetEngineLayer#a1676 flutter: │ │ handles: 2 flutter: │ │ offset: Offset(0.0, 0.0) flutter: │ │ flutter: │ └─child 1: PictureLayer#aee55 flutter: │ handles: 1 flutter: │ paint bounds: Rect.fromLTRB(0.0, 0.0, 800.0, 600.0) flutter: │ picture: \_NativePicture#e732d flutter: │ raster cache hints: isComplex = false, willChange = false flutter: │ flutter: └─child 2: PictureLayer#b16e5 flutter: handles: 1 flutter: paint bounds: Rect.fromLTRB(0.0, 0.0, 800.0, 600.0) flutter: picture: \_NativePicture#eef0a flutter: raster cache hints: isComplex = false, willChange = false flutter: \`\`\` The \`RepaintBoundary\` widget creates: 1. A \`RenderRepaintBoundary\` RenderObject in the render tree as shown in the \*\*Example 5\*\* results. \`\`\`plaintext ╎ └─child: RenderRepaintBoundary#f8f28 ╎ │ needs compositing ╎ │ creator: RepaintBoundary ← \_FocusInheritedScope ← Semantics ← ╎ │ FocusScope ← PrimaryScrollController ← \_ActionsScope ← Actions ╎ │ ← Builder ← PageStorage ← Offstage ← \_ModalScopeStatus ← ╎ │ UnmanagedRestorationScope ← ⋯ ╎ │ parentData: (can use size) ╎ │ constraints: BoxConstraints(w=800.0, h=600.0) ╎ │ layer: OffsetLayer#e73b7 ╎ │ size: Size(800.0, 600.0) ╎ │ metrics: 66.7% useful (1 bad vs 2 good) ╎ │ diagnosis: insufficient data to draw conclusion (less than five ╎ │ repaints) \`\`\` 1. A new layer in the layer tree as shown in the \*\*Example 6\*\* results. \`\`\`plaintext ├─child 1: OffsetLayer#0f766 │ │ creator: RepaintBoundary ← \_FocusInheritedScope ← Semantics ← │ │ FocusScope ← PrimaryScrollController ← \_ActionsScope ← Actions │ │ ← Builder ← PageStorage ← Offstage ← \_ModalScopeStatus ← │ │ UnmanagedRestorationScope ← ⋯ │ │ engine layer: OffsetEngineLayer#1768d │ │ handles: 2 │ │ offset: Offset(0.0, 0.0) \`\`\` This reduces how much needs to be repainted. ### Print the focus tree To debug a focus or shortcut issue, dump the focus tree using the \[\`debugDumpFocusTree()\`]\[] function. The \`debugDumpFocusTree()\` method returns the focus tree for the app. The focus tree labels nodes in the following way: \* The focused node is labeled \`PRIMARY FOCUS\`. \* Ancestors of the focus nodes are labeled \`IN FOCUS PATH\`. If your app uses the \[\`Focus\`]\[] widget, use the \[\`debugLabel\`]\[] property to simplify finding its focus node in the tree. You can also use the \[\`debugFocusChanges\`]\[] boolean property to enable extensive logging when the focus changes. #### Example 7: Call \`debugDumpFocusTree()\`

<!--?code-excerpt "lib/dump_focus_tree.dart"?-->

\`\`\`dart import 'package:material\_ui/material\_ui.dart'; void main() { runApp(const MaterialApp(home: AppHome())); } class AppHome extends StatelessWidget { const AppHome({super.key}); @override Widget build(BuildContext context) { return Material( child: Center( child: TextButton( onPressed: () { debugDumpFocusTree(); }, child: const Text('Dump Focus Tree'), ), ), ); } } \`\`\`

**Expand to view the focus tree for Example 7**

\`\`\`plaintext flutter: FocusManager#9d096 flutter: │ primaryFocus: FocusScopeNode#926dc(\_ModalScopeState flutter: │ Focus Scope \[PRIMARY FOCUS]) flutter: │ primaryFocusCreator: FocusScope ← PrimaryScrollController ← flutter: │ \_ActionsScope ← Actions ← Builder ← PageStorage ← Offstage ← flutter: │ \_ModalScopeStatus ← UnmanagedRestorationScope ← flutter: │ RestorationScope ← AnimatedBuilder ← flutter: │ \_ModalScope-\[LabeledGlobalKey<\_ModalScopeState>#bd53e] flutter: │ ← Semantics ← \_RenderTheaterMarker ← \_EffectiveTickerMode ← flutter: │ TickerMode ← flutter: │ \_OverlayEntryWidget-\[LabeledGlobalKey<\_OverlayEntryWidgetState>#89dd7] flutter: │ ← \_Theater ← Overlay-\[LabeledGlobalKey#52f82] ← flutter: │ UnmanagedRestorationScope ← ⋯ flutter: │ flutter: └─rootScope: FocusScopeNode#f4205(Root Focus Scope \[IN FOCUS PATH]) flutter: │ IN FOCUS PATH flutter: │ focusedChildren: FocusScopeNode#a0d10(Navigator Scope \[IN FOCUS flutter: │ PATH]) flutter: │ flutter: └─Child 1: FocusNode#088ec(\[IN FOCUS PATH]) flutter: │ context: Focus flutter: │ NOT FOCUSABLE flutter: │ IN FOCUS PATH flutter: │ flutter: └─Child 1: FocusNode#85f70(Shortcuts \[IN FOCUS PATH]) flutter: │ context: Focus flutter: │ NOT FOCUSABLE flutter: │ IN FOCUS PATH flutter: │ flutter: └─Child 1: FocusNode#f0c18(Shortcuts \[IN FOCUS PATH]) flutter: │ context: Focus flutter: │ NOT FOCUSABLE flutter: │ IN FOCUS PATH flutter: │ flutter: └─Child 1: FocusNode#0749f(Shortcuts \[IN FOCUS PATH]) flutter: │ context: Focus flutter: │ NOT FOCUSABLE flutter: │ IN FOCUS PATH flutter: │ flutter: └─Child 1: \_FocusTraversalGroupNode#28990(FocusTraversalGroup \[IN FOCUS PATH]) flutter: │ context: Focus flutter: │ NOT FOCUSABLE flutter: │ IN FOCUS PATH flutter: │ flutter: └─Child 1: FocusNode#5b515(Shortcuts \[IN FOCUS PATH]) flutter: │ context: Focus flutter: │ NOT FOCUSABLE flutter: │ IN FOCUS PATH flutter: │ flutter: └─Child 1: FocusScopeNode#a0d10(Navigator Scope \[IN FOCUS PATH]) flutter: │ context: FocusScope flutter: │ IN FOCUS PATH flutter: │ focusedChildren: FocusScopeNode#926dc(\_ModalScopeState flutter: │ Focus Scope \[PRIMARY FOCUS]) flutter: │ flutter: └─Child 1: \_FocusTraversalGroupNode#72c8a(FocusTraversalGroup \[IN FOCUS PATH]) flutter: │ context: Focus flutter: │ NOT FOCUSABLE flutter: │ IN FOCUS PATH flutter: │ flutter: └─Child 1: FocusNode#eb709(Navigator \[IN FOCUS PATH]) flutter: │ context: Focus flutter: │ IN FOCUS PATH flutter: │ flutter: └─Child 1: FocusScopeNode#926dc(\_ModalScopeState Focus Scope \[PRIMARY FOCUS]) flutter: │ context: FocusScope flutter: │ PRIMARY FOCUS flutter: │ flutter: └─Child 1: FocusNode#a6b74 flutter: context: Focus flutter: \`\`\` ### Print the semantics tree The \`debugDumpSemanticsTree()\` function prints the semantic tree for the app. The Semantics tree is presented to the system accessibility APIs. To obtain a dump of the Semantics tree: 1. Enable accessibility using a system accessibility tool or the \`SemanticsDebugger\` 1. Use the \[\`debugDumpSemanticsTree()\`]\[] function. #### Example 8: Call \`debugDumpSemanticsTree()\`

<!--?code-excerpt "lib/dump_semantic_tree.dart"?-->

\`\`\`dart import 'package:flutter/foundation.dart'; import 'package:flutter/rendering.dart'; import 'package:material\_ui/material\_ui.dart'; void main() { runApp(const MaterialApp(home: AppHome())); } class AppHome extends StatelessWidget { const AppHome({super.key}); @override Widget build(BuildContext context) { return Material( child: Center( child: Semantics( button: true, enabled: true, label: 'Clickable text here!', child: GestureDetector( onTap: () { debugDumpSemanticsTree(); if (kDebugMode) { print('Clicked!'); } }, child: const Text('Click Me!', style: TextStyle(fontSize: 56)), ), ), ), ); } } \`\`\`

**Expand to view the semantic tree for Example 8**

\`\`\`plaintext flutter: SemanticsNode#0 flutter: │ Rect.fromLTRB(0.0, 0.0, 800.0, 600.0) flutter: │ flutter: └─SemanticsNode#1 flutter: │ Rect.fromLTRB(0.0, 0.0, 800.0, 600.0) flutter: │ textDirection: ltr flutter: │ flutter: └─SemanticsNode#2 flutter: │ Rect.fromLTRB(0.0, 0.0, 800.0, 600.0) flutter: │ sortKey: OrdinalSortKey#824a2(order: 0.0) flutter: │ flutter: └─SemanticsNode#3 flutter: │ Rect.fromLTRB(0.0, 0.0, 800.0, 600.0) flutter: │ flags: scopesRoute flutter: │ flutter: └─SemanticsNode#4 flutter: Rect.fromLTRB(278.0, 267.0, 522.0, 333.0) flutter: actions: tap flutter: flags: isButton, hasEnabledState, isEnabled flutter: label: flutter: "Clickable text here! flutter: Click Me!" flutter: textDirection: ltr flutter: flutter: Clicked! \`\`\` ### Print event timings If you want to find out where your events happen relative to the frame's begin and end, you can set prints to log these events. To print the beginning and end of the frames to the console, toggle the \[\`debugPrintBeginFrameBanner\`]\[] and the \[\`debugPrintEndFrameBanner\`]\[]. \*\*The print frame banner log for Example 1\*\* \`\`\`plaintext I/flutter : ▄▄▄▄▄▄▄▄ Frame 12 30s 437.086ms ▄▄▄▄▄▄▄▄ I/flutter : Debug print: Am I performing this work more than once per frame? I/flutter : Debug print: Am I performing this work more than once per frame? I/flutter : ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ \`\`\` To print the call stack causing the current frame to be scheduled, use the \[\`debugPrintScheduleFrameStacks\`]\[] flag. ## Debug layout issues To debug a layout problem using a GUI, set \[\`debugPaintSizeEnabled\`]\[] to \`true\`. This flag can be found in the \`rendering\` library. You can enable it at any time and affects all painting while \`true\`. Consider adding it to the top of your \`void main()\` entry point. #### Example 9 See an example in the following code:

<!--?code-excerpt "lib/debug_flags.dart (debug-paint-size-enabled)"?-->

\`\`\`dart // Add import to the Flutter rendering library. import 'package:flutter/rendering.dart'; import 'package:material\_ui/material\_ui.dart'; void main() { debugPaintSizeEnabled = true; runApp(const MyApp()); } \`\`\` When enabled, Flutter displays the following changes to your app: \* Displays all boxes in a bright teal border. \* Displays all padding as a box with a faded blue fill and blue border around the child widget. \* Displays all alignment positioning with yellow arrows. \* Displays all spacers in gray, when they have no child. The \[\`debugPaintBaselinesEnabled\`]\[] flag does something similar but for objects with baselines. The app displays the baseline for alphabetic characters in bright green and the baseline for ideographic characters in orange. Alphabetic characters "sit" on the alphabetic baseline, but that baseline "cuts" through the bottom of \[CJK characters]\[cjk]. Flutter positions the ideographic baseline at the very bottom of the text line. The \[\`debugPaintPointersEnabled\`]\[] flag turns on a special mode that highlights any objects that you tap in teal. This can help you determine if an object fails to hit test. This might happen if the object falls outside the bounds of its parent and thus not considered for hit testing in the first place. If you're trying to debug compositor layers, consider using the following flags. \* Use the \[\`debugPaintLayerBordersEnabled\`]\[] flag to find the boundaries of each layer. This flag results in outlining each layer's bounds in orange. \* Use the \[\`debugRepaintRainbowEnabled\`]\[] flag to display a repainted layer. Whenever a layer repaints, it overlays with a rotating set of colors. Any function or method in the Flutter framework that starts with \`debug...\` only works in \[debug mode]\[]. \[cjk]: https\://en.wikipedia.org/wiki/CJK\_characters ## Debug animation issues :::note To debug animations with the least effort, slow them down. To slow down the animation, click \*\*Slow Animations\*\* in DevTools' \[Inspector view]\[]. This reduces the animation to 20% speed. If you want more control over the amount of slowness, use the following instructions. ::: Set the \[\`timeDilation\`]\[] variable (from the \`scheduler\` library) to a number greater than 1.0, for instance, 50.0. It's best to only set this once on app startup. If you change it on the fly, especially if you reduce it while animations are running, it's possible that the framework will observe time going backwards, which will probably result in asserts and generally interfere with your efforts. ## Debug performance issues :::note You can achieve similar results to some of these debug flags using \[DevTools]\[]. Some of the debug flags provide little benefit. If you find a flag with functionality you would like to add to \[DevTools]\[], \[file an issue]\[]. ::: Flutter provides a wide variety of top-level properties and functions to help you debug your app at various points along the development cycle. To use these features, compile your app in debug mode. The following list highlights some flags and one function from the \[rendering library]\[] for debugging performance issues. \[\`debugDumpRenderTree()\`]\[] : To dump the rendering tree to the console, call this function when not in a layout or repaint phase. To set these flags either: \* Edit the framework code. \* Import the module, set the value in your \`main()\` function, then hot restart. \[\`debugPaintLayerBordersEnabled\`]\[] : To display the boundaries of each layer, set this property to \`true\`. When set, each layer paints a box around its boundary. \[\`debugRepaintRainbowEnabled\`]\[] : To display a colored border around each widget, set this property to \`true\`. These borders change color as the app user scrolls in the app. To set this flag, add \`debugRepaintRainbowEnabled = true;\` as a top-level property in your app. If any static widgets rotate through colors after setting this flag, consider adding repaint boundaries to those areas. \[\`debugPrintMarkNeedsLayoutStacks\`]\[] : To determine if your app creates more layouts than expected, set this property to \`true\`. This layout issue could happen on the timeline, on a profile, or from a \`print\` statement inside a layout method. When set, the framework outputs stack traces to the console to explain why your app marks each render object to be laid out. \[\`debugPrintMarkNeedsPaintStacks\`]\[] : To determine if your app paints more layouts than expected, set this property to \`true\`. You can generate stack traces on demand as well. To print your own stack traces, add the \`debugPrintStack()\` function to your app. ### Trace Dart code performance :::note You can use the DevTools \[Timeline events tab]\[] to perform traces. You can also import and export trace files into the Timeline view, but only files generated by DevTools. ::: To perform custom performance traces and measure wall or CPU time of arbitrary segments of Dart code, use \`dart:developer\` \[Timeline]\[] utilities. 1. Open your source code. 1. Wrap the code you want to measure in \`Timeline\` methods.

<!--?code-excerpt "lib/perf_trace.dart"?-->

\`\`\`dart import 'dart:developer'; void main() { Timeline.startSync('interesting function'); // iWonderHowLongThisTakes(); Timeline.finishSync(); } \`\`\` 1. While connected to your app, open DevTools' \[Timeline events tab]\[]. 1. Select the \*\*Dart\*\* recording option in the \*\*Performance settings\*\*. 1. Perform the function you want to measure. To ensure that the runtime performance characteristics closely match that of your final product, run your app in \[profile mode]\[]. ### Add performance overlay :::note You can toggle display of the performance overlay on your app using the \*\*Performance Overlay\*\* button in the \[Flutter inspector]\[]. If you prefer to do it in code, use the following instructions. ::: To enable the \`PerformanceOverlay\` widget in your code, set the \`showPerformanceOverlay\` property to \`true\` on the \[\`MaterialApp\`]\[], \[\`CupertinoApp\`]\[], or \[\`WidgetsApp\`]\[] constructor: #### Example 10

<!--?code-excerpt "lib/performance_overlay.dart (show-overlay)"?-->

\`\`\`dart import 'package:material\_ui/material\_ui.dart'; class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( showPerformanceOverlay: true, title: 'My Awesome App', theme: ThemeData( colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), ), home: const MyHomePage(title: 'My Awesome App'), ); } } \`\`\` (If you're not using \`MaterialApp\`, \`CupertinoApp\`, or \`WidgetsApp\`, you can get the same effect by wrapping your application in a stack and putting a widget on your stack that was created by calling \[\`PerformanceOverlay.allEnabled()\`]\[].) To learn how to interpret the graphs in the overlay, check out \[The performance overlay]\[] in \[Profiling Flutter performance]\[]. ## Add widget alignment grid To add an overlay to a \[Material Design baseline grid]\[] on your app to help verify alignments, add the \`debugShowMaterialGrid\` argument in the \[\`MaterialApp\` constructor]\[]. To add an overlay to non-Material applications, add a \[\`GridPaper\`]\[] widget. \[\`\_InkFeatures\`]: https\://pub.dev/documentation/material\_ui/latest/material\_ui/InkFeature-class.html \[\`BoxConstraints\`]: https\://api.flutter.dev/flutter/rendering/BoxConstraints-class.html \[\`Center\`]: https\://api.flutter.dev/flutter/widgets/Center-class.html \[\`CupertinoApp\`]: https\://pub.dev/documentation/cupertino\_ui/latest/cupertino\_ui/CupertinoApp-class.html \[\`debugDumpApp()\`]: https\://api.flutter.dev/flutter/widgets/debugDumpApp.html \[\`debugDumpFocusTree()\`]: https\://api.flutter.dev/flutter/widgets/debugDumpFocusTree.html \[\`debugDumpLayerTree()\`]: https\://api.flutter.dev/flutter/rendering/debugDumpLayerTree.html \[\`debugDumpRenderTree()\`]: https\://api.flutter.dev/flutter/rendering/debugDumpRenderTree.html \[\`debugDumpSemanticsTree()\`]: https\://api.flutter.dev/flutter/rendering/debugDumpSemanticsTree.html \[\`debugFocusChanges\`]: https\://api.flutter.dev/flutter/widgets/debugFocusChanges.html \[\`debugLabel\`]: https\://api.flutter.dev/flutter/widgets/Focus/debugLabel.html \[\`debugPaintBaselinesEnabled\`]: https\://api.flutter.dev/flutter/rendering/debugPaintBaselinesEnabled.html \[\`debugPaintLayerBordersEnabled\`]: https\://api.flutter.dev/flutter/rendering/debugPaintLayerBordersEnabled.html \[\`debugPaintPointersEnabled\`]: https\://api.flutter.dev/flutter/rendering/debugPaintPointersEnabled.html \[\`debugPaintSizeEnabled\`]: https\://api.flutter.dev/flutter/rendering/debugPaintSizeEnabled.html \[\`debugPrint()\`]: https\://api.flutter.dev/flutter/widgets/debugPrint.html \[\`debugPrintBeginFrameBanner\`]: https\://api.flutter.dev/flutter/scheduler/debugPrintBeginFrameBanner.html \[\`debugPrintEndFrameBanner\`]: https\://api.flutter.dev/flutter/scheduler/debugPrintEndFrameBanner.html \[\`debugPrintMarkNeedsLayoutStacks\`]: https\://api.flutter.dev/flutter/rendering/debugPrintMarkNeedsLayoutStacks.html \[\`debugPrintMarkNeedsPaintStacks\`]: https\://api.flutter.dev/flutter/rendering/debugPrintMarkNeedsPaintStacks.html \[\`debugPrintScheduleFrameStacks\`]: https\://api.flutter.dev/flutter/scheduler/debugPrintScheduleFrameStacks.html \[\`debugRepaintRainbowEnabled\`]: https\://api.flutter.dev/flutter/rendering/debugRepaintRainbowEnabled.html \[\`Focus\`]: https\://api.flutter.dev/flutter/widgets/Focus-class.html \[\`GridPaper\`]: https\://api.flutter.dev/flutter/widgets/GridPaper-class.html \[\`log()\`]: https\://api.flutter.dev/flutter/dart-developer/log.html \[\`Material\`]: https\://pub.dev/documentation/material\_ui/latest/material\_ui/Material-class.html \[\`MaterialApp\` constructor]: https\://pub.dev/documentation/material\_ui/latest/material\_ui/MaterialApp/MaterialApp.html \[\`MaterialApp\`]: https\://pub.dev/documentation/material\_ui/latest/material\_ui/MaterialApp/MaterialApp.html \[\`PerformanceOverlay.allEnabled()\`]: https\://api.flutter.dev/flutter/widgets/PerformanceOverlay/PerformanceOverlay.allEnabled.html \[\`print()\`]: https\://api.flutter.dev/flutter/dart-core/print.html \[\`RenderParagraph\`]: https\://api.flutter.dev/flutter/rendering/RenderParagraph-class.html \[\`RenderPositionedBox\`]: https\://api.flutter.dev/flutter/rendering/RenderPositionedBox-class.html \[\`setState()\`]: https\://api.flutter.dev/flutter/widgets/State/setState.html \[\`stderr.method\_to\_invoke()\`]: https\://api.flutter.dev/flutter/dart-io/stderr.html \[\`TextButton\`]: https\://pub.dev/documentation/material\_ui/latest/material\_ui/TextButton-class.html \[\`timeDilation\`]: https\://api.flutter.dev/flutter/scheduler/timeDilation.html \[\`WidgetsApp\`]: https\://api.flutter.dev/flutter/widgets/WidgetsApp-class.html \[debug mode]: /testing/build-modes#debug \[Debugger]: /tools/devtools/debugger \[Debugging]: /testing/debugging \[DevTools]: /tools/devtools \[DiagnosticsProperty]: https\://api.flutter.dev/flutter/foundation/DiagnosticsProperty-class.html \[file an issue]: https\://github.com/flutter/devtools/issues \[Flutter inspector]: /tools/devtools/inspector \[frame callback]: https\://api.flutter.dev/flutter/scheduler/SchedulerBinding/addPersistentFrameCallback.html \[Inspector view]: /tools/devtools/inspector \[Logging view]: /tools/devtools/logging \[Material Design baseline grid]: https\://m3.material.io/foundations/layout/understanding-layout/spacing \[profile mode]: /testing/build-modes#profile \[Profiling Flutter performance]: /perf/ui-performance \[render-fill]: https\://api.flutter.dev/flutter/rendering/Layer/debugFillProperties.html \[rendering library]: https\://api.flutter.dev/flutter/rendering/rendering-library.html \[The performance overlay]: /perf/ui-performance#the-performance-overlay \[Timeline events tab]: /tools/devtools/performance#timeline-events-tab \[Timeline]: https\://api.dart.dev/dart-developer/Timeline-class.html \[widget-fill]: https\://api.flutter.dev/flutter/widgets/Widget/debugFillProperties.html

----
url: https://docs.flutter.dev/platform-integration/web/faq
----

## Questions

[#](#questions)

### What scenarios are ideal for Flutter on the web?

[#](#what-scenarios-are-ideal-for-flutter-on-the-web)

Not every web page makes sense in Flutter, but we think Flutter is particularly suited for app-centric experiences:

* Progressive Web Apps
* Single Page Apps
* Existing Flutter mobile apps

At this time, Flutter is not suitable for static websites with text-rich flow-based content. For example, blog articles benefit from the document-centric model that the web is built around, rather than the app-centric services that a UI framework like Flutter can deliver. However, you *can* use Flutter to embed interactive experiences into these websites.

For more information on how you can use Flutter on the web, see [Web support for Flutter](/platform-integration/web).

### Search Engine Optimization (SEO)

[#](#search-engine-optimization-seo)

In general, Flutter is geared towards dynamic application experiences. Flutter's web support is no exception. Flutter web prioritizes performance, fidelity, and consistency. This means application output doesn't align with what search engines need to properly index.

However, a community-released Dart package, [Jaspr](https://jaspr.site/) *does* support static websites. In fact, the [Dart documentation](https://dart.dev), [Flutter documentation](/), and [Flutter marketing](https://flutter.dev) websites were migrated to using the Jaspr package.

To summarize, for web content that is static or document-like, we recommend *either* using:

1. [Jaspr](https://jaspr.site/), if you want to use Dart but want a more traditional DOM-based website. Also note that Jaspr makes SEO work in the same way a traditional website would.
2. HTML—in this case, consider separating your primary application experience (created in Flutter), from your landing page, marketing content, and help content (created using search engine optimized HTML).

### Does hot reload work with a web app?

[#](#does-hot-reload-work-with-a-web-app)

Yes! For more information, check out [hot reload on the web](/platform-integration/web/building#hot-reload-web).

### Which web browsers are supported by Flutter?

[#](#which-web-browsers-are-supported-by-flutter)

Flutter web apps can run on the following browsers:

* Chrome (mobile & desktop)
* Safari (mobile & desktop)
* Edge (mobile & desktop)
* Firefox (mobile & desktop)

During development, Chrome (on macOS, Windows, and Linux), and Edge (on Windows) are supported as the default browsers for debugging your app.

### Can I build, run, and deploy web apps in any of the IDEs?

[#](#can-i-build-run-and-deploy-web-apps-in-any-of-the-ides)

You can select **Chrome** or **Edge** as the target device in Android Studio/IntelliJ and VS Code.

The device pulldown should now include the **Chrome (web)** option for all channels.

### How do I build a responsive app for the web?

[#](#how-do-i-build-a-responsive-app-for-the-web)

See [Creating responsive apps](/ui/adaptive-responsive).

### Can I use `dart:io` with a web app?

[#](#can-i-use-dart-io-with-a-web-app)

No. The file system is not accessible from the browser. For network functionality, use the [`http`](https://pub.dev/packages/http) package. Note that security works somewhat differently because the browser (and not the app) controls the headers on an HTTP request.

### How do I handle web-specific imports?

[#](#how-do-i-handle-web-specific-imports)

Some plugins require platform-specific imports, particularly if they use the file system, which is not accessible from the browser. To use these plugins in your app, use conditional imports.

When compiling for WebAssembly, check for `dart.library.js_interop` (rather than `dart.library.js` or `dart.library.html`, which are not supported on Wasm):

dart

```
import 'fallback.dart'
  if (dart.library.js) 'legacy_web_interop.dart'
  if (dart.library.js_interop) 'wasm_web_interop.dart';
```

For more details, see the [documentation for conditional imports](https://dart.dev/interop/js-interop/package-web#conditional-imports) on [dart.dev](https://dart.dev). For help dealing with Wasm compilation failures due to unsupported imports, check out [Diagnosing Wasm compilation errors](/platform-integration/web/wasm#diagnosing-wasm-compilation-errors).

### Does Flutter web support concurrency?

[#](#does-flutter-web-support-concurrency)

Dart's concurrency support that uses [isolates](https://dart.dev/guides/language/concurrency) is not currently supported in Flutter web.

Flutter web apps can potentially work around this by using [web workers](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers), although such support isn't built in.

### How do I deploy a web app?

[#](#how-do-i-deploy-a-web-app)

See [Preparing a web app for release](/deployment/web).

### Does `Platform.is` work on the web?

[#](#does-platform-is-work-on-the-web)

No. While you can technically import `dart:io` when compiling for the web, calling any `Platform.isXYZ` method throws an `UnsupportedError`. Furthermore, importing `dart:io` in a package (except through conditional imports) causes pub.dev to score the package as not supporting the web.

* If you are developing a Flutter app, consider using [`kIsWeb`](https://api.flutter.dev/flutter/foundation/kIsWeb-constant.html).
* If you are developing a package (especially one without a Flutter dependency), consider using the [`os_detect`](https://pub.dev/packages/os_detect) package.

### Why doesn't my app update immediately after it's deployed?

[#](#why-doesnt-my-app-update-immediately-after-its-deployed)

You might need to configure the `Cache-Control` header returned by your web server. For example, if this header is set to 3600, then the browser and CDN will cache the asset for 1 hour, and your users might see an out-of-date version of your app up to 1 hour after you deploy a new version. For more information about caching on the web, check out [Prevent unnecessary network requests with the HTTP Cache](https://web.dev/articles/http-cache).

It's a good idea to be aware of this behavior to avoid an undesirable user experience. After you deploy your app, users might use a cached version of your app (cached by the browser or CDN) for the duration defined by your cache headers. This can lead to using a version of your app that is incompatible with changes that have been deployed to backend services.

### How do I clear the web cache after a deployment and force an app download?

[#](#how-do-i-clear-the-web-cache-after-a-deployment-and-force-an-app-download)

If you wish to defeat these cache headers after each deployment, a common technique is to append a build ID of some sort to the links of your static resources, or update the filenames themselves. For example, `logo.png` might become `logo.v123.png`.

html

```
<!-- Option 1, append build ID as a query parameter in your links -->
<script src="flutter_bootstrap.js?v=123" async></script>
​
<!-- Option 2, update the filename and update your links -->
<script src="flutter_bootstrap.v123.js" async></script>
```

Flutter doesn't currently support appending build IDs to resources automatically.

### How do I configure my cache headers?

[#](#how-do-i-configure-my-cache-headers)

If you are using Firebase Hosting, the shared cache (CDN) is invalidated when you deploy a new version of your app. However, by default Firebase Hosting sets `Cache-Control: max-age=3600` on static files, meaning browsers can serve stale `index.html`, `flutter_bootstrap.js`, or manifest files for up to an hour after a deploy.

To make sure that browsers revalidate your app's files on each load (receiving fast `304 Not Modified` responses from the CDN edge when unchanged) and don't cache missing assets (`404.html`), configure your `firebase.json` file as follows:

json

```
{
  "hosting": {
    "public": "build/web",
    "headers": [
      {
        "source": "**",
        "headers": [
          {
            "key": "Cache-Control",
            "value": "max-age=0, must-revalidate"
          }
        ]
      },
      {
        "source": "404.html",
        "headers": [
          {
            "key": "Cache-Control",
            "value": "max-age=0, must-revalidate"
          }
        ]
      }
    ],
    "rewrites": [
      {
        "source": "!(/assets/**|/canvaskit/**|/icons/**|/main.dart.*)",
        "destination": "/index.html"
      }
    ]
  }
}
```

The `/index.html` rewrite exclusion (`!(/assets/**|/canvaskit/**|/icons/**|/main.dart.*)`) ensures that requests for missing assets or compiled entry points return a real `404` status rather than `index.html` with a `200 OK` status, and the `404.html` rule prevents the CDN from caching 404 responses under wildcard TTLs.

### How do I configure a service worker?

[#](#how-do-i-configure-a-service-worker)

Flutter no longer generates or manages a caching service worker by default (in newer versions, it writes a self-cleaning stub to remove legacy service workers).

If your application requires offline support or advanced caching, you need to configure a service worker yourself using standard web tooling or third-party solutions such as [Workbox](https://github.com/GoogleChrome/workbox).

For more information on building custom service workers, check out [Using service workers](https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API/Using_Service_Workers).

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-09-21. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/platform-integration/web/faq.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/platform-integration/web/faq\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/platform-integration/web/faq.md "Report an issue with this page").

----
url: https://docs.flutter.dev/deployment/ios
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Build and release an iOS app](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

This guide provides a step-by-step walkthrough of releasing a Flutter app to the [App Store](https://developer.apple.com/app-store/submissions/) and [TestFlight](https://developer.apple.com/testflight/).

## Preliminaries

[#](#preliminaries)

Xcode is required to build and release your app. You must use a device running macOS to follow this guide.

Before beginning the process of releasing your app, ensure that it meets Apple's [App Review Guidelines](https://developer.apple.com/app-store/review/).

To publish your app to the App Store, you must first enroll in the [Apple Developer Program](https://developer.apple.com/programs/). You can read more about the various membership options in Apple's [Choosing a Membership](https://developer.apple.com/support/compare-memberships/) guide.

## Video overview

[#](#video-overview)

For those who prefer video over text, the following video covers the same material as this guide.

[Watch on YouTube in a new tab: "Release an iOS app built with Flutter in 7 steps"](https://www.youtube.com/watch/iE2bpP56QKc)

## Register your app on App Store Connect

[#](#register-your-app-on-app-store-connect)

Manage your app's life cycle on [App Store Connect](https://developer.apple.com/support/app-store-connect/) (formerly iTunes Connect). You define your app name and description, add screenshots, set pricing, and manage releases to the App Store and TestFlight.

Registering your app involves two steps: registering a unique Bundle ID, and creating an application record on App Store Connect.

For a detailed overview of App Store Connect, see the [App Store Connect](https://developer.apple.com/support/app-store-connect/) guide.

### Register a Bundle ID

[#](#register-a-bundle-id)

Every iOS application is associated with a Bundle ID, a unique identifier registered with Apple. To register a Bundle ID for your app, follow these steps:

1. Open the [App IDs](https://developer.apple.com/account/ios/identifier/bundle) page of your developer account.

2. On the App Store Connect landing page, click **Apps**.
3. Click **+** in the top-left corner, then select **New App**.
4. Fill in your app details in the form that appears. In the Platforms section, ensure that iOS is checked. Since Flutter does not currently support tvOS, leave that checkbox unchecked. Click **Create**.

1. Open the default Xcode workspace in your project by running `open ios/Runner.xcworkspace` in a terminal window from your Flutter project directory.
2. To view your app's settings, select the **Runner** target in the Xcode navigator.

Verify the most important settings.

In the **Identity** section of the **General** tab:

* `Display Name`

  The display name of your app.

* `Bundle Identifier`

  The App ID you registered on App Store Connect.

In the **Signing & Capabilities** tab:

* `Automatically manage signing`

  Whether Xcode should automatically manage app signing and provisioning. This is set `true` by default, which should be sufficient for most apps. For more complex scenarios, see the [Code Signing Guide](https://developer.apple.com/library/content/documentation/Security/Conceptual/CodeSigningGuide/Introduction/Introduction.html).

* `Team`

  Select the team associated with your registered Apple Developer account. If required, select **Add Account...**, then update this setting.

In the **Deployment** section of the **Build Settings** tab:

* `iOS Deployment Target`

  The minimum iOS version that your app supports. Flutter supports iOS 13 and later. If your app or plugins include Objective-C or Swift code that makes use of APIs newer than iOS 13, update this setting to the highest required version.

The **General** tab of your project settings should resemble the following:

For a detailed overview of app signing, see [Create, export, and delete signing certificates](https://help.apple.com/xcode/mac/current/#/dev154b28f09).

## Add an app icon

[#](#add-an-app-icon)

When a new Flutter app is created, a placeholder icon set is created. This step covers replacing these placeholder icons with your app's icons:

1. Review the [iOS App Icon](https://developer.apple.com/design/human-interface-guidelines/app-icons/) guidelines and, in particular, the advice on [creating light, dark, and tinted](https://developer.apple.com/design/human-interface-guidelines/app-icons#iOS-iPadOS) icons for your app.
2. In the Xcode project navigator, select `Assets.xcassets` in the `Runner` folder. Update the placeholder icons with your own app icons.
3. Verify the icon has been replaced by running your app using `flutter run`.

## Add a launch image

[#](#add-a-launch-image)

Similar to the app icon, you can also replace the placeholder launch image:

1. In the Xcode project navigator, select `Assets.xcassets` in the `Runner` folder. Update the placeholder launch image with your own launch image.
2. Verify the new launch image by hot restarting your app. (Don't use `hot reload`.)

## Create a build archive and upload to App Store Connect

[#](#create-a-build-archive-and-upload-to-app-store-connect)

During development, you've been building, debugging, and testing with *debug* builds. When you're ready to ship your app to users on the App Store or TestFlight, you need to prepare a *release* build.

### Update the app's build and version numbers

[#](#update-the-apps-build-and-version-numbers)

The default version number of the app is `1.0.0`. To update it, navigate to the `pubspec.yaml` file and update the following line:

yaml

```
version: 1.0.0+1
```

The version number is three numbers separated by dots, such as `1.0.0` in the example above, followed by an optional build number such as `1` in the example above, separated by a `+`.

Both the version and the build number can be overridden in `flutter build ipa` by specifying `--build-name` and `--build-number`, respectively.

In iOS, `build-name` uses `CFBundleShortVersionString` while `build-number` uses `CFBundleVersion`. Read more about iOS versioning at [Core Foundation Keys](https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html) on the Apple Developer's site.

You can also override the `pubspec.yaml` build name and number in Xcode:

1. Open `Runner.xcworkspace` in your app's `ios` folder.
2. Select **Runner** in the Xcode project navigator, then select the **Runner** target in the settings view sidebar.
3. In the Identity section, update the **Version** to the user-facing version number you wish to publish.
4. In the Identity section, update the **Build** identifier to a unique build number used to track this build on App Store Connect. Each upload requires a unique build number.

### Create an app bundle

[#](#create-an-app-bundle)

Run `flutter build ipa` to produce an Xcode build archive (`.xcarchive` file) in your project's `build/ios/archive/` directory and an App Store app bundle (`.ipa` file) in `build/ios/ipa`.

Consider adding the `--obfuscate` and `--split-debug-info` flags to [obfuscate your Dart code](/deployment/obfuscate) to make it more difficult to reverse engineer.

If you are not distributing to the App Store, you can optionally choose a different [export method](https://help.apple.com/xcode/mac/current/#/dev31de635e5) by adding the option `--export-method ad-hoc`, `--export-method development` or `--export-method enterprise`.

### Upload the app bundle to App Store Connect

[#](#upload-the-app-bundle-to-app-store-connect)

Once the app bundle is created, upload it to [App Store Connect](https://appstoreconnect.apple.com/) by either:

1. Install and open the [Apple Transport macOS app](https://apps.apple.com/us/app/transporter/id1450874784). Drag and drop the `build/ios/ipa/*.ipa` app bundle into the app.

2. Or upload the app bundle from the command line by running:

   bash

   ```
   xcrun altool --upload-app --type ios -f build/ios/ipa/*.ipa --apiKey your_api_key --apiIssuer your_issuer_id
   ```

   Run `man altool` for details about how to authenticate with the App Store Connect API key.

3. Or open `build/ios/archive/MyApp.xcarchive` in Xcode.

   Click the **Validate App** button. If any issues are reported, address them and produce another build. You can reuse the same build ID until you upload an archive.

   After the archive has been successfully validated, click **Distribute App**.

You can follow the status of your build in the Activities tab of your app's details page on [App Store Connect](https://appstoreconnect.apple.com/). You should receive an email within 30 minutes notifying you that your build has been validated and is available to release to testers on TestFlight. At this point you can choose whether to release on TestFlight, or go ahead and release your app to the App Store.

For more details, see [Upload an app to App Store Connect](https://help.apple.com/xcode/mac/current/#/dev442d7f2ca).

## Create a build archive with Codemagic CLI tools

[#](#create-a-build-archive-with-codemagic-cli-tools)

This step covers creating a build archive and uploading your build to App Store Connect using Flutter build commands and [Codemagic CLI Tools](https://github.com/codemagic-ci-cd/cli-tools) executed in a terminal in the Flutter project directory. This allows you to create a build archive with full control of distribution certificates in a temporary keychain isolated from your login keychain.

1. Install the Codemagic CLI tools:

   bash

   ```
   pip3 install codemagic-cli-tools
   ```

2. You'll need to generate an [App Store Connect API Key](https://appstoreconnect.apple.com/access/api) with App Manager access to automate operations with App Store Connect. To make subsequent commands more concise, set the following environment variables from the new key: issuer id, key id, and API key file.

   bash

   ```
   export APP_STORE_CONNECT_ISSUER_ID=aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee
   export APP_STORE_CONNECT_KEY_IDENTIFIER=ABC1234567
   export APP_STORE_CONNECT_PRIVATE_KEY=`cat /path/to/api/key/AuthKey_XXXYYYZZZ.p8`
   ```

3. You need to export or create an iOS Distribution certificate to code sign and package a build archive.

   If you have existing [certificates](https://developer.apple.com/account/resources/certificates), you can export the private keys by executing the following command for each certificate:

   bash

   ```
   openssl pkcs12 -in <certificate_name>.p12 -nodes -nocerts | openssl rsa -out cert_key
   ```

   Or you can create a new private key by executing the following command:

   bash

   ```
   ssh-keygen -t rsa -b 2048 -m PEM -f cert_key -q -N ""
   ```

   Later, you can have CLI tools automatically create a new iOS Distribution from the private key.

4. Set up a new temporary keychain to be used for code signing:

   bash

   ```
   keychain initialize
   ```

5. Fetch the code signing files from App Store Connect:

   bash

   ```
   app-store-connect fetch-signing-files $(xcode-project detect-bundle-id) \
       --platform IOS \
       --type IOS_APP_STORE \
       --certificate-key=@file:/path/to/cert_key \
       --create
   ```

   Where `cert_key` is either your exported iOS Distribution certificate private key or a new private key which automatically generates a new certificate. The certificate will be created from the private key if it doesn't exist in App Store Connect.

6. Now add the fetched certificates to your keychain:

   bash

   ```
   keychain add-certificates
   ```

7. Update the Xcode project settings to use fetched code signing profiles:

   bash

   ```
   xcode-project use-profiles
   ```

8. Install Flutter dependencies:

   bash

   ```
   flutter packages pub get
   ```

9. Install CocoaPods dependencies:

   bash

   ```
   find . -name "Podfile" -execdir pod install \;
   ```

10. Build the Flutter the iOS project:

    bash

    ```
    flutter build ipa --release \
        --export-options-plist=$HOME/export_options.plist
    ```

    Note that `export_options.plist` is the output of the `xcode-project use-profiles` command.

11. Publish the app to App Store Connect:

    bash

    ```
    app-store-connect publish \
        --path $(find $(pwd) -name "*.ipa")
    ```

12. As mentioned earlier, don't forget to set your login keychain as the default to avoid authentication issues with apps on your machine:

    bash

    ```
    keychain use-login
    ```

You should receive an email within 30 minutes notifying you that your build has been validated and is available to release to testers on TestFlight. At this point you can choose whether to release on TestFlight, or go ahead and release your app to the App Store.

For more details, see [Distribute an app using TestFlight](https://help.apple.com/xcode/mac/current/#/dev2539d985f).

The [Distribute your app](https://help.apple.com/xcode/mac/current/#/devac02c5ab8) guide provides a detailed overview of the process of releasing an app to the App Store.

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-07-31. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/deployment/ios.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/deployment/ios\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/deployment/ios.md "Report an issue with this page").

----
url: https://docs.flutter.dev/cookbook/audio/record
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Record or stream audio input](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

This recipe demonstrates how to use the [`record` package](https://pub.dev/packages/record) to add audio recording and streaming capabilities to your Flutter app. To get started using the package, follow these steps:

## 1. Add the package dependency

[#](#1-add-the-package-dependency)

To add `package:record` as a dependency, use `flutter pub add`:

```
$ flutter pub add record
```

## 2. Initialize an `AudioRecorder`

[#](#2-initialize-an-audiorecorder)

Initialize an `AudioRecorder` object. This is the primary object that controls the recording process.

dart

```
import 'package:record/record.dart';
​
final recorder = AudioRecorder();
```

## 3. Request user permission

[#](#3-request-user-permission)

Before recording, you need to request user permission. You might also need to add platform-specific permission configurations. Refer to the [`record` package](https://pub.dev/packages/record) documentation for details.

dart

```
final recorder = AudioRecorder();
if (await recorder.hasPermission()) {
  // Permission granted, proceed with recording.
} else {
  // Permission denied.
}
```

## 4. Create a recording configuration

[#](#4-create-a-recording-configuration)

Create and configure a `RecordConfig` object to specify the record settings, such as the encoder, sample rate, and channel number. You can also enable features like auto gain, echo cancellation, and noise suppression.

dart

```
final recordConfig = RecordConfig(
  encoder: AudioEncoder.pcm16bits,
  sampleRate: 24000,
  numChannels: 1,
  autoGain: true,
  echoCancel: true,
  noiseSuppress: true,
);
```

## 5. Start recording to a file

[#](#5-start-recording-to-a-file)

To start recording to a file, call the `start` method on the `AudioRecorder`, passing in the `recordConfig` you defined and the path where the file should be stored.

dart

```
// TODO: Specify the path where the audio file should be saved.
final audioFilePath = 'myRecording.wav';
await recorder.start(recordConfig, path: audioFilePath);
```

## 6. Control an ongoing recording

[#](#6-control-an-ongoing-recording)

You can control an ongoing recording using the `pause`, `resume`, and `stop` methods on the `AudioRecorder`.

dart

```
await recorder.pause();
await recorder.resume();
await recorder.stop();
```

## 7. \[Optional] Record to an audio stream

[#](#7-optional-record-to-an-audio-stream)

To stream audio, use the `startStream` method. This returns a [stream](https://api.flutter.dev/flutter/dart-async/Stream-class.html) of audio data.

dart

```
final stream = await recorder.startStream(recordConfig);
stream.listen((audioChunk) {
  // Process the audio data. For example, send it to a server.
});
```

## 8. Stop recording

[#](#8-stop-recording)

To stop the recording and get the path to the saved file, call the asynchronous `stop` method on the `AudioRecorder`.

dart

```
final path = await recorder.stop();
print('Recording stopped. File saved to: $path');
```

## 9. Dispose of the recorder

[#](#9-dispose-of-the-recorder)

When you are finished using the `AudioRecorder`, remember to call its `dispose` method to release its resources.

dart

```
await recorder.dispose();
```

## Supported formats and encodings

[#](#supported-formats-and-encodings)

The `record` package supports various encoders and file formats, but support varies by platform. For the full list of supported encoders per platform, check out the package's [encoding support table](https://pub.dev/packages/record#file).

For more detailed information and examples, visit the [`record` package](https://pub.dev/packages/record) page on pub.dev or check out the `record` Package of the Week video.

[Watch on YouTube in a new tab: "record - Package of the Week"](https://www.youtube.com/watch/Vv2A_nUL1tw)

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/audio/record.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/cookbook/audio/record\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/audio/record.md "Report an issue with this page").

----
url: https://docs.flutter.dev/cookbook/forms/text-input.md
----

\# Create and style a text field > How to implement a text field.

<!--?code-excerpt path-base="cookbook/forms/text_input/"?-->

Text fields allow users to type text into an app. They 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 Material Design text fields: \[\`TextField\`]\[] and \[\`TextFormField\`]\[]. ## \`TextField\` \[\`TextField\`]\[] is the most commonly used text input widget. 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\`]\[] as the \[\`decoration\`]\[] property of the \`TextField\`. To remove the decoration entirely (including the underline and the space reserved for the label), set the \`decoration\` to \`null\`.

<!--?code-excerpt "lib/main.dart (TextField)" replace="/^child\: //g"?-->

\`\`\`dart TextField( decoration: InputDecoration( border: OutlineInputBorder(), hintText: 'Enter a search term', ), ), \`\`\` To retrieve the value when it changes, consult the \[Handle changes to a text field]\[] recipe. ## \`TextFormField\` \[\`TextFormField\`]\[] wraps a \`TextField\` and integrates it with the enclosing \[\`Form\`]\[]. This provides additional functionality, such as validation and integration with other \[\`FormField\`]\[] widgets.

<!--?code-excerpt "lib/main.dart (TextFormField)" replace="/^child\: //g"?-->

\`\`\`dart TextFormField( decoration: const InputDecoration( border: UnderlineInputBorder(), labelText: 'Enter your username', ), ), \`\`\` ## Interactive example

<!--?code-excerpt "lib/main.dart"?-->

\`\`\`dartpad title="Flutter text input hands-on example in DartPad" run="true" import 'package:flutter/material.dart'; void main() => runApp(const MyApp()); class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { const appTitle = 'Form Styling Demo'; return MaterialApp( title: appTitle, home: Scaffold( appBar: AppBar(title: const Text(appTitle)), body: const MyCustomForm(), ), ); } } class MyCustomForm extends StatelessWidget { const MyCustomForm({super.key}); @override Widget build(BuildContext context) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: \[ const Padding( padding: EdgeInsets.symmetric(horizontal: 8, vertical: 16), child: TextField( decoration: InputDecoration( border: OutlineInputBorder(), hintText: 'Enter a search term', ), ), ), Padding( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 16), child: TextFormField( decoration: const InputDecoration( border: UnderlineInputBorder(), labelText: 'Enter your username', ), ), ), ], ); } } \`\`\` To learn more about input validation, consult the \[Build a form with validation]\[] recipe. \[Build a form with validation]: /cookbook/forms/validation \[\`decoration\`]: https\://pub.dev/documentation/material\_ui/latest/material\_ui/TextField/decoration.html \[\`Form\`]: https\://api.flutter.dev/flutter/widgets/Form-class.html \[\`FormField\`]: https\://api.flutter.dev/flutter/widgets/FormField-class.html \[Handle changes to a text field]: /cookbook/forms/text-field-changes \[\`InputDecoration\`]: https\://pub.dev/documentation/material\_ui/latest/material\_ui/InputDecoration-class.html \[\`TextField\`]: https\://pub.dev/documentation/material\_ui/latest/material\_ui/TextField-class.html \[\`TextFormField\`]: https\://pub.dev/documentation/material\_ui/latest/material\_ui/TextFormField-class.html

----
url: https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.19.0.md
----

\# DevTools 2.19.0 release notes > Release notes for Dart and Flutter DevTools version 2.19.0. The 2.19.0 release of the Dart and Flutter DevTools includes the following changes among other general improvements. To learn more about DevTools, check out the \[DevTools overview]\(https\://docs.flutter.dev/tools/devtools). ## Performance updates \* Added a button to toggle the visibility of the Flutter Frames chart - \[#4577]\(https\://github.com/flutter/devtools/pull/4577) !\[diff]\(/assets/images/docs/tools/devtools/release-notes/images-2.19.0/4577.png "Flutter Frames") \* Polish the debug mode warning to better describe which data is accurate in debug mode and which data may be misleading - \[#3537]\(https\://github.com/flutter/devtools/pull/3537) \* Reorder performance tool tabs and only show the CPU profiler for the "Timeline Events" tab - \[#4629]\(https\://github.com/flutter/devtools/pull/4629) ## Memory updates \* Improvements to the memory Profile tab - \[#4583]\(https\://github.com/flutter/devtools/pull/4583) ## Debugger updates \* Fix an issue with hover cards where they were appearing but never disappearing - \[#4627]\(https\://github.com/flutter/devtools/pull/4627) \* Fix a bug with the file search autocomplete dialog - \[#4409]\(https\://github.com/flutter/devtools/pull/4409) ## Network profiler updates \* Added a "Copy" button in the Network Request view (thanks to @netos23) - \[#4509]\(https\://github.com/flutter/devtools/pull/4509) ## Full commit history To find a complete list of changes since the previous release, check out \[the diff on GitHub]\(https\://github.com/flutter/devtools/compare/v2.18.0...v2.19.0).

----
url: https://docs.flutter.dev/reference/security-false-positives.md
----

\# Security false positives > Security vulnerabilities incorrectly reported by automated static analysis tools. ## Introduction We occasionally receive false reports of security vulnerabilities in Dart and Flutter applications, generated by tools that were built for other kinds of applications (for example, those written with Java or C++). This document provides information on reports that we believe are incorrect and explains why the concerns are misplaced. ## Common concerns ### Shared objects should use fortified functions > The shared object does not have any fortified functions. > Fortified functions provides buffer overflow checks against glibc's commons insecure functions like \`strcpy\`, \`gets\` etc. > Use the compiler option \`-D\_FORTIFY\_SOURCE=2\` to fortify functions. When this refers to compiled Dart code (such as the \`libapp.so\` file in Flutter applications), this advice is misguided because Dart code doesn't directly invoke libc functions; all Dart code goes through the Dart standard library. (In general, MobSF gets false positives here because it checks for any use of functions with a \`\_chk\` suffix, but since Dart doesn't use these functions at all, it doesn't have calls with or without the suffix, and therefore MobSF treats the code as containing non-fortified calls.) ### Shared objects should use RELRO > no RELRO found for \`libapp.so\` binaries Dart doesn't use the normal Procedure Linkage Table (PLT) or Global Offsets Table (GOT) mechanisms at all, so the Relocation Read-Only (RELRO) technique doesn't really make much sense for Dart. Dart's equivalent of GOT is the pool pointer, which unlike GOT, is located in a randomized location and is therefore much harder to exploit. In principle, you can create vulnerable code when using Dart FFI, but normal use of Dart FFI wouldn't be prone to these issues either, assuming it's used with C code that itself uses RELRO appropriately. ### Shared objects should use stack canary values > no canary are found for \`libapp.so\` binaries > This shared object does not have a stack canary value added to the stack. > Stack canaries are used to detect and prevent exploits from overwriting return address. > Use the option -fstack-protector-all to enable stack canaries. Dart doesn't generate stack canaries because, unlike C++, Dart doesn't have stack-allocated arrays (the primary source of stack smashing in C/C++). When writing pure Dart (without using \`dart:ffi\`), you already have much stronger isolation guarantees than any C++ mitigation can provide, simply because pure Dart code is a managed language where things like buffer overruns don't exist. In principle, you can create vulnerable code when using Dart FFI, but normal use of Dart FFI would not be prone to these issues either, assuming it's used with C code that itself uses stack canary values appropriately. ### Code should avoid using the \`\_sscanf\`, \`\_strlen\`, and \`\_fopen\` APIs > The binary may contain the following insecure API(s) \`\_sscanf\` , \`\_strlen\` , \`\_fopen\`. The tools that report these issues tend to be overly-simplistic in their scans; for example, finding custom functions with these names and assuming they refer to the standard library functions. Many of Flutter's third-party dependencies have functions with similar names that trip these checks. It's possible that some occurrences are valid concerns, but it's impossible to tell from the output of these tools due to the sheer number of false positives. ### Code should use \`calloc\` (instead of \`\_malloc\`) for memory allocations > The binary may use \`\_malloc\` function instead of \`calloc\`. Memory allocation is a nuanced topic, where trade-offs have to be made between performance and resilience to vulnerabilities. Merely using \`malloc\` is not automatically indicative of a security vulnerability. While we welcome concrete reports (see below) for cases where using \`calloc\` would be preferable, in practice it would be inappropriate to uniformly replace all \`malloc\` calls with \`calloc\`. ### The iOS binary has a Runpath Search Path (\`@rpath\`) set > The binary has Runpath Search Path (\`@rpath\`) set. > In certain cases an attacker can abuse this feature to run arbitrary executable for code execution and privilege escalation. > Remove the compiler option \`-rpath\` to remove \`@rpath\`. When the app is being built, Runpath Search Path refers to the paths the linker searches to find dynamic libraries (dylibs) used by the app. By default, iOS apps have this set to \`@executable\_path/Frameworks\`, which means the linker should search for dylibs in the \`Frameworks\` directory relative to the app binary inside the app bundle. The \`Flutter.framework\` engine, like most embedded frameworks or dylibs, is correctly copied into this directory. When the app runs, it loads the library binary. Flutter apps use the default iOS build setting (\`LD\_RUNPATH\_SEARCH\_PATHS=@executable\_path/Frameworks\`). Vulnerabilities involving \`@rpath\` don't apply in mobile settings, as attackers don't have access to the file system and can't arbitrarily swap out these frameworks. Even if an attacker somehow \_could\_ swap out the framework with a malicious one, the app would crash on launch due to codesigning violations. ### CBC with PKCS5/PKCS7 padding vulnerability We have received vague reports that there is a "CBC with PKCS5/PKCS7 padding vulnerability" in some Flutter packages. As far as we can tell, this is triggered by the HLS implementation in ExoPlayer (the \`com.google.android.exoplayer2.source.hls.Aes128DataSource\` class). HLS is Apple's streaming format, which defines the type of encryption that must be used for DRM; this isn't a vulnerability, as DRM doesn't protect the user's machine or data but instead merely provides obfuscation to limit the user's ability to fully use their software and hardware. ### Apps can read and write to external storage > App can read/write to External Storage. Any App can read data written to External Storage. > As with data from any untrusted source, you should perform input validation when handling data from external storage. > We strongly recommend that you not store executables or class files on external storage prior to dynamic loading. > If your app does retrieve executable files from external storage, > the files should be signed and cryptographically verified prior to dynamic loading. We have received reports that some vulnerability scanning tools interpret the ability for image picker plugins to read and write to external storage as a threat. Reading images from local storage is the purpose of these plugins; this is not a vulnerability. ### Apps delete data using file.delete() > When you delete a file using file. delete, only the reference to the file is removed from the file system table. > The file still exists on disk until other data overwrites it, leaving it vulnerable to recovery. Some vulnerability scanning tools interpret the deletion of temporary files after a camera plugin records data from the device's camera as a security vulnerability. Because the video is recorded by the user and is stored on the user's hardware, there is no actual risk. ## Obsolete concerns This section contains valid messages that might be seen with older versions of Dart and Flutter, but should no longer be seen with more recent versions. If you see these messages with old versions of Dart or Flutter, upgrade to the latest stable version. If you see these with the current stable version, please report them (see the section at the end of this document). ### The stack should have its NX bit set > The shared object does not have NX bit set. > NX bit offer protection against exploitation of memory corruption vulnerabilities by marking memory page as non-executable. > Use option \`--noexecstack\` or \`-z noexecstack\` to mark stack as non executable. (The message from MobSF is misleading; it's looking for whether the stack is marked as non-executable, not the shared object.) In older versions of Dart and Flutter there was a bug where the ELF generator didn't emit the \`gnustack\` segment with the \`\~X\` permission, but this is now fixed. ## Reporting real concerns While automated vulnerability scanning tools report false positives such as the examples above, we can't rule out that there are real issues that deserve closer attention. Should you find an issue that you believe is a legitimate security vulnerability, we would greatly appreciate if you would report it: \* \[Flutter security policy]\(/security) \* \[Dart security policy]\(https\://dart.dev/security)

----
url: https://docs.flutter.dev/testing/testing-plugins
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Testing plugins](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

All of the [usual types of Flutter tests](/testing/overview) apply to plugin packages as well, but because plugins contain native code they often also require other kinds of tests to test all of their functionality.

## Types of plugin tests

[#](#types-of-plugin-tests)

To see examples of each of these types of tests, you can [create a new plugin from the plugin template](/packages-and-plugins/developing-packages#step-1-create-the-package-1) and look in the indicated directories.

* **Dart [unit tests](/cookbook/testing/unit/introduction) and [widget tests](/cookbook/testing/widget/introduction)**. These tests allow you to test the Dart portion of your plugin just as you would test the Dart code of a non-plugin package. However, the plugin's native code [won't be loaded](/testing/plugins-in-tests), so any calls to platform channels need to be [mocked in tests](/testing/plugins-in-tests#mock-the-platform-channel).

  See the `test` directory for an example.

* **Dart [integration tests](/cookbook/testing/integration/introduction)**. Since integration tests run in the context of a Flutter application (the example app), they can test both the Dart and native code, as well as the interaction between them. They are also useful for unit testing web implementation code that needs to run in a browser.

  These are often the most important tests for a plugin. However, Dart integration tests using `integration_test` can't interact with native UI, such as native dialogs or the contents of platform views. For native component interaction support, consider using [`patrol`](https://pub.dev/packages/patrol)

  See the `example/integration_test` directory for an example.

* **Native unit tests.** Just as Dart unit tests can test the Dart portions of a plugin in isolation, native unit tests can test the native parts in isolation. Each platform has its own native unit test system, and the tests are written in the same native languages as the code it is testing.

  Native unit tests can be especially valuable if you need to mock out APIs wrapped by your plugin code, which isn't possible in a Dart integration test.

  You can set up and use any native test frameworks you are familiar with for each platform, but the following are already configured in the plugin template:

  * **Android**: [JUnit](https://github.com/junit-team/junit4/wiki/Getting-started) tests can be found in `android/src/test/`.

  * **iOS** and **macOS**: [XCTest](https://developer.apple.com/documentation/xctest) tests can be found in `example/ios/RunnerTests/` and `example/macos/RunnerTests/` respectively. These are in the example directory, not the top-level package directory, because they are run via the example app's project.

  * **Linux** and **Windows**: [GoogleTest](https://github.com/google/googletest) tests can be found in `linux/test/` and `windows/test/`, respectively.

Other types of tests, which aren't currently pre-configured in the template, are **native UI tests**. Running your application under a native UI testing framework, such as [Espresso](https://github.com/flutter/packages/tree/main/packages/espresso) or [XCUITest](https://developer.apple.com/library/archive/documentation/DeveloperTools/Conceptual/testing_with_xcode/chapters/09-ui_testing.html), enables tests that interact with both native and Flutter UI elements, so can be useful if your plugin can't be tested without native UI interactions.

## Running tests

[#](#running-tests)

### Dart unit tests

[#](#dart-unit-tests)

These can be run like any other Flutter unit tests, either from your preferred Flutter IDE, or using `flutter test`.

### Integration tests

[#](#integration-tests)

For information on running this type of test, check out the [integration test documentation](/cookbook/testing/integration/introduction). The commands must be run in the `example` directory.

### Native unit tests

[#](#native-unit-tests)

For all platforms, you need to build the example application at least once before running the unit tests, to ensure that all of the platform-specific build files have been created.

**Android JUnit**

If you have the example opened as an Android project in Android Studio, you can run the unit tests using the [Android Studio test UI](https://developer.android.com/studio/test/test-in-android-studio).

To run the tests from the command line, use the following command in the `example/android` directory:

sh

```
./gradlew testDebugUnitTest
```

**iOS and macOS XCTest**

If you have the example app opened in Xcode, you can run the unit tests using the [Xcode Test UI](https://developer.apple.com/library/archive/documentation/DeveloperTools/Conceptual/testing_with_xcode/chapters/05-running_tests.html).

To run the tests from the command line, use the following command in the `example/ios` (for iOS) or `example/macos` (for macOS) directory:

sh

```
xcodebuild test -workspace Runner.xcworkspace -scheme Runner -configuration Debug
```

For iOS tests, you might need to first open `Runner.xcworkspace` in Xcode to configure code signing.

**Linux GoogleTest**

To run the tests from the command line, use the following command in the example directory, replacing "my\_plugin" with your plugin project name:

sh

```
build/linux/plugins/x64/debug/my_plugin/my_plugin_test
```

If you built the example app in release mode rather than debug, replace "debug" with "release".

**Windows GoogleTest**

If you have the example app opened in Visual Studio, you can run the unit tests using the [Visual Studio test UI](https://learn.microsoft.com/en-us/visualstudio/test/getting-started-with-unit-testing?view=vs-2022\&tabs=dotnet%2Cmstest#run-unit-tests).

To run the tests from the command line, use the following command in the example directory, replacing "my\_plugin" with your plugin project name:

sh

```
build/windows/plugins/my_plugin/Debug/my_plugin_test.exe
```

If you built the example app in release mode rather than debug, replace "Debug" with "Release".

## What types of tests to add

[#](#what-types-of-tests-to-add)

The [general advice for testing Flutter projects](/testing/overview) applies to plugins as well. Some extra considerations for plugin testing:

* Since only integration tests can test the communication between Dart and the native languages, try to have at least one integration test of each platform channel call.

* If some flows can't be tested using the `integration_test` package (for example if they require interacting with native UI or mocking device state), consider using the [`patrol`](https://pub.dev/packages/patrol) package for native UI interactions, or writing "end to end" tests of the two halves using unit tests:

  * Native unit tests that set up the necessary mocks, then call into the method channel entry point with a synthesized call and validate the method response.

  * Dart unit tests that mock the platform channel, then call the plugin's public API and validate the results.

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-07-31. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/testing/testing-plugins.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/testing/testing-plugins\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/testing/testing-plugins.md "Report an issue with this page").

----
url: https://docs.flutter.dev/ai/ai-toolkit/user-experience
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top User experience](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

The [`LlmChatView`](https://pub.dev/documentation/flutter_ai_toolkit/latest/flutter_ai_toolkit/LlmChatView-class.html) widget is the entry point for the interactive chat experience that AI Toolkit provides. Hosting an instance of the `LlmChatView` enables a number of user experience features that don't require any additional code to use:

* **Multiline text input**: Allows users to paste long text input or insert new lines into their text as they enter it.
* **Voice input**: Allows users to input prompts using speech for ease of use.
* **Multimedia input**: Enables users to take pictures, send images and other file types and attach URLs as link to online resources.
* **Image zoom**: Enables users to zoom into image thumbnails.
* **Copy to clipboard**: Allows the user to copy the text of a message or a LLM response to the clipboard.
* **Message editing**: Allows the user to edit the most recent message for resubmission to the LLM.
* **Material and Cupertino**: Adapts to the best practices of both design languages.

## Multiline text input

[#](#multiline-text-input)

The user has options when it comes to submitting their prompt once they've finished composing it, which again differs depending on their platform:

* **Mobile**: Tap the **Submit** button
* **Web**: Press **Enter** or tap the **Submit** button
* **Desktop**: Press **Enter** or tap the **Submit** button

In addition, the chat view supports text prompts with embedded newlines in them. If the user has existing text with newlines, they can paste them into the prompt text field as normal.

If they'd like to embed newlines into their prompt manually as they enter it, they can do so. The gesture for that activity differs based on the platform they're using:

* **Mobile**: Tap Return key on the virtual keyboard
* **Web**: Press `Shift+Enter`
* **Desktop**: Press `Shift+Enter`

These options look like the following:

**Web and Desktop**:

**Mobile**:

## Voice input

[#](#voice-input)

In addition to text input the chat view can take an audio recording as input by tapping the Mic button, which is visible when no text has yet been entered.

Tap the **Mic** button to start the recording:

Select the **Stop** button to translate the user's voice input into text:

This text can then be edited, augmented and submitted as normal.

## Multimedia input

[#](#multimedia-input)

The chat view can also take images and files as input to pass along to the underlying LLM. The user can select the **Plus** button to the left of the text input and choose from the **Take Photo**, **Image Gallery**, **Attach File** and **Attach Link** icons:

The **Take Photo** button allows the user to use their device's camera to take a photo:

Select the **Image Gallery** button to let the user upload from their device's image gallery:

Select the **Attach File** button to let the user select a file of any type available on their device, like a PDF or TXT file.

Select the **Attach Link** button to let the user enter a link to a web page or an online file.

Once a photo, image, file, or link has been selected, it becomes an attachment and shows up as a thumbnail associated with the currently active prompt:

The user can remove an attachment by clicking the **X** button on the thumbnail.

## Image zoom

[#](#image-zoom)

The user can zoom into an image thumbnail by tapping it:

Pressing the **Esc** key or tapping anywhere outside the image dismisses the zoomed image.

## Copy to clipboard

[#](#copy-to-clipboard)

The user can copy any text prompt or LLM response in their current chat in a variety of ways. On the desktop or the web, the user can mouse to select the text on their screen and copy it to the clipboard as normal:

In addition, at the bottom of each prompt or response, the user can select the **Copy** button that pops up when they hover their mouse:

On mobile platforms, the user can long-tap a prompt or response and choose the Copy option:

## Message editing

[#](#message-editing)

If the user would like to edit their last prompt and cause the LLM to take another run at it, they can do so. On the desktop, the user can tap the **Edit** button alongside the **Copy** button for their most recent prompt:

On a mobile device, the user can long-tap and get access to the **Edit** option on their most recent prompt:

Once the user taps the **Edit** button, they enter Editing mode, which removes both the user's last prompt and the LLM's last response from the chat history, puts the text of the prompt into the text field, and provides an Editing indicator:

In Editing mode, the user can edit the prompt as they choose and submit it to have the LLM produce a response as normal. Or, if they change their mind, they can tap the **X** near the Editing indicator to cancel their edit and restore their previous LLM response.

## Material and Cupertino

[#](#material-and-cupertino)

When the `LlmChatView` widget is hosted in a [Material app](https://pub.dev/documentation/material_ui/latest/material_ui/MaterialApp-class.html), it uses facilities provided by the Material design language, such as Material's [`TextField`](https://pub.dev/documentation/material_ui/latest/material_ui/TextField-class.html). Likewise, when hosted in a [Cupertino app](https://pub.dev/documentation/cupertino_ui/latest/cupertino_ui/CupertinoApp-class.html), it uses those facilities, such as [`CupertinoTextField`](https://pub.dev/documentation/cupertino_ui/latest/cupertino_ui/CupertinoTextField-class.html).

However, while the chat view supports both the Material and Cupertino app types, it doesn't automatically adopt the associated themes. Instead, that's set by the `style` property of the `LlmChatView` as described in the [Custom styling](/ai/ai-toolkit/feature-integration#custom-styling) documentation.

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-09-29. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/ai/ai-toolkit/user-experience.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/ai/ai-toolkit/user-experience\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/ai/ai-toolkit/user-experience.md "Report an issue with this page").

----
url: https://docs.flutter.dev/learn/pathway/tutorial/listenable-builder
----

Learn to use ListenableBuilder to automatically rebuild UI and handle all possible states with switch expressions.

sync Use ListenableBuilder to rebuild UI automatically

alt\_route Handle all possible states with switch expressions

article Build the complete View layer with proper styling

***

## Steps

1### Introductionkeyboard\_arrow\_up

The view layer is your UI, and in Flutter, that refers to your app's widgets. As it pertains to this tutorial, the important part is wiring up your UI to respond to data changes from the ViewModel. [`ListenableBuilder`](https://api.flutter.dev/flutter/widgets/ListenableBuilder-class.html) is a widget that can "listen" to a [`ChangeNotifier`](https://api.flutter.dev/flutter/foundation/ChangeNotifier-class.html), and automatically rebuilds when it's provided `ChangeNotifier` calls `notifyListeners()`.

2### Create the article view widgetkeyboard\_arrow\_up

Create the `ArticleView` widget that manages your page's layout and ViewModel lifecycle. Because it must explicitly initialize data fetching before rendering, implement it as a `StatefulWidget`.

Start by creating the basic stateful structure:

dart

```
import 'package:material_ui/material_ui.dart';
​
class ArticleView extends StatefulWidget {
  const ArticleView({super.key});
​
  @override
  State<ArticleView> createState() => _ArticleViewState();
}
​
class _ArticleViewState extends State<ArticleView> {
  // The view model will be instantiated here next.
​
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Wikipedia Flutter')),
      body: const Center(child: Text('Loading...')),
    );
  }
}
```

3### Instantiate the article view modelkeyboard\_arrow\_up

Next, initialize your `ArticleViewModel` mapping it to the state's lifecycle. Provide the ViewModel and execute `fetchArticle()` within `initState()`:

dart

```
class ArticleView extends StatefulWidget {
  const ArticleView({super.key});
​
  @override
  State<ArticleView> createState() => _ArticleViewState();
}
​
class _ArticleViewState extends State<ArticleView> {
  final ArticleViewModel viewModel = ArticleViewModel(ArticleModel());
​
  @override
  void initState() {
    super.initState();
    viewModel.fetchArticle();
  }
​
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Wikipedia Flutter')),
      body: const Center(child: Text('Loading...')),
    );
  }
}
```

4### Update your app to include the article viewkeyboard\_arrow\_up

Connect everything together by updating your `MainApp` to include your completed `ArticleView`.

Replace your existing `MainApp` with this updated version:

dart

```
class MainApp extends StatelessWidget {
  const MainApp({super.key});
​
  @override
  Widget build(BuildContext context) {
    return const MaterialApp(home: ArticleView());
  }
}
```

This change switches from the console-based test to the full UI experience with proper state management.

5### Listen for state changeskeyboard\_arrow\_up

Wrap your UI in a [`ListenableBuilder`](https://api.flutter.dev/flutter/widgets/ListenableBuilder-class.html) to listen for state changes, and pass it a `ChangeNotifier` object. In this case, the `ArticleViewModel` extends `ChangeNotifier`.

dart

```
class ArticleView extends StatefulWidget {
  const ArticleView({super.key});
​
  @override
  State<ArticleView> createState() => _ArticleViewState();
}
​
class _ArticleViewState extends State<ArticleView> {
  final ArticleViewModel viewModel = ArticleViewModel(ArticleModel());
​
  @override
  void initState() {
    super.initState();
    viewModel.fetchArticle();
  }
​
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Wikipedia Flutter')),
      body: ListenableBuilder(
        listenable: viewModel,
        builder: (context, child) {
          return const Center(child: Text('Loading...'));
        },
      ),
    );
  }
}
```

`ListenableBuilder` uses the *builder* pattern, which requires a callback rather than a `child` widget to build the widget tree below it. These widgets are flexible because you can perform operations within the callback, building different widgets based on the state.

6### Handle possible view model stateskeyboard\_arrow\_up

Recall the `ArticleViewModel`, which has three properties that the UI is interested in:

* `Summary? summary`
* `bool isLoading`
* `Exception? error`

Depending on the combined state of these properties, the UI can display different widgets. Use Dart's support for [switch expressions](https://dart.dev/language/branches#switch-expressions) to handle all possible combinations in a clean, readable way:

dart

```
class ArticleView extends StatefulWidget {
  const ArticleView({super.key});
​
  @override
  State<ArticleView> createState() => _ArticleViewState();
}
​
class _ArticleViewState extends State<ArticleView> {
  final ArticleViewModel viewModel = ArticleViewModel(ArticleModel());
​
  @override
  void initState() {
    super.initState();
    viewModel.fetchArticle();
  }
​
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Wikipedia Flutter')),
      body: Center(
        child: ListenableBuilder(
          listenable: viewModel,
          builder: (context, _) {
            return switch ((
              viewModel.isLoading,
              viewModel.summary,
              viewModel.error,
            )) {
              (true, _, _) => const CircularProgressIndicator(),
              (_, _, final Exception e) => Text('Error: $e'),
              (_, final summary?, _) => ArticlePage(
                summary: summary,
                nextArticleCallback: viewModel.fetchArticle,
              ),
              _ => const Text('Something went wrong!'),
            };
          },
        ),
      ),
    );
  }
}
```

This is an excellent example of how a declarative, reactive framework like Flutter and a pattern like MVVM work together: The UI is rendered based on the state and updates when a state changes demands it, but it doesn't manage any state or the process of updating itself. The business logic and rendering are completely separate from each other.

7### Complete the UIkeyboard\_arrow\_up

The only thing remaining is to use the properties and methods provided by the view model to build the UI.

Now create a `ArticlePage` widget that displays the actual article content. This reusable widget takes summary data and a callback function:

dart

```
class ArticlePage extends StatelessWidget {
  const ArticlePage({
    super.key,
    required this.summary,
    required this.nextArticleCallback,
  });
​
  final Summary summary;
  final VoidCallback nextArticleCallback;
​
  @override
  Widget build(BuildContext context) {
    return const Center(
      child: Text('Article content will be displayed here...'),
    );
  }
}
```

8### Add a scrollable layoutkeyboard\_arrow\_up

Replace the placeholder with a scrollable column layout:

dart

```
class ArticlePage extends StatelessWidget {
  const ArticlePage({
    super.key,
    required this.summary,
    required this.nextArticleCallback,
  });
​
  final Summary summary;
  final VoidCallback nextArticleCallback;
​
  @override
  Widget build(BuildContext context) {
    return const SingleChildScrollView(
      child: Column(
        children: [Text('Article content will be displayed here...')],
      ),
    );
  }
}
```

9### Add article content and buttonkeyboard\_arrow\_up

Complete the layout with an article widget and navigation button:

dart

```
class ArticlePage extends StatelessWidget {
  const ArticlePage({
    super.key,
    required this.summary,
    required this.nextArticleCallback,
  });
​
  final Summary summary;
  final VoidCallback nextArticleCallback;
​
  @override
  Widget build(BuildContext context) {
    return SingleChildScrollView(
      child: Column(
        children: [
          ArticleWidget(summary: summary),
          ElevatedButton(
            onPressed: nextArticleCallback,
            child: const Text('Next random article'),
          ),
        ],
      ),
    );
  }
}
```

10### Create the `ArticleWidget`keyboard\_arrow\_up

The `ArticleWidget` handles the display of the actual article content with proper styling and conditional rendering.

#### Set up the basic article structure

[#](#set-up-the-basic-article-structure)

Start with the widget that accepts a `summary` parameter:

dart

```
class ArticleWidget extends StatelessWidget {
  const ArticleWidget({super.key, required this.summary});
​
  final Summary summary;
​
  @override
  Widget build(BuildContext context) {
    return const Text('Article content will be displayed here...');
  }
}
```

#### Add padding and column layout

[#](#add-padding-and-column-layout)

Wrap the content in proper padding and layout:

dart

```
class ArticleWidget extends StatelessWidget {
  const ArticleWidget({super.key, required this.summary});
​
  final Summary summary;
​
  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(8),
      child: Column(
        spacing: 10,
        children: [const Text('Article content will be displayed here...')],
      ),
    );
  }
}
```

#### Add conditional image display

[#](#add-conditional-image-display)

Add the article image that only shows when available:

dart

```
class ArticleWidget extends StatelessWidget {
  const ArticleWidget({super.key, required this.summary});
​
  final Summary summary;
​
  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(8),
      child: Column(
        spacing: 10,
        children: [
          if (summary.hasImage) Image.network(summary.originalImage!.source),
          const Text('Article content will be displayed here...'),
        ],
      ),
    );
  }
}
```

#### Complete with styled text content

[#](#complete-with-styled-text-content)

Replace the placeholder text with a properly styled title, description, and extract:

dart

```
class ArticleWidget extends StatelessWidget {
  const ArticleWidget({super.key, required this.summary});
​
  final Summary summary;
​
  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(8),
      child: Column(
        spacing: 10,
        children: [
          if (summary.hasImage) Image.network(summary.originalImage!.source),
          Text(
            summary.titles.normalized,
            overflow: TextOverflow.ellipsis,
            style: Theme.of(context).textTheme.displaySmall,
          ),
          if (summary.description != null)
            Text(
              summary.description!,
              overflow: TextOverflow.ellipsis,
              style: Theme.of(context).textTheme.bodySmall,
            ),
          Text(summary.extract),
        ],
      ),
    );
  }
}
```

This widget demonstrates a few important UI concepts:

* **Conditional rendering**: The `if` statements show content only when available.
* **Text styling**: Different text styles create visual hierarchy using Flutter's theme system.
* **Proper spacing**: The `spacing` parameter provides consistent vertical spacing.
* **Overflow handling**: `TextOverflow.ellipsis` prevents text from breaking the layout.

11### Run the complete appkeyboard\_arrow\_up

Hot reload your app one final time. You should now see:

1. A loading spinner while the initial article loads.
2. The article's title, description, and summary extract.
3. An image (if the article has one).
4. A button to load another random article.

To see the reactive UI in action, click the **Next random article** button. The app shows a loading state, fetches new data, and updates the display automatically.

12### Reviewkeyboard\_arrow\_up

sync Used ListenableBuilder to rebuild UI automatically keyboard\_arrow\_up

`ListenableBuilder` listens to your ViewModel and automatically rebuilds its children whenever `notifyListeners()` is called. In the MVVM pattern, this is the key connection between your ViewModel and View.

alt\_route Handled all possible states with switch expressions keyboard\_arrow\_up

Using a switch expression, you accounted for the possible state combinations with an appropriate user interface, Conditionally displaying a loading spinner, an error message, or the actual article content. With this handling, the UI is now more robust and complete.

article Built the complete View layer with proper styling keyboard\_arrow\_up

You created `ArticleView`, `ArticlePage`, and `ArticleWidget` with conditional rendering, text styling, proper spacing, and overflow handling. These are core UI patterns you'll use in every Flutter app.

celebration Completed the MVVM architecture keyboard\_arrow\_up

You've built a complete app with Model (data operations), ViewModel (state management), and View (reactive UI) layers. This separation of concerns helps your code be more testable, maintainable, and scalable.

13### Test yourselfkeyboard\_arrow\_up

### ListenableBuilder Quiz

1 / 2

**What is the purpose of ListenableBuilder in Flutter?**

1. To create animations based on a ChangeNotifier.

   Not quite.

   ListenableBuilder rebuilds UI on state changes, not specifically for animations.

2. To listen to a ChangeNotifier and automatically rebuild its child widgets when `notifyListeners()` is called.

   That's right!

   ListenableBuilder listens to a Listenable and rebuilds its builder function when notified.

3. To manually control when widgets should be rebuilt.

   Not quite.

   The rebuild is automatic when notifyListeners() is called; you don't control it manually.

4. To cache widget builds for better performance.

   Not quite.

   ListenableBuilder is about reactive updates, not caching.

**When does ListenableBuilder rebuild its child widgets?**

1. Every time the app's frame refreshes.

   Not quite.

   ListenableBuilder only rebuilds when notified, not on every frame.

2. When the Listenable it's listening to calls notifyListeners().

   That's right!

   ListenableBuilder subscribes to the Listenable and rebuilds its builder function whenever `notifyListeners()` is called.

3. Only when the widget is first mounted.

   Not quite.

   It rebuilds whenever `notifyListeners()` is called, not just on mount.

4. When the parent widget rebuilds.

   Not quite.

   ListenableBuilder rebuilds based on the Listenable, not parent rebuilds.

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-09-29. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/learn/pathway/tutorial/listenable-builder.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/learn/pathway/tutorial/listenable-builder\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/learn/pathway/tutorial/listenable-builder.md "Report an issue with this page").

----
url: https://docs.flutter.dev/embedded
----

If you would like to embed Flutter engine into a car, a refrigerator, a thermostat... you CAN! For example, you might embed Flutter in the following situations:

* Using Flutter on an "embedded device", typically a low-powered hardware device such as a smart-display, a thermostat, or similar.
* Embedding Flutter into a new operating system or environment, for example a new mobile platform or a new operating system.

The ability to embed Flutter, while stable, uses low-level API and is *not* for beginners. In addition to the resources listed below, you might consider joining [Discord](https://discord.com/invite/N7Yshp4), where Flutter developers (including Google engineers) discuss various aspects of Flutter. The Flutter [community](https://flutter.dev/community) page has info on more community resources.

* [Custom Flutter Engine Embedders](https://github.com/flutter/flutter/blob/main/docs/engine/Custom-Flutter-Engine-Embedders.md), on the Flutter wiki.
* The doc comments in the [Flutter engine `embedder.h` file](https://github.com/flutter/flutter/blob/main/engine/src/flutter/shell/platform/embedder/embedder.h) on GitHub.
* The [Flutter architectural overview](/resources/architectural-overview) on docs.flutter.dev.
* A small, self-contained [Flutter Embedder Engine GLFW example](https://github.com/flutter/flutter/tree/main/engine/src/flutter/examples/glfw#flutter-embedder-engine-glfw-example) in the Flutter engine GitHub repo.
* An exploration into [embedding Flutter in a terminal](https://github.com/jiahaog/flt) by implementing Flutter's custom embedder API.
* [Issue 31043](https://github.com/flutter/flutter/issues/31043): *Questions for porting flutter engine to a new os* might also be helpful.

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/embedded/index.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/embedded\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/embedded/index.md "Report an issue with this page").

----
url: https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.8.0.md
----

\# DevTools 2.8.0 release notes > Release notes for Dart and Flutter DevTools version 2.8.0. The 2.8.0 release of the Dart and Flutter DevTools includes the following changes among other general improvements. To learn more about DevTools, check out the \[DevTools overview]\(https\://docs.flutter.dev/tools/devtools). ## General updates \* Improvements for initial page load time - \[#3325]\(https\://github.com/flutter/devtools/pull/3325) \* Performance improvements for connecting DevTools to a device, particularly impactful for low-memory devices - \[#3468]\(https\://github.com/flutter/devtools/pull/3468) \* For users on Flutter 2.8.0 or greater (or Dart 2.15.0 or greater), DevTools should now be launched via the \`dart devtools\` command instead of running \`pub global activate devtools\`. DevTools 2.8.0 will be the last version of DevTools shipped on pub, and all future versions of DevTools will be shipped as part of the Dart SDK. If you see this warning, be sure to open DevTools via \`dart devtools\` instead of from pub: !\[dart devtools warning dialog]\(/assets/images/docs/tools/devtools/release-notes/images-2.8.0/image1.png "dart devtools warning dialog") ## Performance updates \* Added a new "Enhance Tracing" feature to help users diagnose UI jank stemming from expensive Build, Layout, and Paint operations. !\[Enhance tracing]\(/assets/images/docs/tools/devtools/release-notes/images-2.8.0/image2.png "Enhance tracing") The expected workflow is as such: 1. User is investigating UI jank in the performance page 2. User notices a long Build, Layout, and/or Paint event 3. User turns on the respective tracking toggle in the "Enhance Tracing" feature 4. User reproduces the UI jank in their app 5. User looks at the new set of Timeline events, which should now have additional child events for widgets built, render objects laid out, and/or render objects painted !\[Timeline events]\(/assets/images/docs/tools/devtools/release-notes/images-2.8.0/image3.png "Timeline events") \* Added new "More debugging options" feature to allow for disabling rendering layers for Clip, Opacity, and Physical Shapes. !\[More debugging options]\(/assets/images/docs/tools/devtools/release-notes/images-2.8.0/image4.png "More debugging options") The expected workflow is as such: 1. User is investigating UI jank in the performance page 2. User notices a lot of janky frames and suspects it could be due to excessive use of clipping, opacity, or physical shapes. 3. User turns off the respective render layer toggle in the "More debugging options" feature 4. User reproduces the UI jank in their app 5. If the UI jank is reduced with a rendering layer turned off, the user should try to optimize their app to use less clipping/opacity/physical shape effects. If the UI jank is not reduced, the user now knows that the performance problem is not due to these UI effects. ## Debugger updates \* Replaced the "Libraries" pane with a "File Explorer" pane - \[#3448]\(https\://github.com/flutter/devtools/pull/3448). The "File Explorer" pane has two components: 1. A tree view of the libraries present in your application. You can use the File Explorer to find and open a library, or you can use the existing `Ctrl` / `Cmd` + `P` keyboard shortcut to search for a file. 1. A new "Outline" view that shows the structure of the selected library. This view will show classes, members, methods, etc., and when an item is selected, the source view will jump to the respective line of code for the selected item. !\[Outline view selected library]\(/assets/images/docs/tools/devtools/release-notes/images-2.8.0/image5.png "Outline view selected library") \* Performance improvements to expression evaluation auto complete - \[#3463]\(https\://github.com/flutter/devtools/pull/3463) \* Fixed a bug with keyboard shortcuts - \[#3458]\(https\://github.com/flutter/devtools/pull/3458) \* UI polish - \[#3421]\(https\://github.com/flutter/devtools/pull/3421), \[#3449]\(https\://github.com/flutter/devtools/pull/3449) ## Full commit history To find a complete list of changes since the previous release, check out \[the diff on GitHub]\(https\://github.com/flutter/devtools/compare/v2.7.0...v2.8.0).

----
url: https://docs.flutter.dev/resources/videos
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Videos](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

These Flutter videos, produced both internally at Google and by the Flutter community, might help if you are a visual learner.

Note that many people make Flutter videos. This page shows some that we like, but there are many others, including some in different languages.

***

## Series

[#](#series)

### Flutter Forward

[#](#flutter-forward)

Even if you couldn't make it to Nairobi for the in-person event, you can enjoy the content created for Flutter Forward!

[Watch on YouTube in a new tab: "Flutter Forward 2023 Livestream"](https://www.youtube.com/watch/zKQYGKAe5W8)

[Flutter Forward playlist](https://www.youtube.com/playlist?list=PLjxrf2q8roU3LvrdR8Hv_phLrTj0xmjnD)

### Get ready for Flutter Forward

[#](#get-ready-for-flutter-forward)

Flutter Forward occurred on Jan 25th, 2023 in Nairobi, Kenya. Before the event, the Flutter team provided 17 days of Flutter featuring new content and activities leading up to the event. This playlist contains these and other pre-event videos relating to Flutter Forward.

[Watch on YouTube in a new tab: "Prepare for Flutter Forward 2023 - 17 days of Flutter"](https://www.youtube.com/watch/hpgkrUPRBjc)

**17 Days of Flutter!**

[Get ready for Flutter Forward playlist](https://www.youtube.com/playlist?list=PLjxrf2q8roU3PUEaM4yYXqvFMEwOluBNl)

### Learning to Fly

[#](#learning-to-fly)

Follow along with Khanh's journey as she learns Flutter. From ideation down to the moments of confusion, learn alongside her as she asks important questions like: "What is the best way to build out a theme? How to approach using fonts?" And more!

[Watch on YouTube in a new tab: "Building my first Flutter app | Learning to Fly"](https://www.youtube.com/watch/CkcvVZZEsJE)

[Learning to Fly playlist](https://www.youtube.com/playlist?list=PLjxrf2q8roU3X18pAQWLyCJaa79RpqWnn)

Here are some of the series offered on the [flutterdev YouTube channel](https://www.youtube.com/@flutterdev). For more information, check out *all* of the [Flutter channel's playlists](https://www.youtube.com/@flutterdev/playlists).

### Decoding Flutter

[#](#decoding-flutter)

This series focuses on tips, tricks, best practices, and general advice on getting the most out of Flutter. In the comments section for this series, you can submit suggestions future episodes.

[Watch on YouTube in a new tab: "Introducing Decoding Flutter - Flutter best practices"](https://www.youtube.com/watch/QIW35-vcA2o)

[Decoding Flutter playlist](https://www.youtube.com/playlist?list=PLjxrf2q8roU1fRV40Ec8200rX6OuQkmnl)

### Flutter widget of the week

[#](#flutter-widget-of-the-week)

Do you have 60 seconds? Each one-minute video highlights a Flutter widget.

[Watch on YouTube in a new tab: "Introducing Flutter widget of the week"](https://www.youtube.com/watch/b_sQ9bMltGU)

[Flutter widget of the week playlist](https://www.youtube.com/playlist?list=PLjxrf2q8roU23XGwz3Km7sQZFTdB996iG)

### Flutter package of the week

[#](#flutter-package-of-the-week)

If you have another minute (or two), each of these videos highlights a Flutter package.

[Watch on YouTube in a new tab: "Introducing Flutter package of the week"](https://www.youtube.com/watch/QFcFEpFmNJ8)

[Flutter package of the week playlist](https://www.youtube.com/playlist?list=PLjxrf2q8roU1quF6ny8oFHJ2gBdrYN_AK)

### The Boring Flutter show

[#](#the-boring-flutter-show)

This series features Flutter programmers coding, unscripted and in real time. Mistakes, solutions (some of them correct), and snazzy intro music included.

[Watch on YouTube in a new tab: "Introducing The Boring Show - Flutter live coding"](https://www.youtube.com/watch/vqPG1tU6-c0)

[The Boring Flutter show playlist](https://www.youtube.com/playlist?list=PLjxrf2q8roU3ahJVrSgAnPjzkpGmL9Czl)

### Flutter at Google I/O 2022

[#](#flutter-at-google-io-2022)

Google I/O 2022 is over, but you can still catch the great Flutter content!

[Watch on YouTube in a new tab: "What's new in Flutter at Google I/O 2022"](https://www.youtube.com/watch/w_ezWG1yKQQ)

[Flutter at Google I/O playlist](https://www.youtube.com/playlist?list=PLjxrf2q8roU1kHjuHoFGBLCxjy4h2WOcP)

### Flutter Engage 2021

[#](#flutter-engage-2021)

[Flutter Engage 2021](https://events.flutter.dev/) was a day-long event that officially launched Flutter 2.

Check out the Flutter Engage 2021 highlights reel.

[Watch on YouTube in a new tab: "Top 10 highlights from Flutter Engage 2021"](https://www.youtube.com/watch/IdrCyS7EF8M)

Watch recordings of the sessions on the Flutter YouTube channel.

[Watch on YouTube in a new tab: "Flutter Engage 2021 keynote"](https://www.youtube.com/watch/zSbsIiluixw)

[Flutter Engage 2021 playlist](https://www.youtube.com/playlist?list=PLjxrf2q8roU21cXt24HLm-ZODXmr4Jw0C)

Watch recordings of the sessions offered by the Flutter community.

[Watch on YouTube in a new tab: "Choreographing transitions with Flutter's animation framework"](https://www.youtube.com/watch/-_de6IfY2RU)

[Flutter Engage community talks playlist](https://www.youtube.com/playlist?list=PLjxrf2q8roU1ln3WoiGTUvLr7ZrPPFvXv)

### Flutter in focus

[#](#flutter-in-focus)

Five-to-ten minute tutorials (more or less) on using Flutter.

[Watch on YouTube in a new tab: "Introducing Flutter in Focus - Flutter tutorials"](https://www.youtube.com/watch/wgTBLj7rMPM)

[Flutter in focus playlist](https://www.youtube.com/playlist?list=PLjxrf2q8roU2HdJQDjJzOeO6J3FoFLWr2)

## Conference talks

[#](#conference-talks)

Here are a some Flutter talks given at various conferences.

[Watch on YouTube in a new tab: "Pragmatic state management in Flutter"](https://www.youtube.com/watch/d_m5csmrf7I)

[Conference talks playlist](https://www.youtube.com/playlist?list=PLjxrf2q8roU1UJ0OEpANodVMVm1GeE7Ti)

## Flutter developer stories

[#](#flutter-developer-stories)

Videos showing how various customers, such as Abbey Road Studio, Hamilton, and Alibaba, have used Flutter to create beautiful compelling apps with millions of downloads.

[Watch on YouTube in a new tab: "Flutter developer stories"](https://www.youtube.com/watch/_ACWeGGBP4E)

[Flutter developer stories playlist](https://www.youtube.com/playlist?list=PLjxrf2q8roU33POuWi4bK0zvDpAHK6759)

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/resources/videos.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/resources/videos\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/resources/videos.md "Report an issue with this page").

----
url: https://docs.flutter.dev/learn/pathway/tutorial/http-requests.md
----

\# Fetch data from the internet > Instructions on how to make HTTP requests and parse responses. Learn the MVVM architecture pattern and how to build HTTP requests with async/await. title: What you'll accomplish items: - title: Understand the MVVM architecture pattern icon: layers - title: Build HTTP requests with async/await icon: cloud\_download - title: Handle errors and parse JSON responses icon: data\_object --- ### Introduction The overarching pattern that this tutorial implements is called \_Model-View-ViewModel\_ or \_MVVM\_. MVVM is an \[architectural pattern]\[] used in client apps that separates your app into three layers: - \*\*Model\*\*: Handles data operations. - \*\*View\*\*: Displays the UI. - \*\*ViewModel\*\*: Manages state and connects the two. The core tenet of MVVM (and many other patterns) is \*separation of concerns\*. Managing state in separate classes (outside your UI widgets) makes your code more testable, reusable, and easier to maintain. A single feature in your app contains each one of the MVVM components. In this tutorial, in addition to Flutter widgets, you'll create \`ArticleModel\`, \`ArticleViewModel\`, and \`ArticleView\`. \[architectural pattern]: /app-architecture/guide ### Define the Model The Model is the source-of-truth for your app's data and is responsible for low-level tasks such as making HTTP requests, caching data, or managing system resources such as used by a Flutter plugin. A model doesn't usually need to import Flutter libraries. Create an empty \`ArticleModel\` class in your \`main.dart\` file:

<!--?code-excerpt "fwe/wikipedia_reader/lib/step2a_main.dart (ArticleModel)"?-->

\`\`\`dart class ArticleModel { // Properties and methods will be added here. } \`\`\` ### Build the HTTP request Wikipedia provides a REST API that returns JSON data about articles. For this app, you'll use the endpoint that returns a random article summary. \`\`\`text https\://en.wikipedia.org/api/rest\_v1/page/random/summary \`\`\` Add a method to fetch a random Wikipedia article summary:

<!--?code-excerpt "fwe/wikipedia_reader/lib/step2b_main.dart (ArticleModel)"?-->

\`\`\`dart class ArticleModel { Future

getRandomArticleSummary() async { final uri = Uri.https( 'en.wikipedia.org', '/api/rest\_v1/page/random/summary', ); final response = await get(uri); // TODO: Add error handling and JSON parsing. throw UnimplementedError(); } } \`\`\` Use the \[\`async\` and \`await\`]\[] keywords to handle asynchronous operations. The \`async\` keyword marks a method as asynchronous, and \`await\` waits for expressions that return a \[\`Future\`]\[]. The \`Uri.https\` constructor safely builds URLs by handling encoding and formatting. This approach is more reliable than string concatenation, especially when dealing with special characters or query parameters. \[\`async\` and \`await\`]: https\://dart.dev/language/async \[\`Future\`]: https\://api.flutter.dev/flutter/dart-async/Future-class.html ### Handle network errors Always handle errors when making HTTP requests. A status code of \*\*200\*\* indicates success, while other codes indicate errors. If the status code isn't \*\*200\*\*, the model throws an error for the UI to display to users. <!--?code-excerpt "fwe/wikipedia_reader/lib/step2c_main.dart (ArticleModel)"?-->\`\`\`dart class ArticleModel { FuturegetRandomArticleSummary() async { final uri = Uri.https( 'en.wikipedia.org', '/api/rest\_v1/page/random/summary', ); final response = await get(uri); if (response.statusCode != 200) { throw const HttpException('Failed to update resource'); } // TODO: Parse JSON and return Summary. throw UnimplementedError(); } } \`\`\` ### Parse JSON from Wikipedia The \[Wikipedia API]\[] returns \[JSON]\[] data that you decode into a \`Summary\` class Complete the \`getRandomArticleSummary\` method: <!--?code-excerpt "fwe/wikipedia_reader/lib/step2_main.dart (ArticleModel)"?-->\`\`\`dart class ArticleModel { FuturegetRandomArticleSummary() async { final uri = Uri.https( 'en.wikipedia.org', '/api/rest\_v1/page/random/summary', ); final response = await get(uri); if (response.statusCode != 200) { throw const HttpException('Failed to update resource'); } return Summary.fromJson(jsonDecode(response.body) as Map); } } \`\`\` The \`Summary\` class is defined in \`summary.dart\`. If you're unfamiliar with JSON parsing, check out the \[Getting started with Dart]\[] tutorial. \[Wikipedia API]: https\://en.wikipedia.org/api/rest\_v1/ \[JSON]: https\://dart.dev/tutorial/json \[Getting started with Dart]: https\://dart.dev/tutorial ### Review title: What you accomplished subtitle: Here's a summary of what you built and learned in this lesson. completed: true items: - title: Understood the MVVM architecture pattern icon: layers details: >- MVVM separates your app into Model (data operations), View (user interface), and ViewModel (state management). This separation of concerns makes your code more testable, reusable, and easier to maintain. - title: Built an HTTP request to fetch Wikipedia data icon: cloud\_download details: >- You created an \`ArticleModel\` class with a method that uses \`async\` and \`await\` to fetch data from the Wikipedia API. To safely build the URLs for the requests, you used the \`Uri.https\` constructor which handles encoding and special characters for you. - title: Handled errors and parsed JSON responses icon: data\_object details: >- You checked the HTTP status code to detect errors and used \`jsonDecode\` to parse the response body. Then to convert the raw JSON into a typed Dart object, you used the \`Summary.fromJson\` named constructor. ### Test yourself - question: "What do the \`async\` and \`await\` keywords do in Dart?" options: - text: They make code run on a separate thread. correct: false explanation: Dart is single-threaded; async/await handles asynchronous operations without threads. - text: They mark a function as asynchronous and pause execution until a Future completes. correct: true explanation: "The \`async\` keyword marks a function as asynchronous, and \`await\` pauses execution until the Future resolves." - text: They automatically cache the results of function calls. correct: false explanation: Caching requires separate implementation; async/await is for handling asynchronous operations. - text: They convert synchronous code to run in the background. correct: false explanation: They don't move code to the background; they manage asynchronous execution flow. - question: "Why is \`Uri.https\` preferred over string concatenation when building URLs in Dart?" options: - text: It makes the code shorter. correct: false explanation: Code length isn't the main benefit; proper encoding is. - text: It safely handles encoding and formatting, especially for special characters and query parameters. correct: true explanation: Uri.https properly encodes special characters and formats URLs, preventing common errors. - text: It's required by the http package. correct: false explanation: You can use strings, but Uri.https is safer and more reliable. - text: It automatically validates that the URL exists. correct: false explanation: Uri.https builds the URL; it doesn't check if the endpoint exists.

----
url: https://docs.flutter.dev/ui/widgets/animation
----

Bring animations to your app.

[Implicitly​Animated​Widget](https://api.flutter.dev/flutter/widgets/ImplicitlyAnimatedWidget-class.html)

[An abstract class for building widgets that animate changes to their properties.](https://api.flutter.dev/flutter/widgets/ImplicitlyAnimatedWidget-class.html)

[Decorated​Box​Transition](https://api.flutter.dev/flutter/widgets/DecoratedBoxTransition-class.html)

[Animated version of a DecoratedBox that animates the different properties of its Decoration.](https://api.flutter.dev/flutter/widgets/DecoratedBoxTransition-class.html)

[Default​Text​Style​Transition](https://api.flutter.dev/flutter/widgets/DefaultTextStyleTransition-class.html)

[Animated version of a DefaultTextStyle that animates the different properties of its TextStyle.](https://api.flutter.dev/flutter/widgets/DefaultTextStyleTransition-class.html)

[Fade​Transition](https://api.flutter.dev/flutter/widgets/FadeTransition-class.html)

[Animates the opacity of a widget.](https://api.flutter.dev/flutter/widgets/FadeTransition-class.html)

[Hero](https://api.flutter.dev/flutter/widgets/Hero-class.html)

[A widget that marks its child as being a candidate for hero animations.](https://api.flutter.dev/flutter/widgets/Hero-class.html)

[Matrix​Transition](https://api.flutter.dev/flutter/widgets/MatrixTransition-class.html)

[Animates the Matrix4 of a transformed widget.](https://api.flutter.dev/flutter/widgets/MatrixTransition-class.html)

[Positioned​Transition](https://api.flutter.dev/flutter/widgets/PositionedTransition-class.html)

[Animated version of Positioned which takes a specific Animation to transition the child's position from a start position to and end position over the lifetime...](https://api.flutter.dev/flutter/widgets/PositionedTransition-class.html)

[Relative​Positioned​Transition](https://api.flutter.dev/flutter/widgets/RelativePositionedTransition-class.html)

[Animated version of Positioned which transitions the child's position based on the value of rect relative to a bounding box with the specified size.](https://api.flutter.dev/flutter/widgets/RelativePositionedTransition-class.html)

[Repeating​Animation​Builder](https://api.flutter.dev/flutter/widgets/RepeatingAnimationBuilder-class.html)

[A widget that animates an Animatable value and repeats indefinitely.](https://api.flutter.dev/flutter/widgets/RepeatingAnimationBuilder-class.html)

[Rotation​Transition](https://api.flutter.dev/flutter/widgets/RotationTransition-class.html)

[Animates the rotation of a widget.](https://api.flutter.dev/flutter/widgets/RotationTransition-class.html)

[Scale​Transition](https://api.flutter.dev/flutter/widgets/ScaleTransition-class.html)

[Animates the scale of transformed widget.](https://api.flutter.dev/flutter/widgets/ScaleTransition-class.html)

[Size​Transition](https://api.flutter.dev/flutter/widgets/SizeTransition-class.html)

[Animates its own size and clips and aligns the child.](https://api.flutter.dev/flutter/widgets/SizeTransition-class.html)

[Slide​Transition](https://api.flutter.dev/flutter/widgets/SlideTransition-class.html)

[Animates the position of a widget relative to its normal position.](https://api.flutter.dev/flutter/widgets/SlideTransition-class.html)

[Sliver​Fade​Transition](https://api.flutter.dev/flutter/widgets/SliverFadeTransition-class.html)

[Animates the opacity of a sliver widget.](https://api.flutter.dev/flutter/widgets/SliverFadeTransition-class.html)

Find more widgets in the [widget catalog](/ui/widgets).

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-09-29. [Report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/ui/widgets/animation "Report an issue with this page").

----
url: https://docs.flutter.dev/install/custom
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Set up Flutter for your particular needs](#site-content-title)

To get started developing with Flutter, follow these steps to install and set up Flutter for your preferred development environment and target platform.

If you plan to use VS Code or another Code - OSS derived editor like Antigravity, consider following the [Flutter quick start](/install/quick) instead.

## Install and set up Flutter

[#](#install)

To get started developing apps with Flutter, install the Flutter SDK to your development device. Choose one of the following installation methods:

[Install with VS Code Recommended](/install/with-vs-code)

[Use VS Code to quickly set up your Flutter development environment.](/install/with-vs-code)

[Install manually For specific releases](/install/manual)

[Manually install and set up a specific Flutter SDK release.](/install/manual)

## Set up an IDE or editor

[#](#editor)

For the best development experience with Flutter, install an IDE or editor with support for Dart and Flutter. Some popular options include VS Code, Antigravity, Android Studio, and other Code OSS-based editors.

[VS Code](/tools/vs-code#setup)

[Set up Flutter support in VS Code.](/tools/vs-code#setup)

[Android Studio](/tools/android-studio#setup)

[Set up Flutter support in Android Studio.](/tools/android-studio#setup)

[IntelliJ](/tools/android-studio#setup)

[Set up Flutter support in an IntelliJ-based IDE.](/tools/android-studio#setup)

## Set up a target platform

[#](#target-platform)

Once you've successfully installed Flutter, set up development for at least one target platform to continue your journey with Flutter.

We recommend [developing for the web](/platform-integration/web/setup) first as it requires no additional setup besides an appropriate browser. You can always set up development for additional target platforms later.

## Continue your Flutter journey

[#](#next-steps)

**Congratulations!** Now that you've installed Flutter, set up an IDE or editor, and set up development for a target platform, you can continue your Flutter learning journey.

Follow the [Flutter learning pathway](/learn/pathway), set up development for an [additional target platform](/platform-integration#setup), or explore some of these other learning resources.

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/install/custom.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/install/custom\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/install/custom.md "Report an issue with this page").

----
url: https://docs.flutter.dev/platform-integration/web/web-dev-config-file.md
----

\# Set up a web development configuration file > Centralize web development settings including a development proxy Flutter web includes a development server that defaults to serving your application in the \`localhost\` domain using HTTP on a randomly assigned port. While command-line arguments offer a quick way to modify the server's behavior, this document focuses on a more structured approach: defining your server's behavior through a centralized \`web\_dev\_config.yaml\` file. This configuration file allows you to customize server settings—host, port, HTTPS settings, and proxy rules—ensuring a consistent development environment. :::version-note Support for the \`web\_dev\_config.yaml\` file was added in Flutter 3.38. ::: ## Create a configuration file Add a \`web\_dev\_config.yaml\` file to the root directory of your Flutter project. If you haven't set up a Flutter project, visit \[Building a web application with Flutter]\[] to get started. \[Building a web application with Flutter]: /platform-integration/web/building ## Add configuration settings ### Basic server configuration You can define the host, port, and HTTPS settings for your development server. \`\`\`yaml title="web\_dev\_config.yaml" server: host: "0.0.0.0" # Defines the binding address port: 8080 # Specifies the port for the development server https: cert-path: "/path/to/cert.pem" # Path to your TLS certificate cert-key-path: "/path/to/key.pem" # Path to TLS certificate key \`\`\` ### Custom headers You can also inject custom HTTP headers into the development server's responses. \`\`\`yaml title="web\_dev\_config.yaml" server: headers: - name: "X-Custom-Header" # Name of the HTTP header value: "MyValue" # Value of the HTTP header - name: "Cache-Control" value: "no-cache, no-store, must-revalidate" \`\`\` ### Proxy configuration Requests are matched in order from the \`web\_dev\_config.yaml\` file. #### Basic string proxy Use the \`prefix\` field for simple path prefix matching. \`\`\`yaml title="web\_dev\_config.yaml" server: proxy: - target: "http\://localhost:5000/" # Base URL of your backend prefix: "/users/" # Path - target: "http\://localhost:3000/" prefix: "/data/" replace: "/report/" # Replacement of path in redirected URL (optional) - target: "http\://localhost:4000/" prefix: "/products/" replace: "" \`\`\` \*\*Explanation:\*\* \* A request to \`/users/names\` is forwarded to \`http\://localhost:5000/users/names\`. \* A request to \`/data/2023/\` is forwarded to \`http\://localhost:3000/report/2023\` because \`replace: “/report/”\` replaces the \`/data/\` prefix. \* A request to \`/products/item/123\` is forwarded to \`http\://localhost:4000/item/123\` because \`replace: ""\` removes the \`/products/\` prefix by replacing it with an empty string. #### Advanced regex proxy You can also use the \`regex\` field for more flexible and complex matching. \`\`\`yaml title="web\_dev\_config.yaml" server: proxy: - target: "http\://localhost:5000/" regex: "/users/(\d+)/$" # Path matches requests like /users/123/ - target: "http\://localhost:4000/" regex: "^/api/(v\d+)/(.\*)" # Matches requests like /api/v1/users replace: "/$2?apiVersion=$1" # Allows capture groups (optional) \`\`\` \*\*Explanation:\*\* \* A request to \`/users/123/\` matches the first rule exactly, so it is forwarded to \`http\://localhost:5000/users/123/\`. \* A request to \`/api/v1/users/profile/\` starts with the second rule path so it is forwarded to \`http\://localhost:4000/users/profile/?apiVersion=v1\`. ## Configuration precedence Remember the order of precedence for settings: 1. \*\*Command-line arguments\*\* (such as \`--web-hostname\`, \`--web-port\`) 2. \*\*\`web\_dev\_config.yaml\` settings\*\* 3. \*\*Built-in default values\*\*

----
url: https://docs.flutter.dev/reference/crash-reporting
----

If you have not opted-out of Flutter's analytics and crash reporting, when a `flutter` command crashes, it attempts to send a crash report to Google in order to help Google contribute improvements to Flutter over time. A crash report might contain the following information:

* The name and version of your local operating system.
* The version of Flutter used to run the command.
* The runtime type of the error, for example `StateError` or `NoSuchMethodError`.
* The stack trace generated by the crash, which contains references to the Flutter CLI's own code and contains no references to your application code.
* A client ID: a constant and unique number generated for the computer where Flutter is installed. It helps us deduplicate multiple identical crash reports coming from the same computer. It also helps us verify if a fix works as intended after you upgrade to the next version of Flutter.

Google handles all data reported by this tool in accordance with the [Google Privacy Policy](https://policies.google.com/privacy).

You may review the recently reported data in the `.dart-tool/dart-flutter-telemetry.log` file. On macOS or Linux, this log is located in the home directory (`~/`). On Windows, this log is located in the Roaming AppData directory (`%APPDATA%`).

## Disabling analytics reporting

[#](#disabling-analytics-reporting)

To opt out of anonymous crash reporting and feature usage statistics, run the following command:

```
$ flutter --disable-analytics
```

If you opt out of analytics, Flutter sends an opt-out event. This Flutter installation neither sends nor stores any further information.

To opt into analytics, run the following command:

```
$ flutter --enable-analytics
```

To display the current setting, run the following command:

```
$ flutter config
```

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/reference/crash-reporting.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/reference/crash-reporting\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/reference/crash-reporting.md "Report an issue with this page").

----
url: https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.12.2.md
----

\# DevTools 2.12.2 release notes > Release notes for Dart and Flutter DevTools version 2.12.2. The 2.12.2 release of the Dart and Flutter DevTools includes the following changes among other general improvements. To learn more about DevTools, check out the \[DevTools overview]\(https\://docs.flutter.dev/tools/devtools). ## General updates \* Recover from missing trace events - \[#3960]\(https\://github.com/flutter/devtools/pull/3960) ## Full commit history To find a complete list of changes since the previous release, check out \[the diff on GitHub]\(https\://github.com/flutter/devtools/compare/v2.12.1...v2.12.2).

----
url: https://docs.flutter.dev/cookbook/networking/authenticated-requests.md
----

\# Make authenticated requests > How to fetch authorized data from a web service.

<!--?code-excerpt path-base="cookbook/networking/authenticated_requests/"?-->

To fetch data from most web services, you need to provide authorization. There are many ways to do this, but perhaps the most common uses the \`Authorization\` HTTP header. ## Add authorization headers The \[\`http\`]\[] package provides a convenient way to add headers to your requests. Alternatively, use the \[\`HttpHeaders\`]\[] class from the \`dart:io\` library.

<!--?code-excerpt "lib/main.dart (get)"?-->

\`\`\`dart final response = await http.get( Uri.parse('https\://jsonplaceholder.typicode.com/albums/1'), // Send authorization headers to the backend. headers: {HttpHeaders.authorizationHeader: 'Basic your\_api\_token\_here'}, ); \`\`\` ## Complete example This example builds upon the \[Fetching data from the internet]\[] recipe.

<!--?code-excerpt "lib/main.dart"?-->

\`\`\`dart import 'dart:async'; import 'dart:convert'; import 'dart:io'; import 'package:http/http.dart' as http; Future fetchAlbum() async { final response = await http.get( Uri.parse('https\://jsonplaceholder.typicode.com/albums/1'), // Send authorization headers to the backend. headers: {HttpHeaders.authorizationHeader: 'Basic your\_api\_token\_here'}, ); final responseJson = jsonDecode(response.body) as Map; return Album.fromJson(responseJson); } class Album { final int userId; final int id; final String title; const Album({required this.userId, required this.id, required this.title}); factory Album.fromJson(Map json) { return switch (json) { {'userId': int userId, 'id': int id, 'title': String title} => Album( userId: userId, id: id, title: title, ), \_ => throw const FormatException('Failed to load album.'), }; } } \`\`\` \[Fetching data from the internet]: /cookbook/networking/fetch-data \[\`http\`]: https\://pub.dev/packages/http \[\`HttpHeaders\`]: https\://api.dart.dev/dart-io/HttpHeaders-class.html

----
url: https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.33.0
----

The 2.33.0 release of the Dart and Flutter DevTools includes the following changes among other general improvements. To learn more about DevTools, check out the [DevTools overview](/tools/devtools).

## General updates

[#](#general-updates)

* Improved overall usability by making the DevTools UI more dense. This significantly improves the user experience when using DevTools embedded in an IDE. - [#7030](https://github.com/flutter/devtools/pull/7030)
* Removed the "Dense mode" setting. - [#7086](https://github.com/flutter/devtools/pull/7086)
* Added support for filtering with regular expressions in the Logging, Network, and CPU profiler pages. - [#7027](https://github.com/flutter/devtools/pull/7027)
* Add a DevTools server interaction for getting the DTD URI. - [#7054](https://github.com/flutter/devtools/pull/7054), [#7164](https://github.com/flutter/devtools/pull/7164)
* Enabled expression evaluation with scope for the web, allowing evaluation of inspected widgets. - [#7144](https://github.com/flutter/devtools/pull/7144)
* Update `package:vm_service` constraint to `^14.0.0`. - [#6953](https://github.com/flutter/devtools/pull/6953)
* Onboarding DevTools to [`package:unified_analytics`](https://pub.dev/packages/unified_analytics) for unified telemetry logging across Flutter and Dart tooling. - [#7084](https://github.com/flutter/devtools/pull/7084)

## Debugger updates

[#](#debugger-updates)

* Fixed off by one error causing profiler hits to be rendered on the wrong lines. - [#7178](https://github.com/flutter/devtools/pull/7178)
* Improved contrast of line numbers when displaying code coverage hits in dark mode. - [#7178](https://github.com/flutter/devtools/pull/7178)
* Improved contrast of profiling details when displaying profiler hits in dark mode. - [#7178](https://github.com/flutter/devtools/pull/7178)
* Fixed syntax highlighting for comments when the source file uses `\r\n` line endings [#7190](https://github.com/flutter/devtools/pull/7190)
* Re-establish breakpoints after a hot-restart. - [#7205](https://github.com/flutter/devtools/pull/7205)

## VS Code Sidebar updates

[#](#vs-code-sidebar-updates)

* Do not show DevTools release notes in the Flutter sidebar. - [#7166](https://github.com/flutter/devtools/pull/7166)

## DevTools Extension updates

[#](#devtools-extension-updates)

* Added support for connecting to the Dart Tooling Daemon from the simulated DevTools environment. - [#7133](https://github.com/flutter/devtools/pull/7133)
* Added help buttons to the VM Service and DTD connection text fields in the simulated DevTools environment. - [#7133](https://github.com/flutter/devtools/pull/7133)
* Fixed an issue with not detecting extensions for test files in subdirectories. - [#7174](https://github.com/flutter/devtools/pull/7174)
* Added an example of creating an extension for a pure Dart package. - [#7196](https://github.com/flutter/devtools/pull/7196)
* Updated the `README.md` and `example/README.md` with more complete documentation. - [#7237](https://github.com/flutter/devtools/pull/7237), [#7261](https://github.com/flutter/devtools/pull/7261)
* Added a `devtools_extensions validate` command to validate extension requirements during development. - [#7257](https://github.com/flutter/devtools/pull/7257)

## Full commit history

[#](#full-commit-history)

To find a complete list of changes in this release, check out the [DevTools git log](https://github.com/flutter/devtools/tree/v2.33.0).

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/release-notes/release-notes-2.33.0.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.33.0\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/release-notes/release-notes-2.33.0.md "Report an issue with this page").

----
url: https://docs.flutter.dev/cookbook/animation
----

[Animate the properties of a container](/cookbook/animation/animated-container)

[How to animate properties of a container using implicit animations.](/cookbook/animation/animated-container)

[Animate a page route transition](/cookbook/animation/page-route-animation)

[How to animate from one page to another.](/cookbook/animation/page-route-animation)

[Fade a widget in and out](/cookbook/animation/opacity-animation)

[How to fade a widget in and out.](/cookbook/animation/opacity-animation)

[Animate a widget using a physics simulation](/cookbook/animation/physics-simulation)

[How to implement a physics animation.](/cookbook/animation/physics-simulation)

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/animation/index.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/cookbook/animation\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/animation/index.md "Report an issue with this page").

----
url: https://docs.flutter.dev/platform-integration/android/restore-state-android.md
----

\# Restore state on Android > How to restore the state of your Android app after it's been killed by the OS. When a user runs a mobile app and then selects another app to run, the first app is moved to the background, or \_backgrounded\_. The operating system (both iOS and Android) might kill the backgrounded app to release memory and improve performance for the app running in the foreground. When the user selects the app again, bringing it back to the foreground, the OS relaunches it. But, unless you've set up a way to save the state of the app before it was killed, you've lost the state and the app starts from scratch. The user has lost the continuity they expect, which is clearly not ideal. (Imagine filling out a lengthy form and being interrupted by a phone call \_before\_ clicking \*\*Submit\*\*.) So, how can you restore the state of the app so that it looks like it did before it was sent to the background? Flutter has a solution for this with the \[\`RestorationManager\`]\[] (and related classes) in the \[services]\[] library. With the \`RestorationManager\`, the Flutter framework provides the state data to the engine \_as the state changes\_, so that the app is ready when the OS signals that it's about to kill the app, giving the app only moments to prepare. :::secondary Instance state vs long-lived state When should you use the \`RestorationManager\` and when should you save state to long term storage? \_Instance state\_ (also called \_short-term\_ or \_ephemeral\_ state), includes unsubmitted form field values, the currently selected tab, and so on. On Android, this is limited to 1 MB and, if the app exceeds this, it crashes with a \`TransactionTooLargeException\` error in the native code. ::: \[state]: /data-and-backend/state-mgmt/ephemeral-vs-app ## Overview You can enable state restoration with just a few tasks: 1. Define a \`restorationScopeId\` for classes like \`CupertinoApp\`, \`MaterialApp\`, or \`WidgetsApp\`. 2. Define a \`restorationId\` for widgets that support it, such as \[\`TextField\`]\[] and \[\`ScrollView\`]\[]. This automatically enables built-in state restoration for those widgets. 3. For custom widgets, you must decide what state you want to restore and hold that state in a \[\`RestorableProperty\`]\[]. (The Flutter API provides various subclasses for different data types.) Define those \`RestorableProperty\` widgets in a \`State\` class that uses the \[\`RestorationMixin\`]\[]. Register those widgets with the mixin in a \`restoreState\` method. 4. If you use any Navigator API (like \`push\`, \`pushNamed\`, and so on) migrate to the API that has "restorable" in the name (\`restorablePush\`, \`restorablePushNamed\`, and so on) to restore the navigation stack. Other considerations: \* Providing a \`restorationScopeId\` to \`MaterialApp\`, \`CupertinoApp\`, or \`WidgetsApp\` automatically enables state restoration by injecting a \`RootRestorationScope\`. If you need to restore state \_above\_ the app class, inject a \`RootRestorationScope\` manually. \* \*\*The difference between a \`restorationId\` and a \`restorationScopeId\`:\*\* Widgets that take a \`restorationScopeId\` create a new \`restorationScope\` (a new \`RestorationBucket\`) into which all children store their state. A \`restorationId\` means the widget (and its children) store the data in the surrounding bucket. \[a bit of extra setup]: https\://api.flutter.dev/flutter/services/RestorationManager-class.html#state-restoration-on-ios \[\`restorationId\`]: https\://api.flutter.dev/flutter/widgets/RestorationScope/restorationId.html \[\`restorationScopeId\`]: https\://api.flutter.dev/flutter/widgets/RestorationScope/restorationScopeId.html \[\`RestorationMixin\`]: https\://api.flutter.dev/flutter/widgets/RestorationMixin-mixin.html \[\`RestorationScope\`]: https\://api.flutter.dev/flutter/widgets/RestorationScope-class.html \[\`restoreState\`]: https\://api.flutter.dev/flutter/widgets/RestorationMixin/restoreState.html \[VeggieSeasons]: https\://github.com/flutter/demos/tree/main/veggieseasons ## Restoring navigation state If you want your app to return to a particular route that the user was most recently viewing (the shopping cart, for example), then you must implement restoration state for navigation, as well. If you use the Navigator API directly, migrate the standard methods to restorable methods (that have "restorable" in the name). For example, replace \`push\` with \[\`restorablePush\`]\[]. ## Testing state restoration To test state restoration, set up your mobile device so that it doesn't save state once an app is backgrounded. To learn how to do this for both iOS and Android, check out \[Testing state restoration]\[] on the \[\`RestorationManager\`]\[] page. :::warning Don't forget to reenable storing state on your device once you are finished with testing! ::: \[Testing state restoration]: https\://api.flutter.dev/flutter/services/RestorationManager-class.html#testing-state-restoration \[\`RestorationBucket\`]: https\://api.flutter.dev/flutter/services/RestorationBucket-class.html \[\`RestorationManager\`]: https\://api.flutter.dev/flutter/services/RestorationManager-class.html \[services]: https\://api.flutter.dev/flutter/services/services-library.html ## Other resources For further information on state restoration, check out the following resources. \* To learn more about short term and long term state, check out \[Differentiate between ephemeral state and app state]\[state]. \* For more information on navigation and the \[\`go\_router\`]\[] package, check out \[Navigation and routing]\[] and the \[State restoration]\[] topic on pub.dev. \[\`go\_router\`]: https\://pub.dev/packages/go\_router \[State restoration]: https\://pub.dev/documentation/go\_router/latest/topics/State%20restoration-topic.html \[Navigation and routing]: /ui/navigation \[\`RestorableProperty\`]: https\://api.flutter.dev/flutter/widgets/RestorableProperty-class.html \[\`restorablePush\`]: https\://api.flutter.dev/flutter/widgets/Navigator/restorablePush.html \[\`ScrollView\`]: https\://api.flutter.dev/flutter/widgets/ScrollView/restorationId.html \[\`TextField\`]: https\://pub.dev/documentation/material\_ui/latest/material\_ui/TextField/restorationId.html

----
url: https://docs.flutter.dev/ui/widgets/layout
----

Arrange other widgets columns, rows, grids, and many other layouts.

## Single-child layout widgets

[Align](https://api.flutter.dev/flutter/widgets/Align-class.html)

[A widget that aligns its child within itself and optionally sizes itself based on the child's size.](https://api.flutter.dev/flutter/widgets/Align-class.html)

[Aspect​Ratio](https://api.flutter.dev/flutter/widgets/AspectRatio-class.html)

[A widget that attempts to size the child to a specific aspect ratio.](https://api.flutter.dev/flutter/widgets/AspectRatio-class.html)

[Baseline](https://api.flutter.dev/flutter/widgets/Baseline-class.html)

[Container that positions its child according to the child's baseline.](https://api.flutter.dev/flutter/widgets/Baseline-class.html)

[Center](https://api.flutter.dev/flutter/widgets/Center-class.html)

[Alignment block that centers its child within itself.](https://api.flutter.dev/flutter/widgets/Center-class.html)

[Constrained​Box](https://api.flutter.dev/flutter/widgets/ConstrainedBox-class.html)

[A widget that imposes additional constraints on its child.](https://api.flutter.dev/flutter/widgets/ConstrainedBox-class.html)

[Container](https://api.flutter.dev/flutter/widgets/Container-class.html)

[A convenience widget that combines common painting, positioning, and sizing widgets.](https://api.flutter.dev/flutter/widgets/Container-class.html)

[Custom​Single​Child​Layout](https://api.flutter.dev/flutter/widgets/CustomSingleChildLayout-class.html)

[A widget that defers the layout of its single child to a delegate.](https://api.flutter.dev/flutter/widgets/CustomSingleChildLayout-class.html)

[Expanded](https://api.flutter.dev/flutter/widgets/Expanded-class.html)

[A widget that expands a child of a Row, Column, or Flex.](https://api.flutter.dev/flutter/widgets/Expanded-class.html)

[Fitted​Box](https://api.flutter.dev/flutter/widgets/FittedBox-class.html)

[Scales and positions its child within itself according to fit.](https://api.flutter.dev/flutter/widgets/FittedBox-class.html)

[Fractionally​Sized​Box](https://api.flutter.dev/flutter/widgets/FractionallySizedBox-class.html)

[A widget that sizes its child to a fraction of the total available space. For more details about the layout algorithm, see RenderFractionallySizedOverflowBox.](https://api.flutter.dev/flutter/widgets/FractionallySizedBox-class.html)

[Intrinsic​Height](https://api.flutter.dev/flutter/widgets/IntrinsicHeight-class.html)

[A widget that sizes its child to the child's intrinsic height.](https://api.flutter.dev/flutter/widgets/IntrinsicHeight-class.html)

[Intrinsic​Width](https://api.flutter.dev/flutter/widgets/IntrinsicWidth-class.html)

[A widget that sizes its child to the child's intrinsic width.](https://api.flutter.dev/flutter/widgets/IntrinsicWidth-class.html)

[Limited​Box](https://api.flutter.dev/flutter/widgets/LimitedBox-class.html)

[A box that limits its size only when it's unconstrained.](https://api.flutter.dev/flutter/widgets/LimitedBox-class.html)

[Offstage](https://api.flutter.dev/flutter/widgets/Offstage-class.html)

[A widget that lays the child out as if it was in the tree, but without painting anything, without making the child available for hit...](https://api.flutter.dev/flutter/widgets/Offstage-class.html)

[Overflow​Box](https://api.flutter.dev/flutter/widgets/OverflowBox-class.html)

[A widget that imposes different constraints on its child than it gets from its parent, possibly allowing the child to overflow the parent.](https://api.flutter.dev/flutter/widgets/OverflowBox-class.html)

[Padding](https://api.flutter.dev/flutter/widgets/Padding-class.html)

[A widget that insets its child by the given padding.](https://api.flutter.dev/flutter/widgets/Padding-class.html)

[Sized​Box](https://api.flutter.dev/flutter/widgets/SizedBox-class.html)

[A box with a specified size. If given a child, this widget forces its child to have a specific width and/or height (assuming values are...](https://api.flutter.dev/flutter/widgets/SizedBox-class.html)

[Sized​Overflow​Box](https://api.flutter.dev/flutter/widgets/SizedOverflowBox-class.html)

[A widget that is a specific size but passes its original constraints through to its child, which will probably overflow.](https://api.flutter.dev/flutter/widgets/SizedOverflowBox-class.html)

[Transform](https://api.flutter.dev/flutter/widgets/Transform-class.html)

[A widget that applies a transformation before painting its child.](https://api.flutter.dev/flutter/widgets/Transform-class.html)

## Multi-child layout widgets

[Column](https://api.flutter.dev/flutter/widgets/Column-class.html)

[Layout a list of child widgets in the vertical direction.](https://api.flutter.dev/flutter/widgets/Column-class.html)

[Custom​Multi​Child​Layout](https://api.flutter.dev/flutter/widgets/CustomMultiChildLayout-class.html)

[A widget that uses a delegate to size and position multiple children.](https://api.flutter.dev/flutter/widgets/CustomMultiChildLayout-class.html)

[Carousel​View](https://pub.dev/documentation/material_ui/latest/material_ui/CarouselView-class.html)

[A Material carousel widget that presents a scrollable list of items, each of which can dynamically change size based on the chosen layout.](https://pub.dev/documentation/material_ui/latest/material_ui/CarouselView-class.html)

[Flow](https://api.flutter.dev/flutter/widgets/Flow-class.html)

[A widget that implements the flow layout algorithm.](https://api.flutter.dev/flutter/widgets/Flow-class.html)

[Grid​View](https://api.flutter.dev/flutter/widgets/GridView-class.html)

[A grid list consists of a repeated pattern of cells arrayed in a vertical and horizontal layout. The GridView widget implements this component.](https://api.flutter.dev/flutter/widgets/GridView-class.html)

[Indexed​Stack](https://api.flutter.dev/flutter/widgets/IndexedStack-class.html)

[A Stack that shows a single child from a list of children.](https://api.flutter.dev/flutter/widgets/IndexedStack-class.html)

[Layout​Builder](https://api.flutter.dev/flutter/widgets/LayoutBuilder-class.html)

[Builds a widget tree that can depend on the parent widget's size.](https://api.flutter.dev/flutter/widgets/LayoutBuilder-class.html)

[List​Body](https://api.flutter.dev/flutter/widgets/ListBody-class.html)

[A widget that arranges its children sequentially along a given axis, forcing them to the dimension of the parent in the other axis.](https://api.flutter.dev/flutter/widgets/ListBody-class.html)

[List​View](https://api.flutter.dev/flutter/widgets/ListView-class.html)

[A scrollable, linear list of widgets. ListView is the most commonly used scrolling widget. It displays its children one after another in the scroll direction....](https://api.flutter.dev/flutter/widgets/ListView-class.html)

[Overflow​Bar](https://api.flutter.dev/flutter/widgets/OverflowBar-class.html)

[A widget that lays out its children in a row unless they "overflow" the available horizontal space, in which case it lays them out in...](https://api.flutter.dev/flutter/widgets/OverflowBar-class.html)

[Row](https://api.flutter.dev/flutter/widgets/Row-class.html)

[Layout a list of child widgets in the horizontal direction.](https://api.flutter.dev/flutter/widgets/Row-class.html)

[Stack](https://api.flutter.dev/flutter/widgets/Stack-class.html)

[This class is useful if you want to overlap several children in a simple way, for example having some text and an image, overlaid with...](https://api.flutter.dev/flutter/widgets/Stack-class.html)

[Table](https://api.flutter.dev/flutter/widgets/Table-class.html)

[Displays child widgets in rows and columns.](https://api.flutter.dev/flutter/widgets/Table-class.html)

[Wrap](https://api.flutter.dev/flutter/widgets/Wrap-class.html)

[A widget that displays its children in multiple horizontal or vertical runs.](https://api.flutter.dev/flutter/widgets/Wrap-class.html)

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-09-29. [Report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/ui/widgets/layout "Report an issue with this page").

----
url: https://docs.flutter.dev/platform-integration/linux/setup.md
----

\# Set up Linux development > Configure your development environment to run, build, and deploy Flutter apps for Linux desktop. Learn how to set up your development environment to run, build, and deploy Flutter apps for the Linux desktop platform. :::note If you haven't set up Flutter already, visit and follow \[Install Flutter]\[] first. If you've already installed Flutter, ensure that it's \[up to date]\[]. ::: \[Install Flutter]: /install \[up to date]: /install/upgrade ## Set up tooling {: #set-up-tooling} To run and debug desktop Flutter apps on Linux, download and install the prerequisite packages. Using your preferred package manager or mechanism, install the latest versions of the following packages: - \`clang\` - \`cmake\` - \`ninja-build\` - \`pkg-config\` - \`libgtk-3-dev\` - \`libstdc++-12-dev\` On Debian-based distros with \`apt-get\`, such as Ubuntu, install these packages using the following commands: \`\`\`console $ sudo apt-get update -y && sudo apt-get upgrade -y $ sudo apt-get install -y clang cmake ninja-build pkg-config libgtk-3-dev libstdc++-12-dev \`\`\` ## Validate your setup {: #validate-setup} 1.

### Check for toolchain issues

To check for any issues with your Linux development setup, run the \`flutter doctor\` command in your preferred terminal: \`\`\`console $ flutter doctor -v \`\`\` If you see any errors or tasks to complete under the \*\*Linux toolchain\*\* section, complete and resolve them, then run \`flutter doctor -v\` again to verify any changes. 1.

### Check for Linux devices

To ensure Flutter can find and connect to your Linux device correctly, run \`flutter devices\` in your preferred terminal: \`\`\`console $ flutter devices \`\`\` If you set everything up correctly, there should be at least one entry with the platform marked as \*\*linux\*\*. 1.

### Troubleshoot setup issues

If you need help resolving any setup issues, check out \[Install and setup troubleshooting]\[]. If you still have issues or questions, reach out on one of the Flutter \[community]\[] channels. {: .steps} \[Install and setup troubleshooting]: /install/troubleshoot \[community]: https\://flutter.dev/community ## Start developing for Linux {: #start-developing} Congratulations! Now that you've set up Linux desktop development for Flutter, you can continue your Flutter learning journey while testing on Linux or begin expanding integration with Linux.

----
url: https://docs.flutter.dev/install
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Install Flutter](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

Ready to build beautiful, multiplatform apps from a single codebase? Choose from the following two options to get started setting up your Flutter development environment.

[rocket\_launch Quick start Recommended](/install/quick)

[Use VS Code or another Code OSS-based editor to quickly install, set up, and try out Flutter development!](/install/quick)

[tune Custom setup](/install/custom)

[Install the Flutter SDK, set up any initial target platform, and get started learning and developing with Flutter!](/install/custom)

## Try Flutter

[#](#try)

You can quickly try Flutter online without any local setup.

[DartPad open\_in\_new](https://dartpad.dev)

[Quickly build and run simple single-file Flutter apps on the web.](https://dartpad.dev)

[]()

## Update Flutter

[#](#update)

If you already have Flutter installed and would like to upgrade your Flutter SDK installation or change to a different release channel, check out [Upgrading Flutter](/install/upgrade).

When upgrading, also remember to check out the published list of [breaking changes and migration guides](/release/breaking-changes).

## Download previous releases

[#](#previous-releases)

If you're looking to download and install previous releases of Flutter, check out the [SDK archive](/install/archive).

## Troubleshoot and uninstall Flutter

[#](#uninstall)

Guides to help you resolve common issues with your Flutter development environment and uninstall Flutter.

[Add Flutter to path](/install/add-to-path)

[Using Flutter on the command line requires that the Flutter SDK is added to your system's \`PATH\` environment variable.](/install/add-to-path)

[Troubleshoot SDK](/install/troubleshoot)

[Use this guide to resolve common issues with your Flutter development environment.](/install/troubleshoot)

[Uninstall SDK](/install/uninstall)

[Uninstall Flutter from your system.](/install/uninstall)

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/install/index.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/install\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/install/index.md "Report an issue with this page").

----
url: https://docs.flutter.dev/install/manual
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Install Flutter manually](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

Learn how to install and manually set up your Flutter development environment.

## Choose your development platform

[#](#dev-platform)

The instructions on this page are configured to cover installing Flutter on a **Windows** device.

If you'd like to follow the instructions for a different OS, please select one of the following.

## Download prerequisite software

[#](#download-prerequisites)

Before installing the Flutter SDK, first complete the following setup.

1. ### Install Git for Windows

   Download and install the latest version of [Git for Windows](https://git-scm.com/downloads/win).

   For help installing or troubleshooting Git, reference the [Git documentation](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git).

2. ### Set up an editor or IDE

   For the best experience developing Flutter apps, consider installing and setting up an [editor or IDE with Flutter support](/tools/editors).

1) ### Install the Xcode command-line tools

   Download the Xcode command-line tools to get access to the command-line tools that Flutter relies on, including Git.

   To download the tools, run the following command in your preferred terminal:

   ```
   $ xcode-select --install
   ```

   If you haven't installed the tools already, a dialog should open that confirms you'd like to install them. Click **Install**, then once the installation is complete, click **Done**.

2) ### Set up an editor or IDE

   For the best experience developing Flutter apps, consider installing and setting up an [editor or IDE with Flutter support](/tools/editors).

1. ### Download and install prerequisite packages

   ```
   $ sudo apt-get update -y && sudo apt-get upgrade -y
   $ sudo apt-get install -y curl git unzip xz-utils zip libglu1-mesa
   ```

2. ### Set up an editor or IDE

   For the best experience developing Flutter apps, consider installing and setting up an [editor or IDE with Flutter support](/tools/editors).

1) ### Set up Linux support

   If you haven't set up Linux support on your Chromebook before, [Turn on Linux support](https://support.google.com/chromebook/answer/9145439).

   If you've already turned on Linux support, ensure it's up to date following the [Fix problems with Linux](https://support.google.com/chromebook/answer/9145439?hl=en#:~:text=Fix%20problems%20with%20Linux) instructions.

2) ### Download and install prerequisite packages

   ```
   $ sudo apt-get update -y && sudo apt-get upgrade -y
   $ sudo apt-get install -y curl git unzip xz-utils zip libglu1-mesa
   ```

3) ### Set up an editor or IDE

   For the best experience developing Flutter apps, consider installing and setting up an [editor or IDE with Flutter support](/tools/editors).

## Install and set up Flutter

[#](#install-flutter)

To install the Flutter SDK, download the latest bundle from the SDK archive, then extract the SDK to where you want it stored.

1. ### Download the Flutter SDK bundle

   Download the following installation bundle to get the latest stable release of the Flutter SDK.

   [(loading...)](#)

2. ### Create a folder to store the SDK

   Create or find a folder to store the extracted SDK in. Consider creating and using a directory at `%USERPROFILE%\develop` (`C:\Users\{username}\develop`).

3. ### Extract the SDK

   Extract the SDK bundle you downloaded into the directory you want to store the Flutter SDK in.

   * [PowerShell](#30-tab-panel)
   * [Command prompt](#31-tab-panel)

   1. Copy the following command.

   2. Replace `<sdk_zip_path>` with the path to the bundle you downloaded.

   3. Replace `<destination_directory_path>` with the path to the folder you want the extracted SDK to be in.

   4. Run the edited command in PowerShell:

      powershell

      ```
      Expand-Archive -Path <sdk_zip_path> -DestinationPath <destination_directory_path>
      ```

   For example, if you downloaded the bundle for Flutter 3.47.5 into the `$env:USERPROFILE\Downloads` directory and want to store the extracted SDK in the `$env:USERPROFILE\develop` directory:

   powershell

   ```
   Expand-Archive `
     -Path "$env:USERPROFILE\Downloads\flutter_windows_3.47.5-stable.zip" `
     -DestinationPath "$env:USERPROFILE\develop"
   ```

   1. Copy the following command.

   2. Replace `<sdk_zip_path>` with the path to the bundle you downloaded.

   3. Replace `<destination_directory_path>` with the path to the folder you want the extracted SDK to be in.

   4. Run the edited command in Command prompt:

      bat

      ```
      tar -xf <sdk_zip_path> -C <destination_directory_path>
      ```

   For example, if you downloaded the bundle for Flutter 3.47.5 into the `%USERPROFILE%\Downloads` directory and want to store the extracted SDK in the `%USERPROFILE%\develop` directory:

   bat

   ```
   tar -xf "%USERPROFILE%\Downloads\flutter_windows_3.47.5-stable.zip" -C "%USERPROFILE%\develop"
   ```

1) ### Download the Flutter SDK bundle

   To choose the correct bundle, you need to know your Mac's processor type. *(To check, go to **Apple menu () > About This Mac** and look at the **Processor** or **Chip** section.)*

   Depending on your macOS device's cpu architecture, download one of the following installation bundles to get the latest stable release of the Flutter SDK.

   | Apple Silicon (ARM64) | Intel             |
   | --------------------- | ----------------- |
   | [(loading...)](#)     | [(loading...)](#) |

2) ### Create a folder to store the SDK

   Create or find a folder to store the extracted SDK in. Consider creating and using a directory at `~/develop/`.

3) ### Extract the SDK

   Extract the SDK bundle you downloaded into the directory you want to store the Flutter SDK in.

   1. Copy the following command.
   2. Replace `<sdk_zip_path>` with the path to the bundle you downloaded.
   3. Replace `<destination_directory_path>` with the path to the folder you want the extracted SDK to be in.
   4. Run the edited command in your preferred terminal.

   ```
   $ unzip <sdk_zip_path> -d <destination_directory_path>
   ```

   For example, if you downloaded the bundle for Flutter 3.47.5 into the `~/Downloads` directory and want to store the extracted SDK in the `~/develop` directory:

   ```
   $ unzip ~/Downloads/flutter_macos_3.47.5-stable.zip -d ~/develop/
   ```

1. ### Download the Flutter SDK bundle

   Download the following installation bundle to get the latest stable release of the Flutter SDK.

   [(loading...)](#)

2. ### Create a folder to store the SDK

   Create or find a folder to store the extracted SDK in. Consider creating and using a directory at `~/develop/`.

3. ### Extract the SDK

   Extract the SDK bundle you downloaded into the directory you want to store the Flutter SDK in.

   1. Copy the following command.
   2. Replace `<sdk_zip_path>` with the path to the bundle you downloaded.
   3. Replace `<destination_directory_path>` with the path to the folder you want the extracted SDK to be in.
   4. Run the edited command in your preferred terminal.

   ```
   $ tar -xf <sdk_zip_path> -C <destination_directory_path>
   ```

   For example, if you downloaded the bundle for Flutter 3.47.5 into the `~/Downloads` directory and want to store the extracted SDK in the `~/develop` directory:

   ```
   $ tar -xf ~/Downloads/flutter_linux_3.47.5-stable.tar.xz -C ~/develop/
   ```

## Add Flutter to your PATH

[#](#add-to-path)

Now that you've downloaded the SDK, add the Flutter SDK's `bin` directory to your `PATH` environment variable. Adding Flutter to your `PATH` allows you to use the `flutter` and `dart` command-line tools in terminals and IDEs.

1. ### Determine your Flutter SDK installation location

   Copy the absolute path to the directory that you downloaded and extracted the Flutter SDK into.

2. ### Navigate to the environment variables settings

   1. Press `Windows` + `Pause`.

      If your keyboard lacks a `Pause` key, try `Windows` + `Fn` + `B`.

      The **System > About** dialog opens.

   2. Click **Advanced System Settings** > **Advanced** > **Environment Variables...**.

      The **Environment Variables** dialog opens.

3. ### Add the Flutter SDK bin to your path

   1. In the **User variables for (username)** section of the **Environment Variables** dialog, look for the **Path** entry.

   2. If the **Path** entry exists, double-click it.

      The **Edit Environment Variable** dialog should open.

      1. Double-click inside an empty row.

      2. Type the path to the `bin` directory of your Flutter installation.

         For example, if you downloaded Flutter into a `develop\flutter` folder inside your user directory, you'd type the following:

         ```
         %USERPROFILE%\develop\flutter\bin
         ```

      3. Click the Flutter entry you added to select it.

      4. Click **Move Up** until the Flutter entry sits at the top of the list.

      5. To confirm your changes, click **OK** three times.

   3. If the entry doesn't exist, click **New\...**.

      The **Edit Environment Variable** dialog should open.

      1. In the **Variable Name** box, type `Path`.

      2. In the **Variable Value** box, type the path to the `bin` directory of your Flutter installation.

         For example, if you downloaded Flutter into a `develop\flutter` folder inside your user directory, you'd type the following:

         ```
         %USERPROFILE%\develop\flutter\bin
         ```

      3. To confirm your changes, click **OK** three times.

4. ### Apply your changes

   To apply this change and get access to the `flutter` tool, close and reopen all open command prompts, sessions in your terminal apps, and IDEs.

5. ### Validate your setup

   To ensure you successfully added the SDK to your `PATH`, open command prompt or your preferred terminal app, then try running the `flutter` and `dart` tools.

   ```
   $ flutter --version
   $ dart --version
   ```

   If either command isn't found, check out [Flutter installation troubleshooting](/install/troubleshoot).

1) ### Determine your Flutter SDK installation location

   Copy the absolute path to the directory that you downloaded and extracted the Flutter SDK into.

2) ### Open or create the Zsh environment variable file

   If it exists, open the [Zsh environment variable file](https://zsh.sourceforge.io/Intro/intro_3.html) `~/.zprofile` in your preferred text editor. If it doesn't exist, create the `~/.zprofile` file.

3) ### Add the Flutter SDK bin to your path

   At the end of your `~/.zprofile` file, use the built-in `export` command to update the `PATH` variable to include the `bin` directory of your Flutter installation.

   Replace `<path-to-sdk>` with the path to your Flutter SDK installation.

   bash

   ```
   export PATH="<path-to-sdk>/bin:$PATH"
   ```

   For example, if you downloaded Flutter into a `develop/flutter` folder inside your user directory, you'd add the following to the file:

   bash

   ```
   export PATH="$HOME/develop/flutter/bin:$PATH"
   ```

4) ### Save your changes

   Save, then close, the `~/.zprofile` file you edited.

5) ### Apply your changes

   To apply this change and get access to the `flutter` tool, close and reopen all open Zsh sessions in your terminal apps and IDEs.

6) ### Validate your setup

   To ensure you successfully added the SDK to your `PATH`, open a Zsh session in your preferred terminal, then try running the `flutter` and `dart` tools.

   ```
   $ flutter --version
   $ dart --version
   ```

   If either command isn't found, check out [Flutter installation troubleshooting](/install/troubleshoot).

1. ### Determine your Flutter SDK installation location

   Copy the absolute path to the directory that you downloaded and extracted the Flutter SDK into.

2. ### Determine your default shell

   If you don't know what shell you use, check which shell starts when you open a new console window.

   ```
   $ echo $SHELL
   ```

3. ### Add the Flutter SDK bin to your path

   To add the `bin` directory of your Flutter installation to your `PATH`:

   1. Expand the instructions for your default shell.
   2. Copy the provided command.
   3. Replace `<path-to-sdk>` with the path to your Flutter SDK install.
   4. Run the edited command in your preferred terminal with that shell.

   ***

   Expand for `bash` instructions

   ```
   $ echo 'export PATH="<path-to-sdk>/bin:$PATH"' >> ~/.bashrc
   ```

   For example, if you downloaded Flutter into a `develop/flutter` folder inside your user directory, you'd run the following:

   ```
   $ echo 'export PATH="$HOME/develop/flutter/bin:$PATH"' >> ~/.bashrc
   ```

   Expand for `zsh` instructions

   ```
   $ echo 'export PATH="<path-to-sdk>/bin:$PATH"' >> ~/.zshenv
   ```

   For example, if you downloaded Flutter into a `develop/flutter` folder inside your user directory, you'd run the following:

   ```
   $ echo 'export PATH="$HOME/develop/flutter/bin:$PATH"' >> ~/.zshenv
   ```

   Expand for `fish` instructions

   ```
   $ fish_add_path -U -p <path-to-sdk>/bin
   ```

   For example, if you downloaded Flutter into a `develop/flutter` folder inside your user directory, you'd run the following:

   ```
   $ fish_add_path -U -p ~/develop/flutter/bin
   ```

   Expand for `csh` instructions

   ```
   $ echo 'setenv PATH "<path-to-sdk>/bin:$PATH"' >> ~/.cshrc
   ```

   For example, if you downloaded Flutter into a `develop/flutter` folder inside your user directory, you'd run the following:

   ```
   $ echo 'setenv PATH "$HOME/develop/flutter/bin:$PATH"' >> ~/.cshrc
   ```

   Expand for `tcsh` instructions

   ```
   $ echo 'setenv PATH "<path-to-sdk>/bin:$PATH"' >> ~/.tcshrc
   ```

   For example, if you downloaded Flutter into a `develop/flutter` folder inside your user directory, you'd run the following:

   ```
   $ echo 'setenv PATH "$HOME/develop/flutter/bin:$PATH"' >> ~/.tcshrc
   ```

   Expand for `ksh` instructions

   ```
   $ echo 'export PATH="<path-to-sdk>/bin:$PATH"' >> ~/.profile
   ```

   For example, if you downloaded Flutter into a `develop/flutter` folder inside your user directory, you'd run the following:

   ```
   $ echo 'export PATH="$HOME/develop/flutter/bin:$PATH"' >> ~/.profile
   ```

   Expand for `sh` instructions

   ```
   $ echo 'export PATH="<path-to-sdk>/bin:$PATH"' >> ~/.profile
   ```

   For example, if you downloaded Flutter into a `develop/flutter` folder inside your user directory, you'd run the following:

   ```
   $ echo 'export PATH="$HOME/develop/flutter/bin:$PATH"' >> ~/.profile
   ```

4. ### Apply your changes

   To apply this change and get access to the `flutter` tool, close and reopen all open shell sessions in your terminal apps and IDEs.

5. ### Validate your setup

   To ensure you successfully added the SDK to your `PATH`, open your preferred terminal with your default shell, then try running the `flutter` and `dart` tools.

   ```
   $ flutter --version
   $ dart --version
   ```

   If either command isn't found, check out [Flutter installation troubleshooting](/install/troubleshoot).

1) ### Determine your Flutter SDK installation location

   Copy the absolute path to the directory that you downloaded and extracted the Flutter SDK into.

2) ### Add the Flutter SDK bin to your path

   To add the `bin` directory of your Flutter installation to your `PATH`:

   1. Copy the following command.
   2. Replace `<path-to-sdk>` with the path to your Flutter SDK install.
   3. Run the edited command in your preferred terminal.

   ```
   $ echo 'export PATH="<path-to-sdk>:$PATH"' >> ~/.bash_profile
   ```

   For example, if you downloaded Flutter into a `develop/flutter` folder inside your user directory, you'd run the following:

   ```
   $ echo 'export PATH="$HOME/develop/flutter/bin:$PATH"' >> ~/.bash_profile
   ```

3) ### Apply your changes

   To apply this change and get access to the `flutter` tool, close and reopen all open Zsh sessions in your terminal apps and IDEs.

4) ### Validate your setup

   To ensure you successfully added the SDK to your `PATH`, open a Zsh session in your preferred terminal, then try running the `flutter` and `dart` tools.

   ```
   $ flutter --version
   $ dart --version
   ```

   If either command isn't found, check out [Flutter installation troubleshooting](/install/troubleshoot).

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-10-01. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/install/manual.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/install/manual\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/install/manual.md "Report an issue with this page").

----
url: https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.51.0.md
----

\# DevTools 2.51.0 release notes > Release notes for Dart and Flutter DevTools version 2.51.0. The 2.51.0 release of the Dart and Flutter DevTools includes the following changes among other general improvements. To learn more about DevTools, check out the \[DevTools overview]\(/tools/devtools/overview). ## General updates - Flutter beta channel users were opted into the DevTools-on-Wasm experiment. All other users can still enable the Wasm-compiled DevTools from the settings dialog. - \[#9455]\(https\://github.com/flutter/devtools/pull/9455) ## Inspector updates - Fixed an issue where selecting a widget with the Inspector would open the widget definition file instead of the user's project file. - \[#176530]\(https\://github.com/flutter/flutter/pull/176530) ## Full commit history To find a complete list of changes in this release, check out the \[DevTools git log]\(https\://github.com/flutter/devtools/tree/v2.51.0).

----
url: https://docs.flutter.dev/ui/widgets/accessibility
----

Make your app accessible.

[Exclude​Semantics](https://api.flutter.dev/flutter/widgets/ExcludeSemantics-class.html)

[A widget that drops all the semantics of its descendants. This can be used to hide subwidgets that would otherwise be reported but that would...](https://api.flutter.dev/flutter/widgets/ExcludeSemantics-class.html)

[Merge​Semantics](https://api.flutter.dev/flutter/widgets/MergeSemantics-class.html)

[A widget that merges the semantics of its descendants.](https://api.flutter.dev/flutter/widgets/MergeSemantics-class.html)

[Semantics](https://api.flutter.dev/flutter/widgets/Semantics-class.html)

[A widget that annotates the widget tree with a description of the meaning of the widgets. Used by accessibility tools, search engines, and other semantic...](https://api.flutter.dev/flutter/widgets/Semantics-class.html)

Find more widgets in the [widget catalog](/ui/widgets).

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-09-29. [Report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/ui/widgets/accessibility "Report an issue with this page").

----
url: https://docs.flutter.dev/ui/design/text/typography
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Flutter's fonts and typography](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

[*Typography*](https://en.wikipedia.org/wiki/Typography) covers the style and appearance of type or fonts: it specifies how heavy the font is, the slant of the font, the spacing between the letters, and other visual aspects of the text.

All fonts are *not* created the same.

A font style is defined by, at minimum, a typeface, representing the set of common character rules describing fonts in the same type family, such as **Roboto** or **Noto**, a font weight (for example, Regular, Bold, or a numeric value), and a style (like Regular, *Italic*, etc). All of these and additional pre-set attributes come together to make up what we would call a static font.

Variable fonts allow some of these attributes to be modified at runtime and store what would normally be multiple static fonts in a single file.

## Typographic Scale

[#](#typographic-scale)

A typographical scale is a set of related text styles to provide balance, cohesion, and visual variety in your apps.

The common type scale in Flutter, provided by [`TextTheme`](https://pub.dev/documentation/material_ui/latest/material_ui/TextTheme-class.html), includes five categories of text indicating the function:

* Display
* Headline
* Title
* Label
* Body

There are also three size variations for each:

* Small
* Medium
* Large

Each of these fifteen combinations of a category and text size are represented by a single [`TextStyle`](https://api.flutter.dev/flutter/painting/TextStyle-class.html).

All the platform specific typographical scales that Flutter exposes are contained in the [`Typography`](https://pub.dev/documentation/material_ui/latest/material_ui/Typography-class.html) class. Usually, you will not need to reference this class directly as the `TextTheme` will be localized to your target platform.

## Variable fonts

[#](#variable-fonts)

[Variable fonts](https://fonts.google.com/knowledge/introducing_type/introducing_variable_fonts) allow you to control pre-defined aspects of text styling. Variable fonts support specific axes, such as width, weight, slant (to name a few). The user can select *any value along the continuous axis* when specifying the type.

### Using the Google Fonts type tester

[#](#using-the-google-fonts-type-tester)

A growing number of fonts on Google Fonts offer some variable font capabilities. You can see the range of options by using the Type Tester and see how you might vary a single font.

In real time, move the slider on any of the axes to see how it affects the font. When programming a variable font, use the [`FontVariation`](https://api.flutter.dev/flutter/dart-ui/FontVariation-class.html) class to modify the font's design axes. The `FontVariation` class conforms to the [OpenType font variables spec](https://learn.microsoft.com/en-us/typography/opentype/spec/otvaroverview).

## Static fonts

[#](#static-fonts)

Google Fonts also contains static fonts. As with variable fonts, you need to know how the font is designed to know what options are available to you. Once again, the Google Fonts site can help.

### Using the Google Fonts package

[#](#using-the-google-fonts-package)

While you can download fonts from the site and install them manually in your apps, you can elect to use theme directly from the [google\_fonts](https://pub.dev/packages/google_fonts) package on [pub.dev](https://pub.dev).

They can be used as is by referencing simply the font name:

dart

```
Text(
  'This is Google Fonts',
  style: GoogleFonts.lato(),
),
```

or customized by setting properties on the resulting `TextStyle`:

dart

```
Text(
  'This is Google Fonts',
  style: GoogleFonts.lato(
    textStyle: Theme.of(context).textTheme.displayLarge,
    fontSize: 48,
    fontWeight: FontWeight.w700,
    fontStyle: FontStyle.italic,
  ),
),
```

### Modifying fonts

[#](#modifying-fonts)

Use the following API to programmatically alter a static font (but remember that this only works if the font was *designed* to support the feature):

* [`FontFeature`](https://api.flutter.dev/flutter/dart-ui/FontFeature-class.html) to select glyphs
* [`FontWeight`](https://api.flutter.dev/flutter/dart-ui/FontWeight-class.html) to modify weight
* [`FontStyle`](https://api.flutter.dev/flutter/dart-ui/FontStyle.html) to italicize
* [`FontVariation`](https://api.flutter.dev/flutter/dart-ui/FontVariation-class.html) to specify a range of values for a specific property.

A `FontFeature` corresponds to an [OpenType feature tag](https://learn.microsoft.com/en-us/typography/opentype/spec/featuretags) and can be thought of as a boolean flag to enable or disable a feature of a given font.

## Other resources

[#](#other-resources)

The following video shows you some of the capabilities of Flutter's typography and combines it with the Material *and* Cupertino look and feel (depending on the platform the app runs on), animation, and custom fragment shaders:

[Watch on YouTube in a new tab: "Prototyping beautiful designs with Flutter"](https://www.youtube.com/watch/sA5MRFFUuOU)

To read one engineer's experience customizing variable fonts and animating them as they morph (and was the basis for the above video), check out [Playful typography with Flutter](https://blog.flutter.dev/playful-typography-with-flutter-f030385058b4), a post on the Flutter blog. The associated example also uses a custom shader.

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-09-29. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/ui/design/text/typography.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/ui/design/text/typography\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/ui/design/text/typography.md "Report an issue with this page").

----
url: https://docs.flutter.dev/cookbook/images
----

[Fade in images with a placeholder](/cookbook/images/fading-in-images)

[How to fade images into view.](/cookbook/images/fading-in-images)

[Display images from the internet](/cookbook/images/network-image)

[How to display images from the internet.](/cookbook/images/network-image)

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/images/index.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/cookbook/images\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/images/index.md "Report an issue with this page").

----
url: https://docs.flutter.dev/tools/android-studio.md
----

\# Android Studio and IntelliJ > Learn how to develop Flutter apps in Android Studio and other IntelliJ products.

* [Android Studio and IntelliJ]()
* [Visual Studio Code](/tools/vs-code)

[]()## Installation and setup {: #setup} Android Studio and IntelliJ IDEA offer a complete, IDE experience once you install the Flutter plugin. To install the latest version of the following IDEs, follow their instructions: - \[Android Studio]\[] - \[IntelliJ IDEA Community]\[] - \[IntelliJ IDEA Ultimate]\[] \[Android Studio]: https\://developer.android.com/studio/install \[IntelliJ IDEA Community]: https\://www\.jetbrains.com/idea/download/ \[IntelliJ IDEA Ultimate]: https\://www\.jetbrains.com/idea/download/ ### Install the Flutter plugin {: #install-plugin} 1. Go to \*\*File\*\* > \*\*Settings\*\*. You can also press `Ctrl` + `Alt` + `S`. The \*\*Preferences\*\* dialog opens. 1. From the list at the left, select \*\*Plugins\*\*. 1. From the top of this panel, select \*\*Marketplace\*\*. 1. Type \`flutter\` in the plugin search field. 1. Select the \*\*Flutter\*\* plugin. 1. Click \*\*Install\*\*. 1. Click \*\*Yes\*\* when prompted to install the plugin. 1. Click \*\*Restart\*\* when prompted. 1. Start Android Studio or IntelliJ. 1. From the macOS menu bar, go to \*\*Android Studio\*\* (or \*\*IntelliJ\*\*) > \*\*Settings...\*\*. You can also press `Cmd` + `,`. The \*\*Preferences\*\* dialog opens. 1. From the list at the left, select \*\*Plugins\*\*. 1. From the top of this panel, select \*\*Marketplace\*\*. 1. Type \`flutter\` in the plugin search field. 1. Select the \*\*Flutter\*\* plugin. 1. Click \*\*Install\*\*. 1. Click \*\*Yes\*\* when prompted to install the plugin. 1. Click \*\*Restart\*\* when prompted. 1. Go to \*\*File\*\* > \*\*Settings\*\*. You can also press `Ctrl` + `Alt` + `S`. The \*\*Preferences\*\* dialog opens. 1. From the list at the left, select \*\*Plugins\*\*. 1. From the top of this panel, select \*\*Marketplace\*\*. 1. Type \`flutter\` in the plugin search field. 1. Select the \*\*Flutter\*\* plugin. 1. Click \*\*Install\*\*. 1. Click \*\*Yes\*\* when prompted to install the plugin. 1. Click \*\*Restart\*\* when prompted. ### Updating the plugins {:#updating} Updates to the plugins are shipped on a regular basis. You should be prompted in the IDE when an update is available. To check for updates manually: 1. Open preferences (\*\*Android Studio > Check for Updates\*\* on macOS, \*\*Help > Check for Updates\*\* on Linux). 1. If \`dart\` or \`flutter\` are listed, update them. ## Creating projects You can create a new project in one of several ways. ### Creating a new project Creating a new Flutter project from the Flutter starter app template differs between Android Studio and IntelliJ. \*\*In Android Studio:\*\* 1. In the IDE, click \*\*New Flutter Project\*\* from the \*\*Welcome\*\* window or \*\*File > New > New Flutter Project\*\* from the main IDE window. 1. Specify the \*\*Flutter SDK path\*\* and click \*\*Next\*\*. 1. Enter your desired \*\*Project name\*\*, \*\*Description\*\*, and \*\*Project location\*\*. 1. If you might publish this app, \[set the company domain]\(#set-the-company-domain). 1. Click \*\*Finish\*\*. \*\*In IntelliJ:\*\* 1. In the IDE, click \*\*New Project\*\* from the \*\*Welcome\*\* window or \*\*File > New > Project\*\* from the main IDE window. 1. Select \*\*Flutter\*\* from the \*\*Generators\*\* list in the left panel 1. Specify the \*\*Flutter SDK path\*\* and click \*\*Next\*\*. 1. Enter your desired \*\*Project name\*\*, \*\*Description\*\*, and \*\*Project location\*\*. 1. If you might publish this app, \[set the company domain]\(#set-the-company-domain). 1. Click \*\*Finish\*\*. #### Set the company domain When creating a new app, some Flutter IDE plugins ask for an organization name in reverse domain order, something like \`com.example\`. Along with the name of the app, this is used as the package name for Android, and the Bundle ID for iOS when the app is released. If you think you might ever release this app, it is better to specify these now. They cannot be changed once the app is released. Your organization name should be unique. ### Opening a project from existing source code To open an existing Flutter project: 1. In the IDE, click \*\*Open\*\* from the \*\*Welcome\*\* window, or \*\*File > Open\*\* from the main IDE window. 1. Browse to the directory holding your existing Flutter source code files. 1. Click \*\*Open\*\*. :::important Do \*not\* use the \*\*New > Project from existing sources\*\* option for Flutter projects. ::: ## Editing code and viewing issues The Flutter plugin performs code analysis that enables the following: \* Syntax highlighting. \* Code completions based on rich type analysis. \* Navigating to type declarations (\*\*Navigate > Declaration\*\*), and finding type usages (\*\*Edit > Find > Find Usages\*\*). \* Viewing all current source code problems (\*\*View > Tool Windows > Dart Analysis\*\*). Any analysis issues are shown in the Dart Analysis pane:\
!\[Dart Analysis pane]\(/assets/images/docs/tools/android-studio/dart-analysis.png){:width="90%"} ## Running and debugging :::note You can debug your app in a few ways. \* Using \[DevTools]\[], a suite of debugging and profiling tools that run in a browser \_and include the Flutter inspector\_. \* Using Android Studio's (or IntelliJ's) built-in debugging features, such as the ability to set breakpoints. \* Using the Flutter inspector, directly available in Android Studio and IntelliJ. The instructions below describe features available in Android Studio and IntelliJ. For information on launching DevTools, see \[Running DevTools from Android Studio]\[] in the \[DevTools]\[] docs. ::: Running and debugging are controlled from the main toolbar: !\[Main IntelliJ toolbar]\(/assets/images/docs/tools/android-studio/main-toolbar.png){:width="90%"} ### Selecting a target When a Flutter project is open in the IDE, you should see a set of Flutter-specific buttons on the right-hand side of the toolbar. :::note If the Run and Debug buttons are disabled, and no targets are listed, 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. ::: 1. Locate the \*\*Flutter Target Selector\*\* drop-down button. This shows a list of available targets. 2. Select the target you want your app to be started on. When you connect devices, or start simulators, additional entries appear. ### Run app without breakpoints 1. Click the \*\*Play icon\*\* in the toolbar, or invoke \*\*Run > Run\*\*. The bottom \*\*Run\*\* pane shows logs output. ### Run app with breakpoints 1. If desired, set breakpoints in your source code. 1. Click the \*\*Debug icon\*\* in the toolbar, or invoke \*\*Run > Debug\*\*. \* The bottom \*\*Debugger\*\* pane shows Stack Frames and Variables. \* The bottom \*\*Console\*\* pane shows detailed logs output. \* Debugging is based on a default launch configuration. To customize this, click the drop-down button to the right of the device selector, and select \*\*Edit configuration\*\*. ## Fast edit and refresh development cycle Flutter offers a best-in-class developer cycle enabling you to see the effect of your changes almost instantly with the \_Stateful Hot Reload\_ feature. To learn more, check out \[Hot reload]\[]. ### Show performance data :::note To examine performance issues in Flutter, see the \[Timeline view]\[]. ::: To view the performance data, including the widget rebuild information, start the app in \*\*Debug\*\* mode, and then open the Performance tool window using \*\*View > Tool Windows > Flutter Performance\*\*. !\[Flutter performance window]\(/assets/images/docs/tools/android-studio/widget-rebuild-info.png){:width="90%"} To see the stats about which widgets are being rebuilt, and how often, click \*\*Show widget rebuild information\*\* in the \*\*Performance\*\* pane. The exact count of the rebuilds for this frame displays in the second column from the right. For a high number of rebuilds, a yellow spinning circle displays. The column to the far right shows how many times a widget was rebuilt since entering the current screen. For widgets that aren't rebuilt, a solid grey circle displays. Otherwise, a grey spinning circle displays. :::secondary The app shown in this screenshot has been designed to deliver poor performance, and the rebuild profiler gives you a clue about what is happening in the frame that might cause poor performance. The widget rebuild profiler is not a diagnostic tool, by itself, about poor performance. ::: The purpose of this feature is to make you aware when widgets are rebuilding—you might not realize that this is happening when just looking at the code. If widgets are rebuilding that you didn't expect, it's probably a sign that you should refactor your code by splitting up large build methods into multiple widgets. This tool can help you debug at least four common performance issues: 1. The whole screen (or large pieces of it) are built by a single StatefulWidget, causing unnecessary UI building. Split up the UI into smaller widgets with smaller \`build()\` functions. 1. Offscreen widgets are being rebuilt. This can happen, for example, when a ListView is nested in a tall Column that extends offscreen. Or when the RepaintBoundary is not set for a list that extends offscreen, causing the whole list to be redrawn. 1. The \`build()\` function for an AnimatedBuilder draws a subtree that does not need to be animated, causing unnecessary rebuilds of static objects. 1. An Opacity widget is placed unnecessarily high in the widget tree. Or, an Opacity animation is created by directly manipulating the opacity property of the Opacity widget, causing the widget itself and its subtree to rebuild. You can click on a line in the table to navigate to the line in the source where the widget is created. As the code runs, the spinning icons also display in the code pane to help you visualize which rebuilds are happening. Note that numerous rebuilds doesn't necessarily indicate a problem. Typically you should only worry about excessive rebuilds if you have already run the app in profile mode and verified that the performance is not what you want. And remember, \_the widget rebuild information is only available in a debug build\_. Test the app's performance on a real device in a profile build, but debug performance issues in a debug build. ## Editing tips for Flutter code 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 (\`Alt\`+\`Enter\` on Linux and Windows, \`Option\`+\`Return\` on macOS), as illustrated here: !\[IntelliJ editing assists]\(/assets/images/docs/tools/android-studio/assists.webp){:width="100%"} Quick Fixes are similar, only they are shown with a piece of code has an error and they can assist in correcting it. They are indicated with a red lightbulb. #### Wrap with new widget assist This can be used when you have a widget that you want to wrap in a surrounding widget, for example if you want to wrap a widget in a \`Row\` or \`Column\`. #### Wrap widget list with new widget assist Similar to the assist above, but for wrapping an existing list of widgets rather than an individual widget. #### Convert child to children assist Changes a child argument to a children argument, and wraps the argument value in a list. ### Live templates Live templates can be used to speed up entering typical code structures. They are invoked by typing their prefix, and then selecting it in the code completion window: !\[IntelliJ live templates]\(/assets/images/docs/tools/android-studio/templates.webp){:width="100%"} The Flutter plugin includes the following templates: \* Prefix \`stless\`: Create a new subclass of \`StatelessWidget\`. \* Prefix \`stful\`: Create a new subclass of \`StatefulWidget\` and its associated State subclass. \* Prefix \`stanim\`: Create a new subclass of \`StatefulWidget\` and its associated State subclass, including a field initialized with an \`AnimationController\`. You can also define custom templates in \*\*Settings > Editor > Live Templates\*\*. ### Keyboard shortcuts \*\*Hot reload\*\* On Linux (keymap \_Default for XWin\_) and Windows the keyboard shortcuts are \`Control\`+\`Alt\`+\`;\` and \`Control\`+\`Backslash\`. On macOS (keymap \_Mac OS X 10.5+ copy\_) the keyboard shortcuts are \`Command\`+\`Option\` and \`Command\`+\`Backslash\`. Keyboard mappings can be changed in the IDE Preferences/Settings: Select \*Keymap\*, then enter \_flutter\_ into the search box in the upper right corner. Right click the binding you want to change and \_Add Keyboard Shortcut\_. !\[IntelliJ settings keymap]\(/assets/images/docs/tools/android-studio/keymap-settings-flutter-plugin.png){:width="100%"} ### Hot reload vs. hot restart Hot reload works by injecting updated source code files into the running Dart VM (Virtual Machine). This includes not only adding new classes, but also adding methods and fields to existing classes, and changing existing functions. A few types of code changes cannot be hot reloaded though: \* Global variable initializers \* Static field initializers \* The \`main()\` method of the app For these changes you can fully restart your application, without having to end your debugging session. To perform a hot restart, don't click the Stop button, simply re-click the Run button (if in a run session) or Debug button (if in a debug session), or shift-click the 'hot reload' button. ## Editing Android code in Android Studio with full IDE support {:#android-ide} Opening the root directory of a Flutter project doesn't expose all the Android files to the IDE. Flutter apps contain a subdirectory named \`android\`. If you open this subdirectory as its own separate project in Android Studio, the IDE will be able to fully support editing and refactoring all Android files (like Gradle scripts). If you already have the entire project opened as a Flutter app in Android Studio, there are two equivalent ways to open the Android files on their own for editing in the IDE. Before trying this, make sure that you're on the latest version of Android Studio and the Flutter plugins. \* In the \["project view"]\[], you should see a subdirectory immediately under the root of your flutter app named \`android\`. Right click on it, then select \*\*Flutter > Open Android module in Android Studio\*\*. \* OR, you can open any of the files under the \`android\` subdirectory for editing. You should then see a "Flutter commands" banner at the top of the editor with a link labeled \*\*Open for Editing in Android Studio\*\*. Click that link. For both options, Android Studio gives you the option to use separate windows or to replace the existing window with the new project when opening a second project. Either option is fine. If you don't already have the Flutter project opened in Android studio, you can open the Android files as their own project from the start: 1. Click \*\*Open an existing Android Studio Project\*\* on the Welcome splash screen, or \*\*File > Open\*\* if Android Studio is already open. 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 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. ## Editing Android code in IntelliJ IDEA {:#edit-android-code} To enable editing of Android code in IntelliJ IDEA, you need to configure the location of the Android SDK: 1. In \*\*Preferences > Plugins\*\*, enable \*\*Android Support\*\* if you haven't already. 1. Right-click the \*\*android\*\* folder in the Project view, and select \*\*Open Module Settings\*\*. 1. In the \*\*Sources\*\* tab, locate the \*\*Language level\*\* field, and select level 8 or later. 1. In the \*\*Dependencies\*\* tab, locate the \*\*Module SDK\*\* field, and select an Android SDK. If no SDK is listed, click \*\*New\*\* and specify the location of the Android SDK. Make sure to select an Android SDK matching the one used by Flutter (as reported by \`flutter doctor\`). 1. Click \*\*OK\*\*. ## Flutter Property Editor {: #property-editor} The Flutter Property Editor is a powerful tool provided by the \[Flutter plugin]\[] that lets you view and modify widget properties directly from its visual interface. ### How to open the Flutter Property Editor in Android Studio and IntelliJ 1. Click on the Flutter Property Editor \*\*icon\*\* !\[Flutter Property Editor Android Studio/IntelliJ icon]\(/assets/images/docs/tools/devtools/property-editor-icon-android-studio.png){:width="20px"} in the Android Studio or IntelliJ sidebar. 2. The Flutter Property Editor will load in the side panel. 3. Please refer to the Flutter Property Editor \[documentation]\[] for a detailed usage guide. !\[Flutter Property Editor side panel in Android Studio/IntelliJ]\(/assets/images/docs/tools/devtools/property-editor-android-studio.png){:width="600px"} \[Flutter plugin]: https\://plugins.jetbrains.com/plugin/9212-flutter \[documentation]: /tools/property-editor ## Troubleshooting ### Known issues and feedback Important known issues that might impact your experience are documented in the \[Flutter plugin README]\[] file. Known bugs and feature requests are tracked in the issue trackers: \* \[Flutter plugin issue tracker]\[] \* \[Dart plugin issue tracker]\[] We welcome feedback, both on bugs/issues and feature requests. Prior to filing new issues: \* Do a quick search in the issue trackers to see if the issue is already tracked. \* Make sure you have \[updated]\(#updating) to the most recent version of the plugin. When filing new issues, include the output of \[\`flutter doctor\`]\[]. \[DevTools]: /tools/devtools \[Flutter plugin issue tracker]: https\://github.com/flutter/flutter-intellij/issues \[Dart plugin issue tracker]: https\://github.com/flutter/dart-intellij-third-party/issues \[\`flutter doctor\`]: /resources/bug-reports#provide-some-flutter-diagnostics \[Debugging Flutter apps]: /testing/debugging \[Flutter plugin README]: https\://github.com/flutter/flutter-intellij/blob/master/README.md \["project view"]: https\://developer.android.com/studio/projects/#ProjectView \[let us know]: https\://github.com/flutter/website/issues/new \[Running DevTools from Android Studio]: /tools/devtools/android-studio \[Hot reload]: /tools/hot-reload \[Timeline view]: /tools/devtools/performance

----
url: https://docs.flutter.dev/cookbook/navigation
----

[Return data from a screen](/cookbook/navigation/returning-data)

[How to return data from a new screen.](/cookbook/navigation/returning-data)

[Animate a widget across screens](/cookbook/navigation/hero-animations)

[How to animate a widget from one screen to another](/cookbook/navigation/hero-animations)

[Set up universal links for iOS](/cookbook/navigation/set-up-universal-links)

[Learn how to set up universal links for an iOS application built with Flutter.](/cookbook/navigation/set-up-universal-links)

[Send data to a new screen](/cookbook/navigation/passing-data)

[How to pass data to a new route.](/cookbook/navigation/passing-data)

[Navigate to a new screen and back](/cookbook/navigation/navigation-basics)

[How to navigate between routes.](/cookbook/navigation/navigation-basics)

[Pass arguments to a named route](/cookbook/navigation/navigate-with-arguments)

[How to pass arguments to a named route.](/cookbook/navigation/navigate-with-arguments)

[Navigate with named routes](/cookbook/navigation/named-routes)

[How to implement named routes for navigating between screens.](/cookbook/navigation/named-routes)

[Set up app links for Android](/cookbook/navigation/set-up-app-links)

[Learn how to set up app links for an Android application built with Flutter.](/cookbook/navigation/set-up-app-links)

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/navigation/index.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/cookbook/navigation\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/navigation/index.md "Report an issue with this page").

----
url: https://docs.flutter.dev/cookbook/audio/soloud
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Play or stream sound and music with flutter\_soloud](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

This recipe demonstrates how to integrate the [`flutter_soloud` package](https://pub.dev/packages/flutter_soloud) into your Flutter application to play sound effects and background music with low latency. To get started using the package, follow these steps:

## 1. Add the package dependency

[#](#1-add-the-package-dependency)

To add `package:flutter_soloud` as a dependency, use `flutter pub add`:

```
$ flutter pub add flutter_soloud
```

## 2. Initialize `SoLoud`

[#](#2-initialize-soloud)

Before playing any audio, you need to initialize the `SoLoud` instance. You can also configure playback settings like sample rate, buffer size, and the number of channels.

dart

```
import 'package:flutter_soloud/flutter_soloud.dart';
​
// Initialize with default settings.
await SoLoud.instance.init();
​
// Or configure with custom settings.
await SoLoud.instance.init(
  sampleRate: 44100,
  bufferSize: 2048,
  channels: Channels.stereo,
);
```

## 3. Load audio

[#](#3-load-audio)

The `flutter_soloud` package supports various audio formats including MP3, WAV, OGG, and FLAC. You can load audio from different sources.

From a local file:

dart

```
final sound = await SoLoud.instance.loadFile('path/to/sound.mp3');
```

From an app asset:

dart

```
final sound = await SoLoud.instance.loadAsset('assets/sound.mp3');
```

From a network URL:

dart

```
final sound = await SoLoud.instance.loadUrl(
  'https://example.com/sound.mp3',
  mode: LoadMode.memory,
);
```

From bytes in memory:

dart

```
// TODO: Replace with your actual audio data.
final soundBytes = Uint8List.fromList([]);
​
final sound = await SoLoud.instance.loadMem(
  'reference_name.mp3',
  soundBytes,
  mode: LoadMode.memory,
);
```

## 4. Play audio

[#](#4-play-audio)

Once the audio is loaded, you can play it using `SoLoud.instance.play`. You can also configure gapless looping.

dart

```
// Play the sound.
var handle = await SoLoud.instance.play(sound);
​
// Play with gapless looping.
handle = await SoLoud.instance.play(
  sound,
  looping: true,
  loopingStartAt: Duration(seconds: 1),
);
```

The `play` method returns a handle that references that particular instance of the sound being played. You can use this handle to control the playback.

## 5. Control playback

[#](#5-control-playback)

With the handle returned from `play`, you can perform various actions such as pausing or resuming playback:

dart

```
// Toggle pause state.
SoLoud.instance.pauseSwitch(handle);
```

Seek to a specific timestamp:

dart

```
SoLoud.instance.seek(handle, Duration(seconds: 5));
```

Set playback speed:

dart

```
// Play twice as fast.
SoLoud.instance.setRelativePlaySpeed(handle, 2.0);
```

Adjust playback volume:

dart

```
// Set playback to 50% volume.
SoLoud.instance.setVolume(handle, 0.5);
```

Fade the audio volume:

dart

```
SoLoud.instance.fadeVolume(
  handle,
  0.0, // The volume to fade to.
  Duration(seconds: 2), // The duration of the fade.
);
```

Stop playback:

dart

```
await SoLoud.instance.stop(handle);
```

## 6. Dispose of sound sources

[#](#6-dispose-of-sound-sources)

When you are finished with a sound source, remember to dispose of it to free up resources.

dart

```
await SoLoud.instance.disposeSource(sound);
```

## 7. \[Optional] Stream audio

[#](#7-optional-stream-audio)

The `flutter_soloud` package also supports streaming audio data in real time.

Initialize and configure a buffer stream:

dart

```
final stream = SoLoud.instance.setBufferStream(
  bufferingType: BufferingType.released,
  sampleRate: 24000,
  channels: Channels.mono,
  format: BufferType.s16le, // pcm16bits
);
```

Play the stream:

dart

```
final handle = await SoLoud.instance.play(stream);
```

As your app receives audio data, add it to the stream:

dart

```
// TODO: Replace with your actual audio data.
final audioChunk = Uint8List.fromList([]);
​
SoLoud.instance.addAudioDataStream(
  stream,
  audioChunk,
);
```

When you're done with the audio stream, mark it as complete:

dart

```
SoLoud.instance.setDataIsEnded(stream);
```

## More information

[#](#more-information)

For more detailed information and examples, visit the [`flutter_soloud` package](https://pub.dev/packages/flutter_soloud) on pub.dev or check out the `flutter_soloud` Package of the Week video.

[Watch on YouTube in a new tab: "flutter\_soloud - Package of the Week"](https://www.youtube.com/watch/2t6Bt04EyLw)

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/audio/soloud.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/cookbook/audio/soloud\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/audio/soloud.md "Report an issue with this page").

----
url: https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.9.1.md
----

\# DevTools 2.9.1 release notes > Release notes for Dart and Flutter DevTools version 2.9.1. The 2.9.1 release of the Dart and Flutter DevTools includes the following changes among other general improvements. To learn more about DevTools, check out the \[DevTools overview]\(https\://docs.flutter.dev/tools/devtools). ## Debugger updates \* Improve support for inspecting large lists and maps in the Debugger variables pane - \[#3497]\(https\://github.com/flutter/devtools/pull/3497) !\[Inspection before]\(/assets/images/docs/tools/devtools/release-notes/images-2.9.1/image1.png "Inspection before") !\[Inspection after]\(/assets/images/docs/tools/devtools/release-notes/images-2.9.1/image2.png "Inspection after") \* Added support for selecting objects in the program explorer outline view. Selecting an object will automatically scroll the source code in the debugger to the selected object - \[#3480]\(https\://github.com/flutter/devtools/pull/3480) ## Performance updates \* Fix bugs with performance page search and improve performance - \[#3515]\(https\://github.com/flutter/devtools/pull/3515) \* Added an enhanced tooltip for flutter frames - \[#3493]\(https\://github.com/flutter/devtools/pull/3493) !\[Flutter frame tooltips]\(/assets/images/docs/tools/devtools/release-notes/images-2.9.1/image3.png "Flutter frame tooltips") ## Full commit history To find a complete list of changes since the previous release, check out \[the diff on GitHub]\(https\://github.com/flutter/devtools/compare/v2.8.0...v2.9.1).

----
url: https://docs.flutter.dev/add-to-app/ios/project-setup-legacy.md
----

\# Integrate a Flutter module into your iOS project (Legacy) > Learn how to integrate a Flutter module into your existing iOS project. :::warning As of Flutter 3.44, Swift Package Manager (SwiftPM) replaces CocoaPods as the default dependency manager for iOS and macOS Flutter apps. CocoaPods is officially in maintenance mode, and its registry will permanently \[become read-only on December 2, 2026]\[read-only]. This guide is preserved for reference only, and will not receive ongoing maintenance. Please migrate to using Swift Package Manager using the \[updated integration guide]\[]. ::: Flutter UI components can be incrementally added into your existing iOS application as embedded frameworks. To embed Flutter in your existing application, consider one of the following three methods. | Embedding Method | Methodology | Benefit | |---|---|---| | Use CocoaPods \_(Recommended)\_ | Install and use the Flutter SDK and CocoaPods. Flutter compiles the \`flutter\_module\` from source each time Xcode builds the iOS app. | Least complicated method to embed Flutter into your app. | | Use \[iOS frameworks]\[] | Create iOS frameworks for Flutter components, embed them into your iOS, and update your existing app's build settings. | Doesn't require every developer to install the Flutter SDK and CocoaPods on their local machines. | | Use iOS frameworks and CocoaPods | Embed the frameworks for your iOS app and the plugins in Xcode, but distribute the Flutter engine as a CocoaPods podspec. | Provides an alternative to distributing the large Flutter engine (\`Flutter.xcframework\`) library. | {:.table .table-striped} \[iOS frameworks]: https\://developer.apple.com/library/archive/documentation/MacOSX/Conceptual/BPFrameworks/Concepts/WhatAreFrameworks.html When you add Flutter to your existing iOS app, it \[increases the size of your iOS app]\[app-size]. For examples using an app built with UIKit, see the iOS directories in the \[add\_to\_app code samples]\[]. For an example using SwiftUI, consult the iOS directory in \[News Feed App]\[]. ## Development system requirements Flutter requires the latest version of Xcode and \[CocoaPods]\[]. ## Create a Flutter module To embed Flutter into your existing application with any method, create a Flutter module first. Use the following command to create a Flutter module. \`\`\`console $ cd /path/to/my\_flutter $ flutter create --template module my\_flutter \`\`\` Flutter creates module project under \`/path/to/my\_flutter/\`. If you use the \[CocoaPods method]\[], save the module in the same parent directory as your existing iOS app. \[CocoaPods method]: /add-to-app/ios/project-setup-legacy/?tab=embed-using-cocoapods From the Flutter module directory, you can run the same \`flutter\` commands you would in any other Flutter project, like \`flutter run\` or \`flutter build ios\`. You can also run the module in \[VS Code]\[] or \[Android Studio/IntelliJ]\[] with the Flutter and Dart plugins. This project contains a single-view example version of your module before embedding it in your existing iOS app. This helps when testing the Flutter-only parts of your code. ## Organize your module The \`my\_flutter\` module directory structure resembles a typical Flutter app. - my\_flutter/ - .ios/ - Runner.xcworkspace - Flutter/ - podhelper.rb - lib/ - main.dart - test/ - pubspec.yaml Your Dart code should be added to the \`lib/\` directory. Your Flutter dependencies, packages, and plugins must be added to the \`pubspec.yaml\` file. The \`.ios/\` hidden subfolder contains an Xcode workspace where you can run a standalone version of your module. This wrapper project bootstraps your Flutter code. It contains helper scripts to facilitate building frameworks or embedding the module into your existing application with CocoaPods. :::note \* Add custom iOS code to your own existing application's project or to a plugin, not to the module's \`.ios/\` directory. Changes made in your module's \`.ios/\` directory don't appear in your existing iOS project using the module, and might be overwritten by Flutter. \* Exclude the \`.ios/\` directory from source control as it's autogenerated. \* Before building the module on a new machine, run \`flutter pub get\` in the \`my\_flutter\` directory. This regenerates the \`.ios/\` directory before building the iOS project that uses the Flutter module. ::: ## Embed a Flutter module in your iOS app After you have developed your Flutter module, you can embed it using the methods described in the table at the top of the page. You can run in \*\*Debug\*\* mode on a simulator or a real device, and \*\*Release\*\* mode on a real device. :::note Learn more about \[Flutter's build modes]\[build modes of Flutter]. To use Flutter debugging features such as hot reload, consult \[Debugging your add-to-app module]\[]. ::: ### Use CocoaPods and the Flutter SDK {:#method-a .no\_toc} #### Approach {:#method-a-approach} This first method uses CocoaPods to embed the Flutter modules. CocoaPods manages dependencies for Swift projects, including Flutter code and plugins. Each time Xcode builds the app, CocoaPods embeds the Flutter modules. This allows rapid iteration with the most up-to-date version of your Flutter module without running additional commands outside of Xcode. To learn more about CocoaPods, consult the \[CocoaPods getting started guide]\[]. #### Requirements {:#method-a-reqs} Every developer working on your project must have a local version of the Flutter SDK and CocoaPods installed. #### Example project structure {:#method-a-structure} This section assumes that your existing app and the Flutter module reside in sibling directories. If you have a different directory structure, adjust the relative paths. The example directory structure resembles the following: - my\_flutter/ - .ios/ - Flutter/ - podhelper.rb - MyApp/ - Podfile #### Update your Podfile Add your Flutter modules to your Podfile configuration file. This section presumes you called your Swift app \`MyApp\`. 1. \_(Optional)\_ If your existing app lacks a \`Podfile\` config file, navigate to the root of your app directory. Use the \`pod init\` command to create the \`Podfile\` file. :::tip If the \`pod init\` command errors, check that you're on the latest version of CocoaPods. ::: 1. Update your \`Podfile\` config file. 1. Add the following lines after the \`platform\` declaration. \`\`\`ruby title="MyApp/Podfile" flutter\_application\_path = '../my\_flutter' load File.join(flutter\_application\_path, '.ios', 'Flutter', 'podhelper.rb') \`\`\` 1. For each \[Podfile target]\[] that needs to embed Flutter, add a call to the \`install\_all\_flutter\_pods(flutter\_application\_path)\` method. Add these calls after the settings in the previous step. \`\`\`ruby title="MyApp/Podfile" target 'MyApp' do install\_all\_flutter\_pods(flutter\_application\_path) end \`\`\` 1. In the \`Podfile\`'s \`post\_install\` block, add a call to \`flutter\_post\_install(installer)\`. This block should be the last block in the \`Podfile\` config file. \`\`\`ruby title="MyApp/Podfile" post\_install do |installer| flutter\_post\_install(installer) if defined?(flutter\_post\_install) end \`\`\` To review an example \`Podfile\`, consult this \[Flutter Podfile sample]\[]. #### Embed your frameworks At build time, Xcode packages your Dart code, each Flutter plugin, and the Flutter engine into their own \`\*.xcframework\` bundles. CocoaPod's \`podhelper.rb\` script then embeds these \`\*.xcframework\` bundles into your project. \* \`Flutter.xcframework\` contains the Flutter engine. \* \`App.xcframework\` contains the compiled Dart code for this project. \* \`.xcframework\` contains one Flutter plugin. To embed the Flutter engine, your Dart code, and your Flutter plugins into your iOS app, complete the following procedure. 1. Refresh your Flutter plugins. If you change the Flutter dependencies in the \`pubspec.yaml\` file, run \`flutter pub get\` in your Flutter module directory. This refreshes the list of plugins that the \`podhelper.rb\` script reads. \`\`\`console flutter pub get \`\`\` 1. Embed the plugins and frameworks with CocoaPods. 1. Navigate to your iOS app project at \`/path/to/MyApp/MyApp\`. 1. Use the \`pod install\` command. \`\`\`console pod install \`\`\` Your iOS app's \*\*Debug\*\* and \*\*Release\*\* build configurations embed the corresponding \[Flutter components for that build mode]\[build-modes]. 1. Build the project. 1. Open \`MyApp.xcworkspace\` in Xcode. Verify that you're opening \`MyApp.xcworkspace\` and not opening \`MyApp.xcodeproj\`. The \`.xcworkspace\` file has the CocoaPod dependencies, the \`.xcodeproj\` doesn't. 1. Select \*\*Product\*\* > \*\*Build\*\* or press `Cmd` + `B`. #### Set LLDB Init File :::warning Set your scheme to use Flutter's LLDB Init File. Without this file, debugging on an iOS 26 or later device may crash. ::: 1. Generate Flutter LLDB files. 1. Within your flutter application, run the following: \`\`\`console flutter build ios --config-only \`\`\` This will generate the LLDB files in the \`.ios/Flutter/ephemeral\` directory. 1. Set the LLDB Init File. 1. Go to \*\*Product > Scheme > Edit Scheme\*\*. 1. Select the \*\*Run\*\* section in the left side bar. 1. Set the \*\*LLDB Init File\*\* using the same relative path to your Flutter application as you put in your Podfile in the \*\*Update your Podfile\*\* section. \`\`\`console $(SRCROOT)/../my\_flutter/.ios/Flutter/ephemeral/flutter\_lldbinit \`\`\` If your scheme already has an \*\*LLDB Init File\*\*, you can add Flutter's LLDB file to it. The path to Flutter's LLDB Init File must be relative to the location of your project's LLDB Init File. For example, if your LLDB file is located at \`/path/to/MyApp/.lldbinit\`, you would add the following: \`\`\`console command source --relative-to-command-file "../my\_flutter/.ios/Flutter/ephemeral/flutter\_lldbinit" \`\`\` \[build-modes]: /testing/build-modes \[CocoaPods getting started guide]: https\://guides.cocoapods.org/using/using-cocoapods.html \[Podfile target]: https\://guides.cocoapods.org/syntax/podfile.html#target \[Flutter Podfile sample]: https\://github.com/flutter/samples/blob/main/add\_to\_app/plugin/ios\_using\_plugin/Podfile ### Link and Embed frameworks in Xcode {:#method-b .no\_toc} #### Approach {:#method-b-approach} In this second method, edit your existing Xcode project, generate the necessary frameworks, and embed them in your app. Flutter generates iOS frameworks for Flutter itself, for your compiled Dart code, and for each of your Flutter plugins. Embed these frameworks and update your existing application's build settings. #### Requirements {:#method-b-reqs} No additional software or hardware requirements are needed for this method. Use this method in the following use cases: \* Members of your team can't install the Flutter SDK and CocoaPods \* You don't want to use CocoaPods as a dependency manager in existing iOS apps #### Limitations {:#method-b-limits} Flutter can't handle \[common dependencies with xcframeworks]\[common]. If both the host app and the Flutter module's plugin define the same pod dependency and you integrate Flutter module using this option, errors result. These errors include issues like \`Multiple commands produce 'CommonDependency.framework'\`. To work around this issue, link every plugin source in its \`podspec\` file from the Flutter module to the host app's \`Podfile\`. Link the source instead of the plugins' \`xcframework\` framework. The next section explains how to \[produce that framework]\[ios-framework]. To prevent the error that occurs when common dependencies exist, use \`flutter build ios-framework\` with the \`--no-plugins\` flag. \[common]: https\://github.com/flutter/flutter/issues/130220 \[ios-framework]: https\://github.com/flutter/flutter/issues/114692 #### Example project structure {:#method-b-structure} The following example assumes that you want to generate the frameworks to \`/path/to/MyApp/Flutter/\`. \`\`\`console $ flutter build ios-framework --output=/path/to/MyApp/Flutter/ \`\`\` Run this \_every time\_ you change code in your Flutter module. The resulting project structure should resemble this directory tree. - /path/to/MyApp/ - Flutter/ - Debug/ - Flutter.xcframework - App.xcframework - FlutterPluginRegistrant.xcframework (If you have plugins with iOS-platform code) - example\_plugin.xcframework (One framework file for each plugin) - Profile/ - Flutter.xcframework - App.xcframework - FlutterPluginRegistrant.xcframework - example\_plugin.xcframework - Release/ - Flutter.xcframework - App.xcframework - FlutterPluginRegistrant.xcframework - example\_plugin.xcframework :::warning Always use \`Flutter.xcframework\` and \`App.xcframework\` bundles located in the same directory. Mixing \`.xcframework\` imports from different directories (like \`Profile/Flutter.xcframework\` with \`Debug/App.xcframework\`) causes runtime crashes. ::: #### Procedures How you link, embed, or both the generated frameworks into your existing app in Xcode depends on the type of framework. \* Link and embed dynamic frameworks. \* Link static frameworks. \[Never embed them]\[static-framework]. Flutter plugins might produce \[static or dynamic frameworks]\[]. Link static frameworks, \[\_never\_ embed them]\[static-framework]. If you embed a static framework into your iOS app, you can't publish that app to the App Store. Publishing fails with a \`Found an unexpected Mach-O header code\` archive error. ##### Link all frameworks To link the necessary frameworks, follow this procedure. 1. Choose the frameworks to link. 1. In the \*\*Project Navigator\*\*, click on your project. 1. Click the \*\*Build Phases\*\* tab. 1. Expand \*\*Link Binary With Libraries\*\*. 1. Click \*\*+\*\* (plus sign). 1. Click \*\*Add Other...\*\* then \*\*Add Files...\*\*. 1. From the \*\*Choose frameworks and libraries to add:\*\* dialog box, navigate to the \`/path/to/MyApp/Flutter/Release/\` directory. 1. Command-click the frameworks in that directory then click \*\*Open\*\*. 1. Update the paths to the libraries to account for build modes. 1. Launch the Finder. 1. Navigate to the \`/path/to/MyApp/\` directory. 1. Right-click on \`MyApp.xcodeproj\` and select \*\*Show Package Contents\*\*. 1. Open \`project.pbxproj\` with Xcode. The file opens in Xcode's text editor. This also locks \*\*Project Navigator\*\* until you close the text editor. 1. Find the lines that resemble the following text in the \`/\* Begin PBXFileReference section \*/\`. \`\`\`text 312885572C1A441C009F74FF /\* Flutter.xcframework \*/ = { isa = PBXFileReference; expectedSignature = "AppleDeveloperProgram:S8QB4VV633:FLUTTER.IO LLC"; lastKnownFileType = wrapper.xcframework; name = Flutter.xcframework; path = Flutter/\[!Release!]/Flutter.xcframework; sourceTree = ""; }; 312885582C1A441C009F74FF /\* App.xcframework \*/ = { isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = App.xcframework; path = Flutter/\[!Release!]/App.xcframework; sourceTree = ""; }; \`\`\` 1. Change the \`Release\` text highlighted in the prior step and change it to \`$(CONFIGURATION)\`. Also wrap the path in quotation marks. \`\`\`text 312885572C1A441C009F74FF /\* Flutter.xcframework \*/ = { isa = PBXFileReference; expectedSignature = "AppleDeveloperProgram:S8QB4VV633:FLUTTER.IO LLC"; lastKnownFileType = wrapper.xcframework; name = Flutter.xcframework; path = \[!"!]Flutter/\[!$(CONFIGURATION)!]/Flutter.xcframework\[!"!]; sourceTree = ""; }; 312885582C1A441C009F74FF /\* App.xcframework \*/ = { isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = App.xcframework; path = \[!"!]Flutter/\[!$(CONFIGURATION)!]/App.xcframework\[!"!]; sourceTree = ""; }; \`\`\` 1. Update the search paths. 1. Click the \*\*Build Settings\*\* tab. 1. Navigate to \*\*Search Paths\*\* 1. Double-click to the right of \*\*Framework Search Paths\*\*. 1. In the combo box, click \*\*+\*\* (plus sign). 1. Type \`$(inherited)\`. and press `Enter`. 1. Click \*\*+\*\* (plus sign). 1. Type \`$(PROJECT\_DIR)/Flutter/$(CONFIGURATION)/\` and press `Enter`. After linking the frameworks, they should display in the \*\*Frameworks, Libraries, and Embedded Content\*\* section of your target's \*\*General\*\* settings. ##### Embed the dynamic frameworks To embed your dynamic frameworks, complete the following procedure. 1. Navigate to \*\*General\*\* > \*\*Frameworks, Libraries, and Embedded Content\*\*. 1. Click on each of your dynamic frameworks and select \*\*Embed & Sign\*\*. Don't include any static frameworks, including \`FlutterPluginRegistrant.xcframework\`. 1. Click the \*\*Build Phases\*\* tab. 1. Expand \*\*Embed Frameworks\*\*. Your dynamic frameworks should display in that section. 1. Build the project. 1. Open \`MyApp.xcworkspace\` in Xcode. Verify that you're opening \`MyApp.xcworkspace\` and not opening \`MyApp.xcodeproj\`. The \`.xcworkspace\` file has the CocoaPod dependencies, the \`.xcodeproj\` doesn't. 1. Select \*\*Product\*\* > \*\*Build\*\* or press `Cmd` + `B`. #### Set LLDB Init File :::warning Set your scheme to use Flutter's LLDB Init File. Without this file, debugging on an iOS 26 or later device may crash. ::: 1. Generate Flutter LLDB files. 1. Within your flutter application, re-run \`flutter build ios-framework\` if you haven't already: \`\`\`console $ flutter build ios-framework --output=/path/to/MyApp/Flutter/ \`\`\` This will generate the LLDB files in the \`/path/to/MyApp/Flutter/\` directory. 1. Set the LLDB Init File. 1. Go to \*\*Product > Scheme > Edit Scheme\*\*. 1. Select the \*\*Run\*\* section in the left side bar. 1. Set the \*\*LLDB Init File\*\* to the following: \`\`\`console $(PROJECT\_DIR)/Flutter/flutter\_lldbinit \`\`\` If your scheme already has an \*\*LLDB Init File\*\*, you can add Flutter's LLDB file to it. The path to Flutter's LLDB Init File must be relative to the location of your project's LLDB Init File. For example, if your LLDB file is located at \`/path/to/MyApp/.lldbinit\`, you would add the following: \`\`\`console command source --relative-to-command-file "Flutter/flutter\_lldbinit" \`\`\` \[static or dynamic frameworks]: https\://stackoverflow\.com/questions/32591878/ios-is-it-a-static-or-a-dynamic-framework \[static-framework]: https\://developer.apple.com/library/archive/technotes/tn2435/\_index.html \[static-framework]: https\://developer.apple.com/library/archive/technotes/tn2435/\_index.html ### Use frameworks in Xcode and Flutter framework as podspec {:#method-c .no\_toc} #### Approach {:#method-c-approach} This method generates Flutter as a CocoaPods podspec instead of distributing the large \`Flutter.xcframework\` to other developers, machines, or continuous integration systems. Flutter still generates iOS frameworks for your compiled Dart code, and for each of your Flutter plugins. Embed these frameworks and update your existing application's build settings. #### Requirements {:#method-c-reqs} No additional software or hardware requirements are needed for this method. Use this method in the following use cases: \* Members of your team can't install the Flutter SDK and CocoaPods \* You don't want to use CocoaPods as a dependency manager in existing iOS apps #### Limitations {:#method-c-limits} Flutter can't handle \[common dependencies with xcframeworks]\[common]. If both the host app and the Flutter module's plugin define the same pod dependency and you integrate Flutter module using this option, errors result. These errors include issues like \`Multiple commands produce 'CommonDependency.framework'\`. To work around this issue, link every plugin source in its \`podspec\` file from the Flutter module to the host app's \`Podfile\`. Link the source instead of the plugins' \`xcframework\` framework. The next section explains how to \[produce that framework]\[ios-framework]. To prevent the error that occurs when common dependencies exist, use \`flutter build ios-framework\` with the \`--no-plugins\` flag. \[common]: https\://github.com/flutter/flutter/issues/130220 \[ios-framework]: https\://github.com/flutter/flutter/issues/114692 This method only works with the \`beta\` or \`stable\` \[release channels]\[]. \[release channels]: /install/upgrade#switching-flutter-channels #### Example project structure {:#method-c-structure} The following example assumes that you want to generate the frameworks to \`/path/to/MyApp/Flutter/\`. \`\`\`console $ flutter build ios-framework --output=/path/to/MyApp/Flutter/ \`\`\` Run this \_every time\_ you change code in your Flutter module. The resulting project structure should resemble this directory tree. - /path/to/MyApp/ - Flutter/ - Debug/ - Flutter.xcframework - App.xcframework - FlutterPluginRegistrant.xcframework (If you have plugins with iOS-platform code) - example\_plugin.xcframework (One framework file for each plugin) - Profile/ - Flutter.xcframework - App.xcframework - FlutterPluginRegistrant.xcframework - example\_plugin.xcframework - Release/ - Flutter.xcframework - App.xcframework - FlutterPluginRegistrant.xcframework - example\_plugin.xcframework :::warning Always use \`Flutter.xcframework\` and \`App.xcframework\` bundles located in the same directory. Mixing \`.xcframework\` imports from different directories (like \`Profile/Flutter.xcframework\` with \`Debug/App.xcframework\`) causes runtime crashes. ::: #### Add Flutter engine to your Podfile Host apps using CocoaPods can add the Flutter engine to their Podfile. \`\`\`ruby title="MyApp/Podfile" pod 'Flutter', :podspec => '/path/to/MyApp/Flutter/\[!\[build mode]!]/Flutter.podspec' \`\`\` :::note You must hard code the \`\[build mode]\` value. For example, use \`Debug\` if you need to use \`flutter attach\` and \`Release\` when you're ready to ship. ::: #### Link and embed app and plugin frameworks Flutter plugins might produce \[static or dynamic frameworks]\[]. Link static frameworks, \[\_never\_ embed them]\[static-framework]. If you embed a static framework into your iOS app, you can't publish that app to the App Store. Publishing fails with a \`Found an unexpected Mach-O header code\` archive error. ##### Link all frameworks To link the necessary frameworks, follow this procedure. 1. Choose the frameworks to link. 1. In the \*\*Project Navigator\*\*, click on your project. 1. Click the \*\*Build Phases\*\* tab. 1. Expand \*\*Link Binary With Libraries\*\*. 1. Click \*\*+\*\* (plus sign). 1. Click \*\*Add Other...\*\* then \*\*Add Files...\*\*. 1. From the \*\*Choose frameworks and libraries to add:\*\* dialog box, navigate to the \`/path/to/MyApp/Flutter/Release/\` directory. 1. Command-click the frameworks in that directory then click \*\*Open\*\*. 1. Update the paths to the libraries to account for build modes. 1. Launch the Finder. 1. Navigate to the \`/path/to/MyApp/\` directory. 1. Right-click on \`MyApp.xcodeproj\` and select \*\*Show Package Contents\*\*. 1. Open \`project.pbxproj\` with Xcode. The file opens in Xcode's text editor. This also locks \*\*Project Navigator\*\* until you close the text editor. 1. Find the lines that resemble the following text in the \`/\* Begin PBXFileReference section \*/\`. \`\`\`text 312885572C1A441C009F74FF /\* Flutter.xcframework \*/ = { isa = PBXFileReference; expectedSignature = "AppleDeveloperProgram:S8QB4VV633:FLUTTER.IO LLC"; lastKnownFileType = wrapper.xcframework; name = Flutter.xcframework; path = Flutter/\[!Release!]/Flutter.xcframework; sourceTree = ""; }; 312885582C1A441C009F74FF /\* App.xcframework \*/ = { isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = App.xcframework; path = Flutter/\[!Release!]/App.xcframework; sourceTree = ""; }; \`\`\` 1. Change the \`Release\` text highlighted in the prior step and change it to \`$(CONFIGURATION)\`. Also wrap the path in quotation marks. \`\`\`text 312885572C1A441C009F74FF /\* Flutter.xcframework \*/ = { isa = PBXFileReference; expectedSignature = "AppleDeveloperProgram:S8QB4VV633:FLUTTER.IO LLC"; lastKnownFileType = wrapper.xcframework; name = Flutter.xcframework; path = \[!"!]Flutter/\[!$(CONFIGURATION)!]/Flutter.xcframework\[!"!]; sourceTree = ""; }; 312885582C1A441C009F74FF /\* App.xcframework \*/ = { isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = App.xcframework; path = \[!"!]Flutter/\[!$(CONFIGURATION)!]/App.xcframework\[!"!]; sourceTree = ""; }; \`\`\` 1. Update the search paths. 1. Click the \*\*Build Settings\*\* tab. 1. Navigate to \*\*Search Paths\*\* 1. Double-click to the right of \*\*Framework Search Paths\*\*. 1. In the combo box, click \*\*+\*\* (plus sign). 1. Type \`$(inherited)\`. and press `Enter`. 1. Click \*\*+\*\* (plus sign). 1. Type \`$(PROJECT\_DIR)/Flutter/$(CONFIGURATION)/\` and press `Enter`. After linking the frameworks, they should display in the \*\*Frameworks, Libraries, and Embedded Content\*\* section of your target's \*\*General\*\* settings. ##### Embed the dynamic frameworks To embed your dynamic frameworks, complete the following procedure. 1. Navigate to \*\*General\*\* > \*\*Frameworks, Libraries, and Embedded Content\*\*. 1. Click on each of your dynamic frameworks and select \*\*Embed & Sign\*\*. Don't include any static frameworks, including \`FlutterPluginRegistrant.xcframework\`. 1. Click the \*\*Build Phases\*\* tab. 1. Expand \*\*Embed Frameworks\*\*. Your dynamic frameworks should display in that section. 1. Build the project. 1. Open \`MyApp.xcworkspace\` in Xcode. Verify that you're opening \`MyApp.xcworkspace\` and not opening \`MyApp.xcodeproj\`. The \`.xcworkspace\` file has the CocoaPod dependencies, the \`.xcodeproj\` doesn't. 1. Select \*\*Product\*\* > \*\*Build\*\* or press `Cmd` + `B`. #### Set LLDB Init File :::warning Set your scheme to use Flutter's LLDB Init File. Without this file, debugging on an iOS 26 or later device may crash. ::: 1. Generate Flutter LLDB files. 1. Within your flutter application, re-run \`flutter build ios-framework\` if you haven't already: \`\`\`console $ flutter build ios-framework --output=/path/to/MyApp/Flutter/ \`\`\` This will generate the LLDB files in the \`/path/to/MyApp/Flutter/\` directory. 1. Set the LLDB Init File. 1. Go to \*\*Product > Scheme > Edit Scheme\*\*. 1. Select the \*\*Run\*\* section in the left side bar. 1. Set the \*\*LLDB Init File\*\* to the following: \`\`\`console $(PROJECT\_DIR)/Flutter/flutter\_lldbinit \`\`\` If your scheme already has an \*\*LLDB Init File\*\*, you can add Flutter's LLDB file to it. The path to Flutter's LLDB Init File must be relative to the location of your project's LLDB Init File. For example, if your LLDB file is located at \`/path/to/MyApp/.lldbinit\`, you would add the following: \`\`\`console command source --relative-to-command-file "Flutter/flutter\_lldbinit" \`\`\` \[static or dynamic frameworks]: https\://stackoverflow\.com/questions/32591878/ios-is-it-a-static-or-a-dynamic-framework \[static-framework]: https\://developer.apple.com/library/archive/technotes/tn2435/\_index.html ## Set local network privacy permissions On iOS 14 and later, enable the Dart multicast DNS service in the \*\*Debug\*\* version of your iOS app. This adds \[debugging functionalities such as hot-reload and DevTools]\[] using \`flutter attach\`. :::warning Never enable this service in the \*\*Release\*\* version of your app. The Apple App Store might reject your app. ::: To set local network privacy permissions only in the Debug version of your app, create a separate \`Info.plist\` per build configuration. SwiftUI projects start without an \`Info.plist\` file. If you need to create a property list, you can do so through Xcode or text editor. The following instructions assume the default \*\*Debug\*\* and \*\*Release\*\*. Adjust the names as needed depending on your app's build configurations. 1. Create a new property list. 1. Open your project in Xcode. 1. In the \*\*Project Navigator\*\*, click on the project name. 1. From the \*\*Targets\*\* list in the Editor pane, click on your app. 1. Click the \*\*Info\*\* tab. 1. Expand \*\*Custom iOS Target Properties\*\*. 1. Right-click on the list and select \*\*Add Row\*\*. 1. From the dropdown menu, select \*\*Bonjour Services\*\*. This creates a new property list in the project directory called \`Info\`. This displays as \`Info.plist\` in the Finder. 1. Rename the \`Info.plist\` to \`Info-Debug.plist\` 1. Click on \*\*Info\*\* file in the project list at the left. 1. In the \*\*Identity and Type\*\* panel at the right, change the \*\*Name\*\* from \`Info.plist\` to \`Info-Debug.plist\`. 1. Create a Release property list. 1. In the \*\*Project Navigator\*\*, click on \`Info-Debug.plist\`. 1. Select \*\*File\*\* > \*\*Duplicate...\*\*. You can also press `Cmd` + `Shift` + `S`. 1. In the dialog box, set the \*\*Save As:\*\* field to \`Info-Release.plist\` and click \*\*Save\*\*. 1. Add the necessary properties to the \*\*Debug\*\* property list. 1. In the \*\*Project Navigator\*\*, click on \`Info-Debug.plist\`. 1. Add the String value \`\_dartVmService.\_tcp\` to the \*\*Bonjour Services\*\* array. 1. \_(Optional)\_ To set your desired customized permission dialog text, add the key \*\*Privacy - Local Network Usage Description\*\*. 1. Set the target to use different property lists for different build modes. 1. In the \*\*Project Navigator\*\*, click on your project. 1. Click the \*\*Build Settings\*\* tab. 1. Click \*\*All\*\* and \*\*Combined\*\* sub-tabs. 1. In the Search box, type \`plist\`. This limits the settings to those that include property lists. 1. Scroll through the list until you see \*\*Packaging\*\*. 1. Click on the \*\*Info.plist File\*\* setting. 1. Change the \*\*Info.plist File\*\* value from \`path/to/Info.plist\` to \`path/to/Info-$(CONFIGURATION).plist\`. This resolves to the path \*\*Info-Debug.plist\*\* in \*\*Debug\*\* and \*\*Info-Release.plist\*\* in \*\*Release\*\*. 1. Remove the \*\*Release\*\* property list from the \*\*Build Phases\*\*. 1. In the \*\*Project Navigator\*\*, click on your project. 1. Click the \*\*Build Phases\*\* tab. 1. Expand \*\*Copy Bundle Resources\*\*. 1. If this list includes \`Info-Release.plist\`, click on it and then click the \*\*-\*\* (minus sign) under it to remove the property list from the resources list. 1. The first Flutter screen your Debug app loads prompts for local network permission. Click \*\*OK\*\*. \_(Optional)\_ To grant permission before the app loads, enable \*\*Settings > Privacy > Local Network > Your App\*\*. \[debugging functionalities such as hot-reload and DevTools]: /add-to-app/debugging ## Mitigate known issue with Apple Silicon Macs On \[Macs running Apple Silicon]\[apple-silicon], the host app builds for an \`arm64\` simulator. While Flutter supports \`arm64\` simulators, some plugins might not. If you use one of these plugins, you might see a compilation error like \*\*Undefined symbols for architecture arm64\*\*. If this occurs, exclude \`arm64\` from the simulator architectures in your host app. 1. In the \*\*Project Navigator\*\*, click on your project. 1. Click the \*\*Build Settings\*\* tab. 1. Click \*\*All\*\* and \*\*Combined\*\* sub-tabs. 1. Under \*\*Architectures\*\*, click on \*\*Excluded Architectures\*\*. 1. Expand to see the available build configurations. 1. Click \*\*Debug\*\*. 1. Click the \*\*+\*\* (plus sign). 1. Select \*\*iOS Simulator\*\*. 1. Double-click in the value column for \*\*Any iOS Simulator SDK\*\*. 1. Click the \*\*+\*\* (plus sign). 1. Type \`arm64\` in the \*\*Debug > Any iOS Simulator SDK\*\* dialog box. 1. Press `Esc` to close this dialog box. 1. Repeat these steps for the \*\*Release\*\* build mode. 1. Repeat for any iOS unit test targets. ## Next steps You can now \[add a Flutter screen]\[] to your existing iOS app. \[add\_to\_app code samples]: https\://github.com/flutter/samples/tree/main/add\_to\_app \[add a Flutter screen]: /add-to-app/ios/add-flutter-screen \[Android Studio/IntelliJ]: /tools/android-studio \[build modes of Flutter]: /testing/build-modes \[CocoaPods]: https\://cocoapods.org/ \[app-size]: /resources/faq#how-big-is-the-flutter-engine \[VS Code]: /tools/vs-code \[News Feed app]: https\://github.com/flutter/put-flutter-to-work/tree/022208184ec2623af2d113d13d90e8e1ce722365 \[Debugging your add-to-app module]: /add-to-app/debugging/ \[apple-silicon]: https\://support.apple.com/en-us/116943 \[read-only]: https\://blog.cocoapods.org/CocoaPods-Specs-Repo/ \[updated integration guide]: /add-to-app/ios/project-setup

----
url: https://docs.flutter.dev/cookbook/persistence/key-value
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Store key-value data on disk](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

If you have a relatively small collection of key-values to save, you can use the [`shared_preferences`](https://pub.dev/packages/shared_preferences) plugin.

Normally, you would have to write native platform integrations for storing data on each platform. Fortunately, the [`shared_preferences`](https://pub.dev/packages/shared_preferences) plugin can be used to persist key-value data to disk on each platform Flutter supports. To compare `shared_preferences` with other local and cloud database options, check out [Choose a database for your Flutter app](/data-and-backend/persistence/choose-a-database).

This recipe uses the following steps:

1. Add the dependency.
2. Save data.
3. Read data.
4. Remove data.

## 1. Add the dependency

[#](#1-add-the-dependency)

Before starting, add the [`shared_preferences`](https://pub.dev/packages/shared_preferences) package as a dependency.

To add the `shared_preferences` package as a dependency, run `flutter pub add`:

```
flutter pub add shared_preferences
```

## 2. Save data

[#](#2-save-data)

To persist data, use the setter methods provided by the `SharedPreferences` class. Setter methods are available for various primitive types, such as `setInt`, `setBool`, and `setString`.

Setter methods do two things: First, synchronously update the key-value pair in memory. Then, persist the data to disk.

dart

```
// Load and obtain the shared preferences for this app.
final prefs = await SharedPreferences.getInstance();
​
// Save the counter value to persistent storage under the 'counter' key.
await prefs.setInt('counter', counter);
```

## 3. Read data

[#](#3-read-data)

To read data, use the appropriate getter method provided by the `SharedPreferences` class. For each setter there is a corresponding getter. For example, you can use the `getInt`, `getBool`, and `getString` methods.

dart

```
final prefs = await SharedPreferences.getInstance();
​
// Try reading the counter value from persistent storage.
// If not present, null is returned, so default to 0.
final counter = prefs.getInt('counter') ?? 0;
```

Note that the getter methods throw an exception if the persisted value has a different type than the getter method expects.

## 4. Remove data

[#](#4-remove-data)

To delete data, use the `remove()` method.

dart

```
final prefs = await SharedPreferences.getInstance();
​
// Remove the counter key-value pair from persistent storage.
await prefs.remove('counter');
```

## Supported types

[#](#supported-types)

Although the key-value storage provided by `shared_preferences` is easy and convenient to use, it has limitations:

* Only primitive types can be used: `int`, `double`, `bool`, `String`, and `List<String>`.
* It's not designed to store large amounts of data.
* There is no guarantee that data will be persisted across app restarts.

## Testing support

[#](#testing-support)

It's a good idea to test code that persists data using `shared_preferences`. To enable this, the package provides an in-memory mock implementation of the preference store.

To set up your tests to use the mock implementation, call the `setMockInitialValues` static method in a `setUpAll()` method in your test files. Pass in a map of key-value pairs to use as the initial values.

dart

```
SharedPreferences.setMockInitialValues(<String, Object>{'counter': 2});
```

## Complete example

[#](#complete-example)

dart

```
import 'package:material_ui/material_ui.dart';
import 'package:shared_preferences/shared_preferences.dart';
​
void main() => runApp(const MyApp());
​
class MyApp extends StatelessWidget {
  const MyApp({super.key});
​
  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      title: 'Shared preferences demo',
      home: MyHomePage(title: 'Shared preferences demo'),
    );
  }
}
​
class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});
​
  final String title;
​
  @override
  State<MyHomePage> createState() => _MyHomePageState();
}
​
class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;
​
  @override
  void initState() {
    super.initState();
    _loadCounter();
  }
​
  /// Load the initial counter value from persistent storage on start,
  /// or fallback to 0 if it doesn't exist.
  Future<void> _loadCounter() async {
    final prefs = await SharedPreferences.getInstance();
    setState(() {
      _counter = prefs.getInt('counter') ?? 0;
    });
  }
​
  /// After a click, increment the counter state and
  /// asynchronously save it to persistent storage.
  Future<void> _incrementCounter() async {
    final prefs = await SharedPreferences.getInstance();
    setState(() {
      _counter = (prefs.getInt('counter') ?? 0) + 1;
      prefs.setInt('counter', _counter);
    });
  }
​
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text(widget.title)),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            const Text('You have pushed the button this many times: '),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.headlineMedium,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ),
    );
  }
}
```

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-10-13. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/persistence/key-value.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/cookbook/persistence/key-value\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/persistence/key-value.md "Report an issue with this page").

----
url: https://docs.flutter.dev/packages-and-plugins/developing-packages
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Developing packages & plugins](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

## Package introduction

[#](#package-introduction)

Packages enable the creation of modular code that can be shared easily. A minimal package consists of the following:

* **`pubspec.yaml`**

  A metadata file that declares the package name, version, author, and so on.

* **`lib`**

  The `lib` directory contains the public code in the package, minimally a single `<package-name>.dart` file.

### Package types

[#](#types)

Packages can contain more than one kind of content:

* **Dart packages**

  General packages written in Dart, for example the [`path`](https://pub.dev/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`](https://pub.dev/packages/fluro) package.

* **Plugin packages**

  A specialized Dart package that contains an API written in Dart code combined with one or more platform-specific implementations.

  Plugin packages can be written for Android (using Kotlin or Java), iOS (using Swift or Objective-C), web, macOS, Windows, or Linux, or any combination thereof.

  A concrete example is the [`url_launcher`](https://pub.dev/packages/url_launcher) plugin package. To see how to use the `url_launcher` package, and how it was extended to implement support for web, see the Flutter blog post by Harry Terkelsen, [How to Write a Flutter Web Plugin, Part 1](https://blog.flutter.dev/how-to-write-a-flutter-web-plugin-5e26c689ea1).

* **FFI packages**

  A specialized Dart package that enables calling native code using `dart:ffi`. These packages work in Dart standalone and don't require OS-specific build files. They are created with the `flutter create --template=package_ffi` command (see [Create an FFI package](/platform-integration/bind-native-code)). This is the recommended approach to build and bundle native code since Flutter 3.38.

## Developing Dart packages

[#](#dart)

The following instructions explain how to write a Flutter package.

### Step 1: Create the package

[#](#step-1-create-the-package)

To create a starter Flutter package, use the `--template=package` flag with `flutter create`:

```
$ flutter create --template=package hello
```

This creates a package project in the `hello` folder with the following content:

* **LICENSE**

  A (mostly) empty license text file.

* **test/hello\_test.dart**

  The [unit tests](/testing/overview#unit-tests) for the package.

* **hello.iml**

  A configuration file used by the IntelliJ IDEs.

* **.gitignore**

  A hidden file that tells Git which files or folders to ignore in a project.

* **.metadata**

  A hidden file used by IDEs to track the properties of the Flutter project.

* **pubspec.yaml**

  A yaml file containing metadata that specifies the package's dependencies. Used by the pub tool.

* **README.md**

  A starter markdown file that briefly describes the package's purpose.

* **lib/hello.dart**

  A starter app containing Dart code for the package.

* **.idea/modules.xml**, **.idea/workspace.xml**

  A hidden folder containing configuration files for the IntelliJ IDEs.

* **CHANGELOG.md**

  A (mostly) empty markdown file for tracking version changes to the package.

### Step 2: Implement the package

[#](#step-2-implement-the-package)

For pure Dart packages, simply add the functionality inside the main `lib/<package name>.dart` file, or in several files in the `lib` directory.

To test the package, add [unit tests](/testing/overview#unit-tests) in a `test` directory.

For additional details on how to organize the package contents, see the [Dart library package](https://dart.dev/guides/libraries/create-library-packages) documentation.

## Developing plugin packages

[#](#plugin)

If you want to develop a package that calls into platform-specific APIs, you need to develop a plugin package.

The API is connected to the platform-specific implementation(s) using a [platform channel](/platform-integration/platform-channels).

### Supporting multiple Flutter instances

[#](#supporting-multiple-flutter-instances)

All Flutter platforms allow apps to create [multiple Flutter engines](/add-to-app/multiple-flutters). Each engine creates its own instance of every registered plugin, and those plugin instances can have independent lifetimes. Don't assume that a plugin class has only one instance. Keep engine-specific state on the plugin instance rather than in global or static variables, and clean it up when the plugin detaches from its engine.

If plugin instances share a native singleton or other global resource, coordinate access across the instances, for example by using reference counting. Release the resource only after every plugin instance has stopped using it.

### Federated plugins

[#](#federated-plugins)

**Federated plugins** are a way of splitting the API of a plugin into a platform interface, independent platform implementations of that interface, and an app-facing interface that uses the registered implementation of the running platform.

**Package-separated federated plugins** are federated plugins where the platform interface, platform implementations, and the app-facing interface are all separated into their own Dart packages.

So, a package-separated federated plugin can use one package for iOS, another for Android, another for web, and yet another for a car (as an example of an IoT device). Among other benefits, this approach allows a domain expert to extend an existing plugin to work for the platform they know best.

A federated plugin requires the following:

* **app-facing interface**

  The interface that plugin users interact with when using the plugin. This interface specifies the API used by the Flutter app. In a package-separated federated plugin, this is the package that plugin users depend on to use the plugin.

* **platform implementation(s)**

  One or more implementations that contain the platform-specific implementation code. The app-facing interface calls into these implementations—they aren't directly used, or depended on when package-separated, in an app unless they contain platform-specific functionality accessible to the end user.

* **platform interface**

  The interface that glues the app-facing interface to the platform implementations(s). This declares an interface that any platform implementation must implement to support the app-facing interface. Having a separate package that defines this interface ensures that all platform packages implement the same functionality in a uniform way.

#### Endorsed federated plugin

[#](#endorsed-federated-plugin)

Ideally, when adding a platform implementation to a packaged-separated federated plugin, you will coordinate with the package author to include your implementation. In this way, the original author *endorses* your implementation.

For example, say you write a `foobar_windows` implementation for the (imaginary) `foobar` plugin. In an endorsed plugin, the original `foobar` author adds your Windows implementation as a dependency in the pubspec for the app-facing package. Then, when a developer includes the `foobar` plugin in their Flutter app, the Windows implementation, as well as the other endorsed implementations, are automatically available to the app.

#### Non-endorsed federated plugin

[#](#non-endorsed-federated-plugin)

If you can't, for whatever reason, get your implementation added by the original plugin author, then your plugin is *not* endorsed. A developer can still use your implementation, but must manually add the plugin to the app's `pubspec.yaml` file:

yaml

```
dependencies:
  foobar: ^1.0.0
  foobar_windows: ^1.0.0 # Non-endorsed plugin implementation
```

This approach also works for overriding an already endorsed plugin implementation of `foobar`.

For more information on federated plugins, why they are useful, and how they are implemented, see the Flutter blog post by Harry Terkelsen, [How To Write a Flutter Web Plugin, Part 2](https://blog.flutter.dev/how-to-write-a-flutter-web-plugin-part-2-afdddb69ece6).

### Specifying a plugin's supported platforms

[#](#plugin-platforms)

Plugins can specify the platforms they support by adding keys to the `platforms` map in the `pubspec.yaml` file. For example, the following pubspec file shows the `flutter:` map for the `hello` plugin, which supports only iOS and Android:

yaml

```
flutter:
  plugin:
    platforms:
      android:
        package: com.example.hello
        pluginClass: HelloPlugin
      ios:
        pluginClass: HelloPlugin
```

When adding plugin implementations for more platforms, the `platforms` map should be updated accordingly. For example, here's the map in the pubspec file for the `hello` plugin, when updated to add support for macOS and web:

yaml

```
flutter:
  plugin:
    platforms:
      android:
        package: com.example.hello
        pluginClass: HelloPlugin
      ios:
        pluginClass: HelloPlugin
      macos:
        pluginClass: HelloPlugin
      web:
        pluginClass: HelloPlugin
        fileName: hello_web.dart
```

#### Federated platform packages

[#](#federated-platform-packages)

A platform package uses the same format, but includes an `implements` entry indicating which app-facing package it implements. For example, a `hello_windows` plugin containing the Windows implementation for `hello` would have the following `flutter:` map:

yaml

```
flutter:
  plugin:
    implements: hello
    platforms:
      windows:
        pluginClass: HelloPlugin
```

#### Endorsed implementations

[#](#endorsed-implementations)

An app facing package can endorse a platform package by adding a dependency on it, and including it as a `default_package` in the `platforms:` map. If the `hello` plugin above endorsed `hello_windows`, it would look as follows:

yaml

```
flutter:
  plugin:
    platforms:
      android:
        package: com.example.hello
        pluginClass: HelloPlugin
      ios:
        pluginClass: HelloPlugin
      windows:
        default_package: hello_windows
​
dependencies:
  hello_windows: ^1.0.0
```

Note that, as shown here, an app-facing package can have some platforms implemented within the package, and others in endorsed federated implementations.

#### Shared iOS and macOS implementations

[#](#shared-ios-and-macos-implementations)

Many frameworks support both iOS and macOS with identical or mostly identical APIs, making it possible to implement some plugins for both iOS and macOS with the same codebase. Normally each platform's implementation is in its own folder, but the `sharedDarwinSource` option allows iOS and macOS to use the same folder instead:

yaml

```
flutter:
  plugin:
    platforms:
      ios:
        pluginClass: HelloPlugin
        sharedDarwinSource: true
      macos:
        pluginClass: HelloPlugin
        sharedDarwinSource: true
​
environment:
  sdk: ^3.0.0
  # Flutter versions prior to 3.7 did not support the
  # sharedDarwinSource option.
  flutter: ">=3.7.0"
```

When `sharedDarwinSource` is enabled, instead of an `ios` directory for iOS and a `macos` directory for macOS, both platforms use a shared `darwin` directory for all code and resources. When enabling this option, you need to move any existing files from `ios` and `macos` to the shared directory. You also need to update the podspec file to set the dependencies and deployment targets for both platforms, for example:

ruby

```
  s.ios.dependency 'Flutter'
  s.osx.dependency 'FlutterMacOS'
  s.ios.deployment_target = '13.0'
  s.osx.deployment_target = '10.15'
```

### Step 1: Create the package

[#](#step-1-create-the-package-1)

To create a plugin package, use the `--template=plugin` flag with `flutter create`.

Use the `--platforms=` option followed by a comma-separated list to specify the platforms that the plugin supports. Available platforms are: `android`, `ios`, `web`, `linux`, `macos`, and `windows`. If no platforms are specified, the resulting project doesn't support any platforms.

Use the `--org` option to specify your organization, using reverse domain name notation. This value is used in various package and bundle identifiers in the generated plugin code.

By default, the plugin project uses Swift for iOS code and Kotlin for Android code. If you prefer Objective-C or Java, you can specify the iOS language using `-i` and the Android language using `-a`. Please choose **one** of the following:

```
$ flutter create --org com.example --template=plugin --platforms=android,ios,linux,macos,windows -a kotlin hello
```

```
$ flutter create --org com.example --template=plugin --platforms=android,ios,linux,macos,windows -a java hello
```

```
$ flutter create --org com.example --template=plugin --platforms=android,ios,linux,macos,windows -i objc hello
```

```
$ flutter create --org com.example --template=plugin --platforms=android,ios,linux,macos,windows -i swift hello
```

This creates a plugin project in the `hello` folder with the following specialized content:

* **`lib/hello.dart`**

  The Dart API for the plugin.

* **`android/src/main/java/com/example/hello/HelloPlugin.kt`**

  The Android platform-specific implementation of the plugin API in Kotlin.

* **`ios/Classes/HelloPlugin.m`**

  The iOS-platform specific implementation of the plugin API in Objective-C.

* **`example/`**

  A Flutter app that depends on the plugin, and illustrates how to use it.

### Step 2: Implement the package

[#](#edit-plugin-package)

As a plugin package contains code for several platforms written in several programming languages, some specific steps are needed to ensure a smooth experience.

#### Step 2a: Define the package API (.dart)

[#](#step-2a-define-the-package-api-dart)

The API of the plugin package is defined in Dart code. Open the main `hello/` folder in your favorite [Flutter editor](/tools/editors). Locate the file `lib/hello.dart`.

#### Step 2b: Add Android platform code (.kt/.java)

[#](#step-2b-add-android-platform-code-kt-java)

We recommend you edit the Android code using Android Studio.

Before editing the Android platform code in Android Studio, first make sure that the code has been built at least once (in other words, run the example app from your IDE/editor, or in a terminal execute:

```
cd hello/example; flutter build apk --config-only
```

Then use the following steps:

1. Launch Android Studio.
2. Select **Open an existing Android Studio Project** in the **Welcome to Android Studio** dialog, or select **File > Open** from the menu, and select either the `hello/example/android/build.gradle` or the `hello/example/android/build.gradle.kts` file.
3. In the **Gradle Sync** dialog, select **OK**.
4. In the **Android Gradle Plugin Update** dialog, select **Don't remind me again for this project**.

The Android platform code of your plugin is located in `hello/java/com.example.hello/HelloPlugin`.

You can run the example app from Android Studio by pressing the run (▶) button.

#### Step 2c: Add iOS platform code (.swift/.h+.m)

[#](#step-2c-add-ios-platform-code-swift-h-m)

We recommend you edit the iOS code using Xcode.

Before editing the iOS platform code in Xcode, first make sure that the code has been built at least once (in other words, run the example app from your IDE/editor, or in a terminal execute `cd hello/example; flutter build ios --no-codesign --config-only`).

Then use the following steps:

1. Launch Xcode.
2. Select **File > Open**, and select the `hello/example/ios/Runner.xcworkspace` file.

The iOS platform code for your plugin is located in `Pods/Development Pods/hello/../../example/ios/.symlinks/plugins/hello/ios/Classes` in the Project Navigator. (If you are using `sharedDarwinSource`, the path will end with `Flutter/hello/Sources/hello` instead.)

You can run the example app by pressing the run (▶) button.

##### Add native Darwin dependencies (Swift Package Manager)

[#](#add-native-darwin-dependencies-swift-package-manager)

Flutter uses Swift Package Manager as the primary strategy to manage native iOS and macOS dependencies.

To add a dependency to your plugin using Swift Package Manager:

1. Create a `Package.swift` file in your plugin's `ios/my_plugin_name` or `macos/my_plugin_name` directory.
2. Declare your native dependency within the `dependencies` array of the `Package.swift` descriptor file:

Package.swift

swift

```
dependencies: [
    .package(url: "https://github.com/path/to/HelloLibrary.git", from: "1.0.0")
]
```

For complete details and instructions on structuring native folders, resource bundling, or handling hybrid setups, visit the [Swift Package Manager for plugin authors](/packages-and-plugins/swift-package-manager/for-plugin-authors) guide.

##### Add CocoaPod dependencies (legacy)

[#](#add-cocoapod-dependencies-legacy)

Flutter continues to support CocoaPods for backward compatibility. If your plugin needs to support developers who haven't migrated to Swift Package Manager yet, specify your CocoaPods dependency at the end of `ios/hello.podspec`:

ruby

```
s.dependency 'HelloPod', '0.0.1'
```

For private pods, refer to [Private CocoaPods](https://guides.cocoapods.org/making/private-cocoapods.html) to ensure repo access:

ruby

```
s.source = {
    # For pods hosted on GitHub
    :git => "https://github.com/path/to/HelloPod.git",
    # Alternatively, for pods hosted locally
    # :path => "file:///path/to/private/repo",
    :tag => s.version.to_s
  }
```

##### Installing the plugin dependencies

[#](#installing-the-plugin-dependencies)

To fetch and link the plugin's dependencies, add the plugin to your app project's `pubspec.yaml` dependencies, and run `flutter pub get`. Flutter automatically resolves and wires up the Swift Package Manager descriptors or CocoaPods pod files during the native app build step.

If your plugin requires a privacy manifest, for example, if it uses any **required reason APIs**, update the `PrivacyInfo.xcprivacy` file to describe your plugin's privacy impact, and add the following to the bottom of your podspec file:

ruby

```
s.resource_bundles = {'your_plugin_privacy' => ['your_plugin/Sources/your_plugin/Resources/PrivacyInfo.xcprivacy']}
```

For more information, check out [Privacy manifest files](https://developer.apple.com/documentation/bundleresources/privacy_manifest_files) on the Apple developer site.

#### Step 2d: Add Linux platform code (.h+.cc)

[#](#step-2d-add-linux-platform-code-h-cc)

We recommend you edit the Linux code using an IDE with C++ integration. The instructions below are for Visual Studio Code with the "C/C++" and "CMake" extensions installed, but can be adjusted for other IDEs.

Before editing the Linux platform code in an IDE, first make sure that the code has been built at least once (in other words, run the example app from your Flutter IDE/editor, or in a terminal execute `cd hello/example; flutter build linux`).

Then use the following steps:

1. Launch Visual Studio Code.
2. Open the `hello/example/linux/` directory.
3. Choose **Yes** in the prompt asking: `Would you like to configure project "linux"?`. This will allow C++ autocomplete to work.

The Linux platform code for your plugin is located in `flutter/ephemeral/.plugin_symlinks/hello/linux/`.

You can run the example app using `flutter run`. **Note:** Creating a runnable Flutter application on Linux requires steps that are part of the `flutter` tool, so even if your editor provides CMake integration building and running that way won't work correctly.

#### Step 2e: Add macOS platform code (.swift)

[#](#step-2e-add-macos-platform-code-swift)

We recommend you edit the macOS code using Xcode.

Before editing the macOS platform code in Xcode, first make sure that the code has been built at least once (in other words, run the example app from your IDE/editor, or in a terminal execute `cd hello/example; flutter build macos --config-only`).

Then use the following steps:

1. Launch Xcode.
2. Select **File > Open**, and select the `hello/example/macos/Runner.xcworkspace` file.

The macOS platform code for your plugin is located in `Pods/Development Pods/hello/../../example/macos/Flutter/ephemeral/.symlinks/plugins/hello/macos/Classes` in the Project Navigator. (If you are using `sharedDarwinSource`, the path will end with `hello/darwin/Classes` instead.)

You can run the example app by pressing the run (▶) button.

#### Step 2f: Add Windows platform code (.h+.cpp)

[#](#step-2f-add-windows-platform-code-h-cpp)

We recommend you edit the Windows code using Visual Studio.

Before editing the Windows platform code in Visual Studio, first make sure that the code has been built at least once (in other words, run the example app from your IDE/editor, or in a terminal execute `cd hello/example; flutter build windows`).

Then use the following steps:

1. Launch Visual Studio.
2. Select **Open a project or solution**, and select the `hello/example/build/windows/hello_example.sln` file.

The Windows platform code for your plugin is located in `hello_plugin/Source Files` and `hello_plugin/Header Files` in the Solution Explorer.

You can run the example app by right-clicking `hello_example` in the Solution Explorer and selecting **Set as Startup Project**, then pressing the run (▶) button. **Important:** After making changes to plugin code, you must select **Build > Build Solution** before running again, otherwise an outdated copy of the built plugin will be run instead of the latest version containing your changes.

#### Step 2g: Connect the API and the platform code

[#](#step-2g-connect-the-api-and-the-platform-code)

Finally, you need to connect the API written in Dart code with the platform-specific implementations. This is done using a [platform channel](/platform-integration/platform-channels), or through the interfaces defined in a platform interface package.

### Add support for platforms in an existing plugin project

[#](#add-support-for-platforms-in-an-existing-plugin-project)

To add support for specific platforms to an existing plugin project, run `flutter create` with the `--template=plugin` flag again in the project directory. For example, to add web support in an existing plugin, run:

```
$ flutter create --template=plugin --platforms=web .
```

If this command displays a message about updating the `pubspec.yaml` file, follow the provided instructions.

### Dart platform implementations

[#](#dart-platform-implementations)

In many cases, non-web platform implementations only use the platform-specific implementation language, as shown above. However, platform implementations can also use platform-specific Dart as well.

#### Dart-only platform implementations

[#](#dart-only-platform-implementations)

In some cases, some platforms can be implemented entirely in Dart (for example, using FFI). For a Dart-only platform implementation on a platform other than web, replace the `pluginClass` in pubspec.yaml with a `dartPluginClass`. Here is the `hello_windows` example above modified for a Dart-only implementation:

yaml

```
flutter:
  plugin:
    implements: hello
    platforms:
      windows:
        dartPluginClass: HelloPluginWindows
```

In this version you would have no C++ Windows code, and would instead subclass the `hello` plugin's Dart platform interface class with a `HelloPluginWindows` class that includes a static `registerWith()` method. This method is called during startup, and can be used to register the Dart implementation:

dart

```
class HelloPluginWindows extends HelloPluginPlatform {
  /// Registers this class as the default instance of [HelloPluginPlatform].
  static void registerWith() {
    HelloPluginPlatform.instance = HelloPluginWindows();
  }
```

#### Hybrid platform implementations

[#](#hybrid-platform-implementations)

Platform implementations can also use both Dart and a platform-specific language. For example, a plugin could use a different platform channel for each platform so that the channels can be customized per platform.

A hybrid implementation uses both of the registration systems described above. Here is the `hello_windows` example above modified for a hybrid implementation:

yaml

```
flutter:
  plugin:
    implements: hello
    platforms:
      windows:
        dartPluginClass: HelloPluginWindows
        pluginClass: HelloPlugin
```

The Dart `HelloPluginWindows` class would use the `registerWith()` shown above for Dart-only implementations, while the C++ `HelloPlugin` class would be the same as in a C++-only implementation.

### Testing your plugin

[#](#testing-your-plugin)

We encourage you test your plugin with automated tests to ensure that functionality doesn't regress as you make changes to your code.

To learn more about testing your plugins, check out [Testing plugins](/testing/testing-plugins). If you are writing tests for your Flutter app and plugins are causing crashes, check out [Flutter in plugin tests](/testing/plugins-in-tests).

## Developing FFI packages

[#](#ffi)

If you want to develop a package that calls into native APIs using Dart's FFI, you need to develop an FFI package.

### Step 1: Create the package

[#](#step-1-create-the-package-2)

To create a starter FFI package, use the `--template=package_ffi` flag with `flutter create`:

```
$ flutter create --template=package_ffi hello
```

This creates an FFI package project in the `hello` folder with the following specialized content:

**lib**: The Dart code that defines the API of the package, and which calls into the native code using `dart:ffi`.

**src**: The native source code.

**hook/build.dart**: The build hook script that compiles the native code.

### Step 2: Binding to native code

[#](#step-2-binding-to-native-code)

To use the native code, bindings in Dart are needed.

To avoid writing these by hand, they are generated from the header file (`src/hello.h`) by [`package:ffigen`](https://pub.dev/packages/ffigen). Reference the [ffigen docs](https://pub.dev/packages/ffigen/install) for information on how to install this package.

To regenerate the bindings, run the following command:

```
$ dart run tool/ffigen.dart
```

### Step 3: Invoking native code

[#](#step-3-invoking-native-code)

Very short-running native functions can be directly invoked from any isolate. For an example, see `sum` in `lib/hello.dart`.

Longer-running functions should be invoked on a [helper isolate](https://dart.dev/guides/language/concurrency#background-workers) to avoid dropping frames in Flutter applications. For an example, see `sumAsync` in `lib/hello.dart`.

## Adding documentation

[#](#adding-documentation)

It is recommended practice to add the following documentation to all packages:

1. A `README.md` file that introduces the package
2. A `CHANGELOG.md` file that documents changes in each version
3. A [`LICENSE`](#adding-licenses-to-the-license-file) file containing the terms under which the package is licensed
4. API documentation for all public APIs (see below for details)

### API documentation

[#](#api-documentation)

When you publish a package, API documentation is automatically generated and published to pub.dev/documentation. For example, see the docs for [`device_info_plus`](https://pub.dev/documentation/device_info_plus).

If you wish to generate API documentation locally on your development machine, use the following commands:

1. Change directory to the location of your package:

   ```
   cd ~/dev/mypackage
   ```

2. Tell the documentation tool where the Flutter SDK is located (change the following commands to reflect where you placed it):

   ```
      export FLUTTER_ROOT=~/dev/flutter  # on macOS or Linux

      set FLUTTER_ROOT=~/dev/flutter     # on Windows
   ```

3. Run the \`dart doc\` tool (included as part of the Flutter SDK), as follows:
   ```
      $FLUTTER_ROOT/bin/cache/dart-sdk/bin/dart doc   # on macOS or Linux

      %FLUTTER_ROOT%\bin\cache\dart-sdk\bin\dart doc  # on Windows
   ```

For tips on how to write API documentation, see [Effective Dart Documentation](https://dart.dev/guides/language/effective-dart/documentation).

### Adding licenses to the LICENSE file

[#](#adding-licenses-to-the-license-file)

Individual licenses inside each LICENSE file should be separated by 80 hyphens on their own on a line.

If a LICENSE file contains more than one component license, then each component license must start with the names of the packages to which the component license applies, with each package name on its own line, and the list of package names separated from the actual license text by a blank line. (The packages need not match the names of the pub package. For example, a package might itself contain code from multiple third-party sources, and might need to include a license for each one.)

The following example shows a well-organized license file:

```
package_1

<some license text>

--------------------------------------------------------------------------------
package_2

<some license text>
```

Here is another example of a well-organized license file:

```
package_1

<some license text>

--------------------------------------------------------------------------------
package_1
package_2

<some license text>
```

Here is an example of a poorly-organized license file:

```
<some license text>

--------------------------------------------------------------------------------
<some license text>
```

Another example of a poorly-organized license file:

```
package_1

<some license text>
--------------------------------------------------------------------------------
<some license text>
```

## Publishing your package

[#](#publish)

Once you have implemented a package, you can publish it on [pub.dev](https://pub.dev), so that other developers can easily use it.

Prior to publishing, make sure to review the `pubspec.yaml`, `README.md`, and `CHANGELOG.md` files to make sure their content is complete and correct. Also, to improve the quality and usability of your package (and to make it more likely to achieve the status of a Flutter Favorite), consider including the following items:

* Diverse code usage examples
* Screenshots, animated gifs, or videos
* A link to the corresponding code repository

Next, run the publish command in `dry-run` mode to see if everything passes analysis:

```
$ flutter pub publish --dry-run
```

The next step is publishing to pub.dev, but be sure that you are ready because [publishing is forever](https://dart.dev/tools/pub/publishing#publishing-is-forever):

```
$ flutter pub publish
```

For more details on publishing, see the [publishing docs](https://dart.dev/tools/pub/publishing) on dart.dev.

## Handling package interdependencies

[#](#dependencies)

If you are developing a package `hello` that depends on the Dart API exposed by another package, you need to add that package to the `dependencies` section of your `pubspec.yaml` file. The code below makes the Dart API of the `url_launcher` plugin available to `hello`:

yaml

```
dependencies:
  url_launcher: ^6.3.2
```

You can now `import 'package:url_launcher/url_launcher.dart'` and `launch(someUrl)` in the Dart code of `hello`.

This is no different from how you include packages in Flutter apps or any other Dart project.

But if `hello` happens to be a *plugin* package whose platform-specific code needs access to the platform-specific APIs exposed by `url_launcher`, you also need to add suitable dependency declarations to your platform-specific build files, as shown below.

### Android

[#](#android)

The following example sets a dependency for `url_launcher` in `hello/android/build.gradle`:

groovy

```
android {
    // lines skipped
    dependencies {
        compileOnly rootProject.findProject(":url_launcher")
    }
}
```

You can now `import io.flutter.plugins.urllauncher.UrlLauncherPlugin` and access the `UrlLauncherPlugin` class in the source code at `hello/android/src`.

For more information on `build.gradle` files, see the [Gradle Documentation](https://docs.gradle.org/current/userguide/tutorial_using_tasks.html) on build scripts.

### iOS

[#](#ios)

The following example sets a dependency for `url_launcher` in `hello/ios/hello/Package.swift`:

swift

```
let package = Package(
  // lines skipped
  dependencies: [
    .package(path: "../url_launcher"),
  ],
  targets: [
    .target(
      name: "hello",
      dependencies: [
        .product(name: "url_launcher", package: "url_launcher"),
      ],
    ),
  ],
)
```

You can now `import url_launcher` and access the source code at `hello/ios/hello/Sources`.

For additional details on Swift package manifests, consult [Apple's PackageDescription documentation](https://developer.apple.com/documentation/packagedescription).

#### CocoaPods (legacy)

[#](#cocoapods-legacy)

If your plugin supports CocoaPods for backward compatibility, set a dependency in `hello/ios/hello.podspec`:

ruby

```
Pod::Spec.new do |s|
  # lines skipped
  s.dependency 'url_launcher'
```

You can now `#import "UrlLauncherPlugin.h"` and access the `UrlLauncherPlugin` class in the source code at `hello/ios/Classes`.

For additional details on `.podspec` files, consult the [CocoaPods Documentation](https://guides.cocoapods.org/syntax/podspec.html).

### Web

[#](#web)

All web dependencies are handled by the `pubspec.yaml` file, like any other Dart package.

## Bundle AI package skills

[#](#bundle-ai-package-skills)

You can help AI coding assistants use your package effectively by bundling custom instructions and recipes directly inside your repository.

To distribute skills with your package:

1. Create a `skills/` directory in the root of your package repo.
2. Inside `skills/`, create a subdirectory named after your package (for example, `skills/<package_name>/`).
3. Add a `SKILL.md` file with guidelines and idiomatic code examples.
4. Publish your package to `pub.dev`.

When developers add your package as a dependency, their assistants can automatically discover your instructions. To learn more about authoring guidelines and best practices, check out [Ship skills with packages](https://dart.dev/tools/pub/package-skills).

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-09-16. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/packages-and-plugins/developing-packages.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/packages-and-plugins/developing-packages\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/packages-and-plugins/developing-packages.md "Report an issue with this page").

----
url: https://docs.flutter.dev/flutter-for/uikit-devs
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Flutter for UIKit developers](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

iOS developers with experience using UIKit who want to write mobile apps using Flutter should review this guide. It explains how to apply existing UIKit knowledge to Flutter.

Flutter is a framework for building cross-platform applications that uses the Dart programming language. To understand some differences between programming with Dart and programming with Swift, check out [Learning Dart as a Swift Developer](https://dart.dev/guides/language/coming-from/swift-to-dart) and [Flutter concurrency for Swift developers](/flutter-for/dart-swift-concurrency).

Your iOS and UIKit knowledge and experience are highly valuable when building with Flutter. Flutter also makes a number of adaptations to app behavior when running on iOS. To learn how, see [Platform adaptations](/platform-integration/platform-adaptations).

Use this guide as a cookbook. Jump around and find questions that address your most relevant needs.

## Overview

[#](#overview)

As an introduction, watch the following video. It outlines how Flutter works on iOS and how to use Flutter to build iOS apps.

[Watch on YouTube in a new tab: "Flutter for iOS developers"](https://www.youtube.com/watch/ceMsPBbcEGg)

### Views vs. Widgets

[#](#views-vs-widgets)

In UIKit, most of what you create in the UI is done using view objects, which are instances of the `UIView` class. These can act as containers for other `UIView` classes, which form your layout.

In Flutter, the rough equivalent to a `UIView` is a `Widget`. Widgets don't map exactly to iOS views, but while you're getting acquainted with how Flutter works you can think of them as "the way you declare and construct UI".

However, these have a few differences to a `UIView`. To start, widgets have a different lifespan: they are immutable and only exist until they need to be changed. Whenever widgets or their state change, Flutter's framework creates a new tree of widget instances. In comparison, a UIKit view is not recreated when it changes, but rather it's a mutable entity that is drawn once and doesn't redraw until it is invalidated using `setNeedsDisplay()`.

Furthermore, unlike `UIView`, Flutter's widgets are lightweight, in part due to their immutability. Because they aren't views themselves, and aren't directly drawing anything, but rather are a description of the UI and its semantics that get "inflated" into actual view objects under the hood.

Flutter includes the [Material Components](https://m3.material.io/develop/flutter/) library. These are widgets that implement the [Material Design guidelines](https://m3.material.io/styles/). Material Design is a flexible design system [optimized for all platforms](https://m2.material.io/design/platform-guidance/cross-platform-adaptation.html#cross-platform-guidelines), including iOS.

But Flutter is flexible and expressive enough to implement any design language. On iOS, you can use the [Cupertino widgets](/ui/widgets/cupertino) library to produce an interface that looks like [Apple's iOS design language](https://developer.apple.com/design/resources).

### Updating widgets

[#](#updating-widgets)

To update your views in UIKit, you directly mutate them. In Flutter, widgets are immutable and not updated directly. Instead, you have to manipulate the widget's state.

This is where the concept of Stateful vs Stateless widgets comes in. A `StatelessWidget` is just what it sounds like—a widget with no state attached.

`StatelessWidgets` are useful when the part of the user interface you are describing does not depend on anything other than the initial configuration information in the widget.

For example, with UIKit, this is similar to placing a `UIImageView` with your logo as the `image`. If the logo is not changing during runtime, use a `StatelessWidget` in Flutter.

If you want to dynamically change the UI based on data received after making an HTTP call, use a `StatefulWidget`. After the HTTP call has completed, tell the Flutter framework that the widget's `State` is updated, so it can update the UI.

The important difference between stateless and stateful widgets is that `StatefulWidget`s have a `State` object that stores state data and carries it over across tree rebuilds, so it's not lost.

If you are in doubt, remember this rule: if a widget changes outside of the `build` method (because of runtime user interactions, for example), it's stateful. If the widget never changes, once built, it's stateless. However, even if a widget is stateful, the containing parent widget can still be stateless if it isn't itself reacting to those changes (or other inputs).

The following example shows how to use a `StatelessWidget`. A common`StatelessWidget` is the `Text` widget. If you look at the implementation of the `Text` widget, you'll find it subclasses `StatelessWidget`.

dart

```
Text(
  'I like Flutter!',
  style: TextStyle(fontWeight: FontWeight.bold),
);
```

If you look at the code above, you might notice that the `Text` widget carries no explicit state with it. It renders what is passed in its constructors and nothing more.

But, what if you want to make "I Like Flutter" change dynamically, for example when clicking a `FloatingActionButton`?

To achieve this, wrap the `Text` widget in a `StatefulWidget` and update it when the user clicks the button.

For example:

dart

```
​
class SampleApp extends StatelessWidget {
  // This widget is the root of your application.
  const SampleApp({super.key});
​
  @override
  Widget build(BuildContext context) {
    return const MaterialApp(title: 'Sample App', home: SampleAppPage());
  }
}
​
class SampleAppPage extends StatefulWidget {
  const SampleAppPage({super.key});
​
  @override
  State<SampleAppPage> createState() => _SampleAppPageState();
}
​
class _SampleAppPageState extends State<SampleAppPage> {
  // Default placeholder text
  String textToShow = 'I Like Flutter';
​
  void _updateText() {
    setState(() {
      // Update the text
      textToShow = 'Flutter is Awesome!';
    });
  }
​
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Sample App')),
      body: Center(child: Text(textToShow)),
      floatingActionButton: FloatingActionButton(
        onPressed: _updateText,
        tooltip: 'Update Text',
        child: const Icon(Icons.update),
      ),
    );
  }
}
```

### Widget layout

[#](#widget-layout)

In UIKit, you might use a Storyboard file to organize your views and set constraints, or you might set your constraints programmatically in your view controllers. In Flutter, declare your layout in code by composing a widget tree.

The following example shows how to display a simple widget with padding:

dart

```
@override
Widget build(BuildContext context) {
  return Scaffold(
    appBar: AppBar(title: const Text('Sample App')),
    body: Center(
      child: CupertinoButton(
        onPressed: () {},
        padding: const EdgeInsets.only(left: 10, right: 10),
        child: const Text('Hello'),
      ),
    ),
  );
}
```

You can add padding to any widget, which mimics the functionality of constraints in iOS.

You can view the layouts that Flutter has to offer in the [widget catalog](/ui/widgets/layout).

### Removing Widgets

[#](#removing-widgets)

In UIKit, you call `addSubview()` on the parent, or `removeFromSuperview()` on a child view to dynamically add or remove child views. In Flutter, because widgets are immutable, there is no direct equivalent to `addSubview()`. Instead, you can pass a function to the parent that returns a widget, and control that child's creation with a boolean flag.

The following example shows how to toggle between two widgets when the user clicks the `FloatingActionButton`:

dart

```
class SampleApp extends StatelessWidget {
  // This widget is the root of your application.
  const SampleApp({super.key});
​
  @override
  Widget build(BuildContext context) {
    return const MaterialApp(title: 'Sample App', home: SampleAppPage());
  }
}
​
class SampleAppPage extends StatefulWidget {
  const SampleAppPage({super.key});
​
  @override
  State<SampleAppPage> createState() => _SampleAppPageState();
}
​
class _SampleAppPageState extends State<SampleAppPage> {
  // Default value for toggle.
  bool toggle = true;
​
  void _toggle() {
    setState(() {
      toggle = !toggle;
    });
  }
​
  Widget _getToggleChild() {
    if (toggle) {
      return const Text('Toggle One');
    }
​
    return CupertinoButton(onPressed: () {}, child: const Text('Toggle Two'));
  }
​
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Sample App')),
      body: Center(child: _getToggleChild()),
      floatingActionButton: FloatingActionButton(
        onPressed: _toggle,
        tooltip: 'Update Text',
        child: const Icon(Icons.update),
      ),
    );
  }
}
```

### Animations

[#](#animations)

In UIKit, you create an animation by calling the `animate(withDuration:animations:)` method on a view. In Flutter, use the animation library to wrap widgets inside an animated widget.

In Flutter, use an `AnimationController`, which is an `Animation<double>` that can pause, seek, stop, and reverse the animation. It requires a `Ticker` that signals when vsync happens and produces a linear interpolation between 0 and 1 on each frame while it's running. You then create one or more `Animation`s and attach them to the controller.

For example, you might use `CurvedAnimation` to implement an animation along an interpolated curve. In this sense, the controller is the "master" source of the animation progress and the `CurvedAnimation` computes the curve that replaces the controller's default linear motion. Like widgets, animations in Flutter work with composition.

When building the widget tree you assign the `Animation` to an animated property of a widget, such as the opacity of a `FadeTransition`, and tell the controller to start the animation.

The following example shows how to write a `FadeTransition` that fades the widget into a logo when you press the `FloatingActionButton`:

dart

```
import 'package:material_ui/material_ui.dart';
​
class SampleApp extends StatelessWidget {
  // This widget is the root of your application.
  const SampleApp({super.key});
​
  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      title: 'Fade Demo',
      home: MyFadeTest(title: 'Fade Demo'),
    );
  }
}
​
class MyFadeTest extends StatefulWidget {
  const MyFadeTest({super.key, required this.title});
​
  final String title;
​
  @override
  State<MyFadeTest> createState() => _MyFadeTest();
}
​
class _MyFadeTest extends State<MyFadeTest>
    with SingleTickerProviderStateMixin {
  late AnimationController controller;
  late CurvedAnimation curve;
​
  @override
  void initState() {
    super.initState();
    controller = AnimationController(
      duration: const Duration(milliseconds: 2000),
      vsync: this,
    );
    curve = CurvedAnimation(parent: controller, curve: Curves.easeIn);
  }
​
  @override
  void dispose() {
    controller.dispose();
    super.dispose();
  }
​
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text(widget.title)),
      body: Center(
        child: FadeTransition(
          opacity: curve,
          child: const FlutterLogo(size: 100),
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          controller.forward();
        },
        tooltip: 'Fade',
        child: const Icon(Icons.brush),
      ),
    );
  }
}
```

For more information, see [Animation & Motion widgets](/ui/widgets/animation), the [Animations tutorial](/ui/animations/tutorial), and the [Animations overview](/ui/animations).

### Drawing on the screen

[#](#drawing-on-the-screen)

In UIKit, you use `CoreGraphics` to draw lines and shapes to the screen. Flutter has a different API based on the `Canvas` class, with two other classes that help you draw: `CustomPaint` and `CustomPainter`, the latter of which implements your algorithm to draw to the canvas.

To learn how to implement a signature painter in Flutter, see Collin's answer on [StackOverflow](https://stackoverflow.com/questions/46241071/create-signature-area-for-mobile-app-in-dart-flutter).

dart

```
import 'package:material_ui/material_ui.dart';
​
void main() => runApp(const MaterialApp(home: DemoApp()));
​
class DemoApp extends StatelessWidget {
  const DemoApp({super.key});
​
  @override
  Widget build(BuildContext context) => const Scaffold(body: Signature());
}
​
class Signature extends StatefulWidget {
  const Signature({super.key});
​
  @override
  State<Signature> createState() => SignatureState();
}
​
class SignatureState extends State<Signature> {
  List<Offset?> _points = <Offset?>[];
  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onPanUpdate: (details) {
        setState(() {
          RenderBox? referenceBox = context.findRenderObject() as RenderBox;
          Offset localPosition = referenceBox.globalToLocal(
            details.globalPosition,
          );
          _points = List.from(_points)..add(localPosition);
        });
      },
      onPanEnd: (details) => _points.add(null),
      child: CustomPaint(
        painter: SignaturePainter(_points),
        size: Size.infinite,
      ),
    );
  }
}
​
class SignaturePainter extends CustomPainter {
  SignaturePainter(this.points);
​
  final List<Offset?> points;
​
  @override
  void paint(Canvas canvas, Size size) {
    final Paint paint = Paint()
      ..color = Colors.black
      ..strokeCap = StrokeCap.round
      ..strokeWidth = 5;
    for (int i = 0; i < points.length - 1; i++) {
      if (points[i] != null && points[i + 1] != null) {
        canvas.drawLine(points[i]!, points[i + 1]!, paint);
      }
    }
  }
​
  @override
  bool shouldRepaint(SignaturePainter oldDelegate) =>
      oldDelegate.points != points;
}
```

### Widget opacity

[#](#widget-opacity)

In UIKit, everything has `.opacity` or `.alpha`. In Flutter, most of the time you need to wrap a widget in an `Opacity` widget to accomplish this.

### Custom Widgets

[#](#custom-widgets)

In UIKit, you typically subclass `UIView`, or use a pre-existing view, to override and implement methods that achieve the desired behavior. In Flutter, build a custom widget by [composing](/resources/architectural-overview#composition) smaller widgets (instead of extending them).

For example, how do you build a `CustomButton` that takes a label in the constructor? Create a CustomButton that composes a `ElevatedButton` with a label, rather than by extending `ElevatedButton`:

dart

```
class CustomButton extends StatelessWidget {
  const CustomButton(this.label, {super.key});
​
  final String label;
​
  @override
  Widget build(BuildContext context) {
    return ElevatedButton(onPressed: () {}, child: Text(label));
  }
}
```

Then use `CustomButton`, just as you'd use any other Flutter widget:

dart

```
@override
Widget build(BuildContext context) {
  return const Center(child: CustomButton('Hello'));
}
```

### Managing dependencies

[#](#managing-dependencies)

In iOS, you add dependencies with CocoaPods by adding to your `Podfile`. Flutter uses Dart's build system and the Pub package manager to handle dependencies. The tools delegate the building of the native Android and iOS wrapper apps to the respective build systems.

While there is a Podfile in the iOS folder in your Flutter project, only use this if you are adding native dependencies needed for per-platform integration. In general, use `pubspec.yaml` to declare external dependencies in Flutter. A good place to find great packages for Flutter is on [pub.dev](https://pub.dev/flutter/packages).

[#](#navigation)

This section of the document discusses navigation between pages of an app, the push and pop mechanism, and more.

### Navigating between pages

[#](#navigating-between-pages)

In UIKit, to travel between view controllers, you can use a `UINavigationController` that manages the stack of view controllers to display.

Flutter has a similar implementation, using a `Navigator` and `Routes`. A `Route` is an abstraction for a "screen" or "page" of an app, and a `Navigator` is a [widget](/resources/architectural-overview#widgets) that manages routes. A route roughly maps to a `UIViewController`. The navigator works in a similar way to the iOS `UINavigationController`, in that it can `push()` and `pop()` routes depending on whether you want to navigate to, or back from, a view.

To navigate between pages, you have a couple options:

* Specify a `Map` of route names.
* Directly navigate to a route.

The following example builds a `Map.`

dart

```
void main() {
  runApp(
    CupertinoApp(
      home: const MyAppHome(), // becomes the route named '/'
      routes: <String, WidgetBuilder>{
        '/a': (context) => const MyPage(title: 'page A'),
        '/b': (context) => const MyPage(title: 'page B'),
        '/c': (context) => const MyPage(title: 'page C'),
      },
    ),
  );
}
```

Navigate to a route by `push`ing its name to the `Navigator`.

dart

```
Navigator.of(context).pushNamed('/b');
```

The `Navigator` class handles routing in Flutter and is used to get a result back from a route that you have pushed on the stack. This is done by `await`ing on the `Future` returned by `push()`.

For example, to start a `location` route that lets the user select their location, you might do the following:

dart

```
Object? coordinates = await Navigator.of(context).pushNamed('/location');
```

And then, inside your `location` route, once the user has selected their location, `pop()` the stack with the result:

dart

```
Navigator.of(context).pop({'lat': 43.821757, 'long': -79.226392});
```

### Navigating to another app

[#](#navigating-to-another-app)

In UIKit, to send the user to another application, you use a specific URL scheme. For the system level apps, the scheme depends on the app. To implement this functionality in Flutter, create a native platform integration, or use an [existing plugin](https://pub.dev/flutter), such as [`url_launcher`](https://pub.dev/packages/url_launcher).

### Manually pop back

[#](#manually-pop-back)

Calling `SystemNavigator.pop()` from your Dart code invokes the following iOS code:

objc

```
UIViewController* viewController = [UIApplication sharedApplication].keyWindow.rootViewController;
if ([viewController isKindOfClass:[UINavigationController class]]) {
  [((UINavigationController*)viewController) popViewControllerAnimated:NO];
}
```

If that doesn't do what you want, you can create your own [platform channel](/platform-integration/platform-channels) to invoke arbitrary iOS code.

### Handling localization

[#](#handling-localization)

Unlike iOS, which has the `Localizable.strings` file, Flutter doesn't currently have a dedicated system for handling strings. At the moment, the best practice is to declare your copy text in a class as static fields and access them from there. For example:

dart

```
class Strings {
  static const String welcomeMessage = 'Welcome To Flutter';
}
```

You can access your strings as such:

dart

```
Text(Strings.welcomeMessage);
```

By default, Flutter only supports US English for its strings. If you need to add support for other languages, include the `flutter_localizations` package. You might also need to add Dart's [`intl`](https://pub.dev/packages/intl) package to use i10n machinery, such as date/time formatting.

yaml

```
dependencies:
  flutter_localizations:
    sdk: flutter
  intl: any # Use version of intl from flutter_localizations.
```

To use the `flutter_localizations` package, specify the `localizationsDelegates` and `supportedLocales` on the app widget:

dart

```
import 'package:flutter_localizations/flutter_localizations.dart'
    hide GlobalMaterialLocalizations;
import 'package:material_ui/material_ui.dart';
​
class MyWidget extends StatelessWidget {
  const MyWidget({super.key});
​
  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      localizationsDelegates: <LocalizationsDelegate<dynamic>>[
        // Add app-specific localization delegate[s] here
        GlobalMaterialLocalizations.delegate,
        GlobalWidgetsLocalizations.delegate,
      ],
      supportedLocales: <Locale>[
        Locale('en', 'US'), // English
        Locale('he', 'IL'), // Hebrew
        // ... other locales the app supports
      ],
    );
  }
}
```

The delegates contain the actual localized values, while the `supportedLocales` defines which locales the app supports. The above example uses a `MaterialApp`, so it has both a `GlobalWidgetsLocalizations` for the base widgets localized values, and a `MaterialWidgetsLocalizations` for the Material widgets localizations. If you use `WidgetsApp` for your app, you don't need the latter. Note that these two delegates contain "default" values, but you'll need to provide one or more delegates for your own app's localizable copy, if you want those to be localized too.

When initialized, the `WidgetsApp` (or `MaterialApp`) creates a [`Localizations`](https://api.flutter.dev/flutter/widgets/Localizations-class.html) widget for you, with the delegates you specify. The current locale for the device is always accessible from the `Localizations` widget from the current context (in the form of a `Locale` object), or using the [`Window.locale`](https://api.flutter.dev/flutter/dart-ui/Window/locale.html).

To access localized resources, use the `Localizations.of()` method to access a specific localizations class that is provided by a given delegate. Use the [`intl_translation`](https://pub.dev/packages/intl_translation) package to extract translatable copy to [arb](https://github.com/googlei18n/app-resource-bundle) files for translating, and importing them back into the app for using them with `intl`.

For further details on internationalization and localization in Flutter, see the [internationalization guide](/ui/internationalization), which has sample code with and without the `intl` package.

## ViewControllers

[#](#viewcontrollers)

This section of the document discusses the equivalent of ViewController in Flutter and how to listen to lifecycle events.

### Equivalent of ViewController in Flutter

[#](#equivalent-of-viewcontroller-in-flutter)

In UIKit, a `ViewController` represents a portion of user interface, most commonly used for a screen or section. These are composed together to build complex user interfaces, and help scale your application's UI. In Flutter, this job falls to Widgets. As mentioned in the Navigation section, screens in Flutter are represented by Widgets since "everything is a widget!" Use a `Navigator` to move between different `Route`s that represent different screens or pages, or maybe different states or renderings of the same data.

### Listening to lifecycle events

[#](#listening-to-lifecycle-events)

In UIKit, you can override methods to the `ViewController` to capture lifecycle methods for the view itself, or register lifecycle callbacks in the `AppDelegate`. In Flutter, you have neither concept, but you can instead listen to lifecycle events by hooking into the `WidgetsBinding` observer and listening to the `didChangeAppLifecycleState()` change event.

The observable lifecycle events are:

* **`inactive`**

  The application is in an inactive state and is not receiving

user input. This event only works on iOS, as there is no equivalent event on Android.

* **`paused`**

  The application is not currently visible to the user,

is not responding to user input, but is running in the background.

* **`resumed`**

  The application is visible and responding to user input.

* **`suspending`**

  The application is suspended momentarily.

The iOS platform has no equivalent event.

For more details on the meaning of these states, see [`AppLifecycleState` documentation](https://api.flutter.dev/flutter/dart-ui/AppLifecycleState.html).

## Layouts

[#](#layouts)

This section discusses different layouts in Flutter and how they compare with UIKit.

### Displaying a list view

[#](#displaying-a-list-view)

In UIKit, you might show a list in either a `UITableView` or a `UICollectionView`. In Flutter, you have a similar implementation using a `ListView`. In UIKit, these views have delegate methods for deciding the number of rows, the cell for each index path, and the size of the cells.

Due to Flutter's immutable widget pattern, you pass a list of widgets to your `ListView`, and Flutter takes care of making sure that scrolling is fast and smooth.

dart

```
import 'package:material_ui/material_ui.dart';
​
void main() {
  runApp(const SampleApp());
}
​
class SampleApp extends StatelessWidget {
  const SampleApp({super.key});
​
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return const MaterialApp(title: 'Sample App', home: SampleAppPage());
  }
}
​
class SampleAppPage extends StatefulWidget {
  const SampleAppPage({super.key});
​
  @override
  State<SampleAppPage> createState() => _SampleAppPageState();
}
​
class _SampleAppPageState extends State<SampleAppPage> {
  List<Widget> _getListData() {
    final List<Widget> widgets = [];
    for (int i = 0; i < 100; i++) {
      widgets.add(
        Padding(padding: const EdgeInsets.all(10), child: Text('Row $i')),
      );
    }
    return widgets;
  }
​
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Sample App')),
      body: ListView(children: _getListData()),
    );
  }
}
```

### Detecting what was clicked

[#](#detecting-what-was-clicked)

In UIKit, you implement the delegate method, `tableView:didSelectRowAtIndexPath:`. In Flutter, use the touch handling provided by the passed-in widgets.

dart

```
import 'dart:developer' as developer;
​
import 'package:material_ui/material_ui.dart';
​
void main() {
  runApp(const SampleApp());
}
​
class SampleApp extends StatelessWidget {
  const SampleApp({super.key});
​
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return const MaterialApp(title: 'Sample App', home: SampleAppPage());
  }
}
​
class SampleAppPage extends StatefulWidget {
  const SampleAppPage({super.key});
​
  @override
  State<SampleAppPage> createState() => _SampleAppPageState();
}
​
class _SampleAppPageState extends State<SampleAppPage> {
  List<Widget> _getListData() {
    List<Widget> widgets = [];
    for (int i = 0; i < 100; i++) {
      widgets.add(
        GestureDetector(
          onTap: () {
            developer.log('row tapped');
          },
          child: Padding(
            padding: const EdgeInsets.all(10),
            child: Text('Row $i'),
          ),
        ),
      );
    }
    return widgets;
  }
​
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Sample App')),
      body: ListView(children: _getListData()),
    );
  }
}
```

### Dynamically updating ListView

[#](#dynamically-updating-listview)

In UIKit, you update the data for the list view, and notify the table or collection view using the `reloadData` method.

In Flutter, if you update the list of widgets inside a `setState()`, you quickly see that your data doesn't change visually. This is because when `setState()` is called, the Flutter rendering engine looks at the widget tree to see if anything has changed. When it gets to your `ListView`, it performs an `==` check, and determines that the two `ListView`s are the same. Nothing has changed, so no update is required.

For a simple way to update your `ListView`, create a new `List` inside of `setState()`, and copy the data from the old list to the new list. While this approach is simple, it is not recommended for large data sets, as shown in the next example.

dart

```
import 'dart:developer' as developer;
​
import 'package:material_ui/material_ui.dart';
​
void main() {
  runApp(const SampleApp());
}
​
class SampleApp extends StatelessWidget {
  const SampleApp({super.key});
​
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return const MaterialApp(title: 'Sample App', home: SampleAppPage());
  }
}
​
class SampleAppPage extends StatefulWidget {
  const SampleAppPage({super.key});
​
  @override
  State<SampleAppPage> createState() => _SampleAppPageState();
}
​
class _SampleAppPageState extends State<SampleAppPage> {
  List<Widget> widgets = <Widget>[];
​
  @override
  void initState() {
    super.initState();
    for (int i = 0; i < 100; i++) {
      widgets.add(getRow(i));
    }
  }
​
  Widget getRow(int i) {
    return GestureDetector(
      onTap: () {
        setState(() {
          widgets = List.from(widgets);
          widgets.add(getRow(widgets.length));
          developer.log('row $i');
        });
      },
      child: Padding(padding: const EdgeInsets.all(10), child: Text('Row $i')),
    );
  }
​
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Sample App')),
      body: ListView(children: widgets),
    );
  }
}
```

The recommended, efficient, and effective way to build a list uses a `ListView.Builder`. This method is great when you have a dynamic list or a list with very large amounts of data.

dart

```
import 'dart:developer' as developer;
​
import 'package:material_ui/material_ui.dart';
​
void main() {
  runApp(const SampleApp());
}
​
class SampleApp extends StatelessWidget {
  const SampleApp({super.key});
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return const MaterialApp(title: 'Sample App', home: SampleAppPage());
  }
}
​
class SampleAppPage extends StatefulWidget {
  const SampleAppPage({super.key});
​
  @override
  State<SampleAppPage> createState() => _SampleAppPageState();
}
​
class _SampleAppPageState extends State<SampleAppPage> {
  List<Widget> widgets = [];
​
  @override
  void initState() {
    super.initState();
    for (int i = 0; i < 100; i++) {
      widgets.add(getRow(i));
    }
  }
​
  Widget getRow(int i) {
    return GestureDetector(
      onTap: () {
        setState(() {
          widgets.add(getRow(widgets.length));
          developer.log('row $i');
        });
      },
      child: Padding(padding: const EdgeInsets.all(10), child: Text('Row $i')),
    );
  }
​
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Sample App')),
      body: ListView.builder(
        itemCount: widgets.length,
        itemBuilder: (context, position) {
          return getRow(position);
        },
      ),
    );
  }
}
```

Instead of creating a `ListView`, create a `ListView.builder` that takes two key parameters: the initial length of the list, and an `ItemBuilder` function.

The `ItemBuilder` function is similar to the `cellForItemAt` delegate method in an iOS table or collection view, as it takes a position, and returns the cell you want rendered at that position.

Finally, but most importantly, notice that the `onTap()` function doesn't recreate the list anymore, but instead `.add`s to it.

### Creating a scroll view

[#](#creating-a-scroll-view)

In UIKit, you wrap your views in a `ScrollView` that allows a user to scroll your content if needed.

In Flutter the easiest way to do this is using the `ListView` widget. This acts as both a `ScrollView` and an iOS `TableView`, as you can lay out widgets in a vertical format.

dart

```
@override
Widget build(BuildContext context) {
  return ListView(
    children: const <Widget>[
      Text('Row One'),
      Text('Row Two'),
      Text('Row Three'),
      Text('Row Four'),
    ],
  );
}
```

For more detailed docs on how to lay out widgets in Flutter, see the [layout tutorial](/ui/widgets/layout).

## Gesture detection and touch event handling

[#](#gesture-detection-and-touch-event-handling)

This section discusses how to detect gestures and handle different events in Flutter, and how they compare with UIKit.

### Adding a click listener

[#](#adding-a-click-listener)

In UIKit, you attach a `GestureRecognizer` to a view to handle click events. In Flutter, there are two ways of adding touch listeners:

1. If the widget supports event detection, pass a function to it, and handle the event in the function. For example, the `ElevatedButton` widget has an `onPressed` parameter:

dart

```
@override
Widget build(BuildContext context) {
 return ElevatedButton(
   onPressed: () {
     developer.log('click');
   },
   child: const Text('Button'),
 );
}
```

2. If the Widget doesn't support event detection, wrap the widget in a GestureDetector and pass a function to the `onTap` parameter.

dart

```
class SampleTapApp extends StatelessWidget {
 const SampleTapApp({super.key});
​
 @override
 Widget build(BuildContext context) {
   return Scaffold(
     body: Center(
       child: GestureDetector(
         onTap: () {
           developer.log('tap');
         },
         child: const FlutterLogo(size: 200),
       ),
     ),
   );
 }
}
```

### Handling other gestures

[#](#handling-other-gestures)

Using `GestureDetector` you can listen to a wide range of gestures such as:

* **Tapping**

  * **`onTapDown`**

    A pointer that might cause a tap has contacted the

  screen at a particular location.

  * **`onTapUp`**

    A pointer that triggers a tap has stopped contacting the

  screen at a particular location.

  * **`onTap`**

    A tap has occurred.

  * **`onTapCancel`**

    The pointer that previously triggered the `onTapDown`

  won't cause a tap.

* **Double tapping**

  * **`onDoubleTap`**

    The user tapped the screen at the same location twice in

  quick succession.

* **Long pressing**

  * **`onLongPress`**

    A pointer has remained in contact with the screen

  at the same location for a long period of time.

* **Vertical dragging**

  * **`onVerticalDragStart`**

    A pointer has contacted the screen and might begin to

  move vertically.

  * **`onVerticalDragUpdate`**

    A pointer in contact with the screen

  has moved further in the vertical direction.

  * **`onVerticalDragEnd`**

    A pointer that was previously in contact with the

  screen and moving vertically is no longer in contact with the screen and was moving at a specific velocity when it stopped contacting the screen.

* **Horizontal dragging**

  * **`onHorizontalDragStart`**

    A pointer has contacted the screen and might begin

  to move horizontally.

  * **`onHorizontalDragUpdate`**

    A pointer in contact with the screen

  has moved further in the horizontal direction.

  * **`onHorizontalDragEnd`**

    A pointer that was previously in contact with the

  screen and moving horizontally is no longer in contact with the screen.

The following example shows a `GestureDetector` that rotates the Flutter logo on a double tap:

dart

```
class SampleApp extends StatefulWidget {
  const SampleApp({super.key});
​
  @override
  State<SampleApp> createState() => _SampleAppState();
}
​
class _SampleAppState extends State<SampleApp>
    with SingleTickerProviderStateMixin {
  late AnimationController controller;
  late CurvedAnimation curve;
​
  @override
  void initState() {
    super.initState();
    controller = AnimationController(
      vsync: this,
      duration: const Duration(milliseconds: 2000),
    );
    curve = CurvedAnimation(parent: controller, curve: Curves.easeIn);
  }
​
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: GestureDetector(
          onDoubleTap: () {
            if (controller.isCompleted) {
              controller.reverse();
            } else {
              controller.forward();
            }
          },
          child: RotationTransition(
            turns: curve,
            child: const FlutterLogo(size: 200),
          ),
        ),
      ),
    );
  }
}
```

## Themes, styles, and media

[#](#themes-styles-and-media)

Flutter applications are easy to style; you can switch between light and dark themes, change the style of your text and UI components, and more. This section covers aspects of styling your Flutter apps and compares how you might do the same in UIKit.

### Using a theme

[#](#using-a-theme)

Out of the box, Flutter comes with a beautiful implementation of Material Design, which takes care of a lot of styling and theming needs that you would typically do.

To take full advantage of Material Components in your app, declare a top-level widget, `MaterialApp`, as the entry point to your application. `MaterialApp` is a convenience widget that wraps a number of widgets that are commonly required for applications implementing Material Design. It builds upon a `WidgetsApp` by adding Material specific functionality.

But Flutter is flexible and expressive enough to implement any design language. On iOS, you can use the [Cupertino library](https://pub.dev/documentation/cupertino_ui/latest/cupertino_ui) to produce an interface that adheres to the [Human Interface Guidelines](https://developer.apple.com/ios/human-interface-guidelines/overview/themes/). For the full set of these widgets, see the [Cupertino widgets](/ui/widgets/cupertino) gallery.

You can also use a `WidgetsApp` as your app widget, which provides some of the same functionality, but is not as rich as `MaterialApp`.

To customize the colors and styles of any child components, pass a `ThemeData` object to the `MaterialApp` widget. For example, in the code below, the color scheme from seed is set to deepPurple and divider color is grey.

dart

```
import 'package:material_ui/material_ui.dart';
​
class SampleApp extends StatelessWidget {
  const SampleApp({super.key});
​
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Sample App',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        dividerColor: Colors.grey,
      ),
      home: const SampleAppPage(),
    );
  }
}
```

### Using custom fonts

[#](#using-custom-fonts)

In UIKit, you import any `ttf` font files into your project and create a reference in the `info.plist` file. In Flutter, place the font file in a folder and reference it in the `pubspec.yaml` file, similar to how you import images.

yaml

```
fonts:
  - family: MyCustomFont
    fonts:
      - asset: fonts/MyCustomFont.ttf
      - style: italic
```

Then assign the font to your `Text` widget:

dart

```
@override
Widget build(BuildContext context) {
  return Scaffold(
    appBar: AppBar(title: const Text('Sample App')),
    body: const Center(
      child: Text(
        'This is a custom font text',
        style: TextStyle(fontFamily: 'MyCustomFont'),
      ),
    ),
  );
}
```

### Styling text

[#](#styling-text)

### Bundling images in apps

[#](#bundling-images-in-apps)

While iOS treats images and assets as distinct items, Flutter apps have only assets. Resources that are placed in the `Images.xcasset` folder on iOS, are placed in an assets' folder for Flutter. As with iOS, assets are any type of file, not just images. For example, you might have a JSON file located in the `my-assets` folder:

```
my-assets/data.json
```

Declare the asset in the `pubspec.yaml` file:

yaml

```
assets:
 - my-assets/data.json
```

And then access it from code using an [`AssetBundle`](https://api.flutter.dev/flutter/services/AssetBundle-class.html):

dart

```
import 'dart:async' show Future;
​
import 'package:flutter/services.dart' show rootBundle;
​
Future<String> loadAsset() async {
  return await rootBundle.loadString('my-assets/data.json');
}
```

For images, Flutter follows a simple density-based format like iOS. Image assets might be `1.0x`, `2.0x`, `3.0x`, or any other multiplier. Flutter's [`devicePixelRatio`](https://api.flutter.dev/flutter/dart-ui/FlutterView/devicePixelRatio.html) expresses the ratio of physical pixels in a single logical pixel.

Assets are located in any arbitrary folder— Flutter has no predefined folder structure. You declare the assets (with location) in the `pubspec.yaml` file, and Flutter picks them up.

For example, to add an image called `my_icon.png` to your Flutter project, you might decide to store it in a folder arbitrarily called `images`. Place the base image (1.0x) in the `images` folder, and the other variants in sub-folders named after the appropriate ratio multiplier:

```
images/my_icon.png       // Base: 1.0x image
images/2.0x/my_icon.png  // 2.0x image
images/3.0x/my_icon.png  // 3.0x image
```

Next, declare these images in the `pubspec.yaml` file:

yaml

```
assets:
 - images/my_icon.png
```

You can now access your images using `AssetImage`:

dart

```
image: AssetImage('images/a_dot_burr.png'),
```

or directly in an `Image` widget:

dart

```
@override
Widget build(BuildContext context) {
  return Image.asset('images/my_image.png');
}
```

For more details, see [Adding Assets and Images in Flutter](/ui/assets/assets-and-images).

## Form input

[#](#form-input)

This section discusses how to use forms in Flutter and how they compare with UIKit.

### Retrieving user input

[#](#retrieving-user-input)

Given how Flutter uses immutable widgets with a separate state, you might be wondering how user input fits into the picture. In UIKit, you usually query the widgets for their current values when it's time to submit the user input, or action on it. How does that work in Flutter?

In practice forms are handled, like everything in Flutter, by specialized widgets. If you have a `TextField` or a `TextFormField`, you can supply a [`TextEditingController`](https://api.flutter.dev/flutter/widgets/TextEditingController-class.html) to retrieve user input:

dart

```
class _MyFormState extends State<MyForm> {
  // 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 disposing of the Widget.
    myController.dispose();
    super.dispose();
  }
​
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Retrieve Text Input')),
      body: Padding(
        padding: const EdgeInsets.all(16),
        child: TextField(controller: myController),
      ),
      floatingActionButton: FloatingActionButton(
        // When the user presses the button, show an alert dialog with the
        // text the user has typed into our text field.
        onPressed: () {
          showDialog<void>(
            context: context,
            builder: (context) {
              return AlertDialog(
                // Retrieve the text the user has typed in using our
                // TextEditingController.
                content: Text(myController.text),
              );
            },
          );
        },
        tooltip: 'Show me the value!',
        child: const Icon(Icons.text_fields),
      ),
    );
  }
}
```

You can find more information and the full code listing in [Retrieve the value of a text field](/cookbook/forms/retrieve-input).

### Placeholder in a text field

[#](#placeholder-in-a-text-field)

In Flutter, you can easily show a "hint" or a placeholder text for your field by adding an `InputDecoration` object to the decoration constructor parameter for the `Text` widget:

dart

```
Center(
  child: TextField(decoration: InputDecoration(hintText: 'This is a hint')),
)
```

### Showing validation errors

[#](#showing-validation-errors)

Just as you would with a "hint", pass an `InputDecoration` object to the decoration constructor for the `Text` widget.

However, you don't want to start off by showing an error. Instead, when the user has entered invalid data, update the state, and pass a new `InputDecoration` object.

dart

```
import 'package:material_ui/material_ui.dart';
​
void main() {
  runApp(const SampleApp());
}
​
class SampleApp extends StatelessWidget {
  const SampleApp({super.key});
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return const MaterialApp(title: 'Sample App', home: SampleAppPage());
  }
}
​
class SampleAppPage extends StatefulWidget {
  const SampleAppPage({super.key});
​
  @override
  State<SampleAppPage> createState() => _SampleAppPageState();
}
​
class _SampleAppPageState extends State<SampleAppPage> {
  String? _errorText;
​
  bool isEmail(String em) {
    String emailRegexp =
        r'^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|'
        r'(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|'
        r'(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$';
​
    RegExp regExp = RegExp(emailRegexp);
​
    return regExp.hasMatch(em);
  }
​
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Sample App')),
      body: Center(
        child: TextField(
          onSubmitted: (text) {
            setState(() {
              if (!isEmail(text)) {
                _errorText = 'Error: This is not an email';
              } else {
                _errorText = null;
              }
            });
          },
          decoration: InputDecoration(
            hintText: 'This is a hint',
            errorText: _errorText,
          ),
        ),
      ),
    );
  }
}
```

## Threading & asynchronicity

[#](#threading-asynchronicity)

This section discusses concurrency in Flutter and how it compares with UIKit.

### Writing asynchronous code

[#](#writing-asynchronous-code)

Dart has a single-threaded execution model, with support for `Isolate`s (a way to run Dart code on another thread), an event loop, and asynchronous programming. Unless you spawn an `Isolate`, your Dart code runs in the main UI thread and is driven by an event loop. Flutter's event loop is equivalent to the iOS main loop—that is, the `Looper` that is attached to the main thread.

Dart's single-threaded model doesn't mean you are required to run everything as a blocking operation that causes the UI to freeze. Instead, use the asynchronous facilities that the Dart language provides, such as `async`/`await`, to perform asynchronous work.

For example, you can run network code without causing the UI to hang by using `async`/`await` and letting Dart do the heavy lifting:

dart

```
Future<void> loadData() async {
  final Uri dataURL = Uri.parse('https://jsonplaceholder.typicode.com/posts');
  final http.Response response = await http.get(dataURL);
  setState(() {
    data = (jsonDecode(response.body) as List).cast<Map<String, Object?>>();
  });
}
```

Once the `await`ed network call is done, update the UI by calling `setState()`, which triggers a rebuild of the widget subtree and updates the data.

The following example loads data asynchronously and displays it in a `ListView`:

dart

```
import 'dart:convert';
​
import 'package:http/http.dart' as http;
import 'package:material_ui/material_ui.dart';
​
void main() {
  runApp(const SampleApp());
}
​
class SampleApp extends StatelessWidget {
  const SampleApp({super.key});
​
  @override
  Widget build(BuildContext context) {
    return const MaterialApp(title: 'Sample App', home: SampleAppPage());
  }
}
​
class SampleAppPage extends StatefulWidget {
  const SampleAppPage({super.key});
​
  @override
  State<SampleAppPage> createState() => _SampleAppPageState();
}
​
class _SampleAppPageState extends State<SampleAppPage> {
  List<Map<String, Object?>> data = [];
​
  @override
  void initState() {
    super.initState();
    loadData();
  }
​
  Future<void> loadData() async {
    final Uri dataURL = Uri.parse('https://jsonplaceholder.typicode.com/posts');
    final http.Response response = await http.get(dataURL);
    setState(() {
      data = (jsonDecode(response.body) as List).cast<Map<String, Object?>>();
    });
  }
​
  Widget getRow(int index) {
    return Padding(
      padding: const EdgeInsets.all(10),
      child: Text('Row ${data[index]['title']}'),
    );
  }
​
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Sample App')),
      body: ListView.builder(
        itemCount: data.length,
        itemBuilder: (context, index) {
          return getRow(index);
        },
      ),
    );
  }
}
```

Refer to the next section for more information on doing work in the background, and how Flutter differs from iOS.

### Moving to the background thread

[#](#moving-to-the-background-thread)

Since Flutter is single threaded and runs an event loop (like Node.js), you don't have to worry about thread management or spawning background threads. If you're doing I/O-bound work, such as disk access or a network call, then you can safely use `async`/`await` and you're done. If, on the other hand, you need to do computationally intensive work that keeps the CPU busy, you want to move it to an `Isolate` to avoid blocking the event loop.

For I/O-bound work, declare the function as an `async` function, and `await` on long-running tasks inside the function:

dart

```
Future<void> loadData() async {
  final Uri dataURL = Uri.parse('https://jsonplaceholder.typicode.com/posts');
  final http.Response response = await http.get(dataURL);
  setState(() {
    data = (jsonDecode(response.body) as List).cast<Map<String, Object?>>();
  });
}
```

This is how you typically do network or database calls, which are both I/O operations.

However, there are times when you might be processing a large amount of data and your UI hangs. In Flutter, use `Isolate`s to take advantage of multiple CPU cores to do long-running or computationally intensive tasks.

Isolates are separate execution threads that do not share any memory with the main execution memory heap. This means you can't access variables from the main thread, or update your UI by calling `setState()`. Isolates are true to their name, and cannot share memory (in the form of static fields, for example).

The following example shows, in a simple isolate, how to share data back to the main thread to update the UI.

dart

```
Future<void> loadData() async {
  final ReceivePort receivePort = ReceivePort();
  await Isolate.spawn(dataLoader, receivePort.sendPort);
​
  // The 'echo' isolate sends its SendPort as the first message.
  final SendPort sendPort = await receivePort.first as SendPort;
​
  final List<Map<String, dynamic>> msg = await sendReceive(
    sendPort,
    'https://jsonplaceholder.typicode.com/posts',
  );
​
  setState(() {
    data = msg;
  });
}
​
// The entry point for the isolate.
static Future<void> dataLoader(SendPort sendPort) async {
  // Open the ReceivePort for incoming messages.
  final ReceivePort port = ReceivePort();
​
  // Notify any other isolates what port this isolate listens to.
  sendPort.send(port.sendPort);
​
  await for (final dynamic msg in port) {
    final String url = msg[0] as String;
    final SendPort replyTo = msg[1] as SendPort;
​
    final Uri dataURL = Uri.parse(url);
    final http.Response response = await http.get(dataURL);
    // Lots of JSON to parse
    replyTo.send(jsonDecode(response.body) as List<Map<String, dynamic>>);
  }
}
​
Future<List<Map<String, dynamic>>> sendReceive(SendPort port, String msg) {
  final ReceivePort response = ReceivePort();
  port.send(<dynamic>[msg, response.sendPort]);
  return response.first as Future<List<Map<String, dynamic>>>;
}
```

Here, `dataLoader()` is the `Isolate` that runs in its own separate execution thread. In the isolate, you can perform more CPU intensive processing (parsing a big JSON, for example), or perform computationally intensive math, such as encryption or signal processing.

You can run the full example below:

dart

```
import 'dart:async';
import 'dart:convert';
import 'dart:isolate';
​
import 'package:http/http.dart' as http;
import 'package:material_ui/material_ui.dart';
​
void main() {
  runApp(const SampleApp());
}
​
class SampleApp extends StatelessWidget {
  const SampleApp({super.key});
​
  @override
  Widget build(BuildContext context) {
    return const MaterialApp(title: 'Sample App', home: SampleAppPage());
  }
}
​
class SampleAppPage extends StatefulWidget {
  const SampleAppPage({super.key});
​
  @override
  State<SampleAppPage> createState() => _SampleAppPageState();
}
​
class _SampleAppPageState extends State<SampleAppPage> {
  List<Map<String, Object?>> data = [];
​
  @override
  void initState() {
    super.initState();
    loadData();
  }
​
  bool get showLoadingDialog => data.isEmpty;
​
  Future<void> loadData() async {
    final ReceivePort receivePort = ReceivePort();
    await Isolate.spawn(dataLoader, receivePort.sendPort);
​
    // The 'echo' isolate sends its SendPort as the first message.
    final SendPort sendPort = await receivePort.first as SendPort;
​
    final List<Map<String, dynamic>> msg = await sendReceive(
      sendPort,
      'https://jsonplaceholder.typicode.com/posts',
    );
​
    setState(() {
      data = msg;
    });
  }
​
  // The entry point for the isolate.
  static Future<void> dataLoader(SendPort sendPort) async {
    // Open the ReceivePort for incoming messages.
    final ReceivePort port = ReceivePort();
​
    // Notify any other isolates what port this isolate listens to.
    sendPort.send(port.sendPort);
​
    await for (final dynamic msg in port) {
      final String url = msg[0] as String;
      final SendPort replyTo = msg[1] as SendPort;
​
      final Uri dataURL = Uri.parse(url);
      final http.Response response = await http.get(dataURL);
      // Lots of JSON to parse
      replyTo.send(jsonDecode(response.body) as List<Map<String, dynamic>>);
    }
  }
​
  Future<List<Map<String, dynamic>>> sendReceive(SendPort port, String msg) {
    final ReceivePort response = ReceivePort();
    port.send(<dynamic>[msg, response.sendPort]);
    return response.first as Future<List<Map<String, dynamic>>>;
  }
​
  Widget getBody() {
    bool showLoadingDialog = data.isEmpty;
​
    if (showLoadingDialog) {
      return getProgressDialog();
    } else {
      return getListView();
    }
  }
​
  Widget getProgressDialog() {
    return const Center(child: CircularProgressIndicator());
  }
​
  ListView getListView() {
    return ListView.builder(
      itemCount: data.length,
      itemBuilder: (context, position) {
        return getRow(position);
      },
    );
  }
​
  Widget getRow(int i) {
    return Padding(
      padding: const EdgeInsets.all(10),
      child: Text("Row ${data[i]["title"]}"),
    );
  }
​
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Sample App')),
      body: getBody(),
    );
  }
}
```

### Making network requests

[#](#making-network-requests)

Making a network call in Flutter is easy when you use the popular [`http` package](https://pub.dev/packages/http). This abstracts away a lot of the networking that you might normally implement yourself, making it simple to make network calls.

To add the `http` package as a dependency, run `flutter pub add`:

```
flutter pub add http
```

To make a network call, call `await` on the `async` function `http.get()`:

dart

```
Future<void> loadData() async {
  final Uri dataURL = Uri.parse('https://jsonplaceholder.typicode.com/posts');
  final http.Response response = await http.get(dataURL);
  setState(() {
    data = (jsonDecode(response.body) as List).cast<Map<String, Object?>>();
  });
}
```

### Showing the progress on long-running tasks

[#](#showing-the-progress-on-long-running-tasks)

In UIKit, you typically use a `UIProgressView` while executing a long-running task in the background.

In Flutter, use a `ProgressIndicator` widget. Show the progress programmatically by controlling when it's rendered through a boolean flag. Tell Flutter to update its state before your long-running task starts, and hide it after it ends.

In the example below, the build function is separated into three different functions. If `showLoadingDialog` is `true` (when `widgets.length == 0`), then render the `ProgressIndicator`. Otherwise, render the `ListView` with the data returned from a network call.

dart

```
import 'dart:convert';
​
import 'package:http/http.dart' as http;
import 'package:material_ui/material_ui.dart';
​
void main() {
  runApp(const SampleApp());
}
​
class SampleApp extends StatelessWidget {
  const SampleApp({super.key});
​
  @override
  Widget build(BuildContext context) {
    return const MaterialApp(title: 'Sample App', home: SampleAppPage());
  }
}
​
class SampleAppPage extends StatefulWidget {
  const SampleAppPage({super.key});
​
  @override
  State<SampleAppPage> createState() => _SampleAppPageState();
}
​
class _SampleAppPageState extends State<SampleAppPage> {
  List<Map<String, Object?>> data = [];
​
  @override
  void initState() {
    super.initState();
    loadData();
  }
​
  bool get showLoadingDialog => data.isEmpty;
​
  Future<void> loadData() async {
    final Uri dataURL = Uri.parse('https://jsonplaceholder.typicode.com/posts');
    final http.Response response = await http.get(dataURL);
    setState(() {
      data = (jsonDecode(response.body) as List).cast<Map<String, Object?>>();
    });
  }
​
  Widget getBody() {
    if (showLoadingDialog) {
      return getProgressDialog();
    }
​
    return getListView();
  }
​
  Widget getProgressDialog() {
    return const Center(child: CircularProgressIndicator());
  }
​
  ListView getListView() {
    return ListView.builder(
      itemCount: data.length,
      itemBuilder: (context, index) {
        return getRow(index);
      },
    );
  }
​
  Widget getRow(int i) {
    return Padding(
      padding: const EdgeInsets.all(10),
      child: Text("Row ${data[i]["title"]}"),
    );
  }
​
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Sample App')),
      body: getBody(),
    );
  }
}
```

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-09-29. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/flutter-for/uikit-devs.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/flutter-for/uikit-devs\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/flutter-for/uikit-devs.md "Report an issue with this page").

----
url: https://docs.flutter.dev/flutter-for/declarative.md
----

\# Introduction to declarative UI > Explains the difference between a declarative and imperative programming style.

<!--?code-excerpt path-base="get-started/flutter-for/declarative"?-->

\_This introduction describes the conceptual difference between the declarative style used by Flutter, and the imperative style used by many other UI frameworks.\_ ## Why a declarative UI? Frameworks from Win32 to web to Android and iOS typically use an imperative style of UI programming. This might be the style you're most familiar with—where you manually construct a full-functioned UI entity, such as a UIView or equivalent, and later mutate it using methods and setters when the UI changes. In order to lighten the burden on developers from having to program how to transition between various UI states, Flutter, by contrast, lets the developer describe the current UI state and leaves the transitioning to the framework. This, however, requires a slight shift in thinking for how to manipulate UI. ## How to change UI in a declarative framework Consider a simplified example below: In the imperative style, you would typically go to ViewB's owner and retrieve the instance \`b\` using selectors or with \`findViewById\` or similar, and invoke mutations on it (and implicitly invalidate it). For example: \`\`\`java // Imperative style b.setColor(red) b.clearChildren() ViewC c3 = new ViewC(...) b.add(c3) \`\`\` You might also need to replicate this configuration in the constructor of ViewB since the source of truth for the UI might outlive instance \`b\` itself. In the declarative style, view configurations (such as Flutter's \`Widget\`s) are immutable and are only lightweight "blueprints". To change the UI, a widget triggers a rebuild on itself (most commonly by calling \`setState\` on \`StatefulWidgets\` in Flutter) and constructs a new \`Widget\` subtree.

<!--?code-excerpt "lib/main.dart (declarative)"?-->

\`\`\`dart // Declarative style return ViewB(color: red, child: const ViewC()); \`\`\` Here, rather than mutating an old instance \`b\` when the UI changes, Flutter constructs new \`Widget\` instances. The framework manages many of the responsibilities of a traditional UI object (such as maintaining the state of the layout) behind the scenes with \[\`RenderObjects\`]\[]. A \`RenderObject\` is a persistent, mutable object that handles the heavy lifting of layout, painting, and hit testing. \`RenderObject\`s persist between frames and Flutter's lightweight, immutable \`Widget\`s serve as blueprints that tell the framework to mutate the \`RenderObject\`s between states. The Flutter framework handles the rest. \[\`RenderObjects\`]: /resources/glossary#renderobject

----
url: https://docs.flutter.dev/learn.md
----

\# Learn Flutter > Find everything you need to start building Flutter apps. ## For beginners Start here for an overview of how to get set up, build Flutter apps, write Dart code, and understand how Flutter works.

This is the \*\*recommended\*\* set of resources to start learning Flutter! Learn how to write Dart code in the first tutorial, then build a series of apps using Flutter in the second. You'll learn the essential best practices for Flutter app development, app architecture, fetching data from the network, and more. Great for beginners with minimal programming experience.

[Start learning](/learn/pathway) ## For experienced developers Dive deeper into more specific topics in Flutter development with sample code, YouTube videos, codelabs, and more.

Find sample code, YouTube videos, codelabs, and more written by the experts on the Flutter team at Google. This index contains the resources you need to learn any aspect of Flutter.

[Explore all resources](/reference/learning-resources)

Learn the recommended app architecture to build a Flutter app. This guide contains everything you need to know to understand and implement MVVM architecture in a Flutter app.

[Start learning](/app-architecture)

----
url: https://docs.flutter.dev/ui/adaptive-responsive/more-info
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Additional resources](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

## Examples

[#](#examples)

If you'd like to see how the adaptive and responsive concepts (as described in these pages) come together, check out the source code for the following apps:

* [Wonderous](https://github.com/gskinnerTeam/flutter-wonderous-app)
* [Flutter adaptive demo](https://github.com/gskinnerTeam/flutter-adaptive-demo)

## Learn more about basic usability principles

[#](#learn-more-about-basic-usability-principles)

Of course, these pages don't constitute an exhaustive list of the things you might consider. The more operating systems, form factors, and input devices you support, the more difficult it becomes to spec out every permutation in design.

Taking time to learn basic usability principles as a developer empowers you to make better decisions, reduces back-and-forth iterations with design during production, and results in improved productivity with better outcomes.

Here are some resources that you might find useful:

* [Android large screen guidelines](https://developer.android.com/docs/quality-guidelines/large-screen-app-quality)
* [Material guidelines on applying layout](https://m3.material.io/foundations/layout/applying-layout/window-size-classes)
* [Material design for large screens](https://m2.material.io/blog/material-design-for-large-screens)
* [Material guidelines on canonical layouts](https://m3.material.io/foundations/layout/canonical-layouts/overview)
* [Build high quality apps (Android)](https://developer.android.com/quality)
* [UI design do's and don'ts (Apple)](https://developer.apple.com/design/tips/)
* [Human interface guidelines (Apple)](https://developer.apple.com/design/human-interface-guidelines/)
* [Responsive design techniques (Microsoft)](https://learn.microsoft.com/windows/apps/design/layout/responsive-design)
* [Machine sizes and breakpoints (Microsoft)](https://learn.microsoft.com/windows/apps/design/layout/screen-sizes-and-breakpoints-for-responsive-design)
* [How to build Adaptive UI with Flutter](https://www.youtube.com/watch?v=LeKLGzpsz9I), a Google I/O 2024 video.

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-07-31. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/ui/adaptive-responsive/more-info.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/ui/adaptive-responsive/more-info\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/ui/adaptive-responsive/more-info.md "Report an issue with this page").

----
url: https://docs.flutter.dev/cookbook/testing/widget/introduction
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top An introduction to widget testing](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

In the [introduction to unit testing](/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`](https://api.flutter.dev/flutter/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 [`WidgetTester`](https://api.flutter.dev/flutter/flutter_test/WidgetTester-class.html) allows building and interacting with widgets in a test environment.
* The [`testWidgets()`](https://api.flutter.dev/flutter/flutter_test/testWidgets.html) function automatically creates a new `WidgetTester` for each test case, and is used in place of the normal `test()` function.
* The [`Finder`](https://api.flutter.dev/flutter/flutter_test/Finder-class.html) classes allow searching for widgets in the test environment.
* Widget-specific [`Matcher`](https://api.flutter.dev/flutter/package-matcher_matcher/Matcher-class.html) constants 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, which uses the following steps:

1. Add the `flutter_test` dependency.
2. Create a widget to test.
3. Create a `testWidgets` test.
4. Build the widget using the `WidgetTester`.
5. Search for the widget using a `Finder`.
6. Verify the widget using a `Matcher`.

## 1. Add the `flutter_test` dependency

[#](#1-add-the-flutter-test-dependency)

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:
  flutter_test:
    sdk: flutter
```

## 2. Create a widget to test

[#](#2-create-a-widget-to-test)

Next, create a widget for testing. For this recipe, create a widget that displays a `title` and `message`.

dart

```
class MyWidget extends StatelessWidget {
  const MyWidget({super.key, required this.title, required this.message});
​
  final String title;
  final String message;
​
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      home: Scaffold(
        appBar: AppBar(title: Text(title)),
        body: Center(child: Text(message)),
      ),
    );
  }
}
```

## 3. Create a `testWidgets` test

[#](#3-create-a-testwidgets-test)

With a widget to test, begin by writing your first test. Use the [`testWidgets()`](https://api.flutter.dev/flutter/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.

This test verifies that `MyWidget` displays a given title and message. It is titled accordingly, and it will be populated in the next section.

dart

```
void main() {
  // 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', (tester) async {
    // Test code goes here.
  });
}
```

## 4. Build the widget using the `WidgetTester`

[#](#4-build-the-widget-using-the-widgettester)

Next, build `MyWidget` inside the test environment by using the [`pumpWidget()`](https://api.flutter.dev/flutter/flutter_test/WidgetTester/pumpWidget.html) method provided by `WidgetTester`. The `pumpWidget` method builds and renders the provided widget.

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', (tester) async {
    // Create the widget by telling the tester to build it.
    await tester.pumpWidget(const MyWidget(title: 'T', message: 'M'));
  });
}
```

### Notes about the pump() methods

[#](#notes-about-the-pump-methods)

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, 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(Duration duration)`](https://api.flutter.dev/flutter/flutter_test/TestWidgetsFlutterBinding/pump.html)

  Schedules a frame and triggers a rebuild of the widget. If a `Duration` is specified, it advances the clock by that amount and schedules a frame. It does not schedule multiple frames even if the duration is longer than a single frame.

- [`tester.pumpAndSettle()`](https://api.flutter.dev/flutter/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.

## 5. Search for our widget using a `Finder`

[#](#5-search-for-our-widget-using-a-finder)

With a widget in the test environment, search through the widget tree for the `title` and `message` Text widgets using a `Finder`. This allows verification that the widgets are being displayed correctly.

For this purpose, use the top-level [`find()`](https://api.flutter.dev/flutter/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()`](https://api.flutter.dev/flutter/flutter_test/CommonFinders/text.html) method.

For more information about `Finder` classes, see the [Finding widgets in a widget test](/cookbook/testing/widget/finders) recipe.

dart

```
void main() {
  testWidgets('MyWidget has a title and message', (tester) async {
    await tester.pumpWidget(const MyWidget(title: 'T', message: 'M'));
​
    // Create the Finders.
    final titleFinder = find.text('T');
    final messageFinder = find.text('M');
  });
}
```

## 6. Verify the widget using a `Matcher`

[#](#6-verify-the-widget-using-a-matcher)

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.

Ensure that the widgets appear on screen exactly one time. For this purpose, use the [`findsOneWidget`](https://api.flutter.dev/flutter/flutter_test/findsOneWidget-constant.html) `Matcher`.

dart

```
void main() {
  testWidgets('MyWidget has a title and message', (tester) async {
    await tester.pumpWidget(const MyWidget(title: 'T', message: 'M'));
    final titleFinder = find.text('T');
    final messageFinder = find.text('M');
​
    // 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);
  });
}
```

### Additional Matchers

[#](#additional-matchers)

In addition to `findsOneWidget`, `flutter_test` provides additional matchers for common cases.

* [`findsNothing`](https://api.flutter.dev/flutter/flutter_test/findsNothing-constant.html)

  Verifies that no widgets are found.

* [`findsWidgets`](https://api.flutter.dev/flutter/flutter_test/findsWidgets-constant.html)

  Verifies that one or more widgets are found.

* [`findsNWidgets`](https://api.flutter.dev/flutter/flutter_test/findsNWidgets.html)

  Verifies that a specific number of widgets are found.

* [`matchesGoldenFile`](https://api.flutter.dev/flutter/flutter_test/matchesGoldenFile.html)

  Verifies that a widget's rendering matches a particular bitmap image ("golden file" testing).

## Complete example

[#](#complete-example)

dart

```
import 'package:flutter_test/flutter_test.dart';
import 'package:material_ui/material_ui.dart';
​
void main() {
  // 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', (tester) async {
    // Create the widget by telling the tester to build it.
    await tester.pumpWidget(const MyWidget(title: 'T', message: 'M'));
​
    // Create the Finders.
    final titleFinder = find.text('T');
    final messageFinder = find.text('M');
​
    // 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);
  });
}
​
class MyWidget extends StatelessWidget {
  const MyWidget({super.key, required this.title, required this.message});
​
  final String title;
  final String message;
​
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      home: Scaffold(
        appBar: AppBar(title: Text(title)),
        body: Center(child: Text(message)),
      ),
    );
  }
}
```

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-09-29. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/testing/widget/introduction.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/cookbook/testing/widget/introduction\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/testing/widget/introduction.md "Report an issue with this page").

----
url: https://docs.flutter.dev/tools/pubspec.md
----

\# Flutter pubspec options > Describes the Flutter-only fields in the pubspec file. Every project written in the Dart language, including all Flutter apps and libraries, includes a \`pubspec.yaml\` file, referred to as \_the pubspec\_. Creating a new project generates a basic pubspec at the top of the project tree that contains metadata and lists project dependencies. \*\*Fields common to both Dart and Flutter projects are described in \[The pubspec file]\[pubspec] on dart.dev.\*\* This page lists fields that are only valid for a Flutter project. \[Package dependency management]: /packages-and-plugins/dependency-management \[pubspec]: https\://dart.dev/tools/pub/pubspec ## Example When you create a new project with the \`flutter create\` command (or by using the equivalent button in your IDE), it creates a pubspec for a basic Flutter app. The first time you build your project, it also creates a \`pubspec.lock\` file that contains specific versions of the included packages. This ensures that you get the same version the next time the project is built. To learn more about how Flutter and Dart resolve package dependencies, check out \[Package dependency management]\[]. The following example of a pubspec file highlights fields used only in a Flutter app. Learn more about the non-highlighted fields on \[dart.dev]\[pubspec]. \`\`\`yaml title="pubspec.yaml" name: description: A new Flutter project. publish\_to: 'none' # Remove this line if you wish to publish to pub.dev version: 1.0.0+1 environment: sdk: ^3.13.0 dependencies: \[!flutter:!] # Required for every Flutter project \[!sdk: flutter!] # Required for every Flutter project \[!flutter\_localizations:!] # Required to enable localization \[!sdk: flutter!] # Required to enable localization \[!material\_ui!] # Required for projects that use the Material components pkg \[!cupertino\_ui!] # Required for projects that use the Cupertino components pkg \[!cupertino\_icons: ^1.0.8!] # Only required if you use Cupertino (iOS style) icons dev\_dependencies: \[!flutter\_test:!] \[!sdk: flutter!] # Required for a Flutter project that includes tests \[!integration\_test:!] \[!sdk: flutter!] # Required for integration tests \[!flutter\_lints: ^6.0.0!] # Contains a set of recommended lints for Flutter code \[!flutter:!] \[!uses-material-design: true!] # Required if you use the Material icon font \[!generate: true!] # Enables generation of localized strings from arb files \[!config:!] # App-specific configuration flags that mirror \`flutter config\` \[!enable-swift-package-manager: true!] \[!assets:!] # Lists assets, such as image files \[!- images/a\_dot\_burr.jpeg!] \[!- images/a\_dot\_ham.jpeg!] \[!licenses:!] # Lists additional license files to be bundled with the app \[!- assets/my\_license.txt!] \[!fonts:!] # Required if your app uses custom fonts \[!- family: Schyler!] \[!fonts:!] \[!- asset: fonts/Schyler-Regular.ttf!] \[!- asset: fonts/Schyler-Italic.ttf!] \[!style: italic!] \[!- family: Trajan Pro!] \[!fonts:!] \[!- asset: fonts/TrajanPro.ttf!] \[!- asset: fonts/TrajanPro\_Bold.ttf!] \[!weight: 700!] \`\`\` ## Fields This page discusses Flutter-only pubspec fields. Learn more about general Dart fields in \[Dart's pubspec supported fields]\[]. :::note The pubspec can have additional auto-generated Flutter fields that aren't listed here. ::: \[Dart's pubspec supported fields]: https\://dart.dev/tools/pub/pubspec#supported-fields ### assets field {: #assets } A list of asset paths that your app uses. These assets are bundled with your application. Common types of assets include static data (for example, \`JSON\`), configuration files, icons, and images (\`JPEG\`, \`WebP\`, \`GIF\`, animated \`WebP/GIF\`, \`PNG\`, \`BMP\`, and \`WBMP\`). Besides listing the images that are included in the app package, an image asset can also refer to one or more resolution-specific "variants". For more information, visit the \[resolution aware]\[] section of the \[Assets and images]\[] page. For information on adding assets from package dependencies, visit the \[asset images in package dependencies]\[] section in the same page. The \`assets\` field has the following structure: \`\`\`yaml title="pubspec.yaml" flutter: assets: - \[ path\_to\_file | path\_to\_directory ] \[ flavor\_path\_field | platform\_path\_field | transformer\_path\_field ] \[...] \`\`\` \`\`\`yaml # path\_to\_file structure - path/to/directory/file \`\`\` \`\`\`yaml # path\_to\_directory structure - path/to/directory/ \`\`\` \`\`\`yaml # flavor\_path\_field structure - path: path/to/directory flavors: - flavor\_name \`\`\` \`\`\`yaml # platform\_path\_field structure - path: path/to/file platforms: - platform\_name \`\`\` \`\`\`yaml # transformer\_path\_field structure - path: path/to/file transformers: - package: transformer\_package\_name args: \['arg1', 'arg2'] # Optional \`\`\` Subfields of \`assets\`: \* \`path\_to\_file\`: A string that represents the path to a file. \* \`path\_to\_directory\`: A string that represents the path to a directory. \* \`flavor\_path\_field\`: A path field and its flavor subfields. \* \`platform\_path\_field\`: A path field and its platform subfields. \* \`transformer\_path\_field\`: A path field and its transformer subfields. \* \`path\`: The path to an asset file or directory. \* \`flavors\`: A list of flutter flavors to use with assets at a specific path. To learn more about flavors, consult the guides on setting up flavors for \[Android]\[], \[iOS and macOS]\[], \[Linux]\[], and \[Windows]\[]. \* \`platforms\`: A list of platforms to use with assets at a specific path. Valid values are \`android\`, \`ios\`, \`web\`, \`linux\`, \`macos\`, and \`windows\`. \* \`transformers\`: A list of transformer packages to run on the asset at build time. To learn more, see \[Transforming assets at build time]\[]. You can pass in a path to a file: \`\`\`yaml title="pubspec.yaml" flutter: assets: - assets/images/my\_image\_a.png - assets/images/my\_image\_b.png \`\`\` You can pass in a path to a directory: \`\`\`yaml title="pubspec.yaml" flutter: assets: - assets/images/ - assets/icons/ \`\`\` You can pass in a path to a directory for specific flavors: \`\`\`yaml title="pubspec.yaml" flutter: assets: - path: assets/flavor\_a\_and\_b/images flavors: - flavor\_a - flavor\_b - path: assets/flavor\_c/images flavors: - flavor\_c \`\`\` You can pass in a path to a file for specific platforms: \`\`\`yaml title="pubspec.yaml" flutter: assets: - path: assets/web\_worker.js platforms: - web - path: assets/desktop\_icon.png platforms: - windows - linux - macos \`\`\` You can configure build-time asset transformers: \`\`\`yaml title="pubspec.yaml" flutter: assets: - path: assets/logo.svg transformers: - package: vector\_graphics\_compiler args: \['--tessellate'] \`\`\` \[Android]: /deployment/flavors \[iOS and macOS]: /deployment/flavors-ios \[Linux]: /deployment/flavors-linux \[Windows]: /deployment/flavors-windows \[Transforming assets at build time]: /ui/assets/asset-transformation \[Assets and images]: /ui/assets/assets-and-images \[asset images in package dependencies]: /ui/assets/assets-and-images#from-packages \[resolution aware]: /ui/assets/assets-and-images#resolution-aware ### config field {: #config } A map of keys to flags (\`true\` or \`false\`) that influences how the \`flutter\` CLI is executed. The available keys mirror those available in \`flutter config --list\`. \`\`\`yaml title="pubspec.yaml" flutter: config: cli-animations: false \`\`\` Use \`flutter config --help\` for a description of each flag. Flags are only read from the current \_application\_ package, and have no effect in the context of a package or dependency. []()#### enable-swift-package-manager field {: #enable-swift-package-manager } Control whether Flutter uses Swift Package Manager to manage native iOS and macOS dependencies for this project. To disable it for this project, set the field to \`false\`. \`\`\`yaml title="pubspec.yaml" flutter: config: enable-swift-package-manager: false \`\`\` :::version-note In Flutter versions earlier than 3.44, Swift Package Manager isn't enabled by default. Support for disabling Swift Package Manager will be removed in a future Flutter release. To get started with Swift Package Manager, check out \[Swift Package Manager for app developers]\[] or \[Swift Package Manager for plugin authors]\[]. ::: \[Swift Package Manager for app developers]: /packages-and-plugins/swift-package-manager/for-app-developers \[Swift Package Manager for plugin authors]: /packages-and-plugins/swift-package-manager/for-plugin-authors ### default-flavor field {: #default-flavor } Assign a default Flutter flavor for an app. When used, you don't need to include the name of this flavor in the Flutter launch command. \`\`\`yaml title="pubspec.yaml" flutter: default-flavor: flavor\_name \`\`\` In the following example, an Android Flutter app has a flavor called \`staging\` and \`production\`. The \`production\` flavor is the default flavor. When that flavor is run, you don't need to include it in the launch command. \`\`\`yaml title="pubspec.yaml" flutter: default-flavor: production \`\`\` \`\`\`console title="console" # Use this command to run the default flavor (production). $ flutter run # Use this command to run non-default flavors (staging). $ flutter run --flavor staging \`\`\` To learn how to create Flutter flavors, consult the guides on setting up flavors for \[Android]\[], \[iOS and macOS]\[], \[Linux]\[], and \[Windows]\[]. ### deferred-components field {: #deferred-components } Defer downloading components to reduce the initial download size of an Android app. Most often used with large applications, modularized applications, and applications with on-demand features. The \`deferred-components\` field has the following structure: \`\`\`yaml title="pubspec.yaml" flutter: deferred-components: - name: component\_name libraries: - string\_expression \[...] assets: - string\_expression \[...] \[...] \`\`\` Deferred component subfields: \* \`name\`: The unique identifier for a specific deferred component. \* \`libraries\`: A list of Dart libraries that are part of the deferred component. \* \`assets\`: A list of asset paths that are associated with the deferred component. Example: \`\`\`yaml title="pubspec.yaml" flutter: deferred-components: - name: box\_component libraries: - package:testdeferredcomponents/box.dart - name: gallery\_feature libraries: - package:testdeferredcomponents/gallery\_feature.dart assets: - assets/gallery\_images/gallery\_feature.png \`\`\` To learn more about how you can use deferred components with a Flutter Android app, see \[Deferred components for Android]. \[Deferred components for Android]: /perf/deferred-components ### flutter field {: #flutter } A field that contains Flutter-specific settings for your app. \`\`\`yaml title="pubspec.yaml" flutter: \[flutter\_field] \[...] \`\`\` ### fonts field {: #fonts } Configure and include custom fonts in your Flutter application. For examples of using fonts visit the \[Use a custom font]\[] and \[Export fonts from a package]\[] recipes in the Flutter cookbook. The \`fonts\` field has the following structure: \`\`\`yaml title="pubspec.yaml" flutter: fonts: - { font\_family\_field | font\_asset\_field } \[...] \`\`\` \`\`\`yaml # font\_family\_field structure - family: font\_name fonts: - font\_asset\_field \[...] \`\`\` \`\`\`yaml # font\_asset\_field structure - asset: path/to/directory/font\_name weight: int\_expression # Optional style: string\_expression # Optional \`\`\` Subfields of \`fonts\`: + \`family\`: Optional. The font family name. Can have multiple font assets. + \`asset\`: The font to use. + \`weight\`: Optional. The weight of the font. This can be \`100\`, \`200\`, \`300\`, \`400\`, \`500\`, \`600\`, \`700\`, \`800\` or \`900\`. + \`style\`: Optional. The style of the font. This can be \`italic\`. Use a font that is not part of a font family: \`\`\`yaml title="pubspec.yaml" flutter: fonts: - asset: fonts/Roboto-Regular.ttf weight: 900 # Optional style: italic # Optional \`\`\` Use a font family: \`\`\`yaml title="pubspec.yaml" flutter: fonts: - family: Roboto # Optional fonts: - asset: fonts/Roboto-Regular.ttf - asset: fonts/Roboto-Bold.ttf weight: 700 # Optional style: italic # Optional \`\`\` Alternatively, if you have a font that requires no family, weight or style requirements, you can declare it as a simple asset: \`\`\`yaml title="pubspec.yaml" flutter: assets: - fonts/Roboto-Regular.ttf \`\`\` \[Export fonts from a package]: /cookbook/design/package-fonts \[Use a custom font]: /cookbook/design/fonts ### generate field {: #generate } Enables generation of Dart files with localized strings from \`.arb\` files. Enable general localization: \`\`\`yaml title="pubspec.yaml" flutter: generate: true \`\`\` To learn more, visit \[Internationalizing Flutter apps]\[]. \[Internationalizing Flutter apps]: /ui/accessibility-and-internationalization/internationalization ### licenses field {: #licenses } A list of additional license file paths that should be bundled with your application. These files are typically found within your project's \`assets\` directory. The \`licenses\` field has the following structure: \`\`\`yaml title="pubspec.yaml" flutter: licenses: - \[path\_to\_file] \`\`\` ### module field {: #module } Configure settings for a Flutter module project when adding Flutter to an existing host Android or iOS application (Add-to-App). The \`module\` field has the following structure: \`\`\`yaml title="pubspec.yaml" flutter: module: androidPackage: com.example.my\_module # Optional iosBundleIdentifier: com.example.myModule # Optional \`\`\` Subfields of \`module\`: \* \`androidX\`: Optional. Specifies whether the module uses AndroidX. \* \`androidPackage\`: Optional. The package name used for generated Android code. \* \`iosBundleIdentifier\`: Optional. The bundle identifier used for generated iOS code. To learn more, visit \[Add Flutter to existing apps]\[]. \[Add Flutter to existing apps]: /add-to-app ### plugin field {: #plugin } Configure settings specifically for Flutter plugins. The \`plugin\` field has the following structure: \`\`\`yaml title="pubspec.yaml" flutter: plugin: platforms: android: # Optional package: com.example.my\_plugin pluginClass: MyPlugin dartPluginClass: MyPluginClassName ffiPlugin: true default\_package: my\_plugin\_name fileName: my\_file.dart ios: # Optional pluginClass: MyPlugin dartPluginClass: MyPluginClassName ffiPlugin: true default\_package: my\_plugin\_name fileName: my\_file.dart sharedDarwinSource: true macos: # Optional pluginClass: MyPlugin dartPluginClass: MyPluginClassName ffiPlugin: true default\_package: my\_plugin\_name fileName: my\_file.dart sharedDarwinSource: true windows: # Optional pluginClass: MyPlugin dartPluginClass: MyPluginClassName ffiPlugin: true default\_package: my\_plugin\_name fileName: my\_file.dart linux: # Optional pluginClass: MyPlugin dartPluginClass: MyPluginClassName ffiPlugin: true default\_package: my\_plugin\_name fileName: my\_file.dart web: # Optional ffiPlugin: true default\_package: my\_plugin\_name fileName: my\_file.dart implements: # Optional - example\_platform\_interface \`\`\` Subfields of \`plugin\`: \* \`platforms\`: A list of platforms that will have configuration settings. \* \`package\`: The Android package name of the plugin. This can be used with the Android platform and is required. \* \`pluginClass\`: The name of the plugin class. Optional if \`dartPluginClass\` is used for the same platform. This can be used with the Android, iOS, Linux macOS, and Windows platforms. \* \`default\_package\`: Optional. The package that should be used as the default implementation of a platform interface. Only applicable to federated plugins, where the plugin's implementation is split into multiple platform-specific packages. \* \`dartPluginClass\`: Optional. The Dart class that serves as the entry point for a Flutter plugin. This can be used with the Android, iOS, Linux macOS, and Windows platforms. \* \`sharedDarwinSource\`: Optional. Indicates that the plugin shares native code between iOS and macOS. This can be used with the iOS and macOS platforms. \* \`fileName\`: Optional. The file that contains the plugin class. \* \`ffiPlugin\`: Optional. True if the plugin uses a Foreign Function Interface (FFI). \* \`implements\`: Optional. The platform interfaces that a Flutter plugin implements. To learn more about plugins, see \[Developing packages & plugins]\[]. \[Developing packages & plugins]: /packages-and-plugins/developing-packages ### shaders field {: #shaders } GLSL shaders with the \`.frag\` extension must be declared in the \`shaders\` section of your project's \`pubspec.yaml\` file. The Flutter command-line tool compiles the shader to its appropriate backend format and generates its necessary runtime metadata. The compiled shader is then included in the application just like an asset. The \`shaders\` field accepts a list of individual file paths; directory paths aren't supported. The field has the following structure: \`\`\`yaml title="pubspec.yaml" flutter: shaders: - \[path\_to\_file] \[...] \`\`\` For example, to add specific shaders to your project: \`\`\`yaml title="pubspec.yaml" flutter: shaders: - assets/shaders/shader\_a.frag - assets/shaders/shader\_b.frag \`\`\` Don't list shader files under the \`assets\` field, either directly or through a directory that contains them. The Flutter tool reports an error if a shader is declared in both places. Like other assets, shaders also support \`flavors\`, \`platforms\`, and \`transformers\`. To learn more about using shaders in Flutter, check out \[Writing and using fragment shaders]\[]. \[Writing and using fragment shaders]: /ui/design/graphics/fragment-shaders ### uses-material-design field {: #uses-material-design } Use Material Design components and icon font in your Flutter app. \`\`\`yaml title="pubspec.yaml" flutter: uses-material-design: true \`\`\` ## Packages The following Flutter-specific packages can be added to the pubspec. If you add a package, run \`flutter pub get\` in your terminal to install the package. ### flutter package {: #flutter-package } A package that represents the Flutter SDK itself and can be added to the \`dependencies\` field. Use this if your project relies on the Flutter SDK, not a regular package from pub.dev. \`\`\`yaml title="pubspec.yaml" dependencies: flutter: sdk: flutter \`\`\` ### flutter\_localizations package {: #flutter-localizations } A package provided by the Flutter SDK that enables localization of \`ARB\` files. Often used with the \`intl\` package. \`\`\`yaml title="pubspec.yaml" dependencies: flutter\_localizations: sdk: flutter intl: any \`\`\` ### flutter\_test package {: #flutter\_test } A package provided by the Flutter SDK that contains testing utilities for unit and widget tests for your Flutter app. \`\`\`yaml title="pubspec.yaml" dev\_dependencies: flutter\_test: sdk: flutter \`\`\` ### integration\_test package {: #integration\_test } A package provided by the Flutter SDK for running integration tests on target devices or emulators. \`\`\`yaml title="pubspec.yaml" dev\_dependencies: integration\_test: sdk: flutter \`\`\` To learn more, visit \[Integration testing]\[]. \[Integration testing]: /testing/integration-tests ### flutter\_lints package {: #flutter\_lints } A package that provides a set of recommended lints for Flutter projects. This package can be added to the \`dev\_dependencies\` field in the pubspec. \`\`\`yaml title="pubspec.yaml" dev\_dependencies: flutter\_lints: ^6.0.0 \`\`\` ### cupertino\_icons package {: #cupertino\_icons } A package that provides a set of Apple's Cupertino icons for use in Flutter applications. This package can be added to the \`dependencies\` field in the pubspec. \`\`\`yaml title="pubspec.yaml" dependencies: cupertino\_icons: ^1.0.8 \`\`\` ## More information For more information on packages, plugins, and pubspec files, visit the following: \* \[Add Flutter to existing apps]\[] \* \[Creating packages]\[] on dart.dev \* \[Glossary of package terms]\[] on dart.dev \* \[Integration testing]\[] \* \[Package dependency management]\[] \* \[Package dependencies]\[] on dart.dev \* \[Set up Flutter flavors for Android]\[Android] \* \[Set up Flutter flavors for iOS and macOS]\[iOS and macOS] \* \[Set up Flutter flavors for Linux]\[Linux] \* \[Set up Flutter flavors for Windows]\[Windows] \* \[Transforming assets at build time]\[] \* \[Using packages]\[] \* \[What not to commit]\[] on dart.dev \[Creating packages]: https\://dart.dev/guides/libraries/create-library-packages \[Developing packages and plugins]: /packages-and-plugins/developing-packages \[Federated plugins]: /packages-and-plugins/developing-packages#federated-plugins \[Glossary of package terms]: https\://dart.dev/tools/pub/glossary \[Package dependencies]: https\://dart.dev/tools/pub/dependencies \[Using packages]: /packages-and-plugins/using-packages \[What not to commit]: https\://dart.dev/guides/libraries/private-files#pubspeclock

----
url: https://docs.flutter.dev/cookbook/audio/record.md
----

\# Record or stream audio input > Learn how to record or stream audio input in your Flutter app using the record package. This recipe demonstrates how to use the \[\`record\` package]\[] to add audio recording and streaming capabilities to your Flutter app. To get started using the package, follow these steps: \[\`record\` package]: https\://pub.dev/packages/record ## 1. Add the package dependency To add \`package:record\` as a dependency, use \`flutter pub add\`: \`\`\`console $ flutter pub add record \`\`\` ## 2. Initialize an \`AudioRecorder\` Initialize an \`AudioRecorder\` object. This is the primary object that controls the recording process. \`\`\`dart import 'package:record/record.dart'; final recorder = AudioRecorder(); \`\`\` ## 3. Request user permission Before recording, you need to request user permission. You might also need to add platform-specific permission configurations. Refer to the \[\`record\` package]\[] documentation for details. \`\`\`dart final recorder = AudioRecorder(); if (await recorder.hasPermission()) { // Permission granted, proceed with recording. } else { // Permission denied. } \`\`\` ## 4. Create a recording configuration Create and configure a \`RecordConfig\` object to specify the record settings, such as the encoder, sample rate, and channel number. You can also enable features like auto gain, echo cancellation, and noise suppression. \`\`\`dart final recordConfig = RecordConfig( encoder: AudioEncoder.pcm16bits, sampleRate: 24000, numChannels: 1, autoGain: true, echoCancel: true, noiseSuppress: true, ); \`\`\` ## 5. Start recording to a file To start recording to a file, call the \`start\` method on the \`AudioRecorder\`, passing in the \`recordConfig\` you defined and the path where the file should be stored. \`\`\`dart // TODO: Specify the path where the audio file should be saved. final audioFilePath = 'myRecording.wav'; await recorder.start(recordConfig, path: audioFilePath); \`\`\` ## 6. Control an ongoing recording You can control an ongoing recording using the \`pause\`, \`resume\`, and \`stop\` methods on the \`AudioRecorder\`. \`\`\`dart await recorder.pause(); await recorder.resume(); await recorder.stop(); \`\`\` ## 7. \[Optional] Record to an audio stream To stream audio, use the \`startStream\` method. This returns a \[stream]\[] of audio data. \`\`\`dart final stream = await recorder.startStream(recordConfig); stream.listen((audioChunk) { // Process the audio data. For example, send it to a server. }); \`\`\` \[stream]: https\://api.flutter.dev/flutter/dart-async/Stream-class.html ## 8. Stop recording To stop the recording and get the path to the saved file, call the asynchronous \`stop\` method on the \`AudioRecorder\`. \`\`\`dart final path = await recorder.stop(); print('Recording stopped. File saved to: $path'); \`\`\` ## 9. Dispose of the recorder When you are finished using the \`AudioRecorder\`, remember to call its \`dispose\` method to release its resources. \`\`\`dart await recorder.dispose(); \`\`\` ## Supported formats and encodings The \`record\` package supports various encoders and file formats, but support varies by platform. For the full list of supported encoders per platform, check out the package's \[encoding support table]\[]. For more detailed information and examples, visit the \[\`record\` package]\[] page on pub.dev or check out the \`record\` Package of the Week video.\[encoding support table]: https\://pub.dev/packages/record#file

----
url: https://docs.flutter.dev/cookbook/design/fonts
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Use a custom font](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

Although Android and iOS offer high quality system fonts, designers want support for custom fonts. You might have a custom-built font from a designer, or perhaps you downloaded a font from [Google Fonts](https://fonts.google.com).

A typeface is the collection of glyphs or shapes that comprise a given style of lettering. A font is one representation of that typeface at a given weight or variation. Roboto is a typeface and Roboto Bold is a font.

Flutter lets you apply a custom font across an entire app or to individual widgets. This recipe creates an app that uses custom fonts with the following steps.

1. Choose your fonts.
2. Import the font files.
3. Declare the font in the pubspec.
4. Set a font as the default.
5. Use a font in a specific widget.

You don't need to follow each step as you go. The guide offers completed example files at the end.

## Choose a font

[#](#choose-a-font)

Your choice of font should be more than a preference. Consider which file formats work with Flutter and how the font could affect design options and app performance.

#### Pick a supported font format

[#](#pick-a-supported-font-format)

Flutter supports the following font formats:

* OpenType font collections: `.ttc`
* TrueType fonts: `.ttf`
* OpenType fonts: `.otf`

Flutter does not support fonts in the Web Open Font Format, `.woff` and `.woff2`, on desktop platforms.

#### Choose fonts for their specific benefits

[#](#choose-fonts-for-their-specific-benefits)

Few sources agree on what a font file type is or which uses less space. The key difference between font file types involves how the format encodes the glyphs in the file. Most TrueType and OpenType font files have similar capabilities as they borrowed from each other as the formats and fonts improved over time.

Which font you should use depends on the following considerations.

* How much variation you need for fonts in your app?
* How much file size you can accept fonts using in your app?
* How many languages you need to support in your app?

Research what options a given font offers, like more than one weight or style per font file, [variable font capability](https://fonts.google.com/knowledge/introducing_type/introducing_variable_fonts), the availability of multiple font files for a multiple font weights, or more than one width per font.

Choose the typeface or font family that meets the design needs of your app.

## Import the font files

[#](#import-the-font-files)

To work with a font, import its font files into your Flutter project.

To import font files, perform the following steps.

1. If necessary, to match the remaining steps in this guide, change the name of your Flutter app to `custom_fonts`.

   ```
   $ mv /path/to/my_app /path/to/custom_fonts
   ```

2. Navigate to the root of your Flutter project.

   ```
   $ cd /path/to/custom_fonts
   ```

3. Create a `fonts` directory at the root of your Flutter project.

   ```
   $ mkdir fonts
   ```

4. Move or copy the font files in a `fonts` or `assets` folder at the root of your Flutter project.

   ```
   $ cp ~/Downloads/*.ttf ./fonts
   ```

The resulting folder structure should resemble the following:

```
custom_fonts/
|- fonts/
  |- Raleway-Regular.ttf
  |- Raleway-Italic.ttf
  |- RobotoMono-Regular.ttf
  |- RobotoMono-Bold.ttf
```

## Declare the font in the pubspec.yaml file

[#](#declare-the-font-in-the-pubspec-yaml-file)

After you've downloaded a font, include a font definition in the `pubspec.yaml` file. This font definition also specifies which font file should be used to render a given weight or style in your app.

### Define fonts in the `pubspec.yaml` file

[#](#define-fonts-in-the-pubspec-yaml-file)

To add font files to your Flutter app, complete the following steps.

1. Open the `pubspec.yaml` file at the root of your Flutter project.

   ```
   $ vi pubspec.yaml
   ```

2. Paste the following YAML block after the `flutter` declaration.

   yaml

   ```
     fonts:
       - family: Raleway
         fonts:
           - asset: fonts/Raleway-Regular.ttf
           - asset: fonts/Raleway-Italic.ttf
             style: italic
       - family: RobotoMono
         fonts:
           - asset: fonts/RobotoMono-Regular.ttf
           - asset: fonts/RobotoMono-Bold.ttf
             weight: 700
   ```

This `pubspec.yaml` file defines the italic style for the `Raleway` font family as the `Raleway-Italic.ttf` font file. When you set `style: TextStyle(fontStyle: FontStyle.italic)`, Flutter swaps `Raleway-Regular` with `Raleway-Italic`.

The `family` value sets the name of the typeface. You use this name in the [`fontFamily`](https://api.flutter.dev/flutter/painting/TextStyle/fontFamily.html) property of a [`TextStyle`](https://api.flutter.dev/flutter/painting/TextStyle-class.html) object.

The value of an `asset` is a relative path from the `pubspec.yaml` file to the font file. These files contain the outlines for the glyphs in the font. When building the app, Flutter includes these files in the app's asset bundle.

### Include font files for each font

[#](#include-font-files-for-each-font)

Different typefaces implement font files in different ways. If you need a typeface with a variety of font weights and styles, choose and import font files that represent that variety.

When you import a font file that doesn't include either multiple fonts within it or variable font capabilities, don't use the `style` or `weight` property to adjust how they display. If you use those properties on a regular font file, Flutter attempts to *simulate* the look. The visual result will look quite different from using the correct font file.

### Declare styles and weights

[#](#declare-styles-and-weights)

Flutter doesn't infer a font's weight or style from its file name. To use a specific weight or style, you must explicitly declare the `weight` or `style` property for that font file in your `pubspec.yaml`.

#### Set font weight

[#](#set-font-weight)

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`](https://api.flutter.dev/flutter/dart-ui/FontWeight-class.html) and can be used in the [`fontWeight`](https://api.flutter.dev/flutter/painting/TextStyle/fontWeight.html) property of a [`TextStyle`](https://api.flutter.dev/flutter/painting/TextStyle-class.html) object.

In the `pubspec.yaml` shown in this guide, you defined `RobotoMono-Bold` as the `700` weight of the font family. To use the `RobotoMono-Bold` font that you added to your app, set `fontWeight` to `FontWeight.w700` in your `TextStyle` widget.

If you hadn't added `RobotoMono-Bold` to your app and declared its weight, Flutter attempts to make the regular font *look* bold. The text then might appear to be somewhat darker.

You can't use the `weight` property to override the weight of the font. You can't set `RobotoMono-Bold` to any other weight than `700`. If you set `TextStyle(fontFamily: 'RobotoMono', fontWeight: FontWeight.w900)`, the displayed font would still render as however bold `RobotoMono-Bold` looks.

#### Set font style

[#](#set-font-style)

The `style` property specifies whether the glyphs in the font file display as either `italic` or `normal`. These values correspond to the [`FontStyle`](https://api.flutter.dev/flutter/dart-ui/FontStyle.html). You can use these styles in the [`fontStyle`](https://api.flutter.dev/flutter/painting/TextStyle/fontStyle.html) property of a [`TextStyle`](https://api.flutter.dev/flutter/painting/TextStyle-class.html) object.

In the `pubspec.yaml` shown in this guide, you defined `Raleway-Italic` as being in the `italic` style. To use the `Raleway-Italic` font that you added to your app, set `style: TextStyle(fontStyle: FontStyle.italic)`. Flutter swaps `Raleway-Regular` with `Raleway-Italic` when rendering.

If you hadn't added `Raleway-Italic` to your app and declared its style, Flutter attempts to make the regular font *look* italic. The text then might appear to be leaning to the right.

You can't use the `style` property to override the glyphs of a font. If you set `TextStyle(fontFamily: 'Raleway', fontStyle: FontStyle.normal)`, the displayed font would still render as italic. The `regular` style of an italic font *is* italic.

## Set a font as the default

[#](#set-a-font-as-the-default)

To apply a font to text, you can set the font as the app's default font in its `theme`.

To set a default font, set the `fontFamily` property in the app's `theme`. Match the `fontFamily` value to the `family` name declared in the `pubspec.yaml` file.

The result would resemble the following code.

dart

```
return MaterialApp(
  title: 'Custom Fonts',
  // Set Raleway as the default app font.
  theme: ThemeData(fontFamily: 'Raleway'),
  home: const MyHomePage(),
);
```

To learn more about themes, check out the [Using Themes to share colors and font styles](/cookbook/design/themes) recipe.

## Set the font in a specific widget

[#](#set-the-font-in-a-specific-widget)

To apply the font to a specific widget like a `Text` widget, provide a [`TextStyle`](https://api.flutter.dev/flutter/painting/TextStyle-class.html) to the widget.

For this guide, try to apply the `RobotoMono` font to a single `Text` widget. Match the `fontFamily` value to the `family` name declared in the `pubspec.yaml` file.

The result would resemble the following code.

dart

```
child: Text(
  'Roboto Mono sample',
  style: TextStyle(fontFamily: 'RobotoMono'),
),
```

## Try the complete example

[#](#try-the-complete-example)

### Download fonts

[#](#download-fonts)

Download the Raleway and RobotoMono font files from [Google Fonts](https://fonts.google.com).

### Update the `pubspec.yaml` file

[#](#update-the-pubspec-yaml-file)

1. Open the `pubspec.yaml` file at the root of your Flutter project.

   ```
   $ vi pubspec.yaml
   ```

2. Replace its contents with the following YAML.

   yaml

   ```
   name: custom_fonts
   description: An example of how to use custom fonts with Flutter
   ​
   dependencies:
     flutter:
       sdk: flutter
   ​
   dev_dependencies:
     flutter_test:
       sdk: flutter
   ​
   flutter:
     fonts:
       - family: Raleway
         fonts:
           - asset: fonts/Raleway-Regular.ttf
           - asset: fonts/Raleway-Italic.ttf
             style: italic
       - family: RobotoMono
         fonts:
           - asset: fonts/RobotoMono-Regular.ttf
           - asset: fonts/RobotoMono-Bold.ttf
             weight: 700
     uses-material-design: true
   ```

### Use this `main.dart` file

[#](#use-this-main-dart-file)

1. Open the `main.dart` file in the `lib/` directory of your Flutter project.

   ```
   $ vi lib/main.dart
   ```

2. Replace its contents with the following Dart code.

   dart

   ```
   import 'package:material_ui/material_ui.dart';
   ​
   void main() => runApp(const MyApp());
   ​
   class MyApp extends StatelessWidget {
     const MyApp({super.key});
   ​
     @override
     Widget build(BuildContext context) {
       return MaterialApp(
         title: 'Custom Fonts',
         // Set Raleway as the default app font.
         theme: ThemeData(fontFamily: 'Raleway'),
         home: const MyHomePage(),
       );
     }
   }
   ​
   class MyHomePage extends StatelessWidget {
     const MyHomePage({super.key});
   ​
     @override
     Widget build(BuildContext context) {
       return Scaffold(
         // The AppBar uses the app-default Raleway font.
         appBar: AppBar(title: const Text('Custom Fonts')),
         body: const Center(
           // This Text widget uses the RobotoMono font.
           child: Text(
             'Roboto Mono sample',
             style: TextStyle(fontFamily: 'RobotoMono'),
           ),
         ),
       );
     }
   }
   ```

The resulting Flutter app should display the following screen.

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-09-29. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/design/fonts.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/cookbook/design/fonts\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/design/fonts.md "Report an issue with this page").

----
url: https://docs.flutter.dev/cookbook/testing/unit/introduction
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top An introduction to unit testing](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

How can you ensure that your app continues to work as you add more features or change existing functionality? By writing tests.

Unit tests are handy for verifying the behavior of a single function, method, or class. The [`test`](https://pub.dev/packages/test) package provides the core framework for writing unit tests, and the [`flutter_test`](https://api.flutter.dev/flutter/flutter_test/flutter_test-library.html) package provides additional utilities for testing widgets.

This recipe demonstrates the core features provided by the `test` package using the following steps:

1. Add the `test` or `flutter_test` dependency.
2. Create a test file.
3. Create a class to test.
4. Write a `test` for our class.
5. Combine multiple tests in a `group`.
6. Run the tests.

For more information about the test package, see the [test package documentation](https://pub.dev/packages/test).

## 1. Add the test dependency

[#](#1-add-the-test-dependency)

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.

To add the `test` package as a dev dependency, run `flutter pub add`:

```
$ flutter pub add dev:test
```

## 2. Create a test file

[#](#2-create-a-test-file)

In this example, create two files: `counter.dart` and `counter_test.dart`.

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 of your Flutter application or package. Test files should always end with `_test.dart`, this is the convention used by the test runner when searching for tests.

When you're finished, the folder structure should look like this:

```
counter_app/
  lib/
    counter.dart
  test/
    counter_test.dart
```

## 3. Create a class to test

[#](#3-create-a-class-to-test)

Next, you need a "unit" to test. Remember: "unit" is another name for a 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`.

dart

```
class Counter {
  int value = 0;
​
  void increment() => value++;
​
  void decrement() => value--;
}
```

**Note:** For simplicity, this tutorial does not follow the "Test Driven Development" approach. If you're more comfortable with that style of development, you can always go that route.

## 4. Write a test for our class

[#](#4-write-a-test-for-our-class)

Inside the `counter_test.dart` file, write the first unit test. Tests are defined using the top-level `test` function, and you can check if the results are correct by using the top-level `expect` function. Both of these functions come from the `test` package.

dart

```
// Import the test package and Counter class
import 'package:counter_app/counter.dart';
import 'package:test/test.dart';
​
void main() {
  test('Counter value should be incremented', () {
    final counter = Counter();
​
    counter.increment();
​
    expect(counter.value, 1);
  });
}
```

## 5. Combine multiple tests in a `group`

[#](#5-combine-multiple-tests-in-a-group)

If you want to run a series of related tests, use the `flutter_test` package [`group`](https://api.flutter.dev/flutter/flutter_test/group.html) function to categorize the tests. Once put into a group, you can call `flutter test` on all tests in that group with one command.

dart

```
import 'package:counter_app/counter.dart';
import 'package:test/test.dart';
​
void main() {
  group('Test start, increment, decrement', () {
    test('value should start at 0', () {
      expect(Counter().value, 0);
    });
​
    test('value should be incremented', () {
      final counter = Counter();
​
      counter.increment();
​
      expect(counter.value, 1);
    });
​
    test('value should be decremented', () {
      final counter = Counter();
​
      counter.decrement();
​
      expect(counter.value, -1);
    });
  });
}
```

## 6. Run the tests

[#](#6-run-the-tests)

Now that you have a `Counter` class with tests in place, you can run the tests.

### Run tests using IntelliJ or VSCode

[#](#run-tests-using-intellij-or-vscode)

The Flutter plugins for IntelliJ and VSCode support running tests. This is often the best option while writing tests because it provides the fastest feedback loop as well as the ability to set breakpoints.

* **IntelliJ**

  1. Open the `counter_test.dart` file
  2. Go to **Run** > **Run 'tests in counter\_test.dart'**. You can also press the appropriate keyboard shortcut for your platform.

* **VSCode**

  1. Open the `counter_test.dart` file
  2. Go to **Run** > **Start Debugging**. You can also press the appropriate keyboard shortcut for your platform.

### Run tests in a terminal

[#](#run-tests-in-a-terminal)

To run the all tests from the terminal, run the following command from the root of the project:

```
flutter test test/counter_test.dart
```

To run all tests you put into one `group`, run the following command from the root of the project:

```
flutter test --plain-name "Test start, increment, decrement"
```

This example uses the `group` created in **section 5**.

To learn more about unit tests, you can execute this command:

```
flutter test --help
```

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/testing/unit/introduction.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/cookbook/testing/unit/introduction\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/testing/unit/introduction.md "Report an issue with this page").

----
url: https://docs.flutter.dev/tools/hot-reload
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Hot reload](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

Flutter's hot reload feature helps you quickly and easily experiment, build UIs, add features, and fix bugs. Hot reload works by injecting updated source code files into the [Dart runtime](https://dart.dev/overview#platform). After the Dart runtime updates classes with the new versions of fields and functions, the Flutter framework automatically rebuilds the widget tree, allowing you to quickly view the effects of your changes.

A demo of hot reload in DartPad

## How to perform a hot reload

[#](#how-to-perform-a-hot-reload)

If you are using an AI coding assistant like Google Antigravity, the assistant can automatically hot reload your running application after making changes, or when asked to "Reload the app". To learn more, check out [Get started with AI](/ai/get-started).

To hot reload a Flutter app manually:

1. Run the app from a supported [Flutter editor](/tools/editors) or a terminal window. Either a physical or virtual device can be the target. **Only Flutter apps in debug mode can be hot reloaded or hot restarted.**

2. Modify one of the Dart files in your project. Most types of code changes can be hot reloaded; for a list of changes that require a hot restart, see [Special cases](#special-cases).

3. If you're working in an IDE/editor that supports Flutter's IDE tools and hot reload on save is enabled, select **Save All** (`cmd-s`/`ctrl-s`), or click the hot reload button on the toolbar.

   []()

   If you're running the app at the command line using `flutter run`, enter `r` in the terminal window.

After a successful hot reload operation, you'll see a message in the console similar to:

```
Performing hot reload...
Reloaded 1 of 448 libraries in 978ms.
```

The app updates to reflect your change, and the current state of the app is preserved. Your app continues to execute from where it was prior to run the hot reload command. The code updates and execution continues.

[]()

Controls for run, run debug, hot reload, and hot restart in Android Studio

A code change has a visible effect only if the modified Dart code is run again after the change. Specifically, a hot reload causes all the existing widgets to rebuild. Only code involved in the rebuilding of the widgets is automatically re-executed. The `main()` and `initState()` functions, for example, are not run again.

## Special cases

[#](#special-cases)

The next sections describe specific scenarios that involve hot reload. In some cases, small changes to the Dart code enable you to continue using hot reload for your app. In other cases, a hot restart, or a full restart is needed.

### An app is killed

[#](#an-app-is-killed)

Hot reload can break when the app is killed. For example, if the app was in the background for too long.

### Compilation errors

[#](#compilation-errors)

When a code change introduces a compilation error, hot reload generates an error message similar to:

```
Hot reload was rejected:
'/path/to/project/lib/main.dart': warning: line 16 pos 38: unbalanced '{' opens here
  Widget build(BuildContext context) {
                                     ^
'/path/to/project/lib/main.dart': error: line 33 pos 5: unbalanced ')'
    );
    ^
```

In this situation, simply correct the errors on the specified lines of Dart code to keep using hot reload.

### CupertinoTabView's builder

[#](#cupertinotabviews-builder)

Hot reload won't apply changes made to a `builder` of a `CupertinoTabView`. For more information, see [Issue 43574](https://github.com/flutter/flutter/issues/43574).

### Enumerated types

[#](#enumerated-types)

Hot reload doesn't work when enumerated types are changed to regular classes or regular classes are changed to enumerated types.

For example:

Before the change:

dart

```
enum Color { red, green, blue }
```

After the change:

dart

```
class Color {
  Color(this.i, this.j);
  final int i;
  final int j;
}
```

### Generic types

[#](#generic-types)

Hot reload won't work when generic type declarations are modified. For example, the following won't work:

Before the change:

dart

```
class A<T> {
  T? i;
}
```

After the change:

dart

```
class A<T, V> {
  T? i;
  V? v;
}
```

### Native code

[#](#native-code)

If you've changed native code (such as Kotlin, Java, Swift, or Objective-C), you must perform a full restart (stop and restart the app) to see the changes take effect.

### Previous state is combined with new code

[#](#previous-state-is-combined-with-new-code)

Flutter's stateful hot reload preserves the state of your app. This approach enables you to view the effect of the most recent change only, without throwing away the current state. For example, if your app requires a user to log in, you can modify and hot reload a page several levels down in the navigation hierarchy, without re-entering your login credentials. State is kept, which is usually the desired behavior.

If code changes affect the state of your app (or its dependencies), the data your app has to work with might not be fully consistent with the data it would have if it executed from scratch. The result might be different behavior after a hot reload versus a hot restart.

### Recent code change is included but app state is excluded

[#](#recent-code-change-is-included-but-app-state-is-excluded)

In Dart, [static fields are lazily initialized](https://dart.dev/language/classes#static-variables). This means that the first time you run a Flutter app and a static field is read, it's set to whatever value its initializer was evaluated to. Global variables and static fields are treated as state, and are therefore not reinitialized during hot reload.

If you change initializers of global variables and static fields, a hot restart or restart the state where the initializers are hold is necessary to see the changes. For example, consider the following code:

dart

```
final sampleTable = [
  Table(
    children: const [
      TableRow(children: [Text('T1')]),
    ],
  ),
  Table(
    children: const [
      TableRow(children: [Text('T2')]),
    ],
  ),
  Table(
    children: const [
      TableRow(children: [Text('T3')]),
    ],
  ),
  Table(
    children: const [
      TableRow(children: [Text('T4')]),
    ],
  ),
];
```

After running the app, you make the following change:

dart

```
final sampleTable = [
  Table(
    children: const [
      TableRow(children: [Text('T1')]),
    ],
  ),
  Table(
    children: const [
      TableRow(children: [Text('T2')]),
    ],
  ),
  Table(
    children: const [
      TableRow(children: [Text('T3')]),
    ],
  ),
  Table(
    children: const [
      TableRow(
        children: [Text('T10')], // modified
      ),
    ],
  ),
];
```

You hot reload, but the change is not reflected.

Conversely, in the following example:

dart

```
const foo = 1;
final bar = foo;
void onClick() {
  print(foo);
  print(bar);
}
```

Running the app for the first time prints `1` and `1`. Then, you make the following change:

dart

```
const foo = 2; // modified
final bar = foo;
void onClick() {
  print(foo);
  print(bar);
}
```

While changes to `const` field values are always hot reloaded, the static field initializer is not rerun. Conceptually, `const` fields are treated like aliases instead of state.

The Dart VM detects initializer changes and flags when a set of changes needs a hot restart to take effect. The flagging mechanism is triggered for most of the initialization work in the above example, but not for cases like the following:

dart

```
final bar = foo;
```

To update `foo` and view the change after hot reload, consider redefining the field as `const` or using a getter to return the value, rather than using `final`. For example, either of the following solutions work:

dart

```
const foo = 1;
const bar = foo; // Convert foo to a const...
void onClick() {
  print(foo);
  print(bar);
}
```

dart

```
const foo = 1;
int get bar => foo; // ...or provide a getter.
void onClick() {
  print(foo);
  print(bar);
}
```

For more information, read about the [differences between the `const` and `final` keywords](https://dart.dev/language/variables#final-and-const) in Dart.

### Recent UI change is excluded

[#](#recent-ui-change-is-excluded)

Even when a hot reload operation appears successful and generates no exceptions, some code changes might not be visible in the refreshed UI. This behavior is common after changes to the app's `main()` or `initState()` methods.

As a general rule, if the modified code is downstream of the root widget's `build()` method, then hot reload behaves as expected. However, if the modified code won't be re-executed as a result of rebuilding the widget tree, then you won't see its effects after hot reload.

For example, consider the following code:

dart

```
import 'package:material_ui/material_ui.dart';
​
void main() {
  runApp(MyApp());
}
​
class MyApp extends StatelessWidget {
  const MyApp({super.key});
​
  @override
  Widget build(BuildContext context) {
    return GestureDetector(onTap: () => print('tapped'));
  }
}
```

After running this app, change the code as follows:

dart

```
import 'package:flutter/widgets.dart';
import 'package:material_ui/material_ui.dart';
​
void main() {
  runApp(const Center(child: Text('Hello', textDirection: TextDirection.ltr)));
}
```

With a hot restart, the program starts from the beginning, executes the new version of `main()`, and builds a widget tree that displays the text `Hello`.

However, if you hot reload the app after this change, `main()` and `initState()` are not re-executed, and the widget tree is rebuilt with the unchanged instance of `MyApp` as the root widget. This results in no visible change after hot reload.

## How it works

[#](#how-it-works)

When hot reload is invoked, the host machine looks at the edited code since the last compilation. The following libraries are recompiled:

* Any libraries with changed code
* The application's main library
* The libraries from the main library leading to affected libraries

The source code from those libraries is compiled into [kernel files](https://github.com/dart-lang/sdk/tree/main/pkg/kernel) and sent to the mobile device's Dart VM.

The Dart VM re-loads all libraries from the new kernel file. So far no code is re-executed.

The hot reload mechanism then causes the Flutter framework to trigger a rebuild/re-layout/repaint of all existing widgets and render objects.

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-09-29. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/hot-reload.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/tools/hot-reload\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/hot-reload.md "Report an issue with this page").

----
url: https://docs.flutter.dev/deployment/android
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Build and release an Android app](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

To test an app, you can use `flutter run` at the command line, or the **Run** and **Debug** options in your IDE.

When you're ready to prepare a *release* version of your app, for example to [publish to the Google Play Store](https://developer.android.com/distribute), this page can help. Before publishing, you might want to put some finishing touches on your app. This guide explains how to perform the following tasks:

* [Add a launcher icon](#add-a-launcher-icon)
* [Enable Material Components](#enable-material-components)
* [Sign the app](#sign-the-app)
* [Shrink your code with R8](#shrink-your-code-with-r8)
* [Enable multidex support](#enable-multidex-support)
* [Review the app manifest](#review-the-app-manifest)
* [Review the build configuration](#review-the-gradle-build-configuration)
* [Build the app for release](#build-the-app-for-release)
* [Publish to the Google Play Store](#publish-to-the-google-play-store)
* [Update the app's version number](#update-the-apps-version-number)
* [Android release FAQ](#android-release-faq)

## Add a launcher icon

[#](#add-a-launcher-icon)

When a new Flutter app is created, it has a default launcher icon. To learn about customizing this icon, check out the [flutter\_launcher\_icons](https://pub.dev/packages/flutter_launcher_icons) package.

Alternatively, you can do it manually using the following steps:

1. Review the Android [adaptive icon guidelines](https://developer.android.com/develop/ui/views/launch/icon_design_adaptive) for icon design and sizing specifications.

2. In the `[project]/android/app/src/main/res/` directory, place your icon files in folders named using [configuration qualifiers](https://developer.android.com/guide/topics/resources/providing-resources#AlternativeResources). The default `mipmap-` folders demonstrate the correct naming convention.

3. In `AndroidManifest.xml`, update the [`application`](https://developer.android.com/guide/topics/manifest/application-element) tag's `android:icon` attribute to reference icons from the previous step (for example, `<application android:icon="@mipmap/ic_launcher" ...`).

4. To verify that the icon has been replaced, run your app and inspect the app icon in the Launcher.

## Enable Material Components

[#](#enable-material-components)

If your app uses [platform views](/platform-integration/android/platform-views), you might want to enable Material Components by following the steps described in the [Getting Started guide for Android](https://m3.material.io/develop/android/mdc-android).

For example:

1. Add the dependency on Android's Material in `<my-app>/android/app/build.gradle.kts`:

* [Kotlin](#232-tab-panel)
* [Groovy](#233-tab-panel)

kotlin

```
dependencies {
    // ...
    implementation("com.google.android.material:material:<version>")
    // ...
}
```

groovy

```
dependencies {
    // ...
    implementation 'com.google.android.material:material:<version>'
    // ...
}
```

To find out the latest version, visit [Google Maven](https://maven.google.com/web/index.html#com.google.android.material:material).

1. Set the light theme in `<my-app>/android/app/src/main/res/values/styles.xml`:

   xml

   ```
   <style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
   <style name="NormalTheme" parent="Theme.MaterialComponents.Light.NoActionBar">
   ```

2. Set the dark theme in `<my-app>/android/app/src/main/res/values-night/styles.xml`:

   xml

   ```
   <style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
   <style name="NormalTheme" parent="Theme.MaterialComponents.DayNight.NoActionBar">
   ```

[]()

## Sign the app

[#](#sign-the-app)

To publish on the Play Store, you must sign your app with a digital certificate.

Android uses two signing keys: *upload* and *app signing*.

* Developers upload an `.aab` or `.apk` file signed with an *upload key* to the Play Store.
* The end-users download the `.apk` file signed with an *app signing key*.

To create your app signing key, use Play App Signing as described in the [official Play Store documentation](https://support.google.com/googleplay/android-developer/answer/7384423?hl=en).

To sign your app, use the following instructions.

### Create an upload keystore

[#](#create-an-upload-keystore)

If you have an existing keystore, skip to the next step. If not, create one using one of the following methods:

1. Follow the [Android Studio key generation steps](https://developer.android.com/studio/publish/app-signing#generate-key).

2. Run the following command at the command line:

   On macOS or Linux, use the following command:

   ```
   keytool -genkey -v -keystore ~/upload-keystore.jks -keyalg RSA \
           -storetype JKS -keysize 2048 -validity 10000 -alias upload
   ```

   On Windows, use the following command in PowerShell:

   ```
   keytool -genkey -v -keystore $env:USERPROFILE\upload-keystore.jks `
           -storetype JKS -keyalg RSA -keysize 2048 -validity 10000 `
           -alias upload
   ```

   This command stores the `upload-keystore.jks` file in your home directory. If you want to store it elsewhere, change the argument you pass to the `-keystore` parameter. **However, keep the `keystore` file private; don't check it into public source control!**

### Reference the keystore from the app

[#](#reference-the-keystore-from-the-app)

Create a file named `[project]/android/key.properties` that contains a reference to your keystore. Don't include the angle brackets (`< >`), which indicate that the text serves as a placeholder for your values:

properties

```
storePassword=<password-from-previous-step>
keyPassword=<password-from-previous-step>
keyAlias=upload
storeFile=<keystore-file-location>
```

The `storeFile` might be located at `/Users/<user name>/upload-keystore.jks` on macOS or `C:\\Users\\<user name>\\upload-keystore.jks` on Windows.

### Configure signing in Gradle

[#](#configure-signing-in-gradle)

When building your app in release mode, configure Gradle to use your upload key. To configure Gradle, edit the `<project>/android/app/build.gradle.kts` file.

1. Define and load the keystore properties file before the `android` property block.

2. Set the `keystoreProperties` object to load the `key.properties` file:

* [Kotlin](#234-tab-panel)
* [Groovy](#235-tab-panel)

\[project]/android/app/build.gradle.kts

kotlin

```
import java.util.Properties
import java.io.FileInputStream
​
plugins {
   ...
}
​
val keystoreProperties = Properties()
val keystorePropertiesFile = rootProject.file("key.properties")
if (keystorePropertiesFile.exists()) {
    keystoreProperties.load(FileInputStream(keystorePropertiesFile))
}
​
android {
   ...
}
```

\[project]/android/app/build.gradle

groovy

```
import java.util.Properties
import java.io.FileInputStream
​
plugins {
   ...
}
​
def keystoreProperties = new Properties()
def keystorePropertiesFile = rootProject.file('key.properties')
if (keystorePropertiesFile.exists()) {
    keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
}
​
android {
   ...
}
```

1. Add the signing configuration before the `buildTypes` property block inside the `android` property block:

* [Kotlin](#236-tab-panel)
* [Groovy](#237-tab-panel)

\[project]/android/app/build.gradle.kts

kotlin

```
android {
    // ...
​
    signingConfigs {
        create("release") {
            keyAlias = keystoreProperties.getProperty("keyAlias")
            keyPassword = keystoreProperties.getProperty("keyPassword")
            storeFile = keystoreProperties.getProperty("storeFile")?.let { file(it) }
            storePassword = keystoreProperties.getProperty("storePassword")
        }
    }
    buildTypes {
        release {
            // TODO: Add your own signing config for the release build.
            // Signing with the debug keys for now,
            // so `flutter run --release` works.
            signingConfig = signingConfigs.getByName("debug")
            signingConfig = signingConfigs.getByName("release")
        }
    }
...
}
```

\[project]/android/app/build.gradle

groovy

```
android {
    // ...
​
    signingConfigs {
        release {
            keyAlias = keystoreProperties['keyAlias']
            keyPassword = keystoreProperties['keyPassword']
            storeFile = keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null
            storePassword = keystoreProperties['storePassword']
        }
    }
    buildTypes {
        release {
            // TODO: Add your own signing config for the release build.
            // Signing with the debug keys for now,
            // so `flutter run --release` works.
            signingConfig = signingConfigs.debug
            signingConfig = signingConfigs.release
        }
    }
...
}
```

Flutter now signs all release builds.

To learn about signing your app, check out [Sign your app](https://developer.android.com/studio/publish/app-signing.html#generate-key) on the Android developer docs.

### Post-quantum cryptography (PQC) hybrid signing (Android 17+)

[#](#post-quantum-cryptography-pqc-hybrid-signing-android-17)

Android 17 introduces the v3.2 APK Signature Scheme. This scheme combines classical signatures (such as RSA or EC) with ML-DSA signatures to enable post-quantum cryptography (PQC) hybrid signing. This future-proofs your app's signing identity against the potential threat of attacks that make use of quantum computing.

* **Apps using Play App Signing**: If you use Play App Signing, you can wait for Google Play to give you the option to upgrade to a hybrid signature using a PQC key generated by Google Play.
* **Apps using self-managed keys**: If you manage your own signing keys, you can use updated Android build tools (such as `apksigner`) to rotate to a hybrid identity, combining a PQC key with a new classical key. Note that you must create a new classical key; you cannot reuse the older one.

For details, consult the [Android documentation on PQC APK signing](https://developer.android.com/about/versions/17/features#pqc-apk-signing).

## Shrink your code with R8

[#](#shrink-your-code-with-r8)

[R8](https://developer.android.com/studio/build/shrink-code) is the new code shrinker from Google. It's enabled by default when you build a release APK or AAB. To disable R8, pass the `--no-shrink` flag to `flutter build apk` or `flutter build appbundle`.

## Enable multidex support

[#](#enable-multidex-support)

When writing large apps or making use of large plugins, you might encounter Android's dex limit of 64k methods when targeting a minimum API of 20 or below. This might also be encountered when running debug versions of your app using `flutter run` that doesn't have shrinking enabled.

Flutter tool supports easily enabling multidex. The simplest way is to opt into multidex support when prompted. The tool detects multidex build errors and asks before making changes to your Android project. Opting in allows Flutter to automatically depend on `androidx.multidex:multidex` and use a generated `FlutterMultiDexApplication` as the project's application.

When you try to build and run your app with the **Run** and **Debug** options in your IDE, your build might fail with the following message:

To enable multidex from the command line, run `flutter run --debug` and select an Android-powered device:

When prompted, enter `y`. The Flutter tool enables multidex support and retries the build:

You might also choose to manually support multidex by following Android's guides and modifying your project's Android directory configuration. A [multidex keep file](https://developer.android.com/studio/build/multidex#keep) must be specified to include:

```
io/flutter/embedding/engine/loader/FlutterLoader.class
io/flutter/util/PathUtils.class
```

Also, include any other classes used in app startup. To learn about adding multidex support manually, consult the official [Android documentation](https://developer.android.com/studio/build/multidex).

## Review the app manifest

[#](#review-the-app-manifest)

Review the default [App Manifest](https://developer.android.com/guide/topics/manifest/manifest-intro) file:

\[project]/android/app/src/main/AndroidManifest.xml

xml

```
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    <application
        android:label="[project]"
        ...
    </application>
    ...
    <uses-permission android:name="android.permission.INTERNET"/>
</manifest>
```

Verify the following values:

| Tag                                                                                              | Attribute                                                                                                                                                                                                                                                                                                                                                              | Value |
| ------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- |
| [`application`](https://developer.android.com/guide/topics/manifest/application-element)         | Edit the `android:label` in the [`application`](https://developer.android.com/guide/topics/manifest/application-element) tag to reflect the final name of the app.                                                                                                                                                                                                     |       |
| [`uses-permission`](https://developer.android.com/guide/topics/manifest/uses-permission-element) | Add the `android.permission.INTERNET` [permission](https://developer.android.com/guide/topics/manifest/uses-permission-element) value to the `android:name` attribute if your app needs Internet access. The standard template doesn't include this tag but allows Internet access during development to enable communication between Flutter tools and a running app. |       |

## Review the Gradle build configuration

[#](#review-the-gradle-build-configuration)

To verify the Android build configuration, review the `android` block in the default [Gradle build script](https://developer.android.com/studio/build/#module-level) found at `[project]/android/app/build.gradle.kts`:

\[project]/android/app/build.gradle.kts

kotlin

```
android {
    namespace = "com.example.[project]"
    // Any value starting with "flutter." gets its value from
    // the Flutter Gradle plugin.
    // To change from these defaults, make your changes in this file.
    compileSdk = flutter.compileSdkVersion
    ndkVersion = flutter.ndkVersion
​
    ...
​
    defaultConfig {
        // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
        applicationId = "com.example.[project]"
        // You can update the following values to match your application needs.
        // For details, see https://flutter.dev/to/review-gradle-config.
        minSdk = flutter.minSdkVersion
        targetSdk = flutter.targetSdkVersion
        versionCode = flutter.versionCode
        versionName = flutter.versionName
    }
​
    buildTypes {
        ...
    }
}
```

### Application ID

[#](#application-id)

The `applicationId` is the unique identifier for your app on the Google Play Store and on developers' devices.

If you update the `applicationId` and `namespace` properties, you must also update the `package` statement in your `MainActivity.kt` or `MainActivity.java` file and move the file to the corresponding directory structure.

For example:

* In Kotlin, if your new ID is `com.example.myapp`, move your `MainActivity` file to `android/app/src/main/kotlin/com/example/myapp/MainActivity.kt` and ensure the first line is `package com.example.myapp`.
* In Java, move your `MainActivity` file to `android/app/src/main/java/com/example/myapp/MainActivity.java` and ensure the first line is `package com.example.myapp`.

### Android SDK versions

[#](#android-sdk-versions)

The Flutter tooling sets default values for the Android SDK versions:

* **`compileSdk`**: The version of the Android SDK used to compile the app.
* **`minSdk`**: The minimum Android version that the app supports.
* **`targetSdk`**: The Android version the app is designed and tested to run on.

These default values (`flutter.compileSdkVersion`, etc.) are managed by Flutter to ensure compatibility with the framework and plugins. You typically **do not** need to change these unless:

1. **You need a newer API**: If you are using a plugin or feature that requires a higher `minSdk` than Flutter's default, you can manually set it to a higher version number (for example, `minSdk = 24`).
2. **You need to lock versions**: If you want to prevent automatic updates to these versions when upgrading Flutter, you can replace the default variables with specific integer values.

### Version code and name

[#](#version-code-and-name)

The `versionCode` and `versionName` are automatically set from your `pubspec.yaml` file (using the `version: 1.0.0+1` field). You generally don't need to modify these in the Gradle file.

## Build the app for release

[#](#build-the-app-for-release)

You have two possible release formats when publishing to the Play Store.

* App bundle (preferred)
* APK

### Build an app bundle

[#](#build-an-app-bundle)

This section describes how to build a release app bundle. If you completed the signing steps, the app bundle will be signed. At this point, you might consider [obfuscating your Dart code](/deployment/obfuscate) to make it more difficult to reverse engineer. Obfuscating your code involves adding flags to your build command and maintaining additional files to de-obfuscate stack traces.

From the command line:

1. Enter `cd [project]`
2. Run `flutter build appbundle`\
   (Running `flutter build` defaults to a release build.)

The release bundle for your app is created at `[project]/build/app/outputs/bundle/release/app.aab`.

By default, the app bundle contains your Dart code and the Flutter runtime compiled for [armeabi-v7a](https://developer.android.com/ndk/guides/abis#v7a) (ARM 32-bit), [arm64-v8a](https://developer.android.com/ndk/guides/abis#arm64-v8a) (ARM 64-bit), and [x86-64](https://developer.android.com/ndk/guides/abis#86-64) (x86 64-bit).

### Test the app bundle

[#](#test-the-app-bundle)

An app bundle can be tested in multiple ways. This section describes two.

#### Offline using the bundle tool

[#](#offline-using-the-bundle-tool)

1. If you haven't done so already, download `bundletool` from its [GitHub repository](https://github.com/google/bundletool/releases/latest).
2. [Generate a set of APKs](https://developer.android.com/studio/command-line/bundletool#generate_apks) from your app bundle.
3. [Deploy the APKs](https://developer.android.com/studio/command-line/bundletool#deploy_with_bundletool) to connected devices.

#### Online using Google Play

[#](#online-using-google-play)

1. Upload your bundle to Google Play to test it. You can use the internal test track, or the alpha or beta channels to test the bundle before releasing it in production.
2. Follow the steps to [upload your bundle](https://developer.android.com/studio/publish/upload-bundle) to the Play Store.

### Build an APK

[#](#build-an-apk)

Although app bundles are preferred over APKs, there are stores that don't yet support app bundles. In this case, build a release APK for each target ABI (Application Binary Interface).

If you completed the signing steps, the APK will be signed. At this point, you might consider [obfuscating your Dart code](/deployment/obfuscate) to make it more difficult to reverse engineer. Obfuscating your code involves adding flags to your build command.

From the command line:

1. Enter `cd [project]`.

2. Run `flutter build apk --split-per-abi`. (The `flutter build` command defaults to `--release`.)

This command results in three APK files:

* `[project]/build/app/outputs/flutter-apk/app-armeabi-v7a-release.apk`
* `[project]/build/app/outputs/flutter-apk/app-arm64-v8a-release.apk`
* `[project]/build/app/outputs/flutter-apk/app-x86_64-release.apk`

Removing the `--split-per-abi` flag results in a fat APK that contains your code compiled for *all* the target ABIs. Such APKs are larger in size than their split counterparts, causing the user to download native binaries that aren't applicable to their device's architecture.

When using split APKs, the framework adds `ABI_VERSION * 1000` to the version code. This is because the Google Play Store [doesn't allow](https://developer.android.com/studio/build/configure-apk-splits#configure-APK-versions) multiple APKs for the same app to have the same version code. To force the default version code, specify the `-P force-version-code-ignoring-abi=true` flag during the build.

### Install an APK on a device

[#](#install-an-apk-on-a-device)

Follow these steps to install the APK on a connected Android-powered device.

From the command line:

1. Connect your Android-powered device to your computer with a USB cable.
2. Enter `cd [project]`.
3. Run `flutter install`.

## Publish to the Google Play Store

[#](#publish-to-the-google-play-store)

To learn about publishing your app to the Google Play Store, consult the [Google Play launch](https://developer.android.com/distribute) documentation.

## Update the app's version number

[#](#update-the-apps-version-number)

The default version number of the app is `1.0.0`. To update it, navigate to the `pubspec.yaml` file and update the following line:

yaml

```
version: 1.0.0+1
```

The version number is three numbers separated by dots, such as `1.0.0` in the preceding example, followed by an optional build number, such as `1` in the preceding example, separated by a `+`.

Both the version and the build number can be overridden in Flutter's build by specifying `--build-name` and `--build-number`, respectively.

In Android, `build-name` is used as `versionName` while `build-number` used as `versionCode`. For details, refer to [Version your app](https://developer.android.com/studio/publish/versioning) in the Android documentation.

When you rebuild the app for Android, any updates in the version number from the pubspec file will update the `versionName` and `versionCode`in the `local.properties` file.

## Android release FAQ

[#](#android-release-faq)

Here are some commonly asked questions about deployment for Android apps.

### When should I build app bundles versus APKs?

[#](#when-should-i-build-app-bundles-versus-apks)

The Google Play Store recommends that you deploy app bundles over APKs because they allow a more efficient delivery of the application to your users. However, if you're distributing your application by means other than the Play Store, an APK might be your only option.

### What is a fat APK?

[#](#what-is-a-fat-apk)

A [fat APK](https://en.wikipedia.org/wiki/Fat_binary) is a single APK that contains binaries for multiple ABIs embedded within it. This has the benefit that the single APK runs on multiple architectures and thus has wider compatibility, but it has the drawback that its file size is much larger, causing users to download and store more bytes when installing your application. When building APKs instead of app bundles, it is strongly recommended to build split APKs, as described in [build an APK](#build-an-apk) using the `--split-per-abi` flag.

### What are the supported target architectures?

[#](#what-are-the-supported-target-architectures)

When building your application in release mode, Flutter apps can be compiled for [armeabi-v7a](https://developer.android.com/ndk/guides/abis#v7a) (ARM 32-bit), [arm64-v8a](https://developer.android.com/ndk/guides/abis#arm64-v8a) (ARM 64-bit), and [x86-64](https://developer.android.com/ndk/guides/abis#86-64) (x86 64-bit).

### How do I sign the app bundle created by `flutter build appbundle`?

[#](#how-do-i-sign-the-app-bundle-created-by-flutter-build-appbundle)

Check out [Sign the app](#sign-the-app).

### How do I build a release from within Android Studio?

[#](#how-do-i-build-a-release-from-within-android-studio)

In Android Studio, open the existing `android/` folder under your app's folder. Then, select **build.gradle (Module: app)** in the project panel:

Next, select the build variant. Click **Build > Select Build Variant** in the main menu. Select any of the variants in the **Build Variants** panel (debug is the default):

The resulting app bundle or APK files are located in `build/app/outputs` within your app's folder.

### How to tell if an apk uses Flutter?

[#](#how-to-tell-if-an-apk-uses-flutter)

You can use the [`apkanalyzer`](https://developer.android.com/tools/apkanalyzer) tool and list the files:

sh

```
apkanalyzer files list --files-only <SOME-APK> files list --files-only <SOME-APK>
```

Then look for a file in `/lib/<ARCH>/libflutter.so`.

For example, the following should return a number greater than 0:

sh

```
apkanalyzer files list some-flutter-app.apk | grep flutter.so | wc -l
```

**Why this works**

Flutter depends on C++ code used by the Flutter engine. In Android, this code is bundled with the Flutter framework and the developer's Dart code as a native library called `libflutter.so`. The Java/Android tooling renames the `flutter` library with the `lib` prefix and handles library location across architectures. This is how some reverse engineer an APK to identify it as a Flutter app.

#### Secondary Evaluation:

[#](#secondary-evaluation)

Run `apkanalyzer manifest print <SOME-APK>` and look for a `<meta-data>` tag with `android:name="flutterEmbedding"`. The value can be `1` or `2`.

This command, `apkanalyzer manifest print some-flutter-app.apk | grep flutterEmbedding -C 2`, returns the following output:

```
<meta-data
   android:name="flutterEmbedding"
   android:value="2" />
```

**Why this works**

Flutter has had two different embedders, and this flag was read to determine which embedder was used. [Flutter 3.22](https://blog.flutter.dev/whats-new-in-flutter-3-22-fbde6c164fe3) removed the ability of v1 embedder apps to build. This mechanism is not recommended because it's unclear how long the `flutterEmbedding` value will continue to be included in all Flutter apps. Additionally, this won't work for all libraries written in Flutter that are imported into Android apps as AAR dependencies.

#### Non-technical evaluation

[#](#non-technical-evaluation)

* Download [Flutter Shark](https://play.google.com/store/apps/details?id=com.fluttershark.fluttersharkapp\&pli=1) on a device and let it scan local apps.
* Visit the [Flutter Hunt](https://flutterhunt.com/) website.

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-09-10. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/deployment/android.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/deployment/android\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/deployment/android.md "Report an issue with this page").

----
url: https://docs.flutter.dev/app-architecture/design-patterns/key-value-data
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Persistent storage architecture: Key-value data](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

Most Flutter applications, no matter how small or big they are, require storing data on the user’s device at some point, such as API keys, user preferences or data that should be available offline.

In this recipe, you will learn how to integrate persistent storage for key-value data in a Flutter application that uses the recommended [Flutter architecture design](/app-architecture). If you aren’t familiar with storing data to disk at all, you can read the [Store key-value data on disk](/cookbook/persistence/key-value) recipe.

Key-value stores are often used for saving simple data, such as app configuration, and in this recipe you’ll use it to save Dark Mode preferences. If you want to learn how to store complex data on a device, you’ll likely want to use SQL. In that case, take a look at the cookbook recipe that follows this one called [Persistent storage architecture: SQL](/app-architecture/design-patterns/sql).

## Example application: App with theme selection

[#](#example-application-app-with-theme-selection)

The example application consists of a single screen with an app bar at the top, a list of items, and a text field input at the bottom.

In the `AppBar`, a `Switch` allows users to change between dark and light theme modes. This setting is applied immediately and it’s stored in the device using a key-value data storage service. The setting is restored when the user starts the application again.

## Storing theme selection key-value data

[#](#storing-theme-selection-key-value-data)

This functionality follows the recommended Flutter architecture design pattern, with a presentation and a data layer.

* The presentation layer contains the `ThemeSwitch` widget and the `ThemeSwitchViewModel`.
* The data layer contains the `ThemeRepository` and the `SharedPreferencesService`.

### Theme selection presentation layer

[#](#theme-selection-presentation-layer)

The `ThemeSwitch` is a `StatelessWidget` that contains a `Switch` widget. The state of the switch is represented by the public field `isDarkMode` in the `ThemeSwitchViewModel`. When the user taps the switch, the code executes the command `toggle` in the view model.

dart

```
class ThemeSwitch extends StatelessWidget {
  const ThemeSwitch({super.key, required this.viewmodel});
​
  final ThemeSwitchViewModel viewmodel;
​
  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.symmetric(horizontal: 16.0),
      child: Row(
        children: [
          const Text('Dark Mode'),
          ListenableBuilder(
            listenable: viewmodel,
            builder: (context, _) {
              return Switch(
                value: viewmodel.isDarkMode,
                onChanged: (_) {
                  viewmodel.toggle.execute();
                },
              );
            },
          ),
        ],
      ),
    );
  }
}
```

The `ThemeSwitchViewModel` implements a view model as described in the MVVM pattern. This view model contains the state of the `ThemeSwitch` widget, represented by the boolean variable `_isDarkMode`.

The view model uses the `ThemeRepository` to store and load the dark mode setting.

It contains two different command actions: `load`, which loads the dark mode setting from the repository, and `toggle`, which switches the state between dark mode and light mode. It exposes the state through the `isDarkMode` getter.

The `_load` method implements the `load` command. This method calls `ThemeRepository.isDarkMode` to obtain the stored setting and calls `notifyListeners()` to refresh the UI.

The `_toggle` method implements the `toggle` command. This method calls `ThemeRepository.setDarkMode` to store the new dark mode setting. As well, it changes the local state of `_isDarkMode` then calls `notifyListeners()` to update the UI.

dart

```
class ThemeSwitchViewModel extends ChangeNotifier {
  ThemeSwitchViewModel(this._themeRepository) {
    load = Command0(_load)..execute();
    toggle = Command0(_toggle);
  }
​
  final ThemeRepository _themeRepository;
​
  bool _isDarkMode = false;
​
  /// If true show dark mode
  bool get isDarkMode => _isDarkMode;
​
  late final Command0<void> load;
​
  late final Command0<void> toggle;
​
  /// Load the current theme setting from the repository
  Future<Result<void>> _load() async {
    final result = await _themeRepository.isDarkMode();
    if (result is Ok<bool>) {
      _isDarkMode = result.value;
    }
    notifyListeners();
    return result;
  }
​
  /// Toggle the theme setting
  Future<Result<void>> _toggle() async {
    _isDarkMode = !_isDarkMode;
    final result = await _themeRepository.setDarkMode(_isDarkMode);
    notifyListeners();
    return result;
  }
}
```

### Theme selection data layer

[#](#theme-selection-data-layer)

Following the architecture guidelines, the data layer is split into two parts: the `ThemeRepository` and the `SharedPreferencesService`.

The `ThemeRepository` is the single source of truth for all the theming configuration settings, and handles any possible errors coming from the service layer.

In this example, the `ThemeRepository` also exposes the dark mode setting through an observable `Stream`. This allows other parts of the application to subscribe to changes in the dark mode setting.

The `ThemeRepository` depends on `SharedPreferencesService`. The repository obtains the stored value from the service, and stores it when it changes.

The `setDarkMode()` method passes the new value to the `StreamController`, so that any component listening to the `observeDarkMode` stream

dart

```
class ThemeRepository {
  ThemeRepository(this._service);
​
  final _darkModeController = StreamController<bool>.broadcast();
​
  final SharedPreferencesService _service;
​
  /// Get if dark mode is enabled
  Future<Result<bool>> isDarkMode() async {
    try {
      final value = await _service.isDarkMode();
      return Result.ok(value);
    } on Exception catch (e) {
      return Result.error(e);
    }
  }
​
  /// Set dark mode
  Future<Result<void>> setDarkMode(bool value) async {
    try {
      await _service.setDarkMode(value);
      _darkModeController.add(value);
      return Result.ok(null);
    } on Exception catch (e) {
      return Result.error(e);
    }
  }
​
  /// Stream that emits theme config changes.
  /// ViewModels should call [isDarkMode] to get the current theme setting.
  Stream<bool> observeDarkMode() => _darkModeController.stream;
}
```

The `SharedPreferencesService` wraps the `SharedPreferences` plugin functionality, and calls to the `setBool()` and `getBool()` methods to store the dark mode setting, hiding this third-party dependency from the rest of the application

dart

```
class SharedPreferencesService {
  static const String _kDarkMode = 'darkMode';
​
  Future<void> setDarkMode(bool value) async {
    final prefs = await SharedPreferences.getInstance();
    await prefs.setBool(_kDarkMode, value);
  }
​
  Future<bool> isDarkMode() async {
    final prefs = await SharedPreferences.getInstance();
    return prefs.getBool(_kDarkMode) ?? false;
  }
}
```

## Putting it all together

[#](#putting-it-all-together)

In this example, the `ThemeRepository` and `SharedPreferencesService` are created in the `main()` method and passed to the `MainApp` as constructor argument dependency.

dart

```
void main() {
  // ···
  runApp(
    MainApp(
      themeRepository: ThemeRepository(SharedPreferencesService()),
      // ···
    ),
  );
}
```

Then, when the `ThemeSwitch` is created, also create `ThemeSwitchViewModel` and pass the `ThemeRepository` as dependency.

dart

```
ThemeSwitch(
  viewmodel: ThemeSwitchViewModel(widget.themeRepository),
),
```

The example application also includes the `MainAppViewModel` class, which listens to changes in the `ThemeRepository` and exposes the dark mode setting to the `MaterialApp` widget.

dart

```
class MainAppViewModel extends ChangeNotifier {
  MainAppViewModel(this._themeRepository) {
    _subscription = _themeRepository.observeDarkMode().listen((isDarkMode) {
      _isDarkMode = isDarkMode;
      notifyListeners();
    });
    _load();
  }
​
  final ThemeRepository _themeRepository;
  StreamSubscription<bool>? _subscription;
​
  bool _isDarkMode = false;
​
  bool get isDarkMode => _isDarkMode;
​
  Future<void> _load() async {
    final result = await _themeRepository.isDarkMode();
    if (result is Ok<bool>) {
      _isDarkMode = result.value;
    }
    notifyListeners();
  }
​
  @override
  void dispose() {
    _subscription?.cancel();
    super.dispose();
  }
}
```

dart

```
ListenableBuilder(
  listenable: _viewModel,
  builder: (context, child) {
    return MaterialApp(
      theme: _viewModel.isDarkMode ? ThemeData.dark() : ThemeData.light(),
      home: child,
    );
  },
  child: //...
)
```

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/app-architecture/design-patterns/key-value-data.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/app-architecture/design-patterns/key-value-data\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/app-architecture/design-patterns/key-value-data.md "Report an issue with this page").

----
url: https://docs.flutter.dev/learn/pathway/tutorial/implicit-animations.md
----

\# Simple animations > Learn the simplest way to implement animations in Flutter. Flutter provides a rich set of animation APIs, and the simplest way to start using them is with \*\*implicit animations\*\*. "Implicit animations" refers to a group of widgets that automatically animate changes to their properties without you needing to manage any intermediate behavior. title: What you'll accomplish items: - title: Discover implicit animations in Flutter icon: auto\_awesome - title: Animate property changes with AnimatedContainer icon: animation - title: Customize timing with duration and curves icon: timeline In this lesson, you'll learn about one of the most common and versatile implicit animation widgets: \[\`AnimatedContainer\`]\[]. With just two additional lines of code, the background color of each \`Tile\` animates to a new color in about half a second. \[\`AnimatedContainer\`]: https\://api.flutter.dev/flutter/widgets/AnimatedContainer-class.html --- ### Convert \`Container\` to \`AnimatedContainer\` Currently, the \`Tile.build\` method returns a \`Container\` to display a letter. When the \`hitType\` changes, like from \`HitType.none\` to \`HitType.hit\`, the background color of the tile changes instantly. For example, from white to green in the case of \`HitType.none\` to \`HitType.hit\`. For reference, here's the current implementation of the \`Tile\` widget: \`\`\`dart class Tile extends StatelessWidget { const Tile(this.letter, this.hitType, {super.key}); final String letter; final HitType hitType; @override Widget build(BuildContext context) { return Container( height: 60, width: 60, decoration: BoxDecoration( border: Border.all(color: Colors.grey.shade300), color: switch (type) { HitType.hit => Colors.green, HitType.partial => Colors.yellow, HitType.miss => Colors.grey, \_ => Colors.white, }, ), child: Center( child: Text( letter.toUpperCase(), style: Theme.of(context).textTheme.titleLarge, ), ), ); } } \`\`\` To make the color change animate smoothly, replace the \`Container\` widget with an \`AnimatedContainer\`. An \`AnimatedContainer\` is like a \`Container\`, but it automatically animates changes to its properties over a specified \`duration\`. When properties such as \`color\`, \`height\`, \`width\`, \`decoration\`, or \`alignment\` change, \`AnimatedContainer\` interpolates between the old and new values, creating a smooth transition. Modify your \`Tile\` widget as follows: \`\`\`dart class Tile extends StatelessWidget { const Tile(this.letter, this.hitType, {super.key}); final String letter; final HitType hitType; @override Widget build(BuildContext context) { return AnimatedContainer( duration: Duration(milliseconds: 500), height: 60, width: 60, decoration: BoxDecoration( border: Border.all(color: Colors.grey.shade300), color: switch (hitType) { HitType.hit => Colors.green, HitType.partial => Colors.yellow, HitType.miss => Colors.grey, \_ => Colors.white, }, ), child: Center( child: Text( letter.toUpperCase(), style: Theme.of(context).textTheme.titleLarge, ), ), ); } } \`\`\` \*\*\`duration\`\*\* is a required property that specifies how long the animation should take. In this example, passing \`Duration(milliseconds: 500)\` means the color transition will take half a second. You can also specify seconds, minutes, and many other units of time. Now, when the \`hitType\` changes and the \`Tile\` widget rebuilds (because \`setState\` was called in \`GamePage\`), the color of the tile smoothly animates from its old color to the new one over the specified duration. ### Adjust the animation curve To add a bit of customization to an implicit animation, you can pass it a different \[\`Curve\`]\[]. Different curves change the speed of the animation at different points throughout the animation. For example, the default curve for Flutter animations is \`Curves.linear\`. This gif shows how the animation curve behaves: Compare that to \`Curve.bounceIn\`, another common curve: To change the \`Curve\` of this animation, update the code to the following: \`\`\`dart class Tile extends StatelessWidget { const Tile(this.letter, this.hitType, {super.key}); final String letter; final HitType hitType; @override Widget build(BuildContext context) { return AnimatedContainer( duration: Duration(milliseconds: 500), curve: Curves.bounceIn, // NEW height: 60, width: 60, decoration: BoxDecoration( border: Border.all(color: Colors.grey.shade300), color: switch (hitType) { HitType.hit => Colors.green, HitType.partial => Colors.yellow, HitType.miss => Colors.grey, \_ => Colors.white, }, ), child: Center( child: Text( letter.toUpperCase(), style: Theme.of(context).textTheme.titleLarge, ), ), ); } } \`\`\` There are many different curves provided by the Flutter SDK, so feel free to try them out by passing different types to the \`curve\` parameter. Implicit animations like \`AnimatedContainer\` are powerful because you just tell the widget what the new state should be, and it handles the "how" of the animation. For complex, custom animations, you can write your own animated widgets. If you're curious, try it out in the \[animations tutorial]\[]. \[\`Curve\`]: https\://api.flutter.dev/flutter/animation/Curves-class.html \[animations tutorial]: /ui/animations/tutorial ### Review title: What you accomplished subtitle: Here's a summary of what you built and learned in this lesson. completed: true items: - title: Discovered implicit animations icon: auto\_awesome details: >- Implicit animations are widgets that automatically animate changes to their properties. You specify the new state, and the widget handles the animation for you without requiring manual animation management. - title: Animated the tiles with AnimatedContainer icon: animation details: >- By replacing \`Container\` with \`AnimatedContainer\` and adding a \`duration\`, your tiles now smoothly transition between colors. With just two lines of code, you added professional polish to your app! - title: Customized timing with duration and curves icon: timeline details: >- The \`duration\` property controls how long the animation takes, while \`curve\` changes the animation's feel. You tried \`Curves.decelerate\`, but you can also try other values like \`Curves.easeIn\`, \`Curves.bounceOut\`, or \`Curves.elasticIn\`. - title: Completed the Birdle game icon: celebration details: >- You've built a complete Wordle-style game with custom widgets, dynamic layouts, user input, state management, and smooth animations. You now have the foundational skills to build your own Flutter apps! ### Test yourself - question: What widget can you use to automatically animate changes to properties like color, size, and decoration? options: - text: Container correct: false explanation: Container doesn't animate; property changes happen instantly. - text: AnimatedContainer correct: true explanation: AnimatedContainer automatically animates changes to its properties over the specified duration. - text: AnimationController correct: false explanation: AnimationController is for explicit animations; AnimatedContainer is simpler for basic animations. - text: TransitionContainer correct: false explanation: There is no TransitionContainer widget; use AnimatedContainer for implicit animations. - question: "What does the \`duration\` property control in an AnimatedContainer?" options: - text: How long the widget stays on screen before disappearing. correct: false explanation: Duration controls animation time, not widget visibility. - text: How long the animation takes to transition from the old value to the new value. correct: true explanation: The duration specifies the time over which the property change is animated. - text: The delay before the animation starts. correct: false explanation: Duration is about animation length; delays require separate configuration. - text: How many times the animation repeats. correct: false explanation: Implicit animations run once per state change; repetition requires explicit animation controllers.

----
url: https://docs.flutter.dev/cookbook/maintenance/error-reporting.md
----

\# Report errors to a service > How to keep track of errors that users encounter.

<!--?code-excerpt path-base="cookbook/maintenance/error_reporting/"?-->

While one always tries to create apps that are free of bugs, they're sure to crop up from time to time. Since buggy apps lead to unhappy users and customers, it's important to understand how often your users experience bugs and where those bugs occur. That way, you can prioritize the bugs with the highest impact and work to fix them. How can you determine how often your users experiences bugs? Whenever an error occurs, create a report containing the error that occurred and the associated stacktrace. You can then send the report to an error tracking service, such as \[Bugsnag]\[], \[Datadog]\[], \[Firebase Crashlytics]\[], \[Rollbar]\[], or Sentry. 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, learn how to report errors to the \[Sentry]\[] crash reporting service using the following steps: 1. Get a DSN from Sentry. 2. Import the Flutter Sentry package 3. Initialize the Sentry SDK 4. Capture errors programmatically ## 1. Get a DSN from Sentry 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: 1. \[Create an account with Sentry]\[]. 2. Log in to the account. 3. Create a new Flutter project. 4. Copy the code snippet that includes the DSN. ## 2. Import the Sentry package Import the \[\`sentry\_flutter\`]\[] package into the app. The sentry package makes it easier to send error reports to the Sentry error tracking service. To add the \`sentry\_flutter\` package as a dependency, run \`flutter pub add\`: \`\`\`console $ flutter pub add sentry\_flutter \`\`\` ## 3. Initialize the Sentry SDK Initialize the SDK to capture different unhandled errors automatically:

<!--?code-excerpt "lib/main.dart (InitializeSDK)"?-->

\`\`\`dart import 'package:flutter/widgets.dart'; import 'package:sentry\_flutter/sentry\_flutter.dart'; Future main() async { await SentryFlutter.init( (options) => options.dsn = 'https\://example\@sentry.io/example', appRunner: () => runApp(const MyApp()), ); } \`\`\` Alternatively, you can pass the DSN to Flutter using the \`dart-define\` tag: \`\`\`sh --dart-define SENTRY\_DSN=https\://example\@sentry.io/example \`\`\` ### What does that give me? This is all you need for Sentry to capture unhandled errors in Dart and native layers. This includes Swift, Objective-C, C, and C++ on iOS, and Java, Kotlin, C, and C++ on Android. ## 4. Capture errors programmatically Besides the automatic error reporting that Sentry generates by importing and initializing the SDK, you can use the API to report errors to Sentry:

<!--?code-excerpt "lib/main.dart (CaptureException)"?-->

\`\`\`dart await Sentry.captureException(exception, stackTrace: stackTrace); \`\`\` For more information, see the \[Sentry API]\[] docs on pub.dev. ## Learn more Extensive documentation about using the Sentry SDK can be found on \[Sentry's site]\[]. ## Complete example To view a working example, see the \[Sentry flutter example]\[] app. \[Sentry flutter example]: https\://github.com/getsentry/sentry-dart/tree/main/flutter/example \[Create an account with Sentry]: https\://sentry.io/signup/ \[Bugsnag]: https\://www\.bugsnag.com/platforms/flutter \[Datadog]: https\://docs.datadoghq.com/real\_user\_monitoring/flutter/ \[Rollbar]: https\://rollbar.com/ \[Sentry]: https\://sentry.io/welcome/ \[\`sentry\_flutter\`]: https\://pub.dev/packages/sentry\_flutter \[Sentry API]: https\://pub.dev/documentation/sentry\_flutter/latest/sentry\_flutter/sentry\_flutter-library.html \[Sentry's site]: https\://docs.sentry.io/platforms/flutter/ \[Firebase Crashlytics]: https\://firebase.google.com/docs/crashlytics

----
url: https://docs.flutter.dev/cookbook/audio/soloud.md
----

\# Play or stream sound and music with flutter\_soloud > Learn how to play or stream audio in your Flutter app using the flutter\_soloud package. This recipe demonstrates how to integrate the \[\`flutter\_soloud\` package]\[] into your Flutter application to play sound effects and background music with low latency. To get started using the package, follow these steps: \[\`flutter\_soloud\` package]: https\://pub.dev/packages/flutter\_soloud ## 1. Add the package dependency To add \`package:flutter\_soloud\` as a dependency, use \`flutter pub add\`: \`\`\`console $ flutter pub add flutter\_soloud \`\`\` ## 2. Initialize \`SoLoud\` Before playing any audio, you need to initialize the \`SoLoud\` instance. You can also configure playback settings like sample rate, buffer size, and the number of channels. \`\`\`dart import 'package:flutter\_soloud/flutter\_soloud.dart'; // Initialize with default settings. await SoLoud.instance.init(); // Or configure with custom settings. await SoLoud.instance.init( sampleRate: 44100, bufferSize: 2048, channels: Channels.stereo, ); \`\`\` ## 3. Load audio The \`flutter\_soloud\` package supports various audio formats including MP3, WAV, OGG, and FLAC. You can load audio from different sources. From a local file: \`\`\`dart final sound = await SoLoud.instance.loadFile('path/to/sound.mp3'); \`\`\` From an app asset: \`\`\`dart final sound = await SoLoud.instance.loadAsset('assets/sound.mp3'); \`\`\` From a network URL: \`\`\`dart final sound = await SoLoud.instance.loadUrl( 'https\://example.com/sound.mp3', mode: LoadMode.memory, ); \`\`\` From bytes in memory: \`\`\`dart // TODO: Replace with your actual audio data. final soundBytes = Uint8List.fromList(\[]); final sound = await SoLoud.instance.loadMem( 'reference\_name.mp3', soundBytes, mode: LoadMode.memory, ); \`\`\` ## 4. Play audio Once the audio is loaded, you can play it using \`SoLoud.instance.play\`. You can also configure gapless looping. \`\`\`dart // Play the sound. var handle = await SoLoud.instance.play(sound); // Play with gapless looping. handle = await SoLoud.instance.play( sound, looping: true, loopingStartAt: Duration(seconds: 1), ); \`\`\` The \`play\` method returns a handle that references that particular instance of the sound being played. You can use this handle to control the playback. ## 5. Control playback With the handle returned from \`play\`, you can perform various actions such as pausing or resuming playback: \`\`\`dart // Toggle pause state. SoLoud.instance.pauseSwitch(handle); \`\`\` Seek to a specific timestamp: \`\`\`dart SoLoud.instance.seek(handle, Duration(seconds: 5)); \`\`\` Set playback speed: \`\`\`dart // Play twice as fast. SoLoud.instance.setRelativePlaySpeed(handle, 2.0); \`\`\` Adjust playback volume: \`\`\`dart // Set playback to 50% volume. SoLoud.instance.setVolume(handle, 0.5); \`\`\` Fade the audio volume: \`\`\`dart SoLoud.instance.fadeVolume( handle, 0.0, // The volume to fade to. Duration(seconds: 2), // The duration of the fade. ); \`\`\` Stop playback: \`\`\`dart await SoLoud.instance.stop(handle); \`\`\` ## 6. Dispose of sound sources When you are finished with a sound source, remember to dispose of it to free up resources. \`\`\`dart await SoLoud.instance.disposeSource(sound); \`\`\` ## 7. \[Optional] Stream audio The \`flutter\_soloud\` package also supports streaming audio data in real time. Initialize and configure a buffer stream: \`\`\`dart final stream = SoLoud.instance.setBufferStream( bufferingType: BufferingType.released, sampleRate: 24000, channels: Channels.mono, format: BufferType.s16le, // pcm16bits ); \`\`\` Play the stream: \`\`\`dart final handle = await SoLoud.instance.play(stream); \`\`\` As your app receives audio data, add it to the stream: \`\`\`dart // TODO: Replace with your actual audio data. final audioChunk = Uint8List.fromList(\[]); SoLoud.instance.addAudioDataStream( stream, audioChunk, ); \`\`\` When you're done with the audio stream, mark it as complete: \`\`\`dart SoLoud.instance.setDataIsEnded(stream); \`\`\` ## More information For more detailed information and examples, visit the \[\`flutter\_soloud\` package]\[] on pub.dev or check out the \`flutter\_soloud\` Package of the Week video.

----
url: https://docs.flutter.dev/ui/adaptive-responsive/large-screens
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Large screen devices](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

This page provides guidance on optimizing your app to improve its behavior on large screens.

Flutter, like Android, defines [large screens](https://developer.android.com/guide/topics/large-screens/get-started-with-large-screens) as tablets, foldables, and ChromeOS devices running Android. Flutter *also* defines large screen devices as web, desktop, and iPads.

## Layout with GridView

[#](#layout-with-gridview)

Consider the following screenshots of an app. The app displays its UI in a `ListView`. The image on the left shows the app running on a mobile device. The image on the right shows the app running on a large screen device *before the advice on this page was applied*.

This is not optimal.

The [Android Large Screen App Quality Guidelines](https://developer.android.com/docs/quality-guidelines/large-screen-app-quality) and the [iOS equivalent](https://developer.apple.com/design/human-interface-guidelines/designing-for-ipados) say that neither text nor boxes should take up the full screen width. How to solve this in an adaptive way?

A common solution uses `GridView`, as shown in the next section.

### GridView

[#](#gridview)

You can use the `GridView` widget to transform your existing `ListView` into more reasonably-sized items.

`GridView` is similar to the [`ListView`](https://api.flutter.dev/flutter/widgets/ListView-class.html) widget, but instead of handling only a list of widgets arranged linearly, `GridView` can arrange widgets in a two-dimensional array.

`GridView` also has constructors that are similar to `ListView`. The `ListView` default constructor maps to `GridView.count`, and `ListView.builder` is similar to `GridView.builder`.

`GridView` has some additional constructors for more custom layouts. To learn more, visit the [`GridView`](https://api.flutter.dev/flutter/widgets/GridView-class.html) API page.

For example, if your original app used a `ListView.builder`, swap that out for a `GridView.builder`. If your app has a large number of items, it's recommended to use this builder constructor to only build the item widgets that are actually visible.

Most of the parameters in the constructor are the same between the two widgets, so it's a straightforward swap. However, you need to figure out what to set for the `gridDelegate`.

Flutter provides powerful premade `gridDelegates` that you can use, namely:

* [`SliverGridDelegateWithFixedCrossAxisCount`](https://api.flutter.dev/flutter/rendering/SliverGridDelegateWithFixedCrossAxisCount-class.html)

  Lets you assign a specific number of columns to your grid.

* [`SliverGridDelegateWithMaxCrossAxisExtent`](https://api.flutter.dev/flutter/rendering/SliverGridDelegateWithMaxCrossAxisExtent-class.html)

  Lets you define a max item width.

### Other solutions

[#](#other-solutions)

Another way to approach these situations is to use the `maxWidth` property of `BoxConstraints`. This involves the following:

* Wrap the `GridView`in a `ConstrainedBox` and give it a `BoxConstraints` with a maximum width set.
* Use a `Container` instead of a `ConstrainedBox` if you want other functionality like setting the background color.

For choosing the maximum width value, consider using the values recommended by Material 3 in the [Applying layout](https://m3.material.io/foundations/layout/applying-layout/window-size-classes) guide.

## Foldables

[#](#foldables)

As mentioned previously, Android and Flutter both recommend in their design guidance **not** to lock screen orientation, but some apps lock screen orientation anyway. Be aware that this can cause problems when running your app on a foldable device.

When running on a foldable, the app might look ok when the device is folded. But when unfolding, you might find the app letterboxed.

As described in the [SafeArea & MediaQuery](/ui/adaptive-responsive/safearea-mediaquery) page, letterboxing means that the app's window is locked to the center of the screen while the window is surrounded with black.

Why can this happen?

This can happen when using `MediaQuery` to figure out the window size for your app. When the device is folded, orientation is restricted to portrait mode. Under the hood, `setPreferredOrientations` causes Android to use a portrait compatibility mode and the app is displayed in a letterboxed state. In the letterboxed state, `MediaQuery` never receives the larger window size that allows the UI to expand.

You can solve this in one of two ways:

* Support all orientations.
* Use the dimensions of the *physical display*. In fact, this is one of the *few* situations where you would use the physical display dimensions and *not* the window dimensions.

How to obtain the physical screen dimensions?

You can use the [`Display`](https://api.flutter.dev/flutter/dart-ui/Display-class.html) API, which contains the size, pixel ratio, and refresh rate of the physical device.

The following sample code retrieves a `Display` object:

dart

```
/// AppState object.
ui.FlutterView? _view;
​
@override
void didChangeDependencies() {
  super.didChangeDependencies();
  _view = View.maybeOf(context);
}
​
void didChangeMetrics() {
  final ui.Display? display = _view?.display;
}
```

The important thing is to find the display of the view that you care about. This creates a forward-looking API that should handle current *and* future multi-display and multi-view devices.

## Adaptive input

[#](#adaptive-input)

Adding support for more screens, also means expanding input controls.

Android guidelines describe three tiers of large format device support.

Tier 3, the lowest level of support, includes support for mouse and stylus input ([Material 3 guidelines](https://developer.android.com/docs/quality-guidelines/large-screen-app-quality), [Apple guidelines](https://developer.apple.com/design/human-interface-guidelines/designing-for-ipados#Best-practices)).

If your app uses Material 3 and its buttons and selectors, then your app already has built-in support for various additional input states.

But what if you have a custom widget? Check out the [User input](/ui/adaptive-responsive/input) page for guidance on adding [input support for widgets](/ui/adaptive-responsive/input#custom-widgets).

[#](#navigation)

Navigation can create unique challenges when working with a variety of differently-sized devices. Generally, you want to switch between a [`BottomNavigationBar`](https://pub.dev/documentation/material_ui/latest/material_ui/BottomNavigationBar-class.html) and a [`NavigationRail`](https://pub.dev/documentation/material_ui/latest/material_ui/NavigationRail-class.html) depending on available screen space.

For more information (and corresponding example code), check out [Problem: Navigation rail](https://blog.flutter.dev/developing-flutter-apps-for-large-screens-53b7b0e17f10#:~:text=Problem%3A%20Navigation%20rail1), a section in the [Developing Flutter apps for Large screens](https://blog.flutter.dev/developing-flutter-apps-for-large-screens-53b7b0e17f10) article.

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-09-29. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/ui/adaptive-responsive/large-screens.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/ui/adaptive-responsive/large-screens\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/ui/adaptive-responsive/large-screens.md "Report an issue with this page").

----
url: https://docs.flutter.dev/ai/best-practices/structure-output
----

When you're writing programs against an LLM, you want to provide unambiguous input and get unambiguous output.

### Structured input

[#](#structured-input)

As input, an LLM can take pretty much anything you can render as text. That includes free form text and semi-structured text like Markdown, but also includes structured formats like CSV, JSON, and XML. If you have data with structure, format the data with that structure and the LLM is going to give you better results.

In addition to structured text input, you can also pass binary data, like images or PDFs. In the sample, the app passes the crossword puzzle screenshot images to Gemini for it to infer the grid data:

dart

```
final imageParts = <Part>[];
for (final image in images) {
  final imageBytes = await image.readAsBytes();
  final mimeType = lookupMimeType(image.path, headerBytes: imageBytes)!;
  imageParts.add(InlineDataPart(mimeType, imageBytes));
}
​
final content = [
  Content.multi([
    TextPart(_crosswordPrompt),
    ...imageParts,
  ]),
];
​
final response = await _crosswordModel.generateContent(content);
...
```

This code passes the prompt and the images to Gemini as part of the same request.

### Structured output

[#](#structured-output)

An LLM can have a harder time with structured output than with structured input. You want to be clear and thorough when asking the model for JSON output to ensure you get something that you can reliably parse in your apps.

Start by initializing the model instance with your expected output format:

dart

```
// the schema for the clue solver output
static final _crosswordSchema = Schema(
  SchemaType.object,
  properties: {
    'width': Schema(SchemaType.integer),
    'height': Schema(SchemaType.integer),
    'grid': Schema(
      SchemaType.array,
      items: Schema(
        SchemaType.array,
        items: Schema(
          SchemaType.object,
          properties: {
            'color': Schema(SchemaType.string),
            'clueNumber': Schema(SchemaType.integer, nullable: true),
          },
        ),
      ),
    ),
    'clues': Schema(
      SchemaType.object,
      properties: {
        'across': Schema(
          SchemaType.array,
          items: Schema(
            SchemaType.object,
            properties: {
              'number': Schema(SchemaType.integer),
              'text': Schema(SchemaType.string),
            },
          ),
        ),
        'down': Schema(
          SchemaType.array,
          items: Schema(
            SchemaType.object,
            properties: {
              'number': Schema(SchemaType.integer),
              'text': Schema(SchemaType.string),
            },
          ),
        ),
      },
    ),
  },
);
​
// The model for inferring crossword data from images.
_crosswordModel = FirebaseAI.googleAI().generativeModel(
  model: 'gemini-2.5-pro',
  generationConfig: GenerationConfig(
    responseMimeType: 'application/json',
    responseSchema: _crosswordSchema,
  ),
);
```

And while this might be enough, the most reliable results come when you also specify the output schema in the system instruction:

dart

```
final _crosswordPrompt =
'''
Analyze the following crossword puzzle images and return a JSON object
representing the grid size, contents, and clues. The images may contain
different parts of the same puzzle (e.g., the grid the across clues, the down
clues). Combine them to form a complete puzzle.
​
The JSON schema is as follows: ${jsonEncode(_crosswordSchema.toJson())}
'''
```

Now you can parse the model's text response as JSON:

dart

```
final response = await _crosswordModel.generateContent(content);
​
final json = jsonDecode(response.text!);
final width = json['width'] as int;
final height = json['height'] as int;
final gridData = json['grid'] as List;
final cluesData = json['clues'] as Map<String, dynamic>;
...
```

Reliable JSON output from the model is what makes it possible to integrate AI into your app. The data might or might not be correct, but it will be in a format your app can work with.

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-08-19. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/ai/best-practices/structure-output.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/ai/best-practices/structure-output\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/ai/best-practices/structure-output.md "Report an issue with this page").

----
url: https://docs.flutter.dev/flutter-for/android-devs
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Flutter for Android developers](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

This document is meant for Android developers looking to apply their existing Android knowledge to build mobile apps with Flutter. If you understand the fundamentals of the Android framework then you can use this document as a jump start to Flutter development.

Your Android knowledge and skill set are highly valuable when building with Flutter, because Flutter relies on the mobile operating system for numerous capabilities and configurations. Flutter is a new way to build UIs for mobile, but it has a plugin system to communicate with Android (and iOS) for non-UI tasks. If you're an expert with Android, you don't have to relearn everything to use Flutter.

This document can be used as a cookbook by jumping around and finding questions that are most relevant to your needs.

## Views

[#](#views)

### What is the equivalent of a View in Flutter?

[#](#what-is-the-equivalent-of-a-view-in-flutter)

In Android, the `View` is the foundation of everything that shows up on the screen. Buttons, toolbars, and inputs, everything is a View. In Flutter, the rough equivalent to a `View` is a `Widget`. Widgets don't map exactly to Android views, but while you're getting acquainted with how Flutter works you can think of them as "the way you declare and construct UI".

However, these have a few differences to a `View`. To start, widgets have a different lifespan: they are immutable and only exist until they need to be changed. Whenever widgets or their state change, Flutter's framework creates a new tree of widget instances. In comparison, an Android view is drawn once and does not redraw until `invalidate` is called.

Flutter's widgets are lightweight, in part due to their immutability. Because they aren't views themselves, and aren't directly drawing anything, but rather are a description of the UI and its semantics that get "inflated" into actual view objects under the hood.

Flutter includes the [Material Components](https://m3.material.io/develop/flutter) library. These are widgets that implement the [Material Design guidelines](https://m3.material.io/styles). Material Design is a flexible design system [optimized for all platforms](https://m3.material.io/develop), including iOS.

But Flutter is flexible and expressive enough to implement any design language. For example, on iOS, you can use the [Cupertino widgets](/ui/widgets/cupertino) to produce an interface that looks like [Apple's iOS design language](https://developer.apple.com/design/resources/).

### How do I update widgets?

[#](#how-do-i-update-widgets)

In Android, you update your views by directly mutating them. However, in Flutter, `Widget`s are immutable and are not updated directly, instead you have to work with the widget's state.

This is where the concept of `Stateful` and `Stateless` widgets comes from. A `StatelessWidget` is just what it sounds like—a widget with no state information.

`StatelessWidgets` are useful when the part of the user interface you are describing does not depend on anything other than the configuration information in the object.

For example, in Android, this is similar to placing an `ImageView` with your logo. The logo is not going to change during runtime, so use a `StatelessWidget` in Flutter.

If you want to dynamically change the UI based on data received after making an HTTP call or user interaction then you have to work with `StatefulWidget` and tell the Flutter framework that the widget's `State` has been updated so it can update that widget.

The important thing to note here is at the core both stateless and stateful widgets behave the same. They rebuild every frame, the difference is the `StatefulWidget` has a `State` object that stores state data across frames and restores it.

If you are in doubt, then always remember this rule: if a widget changes (because of user interactions, for example) it's stateful. However, if a widget reacts to change, the containing parent widget can still be stateless if it doesn't itself react to change.

The following example shows how to use a `StatelessWidget`. A common `StatelessWidget` is the `Text` widget. If you look at the implementation of the `Text` widget you'll find that it subclasses `StatelessWidget`.

dart

```
Text(
  'I like Flutter!',
  style: TextStyle(fontWeight: FontWeight.bold),
);
```

As you can see, the `Text` Widget has no state information associated with it, it renders what is passed in its constructors and nothing more.

But, what if you want to make "I Like Flutter" change dynamically, for example when clicking a `FloatingActionButton`?

To achieve this, wrap the `Text` widget in a `StatefulWidget` and update it when the user clicks the button.

For example:

dart

```
import 'package:material_ui/material_ui.dart';
​
void main() {
  runApp(const SampleApp());
}
​
class SampleApp extends StatelessWidget {
  const SampleApp({super.key});
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Sample App',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
      ),
      home: const SampleAppPage(),
    );
  }
}
​
class SampleAppPage extends StatefulWidget {
  const SampleAppPage({super.key});
​
  @override
  State<SampleAppPage> createState() => _SampleAppPageState();
}
​
class _SampleAppPageState extends State<SampleAppPage> {
  // Default placeholder text.
  String textToShow = 'I Like Flutter';
​
  void _updateText() {
    setState(() {
      // Update the text.
      textToShow = 'Flutter is Awesome!';
    });
  }
​
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Sample App')),
      body: Center(child: Text(textToShow)),
      floatingActionButton: FloatingActionButton(
        onPressed: _updateText,
        tooltip: 'Update Text',
        child: const Icon(Icons.update),
      ),
    );
  }
}
```

### How do I lay out my widgets? Where is my XML layout file?

[#](#how-do-i-lay-out-my-widgets-where-is-my-xml-layout-file)

In Android, you write layouts in XML, but in Flutter you write your layouts with a widget tree.

The following example shows how to display a simple widget with padding:

dart

```
@override
Widget build(BuildContext context) {
  return Scaffold(
    appBar: AppBar(title: const Text('Sample App')),
    body: Center(
      child: ElevatedButton(
        style: ElevatedButton.styleFrom(
          padding: const EdgeInsets.only(left: 20, right: 30),
        ),
        onPressed: () {},
        child: const Text('Hello'),
      ),
    ),
  );
}
```

You can view some of the layouts that Flutter has to offer in the [widget catalog](/ui/widgets/layout).

### How do I add or remove a component from my layout?

[#](#how-do-i-add-or-remove-a-component-from-my-layout)

In Android, you call `addChild()` or `removeChild()` on a parent to dynamically add or remove child views. In Flutter, because widgets are immutable there is no direct equivalent to `addChild()`. Instead, you can pass a function to the parent that returns a widget, and control that child's creation with a boolean flag.

For example, here is how you can toggle between two widgets when you click on a `FloatingActionButton`:

dart

```
import 'package:material_ui/material_ui.dart';
​
void main() {
  runApp(const SampleApp());
}
​
class SampleApp extends StatelessWidget {
  const SampleApp({super.key});
​
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Sample App',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
      ),
      home: const SampleAppPage(),
    );
  }
}
​
class SampleAppPage extends StatefulWidget {
  const SampleAppPage({super.key});
​
  @override
  State<SampleAppPage> createState() => _SampleAppPageState();
}
​
class _SampleAppPageState extends State<SampleAppPage> {
  // Default value for toggle.
  bool toggle = true;
  void _toggle() {
    setState(() {
      toggle = !toggle;
    });
  }
​
  Widget _getToggleChild() {
    if (toggle) {
      return const Text('Toggle One');
    } else {
      return ElevatedButton(onPressed: () {}, child: const Text('Toggle Two'));
    }
  }
​
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Sample App')),
      body: Center(child: _getToggleChild()),
      floatingActionButton: FloatingActionButton(
        onPressed: _toggle,
        tooltip: 'Update Text',
        child: const Icon(Icons.update),
      ),
    );
  }
}
```

### How do I animate a widget?

[#](#how-do-i-animate-a-widget)

In Android, you either create animations using XML, or call the `animate()` method on a view. In Flutter, animate widgets using the animation library by wrapping widgets inside an animated widget.

In Flutter, use an `AnimationController` which is an `Animation<double>` that can pause, seek, stop and reverse the animation. It requires a `Ticker` that signals when vsync happens, and produces a linear interpolation between 0 and 1 on each frame while it's running. You then create one or more `Animation`s and attach them to the controller.

For example, you might use `CurvedAnimation` to implement an animation along an interpolated curve. In this sense, the controller is the "master" source of the animation progress and the `CurvedAnimation` computes the curve that replaces the controller's default linear motion. Like widgets, animations in Flutter work with composition.

When building the widget tree you assign the `Animation` to an animated property of a widget, such as the opacity of a `FadeTransition`, and tell the controller to start the animation.

The following example shows how to write a `FadeTransition` that fades the widget into a logo when you press the `FloatingActionButton`:

dart

```
import 'package:material_ui/material_ui.dart';
​
void main() {
  runApp(const FadeAppTest());
}
​
class FadeAppTest extends StatelessWidget {
  const FadeAppTest({super.key});
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Fade Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
      ),
      home: const MyFadeTest(title: 'Fade Demo'),
    );
  }
}
​
class MyFadeTest extends StatefulWidget {
  const MyFadeTest({super.key, required this.title});
​
  final String title;
  @override
  State<MyFadeTest> createState() => _MyFadeTest();
}
​
class _MyFadeTest extends State<MyFadeTest> with TickerProviderStateMixin {
  late AnimationController controller;
  late CurvedAnimation curve;
​
  @override
  void initState() {
    super.initState();
    controller = AnimationController(
      duration: const Duration(milliseconds: 2000),
      vsync: this,
    );
    curve = CurvedAnimation(parent: controller, curve: Curves.easeIn);
  }
​
  @override
  void dispose() {
    controller.dispose();
    super.dispose();
  }
​
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text(widget.title)),
      body: Center(
        child: FadeTransition(
          opacity: curve,
          child: const FlutterLogo(size: 100),
        ),
      ),
      floatingActionButton: FloatingActionButton(
        tooltip: 'Fade',
        onPressed: () {
          controller.forward();
        },
        child: const Icon(Icons.brush),
      ),
    );
  }
}
```

For more information, see [Animation & Motion widgets](/ui/widgets/animation), the [Animations tutorial](/ui/animations/tutorial), and the [Animations overview](/ui/animations).

### How do I use a Canvas to draw/paint?

[#](#how-do-i-use-a-canvas-to-drawpaint)

In Android, you would use the `Canvas` and `Drawable` to draw images and shapes to the screen. Flutter has a similar `Canvas` API as well, since it's based on the same low-level rendering engine, Skia. As a result, painting to a canvas in Flutter is a very familiar task for Android developers.

Flutter has two classes that help you draw to the canvas: `CustomPaint` and `CustomPainter`, the latter of which implements your algorithm to draw to the canvas.

To learn how to implement a signature painter in Flutter, see Collin's answer on [Custom Paint](https://stackoverflow.com/questions/46241071/create-signature-area-for-mobile-app-in-dart-flutter).

dart

```
import 'package:material_ui/material_ui.dart';
​
void main() => runApp(const MaterialApp(home: DemoApp()));
​
class DemoApp extends StatelessWidget {
  const DemoApp({super.key});
​
  @override
  Widget build(BuildContext context) => const Scaffold(body: Signature());
}
​
class Signature extends StatefulWidget {
  const Signature({super.key});
​
  @override
  SignatureState createState() => SignatureState();
}
​
class SignatureState extends State<Signature> {
  List<Offset?> _points = <Offset>[];
  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onPanUpdate: (details) {
        setState(() {
          RenderBox? referenceBox = context.findRenderObject() as RenderBox;
          Offset localPosition = referenceBox.globalToLocal(
            details.globalPosition,
          );
          _points = List.from(_points)..add(localPosition);
        });
      },
      onPanEnd: (details) => _points.add(null),
      child: CustomPaint(
        painter: SignaturePainter(_points),
        size: Size.infinite,
      ),
    );
  }
}
​
class SignaturePainter extends CustomPainter {
  SignaturePainter(this.points);
  final List<Offset?> points;
  @override
  void paint(Canvas canvas, Size size) {
    var paint = Paint()
      ..color = Colors.black
      ..strokeCap = StrokeCap.round
      ..strokeWidth = 5;
    for (int i = 0; i < points.length - 1; i++) {
      if (points[i] != null && points[i + 1] != null) {
        canvas.drawLine(points[i]!, points[i + 1]!, paint);
      }
    }
  }
​
  @override
  bool shouldRepaint(SignaturePainter oldDelegate) =>
      oldDelegate.points != points;
}
```

### How do I build custom widgets?

[#](#how-do-i-build-custom-widgets)

In Android, you typically subclass `View`, or use a pre-existing view, to override and implement methods that achieve the desired behavior.

In Flutter, build a custom widget by [composing](/resources/architectural-overview#composition) smaller widgets (instead of extending them). It is somewhat similar to implementing a custom `ViewGroup` in Android, where all the building blocks are already existing, but you provide a different behavior—for example, custom layout logic.

For example, how do you build a `CustomButton` that takes a label in the constructor? Create a CustomButton that composes a `ElevatedButton` with a label, rather than by extending `ElevatedButton`:

dart

```
class CustomButton extends StatelessWidget {
  final String label;
​
  const CustomButton(this.label, {super.key});
​
  @override
  Widget build(BuildContext context) {
    return ElevatedButton(onPressed: () {}, child: Text(label));
  }
}
```

Then use `CustomButton`, just as you'd use any other Flutter widget:

dart

```
@override
Widget build(BuildContext context) {
  return const Center(child: CustomButton('Hello'));
}
```

## Intents

[#](#intents)

### What is the equivalent of an Intent in Flutter?

[#](#what-is-the-equivalent-of-an-intent-in-flutter)

In Android, there are two main use cases for `Intent`s: navigating between Activities, and communicating with components. Flutter, on the other hand, does not have the concept of intents, although you can still start intents through native integrations, such as the [`android_intent_plus`](https://pub.dev/packages/android_intent_plus) plugin.

Flutter doesn't really have a direct equivalent to activities and fragments; rather, in Flutter you navigate between screens, using a `Navigator` and `Route`s, all within the same `Activity`.

A `Route` is an abstraction for a "screen" or "page" of an app, and a `Navigator` is a widget that manages routes. A route roughly maps to an `Activity`, but it does not carry the same meaning. A navigator can push and pop routes to move from screen to screen. Navigators work like a stack on which you can `push()` new routes you want to navigate to, and from which you can `pop()` routes when you want to "go back".

In Android, you declare your activities inside the app's `AndroidManifest.xml`.

In Flutter, you have a couple options to navigate between pages:

* Specify a `Map` of route names. (using `MaterialApp`)
* Directly navigate to a route. (using `WidgetsApp`)

The following example builds a Map.

dart

```
void main() {
  runApp(
    MaterialApp(
      home: const MyAppHome(), // Becomes the route named '/'.
      routes: <String, WidgetBuilder>{
        '/a': (context) => const MyPage(title: 'page A'),
        '/b': (context) => const MyPage(title: 'page B'),
        '/c': (context) => const MyPage(title: 'page C'),
      },
    ),
  );
}
```

Navigate to a route by `push`ing its name to the `Navigator`.

dart

```
Navigator.of(context).pushNamed('/b');
```

The other popular use-case for `Intent`s is to call external components such as a Camera or File picker. For this, you would need to create a native platform integration (or use an [existing plugin](https://pub.dev/flutter/)).

To learn how to build a native platform integration, see [developing packages and plugins](/packages-and-plugins/developing-packages).

### How do I handle incoming intents from external applications in Flutter?

[#](#how-do-i-handle-incoming-intents-from-external-applications-in-flutter)

Flutter can handle incoming intents from Android by directly talking to the Android layer and requesting the data that was shared.

The following example registers a text share intent filter on the native activity that runs our Flutter code, so other apps can share text with our Flutter app.

The basic flow implies that we first handle the shared text data on the Android native side (in our `Activity`), and then wait until Flutter requests for the data to provide it using a `MethodChannel`.

First, register the intent filter for all intents in `AndroidManifest.xml`:

xml

```
<activity
  android:name=".MainActivity"
  android:launchMode="singleTop"
  android:theme="@style/LaunchTheme"
  android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection"
  android:hardwareAccelerated="true"
  android:windowSoftInputMode="adjustResize">
  <!-- ... -->
  <intent-filter>
    <action android:name="android.intent.action.SEND" />
    <category android:name="android.intent.category.DEFAULT" />
    <data android:mimeType="text/plain" />
  </intent-filter>
</activity>
```

Then in `MainActivity`, handle the intent, extract the text that was shared from the intent, and hold onto it. When Flutter is ready to process, it requests the data using a platform channel, and it's sent across from the native side:

java

```
package com.example.shared;
​
import android.content.Intent;
import android.os.Bundle;
​
import androidx.annotation.NonNull;
​
import io.flutter.plugin.common.MethodChannel;
import io.flutter.embedding.android.FlutterActivity;
import io.flutter.embedding.engine.FlutterEngine;
import io.flutter.plugins.GeneratedPluginRegistrant;
​
public class MainActivity extends FlutterActivity {
​
  private String sharedText;
  private static final String CHANNEL = "app.channel.shared.data";
​
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    Intent intent = getIntent();
    String action = intent.getAction();
    String type = intent.getType();
​
    if (Intent.ACTION_SEND.equals(action) && type != null) {
      if ("text/plain".equals(type)) {
        handleSendText(intent); // Handle text being sent
      }
    }
  }
​
  @Override
  public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) {
      GeneratedPluginRegistrant.registerWith(flutterEngine);
​
      new MethodChannel(flutterEngine.getDartExecutor().getBinaryMessenger(), CHANNEL)
              .setMethodCallHandler(
                      (call, result) -> {
                          if (call.method.contentEquals("getSharedText")) {
                              result.success(sharedText);
                              sharedText = null;
                          }
                      }
              );
  }
​
  void handleSendText(Intent intent) {
    sharedText = intent.getStringExtra(Intent.EXTRA_TEXT);
  }
}
```

Finally, request the data from the Flutter side when the widget is rendered:

dart

```
import 'package:flutter/services.dart';
import 'package:material_ui/material_ui.dart';
​
void main() {
  runApp(const SampleApp());
}
​
class SampleApp extends StatelessWidget {
  const SampleApp({super.key});
​
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Sample Shared App Handler',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
      ),
      home: const SampleAppPage(),
    );
  }
}
​
class SampleAppPage extends StatefulWidget {
  const SampleAppPage({super.key});
​
  @override
  State<SampleAppPage> createState() => _SampleAppPageState();
}
​
class _SampleAppPageState extends State<SampleAppPage> {
  static const platform = MethodChannel('app.channel.shared.data');
  String dataShared = 'No data';
​
  @override
  void initState() {
    super.initState();
    getSharedText();
  }
​
  @override
  Widget build(BuildContext context) {
    return Scaffold(body: Center(child: Text(dataShared)));
  }
​
  Future<void> getSharedText() async {
    var sharedData = await platform.invokeMethod('getSharedText');
    if (sharedData != null) {
      setState(() {
        dataShared = sharedData as String;
      });
    }
  }
}
```

### What is the equivalent of startActivityForResult()?

[#](#what-is-the-equivalent-of-startactivityforresult)

The `Navigator` class handles routing in Flutter and is used to get a result back from a route that you have pushed on the stack. This is done by `await`ing on the `Future` returned by `push()`.

For example, to start a location route that lets the user select their location, you could do the following:

dart

```
Object? coordinates = await Navigator.of(context).pushNamed('/location');
```

And then, inside your location route, once the user has selected their location you can `pop` the stack with the result:

dart

```
Navigator.of(context).pop({'lat': 43.821757, 'long': -79.226392});
```

## Async UI

[#](#async-ui)

### What is the equivalent of runOnUiThread() in Flutter?

[#](#what-is-the-equivalent-of-runonuithread-in-flutter)

Dart has a single-threaded execution model, with support for `Isolate`s (a way to run Dart code on another thread), an event loop, and asynchronous programming. Unless you spawn an `Isolate`, your Dart code runs in the main UI thread and is driven by an event loop. Flutter's event loop is equivalent to Android's main `Looper`\&mdash;that is, the `Looper` that is attached to the main thread.

Dart's single-threaded model doesn't mean you need to run everything as a blocking operation that causes the UI to freeze. Unlike Android, which requires you to keep the main thread free at all times, in Flutter, use the asynchronous facilities that the Dart language provides, such as `async`/`await`, to perform asynchronous work. You might be familiar with the `async`/`await` paradigm if you've used it in C#, Javascript, or if you have used Kotlin's coroutines.

For example, you can run network code without causing the UI to hang by using `async`/`await` and letting Dart do the heavy lifting:

dart

```
Future<void> loadData() async {
  final dataURL = Uri.parse('https://jsonplaceholder.typicode.com/posts');
  final response = await http.get(dataURL);
  setState(() {
    widgets = (jsonDecode(response.body) as List)
        .cast<Map<String, Object?>>();
  });
}
```

Once the `await`ed network call is done, update the UI by calling `setState()`, which triggers a rebuild of the widget subtree and updates the data.

The following example loads data asynchronously and displays it in a `ListView`:

dart

```
import 'dart:convert';
​
import 'package:http/http.dart' as http;
import 'package:material_ui/material_ui.dart';
​
void main() {
  runApp(const SampleApp());
}
​
class SampleApp extends StatelessWidget {
  const SampleApp({super.key});
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Sample App',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
      ),
      home: const SampleAppPage(),
    );
  }
}
​
class SampleAppPage extends StatefulWidget {
  const SampleAppPage({super.key});
​
  @override
  State<SampleAppPage> createState() => _SampleAppPageState();
}
​
class _SampleAppPageState extends State<SampleAppPage> {
  List<Map<String, Object?>> widgets = [];
​
  @override
  void initState() {
    super.initState();
    loadData();
  }
​
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Sample App')),
      body: ListView.builder(
        itemCount: widgets.length,
        itemBuilder: (context, position) {
          return getRow(position);
        },
      ),
    );
  }
​
  Widget getRow(int i) {
    return Padding(
      padding: const EdgeInsets.all(10),
      child: Text("Row ${widgets[i]["title"]}"),
    );
  }
​
  Future<void> loadData() async {
    final dataURL = Uri.parse('https://jsonplaceholder.typicode.com/posts');
    final response = await http.get(dataURL);
    setState(() {
      widgets = (jsonDecode(response.body) as List)
          .cast<Map<String, Object?>>();
    });
  }
}
```

Refer to the next section for more information on doing work in the background, and how Flutter differs from Android.

### How do you move work to a background thread?

[#](#how-do-you-move-work-to-a-background-thread)

In Android, when you want to access a network resource you would typically move to a background thread and do the work, as to not block the main thread, and avoid ANRs. For example, you might be using an `AsyncTask`, a `LiveData`, an `IntentService`, a `JobScheduler` job, or an RxJava pipeline with a scheduler that works on background threads.

Since Flutter is single threaded and runs an event loop (like Node.js), you don't have to worry about thread management or spawning background threads. If you're doing I/O-bound work, such as disk access or a network call, then you can safely use `async`/`await` and you're all set. If, on the other hand, you need to do computationally intensive work that keeps the CPU busy, you want to move it to an `Isolate` to avoid blocking the event loop, like you would keep *any* sort of work out of the main thread in Android.

For I/O-bound work, declare the function as an `async` function, and `await` on long-running tasks inside the function:

dart

```
Future<void> loadData() async {
  final dataURL = Uri.parse('https://jsonplaceholder.typicode.com/posts');
  final response = await http.get(dataURL);
  setState(() {
    widgets = (jsonDecode(response.body) as List)
        .cast<Map<String, Object?>>();
  });
}
```

This is how you would typically do network or database calls, which are both I/O operations.

On Android, when you extend `AsyncTask`, you typically override 3 methods, `onPreExecute()`, `doInBackground()` and `onPostExecute()`. There is no equivalent in Flutter, since you `await` on a long-running function, and Dart's event loop takes care of the rest.

However, there are times when you might be processing a large amount of data and your UI hangs. In Flutter, use `Isolate`s to take advantage of multiple CPU cores to do long-running or computationally intensive tasks.

Isolates are separate execution threads that do not share any memory with the main execution memory heap. This means you can't access variables from the main thread, or update your UI by calling `setState()`. Unlike Android threads, Isolates are true to their name, and cannot share memory (in the form of static fields, for example).

The following example shows, in a simple isolate, how to share data back to the main thread to update the UI.

dart

```
Future<void> loadData() async {
  ReceivePort receivePort = ReceivePort();
  await Isolate.spawn(dataLoader, receivePort.sendPort);
​
  // The 'echo' isolate sends its SendPort as the first message.
  SendPort sendPort = await receivePort.first as SendPort;
​
  final msg = await sendReceive(
    sendPort,
    'https://jsonplaceholder.typicode.com/posts',
  ) as List<Object?>;
  final posts = msg.cast<Map<String, Object?>>();
​
  setState(() {
    widgets = posts;
  });
}
​
// The entry point for the isolate.
static Future<void> dataLoader(SendPort sendPort) async {
  // Open the ReceivePort for incoming messages.
  ReceivePort port = ReceivePort();
​
  // Notify any other isolates what port this isolate listens to.
  sendPort.send(port.sendPort);
​
  await for (var msg in port) {
    String dataUrl = msg[0] as String;
    SendPort replyTo = msg[1] as SendPort;
​
    http.Response response = await http.get(Uri.parse(dataUrl));
    // Lots of JSON to parse
    replyTo.send(jsonDecode(response.body));
  }
}
​
Future<Object?> sendReceive(SendPort port, Object? msg) {
  ReceivePort response = ReceivePort();
  port.send([msg, response.sendPort]);
  return response.first;
}
```

Here, `dataLoader()` is the `Isolate` that runs in its own separate execution thread. In the isolate you can perform more CPU intensive processing (parsing a big JSON, for example), or perform computationally intensive math, such as encryption or signal processing.

You can run the full example below:

dart

```
import 'dart:async';
import 'dart:convert';
import 'dart:isolate';
​
import 'package:http/http.dart' as http;
import 'package:material_ui/material_ui.dart';
​
void main() {
  runApp(const SampleApp());
}
​
class SampleApp extends StatelessWidget {
  const SampleApp({super.key});
​
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Sample App',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
      ),
      home: const SampleAppPage(),
    );
  }
}
​
class SampleAppPage extends StatefulWidget {
  const SampleAppPage({super.key});
​
  @override
  State<SampleAppPage> createState() => _SampleAppPageState();
}
​
class _SampleAppPageState extends State<SampleAppPage> {
  List<Map<String, Object?>> widgets = [];
​
  @override
  void initState() {
    super.initState();
    loadData();
  }
​
  Widget getBody() {
    bool showLoadingDialog = widgets.isEmpty;
    if (showLoadingDialog) {
      return getProgressDialog();
    } else {
      return getListView();
    }
  }
​
  Widget getProgressDialog() {
    return const Center(child: CircularProgressIndicator());
  }
​
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Sample App')),
      body: getBody(),
    );
  }
​
  ListView getListView() {
    return ListView.builder(
      itemCount: widgets.length,
      itemBuilder: (context, position) {
        return getRow(position);
      },
    );
  }
​
  Widget getRow(int i) {
    return Padding(
      padding: const EdgeInsets.all(10),
      child: Text("Row ${widgets[i]["title"]}"),
    );
  }
​
  Future<void> loadData() async {
    ReceivePort receivePort = ReceivePort();
    await Isolate.spawn(dataLoader, receivePort.sendPort);
​
    // The 'echo' isolate sends its SendPort as the first message.
    SendPort sendPort = await receivePort.first as SendPort;
​
    final msg = await sendReceive(
      sendPort,
      'https://jsonplaceholder.typicode.com/posts',
    ) as List<Object?>;
    final posts = msg.cast<Map<String, Object?>>();
​
    setState(() {
      widgets = posts;
    });
  }
​
  // The entry point for the isolate.
  static Future<void> dataLoader(SendPort sendPort) async {
    // Open the ReceivePort for incoming messages.
    ReceivePort port = ReceivePort();
​
    // Notify any other isolates what port this isolate listens to.
    sendPort.send(port.sendPort);
​
    await for (var msg in port) {
      String dataUrl = msg[0] as String;
      SendPort replyTo = msg[1] as SendPort;
​
      http.Response response = await http.get(Uri.parse(dataUrl));
      // Lots of JSON to parse
      replyTo.send(jsonDecode(response.body));
    }
  }
​
  Future<Object?> sendReceive(SendPort port, Object? msg) {
    ReceivePort response = ReceivePort();
    port.send([msg, response.sendPort]);
    return response.first;
  }
}
```

### What is the equivalent of OkHttp on Flutter?

[#](#what-is-the-equivalent-of-okhttp-on-flutter)

Making a network call in Flutter is easy when you use the popular [`http` package](https://pub.dev/packages/http).

While the http package doesn't have every feature found in OkHttp, it abstracts away much of the networking that you would normally implement yourself, making it a simple way to make network calls.

To add the `http` package as a dependency, run `flutter pub add`:

```
$ flutter pub add http
```

To make a network call, call `await` on the `async` function `http.get()`:

dart

```
import 'dart:developer' as developer;
​
import 'package:http/http.dart' as http;
​
Future<void> loadData() async {
  var dataURL = Uri.parse('https://jsonplaceholder.typicode.com/posts');
  http.Response response = await http.get(dataURL);
  developer.log(response.body);
}
```

### How do I show the progress for a long-running task?

[#](#how-do-i-show-the-progress-for-a-long-running-task)

In Android you would typically show a `ProgressBar` view in your UI while executing a long-running task on a background thread.

In Flutter, use a `ProgressIndicator` widget. Show the progress programmatically by controlling when it's rendered through a boolean flag. Tell Flutter to update its state before your long-running task starts, and hide it after it ends.

In the following example, the build function is separated into three different functions. If `showLoadingDialog` is `true` (when `widgets.isEmpty`), then render the `ProgressIndicator`. Otherwise, render the `ListView` with the data returned from a network call.

dart

```
import 'dart:convert';
​
import 'package:http/http.dart' as http;
import 'package:material_ui/material_ui.dart';
​
void main() {
  runApp(const SampleApp());
}
​
class SampleApp extends StatelessWidget {
  const SampleApp({super.key});
​
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Sample App',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
      ),
      home: const SampleAppPage(),
    );
  }
}
​
class SampleAppPage extends StatefulWidget {
  const SampleAppPage({super.key});
​
  @override
  State<SampleAppPage> createState() => _SampleAppPageState();
}
​
class _SampleAppPageState extends State<SampleAppPage> {
  List<Map<String, Object?>> widgets = [];
​
  @override
  void initState() {
    super.initState();
    loadData();
  }
​
  Widget getBody() {
    bool showLoadingDialog = widgets.isEmpty;
    if (showLoadingDialog) {
      return getProgressDialog();
    } else {
      return getListView();
    }
  }
​
  Widget getProgressDialog() {
    return const Center(child: CircularProgressIndicator());
  }
​
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Sample App')),
      body: getBody(),
    );
  }
​
  ListView getListView() {
    return ListView.builder(
      itemCount: widgets.length,
      itemBuilder: (context, position) {
        return getRow(position);
      },
    );
  }
​
  Widget getRow(int i) {
    return Padding(
      padding: const EdgeInsets.all(10),
      child: Text("Row ${widgets[i]["title"]}"),
    );
  }
​
  Future<void> loadData() async {
    final dataURL = Uri.parse('https://jsonplaceholder.typicode.com/posts');
    final response = await http.get(dataURL);
    setState(() {
      widgets = (jsonDecode(response.body) as List)
          .cast<Map<String, Object?>>();
    });
  }
}
```

## Project structure & resources

[#](#project-structure-resources)

### Where do I store my resolution-dependent image files?

[#](#where-do-i-store-my-resolution-dependent-image-files)

While Android treats resources and assets as distinct items, Flutter apps have only assets. All resources that would live in the `res/drawable-*` folders on Android, are placed in an assets folder for Flutter.

Flutter follows a simple density-based format like iOS. Assets might be `1.0x`, `2.0x`, `3.0x`, or any other multiplier. Flutter doesn't have `dp`s but there are logical pixels, which are basically the same as device-independent pixels. Flutter's [`devicePixelRatio`](https://api.flutter.dev/flutter/dart-ui/FlutterView/devicePixelRatio.html) expresses the ratio of physical pixels in a single logical pixel.

The equivalent to Android's density buckets are:

| Android density qualifier | Flutter pixel ratio |
| ------------------------- | ------------------- |
| `ldpi`                    | `0.75x`             |
| `mdpi`                    | `1.0x`              |
| `hdpi`                    | `1.5x`              |
| `xhdpi`                   | `2.0x`              |
| `xxhdpi`                  | `3.0x`              |
| `xxxhdpi`                 | `4.0x`              |

Assets are located in any arbitrary folder—Flutter has no predefined folder structure. You declare the assets (with location) in the `pubspec.yaml` file, and Flutter picks them up.

Assets stored in the native asset folder are accessed on the native side using Android's `AssetManager`:

kotlin

```
val flutterAssetStream = assetManager.open("flutter_assets/assets/my_flutter_asset.png")
```

Flutter can't access native resources or assets.

To add a new image asset called `my_icon.png` to our Flutter project, for example, and deciding that it should live in a folder we arbitrarily called `images`, you would put the base image (1.0x) in the `images` folder, and all the other variants in sub-folders called with the appropriate ratio multiplier:

```
images/my_icon.png       // Base: 1.0x image
images/2.0x/my_icon.png  // 2.0x image
images/3.0x/my_icon.png  // 3.0x image
```

Next, you'll need to declare these images in your `pubspec.yaml` file:

yaml

```
assets:
 - images/my_icon.png
```

You can then access your images using `AssetImage`:

dart

```
AssetImage('images/my_icon.png'),
```

or directly in an `Image` widget:

dart

```
@override
Widget build(BuildContext context) {
  return Image.asset('images/my_image.png');
}
```

### Where do I store strings? How do I handle localization?

[#](#where-do-i-store-strings-how-do-i-handle-localization)

Flutter currently doesn't have a dedicated resources-like system for strings. The best and recommended practice is to hold your strings in a `.arb` file as key-value pairs For example:

json

```
{
   "@@locale": "en",
   "hello":"Hello {userName}",
   "@hello":{
      "description":"A message with a single parameter",
      "placeholders":{
         "userName":{
            "type":"String",
            "example":"Bob"
         }
      }
   }
}
```

Then in your code, you can access your strings as such:

dart

```
Text(AppLocalizations.of(context)!.hello('John'));
```

See [Internationalizing Flutter apps](/ui/internationalization) for more information on this.

### What is the equivalent of a Gradle file? How do I add dependencies?

[#](#what-is-the-equivalent-of-a-gradle-file-how-do-i-add-dependencies)

In Android, you add dependencies by adding to your Gradle build script. Flutter uses Dart's own build system, and the Pub package manager. The tools delegate the building of the native Android and iOS wrapper apps to the respective build systems.

While there are Gradle files under the `android` folder in your Flutter project, only use these if you are adding native dependencies needed for per-platform integration. In general, use `pubspec.yaml` to declare external dependencies to use in Flutter. A good place to find Flutter packages is [pub.dev](https://pub.dev/flutter/packages/).

## Activities and fragments

[#](#activities-and-fragments)

### What are the equivalent of activities and fragments in Flutter?

[#](#what-are-the-equivalent-of-activities-and-fragments-in-flutter)

In Android, an `Activity` represents a single focused thing the user can do. A `Fragment` represents a behavior or a portion of user interface. Fragments are a way to modularize your code, compose sophisticated user interfaces for larger screens, and help scale your application UI. In Flutter, both of these concepts fall under the umbrella of `Widget`s.

To learn more about the UI for building Activities and Fragments, see the community-contributed article, [Flutter for Android Developers: How to design Activity UI in Flutter](https://burhanrashid52.com/flutter-for-android-developers-how-to-design-activity-ui-in-flutter/).

As mentioned in the [Intents](#what-is-the-equivalent-of-an-intent-in-flutter) section, screens in Flutter are represented by `Widget`s since everything is a widget in Flutter. Use a `Navigator` to move between different `Route`s that represent different screens or pages, or perhaps different states or renderings of the same data.

### How do I listen to Android activity lifecycle events?

[#](#how-do-i-listen-to-android-activity-lifecycle-events)

In Android, you can override methods from the `Activity` to capture lifecycle methods for the activity itself, or register `ActivityLifecycleCallbacks` on the `Application`. In Flutter, you have neither concept, but you can instead listen to lifecycle events by hooking into the `WidgetsBinding` observer and listening to the `didChangeAppLifecycleState()` change event.

The observable lifecycle events are:

* `detached` — The application is still hosted on a flutter engine but is detached from any host views.
* `inactive` — The application is in an inactive state and is not receiving user input.
* `paused` — The application is not currently visible to the user, not responding to user input, and running in the background. This is equivalent to `onPause()` in Android.
* `resumed` — The application is visible and responding to user input. This is equivalent to `onPostResume()` in Android.

For more details on the meaning of these states, see the [`AppLifecycleStatus` documentation](https://api.flutter.dev/flutter/dart-ui/AppLifecycleState.html).

As you might have noticed, only a small minority of the Activity lifecycle events are available; while `FlutterActivity` does capture almost all the activity lifecycle events internally and send them over to the Flutter engine, they're mostly shielded away from you. Flutter takes care of starting and stopping the engine for you, and there is little reason for needing to observe the activity lifecycle on the Flutter side in most cases. If you need to observe the lifecycle to acquire or release any native resources, you should likely be doing it from the native side, at any rate.

Here's an example of how to observe the lifecycle status of the containing activity:

dart

```
import 'package:flutter/widgets.dart';
​
class LifecycleWatcher extends StatefulWidget {
  const LifecycleWatcher({super.key});
​
  @override
  State<LifecycleWatcher> createState() => _LifecycleWatcherState();
}
​
class _LifecycleWatcherState extends State<LifecycleWatcher>
    with WidgetsBindingObserver {
  AppLifecycleState? _lastLifecycleState;
​
  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance.addObserver(this);
  }
​
  @override
  void dispose() {
    WidgetsBinding.instance.removeObserver(this);
    super.dispose();
  }
​
  @override
  void didChangeAppLifecycleState(AppLifecycleState state) {
    setState(() {
      _lastLifecycleState = state;
    });
  }
​
  @override
  Widget build(BuildContext context) {
    if (_lastLifecycleState == null) {
      return const Text(
        'This widget has not observed any lifecycle changes.',
        textDirection: TextDirection.ltr,
      );
    }
​
    return Text(
      'The most recent lifecycle state this widget observed was: $_lastLifecycleState.',
      textDirection: TextDirection.ltr,
    );
  }
}
​
void main() {
  runApp(const Center(child: LifecycleWatcher()));
}
```

## Layouts

[#](#layouts)

### What is the equivalent of a LinearLayout?

[#](#what-is-the-equivalent-of-a-linearlayout)

In Android, a LinearLayout is used to lay your widgets out linearly—either horizontally or vertically. In Flutter, use the Row or Column widgets to achieve the same result.

If you notice the two code samples are identical with the exception of the "Row" and "Column" widget. The children are the same and this feature can be exploited to develop rich layouts that can change overtime with the same children.

dart

```
@override
Widget build(BuildContext context) {
  return const Row(
    mainAxisAlignment: MainAxisAlignment.center,
    children: <Widget>[
      Text('Row One'),
      Text('Row Two'),
      Text('Row Three'),
      Text('Row Four'),
    ],
  );
}
```

dart

```
@override
Widget build(BuildContext context) {
  return const Column(
    mainAxisAlignment: MainAxisAlignment.center,
    children: <Widget>[
      Text('Column One'),
      Text('Column Two'),
      Text('Column Three'),
      Text('Column Four'),
    ],
  );
}
```

To learn more about building linear layouts, see the community-contributed Medium article [Flutter for Android Developers: How to design LinearLayout in Flutter](https://proandroiddev.com/flutter-for-android-developers-how-to-design-linearlayout-in-flutter-5d819c0ddf1a).

### What is the equivalent of a RelativeLayout?

[#](#what-is-the-equivalent-of-a-relativelayout)

A RelativeLayout lays your widgets out relative to each other. In Flutter, there are a few ways to achieve the same result.

You can achieve the result of a RelativeLayout by using a combination of Column, Row, and Stack widgets. You can specify rules for the widgets constructors on how the children are laid out relative to the parent.

For a good example of building a RelativeLayout in Flutter, see Collin's answer on [StackOverflow](https://stackoverflow.com/questions/44396075/equivalent-of-relativelayout-in-flutter).

### What is the equivalent of a ScrollView?

[#](#what-is-the-equivalent-of-a-scrollview)

In Android, use a ScrollView to lay out your widgets—if the user's device has a smaller screen than your content, it scrolls.

In Flutter, the easiest way to do this is using the ListView widget. This might seem like overkill coming from Android, but in Flutter a ListView widget is both a ScrollView and an Android ListView.

dart

```
@override
Widget build(BuildContext context) {
  return ListView(
    children: const <Widget>[
      Text('Row One'),
      Text('Row Two'),
      Text('Row Three'),
      Text('Row Four'),
    ],
  );
}
```

### How do I handle landscape transitions in Flutter?

[#](#how-do-i-handle-landscape-transitions-in-flutter)

FlutterView handles the config change if AndroidManifest.xml contains:

yaml

```
android:configChanges="orientation|screenSize"
```

## Gesture detection and touch event handling

[#](#gesture-detection-and-touch-event-handling)

### How do I add an onClick listener to a widget in Flutter?

[#](#how-do-i-add-an-onclick-listener-to-a-widget-in-flutter)

In Android, you can attach onClick to views such as button by calling the method 'setOnClickListener'.

In Flutter there are two ways of adding touch listeners:

1. If the Widget supports event detection, pass a function to it and handle it in the function. For example, the ElevatedButton has an `onPressed` parameter:

dart

```
@override
Widget build(BuildContext context) {
  return ElevatedButton(
    onPressed: () {
      developer.log('click');
    },
    child: const Text('Button'),
  );
}
```

2. If the Widget doesn't support event detection, wrap the widget in a GestureDetector and pass a function to the `onTap` parameter.

dart

```
class SampleTapApp extends StatelessWidget {
  const SampleTapApp({super.key});
​
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: GestureDetector(
          onTap: () {
            developer.log('tap');
          },
          child: const FlutterLogo(size: 200),
        ),
      ),
    );
  }
}
```

### How do I handle other gestures on widgets?

[#](#how-do-i-handle-other-gestures-on-widgets)

Using the GestureDetector, you can listen to a wide range of Gestures such as:

* Tap

  * `onTapDown` - A pointer that might cause a tap has contacted the screen at a particular location.
  * `onTapUp` - A pointer that triggers a tap has stopped contacting the screen at a particular location.
  * `onTap` - A tap has occurred.
  * `onTapCancel` - The pointer that previously triggered the `onTapDown` won't cause a tap.

* Double tap

  * `onDoubleTap` - The user tapped the screen at the same location twice in quick succession.

* Long press

  * `onLongPress` - A pointer has remained in contact with the screen at the same location for a long period of time.

* Vertical drag

  * `onVerticalDragStart` - A pointer has contacted the screen and might begin to move vertically.
  * `onVerticalDragUpdate` - A pointer in contact with the screen has moved further in the vertical direction.
  * `onVerticalDragEnd` - A pointer that was previously in contact with the screen and moving vertically is no longer in contact with the screen and was moving at a specific velocity when it stopped contacting the screen.

* Horizontal drag

  * `onHorizontalDragStart` - A pointer has contacted the screen and might begin to move horizontally.
  * `onHorizontalDragUpdate` - A pointer in contact with the screen has moved further in the horizontal direction.
  * `onHorizontalDragEnd` - A pointer that was previously in contact with the screen and moving horizontally is no longer in contact with the screen and was moving at a specific velocity when it stopped contacting the screen.

The following example shows a `GestureDetector` that rotates the Flutter logo on a double tap:

dart

```
class SampleApp extends StatefulWidget {
  const SampleApp({super.key});
​
  @override
  State<SampleApp> createState() => _SampleAppState();
}
​
class _SampleAppState extends State<SampleApp>
    with SingleTickerProviderStateMixin {
  late AnimationController controller;
  late CurvedAnimation curve;
​
  @override
  void initState() {
    super.initState();
    controller = AnimationController(
      vsync: this,
      duration: const Duration(milliseconds: 2000),
    );
    curve = CurvedAnimation(parent: controller, curve: Curves.easeIn);
  }
​
  @override
  void dispose() {
    controller.dispose();
    super.dispose();
  }
​
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: GestureDetector(
          onDoubleTap: () {
            if (controller.isCompleted) {
              controller.reverse();
            } else {
              controller.forward();
            }
          },
          child: RotationTransition(
            turns: curve,
            child: const FlutterLogo(size: 200),
          ),
        ),
      ),
    );
  }
}
```

## Listviews & adapters

[#](#listviews-adapters)

### What is the alternative to a ListView in Flutter?

[#](#what-is-the-alternative-to-a-listview-in-flutter)

The equivalent to a ListView in Flutter is … a ListView!

In an Android ListView, you create an adapter and pass it into the ListView, which renders each row with what your adapter returns. However, you have to make sure you recycle your rows, otherwise, you get all sorts of crazy visual glitches and memory issues.

Due to Flutter's immutable widget pattern, you pass a list of widgets to your ListView, and Flutter takes care of making sure that scrolling is fast and smooth.

dart

```
import 'package:material_ui/material_ui.dart';
​
void main() {
  runApp(const SampleApp());
}
​
class SampleApp extends StatelessWidget {
  const SampleApp({super.key});
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Sample App',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
      ),
      home: const SampleAppPage(),
    );
  }
}
​
class SampleAppPage extends StatefulWidget {
  const SampleAppPage({super.key});
​
  @override
  State<SampleAppPage> createState() => _SampleAppPageState();
}
​
class _SampleAppPageState extends State<SampleAppPage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Sample App')),
      body: ListView(children: _getListData()),
    );
  }
​
  List<Widget> _getListData() {
    List<Widget> widgets = [];
    for (int i = 0; i < 100; i++) {
      widgets.add(
        Padding(padding: const EdgeInsets.all(10), child: Text('Row $i')),
      );
    }
    return widgets;
  }
}
```

### How do I know which list item is clicked on?

[#](#how-do-i-know-which-list-item-is-clicked-on)

In Android, the ListView has a method to find out which item was clicked, 'onItemClickListener'. In Flutter, use the touch handling provided by the passed-in widgets.

dart

```
import 'dart:developer' as developer;
​
import 'package:material_ui/material_ui.dart';
​
void main() {
  runApp(const SampleApp());
}
​
class SampleApp extends StatelessWidget {
  const SampleApp({super.key});
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Sample App',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
      ),
      home: const SampleAppPage(),
    );
  }
}
​
class SampleAppPage extends StatefulWidget {
  const SampleAppPage({super.key});
​
  @override
  State<SampleAppPage> createState() => _SampleAppPageState();
}
​
class _SampleAppPageState extends State<SampleAppPage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Sample App')),
      body: ListView(children: _getListData()),
    );
  }
​
  List<Widget> _getListData() {
    List<Widget> widgets = [];
    for (int i = 0; i < 100; i++) {
      widgets.add(
        GestureDetector(
          onTap: () {
            developer.log('row tapped');
          },
          child: Padding(
            padding: const EdgeInsets.all(10),
            child: Text('Row $i'),
          ),
        ),
      );
    }
    return widgets;
  }
}
```

### How do I update ListView's dynamically?

[#](#how-do-i-update-listviews-dynamically)

On Android, you update the adapter and call `notifyDataSetChanged`.

In Flutter, if you were to update the list of widgets inside a `setState()`, you would quickly see that your data did not change visually. This is because when `setState()` is called, the Flutter rendering engine looks at the widget tree to see if anything has changed. When it gets to your `ListView`, it performs a `==` check, and determines that the two `ListView`s are the same. Nothing has changed, so no update is required.

For a simple way to update your `ListView`, create a new `List` inside of `setState()`, and copy the data from the old list to the new list. While this approach is simple, it is not recommended for large data sets, as shown in the next example.

dart

```
import 'dart:developer' as developer;
​
import 'package:material_ui/material_ui.dart';
​
void main() {
  runApp(const SampleApp());
}
​
class SampleApp extends StatelessWidget {
  const SampleApp({super.key});
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Sample App',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
      ),
      home: const SampleAppPage(),
    );
  }
}
​
class SampleAppPage extends StatefulWidget {
  const SampleAppPage({super.key});
​
  @override
  State<SampleAppPage> createState() => _SampleAppPageState();
}
​
class _SampleAppPageState extends State<SampleAppPage> {
  List<Widget> widgets = [];
​
  @override
  void initState() {
    super.initState();
    for (int i = 0; i < 100; i++) {
      widgets.add(getRow(i));
    }
  }
​
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Sample App')),
      body: ListView(children: widgets),
    );
  }
​
  Widget getRow(int i) {
    return GestureDetector(
      onTap: () {
        setState(() {
          widgets = List.from(widgets);
          widgets.add(getRow(widgets.length));
          developer.log('row $i');
        });
      },
      child: Padding(padding: const EdgeInsets.all(10), child: Text('Row $i')),
    );
  }
}
```

The recommended, efficient, and effective way to build a list uses a `ListView.Builder`. This method is great when you have a dynamic `List` or a `List` with very large amounts of data. This is essentially the equivalent of RecyclerView on Android, which automatically recycles list elements for you:

dart

```
import 'dart:developer' as developer;
​
import 'package:material_ui/material_ui.dart';
​
void main() {
  runApp(const SampleApp());
}
​
class SampleApp extends StatelessWidget {
  const SampleApp({super.key});
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Sample App',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
      ),
      home: const SampleAppPage(),
    );
  }
}
​
class SampleAppPage extends StatefulWidget {
  const SampleAppPage({super.key});
​
  @override
  State<SampleAppPage> createState() => _SampleAppPageState();
}
​
class _SampleAppPageState extends State<SampleAppPage> {
  List<Widget> widgets = [];
​
  @override
  void initState() {
    super.initState();
    for (int i = 0; i < 100; i++) {
      widgets.add(getRow(i));
    }
  }
​
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Sample App')),
      body: ListView.builder(
        itemCount: widgets.length,
        itemBuilder: (context, position) {
          return getRow(position);
        },
      ),
    );
  }
​
  Widget getRow(int i) {
    return GestureDetector(
      onTap: () {
        setState(() {
          widgets.add(getRow(widgets.length));
          developer.log('row $i');
        });
      },
      child: Padding(padding: const EdgeInsets.all(10), child: Text('Row $i')),
    );
  }
}
```

Instead of creating a "ListView", create a `ListView.builder` that takes two key parameters: the initial length of the list, and an `ItemBuilder` function.

The `ItemBuilder` function is similar to the `getView` function in an Android adapter; it takes a position, and returns the row you want rendered at that position.

Finally, but most importantly, notice that the `onTap()` function doesn't recreate the list anymore, but instead `.add`s to it.

## Working with text

[#](#working-with-text)

### How do I set custom fonts on my Text widgets?

[#](#how-do-i-set-custom-fonts-on-my-text-widgets)

In Android SDK (as of Android O), you create a Font resource file and pass it into the FontFamily param for your TextView.

In Flutter, place the font file in a folder and reference it in the `pubspec.yaml` file, similar to how you import images.

yaml

```
fonts:
   - family: MyCustomFont
     fonts:
       - asset: fonts/MyCustomFont.ttf
       - style: italic
```

Then assign the font to your `Text` widget:

dart

```
@override
Widget build(BuildContext context) {
  return Scaffold(
    appBar: AppBar(title: const Text('Sample App')),
    body: const Center(
      child: Text(
        'This is a custom font text',
        style: TextStyle(fontFamily: 'MyCustomFont'),
      ),
    ),
  );
}
```

### How do I style my Text widgets?

[#](#how-do-i-style-my-text-widgets)

Along with fonts, you can customize other styling elements on a `Text` widget. The style parameter of a `Text` widget takes a `TextStyle` object, where you can customize many parameters, such as:

* color
* decoration
* decorationColor
* decorationStyle
* fontFamily
* fontSize
* fontStyle
* fontWeight
* hashCode
* height
* inherit
* letterSpacing
* textBaseline
* wordSpacing

## Form input

[#](#form-input)

For more information on using Forms, see [Retrieve the value of a text field](/cookbook/forms/retrieve-input).

### What is the equivalent of a "hint" on an Input?

[#](#what-is-the-equivalent-of-a-hint-on-an-input)

In Flutter, you can easily show a "hint" or a placeholder text for your input by adding an InputDecoration object to the decoration constructor parameter for the Text Widget.

dart

```
Center(
  child: TextField(decoration: InputDecoration(hintText: 'This is a hint')),
)
```

### How do I show validation errors?

[#](#how-do-i-show-validation-errors)

Just as you would with a "hint", pass an InputDecoration object to the decoration constructor for the Text widget.

However, you don't want to start off by showing an error. Instead, when the user has entered invalid data, update the state, and pass a new `InputDecoration` object.

dart

```
import 'package:material_ui/material_ui.dart';
​
void main() {
  runApp(const SampleApp());
}
​
class SampleApp extends StatelessWidget {
  const SampleApp({super.key});
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Sample App',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
      ),
      home: const SampleAppPage(),
    );
  }
}
​
class SampleAppPage extends StatefulWidget {
  const SampleAppPage({super.key});
​
  @override
  State<SampleAppPage> createState() => _SampleAppPageState();
}
​
class _SampleAppPageState extends State<SampleAppPage> {
  String? _errorText;
​
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Sample App')),
      body: Center(
        child: TextField(
          onSubmitted: (text) {
            setState(() {
              if (!isEmail(text)) {
                _errorText = 'Error: This is not an email';
              } else {
                _errorText = null;
              }
            });
          },
          decoration: InputDecoration(
            hintText: 'This is a hint',
            errorText: _getErrorText(),
          ),
        ),
      ),
    );
  }
​
  String? _getErrorText() {
    return _errorText;
  }
​
  bool isEmail(String em) {
    String emailRegexp =
        r'^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|'
        r'(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|'
        r'(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$';
​
    RegExp regExp = RegExp(emailRegexp);
​
    return regExp.hasMatch(em);
  }
}
```

## Flutter plugins

[#](#flutter-plugins)

### How do I access the GPS sensor?

[#](#how-do-i-access-the-gps-sensor)

Use the [`geolocator`](https://pub.dev/packages/geolocator) community plugin.

### How do I access the camera?

[#](#how-do-i-access-the-camera)

The [`image_picker`](https://pub.dev/packages/image_picker) plugin is popular for accessing the camera.

### How do I log in with Facebook?

[#](#how-do-i-log-in-with-facebook)

To Log in with Facebook, use the [`flutter_facebook_auth`](https://pub.dev/packages/flutter_facebook_auth) community plugin.

If there is platform-specific functionality that Flutter or its community Plugins are missing, you can build your own following the [developing packages and plugins](/packages-and-plugins/developing-packages) page.

Flutter's plugin architecture, in a nutshell, is much like using an Event bus in Android: you fire off a message and let the receiver process and emit a result back to you. In this case, the receiver is code running on the native side on Android or iOS.

### How do I use the NDK in my Flutter application?

[#](#how-do-i-use-the-ndk-in-my-flutter-application)

If you use the NDK in your current Android application and want your Flutter application to take advantage of your native libraries then it's possible by building a custom plugin.

Your custom plugin first talks to your Android app, where you call your `native` functions over JNI. Once a response is ready, send a message back to Flutter and render the result.

Calling native code directly from Flutter is currently not supported.

## Themes

[#](#themes)

### How do I theme my app?

[#](#how-do-i-theme-my-app)

Out of the box, Flutter comes with a beautiful implementation of Material Design, which takes care of a lot of styling and theming needs that you would typically do. Unlike Android where you declare themes in XML and then assign it to your application using AndroidManifest.xml, in Flutter you declare themes in the top level widget.

To take full advantage of Material Components in your app, you can declare a top level widget `MaterialApp` as the entry point to your application. MaterialApp is a convenience widget that wraps a number of widgets that are commonly required for applications implementing Material Design. It builds upon a WidgetsApp by adding Material specific functionality.

You can also use a `WidgetsApp` as your app widget, which provides some of the same functionality, but is not as rich as `MaterialApp`.

To customize the colors and styles of any child components, pass a `ThemeData` object to the `MaterialApp` widget. For example, in the code below, the color scheme from seed is set to deepPurple and text selection color is red.

dart

```
import 'package:material_ui/material_ui.dart';
​
class SampleApp extends StatelessWidget {
  const SampleApp({super.key});
​
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Sample App',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        textSelectionTheme: const TextSelectionThemeData(
          selectionColor: Colors.red,
        ),
      ),
      home: const SampleAppPage(),
    );
  }
}
```

## Homescreen widgets

[#](#homescreen-widgets)

### How do I create a homescreen widget?

[#](#how-do-i-create-a-homescreen-widget)

Android homescreen widgets cannot be created fully using Flutter. They must use either Jetpack Glance(preferred method) or XML layout code. Using the third-party package, [home\_widget](https://pub.dev/packages/home_widget), you can wire a homescreen widget to Dart code, embed a Flutter component (as an image) in a host widget, and share data to/from Flutter to the homescreen widget.

To provide a richer and more engaging experience, it's recommended to add widget previews to include in the widget picker. For devices running Android 15 and above, generated widget previews allowing the user to see a dynamic and personalized version of the target widget, giving them a glimpse of how it will accurately on their home screen. For more information about the Generated Widget Previews and the fallback options for older devices, check the [Add generated previews to your widget picker](https://developer.android.com/develop/ui/compose/glance/generated-previews) documentation page.

## Databases and local storage

[#](#databases-and-local-storage)

### How do I access Shared Preferences?

[#](#how-do-i-access-shared-preferences)

In Android, you can store a small collection of key-value pairs using the SharedPreferences API.

In Flutter, access this functionality using the [Shared\_Preferences plugin](https://pub.dev/packages/shared_preferences). This plugin wraps the functionality of both Shared Preferences and NSUserDefaults (the iOS equivalent).

dart

```
import 'dart:async';
​
import 'package:material_ui/material_ui.dart';
​
import 'package:shared_preferences/shared_preferences.dart';
​
void main() {
  runApp(
    const MaterialApp(
      home: Scaffold(
        body: Center(
          child: ElevatedButton(
            onPressed: _incrementCounter,
            child: Text('Increment Counter'),
          ),
        ),
      ),
    ),
  );
}
​
Future<void> _incrementCounter() async {
  SharedPreferences prefs = await SharedPreferences.getInstance();
  int counter = (prefs.getInt('counter') ?? 0) + 1;
  await prefs.setInt('counter', counter);
}
```

### How do I access SQLite in Flutter?

[#](#how-do-i-access-sqlite-in-flutter)

In Android, you use SQLite to store structured data that you can query using SQL.

In Flutter, for macOS, Android, or iOS, access this functionality using the [SQFlite](https://pub.dev/packages/sqflite) plugin.

In Flutter, access this functionality using the [Firebase Messaging](https://github.com/firebase/flutterfire/tree/master/packages/firebase_messaging) plugin. For more information on using the Firebase Cloud Messaging API, see the [`firebase_messaging`](https://pub.dev/packages/firebase_messaging) plugin documentation.

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-09-29. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/flutter-for/android-devs.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/flutter-for/android-devs\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/flutter-for/android-devs.md "Report an issue with this page").

----
url: https://docs.flutter.dev/tools/property-editor
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Flutter Property Editor](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

## What is it?

[#](#what-is-it)

The Flutter Property Editor is a powerful IDE-tool that lets you view and modify widget properties directly from a visual interface.

It allows you to quickly discover and modify your widgets' existing and available constructor arguments, eliminating the need to jump-to-definition or manually edit the source code. Furthermore, its integration with the Flutter inspector and hot reload enables you to view changes in real time, speeding up UI development and iteration.

## How to access the Flutter Property Editor

[#](#how-to-access-the-flutter-property-editor)

1. Open the Flutter Property Editor in your supported IDE ([VS Code](/tools/vs-code#property-editor), [Android Studio/IntelliJ](/tools/android-studio#property-editor)).

2. Locate a [widget constructor invocation](/learn/pathway/tutorial/widget-fundamentals) in your Flutter code.

3. Move your cursor anywhere inside the widget constructor invocation.

   For example, in the following `build` method, place your cursor anywhere between the `T` of `Text` and the ending parenthesis `)` after `TextOverflow.clip`:

   dart

   ```
   @override
   Widget build(BuildContext context) {
       return Text(
           'Hello World!',
           overflow: TextOverflow.clip,
       );
   }
   ```

4. The Flutter Property Editor panel automatically updates to display the properties of the widget at your cursor location.

### Runtime usage

[#](#runtime-usage)

#### Integration with the Flutter inspector

[#](#integration-with-the-flutter-inspector)

The Flutter Property Editor can be used in conjunction with the [Flutter inspector](/tools/devtools/inspector) to inspect your widgets simultaneously in both tools.

1. From your preferred IDE, run and debug your Flutter application.

   * [VS Code instructions](/tools/devtools/vscode/#run-and-debug)
   * [Android Studio/IntelliJ instructions](/tools/devtools/android-studio/#run-and-debug)

2. Open the [Flutter inspector](/tools/devtools/inspector) in your IDE.

You can then use the Flutter inspector to load a widget in the Flutter Property Editor by either:

1. Selecting a widget in tree:

   * Click on a widget in the [inspector's widget tree](/tools/devtools/inspector#flutter-widget-tree).

2. Selecting a widget in your app:

   * Enable ["Select Widget Mode"](/tools/devtools/inspector#inspecting-a-widget) in the inspector.
   * Click on a widget in your running application.

Both actions will automatically:

* Jump to the widget's declaration in your source code.
* Load the selected widget in the Flutter Property Editor.

#### Integration with hot reload

[#](#integration-with-hot-reload)

The Flutter Property Editor can be used in conjunction with hot reload to view changes in real time.

1. From your preferred IDE, enable autosave and hot reloads on save.

   **VS Code**

   Add the following to your `.vscode/settings.json` file:

   json

   ```
   "files.autoSave": "afterDelay",
   "dart.flutterHotReloadOnSave": "all",
   ```

   **Android Studio and IntelliJ**

   * Open `Settings > Tools > Actions on Save` and select `Configure autosave options`.

     * Check the option to `Save files if the IDE is idle for X seconds`.
     * **Recommended:** Set a small delay duration. For example, 2 seconds.

   * Open `Settings > Languages & Frameworks > Flutter`.

     * Check the option to `Perform hot reload on save`.

2. Run and debug your Flutter application.

   * [VS Code instructions](/tools/devtools/vscode/#run-and-debug)
   * [Android Studio/IntelliJ instructions](/tools/devtools/android-studio/#run-and-debug)

3. Any changes you make from the Flutter Property Editor are automatically reflected in your running app.

## Feature set

[#](#feature-set)

The Flutter Property Editor comes equipped with several features designed to speed up the development process.

### Viewing widget documentation

[#](#viewing-widget-documentation)

When a widget is selected in the Flutter Property Editor, its documentation is displayed at the top. This allows you to quickly read the widget documentation, without needing to jump-to-definition or search online.

By default, the widget documentation is truncated. Click on "Show more" to expand the widget documentation.

### Editing widget properties

[#](#editing-widget-properties)

The Flutter Property Editor contains input fields tailored to the type of each constructor argument.

* **string, double, and int properties:**

  * These are represented by text input fields.
  * Simply type the new value into the field.
  * Press ••Tab•• or ••Enter•• to apply the edit directly to your source code.

* **boolean and enum properties:**

  * These are represented by dropdown menus.
  * Click the dropdown to see the available options (`true`/`false` for booleans, or the various enum values).
  * Select the desired value from the list to apply it to your code.

* **object properties (for example, `TextStyle`, `EdgeInsets`, `Color`):**

  * Currently not supported. The Flutter Property Editor does not yet allow direct editing of complex object properties. You will need to edit these directly in your source code.

### Understanding the property inputs

[#](#understanding-the-property-inputs)

Each property input in the Flutter Property Editor is accompanied by information to help you understand its usage.

* **Type and name:** The **type** (for example, `StackFit`) and the **name** (for example, `fit`) of the constructor parameter are displayed as a label for each input field.

* **Info tooltip (ⓘ):**

  * Hovering over the info icon next to a property input displays a tooltip.

  * The information in the tooltip includes:

    * The default value of the property, if one is defined in the widget's constructor.
    * Any documentation for that property.

- **"Set" and "default" labels:**

  * The **"set"** label appears next to an input if the property has been explicitly set in your source code. This means there is a corresponding argument provided in the widget constructor call.
  * The **"default"** label appears next to an input if the current property value matches the default parameter value as defined in the widget.

  :::tip If a property input has both a "set" and a "default" label, it means you have explicitly provided a value in your code, but this value is the same as the widget's default value for that property. In such cases, you can safely remove this argument from your code to make it more concise, as the widget will use the default value anyway. :::

### Filtering properties

[#](#filtering-properties)

For widgets with many properties, the filter bar can help to quickly locate properties of interest.

* **Filter by text:**

  * Simply type into the filter bar. The list of properties will dynamically update to show only those that match your input.

  * You can filter by a property's name, its current value, or its type. For example:

    * Typing "main" would filter to `mainAxisAlignment`, `mainAxisSize`, or other properties with "main" in their name.
    * Typing "true" would filter to all boolean properties currently set to `true`.
    * Typing "double" would filter to all properties of type `double`.

* **Filter by "set" properties:**

  * Use the filter menu button to open the filter options. Check "Only include properties that are set in the code."
  * This hides all properties that have not been explicitly set in your code, allowing you to focus only on the properties you have explicitly set.

* **Filter with a regex:**

  * The regex toggle (an `*` icon button) allows you to toggle on regex mode for the filter input.
  * When enabled, your filter text will be interpreted as a regular expression.

* **Clear the current filter:**

  * The clear button (an `X` icon button) clears out any active filters, displaying all properties of the widget again.

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/property-editor.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/tools/property-editor\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/property-editor.md "Report an issue with this page").

----
url: https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.45.0
----

The 2.45.0 release of the Dart and Flutter DevTools includes the following changes among other general improvements. To learn more about DevTools, check out the [DevTools overview](/tools/devtools/overview).

## General updates

[#](#general-updates)

* Added a memory pressure warning that allows you to reduce the memory usage of DevTools in order to avoid an OOM crash. - [#8989](https://github.com/flutter/devtools/pull/8989), [#8997](https://github.com/flutter/devtools/pull/8997), [#8998](https://github.com/flutter/devtools/pull/8998)

* Fix a bug with the review history on disconnect experience. - [#8985](https://github.com/flutter/devtools/pull/8985)

* Fixed bug where DevTools would automatically resume instead of pausing on breakpoint on connection. - [#8991](https://github.com/flutter/devtools/pull/8991)

* Prevented text inputs from stealing focus from the IDE. - [#9091](https://github.com/flutter/devtools/pull/9091)

## Inspector updates

[#](#inspector-updates)

* Fixed bug where errors in the inspector tree (e.g. RenderFlex overflow errors) were not removed after a hot-reload. - [#9106](https://github.com/flutter/devtools/pull/9106)

## Debugger updates

[#](#debugger-updates)

* Combine the Pause and Resume buttons into a single button. - [#9095](https://github.com/flutter/devtools/pull/9095)

## Deep links tool updates

[#](#deep-links-tool-updates)

* Fixed an issue with Windows file paths showing incorrectly in the Deep Links page [#9027](https://github.com/flutter/devtools/pull/9027).

* Fixed an issue with the Deep Links page crashing when no iOS configuration is present [#9027](https://github.com/flutter/devtools/pull/9027).

## Full commit history

[#](#full-commit-history)

To find a complete list of changes in this release, check out the [DevTools git log](https://github.com/flutter/devtools/tree/v2.45.0).

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/release-notes/release-notes-2.45.0.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.45.0\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/release-notes/release-notes-2.45.0.md "Report an issue with this page").

----
url: https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.49.0.md
----

\# DevTools 2.49.0 release notes > Release notes for Dart and Flutter DevTools version 2.49.0. The 2.49.0 release of the Dart and Flutter DevTools includes the following changes among other general improvements. To learn more about DevTools, check out the \[DevTools overview]\(/tools/devtools/overview). ## General updates - Fixed a bug preventing the buttons at the top of an embedded DevTools screen from being clicked under certain circumstances. - \[#9327]\(https\://github.com/flutter/devtools/pull/9327) ## Full commit history To find a complete list of changes in this release, check out the \[DevTools git log]\(https\://github.com/flutter/devtools/tree/v2.49.0).

----
url: https://docs.flutter.dev/ui/widgets/text
----

Display and style text.

[Default​Text​Style](https://api.flutter.dev/flutter/widgets/DefaultTextStyle-class.html)

[The text style to apply to descendant Text widgets without explicit style.](https://api.flutter.dev/flutter/widgets/DefaultTextStyle-class.html)

[Rich​Text](https://api.flutter.dev/flutter/widgets/RichText-class.html)

[The RichText widget displays text that uses multiple different styles. The text to display is described using a tree of TextSpan objects, each of which...](https://api.flutter.dev/flutter/widgets/RichText-class.html)

[Text](https://api.flutter.dev/flutter/widgets/Text-class.html)

[A run of text with a single style.](https://api.flutter.dev/flutter/widgets/Text-class.html)

Find more widgets in the [widget catalog](/ui/widgets).

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-09-29. [Report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/ui/widgets/text "Report an issue with this page").

----
url: https://docs.flutter.dev/ui/animations/tutorial.md
----

\# Animations tutorial > A tutorial showing how to build explicit animations in Flutter. :::secondary What you'll learn \* How to use the fundamental classes from the animation library to add animation to a widget. \* When to use \`AnimatedWidget\` vs. \`AnimatedBuilder\`. ::: This tutorial shows you how to build explicit animations in Flutter. The examples build on each other, introducing you to different aspects of the animation library. The tutorial is built on essential concepts, classes, and methods in the animation library that you can learn about in \[Introduction to animations]\[]. The Flutter SDK also provides built-in explicit animations, such as \[\`FadeTransition\`]\[], \[\`SizeTransition\`]\[], and \[\`SlideTransition\`]\[]. These widgets take an \`Animation\` object, often driven by an \`AnimationController\`, and update the UI as its value changes. They require less code than the custom explicit animations described in this tutorial. The following sections walk you through several animation examples. Each section provides a link to the source code for that example. ## Rendering animations :::secondary What's the point? \* How to add basic animation to a widget using \`addListener()\` and \`setState()\`. \* Every time the \`Animation\` generates a new number, the callback registered with \`addListener()\` calls \`setState()\`. \* How to define an \`AnimationController\` with the required \`vsync\` parameter. \* How to use the \`..\` syntax in \`..addListener\`, also known as Dart's \_cascade notation\_. ::: An \`Animation\` object generates a sequence of numbers over time, but doesn't render anything to the screen. To render with an \`Animation\` object, store the \`Animation\` object as a member of your widget, then use its value to decide how to draw. Consider the following app that draws the Flutter logo without animation:

<!--?code-excerpt "animation/animate0/lib/main.dart"?-->

\`\`\`dart import 'package:material\_ui/material\_ui.dart'; void main() => runApp(const LogoApp()); class LogoApp extends StatefulWidget { const LogoApp({super.key}); @override State createState() => \_LogoAppState(); } class \_LogoAppState extends State { @override Widget build(BuildContext context) { return Center( child: Container( margin: const EdgeInsets.symmetric(vertical: 10), height: 300, width: 300, child: const FlutterLogo(), ), ); } } \`\`\` \*\*App source:\*\* \[animate0]\[] The following shows the same code modified to animate the logo to grow from nothing to full size. When defining an \`AnimationController\`, you must pass in a \`vsync\` object. The \`vsync\` parameter is described in the \[\`AnimationController\` section]\[]. The changes from the non-animated example are highlighted: \`\`\`dart diff - class \_LogoAppState extends State { + class \_LogoAppState extends State with SingleTickerProviderStateMixin { + late Animation animation; + late AnimationController controller; + + @override + void initState() { + super.initState(); + controller = AnimationController( + duration: const Duration(seconds: 2), + vsync: this, + ); + animation = Tween(begin: 0, end: 300).animate(controller) + ..addListener(() { + setState(() { + // The state that has changed here is the animation object's value. + }); + }); + controller.forward(); + } + @override Widget build(BuildContext context) { return Center( child: Container( margin: const EdgeInsets.symmetric(vertical: 10), - height: 300, - width: 300, + height: animation.value, + width: animation.value, child: const FlutterLogo(), ), ); } + + @override + void dispose() { + controller.dispose(); + super.dispose(); + } } \`\`\` \*\*App source:\*\* \[animate1]\[] The callback registered with \`addListener()\` calls \`setState()\`, so every time the \`Animation\` generates a new number, the widget is marked for rebuilding. In \`build()\`, the container changes size because its height and width now use \`animation.value\` instead of a hardcoded value. Dispose of the controller when the \`State\` object is discarded to prevent memory leaks. With these few changes, you've created your first animation in Flutter. :::tip Dart language trick You might not be familiar with Dart's cascade notation—the two dots in \`..addListener()\`. This syntax means that the \`addListener()\` method is called with the return value from \`animate()\`. Consider the following example:

<!--?code-excerpt "animation/animate1/lib/main.dart (add-listener)"?-->

\`\`\`dart highlightLines=2 animation = Tween(begin: 0, end: 300).animate(controller) ..addListener(() { // ··· }); \`\`\` This code is equivalent to:

<!--?code-excerpt "animation/animate1/lib/main.dart (add-listener)" replace="/animation.*/$&;/g; /  \./animation/g;"?-->

\`\`\`dart highlightLines=2 animation = Tween(begin: 0, end: 300).animate(controller); animation.addListener(() { // ··· }); \`\`\` To learn more about cascades, check out \[Cascade notation]\[] in the \[Dart language documentation]\[]. ::: ## Simplifying with AnimatedWidget :::secondary What's the point? \* How to use the \[\`AnimatedWidget\`]\[] helper class (instead of \`addListener()\` and \`setState()\`) to create a widget that animates. \* Use \`AnimatedWidget\` to create a widget that performs a reusable animation. To separate the transition from the widget, use an \`AnimatedBuilder\`, as shown in the \[Refactoring with AnimatedBuilder]\[] section. \* Examples of \`AnimatedWidget\` widgets in the Flutter API: \`AnimatedBuilder\`, \`AnimatedModalBarrier\`, \`DecoratedBoxTransition\`, \`FadeTransition\`, \`PositionedTransition\`, \`RelativePositionedTransition\`, \`RotationTransition\`, \`ScaleTransition\`, \`SizeTransition\`, \`SlideTransition\`. ::: The \`AnimatedWidget\` base class allows you to separate out the core widget code from the animation code. \`AnimatedWidget\` manages its own \`State\` object and animation listener, so you don't need to implement them in your subclass. Add the following \`AnimatedLogo\` class:

<!--?code-excerpt "animation/animate2/lib/main.dart (AnimatedLogo)"?-->

\`\`\`dart class AnimatedLogo extends AnimatedWidget { const AnimatedLogo({super.key, required Animation animation}) : super(listenable: animation); @override Widget build(BuildContext context) { final animation = listenable as Animation; return Center( child: Container( margin: const EdgeInsets.symmetric(vertical: 10), height: animation.value, width: animation.value, child: const FlutterLogo(), ), ); } } \`\`\` \`AnimatedLogo\` uses the current value of the \`animation\` when drawing itself. The \`LogoApp\` still manages the \`AnimationController\` and the \`Tween\`, and it passes the \`Animation\` object to \`AnimatedLogo\`: \`\`\`dart diff void main() => runApp(const LogoApp()); + class AnimatedLogo extends AnimatedWidget { + const AnimatedLogo({super.key, required Animation animation}) + : super(listenable: animation); + + @override + Widget build(BuildContext context) { + final animation = listenable as Animation; + return Center( + child: Container( + margin: const EdgeInsets.symmetric(vertical: 10), + height: animation.value, + width: animation.value, + child: const FlutterLogo(), + ), + ); + } + } + class LogoApp extends StatefulWidget { // ... } class \_LogoAppState extends State with SingleTickerProviderStateMixin { // ... @override void initState() { super.initState(); controller = AnimationController( duration: const Duration(seconds: 2), vsync: this, ); - animation = Tween(begin: 0, end: 300).animate(controller) - ..addListener(() { - setState(() { - // The state that has changed here is the animation object's value. - }); - }); + animation = Tween(begin: 0, end: 300).animate(controller); controller.forward(); } @override - Widget build(BuildContext context) { - return Center( - child: Container( - margin: const EdgeInsets.symmetric(vertical: 10), - height: animation.value, - width: animation.value, - child: const FlutterLogo(), - ), - ); - } + Widget build(BuildContext context) => AnimatedLogo(animation: animation); // ... } \`\`\` \*\*App source:\*\* \[animate2]\[] []()## Monitoring the progress of the animation :::secondary What's the point? \* Use \`addStatusListener()\` for notifications of changes to the animation's status, such as starting, reaching an endpoint, or reversing direction. \* Run an animation in an infinite loop by reversing direction when the animation has either completed or returned to its starting state, or by calling \`controller.repeat(reverse: true)\`. ::: It's often helpful to know when an animation changes state, such as finishing, moving forward, or reversing. You can get notifications for this with \`addStatusListener()\`. The following code modifies the previous example so that it listens for a state change and prints an update. The highlighted line shows the change:

<!--?code-excerpt "animation/animate3/lib/main.dart (print-state)" plaster="none" replace="/\/\/ (\.\..*)/$1;/g; /\n  }/$&\n  \/\/ .../g"?-->

\`\`\`dart highlightLines=13 class \_LogoAppState extends State with SingleTickerProviderStateMixin { late Animation animation; late AnimationController controller; @override void initState() { super.initState(); controller = AnimationController( duration: const Duration(seconds: 2), vsync: this, ); animation = Tween(begin: 0, end: 300).animate(controller) ..addStatusListener((status) => debugPrint('$status')); controller.forward(); } // ... } \`\`\` Running this code produces this output: \`\`\`console AnimationStatus.forward AnimationStatus.completed \`\`\` Next, use \`addStatusListener()\` to reverse the animation at the beginning or the end. This creates a "breathing" effect: \`\`\`dart diff void initState() { super.initState(); controller = AnimationController( duration: const Duration(seconds: 2), vsync: this, ); - animation = Tween(begin: 0, end: 300).animate(controller); + animation = Tween(begin: 0, end: 300).animate(controller) + ..addStatusListener((status) { + if (status == AnimationStatus.completed) { + controller.reverse(); + } else if (status == AnimationStatus.dismissed) { + controller.forward(); + } + }) + ..addStatusListener((status) => debugPrint('$status')); controller.forward(); } \`\`\` \*\*App source:\*\* \[animate3]\[] :::tip Repeating an animation While \`addStatusListener()\` is useful for reacting to status changes, if your only goal is to loop an animation continuously in one or both directions, you can call \[\`controller.repeat(reverse: true)\`]\[repeat] instead of manually toggling \`forward()\` and \`reverse()\` in a status listener. Calling \`stop()\` doesn't change the animation's status, so it doesn't notify status listeners. ::: ## Refactoring with AnimatedBuilder :::secondary What's the point? \* An \[\`AnimatedBuilder\`]\[] separates the transition from the widget being animated. \* An \`AnimatedBuilder\` doesn't render the widget itself, nor does it manage the \`Animation\` object. \* Use \`AnimatedBuilder\` to describe an animation as part of a \`build()\` method for another widget. To define a widget with a reusable animation, use an \`AnimatedWidget\`, as shown in the \[Simplifying with AnimatedWidget]\[] section. \* Examples of widgets that use \`AnimatedBuilder\` in the Flutter API: \`BottomSheet\`, \`ExpansionTile\`, \`PopupMenu\`, \`ProgressIndicator\`, \`RefreshIndicator\`, \`Scaffold\`, \`SnackBar\`, \`TabBar\`, \`TextField\`. ::: One problem with the code in the \[animate3]\[] example is that changing the animation required changing the widget that renders the logo. A better solution is to separate responsibilities into different classes: \* Render the logo \* Define the \`Animation\` object \* Render the transition You can accomplish this separation with the help of the \`AnimatedBuilder\` class (or its general-purpose counterpart, \[\`ListenableBuilder\`]\[]). An \`AnimatedBuilder\` is a separate widget in the widget tree. Like \`AnimatedWidget\`, \`AnimatedBuilder\` automatically listens to notifications from the \`Animation\` object, and marks the widget tree dirty as necessary, so you don't need to call \`addListener()\`. The widget tree for the \[animate4]\[] example looks like this: Starting from the bottom of the widget tree, the code for rendering the logo is straightforward:

<!--?code-excerpt "animation/animate4/lib/main.dart (logo-widget)"?-->

\`\`\`dart class LogoWidget extends StatelessWidget { const LogoWidget({super.key}); // Leave out the height and width so it fills the animating parent. @override Widget build(BuildContext context) { return Container( margin: const EdgeInsets.symmetric(vertical: 10), child: const FlutterLogo(), ); } } \`\`\` The middle three blocks in the diagram are all created in the \`build()\` method in \`GrowTransition\`, shown below. The \`GrowTransition\` widget itself is stateless and holds the set of final variables necessary to define the transition animation. The \`build()\` method creates and returns the \`AnimatedBuilder\`, which takes the anonymous \`builder\` callback and the \`LogoWidget\` object as parameters. The work of rendering the transition actually happens in the anonymous \`builder\` callback, which creates a \`SizedBox\` (labeled \`Container\` in the diagram) of the appropriate size to force the \`LogoWidget\` to shrink to fit. One tricky point in the code below is that the child looks like it's specified twice. What's happening is that the outer reference to \`child\` is passed to \`AnimatedBuilder\`, which passes it to the anonymous closure, which then uses that object as its child. The net result is that the \`AnimatedBuilder\` is inserted in between the two widgets in the widget tree, and animation ticks don't rebuild the static \`child\` subtree.

<!--?code-excerpt "animation/animate4/lib/main.dart (grow-transition)"?-->

\`\`\`dart class GrowTransition extends StatelessWidget { const GrowTransition({ required this.child, required this.animation, super.key, }); final Widget child; final Animation animation; @override Widget build(BuildContext context) { return Center( child: AnimatedBuilder( animation: animation, builder: (context, child) { return SizedBox( height: animation.value, width: animation.value, child: child, ); }, child: child, ), ); } } \`\`\` Finally, the code to initialize the animation looks very similar to the \[animate2]\[] example. The \`initState()\` method creates an \`AnimationController\` and a \`Tween\`, then binds them with \`animate()\`. Next, the \`build()\` method returns a \`GrowTransition\` object with a \`LogoWidget\` as a child, and an animation object to drive the transition. These are the three elements listed in the bullet points above. \`\`\`dart diff void main() => runApp(const LogoApp()); + class LogoWidget extends StatelessWidget { + const LogoWidget({super.key}); + + // Leave out the height and width so it fills the animating parent. + @override + Widget build(BuildContext context) { + return Container( + margin: const EdgeInsets.symmetric(vertical: 10), + child: const FlutterLogo(), + ); + } + } + + class GrowTransition extends StatelessWidget { + const GrowTransition({ + required this.child, + required this.animation, + super.key, + }); + + final Widget child; + final Animation animation; + + @override + Widget build(BuildContext context) { + return Center( + child: AnimatedBuilder( + animation: animation, + builder: (context, child) { + return SizedBox( + height: animation.value, + width: animation.value, + child: child, + ); + }, + child: child, + ), + ); + } + } class LogoApp extends StatefulWidget { // ... } class \_LogoAppState extends State with SingleTickerProviderStateMixin { // ... @override - Widget build(BuildContext context) => AnimatedLogo(animation: animation); + Widget build(BuildContext context) { + return GrowTransition(animation: animation, child: const LogoWidget()); + } // ... } \`\`\` \*\*App source:\*\* \[animate4]\[] ## Simultaneous animations :::secondary What's the point? \* The \[\`Curves\`]\[] class defines an array of commonly used curves that you can use with a \[\`CurvedAnimation\`]\[]. ::: In this section, you'll build on the example from \[Monitoring the progress of the animation]\[] (\[animate3]\[]), which used \`AnimatedWidget\` to animate in and out continuously. Consider the case where you want to animate in and out while the opacity animates from transparent to opaque. :::note This example shows how to use multiple tweens on the same animation controller, where each tween manages a different effect in the animation. It is for illustrative purposes only. For opacity animations in production code, prefer \[\`FadeTransition\`]\[] to avoid rebuilding widgets on every frame. For size changes, consider \[\`SizeTransition\`]\[] when you want to animate how much of a child is revealed. ::: Each tween manages an aspect of the animation. For example:

<!--?code-excerpt "animation/animate5/lib/main.dart (tweens)" plaster="none"?-->

\`\`\`dart controller = AnimationController( duration: const Duration(seconds: 2), vsync: this, ); sizeAnimation = Tween(begin: 0, end: 300).animate(controller); opacityAnimation = Tween(begin: 0.1, end: 1).animate(controller); \`\`\` You can get the size with \`sizeAnimation.value\` and the opacity with \`opacityAnimation.value\`. However, \`AnimatedLogo\` already listens to the controller's animation, so it can calculate both values from that single animation instead. Change \`AnimatedLogo\` to encapsulate its own \`Tween\` objects, and its \`build()\` method calls \`Tween.evaluate()\` on the parent's animation object to calculate the required size and opacity values. The following code shows the changes with highlights:

<!--?code-excerpt "animation/animate5/lib/main.dart (diff)"?-->

\`\`\`dart highlightLines=6-7,13-14,17-18,34,44 class AnimatedLogo extends AnimatedWidget { const AnimatedLogo({super.key, required Animation animation}) : super(listenable: animation); // Make the tweens static because they don't change. static final Tween \_opacityTween = Tween(begin: 0.1, end: 1); static final Tween \_sizeTween = Tween(begin: 0, end: 300); @override Widget build(BuildContext context) { final animation = listenable as Animation; return Center( child: Opacity( opacity: \_opacityTween.evaluate(animation), child: Container( margin: const EdgeInsets.symmetric(vertical: 10), height: \_sizeTween.evaluate(animation), width: \_sizeTween.evaluate(animation), child: const FlutterLogo(), ), ), ); } } class LogoApp extends StatefulWidget { const LogoApp({super.key}); @override State createState() => \_LogoAppState(); } class \_LogoAppState extends State with SingleTickerProviderStateMixin { late Animation animation; late AnimationController controller; @override void initState() { super.initState(); controller = AnimationController( duration: const Duration(seconds: 2), vsync: this, ); animation = CurvedAnimation(parent: controller, curve: Curves.easeIn) ..addStatusListener((status) { if (status == AnimationStatus.completed) { controller.reverse(); } else if (status == AnimationStatus.dismissed) { controller.forward(); } }); controller.forward(); } @override Widget build(BuildContext context) => AnimatedLogo(animation: animation); @override void dispose() { controller.dispose(); super.dispose(); } } \`\`\` \*\*App source:\*\* \[animate5]\[] ## Next steps This tutorial gives you a foundation for creating explicit animations in Flutter using tweens, but there are many other animation APIs to explore: \* \[Implicit animations]\[] and \[\`TweenAnimationBuilder\`]\[] for simpler animations that don't require managing an \`AnimationController\`. \* Specialized \[\`Tween\`]\[] subclasses, such as \[\`ColorTween\`]\[], \[\`RectTween\`]\[], and \[\`IntTween\`]\[]. \* \[Staggered animations]\[] for coordinating sequential or overlapping motions. \* \[Hero animations]\[] for shared element transitions between routes. \* \[Physics simulations]\[] and \[\`fling()\`]\[] methods for motion modeled on real-world physics. \* \[\`ReverseAnimation\`]\[] and the \[Animations overview]\[] for a deeper look at Flutter's animation architecture. \[animate0]: https\://github.com/flutter/website/tree/main/examples/animation/animate0 \[animate1]: https\://github.com/flutter/website/tree/main/examples/animation/animate1 \[animate2]: https\://github.com/flutter/website/tree/main/examples/animation/animate2 \[animate3]: https\://github.com/flutter/website/tree/main/examples/animation/animate3 \[animate4]: https\://github.com/flutter/website/tree/main/examples/animation/animate4 \[animate5]: https\://github.com/flutter/website/tree/main/examples/animation/animate5 \[\`AnimatedWidget\`]: https\://api.flutter.dev/flutter/widgets/AnimatedWidget-class.html \[\`AnimatedBuilder\`]: https\://api.flutter.dev/flutter/widgets/AnimatedBuilder-class.html \[Animations overview]: /ui/animations/overview \[Introduction to animations]: /ui/animations \[\`AnimationController\`]: https\://api.flutter.dev/flutter/animation/AnimationController-class.html \[\`AnimationController\` section]: /ui/animations#animationcontroller \[\`ColorTween\`]: https\://api.flutter.dev/flutter/animation/ColorTween-class.html \[\`Curves\`]: https\://api.flutter.dev/flutter/animation/Curves-class.html \[\`CurvedAnimation\`]: https\://api.flutter.dev/flutter/animation/CurvedAnimation-class.html \[Cascade notation]: https\://dart.dev/language/operators#cascade-notation \[Dart language documentation]: https\://dart.dev/language \[\`FadeTransition\`]: https\://api.flutter.dev/flutter/widgets/FadeTransition-class.html \[\`fling()\`]: https\://api.flutter.dev/flutter/animation/AnimationController/fling.html \[Hero animations]: /ui/animations/hero-animations \[Implicit animations]: /ui/animations/implicit-animations \[\`IntTween\`]: https\://api.flutter.dev/flutter/animation/IntTween-class.html \[\`ListenableBuilder\`]: https\://api.flutter.dev/flutter/widgets/ListenableBuilder-class.html \[Monitoring the progress of the animation]: #monitoring \[Physics simulations]: /cookbook/animation/physics-simulation \[\`RectTween\`]: https\://api.flutter.dev/flutter/animation/RectTween-class.html \[Refactoring with AnimatedBuilder]: #refactoring-with-animatedbuilder \[repeat]: https\://api.flutter.dev/flutter/animation/AnimationController/repeat.html \[\`ReverseAnimation\`]: https\://api.flutter.dev/flutter/animation/ReverseAnimation-class.html \[\`SlideTransition\`]: https\://api.flutter.dev/flutter/widgets/SlideTransition-class.html \[Simplifying with AnimatedWidget]: #simplifying-with-animatedwidget \[\`SizeTransition\`]: https\://api.flutter.dev/flutter/widgets/SizeTransition-class.html \[Staggered animations]: /ui/animations/staggered-animations \[\`Tween\`]: https\://api.flutter.dev/flutter/animation/Tween-class.html \[\`TweenAnimationBuilder\`]: https\://api.flutter.dev/flutter/widgets/TweenAnimationBuilder-class.html

----
url: https://docs.flutter.dev/tools/devtools/network
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Use the Network View](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

## What is it?

[#](#what-is-it)

The network view allows you to inspect HTTP, HTTPS, and WebSocket traffic from your Dart or Flutter application.

## What network traffic is recorded?

[#](#what-network-traffic-is-recorded)

All network traffic that originates from `dart:io` (like the [`HttpClient`](https://api.flutter.dev/dart-io/HttpClient-class.html) class) is logged, including the [`dio`](https://pub.dev/packages/dio) package. Also all network traffic that is logged using the [`http_profile`](https://pub.dev/packages/http_profile) package is recorded in the network request table. This includes network traffic from the [`cupertino_http`](https://pub.dev/packages/cupertino_http), [`cronet_http`](https://pub.dev/packages/cronet_http), and [`ok_http`](https://pub.dev/packages/ok_http) packages.

For a web app that makes requests using the browser, we recommend using browser tools to inspect network traffic, such as [Chrome DevTools](https://developer.chrome.com/docs/devtools/network).

## How to use it

[#](#how-to-use-it)

When you open the Network page, DevTools immediately starts recording network traffic. To pause and resume recording, use the **Pause** and **Resume** buttons (upper left).

When a network request is sent by your app, it appears in the network request table (left). It's listed as "Pending" until a complete response is received.

Select a network request from the table (left) to view details (right). You can inspect general and timing information about the request, as well as the content of response and request headers and bodies. Some data is not available until the response is received.

### Search and filtering

[#](#search-and-filtering)

You can use the search and filter controls to find a specific request or filter requests out of the request table.

To apply a filter, press the filter button (right of the search bar). You will see a filter dialog pop up:

The filter query syntax is described in the dialog. You can filter network requests by the following keys:

* `method`, `m`: this filter corresponds to the value in the "Method" column
* `status`, `s`: this filter corresponds to the value in the "Status" column
* `type`, `t`: this filter corresponds to the value in the "Type" column

Any text that is not paired with an available filter key will be queried against all categories (method, URI, status, type).

Example filter queries:

```
my-endpoint m:get t:json s:200
```

```
https s:404
```

### Recording network requests on app startup

[#](#recording-network-requests-on-app-startup)

To record network traffic on app startup, you can start your app in a paused state, and then begin recording network traffic in DevTools before resuming your app.

1. Start your app in a paused state:

   * `flutter run --start-paused ...`
   * `dart run --pause-isolates-on-start --observe ...`

2. Open DevTools from the IDE where you started your app, or from the link that was printed to the command line if you started your app from the CLI.

3. Navigate to the Network screen and ensure that recording has started.

4. Resume your app.

5. The Network profiler will now record all network traffic from your app, including traffic from app startup.

## Other resources

[#](#other-resources)

HTTP and HTTPS requests are also surfaced in the [`Timeline`](/tools/devtools/performance#timeline-events-tab) as asynchronous timeline events. Viewing network activity in the timeline can be useful if you want to see how HTTP traffic aligns with other events happening in your app or in the Flutter framework.

To learn how to monitor an app's network traffic and inspect different types of requests using the DevTools, check out a guided [Network View tutorial](https://medium.com/@fluttergems/mastering-dart-flutter-devtools-network-view-part-4-of-8-afce2463687c). The tutorial also uses the view to identify network activity that causes poor app performance.

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-07-31. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/network.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/tools/devtools/network\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/network.md "Report an issue with this page").

----
url: https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.28.3.md
----

\# DevTools 2.28.3 release notes > Release notes for Dart and Flutter DevTools version 2.28.3. The 2.28.3 release of the Dart and Flutter DevTools includes the following changes among other general improvements. To learn more about DevTools, check out the \[DevTools overview]\(https\://docs.flutter.dev/tools/devtools). This was a cherry-pick release on top of DevTools 2.28.2. To learn about the improvements included in DevTools 2.28.2, please read the \[release notes]\(/tools/devtools/release-notes/release-notes-2.28.2). ## General updates \* Added a link to the new "Dive in to DevTools" YouTube \[video]\(https\://www\.youtube.com/watch?v=\_EYk-E29edo) in the bottom status bar. This video provides a brief tutorial for each DevTools screen. \[#6554]\(https\://github.com/flutter/devtools/pull/6554) !\[Link to watch a DevTools tutorial video]\(/assets/images/docs/tools/devtools/release-notes/images-2.28.3/watch\_tutorial\_link.png "Link to watch a DevTools tutorial video") \* Added a workaround to fix copy button functionality in VSCode. - \[#6598]\(https\://github.com/flutter/devtools/pull/6598) ## Performance updates \* Disable the Raster Stats tool for the Impeller backend since it is not supported. - \[#6616]\(https\://github.com/flutter/devtools/pull/6616) ## VS Code Sidebar updates \* When using VS Code with a light theme, the embedded sidebar provided by DevTools will now also show in the light theme. - \[#6581]\(https\://github.com/flutter/devtools/pull/6581) ## Full commit history To find a complete list of changes in this release, check out the \[DevTools git log]\(https\://github.com/flutter/devtools/tree/v2.28.3).

----
url: https://docs.flutter.dev/ui/adaptive-responsive/general
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top General approach to adaptive apps](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

So, just *how* do you approach taking an app designed for conventional mobile devices, and make it beautiful on a wide range of devices? What steps are required?

Google engineers, who have experience doing this very thing for large apps, recommend the following 3-step approach.

## Step 1: Abstract

[#](#step-1-abstract)

First, identify the widgets that you plan to make dynamic. Analyze the constructors for those widgets and abstract out the data that you can share.

Common widgets that require adaptability are:

* Dialogs, both fullscreen and modal
* Navigation UI, both rail and bottom bar
* Custom layout, such as "is the UI area taller or wider?"

For example, in a `Dialog` widget, you can share the info that contains the *content* of the dialog.

Or, perhaps you want to switch between a `NavigationBar` when the app window is small, and a `NavigationRail` when the app window is large. These widgets would likely share a list of navigable destinations. In this case, you might create a `Destination` widget to hold this info, and specify the `Destination` as having both an icon and a text label.

Next, you will evaluate your screen size to decide on how to display your UI.

## Step 2: Measure

[#](#step-2-measure)

You have two ways to determine the size of your display area: `MediaQuery` and `LayoutBuilder`.

### MediaQuery

[#](#mediaquery)

In the past, you might have used `MediaQuery.of` to determine the size of the device's screen. However, devices today feature screens with a wide variety of sizes and shapes, and this test can be misleading.

For example, maybe your app currently occupies a small window on a large screen. If you use the `MediaQuery.of` method and conclude the screen to be small (when, in fact, the app displays in a tiny window on a large screen), and you've portrait locked your app, it causes the app's window to lock to the center of the screen, surrounded with black. This is hardly an ideal UI on a large screen.

Keep in mind that `MediaQuery.sizeOf` returns the current size of the app's entire screen and not just a single widget.

You have two ways to measure your screen space. You can use either `MediaQuery.sizeOf` or `LayoutBuilder`, depending on whether you want the size of the whole app window, or more local sizing.

If you want your widget to be fullscreen, even when the app window is small, use `MediaQuery.sizeOf` so you can choose the UI based on the size of the app window itself. In the previous section, you want to base the sizing behavior on the entire app's window, so you would use `MediaQuery.sizeOf`.

Requesting the size of the app window from inside the `build` method, as in `MediaQuery.sizeOf(context)`, causes the given `BuildContext` to rebuild any time the size property changes.

### LayoutBuilder

[#](#layoutbuilder)

`LayoutBuilder` accomplishes a similar goal as `MediaQuery.sizeOf`, with some distinctions.

Rather than providing the size of the app's window, `LayoutBuilder` provides the layout constraints from the parent `Widget`. This means that you get sizing information based on the specific spot in the widget tree where you added the `LayoutBuilder`. Also, `LayoutBuilder` returns a `BoxConstraints` object instead of a `Size` object, so you are given the valid width and height ranges (minimum and maximum) for the content, rather than just a fixed size. This can be useful for custom widgets.

For example, imagine a custom widget, where you want the sizing to be based on the space specifically given to that widget, and not the app window in general. In this scenario, use `LayoutBuilder`.

## Step 3: Branch

[#](#step-3-branch)

At this point, you must decide what sizing breakpoints to use when choosing what version of the UI to display. For example, the [Material layout](https://m3.material.io/foundations/layout/applying-layout/window-size-classes) guidelines suggest using a bottom nav bar for windows less than 600 logical pixels wide, and a nav rail for those that are 600 pixels wide or greater. Again, your choice shouldn't depend on the *type* of device, but on the device's available window size.

To work through an example that switches between a `NavigationRail` and a `NavigationBar`, check out the [Building an animated responsive app layout with Material 3](https://codelabs.developers.google.com/codelabs/flutter-animated-responsive-layout).

The next page discusses how to ensure that your app looks best on large screens and foldables.

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/ui/adaptive-responsive/general.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/ui/adaptive-responsive/general\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/ui/adaptive-responsive/general.md "Report an issue with this page").

----
url: https://docs.flutter.dev/cookbook/effects/nested-nav
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Create a nested navigation flow](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

Apps accumulate dozens and then hundreds of routes over time. Some of your routes make sense as top-level (global) routes. For example, "/", "profile", "contact", "social\_feed" are all possible top-level routes within your app. But, imagine that you defined every possible route in your top-level `Navigator` widget. The list would be very long, and many of these routes would be better handled nested within another widget.

Consider an Internet of Things (IoT) setup flow for a wireless light bulb that you control with your app. This setup flow consists of four pages:

* `find_devices` page: Find nearby bulbs.
* `select_device` page: Select the bulb that you want to add.
* `connecting` page: Add the bulb.
* `finished` page: Complete the setup.

You could orchestrate this behavior from your top-level `Navigator` widget. However, it makes more sense to define a second, nested `Navigator` widget within your `SetupFlow` widget, and let the nested `Navigator` take ownership over the four pages in the setup flow. This delegation of navigation facilitates greater local control, which is generally preferable when developing software.

The following animation shows the app's behavior:

In this recipe, you implement a four-page IoT setup flow that maintains its own navigation nested beneath the top-level `Navigator` widget.

## Prepare for navigation

[#](#prepare-for-navigation)

This IoT app has two top-level screens, along with the setup flow. Define these route names as constants so that they can be referenced within code.

dart

```
const routeHome = '/';
const routeSettings = '/settings';
const routePrefixDeviceSetup = '/setup/';
const routeDeviceSetupStart = '/setup/$routeDeviceSetupStartPage';
const routeDeviceSetupStartPage = 'find_devices';
const routeDeviceSetupSelectDevicePage = 'select_device';
const routeDeviceSetupConnectingPage = 'connecting';
const routeDeviceSetupFinishedPage = 'finished';
```

The home and settings screens are referenced with static names. The setup flow pages, however, use two paths to create their route names: a `/setup/` prefix followed by the name of the specific page. By combining the two paths, your `Navigator` can determine that a route name is intended for the setup flow without recognizing all the individual pages associated with the setup flow.

The top-level `Navigator` isn't responsible for identifying individual setup flow pages. Therefore, your top-level `Navigator` needs to parse the incoming route name to identify the setup flow prefix. Needing to parse the route name means that you can't use the `routes` property of your top-level `Navigator`. Instead, you must provide a function for the `onGenerateRoute` property.

Implement `onGenerateRoute` to return the appropriate widget for each of the three top-level paths.

dart

```
onGenerateRoute: (settings) {
  final Widget page;
  if (settings.name == routeHome) {
    page = const HomeScreen();
  } else if (settings.name == routeSettings) {
    page = const SettingsScreen();
  } else if (settings.name!.startsWith(routePrefixDeviceSetup)) {
    final subRoute = settings.name!.substring(
      routePrefixDeviceSetup.length,
    );
    page = SetupFlow(setupPageRoute: subRoute);
  } else {
    throw Exception('Unknown route: ${settings.name}');
  }
​
  return MaterialPageRoute<void>(
    builder: (context) {
      return page;
    },
    settings: settings,
  );
},
```

Notice that the home and settings routes are matched with exact route names. However, the setup flow route condition only checks for a prefix. If the route name contains the setup flow prefix, then the rest of the route name is ignored and passed on to the `SetupFlow` widget to process. This splitting of the route name is what allows the top-level `Navigator` to be agnostic toward the various subroutes within the setup flow.

Create a stateful widget called `SetupFlow` that accepts a route name.

dart

```
class SetupFlow extends StatefulWidget {
  const SetupFlow({super.key, required this.setupPageRoute});
​
  final String setupPageRoute;
​
  @override
  State<SetupFlow> createState() => SetupFlowState();
}
​
class SetupFlowState extends State<SetupFlow> {
  //...
}
```

## Display an app bar for the setup flow

[#](#display-an-app-bar-for-the-setup-flow)

The setup flow displays a persistent app bar that appears across all pages.

Return a `Scaffold` widget from your `SetupFlow` widget's `build()` method, and include the desired `AppBar` widget.

dart

```
@override
Widget build(BuildContext context) {
  return Scaffold(appBar: _buildFlowAppBar(), body: const SizedBox());
}
​
PreferredSizeWidget _buildFlowAppBar() {
  return AppBar(title: const Text('Bulb Setup'));
}
```

The app bar displays a back arrow and exits the setup flow when the back arrow is pressed. However, exiting the flow causes the user to lose all progress. Therefore, the user is prompted to confirm whether they want to exit the setup flow.

Prompt the user to confirm exiting the setup flow, and ensure that the prompt appears when the user presses the hardware back button on their device.

dart

```
Future<void> _onExitPressed() async {
  final isConfirmed = await _isExitDesired();
​
  if (isConfirmed && mounted) {
    _exitSetup();
  }
}
​
Future<bool> _isExitDesired() async {
  return await showDialog<bool>(
        context: context,
        builder: (context) {
          return AlertDialog(
            title: const Text('Are you sure?'),
            content: const Text(
              'If you exit device setup, your progress will be lost.',
            ),
            actions: [
              TextButton(
                onPressed: () {
                  Navigator.of(context).pop(true);
                },
                child: const Text('Leave'),
              ),
              TextButton(
                onPressed: () {
                  Navigator.of(context).pop(false);
                },
                child: const Text('Stay'),
              ),
            ],
          );
        },
      ) ??
      false;
}
​
void _exitSetup() {
  Navigator.of(context).pop();
}
​
@override
Widget build(BuildContext context) {
  return PopScope(
    canPop: false,
    onPopInvokedWithResult: (didPop, _) async {
      if (didPop) return;
​
      if (await _isExitDesired() && context.mounted) {
        _exitSetup();
      }
    },
    child: Scaffold(appBar: _buildFlowAppBar(), body: const SizedBox()),
  );
}
​
PreferredSizeWidget _buildFlowAppBar() {
  return AppBar(
    leading: IconButton(
      onPressed: _onExitPressed,
      icon: const Icon(Icons.chevron_left),
    ),
    title: const Text('Bulb Setup'),
  );
}
```

When the user taps the back arrow in the app bar, or presses the back button on their device, an alert dialog pops up to confirm that the user wants to leave the setup flow. If the user presses **Leave**, then the setup flow pops itself from the top-level navigation stack. If the user presses **Stay**, then the action is ignored.

You might notice that the `Navigator.pop()` is invoked by both the **Leave** and **Stay** buttons. To be clear, this `pop()` action pops the alert dialog off the navigation stack, not the setup flow.

## Generate nested routes

[#](#generate-nested-routes)

The setup flow's job is to display the appropriate page within the flow.

Add a `Navigator` widget to `SetupFlow`, and implement the `onGenerateRoute` property.

dart

```
final _navigatorKey = GlobalKey<NavigatorState>();
​
void _onDiscoveryComplete() {
  _navigatorKey.currentState!.pushNamed(routeDeviceSetupSelectDevicePage);
}
​
void _onDeviceSelected(String deviceId) {
  _navigatorKey.currentState!.pushNamed(routeDeviceSetupConnectingPage);
}
​
void _onConnectionEstablished() {
  _navigatorKey.currentState!.pushNamed(routeDeviceSetupFinishedPage);
}
​
@override
Widget build(BuildContext context) {
  return PopScope(
    canPop: false,
    onPopInvokedWithResult: (didPop, _) async {
      if (didPop) return;
​
      if (await _isExitDesired() && context.mounted) {
        _exitSetup();
      }
    },
    child: Scaffold(
      appBar: _buildFlowAppBar(),
      body: Navigator(
        key: _navigatorKey,
        initialRoute: widget.setupPageRoute,
        onGenerateRoute: _onGenerateRoute,
      ),
    ),
  );
}
​
Route<Widget> _onGenerateRoute(RouteSettings settings) {
  final page = switch (settings.name) {
    routeDeviceSetupStartPage => WaitingPage(
      message: 'Searching for nearby bulb...',
      onWaitComplete: _onDiscoveryComplete,
    ),
    routeDeviceSetupSelectDevicePage => SelectDevicePage(
      onDeviceSelected: _onDeviceSelected,
    ),
    routeDeviceSetupConnectingPage => WaitingPage(
      message: 'Connecting...',
      onWaitComplete: _onConnectionEstablished,
    ),
    routeDeviceSetupFinishedPage => FinishedPage(onFinishPressed: _exitSetup),
    _ => throw StateError('Unexpected route name: ${settings.name}!'),
  };
​
  return MaterialPageRoute(
    builder: (context) {
      return page;
    },
    settings: settings,
  );
}
```

The `_onGenerateRoute` function works the same as for a top-level `Navigator`. A `RouteSettings` object is passed into the function, which includes the route's `name`. Based on that route name, one of four flow pages is returned.

The first page, called `find_devices`, waits a few seconds to simulate network scanning. After the wait period, the page invokes its callback. In this case, that callback is `_onDiscoveryComplete`. The setup flow recognizes that, when device discovery is complete, the device selection page should be shown. Therefore, in `_onDiscoveryComplete`, the `_navigatorKey` instructs the nested `Navigator` to navigate to the `select_device` page.

The `select_device` page asks the user to select a device from a list of available devices. In this recipe, only one device is presented to the user. When the user taps a device, the `onDeviceSelected` callback is invoked. The setup flow recognizes that, when a device is selected, the connecting page should be shown. Therefore, in `_onDeviceSelected`, the `_navigatorKey` instructs the nested `Navigator` to navigate to the `"connecting"` page.

The `connecting` page works the same way as the `find_devices` page. The `connecting` page waits for a few seconds and then invokes its callback. In this case, the callback is `_onConnectionEstablished`. The setup flow recognizes that, when a connection is established, the final page should be shown. Therefore, in `_onConnectionEstablished`, the `_navigatorKey` instructs the nested `Navigator` to navigate to the `finished` page.

The `finished` page provides the user with a **Finish** button. When the user taps **Finish**, the `_exitSetup` callback is invoked, which pops the entire setup flow off the top-level `Navigator` stack, taking the user back to the home screen.

Congratulations! You implemented nested navigation with four subroutes.

## Interactive example

[#](#interactive-example)

Run the app:

* On the **Add your first bulb** screen, click the FAB, shown with a plus sign, **+**. This brings you to the **Select a nearby device** screen. A single bulb is listed.
* Click the listed bulb. A **Finished!** screen appears.
* Click the **Finished** button to return to the first screen.

```
import 'package:flutter/material.dart';

const routeHome = '/';
const routeSettings = '/settings';
const routePrefixDeviceSetup = '/setup/';
const routeDeviceSetupStart = '/setup/$routeDeviceSetupStartPage';
const routeDeviceSetupStartPage = 'find_devices';
const routeDeviceSetupSelectDevicePage = 'select_device';
const routeDeviceSetupConnectingPage = 'connecting';
const routeDeviceSetupFinishedPage = 'finished';

void main() {
  runApp(
    MaterialApp(
      theme: ThemeData(
        brightness: Brightness.dark,
        appBarTheme: const AppBarTheme(backgroundColor: Colors.blue),
        floatingActionButtonTheme: const FloatingActionButtonThemeData(
          backgroundColor: Colors.blue,
        ),
      ),
      onGenerateRoute: (settings) {
        final Widget page;
        if (settings.name == routeHome) {
          page = const HomeScreen();
        } else if (settings.name == routeSettings) {
          page = const SettingsScreen();
        } else if (settings.name!.startsWith(routePrefixDeviceSetup)) {
          final subRoute = settings.name!.substring(
            routePrefixDeviceSetup.length,
          );
          page = SetupFlow(setupPageRoute: subRoute);
        } else {
          throw Exception('Unknown route: ${settings.name}');
        }

        return MaterialPageRoute<void>(
          builder: (context) {
            return page;
          },
          settings: settings,
        );
      },
      debugShowCheckedModeBanner: false,
    ),
  );
}

@immutable
class SetupFlow extends StatefulWidget {
  static SetupFlowState of(BuildContext context) {
    return context.findAncestorStateOfType<SetupFlowState>()!;
  }

  const SetupFlow({super.key, required this.setupPageRoute});

  final String setupPageRoute;

  @override
  SetupFlowState createState() => SetupFlowState();
}

class SetupFlowState extends State<SetupFlow> {
  final _navigatorKey = GlobalKey<NavigatorState>();

  @override
  void initState() {
    super.initState();
  }

  void _onDiscoveryComplete() {
    _navigatorKey.currentState!.pushNamed(routeDeviceSetupSelectDevicePage);
  }

  void _onDeviceSelected(String deviceId) {
    _navigatorKey.currentState!.pushNamed(routeDeviceSetupConnectingPage);
  }

  void _onConnectionEstablished() {
    _navigatorKey.currentState!.pushNamed(routeDeviceSetupFinishedPage);
  }

  Future<void> _onExitPressed() async {
    final isConfirmed = await _isExitDesired();

    if (isConfirmed && mounted) {
      _exitSetup();
    }
  }

  Future<bool> _isExitDesired() async {
    return await showDialog<bool>(
          context: context,
          builder: (context) {
            return AlertDialog(
              title: const Text('Are you sure?'),
              content: const Text(
                'If you exit device setup, your progress will be lost.',
              ),
              actions: [
                TextButton(
                  onPressed: () {
                    Navigator.of(context).pop(true);
                  },
                  child: const Text('Leave'),
                ),
                TextButton(
                  onPressed: () {
                    Navigator.of(context).pop(false);
                  },
                  child: const Text('Stay'),
                ),
              ],
            );
          },
        ) ??
        false;
  }

  void _exitSetup() {
    Navigator.of(context).pop();
  }

  @override
  Widget build(BuildContext context) {
    return PopScope(
      canPop: false,
      onPopInvokedWithResult: (didPop, _) async {
        if (didPop) return;

        if (await _isExitDesired() && context.mounted) {
          _exitSetup();
        }
      },
      child: Scaffold(
        appBar: _buildFlowAppBar(),
        body: Navigator(
          key: _navigatorKey,
          initialRoute: widget.setupPageRoute,
          onGenerateRoute: _onGenerateRoute,
        ),
      ),
    );
  }

  Route<Widget> _onGenerateRoute(RouteSettings settings) {
    final page = switch (settings.name) {
      routeDeviceSetupStartPage => WaitingPage(
        message: 'Searching for nearby bulb...',
        onWaitComplete: _onDiscoveryComplete,
      ),
      routeDeviceSetupSelectDevicePage => SelectDevicePage(
        onDeviceSelected: _onDeviceSelected,
      ),
      routeDeviceSetupConnectingPage => WaitingPage(
        message: 'Connecting...',
        onWaitComplete: _onConnectionEstablished,
      ),
      routeDeviceSetupFinishedPage => FinishedPage(onFinishPressed: _exitSetup),
      _ => throw StateError('Unexpected route name: ${settings.name}!'),
    };

    return MaterialPageRoute(
      builder: (context) {
        return page;
      },
      settings: settings,
    );
  }

  PreferredSizeWidget _buildFlowAppBar() {
    return AppBar(
      leading: IconButton(
        onPressed: _onExitPressed,
        icon: const Icon(Icons.chevron_left),
      ),
      title: const Text('Bulb Setup'),
    );
  }
}

class SelectDevicePage extends StatelessWidget {
  const SelectDevicePage({super.key, required this.onDeviceSelected});

  final void Function(String deviceId) onDeviceSelected;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Padding(
          padding: const EdgeInsets.symmetric(horizontal: 24),
          child: Column(
            mainAxisSize: MainAxisSize.min,
            children: [
              Text(
                'Select a nearby device:',
                style: Theme.of(context).textTheme.titleLarge,
              ),
              const SizedBox(height: 24),
              SizedBox(
                width: double.infinity,
                height: 54,
                child: ElevatedButton(
                  style: ButtonStyle(
                    backgroundColor: WidgetStateColor.resolveWith((states) {
                      return const Color(0xFF222222);
                    }),
                  ),
                  onPressed: () {
                    onDeviceSelected('22n483nk5834');
                  },
                  child: const Text(
                    'Bulb 22n483nk5834',
                    style: TextStyle(fontSize: 24),
                  ),
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

class WaitingPage extends StatefulWidget {
  const WaitingPage({
    super.key,
    required this.message,
    required this.onWaitComplete,
  });

  final String message;
  final VoidCallback onWaitComplete;

  @override
  State<WaitingPage> createState() => _WaitingPageState();
}

class _WaitingPageState extends State<WaitingPage> {
  @override
  void initState() {
    super.initState();
    _startWaiting();
  }

  Future<void> _startWaiting() async {
    await Future<dynamic>.delayed(const Duration(seconds: 3));

    if (mounted) {
      widget.onWaitComplete();
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Padding(
          padding: const EdgeInsets.symmetric(horizontal: 24),
          child: Column(
            mainAxisSize: MainAxisSize.min,
            children: [
              const CircularProgressIndicator(),
              const SizedBox(height: 32),
              Text(widget.message),
            ],
          ),
        ),
      ),
    );
  }
}

class FinishedPage extends StatelessWidget {
  const FinishedPage({super.key, required this.onFinishPressed});

  final VoidCallback onFinishPressed;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Padding(
          padding: const EdgeInsets.symmetric(horizontal: 24),
          child: SingleChildScrollView(
            child: Column(
              mainAxisSize: MainAxisSize.min,
              children: [
                Container(
                  width: 200,
                  height: 200,
                  decoration: const BoxDecoration(
                    shape: BoxShape.circle,
                    color: Color(0xFF222222),
                  ),
                  child: const Center(
                    child: Icon(
                      Icons.lightbulb,
                      size: 140,
                      color: Colors.white,
                    ),
                  ),
                ),
                const SizedBox(height: 32),
                const Text(
                  'Bulb added!',
                  textAlign: TextAlign.center,
                  style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
                ),
                const SizedBox(height: 32),
                ElevatedButton(
                  style: ButtonStyle(
                    padding: WidgetStateProperty.resolveWith((states) {
                      return const EdgeInsets.symmetric(
                        horizontal: 24,
                        vertical: 12,
                      );
                    }),
                    backgroundColor: WidgetStateColor.resolveWith((states) {
                      return const Color(0xFF222222);
                    }),
                    shape: WidgetStateProperty.resolveWith((states) {
                      return const StadiumBorder();
                    }),
                  ),
                  onPressed: onFinishPressed,
                  child: const Text('Finish', style: TextStyle(fontSize: 24)),
                ),
              ],
            ),
          ),
        ),
      ),
    );
  }
}

@immutable
class HomeScreen extends StatelessWidget {
  const HomeScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: _buildAppBar(context),
      body: Center(
        child: Padding(
          padding: const EdgeInsets.symmetric(horizontal: 24),
          child: Column(
            mainAxisSize: MainAxisSize.min,
            children: [
              Container(
                width: 200,
                height: 200,
                decoration: const BoxDecoration(
                  shape: BoxShape.circle,
                  color: Color(0xFF222222),
                ),
                child: Center(
                  child: Icon(
                    Icons.lightbulb,
                    size: 140,
                    color: Theme.of(context).scaffoldBackgroundColor,
                  ),
                ),
              ),
              const SizedBox(height: 32),
              const Text(
                'Add your first bulb',
                textAlign: TextAlign.center,
                style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
              ),
            ],
          ),
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          Navigator.of(context).pushNamed(routeDeviceSetupStart);
        },
        child: const Icon(Icons.add),
      ),
    );
  }

  PreferredSizeWidget _buildAppBar(BuildContext context) {
    return AppBar(
      title: const Text('Welcome'),
      actions: [
        IconButton(
          icon: const Icon(Icons.settings),
          onPressed: () {
            Navigator.pushNamed(context, routeSettings);
          },
        ),
      ],
    );
  }
}

class SettingsScreen extends StatelessWidget {
  const SettingsScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: _buildAppBar(),
      body: SingleChildScrollView(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: List.generate(8, (index) {
            return Container(
              width: double.infinity,
              height: 54,
              margin: const EdgeInsets.only(left: 16, right: 16, top: 16),
              decoration: BoxDecoration(
                borderRadius: BorderRadius.circular(8),
                color: const Color(0xFF222222),
              ),
            );
          }),
        ),
      ),
    );
  }

  PreferredSizeWidget _buildAppBar() {
    return AppBar(title: const Text('Settings'));
  }
}
```

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/effects/nested-nav.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/cookbook/effects/nested-nav\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/effects/nested-nav.md "Report an issue with this page").

----
url: https://docs.flutter.dev/cookbook/audio
----

[Play or stream sound and music with flutter\_soloud](/cookbook/audio/soloud)

[Learn how to play or stream audio in your Flutter app using the flutter\_soloud package.](/cookbook/audio/soloud)

[Record or stream audio input](/cookbook/audio/record)

[Learn how to record or stream audio input in your Flutter app using the record package.](/cookbook/audio/record)

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/audio/index.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/cookbook/audio\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/audio/index.md "Report an issue with this page").

----
url: https://docs.flutter.dev/platform-integration/macos/setup
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Set up macOS development](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

Learn how to set up your development environment to run, build, and deploy Flutter apps for the macOS desktop platform.

## Set up tooling

[#](#set-up-tooling)

With Xcode, you can run Flutter apps on macOS as well as compile and debug native Swift and Objective-C code.

1. ### Install Xcode

   If you haven't done so already, [install and set up the latest version of Xcode](https://developer.apple.com/xcode/).

   If you've already installed Xcode, update it to the latest version using the same installation method you used originally.

2. ### Set up Xcode command-line tools

   To configure the Xcode command-line tools to use the version of Xcode you installed, run the following command in your preferred terminal:

   ```
   $ sudo sh -c 'xcode-select -s /Applications/Xcode.app/Contents/Developer && xcodebuild -runFirstLaunch'
   ```

   If you downloaded Xcode elsewhere or need to use a different version, replace `/Applications/Xcode.app` with the path to your Xcode directory.

3. ### Agree to the Xcode licenses

   After you've set up Xcode and configured its command-line tools, agree to the Xcode licenses.

   1. Open your preferred terminal.

   2. Run the following command to review and sign the Xcode licenses.

      ```
      $ sudo xcodebuild -license
      ```

   3. Read and agree to all necessary licenses.

      Before agreeing to the terms of each license, read each with care.

      Once you've accepted all the necessary licenses successfully, the command should output how to review the licenses.

4. ### Install CocoaPods

   To support [Flutter plugins](/packages-and-plugins/developing-packages#types) that use native macOS code, install the latest version of [CocoaPods](https://cocoapods.org/).

   Install CocoaPods following the [CocoaPods installation guide](https://guides.cocoapods.org/using/getting-started.html#installation).

   If you've already installed CocoaPods, update it following the [CocoaPods update guide](https://guides.cocoapods.org/using/getting-started.html#updating-cocoapods).

## Validate your setup

[#](#validate-setup)

1. ### Check for toolchain issues

   To check for any issues with your macOS development setup, run the `flutter doctor` command in your preferred terminal:

   ```
   $ flutter doctor -v
   ```

   If you see any errors or tasks to complete under the **Xcode** section, complete and resolve them, then run `flutter doctor -v` again to verify any changes.

2. ### Check for macOS devices

   To ensure Flutter can find and connect to your macOS device correctly, run `flutter devices` in your preferred terminal:

   ```
   $ flutter devices
   ```

   If you set everything up correctly, there should be at least one entry with the platform marked as **macos**.

3. ### Troubleshoot setup issues

   If you need help resolving any setup issues, check out [Install and setup troubleshooting](/install/troubleshoot).

   If you still have issues or questions, reach out on one of the Flutter [community](https://flutter.dev/community) channels.

## Start developing for macOS

[#](#start-developing)

Congratulations! Now that you've set up macOS desktop development for Flutter, you can continue your Flutter learning journey while testing on macOS or begin expanding integration with macOS.

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-08-18. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/platform-integration/macos/setup.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/platform-integration/macos/setup\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/platform-integration/macos/setup.md "Report an issue with this page").

----
url: https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.9.2
----

The 2.9.2 release of the Dart and Flutter DevTools includes the following changes among other general improvements. To learn more about DevTools, check out the [DevTools overview](https://docs.flutter.dev/tools/devtools).

## General updates

[#](#general-updates)

* Take our 2022 DevTools survey! Provide your feedback and help us improve your development experience. This survey prompt will show up directly in DevTools sometime in mid-February.

  *Note*: If you are having issues launching the survey, please make sure you have upgraded to the latest Flutter stable branch 2.10. There was a bug in DevTools (fixed in [#3574](https://github.com/flutter/devtools/pull/3574)) that prevented the survey from being able to be opened, and unless you are on Flutter 2.10, this bug will still be present.\_

* General bug fixes and improvements - [#3528](https://github.com/flutter/devtools/pull/3528), [#3531](https://github.com/flutter/devtools/pull/3531), [#3532](https://github.com/flutter/devtools/pull/3532), [#3539](https://github.com/flutter/devtools/pull/3539)

## Performance updates

[#](#performance-updates)

* Added frame numbers to x-axis the Flutter frames chart - [#3526](https://github.com/flutter/devtools/pull/3526)

## Debugger updates

[#](#debugger-updates)

* Fix a bug where the File Explorer in the Debugger did not show contents after a hot restart - [#3527](https://github.com/flutter/devtools/pull/3527)

## Full commit history

[#](#full-commit-history)

To find a complete list of changes since the previous release, check out [the diff on GitHub](https://github.com/flutter/devtools/compare/v2.9.1...v2.9.2).

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/release-notes/release-notes-2.9.2.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.9.2\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/release-notes/release-notes-2.9.2.md "Report an issue with this page").

----
url: https://docs.flutter.dev/cookbook/design/orientation
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Update the UI based on orientation](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

In some situations, you want to update the display of an app when the shape of the available space changes like when a user rotates the 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. Expanded docs covering this and more can be found in the [adaptive ui documentation](https://api.flutter.dev/ui/adaptive-responsive).

In Flutter, you can build different layouts depending on a given [`Orientation`](https://api.flutter.dev/flutter/widgets/Orientation.html). In this example, build a list that displays two columns in portrait mode and three columns in landscape mode using the following steps:

1. Build a `GridView` with two columns.
2. Use an `OrientationBuilder` to change the number of columns.

## 1. Build a `GridView` with two columns

[#](#1-build-a-gridview-with-two-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

```
return GridView.count(
  // A list with 2 columns
  crossAxisCount: 2,
  // ...
);
```

To learn more about working with `GridViews`, see the [Creating a grid list](/cookbook/lists/grid-lists) recipe.

## 2. Use an `OrientationBuilder` to change the number of columns

[#](#2-use-an-orientationbuilder-to-change-the-number-of-columns)

To determine the app's current `Orientation`, use the [`OrientationBuilder`](https://api.flutter.dev/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.

Using the `Orientation`, build a list that displays two columns in portrait mode, or three columns in landscape mode.

dart

```
body: OrientationBuilder(
  builder: (context, orientation) {
    return GridView.count(
      // Create a grid with 2 columns in portrait mode,
      // or 3 columns in landscape mode.
      crossAxisCount: orientation == Orientation.portrait ? 2 : 3,
    );
  },
),
```

## Interactive example

[#](#interactive-example)

```
import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    const appTitle = 'Orientation Demo';

    return const MaterialApp(
      title: appTitle,
      home: OrientationList(title: appTitle),
    );
  }
}

class OrientationList extends StatelessWidget {
  final String title;

  const OrientationList({super.key, required this.title});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text(title)),
      body: OrientationBuilder(
        builder: (context, orientation) {
          return GridView.count(
            // 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.
            children: List.generate(100, (index) {
              return Center(
                child: Text(
                  'Item $index',
                  style: TextTheme.of(context).headlineLarge,
                ),
              );
            }),
          );
        },
      ),
    );
  }
}
```

## Locking device orientation

[#](#locking-device-orientation)

In the previous section, you learned how to adapt the app UI to device orientation changes.

Flutter also allows you to specify the orientations your app supports using the values of [`DeviceOrientation`](https://api.flutter.dev/flutter/services/DeviceOrientation.html). You can either:

* Lock the app to a single orientation, like only the `portraitUp` position, or...
* Allow multiple orientations, like both `portraitUp` and `portraitDown`, but not landscape.

In the application `main()` method, call [`SystemChrome.setPreferredOrientations()`](https://api.flutter.dev/flutter/services/SystemChrome/setPreferredOrientations.html) with the list of preferred orientations that your app supports.

To lock the device to a single orientation, you can pass a list with a single item.

For a list of all the possible values, check out [`DeviceOrientation`](https://api.flutter.dev/flutter/services/DeviceOrientation.html).

dart

```
void main() {
  WidgetsFlutterBinding.ensureInitialized();
  SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
  runApp(const MyApp());
}
```

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-11. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/design/orientation.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/cookbook/design/orientation\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/design/orientation.md "Report an issue with this page").

----
url: https://docs.flutter.dev/tools/devtools/performance.md
----

\# Use the Performance view > Learn how to use the DevTools performance view. :::note The DevTools performance view works for Flutter mobile and desktop apps. For web apps, Flutter adds timeline events to the performance panel of Chrome DevTools instead. To learn about profiling web apps, check out \[Debugging web performance]\[]. ::: \[Debugging web performance]: /perf/web-performance The performance page can help you diagnose performance problems and UI jank in your application. This page offers timing and performance information for activity in your application. It consists of several tools to help you identify the cause of poor performance in your app: \* Flutter frames chart (Flutter apps only) \* Frame analysis tab (Flutter apps only) \* Timeline events trace viewer (all native Dart applications) \* Advanced debugging tools (Flutter apps only) :::secondary \*\*Use a \[profile build]\[] of your application to analyze performance.\*\* Frame rendering times aren't indicative of release performance when running in debug mode. Run your app in profile mode, which still preserves useful debugging information. ::: \[profile build]: /testing/build-modes#profile The performance view also supports importing and exporting of data snapshots. For more information, check out the \[Import and export]\[] section. ## What is a frame in Flutter? Flutter is designed to render its UI at 60 frames per second (fps), or 120 fps on devices capable of 120Hz updates. Each render is called a \_frame\_. This means that, approximately every 16ms, the UI updates to reflect animations or other changes to the UI. A frame that takes longer than 16ms to render causes jank (jerky motion) on the display device. ## Flutter frames chart This chart contains Flutter frame information for your application. Each bar set in the chart represents a single Flutter frame. The bars are color-coded to highlight the different portions of work that occur when rendering a Flutter frame: work from the UI thread and work from the raster thread. This chart contains Flutter frame timing information for your application. Each pair of bars in the chart represents a single Flutter frame. Selecting a frame from this chart updates the data that is displayed below in the \[Frame analysis]\(#frame-analysis-tab) tab or the \[Timeline events]\(#timeline-events-tab) tab. \[DevTools 2.23.1]: /tools/devtools/release-notes/release-notes-2.23.1 The flutter frames chart updates when new frames are drawn in your app. To pause updates to this chart, click the pause button to the right of the chart. This chart can be collapsed to provide more viewing space for data below by clicking the \*\*Flutter frames\*\* button above the chart. !\[Screenshot of a Flutter frames chart]\(/assets/images/docs/tools/devtools/flutter-frames-chart.png) The pair of bars representing each Flutter frame are color-coded to highlight the different portions of work that occur when rendering a Flutter frame: work from the UI thread and work from the raster thread. ### UI The UI thread executes Dart code in the Dart VM. This includes code from your application as well as the Flutter framework. When your app creates and displays a scene, the UI thread creates a layer tree, a lightweight object containing device-agnostic painting commands, and sends the layer tree to the raster thread to be rendered on the device. Do \*\*not\*\* block this thread. ### Raster The raster thread executes graphics code from the Flutter Engine. This thread takes the layer tree and displays it by talking to the GPU (graphic processing unit). You can't directly access the raster thread or its data, but if this thread is slow, it's a result of something you've done in the Dart code. Skia, the graphics library, runs on this thread. \[Impeller]\[] also uses this thread. \[Impeller]: /perf/impeller Sometimes a scene results in a layer tree that is easy to construct, but expensive to render on the raster thread. In this case, you need to figure out what your code is doing that is causing rendering code to be slow. Specific kinds of workloads are more difficult for the GPU. They might involve unnecessary calls to \`saveLayer()\`, intersecting opacities with multiple objects, and clips or shadows in specific situations. For more information on profiling, check out \[Identifying problems in the GPU graph]\[GPU graph]. ### Jank (slow frame) The frame rendering chart shows jank with a red overlay. A frame is considered to be janky if it takes more than \~16 ms to complete (for 60 FPS devices). To achieve a frame rendering rate of 60 FPS (frames per second), each frame must render in \~16 ms or less. When this target is missed, you may experience UI jank or dropped frames. For more information on how to analyze your app's performance, check out \[Flutter performance profiling]\[]. ### Shader compilation Shader compilation occurs when a shader is first used in your Flutter app. Frames that perform shader compilation are marked in dark red: !\[Screenshot of shader compilation for a frame]\(/assets/images/docs/tools/devtools/shader-compilation-frames-chart.png) For more information on how to reduce shader compilation jank, check out \[Reduce shader compilation jank on mobile]\[]. ## Frame analysis tab Selecting a janky frame (slow, colored in red) from the Flutter frames chart above shows debugging hints in the Frame analysis tab. These hints help you diagnose jank in your app, and notify you of any expensive operations that we have detected that might have contributed to the slow frame time. !\[Screenshot of the frame analysis tab]\(/assets/images/docs/tools/devtools/frame-analysis-tab.png) ## Timeline events tab The timeline events chart shows all event tracing from your application. The Flutter framework emits timeline events as it works to build frames, draw scenes, and track other activity such as HTTP request timings and garbage collection. These events show up here in the Timeline. You can also send your own Timeline events using the dart:developer \[\`Timeline\`]\[] and \[\`TimelineTask\`]\[] APIs. \[\`Timeline\`]: https\://api.flutter.dev/flutter/dart-developer/Timeline-class.html \[\`TimelineTask\`]: https\://api.flutter.dev/flutter/dart-developer/TimelineTask-class.html !\[Screenshot of a timeline events tab]\(/assets/images/docs/tools/devtools/timeline-events-tab.png) For help with navigating and using the trace viewer, click the \*\*?\*\* button at the top right of the timeline events tab bar. To refresh the timeline with new events from your application, click the refresh button (also in the upper right corner of the tab controls). ## Advanced debugging tools ### Enhance tracing To view more detailed tracing in the timeline events chart, use the options in the enhance tracing dropdown: :::note Frame times might be negatively affected when these options are enabled. ::: !\[Screenshot of enhanced tracing options]\(/assets/images/docs/tools/devtools/enhanced-tracing.png) To see the new timeline events, reproduce the activity in your app that you are interested in tracing, and then select a frame to inspect the timeline. ### Track widget builds To see the \`build()\` method events in the timeline, enable the \*\*Track Widget Builds\*\* option. The name of the widget is shown in the timeline event. !\[Screenshot of track widget builds]\(/assets/images/docs/tools/devtools/track-widget-builds.png) \[Watch this video for an example of tracking widget builds]\[track-widgets] ### Track layouts To see render object layout events in the timeline, enable the \*\*Track Layouts\*\* option: !\[Screenshot of track layouts]\(/assets/images/docs/tools/devtools/track-layouts.png) \[Watch this video for an example of tracking layouts]\[track-layouts] ### Track paints To see render object paint events in the timeline, enable the \*\*Track Paints\*\* option: !\[Screenshot of track paints]\(/assets/images/docs/tools/devtools/track-paints.png) \[Watch this video for an example of tracking paints]\[track-paints] ## More debugging options To diagnose performance problems related to rendering layers, toggle off a rendering layer. These options are enabled by default. To see the effects on your app's performance, reproduce the activity in your app. Then select the new frames in the frames chart to inspect the timeline events with the layers disabled. If raster time has significantly decreased, excessive use of the effects you disabled might be contributing to the jank you saw in your app. \*\*Render Clip layers\*\* : Disable this option to check whether excessive use of clipping is affecting performance. If performance improves with this option disabled, try to reduce the use of clipping effects in your app. \*\*Render Opacity layers\*\* : Disable this option to check whether excessive use of opacity effects are affecting performance. If performance improves with this option disabled, try to reduce the use of opacity effects in your app. \*\*Render Physical Shape layers\*\* : Disable this option to check whether excessive use of physical modeling effects are affecting performance, such as shadows or elevation. If performance improves with this option disabled, try to reduce the use of physical modeling effects in your app. !\[Screenshot of more debugging options]\(/assets/images/docs/tools/devtools/more-debugging-options.png) ## Import and export DevTools supports importing and exporting performance snapshots. Clicking the export button (upper-right corner above the frame rendering chart) downloads a snapshot of the current data on the performance page. To import a performance snapshot, you can drag and drop the snapshot into DevTools from any page. \*\*Note that DevTools only supports importing files that were originally exported from DevTools.\*\* ## Other resources To learn how to monitor an app's performance and detect jank using DevTools, check out a guided \[Performance View tutorial]\[performance-tutorial]. \[GPU graph]: /perf/ui-performance#identifying-problems-in-the-gpu-graph \[Flutter performance profiling]: /perf/ui-performance \[Reduce shader compilation jank on mobile]: /perf/rendering-performance \[Import and export]: #import-and-export \[performance-tutorial]: https\://medium.com/@fluttergems/mastering-dart-flutter-devtools-performance-view-part-8-of-8-4ae762f91230 \[track-widgets]: https\://www\.youtube.com/watch/\_EYk-E29edo?t=623 \[track-layouts]: https\://www\.youtube.com/watch/\_EYk-E29edo?t=676 \[track-paints]: https\://www\.youtube.com/watch/\_EYk-E29edo?t=748

----
url: https://docs.flutter.dev/ui/adaptive-responsive/platform-adaptations
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Automatic platform adaptations](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

## Adaptation philosophy

[#](#adaptation-philosophy)

In general, two cases of platform adaptiveness exist:

1. Things that are behaviors of the OS environment (such as text editing and scrolling) and that would be 'wrong' if a different behavior took place.
2. Things that are conventionally implemented in apps using the OEM's SDKs (such as using parallel tabs on iOS or showing an [`android.app.AlertDialog`](https://developer.android.com/reference/android/app/AlertDialog.html) on Android).

This article mainly covers the automatic adaptations provided by Flutter in case 1 on Android and iOS.

For case 2, Flutter bundles the means to produce the appropriate effects of the platform conventions but doesn't adapt automatically when app design choices are needed. For a discussion, see [issue #8410](https://github.com/flutter/flutter/issues/8410#issuecomment-468034023) and the [Material/Cupertino adaptive widget problem definition](https://bit.ly/flutter-adaptive-widget-problem).

For an example of an app using different information architecture structures on Android and iOS but sharing the same content code, see the [platform\_design code samples](https://github.com/flutter/samples/tree/main/platform_design).

## Page navigation

[#](#page-navigation)

Flutter provides the navigation patterns seen on Android and iOS and also automatically adapts the navigation animation to the current platform.

### Navigation transitions

[#](#navigation-transitions)

On **Android**, the default [`Navigator.push()`](https://api.flutter.dev/flutter/widgets/Navigator/push.html) transition is modeled after [`startActivity()`](https://developer.android.com/reference/kotlin/android/app/Activity#startactivity), which generally has one bottom-up animation variant.

On **iOS**:

* The default [`Navigator.push()`](https://api.flutter.dev/flutter/widgets/Navigator/push.html) API produces an iOS Show/Push style transition that animates from end-to-start depending on the locale's RTL setting. The page behind the new route also parallax-slides in the same direction as in iOS.
* A separate bottom-up transition style exists when pushing a page route where [`PageRoute.fullscreenDialog`](https://api.flutter.dev/flutter/widgets/PageRoute-class.html) is true. This represents iOS's Present/Modal style transition and is typically used on fullscreen modal pages.

Android page transition

iOS push transition

iOS present transition

### Platform-specific transition details

[#](#platform-specific-transition-details)

On **Android**, Flutter uses the [`ZoomPageTransitionsBuilder`](https://pub.dev/documentation/material_ui/latest/material_ui/ZoomPageTransitionsBuilder-class.html) animation. When the user taps on an item, the UI zooms in to a screen that features that item. When the user taps to go back, the UI zooms out to the previous screen.

On **iOS** when the push style transition is used, Flutter's bundled [`CupertinoNavigationBar`](https://pub.dev/documentation/cupertino_ui/latest/cupertino_ui/CupertinoNavigationBar-class.html) and [`CupertinoSliverNavigationBar`](https://pub.dev/documentation/cupertino_ui/latest/cupertino_ui/CupertinoSliverNavigationBar-class.html) nav bars automatically animate each subcomponent to its corresponding subcomponent on the next or previous page's `CupertinoNavigationBar` or `CupertinoSliverNavigationBar`.

Android

iOS Nav Bar

### Back navigation

[#](#back-navigation)

On **Android**, the OS back button, by default, is sent to Flutter and pops the top route of the [`WidgetsApp`](https://api.flutter.dev/flutter/widgets/WidgetsApp-class.html)'s Navigator.

On **iOS**, an edge swipe gesture can be used to pop the top route.

Android back button

iOS back swipe gesture

## Scrolling

[#](#scrolling)

Scrolling is an important part of the platform's look and feel, and Flutter automatically adjusts the scrolling behavior to match the current platform.

### Physics simulation

[#](#physics-simulation)

Android and iOS both have complex scrolling physics simulations that are difficult to describe verbally. Generally, iOS's scrollable has more weight and dynamic friction but Android has more static friction. Therefore iOS gains high speed more gradually but stops less abruptly and is more slippery at slow speeds.

Soft fling comparison

Medium fling comparison

Strong fling comparison

### Overscroll behavior

[#](#overscroll-behavior)

On **Android**, scrolling past the edge of a scrollable shows an [overscroll glow indicator](https://api.flutter.dev/flutter/widgets/GlowingOverscrollIndicator-class.html) (based on the color of the current Material theme).

On **iOS**, scrolling past the edge of a scrollable [overscrolls](https://api.flutter.dev/flutter/widgets/BouncingScrollPhysics-class.html) with increasing resistance and snaps back.

Dynamic overscroll comparison

Static overscroll comparison

### Scrollbars

[#](#scrollbars)

On **Material-based platforms** (such as Android and web), scrollbars are typically visible during scrolling and may remain visible depending on the platform and theme.

On **Cupertino-based platforms** (such as iOS), scrollbars are more minimal and generally only appear briefly while the user is actively scrolling, fading out when interaction stops.

This difference reflects each platform’s visual conventions and helps maintain a native look and feel across devices.

### Momentum

[#](#momentum)

On **iOS**, repeated flings in the same direction stacks momentum and builds more speed with each successive fling. There is no equivalent behavior on Android.

iOS scroll momentum

### Return to top

[#](#return-to-top)

On **iOS**, tapping the OS status bar scrolls the primary scroll controller to the top position. There is no equivalent behavior on Android.

iOS status bar tap to top

## Typography

[#](#typography)

When using the Material package, the typography automatically defaults to the font family appropriate for the platform. Android uses the Roboto font. iOS uses the San Francisco font.

When using the Cupertino package, the [default theme](https://github.com/flutter/flutter/blob/main/packages/flutter/lib/src/cupertino/text_theme.dart) uses the San Francisco font.

The San Francisco font license limits its usage to software running on iOS, macOS, or tvOS only. Therefore a fallback font is used when running on Android if the platform is debug-overridden to iOS or the default Cupertino theme is used.

You might choose to adapt the text styling of Material widgets to match the default text styling on iOS. You can see widget-specific examples in the [UI Component section](#ui-components).

Roboto on Android

San Francisco on iOS

## Iconography

[#](#iconography)

When using the Material package, certain icons automatically show different graphics depending on the platform. For instance, the overflow button's three dots are horizontal on iOS and vertical on Android. The back button is a simple chevron on iOS and has a stem/shaft on Android.

Icons on Android

Icons on iOS

The material library also provides a set of platform-adaptive icons through [`Icons.adaptive`](https://pub.dev/documentation/material_ui/latest/material_ui/PlatformAdaptiveIcons-class.html).

## Haptic feedback

[#](#haptic-feedback)

The Material and Cupertino packages automatically trigger the platform appropriate haptic feedback in certain scenarios.

For instance, a word selection via text field long-press triggers a 'buzz' vibrate on Android and not on iOS.

Scrolling through picker items on iOS triggers a 'light impact' knock and no feedback on Android.

## Text editing

[#](#text-editing)

Both the Material and Cupertino Text Input fields support spellcheck and adapt to use the proper spellcheck configuration for the platform, and the proper spell check menu and highlight colors.

Flutter also makes the below adaptations while editing the content of text fields to match the current platform.

### Keyboard gesture navigation

[#](#keyboard-gesture-navigation)

On **Android**, horizontal swipes can be made on the soft keyboard's `space` key to move the cursor in Material and Cupertino text fields.

On **iOS** devices with 3D Touch capabilities, a force-press-drag gesture could be made on the soft keyboard to move the cursor in 2D via a floating cursor. This works on both Material and Cupertino text fields.

Android space key cursor move

iOS 3D Touch drag cursor move

### Text selection toolbar

[#](#text-selection-toolbar)

With **Material on Android**, the Android style selection toolbar is shown when a text selection is made in a text field.

With **Material on iOS** or when using **Cupertino**, the iOS style selection toolbar is shown when a text selection is made in a text field.

Android text selection toolbar

iOS text selection toolbar

### Single tap gesture

[#](#single-tap-gesture)

With **Material on Android**, a single tap in a text field puts the cursor at the location of the tap.

A collapsed text selection also shows a draggable handle to subsequently move the cursor.

With **Material on iOS** or when using **Cupertino**, a single tap in a text field puts the cursor at the nearest edge of the word tapped.

Collapsed text selections don't have draggable handles on iOS.

Android tap

iOS tap

### Long-press gesture

[#](#long-press-gesture)

With **Material on Android**, a long press selects the word under the long press. The selection toolbar is shown upon release.

With **Material on iOS** or when using **Cupertino**, a long press places the cursor at the location of the long press. The selection toolbar is shown upon release.

Android long press

iOS long press

### Long-press drag gesture

[#](#long-press-drag-gesture)

With **Material on Android**, dragging while holding the long press expands the words selected.

With **Material on iOS** or when using **Cupertino**, dragging while holding the long press moves the cursor.

Android long-press drag

iOS long-press drag

### Double tap gesture

[#](#double-tap-gesture)

On both Android and iOS, a double tap selects the word receiving the double tap and shows the selection toolbar.

Android double tap

iOS double tap

## UI components

[#](#ui-components)

This section includes preliminary recommendations on how to adapt Material widgets to deliver a natural and compelling experience on iOS. Your feedback is welcomed on [issue #8427](https://github.com/flutter/website/issues/8427).

### Widgets with .adaptive() constructors

[#](#widgets-with-adaptive-constructors)

Several widgets support `.adaptive()` constructors. The following table lists these widgets. Adaptive constructors substitute the corresponding Cupertino components when the app is run on an iOS device.

Widgets in the following table are used primarily for input, selection, and to display system information. Because these controls are tightly integrated with the operating system, users have been trained to recognize and respond to them. Therefore, we recommend that you follow platform conventions.

| Material widget              | Cupertino widget              | Adaptive constructor                                                                                                                                                     |
| ---------------------------- | ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|  `Switch`                    |  `CupertinoSwitch`            | [`Switch.adaptive()`](https://pub.dev/documentation/material_ui/latest/material_ui/Switch/Switch.adaptive.html)                                                          |
|  `Slider`                    |  `CupertinoSlider`            | [`Slider.adaptive()`](https://pub.dev/documentation/material_ui/latest/material_ui/Slider/Slider.adaptive.html)                                                          |
|  `CircularProgressIndicator` |  `CupertinoActivityIndicator` | [`CircularProgressIndicator.adaptive()`](https://pub.dev/documentation/material_ui/latest/material_ui/CircularProgressIndicator/CircularProgressIndicator.adaptive.html) |
|  `RefreshProgressIndicator`  |  `CupertinoActivityIndicator` | [`RefreshIndicator.adaptive()`](https://pub.dev/documentation/material_ui/latest/material_ui/RefreshIndicator/RefreshIndicator.adaptive.html)                            |
|  `Checkbox`                  |  `CupertinoCheckbox`          | [`Checkbox.adaptive()`](https://pub.dev/documentation/material_ui/latest/material_ui/Checkbox/Checkbox.adaptive.html)                                                    |
|  `Radio`                     |  `CupertinoRadio`             | [`Radio.adaptive()`](https://pub.dev/documentation/material_ui/latest/material_ui/Radio/Radio.adaptive.html)                                                             |
|  `AlertDialog`               |  `CupertinoAlertDialog`       | [`AlertDialog.adaptive()`](https://pub.dev/documentation/material_ui/latest/material_ui/AlertDialog/AlertDialog.adaptive.html)                                           |

### Top app bar and navigation bar

[#](#top-app-bar-and-navigation-bar)

Since Android 12, the default UI for top app bars follows the design guidelines defined in [Material 3](https://m3.material.io/components/top-app-bar/overview). On iOS, an equivalent component called "Navigation Bars" is defined in [Apple's Human Interface Guidelines](https://developer.apple.com/design/human-interface-guidelines/components/navigation-and-search/navigation-bars/) (HIG).

Top App Bar in Material 3

Navigation Bar in Human Interface Guidelines

Certain properties of app bars in Flutter apps should be adapted, like system icons and page transitions. These are already automatically adapted when using the Material `AppBar` and `SliverAppBar` widgets. You can also further customize the properties of these widgets to better match iOS platform styles, as shown below.

dart

```
// Map the text theme to iOS styles
TextTheme cupertinoTextTheme = TextTheme(
    headlineMedium: CupertinoThemeData()
        .textTheme
        .navLargeTitleTextStyle
         // fixes a small bug with spacing
        .copyWith(letterSpacing: -1.5),
    titleLarge: CupertinoThemeData().textTheme.navTitleTextStyle)
...
​
// Use iOS text theme on iOS devices
ThemeData(
      textTheme: Platform.isIOS ? cupertinoTextTheme : null,
      ...
)
...
​
// Modify AppBar properties
AppBar(
        surfaceTintColor: Platform.isIOS ? Colors.transparent : null,
        shadowColor: Platform.isIOS ? CupertinoColors.darkBackgroundGray : null,
        scrolledUnderElevation: Platform.isIOS ? .1 : null,
        toolbarHeight: Platform.isIOS ? 44 : null,
        ...
      ),
```

But, because app bars are displayed alongside other content in your page, it's only recommended to adapt the styling so long as it's cohesive with the rest of your application. You can see additional code samples and a further explanation in [the GitHub discussion on app bar adaptations](https://github.com/flutter/uxr/discussions/93).

### Bottom navigation bars

[#](#bottom-navigation-bars)

Since Android 12, the default UI for bottom navigation bars follow the design guidelines defined in [Material 3](https://m3.material.io/components/navigation-bar/overview). On iOS, an equivalent component called "Tab Bars" is defined in [Apple's Human Interface Guidelines](https://developer.apple.com/design/human-interface-guidelines/components/navigation-and-search/tab-bars/) (HIG).

Bottom Navigation Bar in Material 3

Tab Bar in Human Interface Guidelines

Since tab bars are persistent across your app, they should match your own branding. However, if you choose to use Material's default styling on Android, you might consider adapting to the default iOS tab bars.

To implement platform-specific bottom navigation bars, you can use Flutter's `NavigationBar` widget on Android and the `CupertinoTabBar` widget on iOS. Below is a code snippet you can adapt to show a platform-specific navigation bars.

dart

```
final Map<String, Icon> _navigationItems = {
    'Menu': Platform.isIOS ? Icon(CupertinoIcons.house_fill) : Icon(Icons.home),
    'Order': Icon(Icons.adaptive.share),
  };
​
...
​
Scaffold(
  body: _currentWidget,
  bottomNavigationBar: Platform.isIOS
          ? CupertinoTabBar(
              currentIndex: _currentIndex,
              onTap: (index) {
                setState(() => _currentIndex = index);
                _loadScreen();
              },
              items: _navigationItems.entries
                  .map<BottomNavigationBarItem>(
                      (entry) => BottomNavigationBarItem(
                            icon: entry.value,
                            label: entry.key,
                          ))
                  .toList(),
            )
          : NavigationBar(
              selectedIndex: _currentIndex,
              onDestinationSelected: (index) {
                setState(() => _currentIndex = index);
                _loadScreen();
              },
              destinations: _navigationItems.entries
                  .map<Widget>((entry) => NavigationDestination(
                        icon: entry.value,
                        label: entry.key,
                      ))
                  .toList(),
            ));
```

### Text fields

[#](#text-fields)

Since Android 12, text fields follow the [Material 3](https://m3.material.io/components/text-fields/overview) (M3) design guidelines. On iOS, Apple's [Human Interface Guidelines](https://developer.apple.com/design/human-interface-guidelines/text-fields) (HIG) define an equivalent component.

Text Field in Material 3

Text Field in HIG

Since text fields require user input, their design should follow platform conventions.

To implement a platform-specific `TextField` in Flutter, you can adapt the styling of the Material `TextField`.

dart

```
Widget _createAdaptiveTextField() {
  final _border = OutlineInputBorder(
    borderSide: BorderSide(color: CupertinoColors.lightBackgroundGray),
  );
​
  final iOSDecoration = InputDecoration(
    border: _border,
    enabledBorder: _border,
    focusedBorder: _border,
    filled: true,
    fillColor: CupertinoColors.white,
    hoverColor: CupertinoColors.white,
    contentPadding: EdgeInsets.fromLTRB(10, 0, 0, 0),
  );
​
  return Platform.isIOS
      ? SizedBox(
          height: 36.0,
          child: TextField(
            decoration: iOSDecoration,
          ),
        )
      : TextField();
}
```

To learn more about adapting text fields, check out [the GitHub discussion on text fields](https://github.com/flutter/uxr/discussions/95). You can leave feedback or ask questions in the discussion.

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-09-29. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/ui/adaptive-responsive/platform-adaptations.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/ui/adaptive-responsive/platform-adaptations\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/ui/adaptive-responsive/platform-adaptations.md "Report an issue with this page").

----
url: https://docs.flutter.dev/ui/adaptive-responsive
----

One of Flutter's primary goals is to create a framework that allows you to develop apps from a single codebase that look and feel great on any platform.

This means that your app might appear on screens of many different sizes, from a watch, to a foldable phone with two screens, to a high definition monitor. And your input device might be a physical or virtual keyboard, a mouse, a touchscreen, or any number of other devices.

Two terms that describe these design concepts are *adaptive* and *responsive*. Ideally, you'd want your app to be *both* but what, exactly, does this mean?

## What is responsive vs adaptive?

[#](#what-is-responsive-vs-adaptive)

An easy way to think about it is that responsive design is about fitting the UI *into* the space and adaptive design is about the UI being *usable* in the space.

So, a responsive app adjusts the placement of design elements to *fit* the available space. And an adaptive app selects the appropriate layout and input devices to be usable *in* the available space. For example, should a tablet UI use bottom navigation or side-panel navigation?

This section covers various aspects of adaptive and responsive design:

* [General approach](/ui/adaptive-responsive/general)
* [SafeArea & MediaQuery](/ui/adaptive-responsive/safearea-mediaquery)
* [Large screens & foldables](/ui/adaptive-responsive/large-screens)
* [User input & accessibility](/ui/adaptive-responsive/input)
* [Capabilities & policies](/ui/adaptive-responsive/capabilities)
* [Best practices for adaptive apps](/ui/adaptive-responsive/best-practices)
* [Additional resources](/ui/adaptive-responsive/more-info)

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/ui/adaptive-responsive/index.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/ui/adaptive-responsive\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/ui/adaptive-responsive/index.md "Report an issue with this page").

----
url: https://docs.flutter.dev/flutter-for/dart-swift-concurrency
----

Both Dart and Swift support concurrent programming. This guide should help you understand how concurrency works in Dart and how it compares to Swift. With this understanding, you can create high-performing iOS apps.

When developing in the Apple ecosystem, some tasks might take a long time to complete. These tasks include fetching or processing large amounts of data. iOS developers typically use Grand Central Dispatch (GCD) to schedule tasks using a shared thread pool. With GCD, developers add tasks to dispatch queues and GCD decides on which thread to execute them.

But, GCD spins up threads to handle remaining work items. This means you can end up with a large number of threads and the system can become over committed. With Swift, the structured concurrency model reduced the number of threads and context switches. Now, each core has only one thread.

Dart has a single-threaded execution model, with support for `Isolates`, an event loop, and asynchronous code. An `Isolate` is Dart's implementation of a lightweight thread. Unless you spawn an `Isolate`, your Dart code runs in the main UI thread driven by an event loop. Flutter's event loop is equivalent to the iOS main loop—in other words, the Looper attached to the main thread.

Dart's single-threaded model doesn't mean you are required to run everything as a blocking operation that causes the UI to freeze. Instead, use the asynchronous features that the Dart language provides, such as `async`/`await`.

## Asynchronous Programming

[#](#asynchronous-programming)

An asynchronous operation allows other operations to execute before it completes. Both Dart and Swift support asynchronous functions using the `async` and `await` keywords. In both cases, `async` marks that a function performs asynchronous work, and `await` tells the system to await a result from function. This means that the Dart VM *could* suspend the function, if necessary. For more details on asynchronous programming, check out [Concurrency in Dart](https://dart.dev/guides/language/concurrency).

### Leveraging the main thread/isolate

[#](#leveraging-the-main-threadisolate)

For Apple operating systems, the primary (also called the main) thread is where the application begins running. Rendering the user interface always happens on the main thread. One difference between Swift and Dart is that Swift might use different threads for different tasks, and Swift doesn't guarantee which thread is used. So, when dispatching UI updates in Swift, you might need to ensure that the work occurs on the main thread.

Say you want to write a function that fetches the weather asynchronously and displays the results.

In GCD, to manually dispatch a process to the main thread, you might do something like the following.

First, define the `Weather` `enum`:

swift

```
enum Weather: String {
    case rainy, sunny
}
```

Next, define the view model and mark it as an [`@Observable`](https://developer.apple.com/documentation/observation/observable\(\)) that publishes the `result` of type `Weather?`. Use GCD to create a background `DispatchQueue` to send the work to the pool of threads, and then dispatch back to the main thread to update the `result`.

swift

```
@Observable class ContentViewModel {
    private(set) var result: Weather?
​
    private let queue = DispatchQueue(label: "weather_io_queue")
    func load() {
        // Mimic 1 second network delay.
        queue.asyncAfter(deadline: .now() + 1) { [weak self] in
            DispatchQueue.main.async {
                self?.result = .sunny
            }
        }
    }
}
```

Finally, display the results:

swift

```
struct ContentView: View {
    @State var viewModel = ContentViewModel()
    var body: some View {
        Text(viewModel.result?.rawValue ?? "Loading...")
            .onAppear {
                viewModel.load()
        }
    }
}
```

More recently, Swift introduced *actors* to support synchronization for shared, mutable state. To ensure that work is performed on the main thread, define a view model class that is marked as a `@MainActor`, with a `load()` function that internally calls an asynchronous function using `Task`.

swift

```
@MainActor @Observable class ContentViewModel {
  private(set) var result: Weather?
​
  func load() async {
    // Mimic 1 second network delay.
    try? await Task.sleep(nanoseconds: 1_000_000_000)
    self.result = .sunny
  }
}
```

Next, define the view model as a state using `@State`, with a `load()` function that can be called by the view model:

swift

```
struct ContentView: View {
  @State var viewModel = ContentViewModel()
  var body: some View {
    Text(viewModel.result?.rawValue ?? "Loading...")
      .task {
        await viewModel.load()
      }
  }
}
```

In Dart, all work runs on the main isolate by default. To implement the same example in Dart, first, create the `Weather` `enum`:

dart

```
enum Weather { rainy, windy, sunny }
```

Then, define a simple view model (similar to what was created in SwiftUI), to fetch the weather. In Dart, a `Future` object represents a value to be provided in the future. A `Future` is similar to Swift's `@Observable`. In this example, a function within the view model returns a `Future<Weather>` object:

dart

```
@immutable
class HomePageViewModel {
  const HomePageViewModel();
  Future<Weather> load() async {
    await Future<void>.delayed(const Duration(seconds: 1));
    return Weather.sunny;
  }
}
```

The `load()` function in this example shares similarities with the Swift code. The Dart function is marked as `async` because it uses the `await` keyword.

Additionally, a Dart function marked as `async` automatically returns a `Future`. In other words, you don't have to create a `Future` instance manually inside functions marked as `async`.

For the last step, display the weather value. In Flutter, [`FutureBuilder`](https://api.flutter.dev/flutter/widgets/FutureBuilder-class.html) and [`StreamBuilder`](https://api.flutter.dev/flutter/widgets/StreamBuilder-class.html) widgets are used to display the results of a Future in the UI. The following example uses a `FutureBuilder`:

dart

```
class HomePage extends StatelessWidget {
  const HomePage({super.key});
​
  final HomePageViewModel viewModel = const HomePageViewModel();
​
  @override
  Widget build(BuildContext context) {
    return CupertinoPageScaffold(
      // Feed a FutureBuilder to your widget tree.
      child: FutureBuilder<Weather>(
        // Specify the Future that you want to track.
        future: viewModel.load(),
        builder: (context, snapshot) {
          // A snapshot is of type `AsyncSnapshot` and contains the
          // state of the Future. By looking if the snapshot contains
          // an error or if the data is null, you can decide what to
          // show to the user.
          if (snapshot.hasData) {
            return Center(child: Text(snapshot.data.toString()));
          } else {
            return const Center(child: CupertinoActivityIndicator());
          }
        },
      ),
    );
  }
}
```

For the complete example, check out the [async\_weather](https://github.com/flutter/website/blob/main/examples/resources/dart_swift_concurrency/lib/async_weather.dart) file on GitHub.

### Leveraging a background thread/isolate

[#](#leveraging-a-background-threadisolate)

Flutter apps can run on a variety of multi-core hardware, including devices running macOS and iOS. To improve the performance of these applications, you must sometimes run tasks on different cores concurrently. This is especially important to avoid blocking UI rendering with long-running operations.

In Swift, you can leverage GCD to run tasks on global queues with different quality of service class (qos) properties. This indicates the task's priority.

swift

```
func parse(string: String, completion: @escaping ([String:Any]) -> Void) {
  // Mimic 1 sec delay.
  DispatchQueue(label: "data_processing_queue", qos: .userInitiated)
    .asyncAfter(deadline: .now() + 1) {
      let result: [String:Any] = ["foo": 123]
      completion(result)
    }
  }
}
```

In Dart, you can offload computation to a worker isolate, often called a background worker. A common scenario spawns a simple worker isolate and returns the results in a message when the worker exits. You can use `Isolate.run()` to spawn an isolate and run computations:

dart

```
void main() async {
  // Read some data.
  final jsonData = await Isolate.run(() => jsonDecode(jsonString) as Map<String, dynamic>);`
​
  // Use that data.
  print('Number of JSON keys: ${jsonData.length}');
}
```

In Flutter, you can also use the `compute` function to spin up an isolate to run a callback function:

dart

```
final jsonData = await compute(getNumberOfKeys, jsonString);
```

In this case, the callback function is a top-level function as shown below:

dart

```
Map<String, dynamic> getNumberOfKeys(String jsonString) {
 return jsonDecode(jsonString);
}
```

You can find more information on Dart at [Learning Dart as a Swift developer](https://dart.dev/guides/language/coming-from/swift-to-dart), and more information on Flutter at [Flutter for SwiftUI developers](/flutter-for/swiftui-devs) or [Flutter for UIKit developers](/flutter-for/uikit-devs).

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-09-02. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/flutter-for/dart-swift-concurrency.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/flutter-for/dart-swift-concurrency\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/flutter-for/dart-swift-concurrency.md "Report an issue with this page").

----
url: https://docs.flutter.dev/brand
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Flutter Brand Guidelines](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

The "Flutter" name and logo are trademarks owned by Google. These Brand Guidelines describe the appropriate uses of the Flutter trademarks by members of the developer community who have obtained our consent to use the trademarks pursuant to the [Flutter Terms of Service](/tos). These guidelines will ensure that the Flutter trademarks are used in a manner that promotes Google's mission to provide a free and open source SDK for crafting high-quality native interfaces on iOS and Android in record time, and are not associated with objectionable material, as determined by Google.

Use of the Flutter trademarks that is not expressly permitted by these guidelines is prohibited absent written permission from Google.

The official Flutter assets and further guidelines on representing the brand can be found at [Representing the Flutter Brand](https://flutter.dev/brand).

## General Rules That Govern the Use of the Flutter Trademarks

[#](#general-rules-that-govern-the-use-of-the-flutter-trademarks)

You are free to use the Flutter trademarks: (i) in connection with your download and use of the Flutter SDK to build and develop apps, (ii) in training materials (e.g., video tutorials, online publications, etc.) that provide instructions or tips regarding how to use the Flutter SDK to build and develop apps, and (iii) to show your support for the use of the Flutter SDK by members of the developer community.

These guidelines do not restrict your right to use the "Flutter" name in connection with descriptions of the Flutter SDK that would be considered "fair use." For example, you may use the "Flutter" name to make truthful factual statements (e.g., "built with the Flutter SDK") or to accurately describe a feature of the Flutter SDK.

You may use the Flutter trademarks on your personal website, personal blog, or social media account to show your support for the Flutter SDK, provided you do not use the Flutter trademarks in a way that could confuse people into thinking that your site is an official Google site or that Google has sponsored or endorsed your site. In the case of websites or personal blogs, this means you should not use the Flutter trademarks as the primary element on the webpage (e.g., in the masthead of the webpage or the title of the blog). In the case of social media accounts, this means you should not use the Flutter trademarks in the background, in your profile image or in your social media username.

## Specific Rules for Proper Usage of the Flutter Trademarks

[#](#specific-rules-for-proper-usage-of-the-flutter-trademarks)

In addition to the general rules discussed above, below are specific rules governing the proper use of the Flutter trademarks.

**DO:**

* Use the "Flutter" name as an adjective, never as a noun or verb, and never in the plural or possessive form.
* Use a generic term following the "Flutter" name, for example, "the Flutter SDK" or "the Flutter UI toolkit."
* Distinguish the "Flutter" name from the surrounding text in some way. Capitalize the first letter, capitalize or italicize the entire mark, place the mark in quotes, use a different type style or font for the mark.
* Use the trademark symbol TM for the first or most prominent time the "Flutter" name appears in text on your website or blog. Make sure to always use the TM symbol, not the ® symbol.
* Include the following text near the first or most prominent use of the Flutter marks on your website or blog: "Flutter and the related logo are trademarks of Google LLC. We are not endorsed by or affiliated with Google LLC."
* Keep some distance between the Flutter trademarks and any other trademarks, logos, or icons that are displayed on the webpage.

**DON'T:**

* Don't alter, distort, or modify the Flutter trademarks in any way. This includes varying the spelling of the "Flutter" name, or displaying the Flutter logo with color variations or unapproved visual elements.
* Don't combine the Google name with the "Flutter" name to form a unitary brand (e.g., don't use the phrases "Google Flutter" or "Google's Flutter"). You may use the Google name in full text to accurately describe the Flutter SDK (e.g., "The Flutter SDK by Google").
* Don't register the Flutter trademarks or any trademarks, logos, or domain names that are confusingly similar to them.
* Don't incorporate the Flutter trademarks into your own product names, service names, trademarks, logos, or company names.
* Don't display the Flutter trademarks in a manner that is misleading, unfair, defamatory, infringing, libelous, disparaging, obscene or otherwise objectionable to Google.
* Don't use the Flutter trademarks on or in connection with the sale of any non-software goods or services (e.g., merchandise such as clothing, pens, and stickers).

## Community Use Exceptions

[#](#community-use-exceptions)

To allow for the use of the Flutter trademarks by the Flutter community, below are specific exceptions to the rules described above:

1. Local Flutter user groups may: (i) use the "Flutter" name as part of their social media username in the following format: "Flutter + \[name of country/city]" (e.g., "Flutter France"); and (ii) use the Flutter logo in the national colors of the country where the user group is based (e.g., for a user group based in France, the colors blue, white and red), provided the Flutter logo is otherwise unaltered. Such social media accounts should include a disclaimer that clarifies that it is not an official Google account.

2. You may use the Flutter trademarks as part of the name of a newsletter or related community content (e.g., Flutter training courses, Flutter community forums) whose purpose is to promote the use of the Flutter SDK by members of the developer community. Where the Flutter trademarks are displayed on a website as part of a community site name, you should use the trademark symbol TM after the most prominent appearance of the "Flutter" name and include the following text: "Flutter and the related logo are trademarks of Google LLC. We are not endorsed by or affiliated with Google LLC." Where the Flutter trademarks are displayed on a social media account as part of a community site name, you should include a disclaimer that clarifies that it is not an official Google account.

3. **\[Unofficial Flutter Events]** You may use the Flutter trademarks as part of the name of a community event (e.g. conference), but please make sure to include the following disclaimer on the event website in a prominent and easy-to-see spot: "Flutter and the related logo are trademarks of Google LLC. \[Title of event] is not affiliated with or otherwise sponsored by Google LLC."

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/brand/index.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/brand\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/brand/index.md "Report an issue with this page").

----
url: https://docs.flutter.dev/community/china.md
----

\# Using Flutter in China > How to use, access, and learn about Flutter in China. :::note 如果你正在中国的网络环境下配置 Flutter， 请参考 \[在中国网络环境下使用 Flutter]\[] 文档. ::: \[在中国网络环境下使用 Flutter]: https\://docs.flutter.cn/community/china To speed the download and installation of Flutter in China, consider using a \[mirror site]\[] or \_mirror\_. :::important Use mirror sites \_only\_ if you \_trust\_ the provider. The Flutter team can't verify their reliability or security. ::: \[mirror site]: https\://en.wikipedia.org/wiki/Mirror\_site ## Use a Flutter mirror site The \[China Flutter User Group]\[] (CFUG) maintains a Simplified Chinese Flutter website \[https\://flutter.cn]\(https\://flutter.cn) and a mirror. Other mirrors can be found at the \[end of this guide]\(#known-trusted-community-run-mirror-sites). ### Configure your machine to use a mirror site To install or use Flutter in China, use a trustworthy Flutter mirror. This requires setting two environment variables on your machine. \_All examples that follow presume that you are using the CFUG mirror.\_ To set your machine to use a mirror site: These steps require using PowerShell. 1. Open a new window in PowerShell to prepare to run shell commands. 1. Set \`PUB\_HOSTED\_URL\` to your mirror site. \`\`\`ps $ $env:PUB\_HOSTED\_URL="https\://pub.flutter-io.cn" \`\`\` 1. Set \`FLUTTER\_STORAGE\_BASE\_URL\` to your mirror site. \`\`\`ps $ $env:FLUTTER\_STORAGE\_BASE\_URL="https\://storage.flutter-io.cn" \`\`\` 1. Download the Flutter archive from your preferred mirror site. For CFUG, visit their \[Flutter SDK archive]\[], and download the SDK for your platform and architecture. 1. Create a folder where you can install Flutter. Then change into it. Consider a path like \`$env:USERPROFILE\dev\`. \`\`\`ps $ New-Item -Path "$env:USERPROFILE\dev" -ItemType Directory; cd "$env:USERPROFILE\dev" \`\`\` 1. Extract the SDK from the zip archive file. This example assumes you downloaded the Windows version of the Flutter SDK. You'll need to replace the path to the archive with the path to the archive file and version you downloaded. \`\`\`ps $ Expand-Archive .\flutter\_windows\_3.35.5-stable.zip \`\`\` 1. Add Flutter to your \`PATH\` environment variable. \`\`\`ps $ $env:PATH = $pwd.PATH + "\flutter\bin",$env:PATH -join ";" \`\`\` 1. Begin developing with Flutter. After following these steps, Flutter fetches packages and artifacts from \`flutter-io.cn\` in the current terminal window. To set these values permanently across terminals, follow the instructions on adding \[Flutter to your PATH]\[windows-path], also adding the \`PUB\_HOSTED\_URL\` and \`FLUTTER\_STORAGE\_BASE\_URL\` variables. {:.steps} \[windows-path]: /install/add-to-path#windows 1. Open a new window in your terminal to prepare to run shell commands. 1. Set \`PUB\_HOSTED\_URL\` to your mirror site. \`\`\`console $ export PUB\_HOSTED\_URL="https\://pub.flutter-io.cn" \`\`\` 1. Set \`FLUTTER\_STORAGE\_BASE\_URL\` to your mirror site. \`\`\`console $ export FLUTTER\_STORAGE\_BASE\_URL="https\://storage.flutter-io.cn" \`\`\` 1. Download the Flutter archive from your preferred mirror site. For CFUG, visit their \[Flutter SDK archive]\[], and download the SDK for your platform and architecture. 1. Create a folder where you can install Flutter. Then change into it. Consider a path like \`\~/dev\`. \`\`\`console $ mkdir \~/dev; cd \~/dev \`\`\` 1. Extract the SDK from the zip archive file. This example assumes you downloaded the macOS version of the Flutter SDK. You'll need to replace the path to the archive with the path to the archive file and version you downloaded. \`\`\`console $ unzip flutter\_macos\_3.35.5-stable.zip \`\`\` 1. Add Flutter to your \`PATH\` environment variable. \`\`\`console $ export PATH="$PWD/flutter/bin:$PATH" \`\`\` 1. Begin developing with Flutter. After following these steps, Flutter fetches packages and artifacts from \`flutter-io.cn\` in the current terminal window. To set these values permanently across terminals, follow the instructions on adding \[Flutter to your PATH]\[macos-path], also adding the \`PUB\_HOSTED\_URL\` and \`FLUTTER\_STORAGE\_BASE\_URL\` variables. {:.steps} \[macos-path]: /install/add-to-path#macos 1. Open a new window in your terminal to prepare to run shell commands. 1. Set \`PUB\_HOSTED\_URL\` to your mirror site. \`\`\`console $ export PUB\_HOSTED\_URL="https\://pub.flutter-io.cn" \`\`\` 1. Set \`FLUTTER\_STORAGE\_BASE\_URL\` to your mirror site. \`\`\`console $ export FLUTTER\_STORAGE\_BASE\_URL="https\://storage.flutter-io.cn" \`\`\` 1. Download the Flutter archive from your preferred mirror site. For CFUG, visit their \[Flutter SDK archive]\[], and download the SDK for your platform and architecture. 1. Create a folder where you can install Flutter. Then change into it. Consider a path like \`\~/dev\`. \`\`\`console $ mkdir \~/dev; cd \~/dev \`\`\` 1. Extract the SDK from the tar archive file. This example assumes you downloaded the Linux version of the Flutter SDK. You'll need to replace the path to the archive with the path to the archive file and version you downloaded. \`\`\`console $ tar -xf flutter\_linux\_3.35.5-stable.tar.xz \`\`\` 1. Add Flutter to your \`PATH\` environment variable. \`\`\`console $ export PATH="$PWD/flutter/bin:$PATH" \`\`\` 1. Begin developing with Flutter. After following these steps, Flutter fetches packages and artifacts from \`flutter-io.cn\` in the current terminal window. To set these values permanently across terminals, follow the instructions on adding \[Flutter to your PATH]\[linux-path], also adding the \`PUB\_HOSTED\_URL\` and \`FLUTTER\_STORAGE\_BASE\_URL\` variables. {:.steps} \[linux-path]: /install/add-to-path#linux \[Flutter SDK archive]: https\://docs.flutter.cn/install/archive/ ### Download Flutter archives based on a mirror site To download Flutter from the \[SDK archive]\[] from a mirror, replace \`storage.googleapis.com\` with the URL of your trusted mirror. Use your mirror site in the browser or in other applications like IDM or Thunder. This should improve download speed. \[SDK archive]: /install/archive The following example shows how to change the URL for Flutter's download site from Google's archive to CFUG's mirror. To download the x64, Windows version of the Flutter SDK, you would change the original URL from: \`\`\`plaintext \[!https\://storage.googleapis.com!]/flutter\_infra\_release/releases/stable/windows/flutter\_windows\_3.35.5-stable.zip \`\`\` to the mirror URL: \`\`\`plaintext \[!https\://storage.flutter-io.cn!]/flutter\_infra\_release/releases/stable/windows/flutter\_windows\_3.35.5-stable.zip \`\`\` To download the arm64, macOS version of the Flutter SDK, you would change the original URL from: \`\`\`plaintext \[!https\://storage.googleapis.com!]/flutter\_infra\_release/releases/stable/macos/flutter\_macos\_arm64\_3.35.5-stable.zip \`\`\` to the mirror URL: \`\`\`plaintext \[!https\://storage.flutter-io.cn!]/flutter\_infra\_release/releases/stable/macos/flutter\_macos\_arm64\_3.35.5-stable.zip \`\`\` To download the Linux version of the Flutter SDK, you would change the original URL from: \`\`\`plaintext \[!https\://storage.googleapis.com!]/flutter\_infra\_release/releases/stable/linux/flutter\_linux\_3.35.5-stable.tar.xz \`\`\` to the mirror URL: \`\`\`plaintext \[!https\://storage.flutter-io.cn!]/flutter\_infra\_release/releases/stable/linux/flutter\_linux\_3.35.5-stable.tar.xz \`\`\` :::note Not every mirror supports downloading artifacts using their direct URL. ::: ## Configure your machine to publish your package To publish your packages to \`pub.dev\`, you need to be able to access both Google Auth and the \`pub.dev\` site. To enable access to \`pub.dev\`: 1. Configure a proxy. To configure a proxy, check out the \[Dart documentation on proxies]\[]. 1. Verify that your \`PUB\_HOSTED\_URL\` environment variable is either unset or empty. \`\`\`ps $ echo $env:PUB\_HOSTED\_URL \`\`\` If this command returns any value, unset it. \`\`\`ps $ Remove-Item $env:PUB\_HOSTED\_URL \`\`\` 1. Configure a proxy. To configure a proxy, check out the \[Dart documentation on proxies]\[]. 1. Verify that your \`PUB\_HOSTED\_URL\` environment variable is either unset or empty. \`\`\`console $ echo $PUB\_HOSTED\_URL \`\`\` If this command returns any value, unset it. \`\`\`console $ unset $PUB\_HOSTED\_URL \`\`\` 1. Configure a proxy. To configure a proxy, check out the \[Dart documentation on proxies]\[]. 1. Verify that your \`PUB\_HOSTED\_URL\` environment variable is either unset or empty. \`\`\`console $ echo $PUB\_HOSTED\_URL \`\`\` If this command returns any value, unset it. \`\`\`console $ unset $PUB\_HOSTED\_URL \`\`\` To learn more about publishing packages, check out the \[Dart documentation on publishing packages]\[]. \[Dart documentation on proxies]: https\://dart.dev/tools/pub/troubleshoot#pub-get-fails-from-behind-a-corporate-firewall \[Dart documentation on publishing packages]: https\://dart.dev/tools/pub/publishing ## Known, trusted community-run mirror sites The Flutter team can't guarantee the long-term availability of any mirrors. You can use other mirrors if they become available.

***

\### China Flutter User Group \[China Flutter User Group]\[] maintains the \`flutter-io.cn\` mirror. It includes the Flutter SDK and pub packages. #### Configure your machine to use this mirror To set your machine to use this mirror, use these commands. On macOS, Linux, or ChromeOS: \`\`\`console export PUB\_HOSTED\_URL=https\://pub.flutter-io.cn export FLUTTER\_STORAGE\_BASE\_URL=https\://storage.flutter-io.cn \`\`\` On Windows: \`\`\`console $env:PUB\_HOSTED\_URL="https\://pub.flutter-io.cn" $env:FLUTTER\_STORAGE\_BASE\_URL="https\://storage.flutter-io.cn" \`\`\` #### Get support for this mirror If you're running into issues that only occur when using the \`flutter-io.cn\` mirror, report the issue to their \[issue tracker]\(https\://github.com/cfug/flutter.cn/issues/new/choose). \[China Flutter User Group]: https\://github.com/cfug

***

\### Shanghai Jiao Tong University \*nix User Group \[Shanghai Jiao Tong University \*nix User Group]\[] maintains the \`mirror.sjtu.edu.cn\` mirror. It includes the Flutter SDK and pub packages. #### Configure your machine to use this mirror To set your machine to use this mirror, use these commands. On macOS, Linux, or ChromeOS: \`\`\`console export PUB\_HOSTED\_URL=https\://mirror.sjtu.edu.cn/dart-pub export FLUTTER\_STORAGE\_BASE\_URL=https\://mirror.sjtu.edu.cn \`\`\` On Windows: \`\`\`console $env:PUB\_HOSTED\_URL="https\://mirror.sjtu.edu.cn/dart-pub" $env:FLUTTER\_STORAGE\_BASE\_URL="https\://mirror.sjtu.edu.cn" \`\`\` #### Get support for this mirror If you're running into issues that only occur when using the \`mirror.sjtu.edu.cn\` mirror, report the issue to their \[issue tracker]\(https\://github.com/sjtug/mirror-requests). \[Shanghai Jiao Tong University \*nix User Group]: https\://github.com/sjtug

***

\### Tsinghua University TUNA Association \[Tsinghua University TUNA Association]\[] maintains the \`mirrors.tuna.tsinghua.edu.cn\` mirror. It includes the Flutter SDK and pub packages. #### Configure your machine to use this mirror To set your machine to use this mirror, use these commands. On macOS, Linux, or ChromeOS: \`\`\`console export PUB\_HOSTED\_URL=https\://mirrors.tuna.tsinghua.edu.cn/dart-pub export FLUTTER\_STORAGE\_BASE\_URL=https\://mirrors.tuna.tsinghua.edu.cn/flutter \`\`\` On Windows: \`\`\`console $env:PUB\_HOSTED\_URL="https\://mirrors.tuna.tsinghua.edu.cn/dart-pub" $env:FLUTTER\_STORAGE\_BASE\_URL="https\://mirrors.tuna.tsinghua.edu.cn/flutter" \`\`\` #### Get support for this mirror If you're running into issues that only occur when using the \`mirrors.tuna.tsinghua.edu.cn\` mirror, report the issue to their \[issue tracker]\(https\://github.com/tuna/issues). \[Tsinghua University TUNA Association]: https\://tuna.moe ## Offer to host a new mirror site If you're interested in setting up your own mirror, contact \[flutter-dev\@googlegroups.com]\(mailto:flutter-dev\@googlegroups.com) for assistance.

----
url: https://docs.flutter.dev/perf/rendering-performance.md
----

\# Improving rendering performance > How to measure and evaluate your app's rendering performance. :::note To learn how to use the \*\*Performance View\*\* (part of Flutter DevTools) for debugging performance issues, see \[Using the Performance view]\[]. ::: \[Using the Performance view]: /tools/devtools/performance Rendering animations in your app is one of the most cited topics of interest when it comes to measuring performance. Thanks in part to Flutter's Skia engine and its ability to quickly create and dispose of widgets, Flutter applications are performant by default, so you only need to avoid common pitfalls to achieve excellent performance. ## General advice If you see janky (non-smooth) animations, make \*\*sure\*\* that you are profiling performance with an app built in \_profile\_ mode. The default Flutter build creates an app in \_debug\_ mode, which is not indicative of release performance. For information, see \[Flutter's build modes]\[]. A couple common pitfalls: \* Rebuilding far more of the UI than expected each frame. To track widget rebuilds, see \[Show performance data]\[]. \* Building a large list of children directly, rather than using a ListView. For more information on evaluating performance including information on common pitfalls, see the following docs: \* \[Performance best practices]\[] \* \[Flutter performance profiling]\[] ## Mobile-only advice Do you see noticeable jank on your mobile app, but only on the first run of an animation? To avoid this, make sure you're using Flutter's default graphic renderer, \[Impeller]\[]. \[Impeller]: /perf/impeller ## Web-only advice The following series of articles cover what the Flutter Material team learned when improving performance of the Flutter Gallery app on the web: \* \[Optimizing performance in Flutter web apps with tree shaking and deferred loading]\[shaking] \* \[Improving perceived performance with image placeholders, precaching, and disabled navigation transitions]\[images] \* \[Building performant Flutter widgets]\[] \[Building performant Flutter widgets]: https\://blog.flutter.dev/building-performant-flutter-widgets-3b2558aa08fa \[Flutter's build modes]: /testing/build-modes \[Flutter performance profiling]: /perf/ui-performance \[images]: https\://blog.flutter.dev/improving-perceived-performance-with-image-placeholders-precaching-and-disabled-navigation-6b3601087a2b \[Performance best practices]: /perf/best-practices \[shaking]: https\://blog.flutter.dev/optimizing-performance-in-flutter-web-apps-with-tree-shaking-and-deferred-loading-535fbe3cd674 \[Show performance data]: /tools/android-studio#show-performance-data

----
url: https://docs.flutter.dev/tools/hot-reload.md
----

\# Hot reload > Speed up development using Flutter's hot reload feature.

<!--?code-excerpt path-base="tools"?-->

Flutter's hot reload feature helps you quickly and easily experiment, build UIs, add features, and fix bugs. Hot reload works by injecting updated source code files into the \[Dart runtime]\[]. After the Dart runtime updates classes with the new versions of fields and functions, the Flutter framework automatically rebuilds the widget tree, allowing you to quickly view the effects of your changes. !\[Hot reload GIF]\(/assets/images/docs/tools/hot-reload.gif){:width="100%"}\
A demo of hot reload in DartPad ## How to perform a hot reload If you are using an AI coding assistant like Google Antigravity, the assistant can automatically hot reload your running application after making changes, or when asked to "Reload the app". To learn more, check out \[Get started with AI]\(/ai/get-started). To hot reload a Flutter app manually: 1. Run the app from a supported \[Flutter editor]\[] or a terminal window. Either a physical or virtual device can be the target. \*\*Only Flutter apps in debug mode can be hot reloaded or hot restarted.\*\* 1. Modify one of the Dart files in your project. Most types of code changes can be hot reloaded; for a list of changes that require a hot restart, see \[Special cases]\(#special-cases). 1. If you're working in an IDE/editor that supports Flutter's IDE tools and hot reload on save is enabled, select \*\*Save All\*\* (\`cmd-s\`/\`ctrl-s\`), or click the hot reload button on the toolbar. []():::tip To enable hot reload on save From your preferred IDE, enable autosave and hot reloads on save. \*\*VS Code\*\* Add the following to your \`.vscode/settings.json\` file: \`\`\`json "files.autoSave": "afterDelay", "dart.flutterHotReloadOnSave": "all", \`\`\` \*\*Android Studio and IntelliJ\*\* \* Open \`Settings > Tools > Actions on Save\` and select \`Configure autosave options\`. - Check the option to \`Save files if the IDE is idle for X seconds\`. - \*\*Recommended:\*\* Set a small delay duration. For example, 2 seconds. \* Open \`Settings > Languages & Frameworks > Flutter\`. - Check the option to \`Perform hot reload on save\`. ::: If you're running the app at the command line using \`flutter run\`, enter \`r\` in the terminal window. After a successful hot reload operation, you'll see a message in the console similar to: \`\`\`console Performing hot reload... Reloaded 1 of 448 libraries in 978ms. \`\`\` The app updates to reflect your change, and the current state of the app is preserved. Your app continues to execute from where it was prior to run the hot reload command. The code updates and execution continues. []():::secondary \*\*What is the difference between hot reload, hot restart, and full restart?\*\* \* \*\*Hot reload\*\* loads code changes into the VM or the browser, and re-builds the widget tree, preserving the app state; it doesn't rerun \`main()\` or \`initState()\`. (\`⌘\\\` in IntelliJ and Android Studio, \`⌃F5\` in VSCode) \* \*\*Hot restart\*\* loads code changes into the VM or the browser, and restarts the Flutter app, losing the app state. On the web, this can restart the app without a full page refresh. (\`⇧⌘\\\` in IntelliJ and Android Studio, \`⇧⌘F5\` in VSCode) \* \*\*Full restart\*\* restarts the iOS, Android, or web app. This takes longer because it also recompiles the Java / Kotlin / Objective-C / Swift / JavaScript code. On the web, it also restarts the Dart Development Compiler. There is no specific keyboard shortcut for this; you need to stop and start the run configuration. Flutter web now supports hot restart and \[hot reload]\[]. ::: \[hot reload]: /platform-integration/web/building#hot-reload-web !\[Android Studio UI]\(/assets/images/docs/development/tools/android-studio-run-controls.png){:width="100%"}\
Controls for run, run debug, hot reload, and hot restart in Android Studio A code change has a visible effect only if the modified Dart code is run again after the change. Specifically, a hot reload causes all the existing widgets to rebuild. Only code involved in the rebuilding of the widgets is automatically re-executed. The \`main()\` and \`initState()\` functions, for example, are not run again. ## Special cases The next sections describe specific scenarios that involve hot reload. In some cases, small changes to the Dart code enable you to continue using hot reload for your app. In other cases, a hot restart, or a full restart is needed. ### An app is killed Hot reload can break when the app is killed. For example, if the app was in the background for too long. ### Compilation errors When a code change introduces a compilation error, hot reload generates an error message similar to: \`\`\`plaintext Hot reload was rejected: '/path/to/project/lib/main.dart': warning: line 16 pos 38: unbalanced '{' opens here Widget build(BuildContext context) { ^ '/path/to/project/lib/main.dart': error: line 33 pos 5: unbalanced ')' ); ^ \`\`\` In this situation, simply correct the errors on the specified lines of Dart code to keep using hot reload. ### CupertinoTabView's builder Hot reload won't apply changes made to a \`builder\` of a \`CupertinoTabView\`. For more information, see \[Issue 43574]\[]. ### Enumerated types Hot reload doesn't work when enumerated types are changed to regular classes or regular classes are changed to enumerated types. For example: Before the change:

<!--?code-excerpt "lib/hot-reload/before.dart (enum)"?-->

\`\`\`dart enum Color { red, green, blue } \`\`\` After the change:

<!--?code-excerpt "lib/hot-reload/after.dart (enum)"?-->

\`\`\`dart class Color { Color(this.i, this.j); final int i; final int j; } \`\`\` ### Generic types Hot reload won't work when generic type declarations are modified. For example, the following won't work: Before the change:

<!--?code-excerpt "lib/hot-reload/before.dart (class)"?-->

\`\`\`dart class A { T? i; } \`\`\` After the change:

<!--?code-excerpt "lib/hot-reload/after.dart (class)"?-->

\`\`\`dart class A { T? i; V? v; } \`\`\` ### Native code If you've changed native code (such as Kotlin, Java, Swift, or Objective-C), you must perform a full restart (stop and restart the app) to see the changes take effect. ### Previous state is combined with new code Flutter's stateful hot reload preserves the state of your app. This approach enables you to view the effect of the most recent change only, without throwing away the current state. For example, if your app requires a user to log in, you can modify and hot reload a page several levels down in the navigation hierarchy, without re-entering your login credentials. State is kept, which is usually the desired behavior. If code changes affect the state of your app (or its dependencies), the data your app has to work with might not be fully consistent with the data it would have if it executed from scratch. The result might be different behavior after a hot reload versus a hot restart. ### Recent code change is included but app state is excluded In Dart, \[static fields are lazily initialized]\[static-variables]. This means that the first time you run a Flutter app and a static field is read, it's set to whatever value its initializer was evaluated to. Global variables and static fields are treated as state, and are therefore not reinitialized during hot reload. If you change initializers of global variables and static fields, a hot restart or restart the state where the initializers are hold is necessary to see the changes. For example, consider the following code:

<!--?code-excerpt "lib/hot-reload/before.dart (sample-table)"?-->

\`\`\`dart final sampleTable = \[ Table( children: const \[ TableRow(children: \[Text('T1')]), ], ), Table( children: const \[ TableRow(children: \[Text('T2')]), ], ), Table( children: const \[ TableRow(children: \[Text('T3')]), ], ), Table( children: const \[ TableRow(children: \[Text('T4')]), ], ), ]; \`\`\` After running the app, you make the following change:

<!--?code-excerpt "lib/hot-reload/after.dart (sample-table)"?-->

\`\`\`dart final sampleTable = \[ Table( children: const \[ TableRow(children: \[Text('T1')]), ], ), Table( children: const \[ TableRow(children: \[Text('T2')]), ], ), Table( children: const \[ TableRow(children: \[Text('T3')]), ], ), Table( children: const \[ TableRow( children: \[Text('T10')], // modified ), ], ), ]; \`\`\` You hot reload, but the change is not reflected. Conversely, in the following example:

<!--?code-excerpt "lib/hot-reload/before.dart (const)"?-->

\`\`\`dart const foo = 1; final bar = foo; void onClick() { print(foo); print(bar); } \`\`\` Running the app for the first time prints \`1\` and \`1\`. Then, you make the following change:

<!--?code-excerpt "lib/hot-reload/after.dart (const)"?-->

\`\`\`dart const foo = 2; // modified final bar = foo; void onClick() { print(foo); print(bar); } \`\`\` While changes to \`const\` field values are always hot reloaded, the static field initializer is not rerun. Conceptually, \`const\` fields are treated like aliases instead of state. The Dart VM detects initializer changes and flags when a set of changes needs a hot restart to take effect. The flagging mechanism is triggered for most of the initialization work in the above example, but not for cases like the following:

<!--?code-excerpt "lib/hot-reload/after.dart (final-foo)"?-->

\`\`\`dart final bar = foo; \`\`\` To update \`foo\` and view the change after hot reload, consider redefining the field as \`const\` or using a getter to return the value, rather than using \`final\`. For example, either of the following solutions work:

<!--?code-excerpt "lib/hot-reload/foo_const.dart (const)"?-->

\`\`\`dart const foo = 1; const bar = foo; // Convert foo to a const... void onClick() { print(foo); print(bar); } \`\`\`

<!--?code-excerpt "lib/hot-reload/getter.dart (const)"?-->

\`\`\`dart const foo = 1; int get bar => foo; // ...or provide a getter. void onClick() { print(foo); print(bar); } \`\`\` For more information, read about the \[differences between the \`const\` and \`final\` keywords]\[const-new] in Dart. ### Recent UI change is excluded Even when a hot reload operation appears successful and generates no exceptions, some code changes might not be visible in the refreshed UI. This behavior is common after changes to the app's \`main()\` or \`initState()\` methods. As a general rule, if the modified code is downstream of the root widget's \`build()\` method, then hot reload behaves as expected. However, if the modified code won't be re-executed as a result of rebuilding the widget tree, then you won't see its effects after hot reload. For example, consider the following code:

<!--?code-excerpt "lib/hot-reload/before.dart (build)"?-->

\`\`\`dart import 'package:material\_ui/material\_ui.dart'; void main() { runApp(MyApp()); } class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return GestureDetector(onTap: () => print('tapped')); } } \`\`\` After running this app, change the code as follows:

<!--?code-excerpt "lib/hot-reload/after.dart (main)"?-->

\`\`\`dart import 'package:flutter/widgets.dart'; import 'package:material\_ui/material\_ui.dart'; void main() { runApp(const Center(child: Text('Hello', textDirection: TextDirection.ltr))); } \`\`\` With a hot restart, the program starts from the beginning, executes the new version of \`main()\`, and builds a widget tree that displays the text \`Hello\`. However, if you hot reload the app after this change, \`main()\` and \`initState()\` are not re-executed, and the widget tree is rebuilt with the unchanged instance of \`MyApp\` as the root widget. This results in no visible change after hot reload. ## How it works When hot reload is invoked, the host machine looks at the edited code since the last compilation. The following libraries are recompiled: \* Any libraries with changed code \* The application's main library \* The libraries from the main library leading to affected libraries The source code from those libraries is compiled into \[kernel files]\[] and sent to the mobile device's Dart VM. The Dart VM re-loads all libraries from the new kernel file. So far no code is re-executed. The hot reload mechanism then causes the Flutter framework to trigger a rebuild/re-layout/repaint of all existing widgets and render objects. \[static-variables]: https\://dart.dev/language/classes#static-variables \[const-new]: https\://dart.dev/language/variables#final-and-const \[Dart runtime]: https\://dart.dev/overview#platform \[Flutter editor]: /tools/editors \[Issue 43574]: https\://github.com/flutter/flutter/issues/43574 \[kernel files]: https\://github.com/dart-lang/sdk/tree/main/pkg/kernel

----
url: https://docs.flutter.dev/ui/layout/scrolling.md
----

\# Scrolling > Overview of Flutter's scrolling support Flutter has many built-in widgets that automatically scroll and also offers a variety of widgets that you can customize to create specific scrolling behavior. ## Basic scrolling Many Flutter widgets support scrolling out of the box and do most of the work for you. For example, \[\`SingleChildScrollView\`]\[] automatically scrolls its child when necessary. Other useful widgets include \[\`ListView\`]\[] and \[\`GridView\`]\[]. You can check out more of these widgets on the \[scrolling page]\[] of the Widget catalog. ### Infinite scrolling When you have a long list of items in your \`ListView\` or \`GridView\` (including an \_infinite\_ list), you can build the items on demand as they scroll into view. This provides a much more performant scrolling experience. For more information, check out \[\`ListView\.builder\`]\[] or \[\`GridView\.builder\`]\[]. \[\`ListView\.builder\`]: https\://api.flutter.dev/flutter/widgets/ListView/ListView\.builder.html \[\`GridView\.builder\`]: https\://api.flutter.dev/flutter/widgets/GridView/GridView\.builder.html ### Specialized scrollable widgets The following widgets provide more specific scrolling behavior. A video on using \[\`DraggableScrollableSheet\`]\[]: Turn the scrollable area into a wheel with \[\`ListWheelScrollView\`]\[]! \[\`DraggableScrollableSheet\`]: https\://api.flutter.dev/flutter/widgets/DraggableScrollableSheet-class.html \[\`GridView\`]: https\://api.flutter.dev/flutter/widgets/GridView-class.html \[\`ListView\`]: https\://api.flutter.dev/flutter/widgets/ListView-class.html \[\`ListWheelScrollView\`]: https\://api.flutter.dev/flutter/widgets/ListWheelScrollView-class.html \[scrolling page]: /ui/widgets/scrolling \[\`SingleChildScrollView\`]: https\://api.flutter.dev/flutter/widgets/SingleChildScrollView-class.html ## Fancy scrolling Perhaps you want to implement \_elastic\_ scrolling, also called \_scroll bouncing\_. Or maybe you want to implement other dynamic scrolling effects, like parallax scrolling. Or perhaps you want a scrolling header with very specific behavior, such as shrinking or disappearing. You can achieve all this and more using the Flutter \`Sliver\*\` classes. A \_sliver\_ refers to a piece of the scrollable area. You can define and insert a sliver into a \[\`CustomScrollView\`]\[] to have finer-grained control over that area. For more information, check out \[Using slivers to achieve fancy scrolling]\[] and the \[Sliver classes]\[]. \[\`CustomScrollView\`]: https\://api.flutter.dev/flutter/widgets/CustomScrollView-class.html \[Sliver classes]: /ui/widgets/layout#sliver-widgets \[Using slivers to achieve fancy scrolling]: /ui/layout/scrolling/slivers ## Nested scrolling widgets How do you nest a scrolling widget inside another scrolling widget without hurting scrolling performance? Do you set the \`ShrinkWrap\` property to true, or do you use a sliver? Check out the "ShrinkWrap vs Slivers" video:

----
url: https://docs.flutter.dev/packages-and-plugins/swift-package-manager/for-plugin-authors.md
----

\# Swift Package Manager for plugin authors > How to add Swift Package Manager compatibility to iOS and macOS plugins :::note As of the 3.44 release, Flutter uses \[Swift Package Manager]\[] to manage iOS and macOS native dependencies. Flutter continues to support CocoaPods in maintenance mode, however, the CocoaPods registry permanently becomes \[read-only on December 2, 2026]\[cocoapods]. ::: \[cocoapods]: https\://blog.cocoapods.org/CocoaPods-Specs-Repo/ \[Swift Package Manager]: https\://www\.swift.org/documentation/package-manager/ For information on how to turn SwiftPM off and on, visit \[Swift Package Manager for app developers]\[for-app-devs]. \[for-app-devs]: /packages-and-plugins/swift-package-manager/for-app-developers ## How to add Swift Package Manager support to an existing Flutter plugin This guide shows how to add Swift Package Manager support to a plugin that already supports CocoaPods. This ensures the plugin is usable by all Flutter projects. Flutter plugins should support \_both\_ Swift Package Manager and CocoaPods until further notice. Swift Package Manager adoption will be gradual. As of Flutter 3.44, plugins that don't support CocoaPods aren't usable by projects that haven't migrated to Swift Package Manager yet. Plugins that don't support Swift Package Manager can cause problems for projects that have migrated. Please migrate your plugins as soon as possible. Replace \`plugin\_name\` throughout this guide with the name of your plugin. The example below uses \`ios\`, replace \`ios\` with \`macos\` or \`darwin\`, as applicable. 1. Ensure that you are using Flutter 3.44 or later. This enables SwiftPM by default. 1. Start by creating a directory under the \`ios\`, \`macos\`, and/or \`darwin\` directories. Name this new directory the name of the platform package. - plugin\_name/ - ios/ - ... - \*\*plugin\_name/\*\* 1. Within this new directory, create the following files/directories: - \`Package.swift\` (file) - \`Sources\` (directory) - \`Sources/plugin\_name\` (directory) Your plugin should look like: - plugin\_name/ - ios/ - ... - plugin\_name/ - \*\*Package.swift\*\* - \*\*Sources/\*\* - \*\*plugin\_name/\*\* 1. Use the following template in the \`Package.swift\` file: \`\`\`swift title="Package.swift" // swift-tools-version: 5.9 // The swift-tools-version declares the minimum version of Swift required to build this package. import PackageDescription let package = Package( // TODO: Update your plugin name. name: "plugin\_name", platforms: \[ // TODO: Update the platforms your plugin supports. // If your plugin only supports iOS, remove \`.macOS(...)\`. // If your plugin only supports macOS, remove \`.iOS(...)\`. .iOS("13.0"), .macOS("10.15") ], products: \[ // TODO: Update your library and target names. // If the plugin name contains "\_", replace with "-" for the library name. .library(name: "plugin-name", targets: \["plugin\_name"]) ], dependencies: \[ .package(name: "FlutterFramework", path: "../FlutterFramework") ], targets: \[ .target( // TODO: Update your target name. name: "plugin\_name", dependencies: \[ .product(name: "FlutterFramework", package: "FlutterFramework") ], resources: \[ // TODO: If your plugin requires a privacy manifest // (e.g. if it uses any required reason APIs), update the PrivacyInfo.xcprivacy file // to describe your plugin's privacy impact, and then uncomment this line. // For more information, visit: // https\://developer.apple.com/documentation/bundleresources/privacy\_manifest\_files // .process("PrivacyInfo.xcprivacy"), // TODO: If you have other resources that need to be bundled with your plugin, refer to // the following instructions to add them: // https\://developer.apple.com/documentation/xcode/bundling-resources-with-a-swift-package ] ) ] ) \`\`\` 1. Update the \[supported platforms]\[] in your \`Package.swift\` file. \`\`\`swift title="Package.swift" platforms: \[ // TODO: Update the platforms your plugin supports. // If your plugin only supports iOS, remove \`.macOS(...)\`. // If your plugin only supports macOS, remove \`.iOS(...)\`. \[!.iOS("13.0"),!] \[!.macOS("10.15")!] ], \`\`\` \[supported platforms]: https\://developer.apple.com/documentation/packagedescription/supportedplatform 1. Update the package, library, and target names in your \`Package.swift\` file. \`\`\`swift title="Package.swift" let package = Package( // TODO: Update your plugin name. name: \[!"plugin\_name"!], platforms: \[ .iOS("13.0"), .macOS("10.15") ], products: \[ // TODO: Update your library and target names. // If the plugin name contains "\_", replace with "-" for the library name .library(name: \[!"plugin-name"!], targets: \[\[!"plugin\_name"!]]) ], dependencies: \[ .package(name: "FlutterFramework", path: "../FlutterFramework") ], targets: \[ .target( // TODO: Update your target name. name: \[!"plugin\_name"!], dependencies: \[ .product(name: "FlutterFramework", package: "FlutterFramework") ], resources: \[ // TODO: If your plugin requires a privacy manifest // (e.g. if it uses any required reason APIs), update the PrivacyInfo.xcprivacy file // to describe your plugin's privacy impact, and then uncomment this line. // For more information, visit: // https\://developer.apple.com/documentation/bundleresources/privacy\_manifest\_files // .process("PrivacyInfo.xcprivacy"), // TODO: If you have other resources that need to be bundled with your plugin, refer to // the following instructions to add them: // https\://developer.apple.com/documentation/xcode/bundling-resources-with-a-swift-package ] ) ] ) \`\`\` :::note If the plugin name contains \`\_\`, the library name must be a \`-\` separated version of the plugin name. ::: 1. If your plugin has a \[\`PrivacyInfo.xcprivacy\` file]\[], move it to \`ios/plugin\_name/Sources/plugin\_name/PrivacyInfo.xcprivacy\` and uncomment the resource in the \`Package.swift\` file. \`\`\`swift title="Package.swift" resources: \[ // TODO: If your plugin requires a privacy manifest // (e.g. if it uses any required reason APIs), update the PrivacyInfo.xcprivacy file // to describe your plugin's privacy impact, and then uncomment this line. // For more information, visit: // https\://developer.apple.com/documentation/bundleresources/privacy\_manifest\_files \[!.process("PrivacyInfo.xcprivacy"),!] // TODO: If you have other resources that need to be bundled with your plugin, refer to // the following instructions to add them: // https\://developer.apple.com/documentation/xcode/bundling-resources-with-a-swift-package ], \`\`\` 1. Move any resource files from \`ios/Assets\` to \`ios/plugin\_name/Sources/plugin\_name\` (or a subdirectory). Add the resource files to your \`Package.swift\` file, if applicable. For more instructions, visit \[Bundling resources with a Swift package]\[]. 1. Move all files from \`ios/Classes\` to \`ios/plugin\_name/Sources/plugin\_name\`. 1. Add the \`FlutterFramework\` as a dependency. Update \`Package.swift\` to include \`FlutterFramework\`: \`\`\`swift title="Package.swift" dependencies: \[ \[!.package(name: "FlutterFramework", path: "../FlutterFramework")!] ], targets: \[ .target( // TODO: Update your target name. name: "plugin\_name", dependencies: \[ \[!.product(name: "FlutterFramework", package: "FlutterFramework")!] ], ) ] \`\`\` 1. The \`ios/Assets\`, \`ios/Resources\`, and \`ios/Classes\` directories should now be empty and can be deleted. 1. If your plugin uses \[Pigeon]\[], update your Pigeon input file. \`\`\`dart title="pigeons/messages.dart" diff kotlinOptions: KotlinOptions(), javaOut: 'android/app/src/main/java/io/flutter/plugins/Messages.java', javaOptions: JavaOptions(), - swiftOut: 'ios/Classes/messages.g.swift', + swiftOut: 'ios/plugin\_name/Sources/plugin\_name/messages.g.swift', swiftOptions: SwiftOptions(), \`\`\` 1. Update your \`Package.swift\` file with any customizations you might need. 1. In Xcode, open the \`ios/plugin\_name/\` directory. 1. In Xcode, open your \`Package.swift\` file. Verify Xcode doesn't produce any warnings or errors for this file. :::tip If Xcode doesn't show any files, quit Xcode (\*\*Xcode > Quit Xcode\*\*) and reopen. If Xcode doesn't update after you make a change, try clicking \*\*File > Packages > Reset Package Caches\*\*. ::: 1. If your \`ios/plugin\_name.podspec\` file has \[CocoaPods \`dependency\`]\[]s, add the corresponding \[Swift Package Manager dependencies]\[] to your \`Package.swift\` file. 1. If your package must be linked explicitly \`static\` or \`dynamic\` (\[not recommended by Apple]\[]), update the \[Product]\[] to define the type: \`\`\`swift title="Package.swift" products: \[ .library(name: "plugin-name", type: .static, targets: \["plugin\_name"]) ], \`\`\` 1. Make any other customizations. For more information on how to write a \`Package.swift\` file, visit \[\`PackageDescription\`]\[]. :::tip If you add targets to your \`Package.swift\` file, use unique names. This avoids conflicts with targets from other packages. ::: 1. Update your \`ios/plugin\_name.podspec\` to point to new paths. \`\`\`ruby title="ios/plugin\_name.podspec" diff - s.source\_files = 'Classes/\*\*/\*.swift' - s.resource\_bundles = {'plugin\_name\_privacy' => \['Resources/PrivacyInfo.xcprivacy']} + s.source\_files = 'plugin\_name/Sources/plugin\_name/\*\*/\*.swift' + s.resource\_bundles = {'plugin\_name\_privacy' => \['plugin\_name/Sources/plugin\_name/PrivacyInfo.xcprivacy']} \`\`\` 1. Update loading of resources from bundle to use \[\`Bundle.module\`]\[]. \`\`\`swift #if SWIFT\_PACKAGE let settingsURL = Bundle.module.url(forResource: "image", withExtension: "jpg") #else let settingsURL = Bundle(for: Self.self).url(forResource: "image", withExtension: "jpg") #endif \`\`\` :::note \`Bundle.module\` only works if there are resources \[defined in the \`Package.swift\` file]\[Bundling resources] or \[automatically included by Xcode]\[Xcode resource detection]. Otherwise, using \`Bundle.module\` results in an error. ::: 1. If your \`.gitignore\` doesn't include \`.build/\` and \`.swiftpm/\` directories, you'll want to update your \`.gitignore\` to include: \`\`\`text title=".gitignore" .build/ .swiftpm/ \`\`\` Commit your plugin's changes to your version control system. 1. Verify the plugin still works with CocoaPods. 1. Turn off Swift Package Manager. \`\`\`sh flutter config --no-enable-swift-package-manager \`\`\` 1. Navigate to the plugin's example app. \`\`\`sh cd path/to/plugin/example/ \`\`\` 1. Ensure the plugin's example app builds and runs. \`\`\`sh flutter run \`\`\` 1. Navigate to the plugin's top-level directory. \`\`\`sh cd path/to/plugin/ \`\`\` 1. Run CocoaPods validation lints. \`\`\`sh pod lib lint ios/plugin\_name.podspec --configuration=Debug --skip-tests --use-modular-headers --use-libraries \`\`\` \`\`\`sh pod lib lint ios/plugin\_name.podspec --configuration=Debug --skip-tests --use-modular-headers \`\`\` 1. Verify the plugin works with Swift Package Manager. 1. Turn on Swift Package Manager. \`\`\`sh flutter config --enable-swift-package-manager \`\`\` 1. Navigate to the plugin's example app. \`\`\`sh cd path/to/plugin/example/ \`\`\` 1. Ensure the plugin's example app builds and runs. \`\`\`sh flutter run \`\`\` :::note Using the Flutter CLI to run the plugin's example app with the Swift Package Manager feature turned on migrates the project to add Swift Package Manager integration. This raises the example app's Flutter SDK requirement to version 3.24 or higher. If you'd like to run the example app using an older Flutter SDK version, do not commit the migration's changes to your version control system. If needed, you can always \[undo the Swift Package Manager migration]\[removeSPM]. ::: 1. In Xcode, open the plugin's example app. Ensure that \*\*Package Dependencies\*\* shows in the left \*\*Project Navigator\*\*. 1. Verify tests pass. \* \*\*If your plugin has native unit tests (XCTest), make sure you also \[update unit tests in the plugin's example app]\[].\*\* \* Follow instructions for \[testing plugins]\[]. \[\`PrivacyInfo.xcprivacy\` file]: https\://developer.apple.com/documentation/bundleresources/privacy\_manifest\_files \[Pigeon]: https\://pub.dev/packages/pigeon \[CocoaPods \`dependency\`]: https\://guides.cocoapods.org/syntax/podspec.html#dependency \[Swift Package Manager dependencies]: https\://developer.apple.com/documentation/packagedescription/package/dependency \[not recommended by Apple]: https\://developer.apple.com/documentation/packagedescription/product/library(name:type:targets:) \[Product]: https\://developer.apple.com/documentation/packagedescription/product \[\`Bundle.module\`]: https\://developer.apple.com/documentation/xcode/bundling-resources-with-a-swift-package#Access-a-resource-in-code \[Bundling resources]: https\://developer.apple.com/documentation/xcode/bundling-resources-with-a-swift-package#Explicitly-declare-or-exclude-resources \[Xcode resource detection]: https\://developer.apple.com/documentation/xcode/bundling-resources-with-a-swift-package#:\~:text=Xcode%20detects%20common%20resource%20types%20for%20Apple%20platforms%20and%20treats%20them%20as%20a%20resource%20automatically \[removeSPM]: /packages-and-plugins/swift-package-manager/for-app-developers#how-to-remove-swift-package-manager-integration \[update unit tests in the plugin's example app]: /packages-and-plugins/swift-package-manager/for-plugin-authors/#how-to-update-unit-tests-in-a-plugins-example-app \[testing plugins]: /testing/testing-plugins \[Bundling resources with a Swift package]: https\://developer.apple.com/documentation/xcode/bundling-resources-with-a-swift-package \[\`PackageDescription\`]: https\://developer.apple.com/documentation/packagedescription Replace \`plugin\_name\` throughout this guide with the name of your plugin. The example below uses \`ios\`, replace \`ios\` with \`macos\` or \`darwin\`, as applicable. 1. Ensure that you are running Flutter 3.44 or later. This enables SwiftPM by default. 1. Start by creating a directory under the \`ios\`, \`macos\`, and/or \`darwin\` directories. Name this new directory the name of the platform package. - plugin\_name/ - ios/ - ... - \*\*plugin\_name/\*\* 1. Within this new directory, create the following files/directories: - \`Package.swift\` (file) - \`Sources\` (directory) - \`Sources/plugin\_name\` (directory) - \`Sources/plugin\_name/include\` (directory) - \`Sources/plugin\_name/include/plugin\_name\` (directory) - \`Sources/plugin\_name/include/plugin\_name/.gitkeep\` (file) - This file ensures the directory is committed. You can remove the \`.gitkeep\` file if other files are added to the directory. Your plugin should look like: - plugin\_name/ - ios/ - ... - plugin\_name/ - \*\*Package.swift\*\* - \*\*Sources/plugin\_name/include/plugin\_name/\*\* - \*\*.gitkeep/\*\* 1. Use the following template in the \`Package.swift\` file: \`\`\`swift title="Package.swift" // swift-tools-version: 5.9 // The swift-tools-version declares the minimum version of Swift required to build this package. import PackageDescription let package = Package( // TODO: Update your plugin name. name: "plugin\_name", platforms: \[ // TODO: Update the platforms your plugin supports. // If your plugin only supports iOS, remove \`.macOS(...)\`. // If your plugin only supports macOS, remove \`.iOS(...)\`. .iOS("13.0"), .macOS("10.15") ], products: \[ // TODO: Update your library and target names. // If the plugin name contains "\_", replace with "-" for the library name .library(name: "plugin-name", targets: \["plugin\_name"]) ], dependencies: \[ .package(name: "FlutterFramework", path: "../FlutterFramework") ], targets: \[ .target( // TODO: Update your target name. name: "plugin\_name", dependencies: \[ .product(name: "FlutterFramework", package: "FlutterFramework") ], resources: \[ // TODO: If your plugin requires a privacy manifest // (in other words, if it uses any required reason APIs), // update the PrivacyInfo.xcprivacy file // to describe your plugin's privacy impact, and then uncomment this line. // For more information, visit: // https\://developer.apple.com/documentation/bundleresources/privacy\_manifest\_files // .process("PrivacyInfo.xcprivacy"), // TODO: If you have other resources that need to be bundled with your plugin, refer to // the following instructions to add them: // https\://developer.apple.com/documentation/xcode/bundling-resources-with-a-swift-package ], cSettings: \[ // TODO: Update your plugin name. .headerSearchPath("include/plugin\_name") ] ) ] ) \`\`\` 1. Update the \[supported platforms]\[] in your \`Package.swift\` file. \`\`\`swift title="Package.swift" platforms: \[ // TODO: Update the platforms your plugin supports. // If your plugin only supports iOS, remove \`.macOS(...)\`. // If your plugin only supports macOS, remove \`.iOS(...)\`. \[!.iOS("13.0"),!] \[!.macOS("10.15")!] ], \`\`\` \[supported platforms]: https\://developer.apple.com/documentation/packagedescription/supportedplatform 1. Update the package, library, and target names in your \`Package.swift\` file. \`\`\`swift title="Package.swift" let package = Package( // TODO: Update your plugin name. name: \[!"plugin\_name"!], platforms: \[ .iOS("13.0"), .macOS("10.15") ], products: \[ // TODO: Update your library and target names. // If the plugin name contains "\_", replace with "-" for the library name .library(name: \[!"plugin-name"!], targets: \[\[!"plugin\_name"!]]) ], dependencies: \[ .package(name: "FlutterFramework", path: "../FlutterFramework") ], targets: \[ .target( // TODO: Update your target name. name: \[!"plugin\_name"!], dependencies: \[ .product(name: "FlutterFramework", package: "FlutterFramework") ], resources: \[ // TODO: If your plugin requires a privacy manifest // (for example, if it uses any required reason APIs), // update the PrivacyInfo.xcprivacy file // to describe your plugin's privacy impact, and then uncomment this line. // For more information, visit: // https\://developer.apple.com/documentation/bundleresources/privacy\_manifest\_files // .process("PrivacyInfo.xcprivacy"), // TODO: If you have other resources that need to be bundled with your plugin, refer to // the following instructions to add them: // https\://developer.apple.com/documentation/xcode/bundling-resources-with-a-swift-package ], cSettings: \[ // TODO: Update your plugin name. .headerSearchPath("include/\[!plugin\_name!]") ] ) ] ) \`\`\` :::note If the plugin name contains \`\_\`, the library name must be a \`-\` separated version of the plugin name. ::: 1. If your plugin has a \[\`PrivacyInfo.xcprivacy\` file]\[], move it to \`ios/plugin\_name/Sources/plugin\_name/PrivacyInfo.xcprivacy\` and uncomment the resource in the \`Package.swift\` file. \`\`\`swift title="Package.swift" resources: \[ // TODO: If your plugin requires a privacy manifest // (for example, if it uses any required reason APIs), // update the PrivacyInfo.xcprivacy file // to describe your plugin's privacy impact, and then uncomment this line. // For more information, visit: // https\://developer.apple.com/documentation/bundleresources/privacy\_manifest\_files \[!.process("PrivacyInfo.xcprivacy"),!] // TODO: If you have other resources that need to be bundled with your plugin, refer to // the following instructions to add them: // https\://developer.apple.com/documentation/xcode/bundling-resources-with-a-swift-package ], \`\`\` 1. Move any resource files from \`ios/Assets\` to \`ios/plugin\_name/Sources/plugin\_name\` (or a subdirectory). Add the resource files to your \`Package.swift\` file, if applicable. For more instructions, visit \[https\://developer.apple.com/documentation/xcode/bundling-resources-with-a-swift-package]\(https\://developer.apple.com/documentation/xcode/bundling-resources-with-a-swift-package). 1. Move any public headers from \`ios/Classes\` to \`ios/plugin\_name/Sources/plugin\_name/include/plugin\_name\`. \* If you're unsure which headers are public, check your \`podspec\` file's \[\`public\_header\_files\`]\[] attribute. If this attribute isn't specified, all of your headers were public. You should consider whether you want all of your headers to be public. \* The \`pluginClass\` defined in your \`pubspec.yaml\` file must be public and within this directory. 1. Handling \`modulemap\`. Skip this step if your plugin does not have a \`modulemap\`. If you're using a \`modulemap\` for CocoaPods to create a Test submodule, consider removing it for Swift Package Manager. Note that this makes all public headers available through the module. To remove the \`modulemap\` for Swift Package Manager but keep it for CocoaPods, exclude the \`modulemap\` and umbrella header in the plugin's \`Package.swift\` file. The example below assumes the \`modulemap\` and umbrella header are located in the \`ios/plugin\_name/Sources/plugin\_name/include\` directory. \`\`\`swift title="Package.swift" diff .target( name: "plugin\_name", dependencies: \[], + exclude: \["include/cocoapods\_plugin\_name.modulemap", "include/plugin\_name-umbrella.h"], \`\`\` If you want to keep your unit tests compatible with both CocoaPods and Swift Package Manager, you can try the following: \`\`\`objc title="Tests/TestFile.m" diff @import plugin\_name; - @import plugin\_name.Test; + #if \_\_has\_include() + @import plugin\_name.Test; + #endif \`\`\` If you would like to use a custom \`modulemap\` with your Swift package, refer to \[Swift Package Manager's documentation]\[]. 1. Move all remaining files from \`ios/Classes\` to \`ios/plugin\_name/Sources/plugin\_name\`. 1. The \`ios/Assets\`, \`ios/Resources\`, and \`ios/Classes\` directories should now be empty and can be deleted. 1. Add the \`FlutterFramework\` as a dependency. Update \`Package.swift\` to include \`FlutterFramework\`: \`\`\`swift title="Package.swift" dependencies: \[ \[!.package(name: "FlutterFramework", path: "../FlutterFramework")!] ], targets: \[ .target( // TODO: Update your target name. name: "plugin\_name", dependencies: \[ \[!.product(name: "FlutterFramework", package: "FlutterFramework")!] ] ) ] \`\`\` 1. If your header files are no longer in the same directory as your implementation files, you should update your import statements. For example, imagine the following migration: \* Before: \`\`\`plaintext ios/Classes/ ├── PublicHeaderFile.h └── ImplementationFile.m \`\`\` \* After: \`\`\`plaintext highlightLines=2 ios/plugin\_name/Sources/plugin\_name/ └── include/plugin\_name/ └── PublicHeaderFile.h └── ImplementationFile.m \`\`\` In this example, the import statements in \`ImplementationFile.m\` should be updated: \`\`\`objc title="Sources/plugin\_name/ImplementationFile.m" diff - #import "PublicHeaderFile.h" + #import "./include/plugin\_name/PublicHeaderFile.h" \`\`\` 1. If your plugin uses \[Pigeon]\[], update your Pigeon input file. \`\`\`dart title="pigeons/messages.dart" diff javaOptions: JavaOptions(), - objcHeaderOut: 'ios/Classes/messages.g.h', - objcSourceOut: 'ios/Classes/messages.g.m', + objcHeaderOut: 'ios/plugin\_name/Sources/plugin\_name/messages.g.h', + objcSourceOut: 'ios/plugin\_name/Sources/plugin\_name/messages.g.m', copyrightHeader: 'pigeons/copyright.txt', \`\`\` If your \`objcHeaderOut\` file is no longer within the same directory as the \`objcSourceOut\`, you can change the \`#import\` using \`ObjcOptions.headerIncludePath\`: \`\`\`dart title="pigeons/messages.dart" diff javaOptions: JavaOptions(), - objcHeaderOut: 'ios/Classes/messages.g.h', - objcSourceOut: 'ios/Classes/messages.g.m', + objcHeaderOut: 'ios/plugin\_name/Sources/plugin\_name/include/plugin\_name/messages.g.h', + objcSourceOut: 'ios/plugin\_name/Sources/plugin\_name/messages.g.m', + objcOptions: ObjcOptions( + headerIncludePath: './include/plugin\_name/messages.g.h', + ), copyrightHeader: 'pigeons/copyright.txt', \`\`\` Run Pigeon to re-generate its code with the latest configuration. 1. Update your \`Package.swift\` file with any customizations you might need. 1. In Xcode, open the \`ios/plugin\_name/\` directory. 1. In Xcode, open your \`Package.swift\` file. Verify that Xcode doesn't produce any warnings or errors for this file. :::tip If Xcode doesn't show any files, quit Xcode (\*\*Xcode > Quit Xcode\*\*) and reopen. If Xcode doesn't update after you make a change, try clicking \*\*File > Packages > Reset Package Caches\*\*. ::: 1. If your \`ios/plugin\_name.podspec\` file has \[CocoaPods \`dependency\`]\[]s, add the corresponding \[Swift Package Manager dependencies]\[] to your \`Package.swift\` file. 1. If your package must be linked explicitly \`static\` or \`dynamic\` (\[not recommended by Apple]\[]), update the \[Product]\[] to define the type: \`\`\`swift title="Package.swift" products: \[ .library(name: "plugin-name", type: .static, targets: \["plugin\_name"]) ], \`\`\` 1. Make any other customizations. For more information on how to write a \`Package.swift\` file, visit \[https\://developer.apple.com/documentation/packagedescription]\(https\://developer.apple.com/documentation/packagedescription). :::tip If you add targets to your \`Package.swift\` file, use unique names. This avoids conflicts with targets from other packages. ::: 1. Update your \`ios/plugin\_name.podspec\` to point to new paths. \`\`\`ruby title="ios/plugin\_name.podspec" diff - s.source\_files = 'Classes/\*\*/\*.{h,m}' - s.public\_header\_files = 'Classes/\*\*/\*.h' - s.module\_map = 'Classes/cocoapods\_plugin\_name.modulemap' - s.resource\_bundles = {'plugin\_name\_privacy' => \['Resources/PrivacyInfo.xcprivacy']} + s.source\_files = 'plugin\_name/Sources/plugin\_name/\*\*/\*.{h,m}' + s.public\_header\_files = 'plugin\_name/Sources/plugin\_name/include/\*\*/\*.h' + s.module\_map = 'plugin\_name/Sources/plugin\_name/include/cocoapods\_plugin\_name.modulemap' + s.resource\_bundles = {'plugin\_name\_privacy' => \['plugin\_name/Sources/plugin\_name/PrivacyInfo.xcprivacy']} \`\`\` 1. Update loading of resources from bundle to use \`SWIFTPM\_MODULE\_BUNDLE\`: \`\`\`objc #if SWIFT\_PACKAGE NSBundle \*bundle = SWIFTPM\_MODULE\_BUNDLE; #else NSBundle \*bundle = \[NSBundle bundleForClass:\[self class]]; #endif NSURL \*imageURL = \[bundle URLForResource:@"image" withExtension:@"jpg"]; \`\`\` :::note \`SWIFTPM\_MODULE\_BUNDLE\` only works if there are actual resources (either \[defined in the \`Package.swift\` file]\[Bundling resources] or \[automatically included by Xcode]\[Xcode resource detection]). Otherwise, using \`SWIFTPM\_MODULE\_BUNDLE\` results in an error. ::: 1. If your \`ios/plugin\_name/Sources/plugin\_name/include\` directory only contains a \`.gitkeep\`, you'll want to update your \`.gitignore\` to include the following: \`\`\`text title=".gitignore" !.gitkeep \`\`\` Run \`flutter pub publish --dry-run\` to ensure the \`include\` directory is published. 1. Commit your plugin's changes to your version control system. 1. Verify the plugin still works with CocoaPods. 1. Turn off Swift Package Manager: \`\`\`sh flutter config --no-enable-swift-package-manager \`\`\` 1. Navigate to the plugin's example app. \`\`\`sh cd path/to/plugin/example/ \`\`\` 1. Ensure the plugin's example app builds and runs. \`\`\`sh flutter run \`\`\` 1. Navigate to the plugin's top-level directory. \`\`\`sh cd path/to/plugin/ \`\`\` 1. Run CocoaPods validation lints: \`\`\`sh pod lib lint ios/plugin\_name.podspec --configuration=Debug --skip-tests --use-modular-headers --use-libraries \`\`\` \`\`\`sh pod lib lint ios/plugin\_name.podspec --configuration=Debug --skip-tests --use-modular-headers \`\`\` 1. Verify the plugin works with Swift Package Manager. 1. Turn on Swift Package Manager: \`\`\`sh flutter config --enable-swift-package-manager \`\`\` 1. Navigate to the plugin's example app. \`\`\`sh cd path/to/plugin/example/ \`\`\` 1. Ensure the plugin's example app builds and runs. \`\`\`sh flutter run \`\`\` :::note Using the Flutter CLI to run the plugin's example app with the Swift Package Manager feature turned on migrates the project to add Swift Package Manager integration. This raises the example app's Flutter SDK requirement to version 3.24 or higher. If you'd like to run the example app using an older Flutter SDK version, do not commit the migration's changes to your version control system. If needed, you can always \[undo the Swift Package Manager migration]\[removeSPM]. ::: 1. In Xcode, open the plugin's example app. Ensure that \*\*Package Dependencies\*\* shows in the left \*\*Project Navigator\*\*. 1. Verify tests pass. \* \*\*If your plugin has native unit tests (XCTest), make sure you also \[update unit tests in the plugin's example app]\[].\*\* \* Follow instructions for \[testing plugins]\[]. \[\`PrivacyInfo.xcprivacy\` file]: https\://developer.apple.com/documentation/bundleresources/privacy\_manifest\_files \[\`public\_header\_files\`]: https\://guides.cocoapods.org/syntax/podspec.html#public\_header\_files \[Swift Package Manager's documentation]: https\://github.com/apple/swift-package-manager/blob/main/Documentation/Usage.md#creating-c-language-targets \[Pigeon]: https\://pub.dev/packages/pigeon \[CocoaPods \`dependency\`]: https\://guides.cocoapods.org/syntax/podspec.html#dependency \[Swift Package Manager dependencies]: https\://developer.apple.com/documentation/packagedescription/package/dependency \[not recommended by Apple]: https\://developer.apple.com/documentation/packagedescription/product/library(name:type:targets:) \[Product]: https\://developer.apple.com/documentation/packagedescription/product \[Bundling resources]: https\://developer.apple.com/documentation/xcode/bundling-resources-with-a-swift-package#Explicitly-declare-or-exclude-resources \[Xcode resource detection]: https\://developer.apple.com/documentation/xcode/bundling-resources-with-a-swift-package#:\~:text=Xcode%20detects%20common%20resource%20types%20for%20Apple%20platforms%20and%20treats%20them%20as%20a%20resource%20automatically \[removeSPM]: /packages-and-plugins/swift-package-manager/for-app-developers#how-to-remove-swift-package-manager-integration \[update unit tests in the plugin's example app]: /packages-and-plugins/swift-package-manager/for-plugin-authors/#how-to-update-unit-tests-in-a-plugins-example-app \[testing plugins]: https\://docs.flutter.dev/testing/testing-plugins ## (Optional, but recommended) Add plugin as local package in example app If your plugin includes an example, it is recommended to add the plugin as a local package in the example app. This is not required, but provides better Xcode support when editing the plugin's source code in the example app. Visit \[issue #179032]\(https\://github.com/flutter/flutter/issues/179032). ### Add plugin as local package 1. In a terminal navigate to \`my\_plugin\`. 1. In Xcode, run the following command to open the example app's workspace, (replace \`ios\` with \`macos\` as appropriate): \`\`\`bash open example/ios/Runner.xcworkspace \`\`\` 1. Right click \*\*Flutter\*\* > \*\*Add Files to “Runner”\*\*. !\[Add Files to Runner]\(/assets/images/docs/development/packages-and-plugins/swift-package-manager/add-files-to-runner.png) 1. Select \`my\_plugin/ios/my\_plugin\` (or \`macos\` or \`darwin\`, as appropriate). 1. Make sure “Reference files in place” is selected (it should be the default), and click \*\*Finish\*\*. !\[Select Reference files in place]\(/assets/images/docs/development/packages-and-plugins/swift-package-manager/reference-files-in-place.png) This adds the plugin as a local package, but it's referenced by absolute path, which isn't desirable for distribution. To change it to a relative path, use the following instructions. ### Change to relative path 1. Copy “Full Path” for plugin from the File Inspector. !\[Copy Full Path]\(/assets/images/docs/development/packages-and-plugins/swift-package-manager/copy-full-path.png) 1. In terminal: \`open -a Xcode example/ios/Runner.xcodeproj/project.pbxproj\` 1. Find the following: \`\`\`text path = \[COPIED FULL PATH]; sourceTree = "" \`\`\` For example: \`\`\`text path = /Users/username/path/to/my\_plugin/ios/my\_plugin; sourceTree = "" \`\`\` 1. And replace with relative path: \`\`\`text path = ../../ios/my\_plugin; sourceTree = "" \`\`\` (Adjust \`ios\` to \`macos\` or \`darwin\` as needed). ## How to update unit tests in a plugin's example app If your plugin has native XCTests, you might need to update them to work with Swift Package Manager if one of the following is true: \* You're using a CocoaPod dependency for the test. \* Your plugin is explicitly set to \`type: .dynamic\` in its \`Package.swift\` file. To update your unit tests: 1. In Xcode, open your \`example/ios/Runner.xcworkspace\`. 1. If you were using a CocoaPod dependency for tests, such as \`OCMock\`, you'll want to remove it from your \`Podfile\` file. \`\`\`ruby title="ios/Podfile" diff target 'RunnerTests' do inherit! :search\_paths - pod 'OCMock', '3.5' end \`\`\` Then in the terminal, run \`pod install\` in the \`plugin\_name\_ios/example/ios\` directory. 1. Navigate to \*\*Package Dependencies\*\* for the project. 1. Click the \*\*+\*\* button and add any test-only dependencies by searching for them in the top right search bar. :::note OCMock uses unsafe build flags and can only be used if targeted by commit. \`fe1661a3efed11831a6452f4b1a0c5e6ddc08c3d\` is the commit for the 3.9.3 version. ::: 1. Ensure the dependency is added to the \`RunnerTests\` Target. 1. Click the \*\*Add Package\*\* button. 1. If you've explicitly set your plugin's library type to \`.dynamic\` in its \`Package.swift\` file (\[not recommended by Apple]\[library type recommendations]), you'll also need to add it as a dependency to the \`RunnerTests\` target. 1. Ensure \`RunnerTests\` \*\*Build Phases\*\* has a \*\*Link Binary With Libraries\*\* build phase: If the build phase doesn't exist already, create one. Click the button and then click \*\*New Link Binary With Libraries Phase\*\*. 1. Navigate to \*\*Package Dependencies\*\* for the project. 1. Click the button. 1. In the dialog that opens, click the \*\*Add Local...\*\* button. 1. Navigate to \`plugin\_name/plugin\_name\_ios/ios/plugin\_name\_ios\` and click the \*\*Add Package\*\* button. 1. Ensure that it's added to the \`RunnerTests\` target and click the \*\*Add Package\*\* button. 1. Ensure tests pass \*\*Product > Test\*\*. \[library type recommendations]: https\://developer.apple.com/documentation/packagedescription/product/library(name:type:targets:)

----
url: https://docs.flutter.dev/perf/metrics.md
----

\# Performance metrics > Flutter metrics, and which tools and APIs are used to get them \* Startup time to the first frame \* Check the time when \[WidgetsBinding.instance.firstFrameRasterized]\[firstFrameRasterized] is true. \* See the \[perf dashboard]\(https\://flutter-flutter-perf.skia.org/e/?queries=sub\_result%3DtimeToFirstFrameRasterizedMicros). \* Frame buildDuration, rasterDuration, and totalSpan \* See \[\`FrameTiming\`]\(https\://api.flutter.dev/flutter/dart-ui/FrameTiming-class.html) in the API docs. \* Statistics of frame \`buildDuration\` (\`\*\_frame\_build\_time\_millis\`) \* We recommend monitoring four stats: average, 90th percentile, 99th percentile, and worst frame build time. \* See, for example, \[metrics]\[transition\_build] for the \`flutter\_gallery\_\_transition\_perf\` test. \* Statistics of frame \`rasterDuration\` (\`\*\_frame\_build\_time\_millis\`) \* We recommend monitoring four stats: average, 90th percentile, 99th percentile, and worst frame build time. \* See, for example, \[metrics]\[transition\_raster] for the \`flutter\_gallery\_\_transition\_perf\` test. \* CPU/GPU usage (a good approximation for energy use) \* The usage is currently only available through trace events. See \[profiling\_summarizer.dart]\[profiling\_summarizer]. \* See \[metrics]\[cpu\_gpu] for the \`simple\_animation\_perf\_ios\` test. \* release\_size\_bytes to approximately measure the size of a Flutter app \* See the \[basic\_material\_app\_android]\[], \[basic\_material\_app\_ios]\[], \[hello\_world\_android]\[], \[hello\_world\_ios]\[], \[flutter\_gallery\_android]\[], and \[flutter\_gallery\_ios]\[] tests. \* See \[metrics]\[size\_perf] in the dashboard. \* For info on how to measure the size more accurately, see the \[app size]\(/perf/app-size) page. For a complete list of performance metrics Flutter measures per commit, visit the following sites, click \*\*Query\*\*, and filter the \*\*test\*\* and \*\*sub\_result\*\* fields: \* \[https\://flutter-flutter-perf.skia.org/e/]\(https\://flutter-flutter-perf.skia.org/e/) \* \[https\://flutter-engine-perf.skia.org/e/]\(https\://flutter-engine-perf.skia.org/e/) \[firstFrameRasterized]: https\://api.flutter.dev/flutter/widgets/WidgetsBinding/firstFrameRasterized.html \[transition\_build]: https\://flutter-flutter-perf.skia.org/e/?queries=sub\_result%3D90th\_percentile\_frame\_build\_time\_millis%26sub\_result%3D99th\_percentile\_frame\_build\_time\_millis%26sub\_result%3Daverage\_frame\_build\_time\_millis%26sub\_result%3Dworst\_frame\_build\_time\_millis%26test%3Dflutter\_gallery\_\_transition\_perf \[transition\_raster]: https\://flutter-flutter-perf.skia.org/e/?queries=sub\_result%3D90th\_percentile\_frame\_rasterizer\_time\_millis%26sub\_result%3D99th\_percentile\_frame\_rasterizer\_time\_millis%26sub\_result%3Daverage\_frame\_rasterizer\_time\_millis%26sub\_result%3Dworst\_frame\_rasterizer\_time\_millis%26test%3Dflutter\_gallery\_\_transition\_perf \[profiling\_summarizer]: https\://github.com/flutter/flutter/blob/main/packages/flutter\_driver/lib/src/driver/profiling\_summarizer.dart \[cpu\_gpu]: https\://flutter-flutter-perf.skia.org/e/?queries=sub\_result%3Daverage\_cpu\_usage%26sub\_result%3Daverage\_gpu\_usage%26test%3Dsimple\_animation\_perf\_ios \[basic\_material\_app\_android]: https\://github.com/flutter/flutter/blob/main/dev/devicelab/bin/tasks/basic\_material\_app\_android\_\_compile.dart \[basic\_material\_app\_ios]: https\://github.com/flutter/flutter/blob/main/dev/devicelab/bin/tasks/basic\_material\_app\_ios\_\_compile.dart \[hello\_world\_android]: https\://github.com/flutter/flutter/blob/main/dev/devicelab/bin/tasks/hello\_world\_android\_\_compile.dart \[hello\_world\_ios]: https\://github.com/flutter/flutter/blob/main/dev/devicelab/bin/tasks/hello\_world\_ios\_\_compile.dart \[flutter\_gallery\_android]: https\://github.com/flutter/flutter/blob/main/dev/devicelab/bin/tasks/flutter\_gallery\_android\_\_compile.dart \[flutter\_gallery\_ios]: https\://github.com/flutter/flutter/blob/main/dev/devicelab/bin/tasks/flutter\_gallery\_ios\_\_compile.dart \[size\_perf]: https\://flutter-flutter-perf.skia.org/e/?queries=sub\_result%3Drelease\_size\_bytes%26test%3Dbasic\_material\_app\_android\_\_compile%26test%3Dbasic\_material\_app\_ios\_\_compile%26test%3Dhello\_world\_android\_\_compile%26test%3Dhello\_world\_ios\_\_compile%26test%3Dflutter\_gallery\_ios\_\_compile%26test%3Dflutter\_gallery\_android\_\_compile

----
url: https://docs.flutter.dev/resources/videos.md
----

\# Videos > Available videos on various aspects of developing in Flutter. These Flutter videos, produced both internally at Google and by the Flutter community, might help if you are a visual learner. Note that many people make Flutter videos. This page shows some that we like, but there are many others, including some in different languages. --- ## Series ### Flutter Forward Even if you couldn't make it to Nairobi for the in-person event, you can enjoy the content created for Flutter Forward! \[Flutter Forward playlist]\[] \[Flutter Forward playlist]: https\://www\.youtube.com/playlist?list=PLjxrf2q8roU3LvrdR8Hv\_phLrTj0xmjnD ### Get ready for Flutter Forward Flutter Forward occurred on Jan 25th, 2023 in Nairobi, Kenya. Before the event, the Flutter team provided 17 days of Flutter featuring new content and activities leading up to the event. This playlist contains these and other pre-event videos relating to Flutter Forward. \*\*17 Days of Flutter!\*\* \[Get ready for Flutter Forward playlist]\[] \[Get ready for Flutter Forward playlist]: https\://www\.youtube.com/playlist?list=PLjxrf2q8roU3PUEaM4yYXqvFMEwOluBNl ### Learning to Fly :::note Season 2 of Learning to Fly has been released as part of the 17 Days of Flutter, leading up to the 3.7 release! \[Season 2]\[] centers around creating a platform game, DoodleDash, using the Flame engine. ::: \[Season 2]: https\://www\.youtube.com/watch?v=ILTx1Wa33Z0 Follow along with Khanh's journey as she learns Flutter. From ideation down to the moments of confusion, learn alongside her as she asks important questions like: "What is the best way to build out a theme? How to approach using fonts?" And more! \[Learning to Fly playlist]\[] \[Learning to Fly playlist]: https\://www\.youtube.com/playlist?list=PLjxrf2q8roU3X18pAQWLyCJaa79RpqWnn Here are some of the series offered on the \[flutterdev YouTube channel]\[]. For more information, check out \_all\_ of the \[Flutter channel's playlists]\[]. \[Flutter channel's playlists]: https\://www\.youtube.com/@flutterdev/playlists \[flutterdev YouTube channel]: https\://www\.youtube.com/@flutterdev ### Decoding Flutter This series focuses on tips, tricks, best practices, and general advice on getting the most out of Flutter. In the comments section for this series, you can submit suggestions future episodes. \[Decoding Flutter playlist]\[] \[Decoding Flutter playlist]: https\://www\.youtube.com/playlist?list=PLjxrf2q8roU1fRV40Ec8200rX6OuQkmnl ### Flutter widget of the week Do you have 60 seconds? Each one-minute video highlights a Flutter widget. \[Flutter widget of the week playlist]\[] \[Flutter widget of the week playlist]: https\://www\.youtube.com/playlist?list=PLjxrf2q8roU23XGwz3Km7sQZFTdB996iG ### Flutter package of the week If you have another minute (or two), each of these videos highlights a Flutter package. \[Flutter package of the week playlist]\[] \[Flutter package of the week playlist]: https\://www\.youtube.com/playlist?list=PLjxrf2q8roU1quF6ny8oFHJ2gBdrYN\_AK ### The Boring Flutter show This series features Flutter programmers coding, unscripted and in real time. Mistakes, solutions (some of them correct), and snazzy intro music included. \[The Boring Flutter show playlist]\[] \[The Boring Flutter show playlist]: https\://www\.youtube.com/playlist?list=PLjxrf2q8roU3ahJVrSgAnPjzkpGmL9Czl ### Flutter at Google I/O 2022 Google I/O 2022 is over, but you can still catch the great Flutter content! \[Flutter at Google I/O playlist]\[] \[Flutter at Google I/O playlist]: https\://www\.youtube.com/playlist?list=PLjxrf2q8roU1kHjuHoFGBLCxjy4h2WOcP ### Flutter Engage 2021 \[Flutter Engage 2021]\[] was a day-long event that officially launched Flutter 2. \[Flutter Engage 2021]: https\://events.flutter.dev/ Check out the Flutter Engage 2021 highlights reel. Watch recordings of the sessions on the Flutter YouTube channel. \[Flutter Engage 2021 playlist]\[] Watch recordings of the sessions offered by the Flutter community. \[Flutter Engage community talks playlist]\[] \[Flutter Engage 2021 playlist]: https\://www\.youtube.com/playlist?list=PLjxrf2q8roU21cXt24HLm-ZODXmr4Jw0C \[Flutter Engage community talks playlist]: https\://www\.youtube.com/playlist?list=PLjxrf2q8roU1ln3WoiGTUvLr7ZrPPFvXv ### Flutter in focus Five-to-ten minute tutorials (more or less) on using Flutter. \[Flutter in focus playlist]\[] \[Flutter in focus playlist]: https\://www\.youtube.com/playlist?list=PLjxrf2q8roU2HdJQDjJzOeO6J3FoFLWr2 ## Conference talks Here are a some Flutter talks given at various conferences. \[Conference talks playlist]\[] \[Conference talks playlist]: https\://www\.youtube.com/playlist?list=PLjxrf2q8roU1UJ0OEpANodVMVm1GeE7Ti ## Flutter developer stories Videos showing how various customers, such as Abbey Road Studio, Hamilton, and Alibaba, have used Flutter to create beautiful compelling apps with millions of downloads. \[Flutter developer stories playlist]\[] \[Flutter developer stories playlist]: https\://www\.youtube.com/playlist?list=PLjxrf2q8roU33POuWi4bK0zvDpAHK6759

----
url: https://docs.flutter.dev/ui/assets/assets-and-images
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Adding assets and images](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

Flutter apps can include both code and *assets* (sometimes called resources). An asset is a file that is bundled and deployed with your app, and is accessible at runtime. Common types of assets include static data (for example, JSON files), configuration files, icons, and images (JPEG, WebP, GIF, animated WebP/GIF, PNG, BMP, and WBMP).

## Specifying assets

[#](#specifying-assets)

Flutter uses the [`pubspec.yaml`](https://dart.dev/tools/pub/pubspec) file, located at the root of your project, to identify assets required by an app.

Here is an example:

yaml

```
flutter:
  assets:
    - assets/my_icon.png
    - assets/background.png
```

To include all assets under a directory, specify the directory name with the `/` character at the end:

yaml

```
flutter:
  assets:
    - directory/
    - directory/subdirectory/
```

### Asset bundling

[#](#asset-bundling)

The `assets` subsection of the `flutter` section specifies files that should be included with the app. Each asset is identified by an explicit path (relative to the `pubspec.yaml` file) where the asset file is located. The order in which the assets are declared doesn't matter. The actual directory name used (`assets` in first example or `directory` in the above example) doesn't matter.

During a build, Flutter places assets into a special archive called the *asset bundle* that apps read from at runtime.

### Automatic transformation of asset files at build time

[#](#automatic-transformation-of-asset-files-at-build-time)

Flutter supports using a Dart package to transform asset files when building your app. To do this, specify the asset files and transformer package in your pubspec file. To learn how to do this and write your own asset-transforming packages, see [Transforming assets at build time](/ui/assets/asset-transformation).

### Platform-specific assets

[#](#platform-specific-assets)

You can specify assets that should only be bundled for specific platforms. This is useful when you have large assets that are only needed on a subset of platforms, such as high-resolution textures or platform-specific configuration files.

The following configuration only includes an asset on macOS and Windows:

yaml

```
flutter:
  assets:
    - path: assets/desktop_config.json
      platforms:
        - macos
        - windows
```

For more information about configuring platform-specific assets, see the [`assets` field](/tools/pubspec#assets) in the [Flutter pubspec options](/tools/pubspec) reference.

## Loading assets

[#](#loading-assets)

Your app can access its assets through an [`AssetBundle`](https://api.flutter.dev/flutter/services/AssetBundle-class.html) object.

The two main methods on an asset bundle allow you to load a string/text asset (`loadString()`) or an image/binary asset (`load()`) out of the bundle, given a logical key. The logical key maps to the path to the asset specified in the `pubspec.yaml` file at build time.

### Loading text assets

[#](#loading-text-assets)

Each Flutter app has a [`rootBundle`](https://api.flutter.dev/flutter/services/rootBundle.html) object for easy access to the main asset bundle. It is possible to load assets directly using the `rootBundle` global static from `package:flutter/services.dart`.

However, it's recommended to obtain the `AssetBundle` for the current `BuildContext` using [`DefaultAssetBundle`](https://api.flutter.dev/flutter/widgets/DefaultAssetBundle-class.html), rather than the default asset bundle that was built with the app; this approach enables a parent widget to substitute a different `AssetBundle` at run time, which can be useful for localization or testing scenarios.

Typically, you'll use `DefaultAssetBundle.of()` to indirectly load an asset, for example a JSON file, from the app's runtime `rootBundle`.

Outside of a `Widget` context, or when a handle to an `AssetBundle` is not available, you can use `rootBundle` to directly load such assets. For example:

dart

```
import 'package:flutter/services.dart' show rootBundle;
import 'package:material_ui/material_ui.dart';
​
Future<String> loadAsset() async {
  return await rootBundle.loadString('assets/config.json');
}
```

### Loading images

[#](#loading-images)

To load an image, use the [`AssetImage`](https://api.flutter.dev/flutter/painting/AssetImage-class.html) class in a widget's `build()` method.

For example, your app can load the background image from the asset declarations in the previous example:

dart

```
return const Image(image: AssetImage('assets/background.png'));
```

### Resolution-aware image assets

[#](#resolution-aware)

Flutter can load resolution-appropriate images for the current [device pixel ratio](https://api.flutter.dev/flutter/dart-ui/FlutterView/devicePixelRatio.html).

[`AssetImage`](https://api.flutter.dev/flutter/painting/AssetImage-class.html) will map a logical requested asset onto one that most closely matches the current [device pixel ratio](https://api.flutter.dev/flutter/dart-ui/FlutterView/devicePixelRatio.html).

For this mapping to work, assets should be arranged according to a particular directory structure:

```
.../image.png
.../Mx/image.png
.../Nx/image.png
...etc.
```

Where *M* and *N* are numeric identifiers that correspond to the nominal resolution of the images contained within. In other words, they specify the device pixel ratio that the images are intended for.

In this example, `image.png` is considered the *main asset*, while `Mx/image.png` and `Nx/image.png` are considered to be *variants*.

The main asset is assumed to correspond to a resolution of 1.0. For example, consider the following asset layout for an image named `my_icon.png`:

```
.../my_icon.png       (mdpi baseline)
.../1.5x/my_icon.png  (hdpi)
.../2.0x/my_icon.png  (xhdpi)
.../3.0x/my_icon.png  (xxhdpi)
.../4.0x/my_icon.png  (xxxhdpi)
```

On devices with a device pixel ratio of 1.8, the asset `.../2.0x/my_icon.png` is chosen. For a device pixel ratio of 2.7, the asset `.../3.0x/my_icon.png` is chosen.

If the width and height of the rendered image are not specified on the `Image` widget, the nominal resolution is used to scale the asset so that it occupies the same amount of screen space as the main asset would have, just with a higher resolution. That is, if `.../my_icon.png` is 72px by 72px, then `.../3.0x/my_icon.png` should be 216px by 216px; but they both render into 72px by 72px (in logical pixels), if width and height are not specified.

#### Bundling of resolution-aware image assets

[#](#resolution-aware-bundling)

You only need to specify the main asset or its parent directory in the `assets` section of `pubspec.yaml`. Flutter bundles the variants for you. Each entry should correspond to a real file, with the exception of the main asset entry. If the main asset entry doesn't correspond to a real file, then the asset with the lowest resolution is used as the fallback for devices with device pixel ratios below that resolution. The entry should still be included in the `pubspec.yaml` manifest, however.

Anything using the default asset bundle inherits resolution awareness when loading images. (If you work with some of the lower level classes, like [`ImageStream`](https://api.flutter.dev/flutter/painting/ImageStream-class.html) or [`ImageCache`](https://api.flutter.dev/flutter/painting/ImageCache-class.html), you'll also notice parameters related to scale.)

### Asset images in package dependencies

[#](#from-packages)

To load an image from a [package](/packages-and-plugins/using-packages) dependency, the `package` argument must be provided to [`AssetImage`](https://api.flutter.dev/flutter/painting/AssetImage-class.html).

For instance, suppose your application depends on a package called `my_icons`, which has the following directory structure:

```
.../pubspec.yaml
.../icons/heart.png
.../icons/1.5x/heart.png
.../icons/2.0x/heart.png
...etc.
```

To load the image, use:

dart

```
return const AssetImage('icons/heart.png', package: 'my_icons');
```

Assets used by the package itself should also be fetched using the `package` argument as above.

#### Bundling of package assets

[#](#bundling-of-package-assets)

If the desired asset is specified in the `pubspec.yaml` file of the package, it's bundled automatically with the application. In particular, assets used by the package itself must be specified in its `pubspec.yaml`.

A package can also choose to have assets in its `lib/` folder that are not specified in its `pubspec.yaml` file. In this case, for those images to be bundled, the application has to specify which ones to include in its `pubspec.yaml`. For instance, a package named `fancy_backgrounds` could have the following files:

```
.../lib/backgrounds/background1.png
.../lib/backgrounds/background2.png
.../lib/backgrounds/background3.png
```

To include, say, the first image, the `pubspec.yaml` of the application should specify it in the `assets` section:

yaml

```
flutter:
  assets:
    - packages/fancy_backgrounds/backgrounds/background1.png
```

The `lib/` is implied, so it should not be included in the asset path.

If you are developing a package, to load an asset within the package, specify it in the `pubspec.yaml` of the package:

yaml

```
flutter:
  assets:
    - assets/images/
```

To load the image within your package, use:

dart

```
return const AssetImage('packages/fancy_backgrounds/backgrounds/background1.png');
```

## Sharing assets with the underlying platform

[#](#sharing-assets-with-the-underlying-platform)

Flutter assets are readily available to platform code using the `AssetManager` on Android and `NSBundle` on iOS.

### Loading Flutter assets in Android

[#](#loading-flutter-assets-in-android)

On Android the assets are available through the [`AssetManager`](https://developer.android.com/reference/android/content/res/AssetManager) API. The lookup key used in, for instance [`openFd`](https://developer.android.com/reference/android/content/res/AssetManager#openFd\(java.lang.String\)), is obtained from `lookupKeyForAsset` on [`PluginRegistry.Registrar`](https://api.flutter.dev/javadoc/io/flutter/plugin/common/PluginRegistry.Registrar.html) or `getLookupKeyForAsset` on [`FlutterView`](https://api.flutter.dev/javadoc/io/flutter/view/FlutterView.html). `PluginRegistry.Registrar` is available when developing a plugin while `FlutterView` would be the choice when developing an app including a platform view.

As an example, suppose you have specified the following in your pubspec.yaml

yaml

```
flutter:
  assets:
    - icons/heart.png
```

This reflects the following structure in your Flutter app.

```
.../pubspec.yaml
.../icons/heart.png
...etc.
```

To access `icons/heart.png` from your Java plugin code, do the following:

java

```
AssetManager assetManager = registrar.context().getAssets();
String key = registrar.lookupKeyForAsset("icons/heart.png");
AssetFileDescriptor fd = assetManager.openFd(key);
```

### Loading Flutter assets in iOS

[#](#loading-flutter-assets-in-ios)

On iOS the assets are available through the [`mainBundle`](https://developer.apple.com/documentation/foundation/nsbundle/1410786-mainbundle). The lookup key used in, for instance [`pathForResource:ofType:`](https://developer.apple.com/documentation/foundation/nsbundle/1410989-pathforresource), is obtained from `lookupKeyForAsset` or `lookupKeyForAsset:fromPackage:` on [`FlutterPluginRegistrar`](https://api.flutter.dev/ios-embedder/protocol_flutter_plugin_registrar-p.html), or `lookupKeyForAsset:` or `lookupKeyForAsset:fromPackage:` on [`FlutterViewController`](https://api.flutter.dev/ios-embedder/interface_flutter_view_controller.html). `FlutterPluginRegistrar` is available when developing a plugin while `FlutterViewController` would be the choice when developing an app including a platform view.

As an example, suppose you have the Flutter setting from above.

To access `icons/heart.png` from your Objective-C plugin code you would do the following:

objc

```
NSString* key = [registrar lookupKeyForAsset:@"icons/heart.png"];
NSString* path = [[NSBundle mainBundle] pathForResource:key ofType:nil];
```

To access `icons/heart.png` from your Swift app you would do the following:

swift

```
let key = controller.lookupKey(forAsset: "icons/heart.png")
let mainBundle = Bundle.main
let path = mainBundle.path(forResource: key, ofType: nil)
```

For a more complete example, see the implementation of the Flutter [`video_player` plugin](https://pub.dev/packages/video_player) on pub.dev.

### Loading iOS images in Flutter

[#](#loading-ios-images-in-flutter)

When implementing Flutter by [adding it to an existing iOS app](/add-to-app/ios), you might have images hosted in iOS that you want to use in Flutter. To accomplish that, use [platform channels](/platform-integration/platform-channels) to pass the image data to Dart as `FlutterStandardTypedData`.

## Platform assets

[#](#platform-assets)

There are other occasions to work with assets in the platform projects directly. Below are two common cases where assets are used before the Flutter framework is loaded and running.

### Updating the app icon

[#](#updating-the-app-icon)

Updating a Flutter application's launch icon works the same way as updating launch icons in native Android or iOS applications.

#### Android

[#](#android)

In your Flutter project's root directory, navigate to `.../android/app/src/main/res`. The various bitmap resource folders such as `mipmap-hdpi` already contain placeholder images named `ic_launcher.png`. Replace them with your desired assets respecting the recommended icon size per screen density as indicated by the [Android Developer Guide](https://developer.android.com/training/multiscreen/screendensities).

#### iOS

[#](#ios)

In your Flutter project's root directory, navigate to `.../ios/Runner`. The `Assets.xcassets/AppIcon.appiconset` directory already contains placeholder images. Replace them with the appropriately sized images as indicated by their filename as dictated by the Apple [Human Interface Guidelines](https://developer.apple.com/design/human-interface-guidelines/app-icons). Keep the original file names.

### Updating the launch screen

[#](#updating-the-launch-screen)

Flutter also uses native platform mechanisms to draw transitional launch screens to your Flutter app while the Flutter framework loads. This launch screen persists until Flutter renders the first frame of your application.

#### Android

[#](#android-1)

To add a launch screen (also known as "splash screen") to your Flutter application, navigate to `.../android/app/src/main`. In `res/drawable/launch_background.xml`, use this [layer list drawable](https://developer.android.com/guide/topics/resources/drawable-resource#LayerList) XML to customize the look of your launch screen. The existing template provides an example of adding an image to the middle of a white splash screen in commented code. You can uncomment it or use other [drawables](https://developer.android.com/guide/topics/resources/drawable-resource) to achieve the intended effect.

For more details, see [Adding a splash screen to your Android app](/platform-integration/android/splash-screen).

#### iOS

[#](#ios-1)

To add an image to the center of your "splash screen", navigate to `.../ios/Runner`. In `Assets.xcassets/LaunchImage.imageset`, drop in images named `LaunchImage.png`, `LaunchImage@2x.png`, `LaunchImage@3x.png`. If you use different filenames, update the `Contents.json` file in the same directory.

You can also fully customize your launch screen storyboard in Xcode by opening `.../ios/Runner.xcworkspace`. Navigate to `Runner/Runner` in the Project Navigator and drop in images by opening `Assets.xcassets` or do any customization using the Interface Builder in `LaunchScreen.storyboard`.

For more details, see [Adding a splash screen to your iOS app](/platform-integration/ios/splash-screen).

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-09-29. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/ui/assets/assets-and-images.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/ui/assets/assets-and-images\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/ui/assets/assets-and-images.md "Report an issue with this page").

----
url: https://docs.flutter.dev/platform-integration/android/call-jetpack-apis
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Calling JetPack APIs](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

Flutter apps running on Android can always make use of the latest APIs on the first day they are released on Android, no matter what. This page outlines available ways to invoke Android-specific APIs.

## Use an existing solution

[#](#use-an-existing-solution)

In most scenarios, you can use a plugin (as shown in the next section) to invoke native APIs without writing any custom boilerplate or glue code yourself.

### Use a plugin

[#](#use-a-plugin)

Using a plugin is often the easiest way to access native APIs, regardless of where your Flutter app is running. To use plugins, visit [pub.dev](https://pub.dev) and search for the topic you need. Most native features, including accessing common hardware like GPS, the camera, or step counters are supported by robust plugins.

For complete guidance on adding plugins to your Flutter app, see the [Using packages documentation](/packages-and-plugins/using-packages).

Not all native features are supported by plugins, especially immediately after their release. In any scenario where your desired native feature is not covered by a package on [pub.dev](https://pub.dev), continue on to the following sections.

## Creating a custom solution

[#](#creating-a-custom-solution)

Not all scenarios and APIs will be supported by existing solutions; but luckily, you can always add whatever support you need. The next sections describe two different ways to call native code from Dart.

### Call native code directly via FFI

[#](#call-native-code-directly-via-ffi)

The most direct and efficient way to invoke native APIs is by calling the API directly, via FFI. This links your Dart executable to any specified native code at compile-time, allowing you to call it directly from the UI thread through a small amount of glue code. In most cases, [ffigen](https://pub.dev/packages/ffigen) or [jnigen](https://pub.dev/packages/jnigen) are helpful in writing this glue code.

For complete guidance on directly calling native code from your Flutter app, see the [FFI documentation](https://dart.dev/interop/c-interop).

In the coming months, the Dart team hopes to make this process easier with direct support for calling native APIs using the FFI approach, but without any need for the developer to write glue code.

### Add a MethodChannel

[#](#add-a-methodchannel)

[`MethodChannel`](https://api.flutter.dev/flutter/services/MethodChannel-class.html)s are an alternate way Flutter apps can invoke arbitrary native code. Unlike the FFI solution described in the previous step, MethodChannels are always asynchronous, which might or might not matter to you, depending on your use case. As with FFI and direct calls to native code, using a `MethodChannel` requires a small amount of glue code to translate your Dart objects into native objects, and then back again. In most cases, [`pkg:pigeon`](https://pub.dev/packages/pigeon) is helpful in writing this glue code.

For complete guidance on adding MethodChannels to your Flutter app, see the [`MethodChannel`s documentation](/platform-integration/platform-channels).

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/platform-integration/android/call-jetpack-apis.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/platform-integration/android/call-jetpack-apis\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/platform-integration/android/call-jetpack-apis.md "Report an issue with this page").

----
url: https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.55.0
----

The 2.55.0 release of the Dart and Flutter DevTools includes the following changes among other general improvements. To learn more about DevTools, check out the [DevTools overview](/tools/devtools).

## Full commit history

[#](#full-commit-history)

To find a complete list of changes in this release, check out the [DevTools git log](https://github.com/flutter/devtools/tree/v2.55.0).

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/release-notes/release-notes-2.55.0.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.55.0\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/release-notes/release-notes-2.55.0.md "Report an issue with this page").

----
url: https://docs.flutter.dev/cookbook/plugins/play-video.md
----

\# Play and pause a video > How to use the video\_player plugin.

<!--?code-excerpt path-base="cookbook/plugins/play_video/"?-->

Playing videos is a common task in app development, and Flutter apps are no exception. To play videos, the Flutter team provides the \[\`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. :::warning At this time, the \`video\_player\` plugin doesn't work on Linux and Windows. To learn more, check out the \[\`video\_player\`]\[] package. ::: On iOS, the \`video\_player\` plugin makes use of \[\`AVPlayer\`]\[] to handle playback. On Android, it uses \[\`ExoPlayer\`]\[]. This recipe demonstrates how to use the \`video\_player\` package to stream a video from the internet with basic play and pause controls using the following steps: 1. Add the \`video\_player\` dependency. 2. Add permissions to your app. 3. Create and initialize a \`VideoPlayerController\`. 4. Display the video player. 5. Play and pause the video. ## 1. Add the \`video\_player\` dependency This recipe depends on one Flutter plugin: \`video\_player\`. First, add this dependency to your project. To add the \`video\_player\` package as a dependency, run \`flutter pub add\`: \`\`\`console $ flutter pub add video\_player \`\`\` ## 2. Add permissions to your app Next, update your \`android\` and \`ios\` configurations to ensure that your app has the correct permissions to stream videos from the internet. ### Android 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 \`\`\` ### iOS For iOS, add the following to the \`Info.plist\` file found at \`/ios/Runner/Info.plist\`. \`\`\`xml NSAppTransportSecurity NSAllowsArbitraryLoads \`\`\` :::warning The \`video\_player\` plugin can only play asset videos in iOS simulators. You must test network-hosted videos on physical iOS devices. ::: ### macOS If you use network-based videos, \[add the \`com.apple.security.network.client\` entitlement]\[mac-entitlement]. ### Web Flutter web does \*\*not\*\* support \`dart:io\`, so avoid using the \`VideoPlayerController.file\` constructor for the plugin. Using this constructor attempts to create a\`VideoPlayerController.file\` that throws an \`UnimplementedError\`. Different web browsers might have different video-playback capabilities, such as supported formats or autoplay. Check the \[video\_player\_web] package for more web-specific information. The \`VideoPlayerOptions.mixWithOthers\` option can't be implemented in web, at least at the moment. If you use this option in web it will be silently ignored. ## 3. Create and initialize a \`VideoPlayerController\` Now that you have the \`video\_player\` plugin installed with the correct 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. 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\` 3. Add a variable to the \`State\` class to store the \`Future\` returned from \`VideoPlayerController.initialize\` 4. Create and initialize the controller in the \`initState\` method 5. Dispose of the controller in the \`dispose\` method

<!--?code-excerpt "lib/main_step3.dart (VideoPlayerScreen)"?-->

\`\`\`dart class VideoPlayerScreen extends StatefulWidget { const VideoPlayerScreen({super.key}); @override State createState() => \_VideoPlayerScreenState(); } class \_VideoPlayerScreenState extends State { late VideoPlayerController \_controller; late Future \_initializeVideoPlayerFuture; @override void initState() { super.initState(); // Create and store the VideoPlayerController. The VideoPlayerController // offers several different constructors to play videos from assets, files, // or the internet. \_controller = VideoPlayerController.networkUrl( Uri.parse( 'https\://flutter.github.io/assets-for-api-docs/assets/videos/butterfly.mp4', ), ); \_initializeVideoPlayerFuture = \_controller.initialize(); } @override void dispose() { // Ensure disposing of the VideoPlayerController to free up resources. \_controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { // Complete the code in the next step. return Container(); } } \`\`\` ## 4. Display the video player Now, display the video. The \`video\_player\` plugin provides the \[\`VideoPlayer\`]\[] 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, wrap the \`VideoPlayer\` widget in an \[\`AspectRatio\`]\[] widget to ensure that the video has the correct proportions. Furthermore, you must display the \`VideoPlayer\` widget after the \`\_initializeVideoPlayerFuture()\` completes. Use \`FutureBuilder\` to display a loading spinner until the controller finishes initializing. Note: initializing the controller does not begin playback.

<!--?code-excerpt "lib/main.dart (FutureBuilder)" replace="/body: //g;/^\),$/)/g"?-->

\`\`\`dart // 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 video. return AspectRatio( aspectRatio: \_controller.value.aspectRatio, // Use the VideoPlayer widget to display the video. child: VideoPlayer(\_controller), ); } else { // If the VideoPlayerController is still initializing, show a // loading spinner. return const Center(child: CircularProgressIndicator()); } }, ) \`\`\` ## 5. Play and pause the video By default, the video starts in a paused state. To begin playback, call the \[\`play()\`]\[] method provided by the \`VideoPlayerController\`. To pause playback, call the \[\`pause()\`]\[] 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.

<!--?code-excerpt "lib/main.dart (FAB)" replace="/^floatingActionButton: //g;/^\),$/)/g"?-->

\`\`\`dart FloatingActionButton( onPressed: () { // 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. \_controller.play(); } }); }, // Display the correct icon depending on the state of the player. child: Icon( \_controller.value.isPlaying ? Icons.pause : Icons.play\_arrow, ), ) \`\`\` ## Complete example

<!--?code-excerpt "lib/main.dart"?-->

\`\`\`dartpad title="Flutter video player hands-on example in DartPad" run="true" import 'dart:async'; import 'package:flutter/material.dart'; import 'package:video\_player/video\_player.dart'; void main() => runApp(const VideoPlayerApp()); class VideoPlayerApp extends StatelessWidget { const VideoPlayerApp({super.key}); @override Widget build(BuildContext context) { return const MaterialApp( title: 'Video Player Demo', home: VideoPlayerScreen(), ); } } class VideoPlayerScreen extends StatefulWidget { const VideoPlayerScreen({super.key}); @override State createState() => \_VideoPlayerScreenState(); } class \_VideoPlayerScreenState extends State { late VideoPlayerController \_controller; late Future \_initializeVideoPlayerFuture; @override void initState() { super.initState(); // Create and store the VideoPlayerController. The VideoPlayerController // offers several different constructors to play videos from assets, files, // or the internet. \_controller = VideoPlayerController.networkUrl( Uri.parse( 'https\://flutter.github.io/assets-for-api-docs/assets/videos/butterfly.mp4', ), ); // Initialize the controller and store the Future for later use. \_initializeVideoPlayerFuture = \_controller.initialize(); // Use the controller to loop the video. \_controller.setLooping(true); } @override void dispose() { // Ensure disposing of the VideoPlayerController to free up resources. \_controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('Butterfly Video')), // 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. return AspectRatio( aspectRatio: \_controller.value.aspectRatio, // Use the VideoPlayer widget to display the video. child: VideoPlayer(\_controller), ); } else { // If the VideoPlayerController is still initializing, show a // loading spinner. return const Center(child: CircularProgressIndicator()); } }, ), floatingActionButton: FloatingActionButton( onPressed: () { // 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. \_controller.play(); } }); }, // Display the correct icon depending on the state of the player. child: Icon( \_controller.value.isPlaying ? Icons.pause : Icons.play\_arrow, ), ), ); } } \`\`\` \[\`AspectRatio\`]: https\://api.flutter.dev/flutter/widgets/AspectRatio-class.html \[\`AVPlayer\`]: https\://developer.apple.com/documentation/avfoundation/avplayer \[\`ExoPlayer\`]: https\://google.github.io/ExoPlayer/ \[\`pause()\`]: https\://pub.dev/documentation/video\_player/latest/video\_player/VideoPlayerController/pause.html \[\`play()\`]: https\://pub.dev/documentation/video\_player/latest/video\_player/VideoPlayerController/play.html \[\`video\_player\`]: https\://pub.dev/packages/video\_player \[\`VideoPlayer\`]: https\://pub.dev/documentation/video\_player/latest/video\_player/VideoPlayer-class.html \[mac-entitlement]: /platform-integration/macos/building#entitlements-and-the-app-sandbox \[video\_player\_web]: https\://pub.dev/packages/video\_player\_web

----
url: https://docs.flutter.dev/ui/adaptive-responsive/input.md
----

\# User input & accessibility > A truly adaptive app also handles differences in how user input works and also programs to help folks with accessibility issues.

<!--?code-excerpt path-base="ui/adaptive_app_demos"?-->

It isn't enough to just adapt how your app looks, you also have to support a variety of user inputs. The mouse and keyboard introduce input types beyond those found on a touch device, like scroll wheel, right-click, hover interactions, tab traversal, and keyboard shortcuts. Some of these features work by default on Material widgets. But, if you've created a custom widget, you might need to implement them directly. Some features that encompass a well-designed app, also help users who work with assistive technologies. For example, aside from being \*\*good app design\*\*, some features, like tab traversal and keyboard shortcuts, are \_critical for users who work with assistive devices\_. In addition to the standard advice for \[creating accessible apps]\[], this page covers info for creating apps that are both adaptive \_and\_ accessible. \[creating accessible apps]: /ui/accessibility ## Scroll wheel for custom widgets Scrolling widgets like \`ScrollView\` or \`ListView\` support the scroll wheel by default, and because almost every scrollable custom widget is built using one of these, it works with those as well. If you need to implement custom scroll behavior, you can use the \[\`Listener\`]\[] widget, which lets you customize how your UI reacts to the scroll wheel.

<!--?code-excerpt "lib/widgets/extra_widget_excerpts.dart (pointer-scroll)"?-->

\`\`\`dart return Listener( onPointerSignal: (event) { if (event is PointerScrollEvent) print(event.scrollDelta.dy); }, child: ListView(), ); \`\`\` \[\`Listener\`]: https\://api.flutter.dev/flutter/widgets/Listener-class.html ## Tab traversal and focus interactions Users with physical keyboards expect that they can use the tab key to quickly navigate an application, and users with motor or vision differences often rely completely on keyboard navigation. There are two considerations for tab interactions: how focus moves from widget to widget, known as traversal, and the visual highlight shown when a widget is focused. Most built-in components, like buttons and text fields, support traversal and highlights by default. If you have your own widget that you want included in traversal, you can use the \[\`FocusableActionDetector\`]\[] widget to create your own controls. The \[\`FocusableActionDetector\`]\[] widget is helpful for combining focus, mouse input, and shortcuts together in one widget. You can create a detector that defines actions and key bindings, and provides callbacks for handling focus and hover highlights.

<!--?code-excerpt "lib/pages/focus_examples_page.dart (focusable-action-detector)"?-->

\`\`\`dart class \_BasicActionDetectorState extends State { bool \_hasFocus = false; @override Widget build(BuildContext context) { return FocusableActionDetector( onFocusChange: (value) => setState(() => \_hasFocus = value), actions: >{ ActivateIntent: CallbackAction( onInvoke: (intent) { print('Enter or Space was pressed!'); return null; }, ), }, child: Stack( clipBehavior: Clip.none, children: \[ const FlutterLogo(size: 100), // Position focus in the negative margin for a cool effect if (\_hasFocus) Positioned( left: -4, top: -4, bottom: -4, right: -4, child: \_roundedBorder(), ), ], ), ); } } \`\`\` \[\`Actions\`]: https\://api.flutter.dev/flutter/widgets/Actions-class.html \[\`Focus\`]: https\://api.flutter.dev/flutter/widgets/Focus-class.html \[\`FocusableActionDetector\`]: https\://api.flutter.dev/flutter/widgets/FocusableActionDetector-class.html \[\`MouseRegion\`]: https\://api.flutter.dev/flutter/widgets/MouseRegion-class.html \[\`Shortcuts\`]: https\://api.flutter.dev/flutter/widgets/Shortcuts-class.html ### Controlling traversal order To get more control over the order that widgets are focused on when the user tabs through, you can use \[\`FocusTraversalGroup\`]\[] to define sections of the tree that should be treated as a group when tabbing. For example, you might to tab through all the fields in a form before tabbing to the submit button:

<!--?code-excerpt "lib/pages/focus_examples_page.dart (focus-traversal-group)"?-->

\`\`\`dart return Column( children: \[ FocusTraversalGroup(child: MyFormWithMultipleColumnsAndRows()), SubmitButton(), ], ); \`\`\` Flutter has several built-in ways to traverse widgets and groups, defaulting to the \`ReadingOrderTraversalPolicy\` class. This class usually works well, but it's possible to modify this using another predefined \`TraversalPolicy\` class or by creating a custom policy. \[\`FocusTraversalGroup\`]: https\://api.flutter.dev/flutter/widgets/FocusTraversalGroup-class.html ## Keyboard accelerators In addition to tab traversal, desktop and web users are accustomed to having various keyboard shortcuts bound to specific actions. Whether it's the \`Delete\` key for quick deletions or \`Control+N\` for a new document, be sure to consider the different accelerators your users expect. The keyboard is a powerful input tool, so try to squeeze as much efficiency from it as you can. Your users will appreciate it! Keyboard accelerators can be accomplished in a few ways in Flutter, depending on your goals. If you have a single widget like a \`TextField\` or a \`Button\` that already has a focus node, you can wrap it in a \[\`KeyboardListener\`]\[] or a \[\`Focus\`]\[] widget and listen for keyboard events:

<!--?code-excerpt "lib/pages/focus_examples_page.dart (focus-keyboard-listener)"?-->

\`\`\`dart @override Widget build(BuildContext context) { return Focus( onKeyEvent: (node, event) { if (event is KeyDownEvent) { print(event.logicalKey); } return KeyEventResult.ignored; }, child: ConstrainedBox( constraints: const BoxConstraints(maxWidth: 400), child: const TextField( decoration: InputDecoration(border: OutlineInputBorder()), ), ), ); } } \`\`\` To apply a set of keyboard shortcuts to a large section of the tree, use the \[\`Shortcuts\`]\[] widget:

<!--?code-excerpt "lib/widgets/extra_widget_excerpts.dart (shortcuts)"?-->

\`\`\`dart // Define a class for each type of shortcut action you want class CreateNewItemIntent extends Intent { const CreateNewItemIntent(); } Widget build(BuildContext context) { return Shortcuts( // Bind intents to key combinations shortcuts: const { SingleActivator(LogicalKeyboardKey.keyN, control: true): CreateNewItemIntent(), }, child: Actions( // Bind intents to an actual method in your code actions: >{ CreateNewItemIntent: CallbackAction( onInvoke: (intent) => \_createNewItem(), ), }, // Your sub-tree must be wrapped in a focusNode, so it can take focus. child: Focus(autofocus: true, child: Container()), ), ); } \`\`\` The \[\`Shortcuts\`]\[] widget is useful because it only allows shortcuts to be fired when this widget tree or one of its children has focus and is visible. The final option is a global listener. This listener can be used for always-on, app-wide shortcuts or for panels that can accept shortcuts whenever they're visible (regardless of their focus state). Adding global listeners is easy with \[\`HardwareKeyboard\`]\[]:

<!--?code-excerpt "lib/widgets/extra_widget_excerpts.dart (hardware-keyboard)"?-->

\`\`\`dart @override void initState() { super.initState(); HardwareKeyboard.instance.addHandler(\_handleKey); } @override void dispose() { HardwareKeyboard.instance.removeHandler(\_handleKey); super.dispose(); } \`\`\` To check key combinations with the global listener, you can use the \`HardwareKeyboard.instance.logicalKeysPressed\` set. For example, a method like the following can check whether any of the provided keys are being held down:

<!--?code-excerpt "lib/widgets/extra_widget_excerpts.dart (keys-pressed)"?-->

\`\`\`dart static bool isKeyDown(Set keys) { return keys .intersection(HardwareKeyboard.instance.logicalKeysPressed) .isNotEmpty; } \`\`\` Putting these two things together, you can fire an action when \`Shift+N\` is pressed:

<!--?code-excerpt "lib/widgets/extra_widget_excerpts.dart (handle-key)"?-->

\`\`\`dart bool \_handleKey(KeyEvent event) { bool isShiftDown = isKeyDown({ LogicalKeyboardKey.shiftLeft, LogicalKeyboardKey.shiftRight, }); if (isShiftDown && event.logicalKey == LogicalKeyboardKey.keyN) { \_createNewItem(); return true; } return false; } \`\`\` One note of caution when using the static listener, is that you often need to disable it when the user is typing in a field or when the widget it's associated with is hidden from view. Unlike with \`Shortcuts\` or \`KeyboardListener\`, this is your responsibility to manage. This can be especially important when you're binding a Delete/Backspace accelerator for \`Delete\`, but then have child \`TextFields\` that the user might be typing in. \[\`HardwareKeyboard\`]: https\://api.flutter.dev/flutter/services/HardwareKeyboard-class.html \[\`KeyboardListener\`]: https\://api.flutter.dev/flutter/widgets/KeyboardListener-class.html ## Mouse enter, exit, and hover for custom widgets {:#custom-widgets} On desktop, it's common to change the mouse cursor to indicate the functionality about the content the mouse is hovering over. For example, you typically see a hand cursor when you hover over a button, or an \`I\` cursor when you hover over text. Flutter's Material buttons handle basic focus states for standard button and text cursors. (A notable exception is if you change the default styling of the Material buttons to set the \`overlayColor\` to transparent.) Implement a focus state for any custom buttons or gesture detectors in your app. If you change the default Material button styles, test for keyboard focus states and implement your own, if needed. To change the cursor from within your custom widgets, use \[\`MouseRegion\`]\[]:

<!--?code-excerpt "lib/pages/focus_examples_page.dart (mouse-region)"?-->

\`\`\`dart // Show hand cursor return MouseRegion( cursor: SystemMouseCursors.click, // Request focus when clicked child: GestureDetector( onTap: () { Focus.of(context).requestFocus(); \_submit(); }, child: Logo(showBorder: hasFocus), ), ); \`\`\` \`MouseRegion\` is also useful for creating custom rollover and hover effects:

<!--?code-excerpt "lib/pages/focus_examples_page.dart (mouse-over)"?-->

\`\`\`dart return MouseRegion( onEnter: (\_) => setState(() => \_isMouseOver = true), onExit: (\_) => setState(() => \_isMouseOver = false), onHover: (e) => print(e.localPosition), child: Container( height: 500, color: \_isMouseOver ? Colors.blue : Colors.black, ), ); \`\`\` For an example that changes the button style to outline the button when it has focus, check out the \[button code for the Wonderous app]\[]. The app modifies the \[\`FocusNode.hasFocus\`]\[] property to check whether the button has focus and, if so, adds an outline. \[button code for the Wonderous app]: https\://github.com/gskinnerTeam/flutter-wonderous-app/blob/8a29d6709668980340b1b59c3d3588f123edd4d8/lib/ui/common/controls/buttons.dart#L143 \[\`FocusNode.hasFocus\`]: https\://api.flutter.dev/flutter/widgets/FocusNode/hasFocus.html ## Visual density You might consider enlarging the "hit area" of a widget to accommodate a touch screen, for example. Different input devices offer various levels of precision, which necessitate differently-sized hit areas. Flutter's \`VisualDensity\` class makes it easy to adjust the density of your views across the entire application, for example, by making a button larger (and therefore easier to tap) on a touch device. When you change the \`VisualDensity\` for your \`MaterialApp\`, \`MaterialComponents\` that support it animate their densities to match. By default, both horizontal and vertical densities are set to 0.0, but you can set the densities to any negative or positive value that you want. By switching between different densities, you can easily adjust your UI. !\[Adaptive scaffold]\(/assets/images/docs/ui/adaptive-responsive/adaptive\_scaffold.webp){:width="100%"} To set a custom visual density, inject the density into your \`MaterialApp\` theme:

<!--?code-excerpt "lib/main.dart (visual-density)"?-->

\`\`\`dart double densityAmt = touchMode ? 0.0 : -1.0; VisualDensity density = VisualDensity( horizontal: densityAmt, vertical: densityAmt, ); return MaterialApp( theme: ThemeData(visualDensity: density), home: MainAppScaffold(), debugShowCheckedModeBanner: false, ); \`\`\` To use \`VisualDensity\` inside your own views, you can look it up:

<!--?code-excerpt "lib/pages/adaptive_reflow_page.dart (visual-density-own-view)"?-->

\`\`\`dart VisualDensity density = Theme.of(context).visualDensity; \`\`\` Not only does the container react automatically to changes in density, it also animates when it changes. This ties together your custom components, along with the built-in components, for a smooth transition effect across the app. As shown, \`VisualDensity\` is unit-less, so it can mean different things to different views. In the following example, 1 density unit equals 6 pixels, but this is totally up to you to decide. The fact that it is unit-less makes it quite versatile, and it should work in most contexts. It's worth noting that the Material generally use a value of around 4 logical pixels for each visual density unit. For more information about the supported components, see the \[\`VisualDensity\`]\[] API. For more information about density principles in general, see the \[Material Design guide]\[]. \[Material Design guide]: https\://m2.material.io/design/layout/applying-density.html#usage \[\`VisualDensity\`]: https\://pub.dev/documentation/material\_ui/latest/material\_ui/VisualDensity-class.html

----
url: https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.54.0.md
----

\# DevTools 2.54.0 release notes The 2.54.0 release of the Dart and Flutter DevTools includes the following changes among other general improvements. To learn more about DevTools, check out the \[DevTools overview]\(/tools/devtools/overview). ## General updates - Dropped connections to DTD will now automatically be retried to improve the experience when your machine is resumed from sleep. - \[#9587]\(https\://github.com/flutter/devtools/pull/9587) ## Inspector updates - Added a warning banner that the legacy inspector will be removed in a future release. - \[#9572]\(https\://github.com/flutter/devtools/pull/9572) ## Memory updates - Fixed an error preventing users from changing the zoom duration. - \[#9573]\(https\://github.com/flutter/devtools/pull/9573) ## Deep links tool updates - Added a more informative dialog if the Deep Links tool is unable to find build options for the iOS or Android app. - \[#9571]\(https\://github.com/flutter/devtools/pull/9571) - Fixed null error when parsing universal link settings. - \[#9581]\(https\://github.com/flutter/devtools/pull/9581) ## Full commit history To find a complete list of changes in this release, check out the \[DevTools git log]\(https\://github.com/flutter/devtools/tree/v2.54.0).

----
url: https://docs.flutter.dev/ui/internationalization
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Internationalizing Flutter apps](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

If your app might be deployed to users who speak another language then you'll need to internationalize it. That means you need to write the app in a way that makes it possible to localize values like text and layouts for each language or locale that the app supports. Flutter provides widgets and classes that help with internationalization and the Flutter libraries themselves are internationalized.

This page covers concepts and workflows necessary to localize a Flutter application using the `MaterialApp` and `CupertinoApp` classes, as most apps are written that way. However, applications written using the lower level `WidgetsApp` class can also be internationalized using the same classes and logic.

## Introduction to localizations in Flutter

[#](#introduction-to-localizations-in-flutter)

This section provides a tutorial on how to create and internationalize a new Flutter application, along with any additional setup that a target platform might require.

You can find the source code for this example in [`gen_l10n_example`](https://github.com/flutter/website/tree/main/examples/internationalization/gen_l10n_example).

### Set up an internationalized app with `flutter_localizations`

[#](#setting-up)

By default, Flutter only provides US English localizations. To add support for other languages, an application must specify additional `MaterialApp` (or `CupertinoApp`) properties, and include a package called `flutter_localizations`.

To begin, start by creating a new Flutter application in a directory of your choice with the `flutter create` command.

```
$ flutter create <name_of_flutter_app>
```

Flutter projects will soon be encouraged to use the `material_ui` and `cupertino_ui` packages, but the `flutter create` command hasn't yet been updated to do this by default. You can get ahead of things and migrate now by running the following command:

```
$ dart fix --apply --code=migrate_design_widgets
```

To use `flutter_localizations`, add it and the `intl` package as dependencies:

```
$ flutter pub add flutter_localizations:"{sdk: flutter}" intl:any
```

This updates your `pubspec.yaml` file with the following entries:

pubspec.yaml

yaml

```
dependencies:
  flutter:
    sdk: flutter
  flutter_localizations:
    sdk: flutter
  intl: any
```

Import the package with the `flutter_localizations` prefix:

dart

```
import 'package:flutter_localizations/flutter_localizations.dart'
    as flutter_localizations;
```

Then specify `localizationsDelegates` and `supportedLocales` for your `MaterialApp`, `CupertinoApp`, or `WidgetsApp`:

dart

```
return const MaterialApp(
  title: 'Localizations Sample App',
  localizationsDelegates: GlobalMaterialLocalizations.delegates,
  supportedLocales: [
    Locale('en'), // English
    Locale('es'), // Spanish
  ],
  home: MyHomePage(),
);
```

After depending on `flutter_localizations` and adding the previous code, Flutter localizes the Material and Cupertino libraries in the selected supported locale. Widgets should be adapted to the localized messages and use the appropriate left-to-right or right-to-left layout.

Try switching the target platform's locale to Spanish (`es`) and the messages should be localized.

The full `Locale.fromSubtags` constructor is preferred as it supports [`scriptCode`](https://api.flutter.dev/flutter/package-intl_locale/Locale/scriptCode.html), though the `Locale` default constructor is still fully valid.

The elements of the `localizationsDelegates` list are factories that produce collections of localized values. `GlobalMaterialLocalizations.delegate` provides localized strings and other values for the Material Components library. `flutter_localizations.GlobalWidgetsLocalizations.delegate` defines the default text direction, either left-to-right or right-to-left, for the `widgets.dart` library.

More information about these app properties, the types they depend on, and how internationalized Flutter apps are typically structured, is covered on this page.

[]()[]()

### Override the locale

[#](#override-the-locale)

`Localizations.override` is a factory constructor for the `Localizations` widget that allows for (the typically rare) situation where a section of your application needs to be localized to a different locale than the locale configured for your device.

To observe this behavior, add a call to `Localizations.override` and a simple `CalendarDatePicker`:

dart

```
Widget build(BuildContext context) {
  return Scaffold(
    appBar: AppBar(title: Text(widget.title)),
    body: Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          // Add the following code
          Localizations.override(
            context: context,
            locale: const Locale('es'),
            // Using a Builder to get the correct BuildContext.
            // Alternatively, you can create a new widget and Localizations.override
            // will pass the updated BuildContext to the new widget.
            child: Builder(
              builder: (context) {
                // A toy example for an internationalized Material widget.
                return CalendarDatePicker(
                  initialDate: DateTime.now(),
                  firstDate: DateTime(1900),
                  lastDate: DateTime(2100),
                  onDateChanged: (value) {},
                );
              },
            ),
          ),
        ],
      ),
    ),
  );
}
```

Hot reload the app and the `CalendarDatePicker` widget should re-render in Spanish.

[]()[]()

### Add your own localized messages

[#](#add-your-own-localized-messages)

To add localized text to your application, complete the following instructions:

1. Add the `flutter_localizations` and `intl` packages as dependencies:

   ```
   $ flutter pub add flutter_localizations:"{sdk: flutter}" intl:any
   ```

   This adds the following entries in the `pubspec.yaml` file:

   pubspec.yaml

   yaml

   ```
   dependencies:
     flutter:
       sdk: flutter
     flutter_localizations:
       sdk: flutter
     intl: any
   ```

2. Open the `pubspec.yaml` file and enable the `generate` flag. This flag is found in the `flutter` section in the pubspec file.

   pubspec.yaml

   yaml

   ```
   # The following section is specific to Flutter.
   flutter:
     generate: true # Add this line
   ```

3. Add a new YAML file to the root directory of the Flutter project. Name this file `l10n.yaml` and include the following content:

   l10n.yaml

   yaml

   ```
   arb-dir: lib/l10n
   template-arb-file: app_en.arb
   output-localization-file: app_localizations.dart
   ```

   This file configures the localization tool. In this example, you've done the following:

   * Put the [App Resource Bundle](https://github.com/google/app-resource-bundle) (`.arb`) input files in `${FLUTTER_PROJECT}/lib/l10n`. The `.arb` provide localization resources for your app.
   * Set the English template as `app_en.arb`.
   * Told Flutter to generate localizations in the `app_localizations.dart` file.

4. In `${FLUTTER_PROJECT}/lib/l10n`, add the `app_en.arb` template file. For example:

   app\_en.arb

   json

   ```
   {
     "helloWorld": "Hello World!",
     "@helloWorld": {
       "description": "The conventional newborn programmer greeting"
     }
   }
   ```

5. Add another bundle file called `app_es.arb` in the same directory. In this file, add the Spanish translation of the same message.

   app\_es.arb

   json

   ```
   {
       "helloWorld": "¡Hola Mundo!"
   }
   ```

6. Now, run `flutter pub get` or `flutter run` and code generation takes place automatically. You should find generated files in the directory at the path you specified with the `arb-dir` or `output-dir` options Alternatively, you can also run `flutter gen-l10n` to generate the same files without running the app.

7. Import `app_localizations.dart`, then add `AppLocalizations.delegate` to the `localizationsDelegates` list in the `MaterialApp` constructor:

   dart

   ```
   import 'l10n/app_localizations.dart';
   ```

   dart

   ```
   return const MaterialApp(
     title: 'Localizations Sample App',
     localizationsDelegates: [
       AppLocalizations.delegate, // Add this line
       ...GlobalMaterialLocalizations.delegates,
     ],
     supportedLocales: [
       Locale('en'), // English
       Locale('es'), // Spanish
     ],
     home: MyHomePage(),
   );
   ```

   The `AppLocalizations` class also provides an auto-generated `supportedLocales` list. You can use it instead of providing the locales manually.

   dart

   ```
   const MaterialApp(
     title: 'Localizations Sample App',
     localizationsDelegates: [
       AppLocalizations.delegate,
       ...GlobalMaterialLocalizations.delegates,
     ],
     supportedLocales: AppLocalizations.supportedLocales,
   );
   ```

8. Once the Material app has started, you can use `AppLocalizations` anywhere in your app:

   dart

   ```
   appBar: AppBar(
     // The [AppBar] title text should update its message
     // according to the system locale of the target platform.
     // Switching between English and Spanish locales should
     // cause this text to update.
     title: Text(AppLocalizations.of(context)!.helloWorld),
   ),
   ```

   This code generates a `Text` widget that displays "Hello World!" if the target device's locale is set to English, and "¡Hola Mundo!" if the target device's locale is set to Spanish. In the `arb` files, the key of each entry is used as the method name of the getter, while the value of that entry contains the localized message.

The [`gen_l10n_example`](https://github.com/flutter/website/tree/main/examples/internationalization/gen_l10n_example) uses this tool.

To localize your device app description, pass the localized string to [`MaterialApp.onGenerateTitle`](https://pub.dev/documentation/material_ui/latest/material_ui/MaterialApp/onGenerateTitle.html):

dart

```
return MaterialApp(
  onGenerateTitle: (context) => DemoLocalizations.of(context).title,
```

### Placeholders, plurals, and selects

[#](#placeholders-plurals-and-selects)

You can also include application values in a message with special syntax that uses a *placeholder* to generate a method instead of a getter. A placeholder, which must be a valid Dart identifier name, becomes a positional parameter in the generated method in the `AppLocalizations` code. Define a placeholder name by wrapping it in curly braces as follows:

json

```
"{placeholderName}"
```

Define each placeholder in the `placeholders` object in the app's `.arb` file. For example, to define a hello message with a `userName` parameter, add the following to `lib/l10n/app_en.arb`:

json

```
"hello": "Hello {userName}",
"@hello": {
  "description": "A message with a single parameter",
  "placeholders": {
    "userName": {
      "type": "String",
      "example": "Bob"
    }
  }
}
```

This code snippet adds a `hello` method call to the `AppLocalizations.of(context)` object, and the method accepts a parameter of type `String`; the `hello` method returns a string. Regenerate the `AppLocalizations` file.

Replace the code passed into `Builder` with the following:

dart

```
// Examples of internationalized strings.
return Column(
  children: <Widget>[
    // Returns 'Hello John'
    Text(AppLocalizations.of(context)!.hello('John')),
  ],
);
```

You can also use numerical placeholders to specify multiple values. Different languages have different ways to pluralize words. The syntax also supports specifying *how* a word should be pluralized. A *pluralized* message must include a `num` parameter indicating how to pluralize the word in different situations. English, for example, pluralizes "person" to "people", but that doesn't go far enough. The `message0` plural might be "no people" or "zero people". The `messageFew` plural might be "several people", "some people", or "a few people". The `messageMany` plural might be "most people" or "many people", or "a crowd". Only the more general `messageOther` field is required. The following example shows what options are available:

json

```
"{countPlaceholder, plural, =0{message0} =1{message1} =2{message2} few{messageFew} many{messageMany} other{messageOther}}"
```

The previous expression is replaced by the message variation (`message0`, `message1`, ...) corresponding to the value of the `countPlaceholder`. Only the `messageOther` field is required.

The following example defines a message that pluralizes the word, "wombat":

json

```
"nWombats": "{count, plural, =0{no wombats} =1{1 wombat} other{{count} wombats}}",
"@nWombats": {
  "description": "A plural message",
  "placeholders": {
    "count": {
      "type": "num",
      "format": "compact"
    }
  }
}
```

Use a plural method by passing in the `count` parameter:

dart

```
// Examples of internationalized strings.
return Column(
  children: <Widget>[
    ...
    // Returns 'no wombats'
    Text(AppLocalizations.of(context)!.nWombats(0)),
    // Returns '1 wombat'
    Text(AppLocalizations.of(context)!.nWombats(1)),
    // Returns '5 wombats'
    Text(AppLocalizations.of(context)!.nWombats(5)),
  ],
);
```

Similar to plurals, you can also choose a value based on a `String` placeholder. This is most often used to support gendered languages. The syntax is as follows:

json

```
"{selectPlaceholder, select, case{message} ... other{messageOther}}"
```

The next example defines a message that selects a pronoun based on gender:

json

```
"pronoun": "{gender, select, male{he} female{she} other{they}}",
"@pronoun": {
  "description": "A gendered message",
  "placeholders": {
    "gender": {
      "type": "String"
    }
  }
}
```

Use this feature by passing the gender string as a parameter:

dart

```
// Examples of internationalized strings.
return Column(
  children: <Widget>[
    ...
    // Returns 'he'
    Text(AppLocalizations.of(context)!.pronoun('male')),
    // Returns 'she'
    Text(AppLocalizations.of(context)!.pronoun('female')),
    // Returns 'they'
    Text(AppLocalizations.of(context)!.pronoun('other')),
  ],
);
```

Keep in mind that when using `select` statements, comparison between the parameter and the actual value is case-sensitive. That is, `AppLocalizations.of(context)!.pronoun("Male")` defaults to the "other" case, and returns "they".

### Escaping syntax

[#](#escaping-syntax)

Sometimes, you have to use tokens, such as `{` and `}`, as normal characters. To ignore such tokens from being parsed, enable the `use-escaping` flag by adding the following to `l10n.yaml`:

yaml

```
use-escaping: true
```

The parser ignores any string of characters wrapped with a pair of single quotes. To use a normal single quote character, use a pair of consecutive single quotes. For example, the following text is converted to a Dart `String`:

json

```
{
  "helloWorld": "Hello! '{Isn''t}' this a wonderful day?"
}
```

The resulting string is as follows:

dart

```
"Hello! {Isn't} this a wonderful day?"
```

### Messages with numbers and currencies

[#](#messages-with-numbers-and-currencies)

Numbers, including those that represent currency values, are displayed very differently in different locales. The localizations generation tool in `flutter_localizations` uses the [`NumberFormat`](https://api.flutter.dev/flutter/package-intl_intl/NumberFormat-class.html) class in the `intl` package to format numbers based on the locale and the desired format.

The `int`, `double`, and `num` types can use any of the following `NumberFormat` constructors:

| Message "format" value    | Output for 1200000 |
| ------------------------- | ------------------ |
| `compact`                 | "1.2M"             |
| `compactCurrency`\*       | "$1.2M"            |
| `compactSimpleCurrency`\* | "$1.2M"            |
| `compactLong`             | "1.2 million"      |
| `currency`\*              | "USD1,200,000.00"  |
| `decimalPattern`          | "1,200,000"        |
| `decimalPatternDigits`\*  | "1,200,000"        |
| `decimalPercentPattern`\* | "120,000,000%"     |
| `percentPattern`          | "120,000,000%"     |
| `scientificPattern`       | "1E6"              |
| `simpleCurrency`\*        | "$1,200,000"       |

The starred `NumberFormat` constructors in the table offer optional, named parameters. Those parameters can be specified as the value of the placeholder's `optionalParameters` object. For example, to specify the optional `decimalDigits` parameter for `compactCurrency`, make the following changes to the `lib/l10n/app_en.arb` file:

json

```
"numberOfDataPoints": "Number of data points: {value}",
"@numberOfDataPoints": {
  "description": "A message with a formatted int parameter",
  "placeholders": {
    "value": {
      "type": "int",
      "format": "compactCurrency",
      "optionalParameters": {
        "decimalDigits": 2
      }
    }
  }
}
```

### Messages with dates

[#](#messages-with-dates)

Dates strings are formatted in many different ways depending both the locale and the app's needs.

Placeholder values with type `DateTime` are formatted with [`DateFormat`](https://api.flutter.dev/flutter/package-intl_intl/DateFormat-class.html) in the `intl` package.

There are 41 format variations, identified by the names of their `DateFormat` factory constructors. In the following example, the `DateTime` value that appears in the `helloWorldOn` message is formatted with `DateFormat.yMd`:

json

```
"helloWorldOn": "Hello World on {date}",
"@helloWorldOn": {
  "description": "A message with a date parameter",
  "placeholders": {
    "date": {
      "type": "DateTime",
      "format": "yMd"
    }
  }
}
```

In an app where the locale is US English, the following expression would produce "7/9/1959". In a Russian locale, it would produce "9.07.1959".

dart

```
AppLocalizations.of(context).helloWorldOn(DateTime.utc(1959, 7, 9))
```

[]()

### Localizing for iOS: Updating the iOS app bundle

[#](#localizing-for-ios-updating-the-ios-app-bundle)

Although the localizations are handled by Flutter, you need to add the supported languages in the Xcode project. This ensures your entry in the App Store correctly displays the supported languages.

To configure the locales supported by your app, use the following instructions:

1. Open your project's `ios/Runner.xcodeproj` Xcode file.

2. In the **Project Navigator**, select the `Runner` project file under **Projects**.

3. Select the `Info` tab in the project editor.

4. In the **Localizations** section, click the `Add` button (`+`) to add the supported languages and regions to your project. When asked to choose files and reference language, simply select `Finish`.

5. Xcode automatically creates empty `.strings` files and updates the `ios/Runner.xcodeproj/project.pbxproj` file. These files are used by the App Store to determine which languages and regions your app supports.

[]()

## Advanced topics for further customization

[#](#advanced-topics-for-further-customization)

This section covers additional ways to customize a localized Flutter application.

[]()

### Advanced locale definition

[#](#advanced-locale-definition)

Some languages with multiple variants require more than just a language code to properly differentiate.

For example, fully differentiating all variants of Chinese requires specifying the language code, script code, and country code. This is due to the existence of simplified and traditional script, as well as regional differences in the way characters are written within the same script type.

In order to fully express every variant of Chinese for the country codes `CN`, `TW`, and `HK`, the list of supported locales should include:

dart

```
supportedLocales: [
  Locale.fromSubtags(languageCode: 'zh'), // generic Chinese 'zh'
  Locale.fromSubtags(
    languageCode: 'zh',
    scriptCode: 'Hans',
  ), // generic simplified Chinese 'zh_Hans'
  Locale.fromSubtags(
    languageCode: 'zh',
    scriptCode: 'Hant',
  ), // generic traditional Chinese 'zh_Hant'
  Locale.fromSubtags(
    languageCode: 'zh',
    scriptCode: 'Hans',
    countryCode: 'CN',
  ), // 'zh_Hans_CN'
  Locale.fromSubtags(
    languageCode: 'zh',
    scriptCode: 'Hant',
    countryCode: 'TW',
  ), // 'zh_Hant_TW'
  Locale.fromSubtags(
    languageCode: 'zh',
    scriptCode: 'Hant',
    countryCode: 'HK',
  ), // 'zh_Hant_HK'
],
```

This explicit full definition ensures that your app can distinguish between and provide the fully nuanced localized content to all combinations of these country codes. If a user's preferred locale isn't specified, Flutter selects the closest match, which likely contains differences to what the user expects. Flutter only resolves to locales defined in `supportedLocales` and provides scriptCode-differentiated localized content for commonly used languages. See [`Localizations`](https://api.flutter.dev/flutter/widgets/WidgetsApp/supportedLocales.html) for information on how the supported locales and the preferred locales are resolved.

Although Chinese is a primary example, other languages like French (`fr_FR`, `fr_CA`) should also be fully differentiated for more nuanced localization.

[]()

### Tracking the locale: The Locale class and the Localizations widget

[#](#tracking-the-locale-the-locale-class-and-the-localizations-widget)

The [`Locale`](https://api.flutter.dev/flutter/dart-ui/Locale-class.html) class identifies the user's language. Mobile devices support setting the locale for all applications, usually using a system settings menu. Internationalized apps respond by displaying values that are locale-specific. For example, if the user switches the device's locale from English to French, then a `Text` widget that originally displayed "Hello World" would be rebuilt with "Bonjour le monde".

The [`Localizations`](https://api.flutter.dev/flutter/flutter_localizations/GlobalWidgetsLocalizations-class.html) widget defines the locale for its child and the localized resources that the child depends on. The [`WidgetsApp`](https://api.flutter.dev/flutter/widgets/WidgetsApp-class.html) widget creates a `Localizations` widget and rebuilds it if the system's locale changes.

You can always look up an app's current locale with `Localizations.localeOf()`:

dart

```
Locale myLocale = Localizations.localeOf(context);
```

[]()

### Specifying the app's supported­Locales parameter

[#](#specifying-the-apps-supportedshylocales-parameter)

Although the `flutter_localizations` library supports many languages and language variants, only English language translations are available by default. It's up to the developer to decide exactly which languages to support.

The `MaterialApp` [`supportedLocales`](https://pub.dev/documentation/material_ui/latest/material_ui/MaterialApp/supportedLocales.html) parameter limits locale changes. When the user changes the locale setting on their device, the app's `Localizations` widget only follows suit if the new locale is a member of this list. If an exact match for the device locale isn't found, then the first supported locale with a matching [`languageCode`](https://api.flutter.dev/flutter/dart-ui/Locale/languageCode.html) is used. If that fails, then the first element of the `supportedLocales` list is used.

An app that wants to use a different "locale resolution" method can provide a [`localeResolutionCallback`](https://api.flutter.dev/flutter/widgets/LocaleResolutionCallback.html). For example, to have your app unconditionally accept whatever locale the user selects:

dart

```
MaterialApp(
  localeResolutionCallback: (locale, supportedLocales) {
    return locale;
  },
);
```

### Configuring the l10n.yaml file

[#](#configuring-the-l10n-yaml-file)

The `l10n.yaml` file allows you to configure the `gen-l10n` tool to specify the following:

* where all the input files are located
* where all the output files should be created
* what Dart class name to give your localizations delegate

For a full list of options, either run `flutter gen-l10n --help` at the command line or refer to the following table:

| Option                              | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  |
| ----------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `arb-dir`                           | The directory where the template and translated arb files are located. The default is `lib/l10n` .                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| `output-dir`                        | The directory where the generated localization classes are written. This option is only relevant if you want to generate the localizations code somewhere else in the Flutter project. You also need to set the `synthetic-package` flag to false. The app must import the file specified in the `output-localization-file` option from this directory. If unspecified, this defaults to the same directory as the input directory specified in `arb-dir` .                                                                                                                                                                                                                                                                                                                                  |
| `template-arb-file`                 | The template arb file that is used as the basis for generating the Dart localization and messages files. The default is `app_en.arb` .                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       |
| `output-localization-file`          | The filename for the output localization and localizations delegate classes. The default is `app_localizations.dart` .                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       |
| `untranslated-messages-file`        | The location of a file that describes the localization messages haven't been translated yet. Using this option creates a JSON file at the target location, in the following format: `"locale": ["message_1", "message_2" ... "message_n"]` If this option is not specified, a summary of the messages that haven't been translated are printed on the command line.                                                                                                                                                                                                                                                                                                                                                                                                                          |
| `output-class`                      | The Dart class name to use for the output localization and localizations delegate classes. The default is `AppLocalizations` .                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
| `preferred-supported-locales`       | The list of preferred supported locales for the application. By default, the tool generates the supported locales list in alphabetical order. Use this flag to default to a different locale. For example, pass in `[ en_US ]` to default to American English if a device supports it.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       |
| `header`                            | The header to prepend to the generated Dart localizations files. This option takes in a string. For example, pass in `"/// All localized files."` to prepend this string to the generated Dart file. Alternatively, check out the `header-file` option to pass in a text file for longer headers.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
| `header-file`                       | The header to prepend to the generated Dart localizations files. The value of this option is the name of the file that contains the header text that is inserted at the top of each generated Dart file. Alternatively, check out the `header` option to pass in a string for a simpler header. This file should be placed in the directory specified in `arb-dir` .                                                                                                                                                                                                                                                                                                                                                                                                                         |
| `[no-]use-deferred-loading`         | Specifies whether to generate the Dart localization file with locales imported as deferred, allowing for lazy loading of each locale in Flutter web. This can reduce a web app's initial startup time by decreasing the size of the JavaScript bundle. When this flag is set to true, the messages for a particular locale are only downloaded and loaded by the Flutter app as they are needed. For projects with a lot of different locales and many localization strings, it can improve performance to defer loading. For projects with a small number of locales, the difference is negligible, and might slow down the start up compared to bundling the localizations with the rest of the application. Note that this flag doesn't affect other platforms such as mobile or desktop. |
| `gen-inputs-and-outputs-list`       | When specified, the tool generates a JSON file containing the tool's inputs and outputs, named `gen_l10n_inputs_and_outputs.json` . This can be useful for keeping track of which files of the Flutter project were used when generating the latest set of localizations. For example, the Flutter tool's build system uses this file to keep track of when to call gen\_l10n during hot reload. The value of this option is the directory where the JSON file is generated. When null, the JSON file won't be generated.                                                                                                                                                                                                                                                                    |
| `synthetic-package`                 | Determines whether the generated output files are generated as a synthetic package or at a specified directory in the Flutter project. This flag is `true` by default. When `synthetic-package` is set to `false` , it generates the localizations files in the directory specified by `arb-dir` by default. If `output-dir` is specified, files are generated there.                                                                                                                                                                                                                                                                                                                                                                                                                        |
| `project-dir`                       | When specified, the tool uses the path passed into this option as the directory of the root Flutter project. When null, the relative path to the present working directory is used.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          |
| `[no-]required-resource-attributes` | Requires all resource ids to contain a corresponding resource attribute. By default, simple messages won't require metadata, but it's highly recommended as this provides context for the meaning of a message to readers. Resource attributes are still required for plural messages.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       |
| `[no-]nullable-getter`              | Specifies whether the localizations class getter is nullable. By default, this value is true so that `Localizations.of(context)` returns a nullable value for backwards compatibility. If this value is false, then a null check is performed on the returned value of `Localizations.of(context)` , removing the need for null checking in user code.                                                                                                                                                                                                                                                                                                                                                                                                                                       |
| `[no-]format`                       | When specified, the `dart format` command is run after generating the localization files.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |
| `use-escaping`                      | Specifies whether to enable the use of single quotes as escaping syntax.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| `[no-]suppress-warnings`            | When specified, all warnings are suppressed.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |
| `[no-]relax-syntax`                 | When specified, the syntax is relaxed so that the special character "{" is treated as a string if not followed by a valid placeholder and "}" is treated as a string if it doesn't close any previous "{" that is treated as a special character.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
| `[no-]use-named-parameters`         | Whether to use named parameters for the generated localization methods.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |

## How internationalization in Flutter works

[#](#how-internationalization-in-flutter-works)

This section covers the technical details of how localizations work in Flutter. If you're planning on supporting your own set of localized messages, the following content would be helpful. Otherwise, you can skip this section.

[]()

### Loading and retrieving localized values

[#](#loading-and-retrieving-localized-values)

The `Localizations` widget is used to load and look up objects that contain collections of localized values. Apps refer to these objects with [`Localizations.of(context,type)`](https://api.flutter.dev/flutter/widgets/Localizations/of.html). If the device's locale changes, the `Localizations` widget automatically loads values for the new locale and then rebuilds widgets that used it. This happens because `Localizations` works like an [`InheritedWidget`](https://api.flutter.dev/flutter/widgets/InheritedWidget-class.html). When a build function refers to an inherited widget, an implicit dependency on the inherited widget is created. When an inherited widget changes (when the `Localizations` widget's locale changes), its dependent contexts are rebuilt.

Localized values are loaded by the `Localizations` widget's list of [`LocalizationsDelegate`](https://api.flutter.dev/flutter/widgets/LocalizationsDelegate-class.html)s. Each delegate must define an asynchronous [`load()`](https://api.flutter.dev/flutter/widgets/LocalizationsDelegate/load.html) method that produces an object that encapsulates a collection of localized values. Typically these objects define one method per localized value.

In a large app, different modules or packages might be bundled with their own localizations. That's why the `Localizations` widget manages a table of objects, one per `LocalizationsDelegate`. To retrieve the object produced by one of the `LocalizationsDelegate`'s `load` methods, specify a `BuildContext` and the object's type.

For example, the localized strings for the Material Components widgets are defined by the [`MaterialLocalizations`](https://pub.dev/documentation/material_ui/latest/material_ui/MaterialLocalizations-class.html) class. Instances of this class are created by a `LocalizationDelegate` provided by the [`MaterialApp`](https://pub.dev/documentation/material_ui/latest/material_ui/MaterialApp-class.html) class. They can be retrieved with `Localizations.of()`:

dart

```
Localizations.of<MaterialLocalizations>(context, MaterialLocalizations);
```

This particular `Localizations.of()` expression is used frequently, so the `MaterialLocalizations` class provides a convenient shorthand:

dart

```
static MaterialLocalizations of(BuildContext context) {
  return Localizations.of<MaterialLocalizations>(context, MaterialLocalizations);
}
​
/// References to the localized values defined by MaterialLocalizations
/// are typically written like this:
​
tooltip: MaterialLocalizations.of(context).backButtonTooltip,
```

[]()

### Defining a class for the app's localized resources

[#](#defining-a-class-for-the-apps-localized-resources)

Putting together an internationalized Flutter app usually starts with the class that encapsulates the app's localized values. The example that follows is typical of such classes.

Complete source code for the [`intl_example`](https://github.com/flutter/website/tree/main/examples/internationalization/intl_example) for this app.

This example is based on the APIs and tools provided by the [`intl`](https://pub.dev/packages/intl) package. The [An alternative class for the app's localized resources](#alternative-class) section describes [an example](https://github.com/flutter/website/tree/main/examples/internationalization/minimal) that doesn't depend on the `intl` package.

The `DemoLocalizations` class (defined in the following code snippet) contains the app's strings (just one for the example) translated into the locales that the app supports. It uses the `initializeMessages()` function generated by Dart's [`intl`](https://pub.dev/packages/intl) package, [`Intl.message()`](https://pub.dev/documentation/intl/latest/intl/Intl/message.html), to look them up.

dart

```
class DemoLocalizations {
  DemoLocalizations(this.localeName);
​
  static Future<DemoLocalizations> load(Locale locale) {
    final String name =
        locale.countryCode == null || locale.countryCode!.isEmpty
        ? locale.languageCode
        : locale.toString();
    final String localeName = Intl.canonicalizedLocale(name);
​
    return initializeMessages(localeName).then((_) {
      return DemoLocalizations(localeName);
    });
  }
​
  static DemoLocalizations of(BuildContext context) {
    return Localizations.of<DemoLocalizations>(context, DemoLocalizations)!;
  }
​
  final String localeName;
​
  String get title {
    return Intl.message(
      'Hello World',
      name: 'title',
      desc: 'Title for the Demo application',
      locale: localeName,
    );
  }
}
```

A class based on the `intl` package imports a generated message catalog that provides the `initializeMessages()` function and the per-locale backing store for `Intl.message()`. The message catalog is produced by an [`intl` tool](#dart-tools) that analyzes the source code for classes that contain `Intl.message()` calls. In this case that would just be the `DemoLocalizations` class.

[]()

### Adding support for a new language

[#](#adding-support-for-a-new-language)

An app that needs to support a language that's not included in [`GlobalMaterialLocalizations`](https://api.flutter.dev/flutter/flutter_localizations/GlobalMaterialLocalizations-class.html) has to do some extra work: it must provide about 70 translations ("localizations") for words or phrases and the date patterns and symbols for the locale.

See the following for an example of how to add support for the Norwegian Nynorsk language.

A new `GlobalMaterialLocalizations` subclass defines the localizations that the Material library depends on. A new `LocalizationsDelegate` subclass, which serves as factory for the `GlobalMaterialLocalizations` subclass, must also be defined.

Here's the source code for the complete [`add_language`](https://github.com/flutter/website/tree/main/examples/internationalization/add_language/lib/main.dart) example, minus the actual Nynorsk translations.

The locale-specific `GlobalMaterialLocalizations` subclass is called `NnMaterialLocalizations`, and the `LocalizationsDelegate` subclass is `_NnMaterialLocalizationsDelegate`. The value of `NnMaterialLocalizations.delegate` is an instance of the delegate, and is all that's needed by an app that uses these localizations.

The delegate class includes basic date and number format localizations. All of the other localizations are defined by `String` valued property getters in `NnMaterialLocalizations`, like this:

dart

```
@override
String get moreButtonTooltip => r'More';
​
@override
String get aboutListTileTitleRaw => r'About $applicationName';
​
@override
String get alertDialogLabel => r'Alert';
```

These are the English translations, of course. To complete the job you need to change the return value of each getter to an appropriate Nynorsk string.

The getters return "raw" Dart strings that have an `r` prefix, such as `r'About $applicationName'`, because sometimes the strings contain variables with a `$` prefix. The variables are expanded by parameterized localization methods:

dart

```
@override
String get pageRowsInfoTitleRaw => r'$firstRow–$lastRow of $rowCount';
​
@override
String get pageRowsInfoTitleApproximateRaw =>
    r'$firstRow–$lastRow of about $rowCount';
```

The date patterns and symbols of the locale also need to be specified, which are defined in the source code as follows:

dart

```
const nnLocaleDatePatterns = {
  'd': 'd.',
  'E': 'ccc',
  'EEEE': 'cccc',
  'LLL': 'LLL',
  // ...
}
```

dart

```
const Map<String, Object?> nnDateSymbols = {
  'NAME': 'nn',
  'ERAS': <dynamic>['f.Kr.', 'e.Kr.'],
```

These values need to be modified for the locale to use the correct date formatting. Unfortunately, since the `intl` library doesn't share the same flexibility for number formatting, the formatting for an existing locale must be used as a substitute in `_NnMaterialLocalizationsDelegate`:

dart

```
class _NnMaterialLocalizationsDelegate
    extends LocalizationsDelegate<MaterialLocalizations> {
  const _NnMaterialLocalizationsDelegate();
​
  @override
  bool isSupported(Locale locale) => locale.languageCode == 'nn';
​
  @override
  Future<MaterialLocalizations> load(Locale locale) {
    final String localeName = intl.Intl.canonicalizedLocale(locale.toString());
​
    // The locale (in this case `nn`) needs to be initialized into the custom
    // date symbols and patterns setup that Flutter uses.
    date_symbol_data_custom.initializeDateFormattingCustom(
      locale: localeName,
      patterns: nnLocaleDatePatterns,
      symbols: intl.DateSymbols.deserializeFromMap(nnDateSymbols),
    );
​
    return SynchronousFuture<MaterialLocalizations>(
      NnMaterialLocalizations(
        localeName: localeName,
        // The `intl` library's NumberFormat class is generated from CLDR data
        // (see https://github.com/dart-lang/i18n/blob/main/pkgs/intl/lib/number_symbols_data.dart).
        // Unfortunately, there is no way to use a locale that isn't defined in
        // this map and the only way to work around this is to use a listed
        // locale's NumberFormat symbols. So, here we use the number formats
        // for 'en_US' instead.
        decimalFormat: intl.NumberFormat('#,##0.###', 'en_US'),
        twoDigitZeroPaddedFormat: intl.NumberFormat('00', 'en_US'),
        // DateFormat here will use the symbols and patterns provided in the
        // `date_symbol_data_custom.initializeDateFormattingCustom` call above.
        // However, an alternative is to simply use a supported locale's
        // DateFormat symbols, similar to NumberFormat above.
        fullYearFormat: intl.DateFormat('y', localeName),
        compactDateFormat: intl.DateFormat('yMd', localeName),
        shortDateFormat: intl.DateFormat('yMMMd', localeName),
        mediumDateFormat: intl.DateFormat('EEE, MMM d', localeName),
        longDateFormat: intl.DateFormat('EEEE, MMMM d, y', localeName),
        yearMonthFormat: intl.DateFormat('MMMM y', localeName),
        shortMonthDayFormat: intl.DateFormat('MMM d'),
      ),
    );
  }
​
  @override
  bool shouldReload(_NnMaterialLocalizationsDelegate old) => false;
}
```

For more information about localization strings, check out the [flutter\_localizations README](https://github.com/flutter/flutter/blob/main/packages/flutter_localizations/lib/src/l10n/README.md).

Once you've implemented your language-specific subclasses of `GlobalMaterialLocalizations` and `LocalizationsDelegate`, you need to add the language and a delegate instance to your app. The following code sets the app's language to Nynorsk and adds the `NnMaterialLocalizations` delegate instance to the app's `localizationsDelegates` list:

dart

```
const MaterialApp(
  localizationsDelegates: [
    GlobalWidgetsLocalizations.delegate,
    GlobalMaterialLocalizations.delegate,
    NnMaterialLocalizations.delegate, // Add the newly created delegate
  ],
  supportedLocales: [Locale('en', 'US'), Locale('nn')],
  home: Home(),
),
```

[]()

## Alternative internationalization workflows

[#](#alternative-internationalization-workflows)

This section describes different approaches to internationalize your Flutter application.

[]()

### An alternative class for the app's localized resources

[#](#an-alternative-class-for-the-apps-localized-resources)

The previous example was defined in terms of the Dart `intl` package. You can choose your own approach for managing localized values for the sake of simplicity or perhaps to integrate with a different i18n framework.

Complete source code for the [`minimal`](https://github.com/flutter/website/tree/main/examples/internationalization/minimal) app.

In the following example, the `DemoLocalizations` class includes all of its translations directly in per language Maps:

dart

```
class DemoLocalizations {
  DemoLocalizations(this.locale);
​
  final Locale locale;
​
  static DemoLocalizations of(BuildContext context) {
    return Localizations.of<DemoLocalizations>(context, DemoLocalizations)!;
  }
​
  static const _localizedValues = <String, Map<String, String>>{
    'en': {'title': 'Hello World'},
    'es': {'title': 'Hola Mundo'},
  };
​
  static List<String> languages() => _localizedValues.keys.toList();
​
  String get title {
    return _localizedValues[locale.languageCode]!['title']!;
  }
}
```

In the minimal app the `DemoLocalizationsDelegate` is slightly different. Its `load` method returns a [`SynchronousFuture`](https://api.flutter.dev/flutter/foundation/SynchronousFuture-class.html) because no asynchronous loading needs to take place.

dart

```
class DemoLocalizationsDelegate
    extends LocalizationsDelegate<DemoLocalizations> {
  const DemoLocalizationsDelegate();
​
  @override
  bool isSupported(Locale locale) =>
      DemoLocalizations.languages().contains(locale.languageCode);
​
  @override
  Future<DemoLocalizations> load(Locale locale) {
    // Returning a SynchronousFuture here because an async "load" operation
    // isn't needed to produce an instance of DemoLocalizations.
    return SynchronousFuture<DemoLocalizations>(DemoLocalizations(locale));
  }
​
  @override
  bool shouldReload(DemoLocalizationsDelegate old) => false;
}
```

[]()

### Using the Dart intl tools

[#](#using-the-dart-intl-tools)

Before building an API using the Dart [`intl`](https://pub.dev/packages/intl) package, review the `intl` package's documentation. The following list summarizes the process for localizing an app that depends on the `intl` package:

The demo app depends on a generated source file called `l10n/messages_all.dart`, which defines all of the localizable strings used by the app.

Rebuilding `l10n/messages_all.dart` requires two steps.

1. With the app's root directory as the current directory, generate `l10n/intl_messages.arb` from `lib/main.dart`:

   ```
   $ dart run intl_translation:extract_to_arb --output-dir=lib/l10n lib/main.dart
   ```

   The `intl_messages.arb` file is a JSON format map with one entry for each `Intl.message()` function defined in `main.dart`. This file serves as a template for the English and Spanish translations, `intl_en.arb` and `intl_es.arb`. These translations are created by you, the developer.

2. With the app's root directory as the current directory, generate `intl_messages_<locale>.dart` for each `intl_<locale>.arb` file and `intl_messages_all.dart`, which imports all the message files:

   ```
   $ dart run intl_translation:generate_from_arb \
       --output-dir=lib/l10n --no-use-deferred-loading \
       lib/main.dart lib/l10n/intl_*.arb
   ```

   ***Windows doesn't support file name wildcarding.*** Instead, list the .arb files that were generated by the `intl_translation:extract_to_arb` command.

   ```
   $ dart run intl_translation:generate_from_arb \
       --output-dir=lib/l10n --no-use-deferred-loading \
       lib/main.dart \
       lib/l10n/intl_en.arb lib/l10n/intl_fr.arb lib/l10n/intl_messages.arb
   ```

   The `DemoLocalizations` class uses the generated `initializeMessages()` function (defined in `intl_messages_all.dart`) to load the localized messages and `Intl.message()` to look them up.

## More information

[#](#more-information)

If you learn best by reading code, check out the following examples.

* [`minimal`](https://github.com/flutter/website/tree/main/examples/internationalization/minimal)\
  The `minimal` example is designed to be as simple as possible.
* [`intl_example`](https://github.com/flutter/website/tree/main/examples/internationalization/intl_example)\
  uses APIs and tools provided by the [`intl`](https://pub.dev/packages/intl) package.

If Dart's `intl` package is new to you, check out [Using the Dart intl tools](#dart-tools).

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-09-29. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/ui/internationalization/index.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/ui/internationalization\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/ui/internationalization/index.md "Report an issue with this page").

----
url: https://docs.flutter.dev/learn/pathway/tutorial/navigation
----

Learn to navigate between screens with Navigator.push and implement adaptive navigation patterns for different screen sizes.

open\_in\_new Navigate between screens with Navigator.push

swipe\_right Use CupertinoPageRoute for iOS-style transitions

devices Create different navigation patterns for each screen size

***

## Steps

1### Introductionkeyboard\_arrow\_up

Now that you understand slivers and scrolling, you can implement navigation between screens. In this lesson, you'll update the small-screen view such that when a contact group is tapped, it navigates to the contact list for that group.

First, revert changes in the adaptive layout widget so that it displays the `ContactGroupsPage` by default on small screens.

dart

```
class _AdaptiveLayoutState extends State<AdaptiveLayout> {
  int selectedListId = 0;
​
  void _onContactListSelected(int listId) {
    setState(() {
      selectedListId = listId;
    });
  }
​
  @override
  Widget build(BuildContext context) {
    return LayoutBuilder(
      builder: (context, constraints) {
        final isLargeScreen = constraints.maxWidth > largeScreenMinWidth;
​
        if (isLargeScreen) {
          return _buildLargeScreenLayout();
        } else {
          return const ContactGroupsPage(); // Reverted
        }
      },
    );
  }
  // ···
}
```

2### Add navigation to contact groupskeyboard\_arrow\_up

The `ContactGroupsPage` already uses a `_ContactGroupsView` and provides it with a callback. That callback needs to be updated to navigate when a group is tapped, rather than printing the group to the console.

Ensure that the `onListSelected` callback and imports in `lib/screens/contact_groups.dart` are implemented as follows:

lib/screens/contact\_groups.dart

dart

```
import 'contacts.dart';
​
class ContactGroupsPage extends StatelessWidget {
  const ContactGroupsPage({super.key});
​
  @override
  Widget build(BuildContext context) {
    return _ContactGroupsView(
      onListSelected: (list) => Navigator.of(context).push(
        CupertinoPageRoute<void>(
          title: list.title,
          builder: (context) => ContactListsPage(listId: list.id),
        ),
      ),
    );
  }
}
```

This small code block contains the most important new information on this page.

`Navigator.of(context)` retrieves the nearest `Navigator` widget from the widget tree. The `push` method adds a new route to the navigator's stack, and displays the widget returned from the `builder` property.

This is the most basic implementation of using stack-based navigation, where new screens are pushed on top of the current screen. To navigate back to the previous screen, you'd use the `Navigator.pop` method.

`CupertinoPageRoute` creates iOS-style page transitions with the following features:

* A slide-in animation from the right.
* Automatic back button support.
* Proper title handling.
* Swipe-to-go-back gesture support.

3### Create the sidebar component for large screenskeyboard\_arrow\_up

For large screens, you need a sidebar that doesn't navigate but instead updates the main content area. Thanks to the refactoring in the previous step, creating this component is more straightforward. Add this widget to the bottom of `lib/screens/contact_groups.dart`:

dart

```
/// A sidebar component for selecting contact groups on large screens.
class ContactGroupsSidebar extends StatelessWidget {
  const ContactGroupsSidebar({
    super.key,
    required this.selectedListId,
    required this.onListSelected,
  });
​
  final int selectedListId;
  final void Function(int) onListSelected;
​
  @override
  Widget build(BuildContext context) {
    return _ContactGroupsView(
      selectedListId: selectedListId,
      onListSelected: (list) => onListSelected(list.id),
    );
  }
}
```

This sidebar component reuses the `_ContactGroupsView` and provides a different callback. Instead of navigating, it calls `onListSelected` with the ID of the tapped list. It also passes the `selectedListId` to `_ContactGroupsView` so that the selected item can be highlighted.

4### Create the detail view for large screenskeyboard\_arrow\_up

For the large screen layout, you need a detail view that doesn't show navigation controls. Just like the sidebar, this can be recreated by reusing the `_ContactListView`. Add this widget to the bottom of your `contacts.dart` file:

dart

```
class ContactListDetail extends StatelessWidget {
  const ContactListDetail({super.key, required this.listId});
​
  final int listId;
​
  @override
  Widget build(BuildContext context) {
    return _ContactListView(listId: listId, automaticallyImplyLeading: false);
  }
}
```

The detail view reuses `_ContactListView` and sets the `automaticallyImplyLeading` parameter to `false` to hide the back button, as navigation is handled by the sidebar.

5### Connect the sidebar to the adaptive layoutkeyboard\_arrow\_up

Now, connect the sidebar to your adaptive layout. Update your `adaptive_layout.dart` file to import the necessary files and update the large screen layout:

dart

```
import 'package:cupertino_ui/cupertino_ui.dart';
​
import 'contact_groups.dart';
import 'contacts.dart';
```

Then update the `_buildLargeScreenLayout` method:

dart

```
Widget _buildLargeScreenLayout() {
  return CupertinoPageScaffold(
    backgroundColor: CupertinoColors.extraLightBackgroundGray,
    child: SafeArea(
      child: Row(
        children: [
          SizedBox(
            width: 320,
            child: ContactGroupsSidebar(
              selectedListId: selectedListId,
              onListSelected: _onContactListSelected,
            ),
          ),
          Container(width: 1, color: CupertinoColors.separator),
          Expanded(child: ContactListDetail(listId: selectedListId)),
        ],
      ),
    ),
  );
}
```

This code creates the classic menu-detail layout where the sidebar controls the content of the detail area.

6### Test the adaptive navigation behaviorkeyboard\_arrow\_up

Hot reload your app and test the navigation:

**Small screens (<600px width):**

* Tap contact groups to navigate to contact details.
* Use the back button or a swipe gesture to return.
* This is a classic stack-based navigation flow.

**Large screens (>600px width):**

* Click contact groups in the sidebar to update the detail view.
* There is no navigation stack. The selection updates the content area.
* This is a master-detail interface pattern.

The app automatically chooses the appropriate navigation pattern based on screen size. This provides an optimal experience on both phones and tablets.

7### Reviewkeyboard\_arrow\_up

open\_in\_new Navigated between screens with Navigator.push keyboard\_arrow\_up

`Navigator.of(context).push` adds a new route to the navigation stack. This is the foundation of stack-based navigation, where screens are pushed on top of each other and popped to go back.

swipe\_right Used CupertinoPageRoute for iOS-style transitions keyboard\_arrow\_up

`CupertinoPageRoute` provides support for native iOS navigation features: slide-in animations from the right, automatic back buttons, proper title handling, and swipe-to-go-back gesture support.

devices Implemented adaptive navigation patterns keyboard\_arrow\_up

You set up different navigation patterns for small and large screens. Small screens use stack-based navigation where tapping a group pushes a new screen. Large screens use a master-detail pattern where selecting a group updates the detail panel without navigation.

celebration Completed the Rolodex app keyboard\_arrow\_up

You've built a complete iOS-style contacts app with adaptive layouts, advanced scrolling, collapsible headers with search, and responsive navigation. These are common patterns used in production apps!

8### Test yourselfkeyboard\_arrow\_up

### Navigation Quiz

1 / 2

**What does `Navigator.of(context).push` do?**

1. Replaces the current screen with a new one.

   Not quite.

   Push adds to the stack; `pushReplacement` replaces the current screen.

2. Adds a new route to the navigation stack, displaying it on top of the current screen.

   That's right!

   Push adds the new route to the stack, allowing users to go back to the previous screen.

3. Removes the current screen from the navigation stack.

   Not quite.

   That's what `pop` does; push adds a new screen.

4. Opens a dialog box over the current screen.

   Not quite.

   Dialogs use `showDialog`; Navigator.push navigates to a full screen.

**What does `Navigator.of(context).pop()` do?**

1. Closes the entire app.

   Not quite.

   Pop only removes the current route; it doesn't close the app.

2. Removes the current route from the navigation stack, returning to the previous screen.

   That's right!

   Pop removes the top route from the stack, revealing the screen beneath it.

3. Clears all routes and shows the home screen.

   Not quite.

   That would require popUntil or pushAndRemoveUntil; pop removes only the top route.

4. Refreshes the current screen with new data.

   Not quite.

   Pop navigates back; to refresh, you'd use setState or other state management.

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-09-29. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/learn/pathway/tutorial/navigation.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/learn/pathway/tutorial/navigation\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/learn/pathway/tutorial/navigation.md "Report an issue with this page").

----
url: https://docs.flutter.dev/deployment/flavors-linux.md
----

\# Set up Flutter flavors for Linux > How to create Flutter flavors for Linux desktop apps. This guide shows you how to create Flutter flavors for Linux desktop apps. :::note To create app flavors for other platforms, visit the pages on setting up flavors for: \* \[Android]\[] \* \[iOS and macOS]\[] \* \[Windows]\[] ::: ## Overview A Flutter flavor represents a collection of settings that define how a specific version of your app builds and runs. For example, a flavor can determine the window title, application ID, API endpoint, asset set, and logging configuration for a build. :::version-note Built-in support for flavors on Linux requires Flutter 3.47 or later. ::: On Linux, Flutter uses \[CMake]\[] to configure and build the native desktop runner. When you run \`flutter run\` or \`flutter build\` with the \`--flavor\` flag, Flutter writes the flavor name into \`linux/flutter/ephemeral/generated\_config.cmake\` as the \`FLUTTER\_APP\_FLAVOR\` variable, and isolates the build outputs in a flavor-specific directory. :::important The \`generated\_config.cmake\` file is included from \`linux/flutter/CMakeLists.txt\`, so \`FLUTTER\_APP\_FLAVOR\` only becomes visible in your top-level \`CMakeLists.txt\` \*\*after\*\* the \`add\_subdirectory(${FLUTTER\_MANAGED\_DIR})\` line. Any \`if(DEFINED FLUTTER\_APP\_FLAVOR)\` block placed before that line is silently skipped. ::: The following table illustrates the build directories that Flutter creates when a project defines two flavors (\`staging\`, \`production\`) and two build modes (\`debug\`, \`release\`): | Flavor | Build mode | Output directory | |------------|------------|------------------------------------------------------| | staging | debug | \`build/linux//staging/debug/bundle/\` | | production | debug | \`build/linux//production/debug/bundle/\` | | staging | release | \`build/linux//staging/release/bundle/\` | | production | release | \`build/linux//production/release/bundle/\` | {:.table .table-striped} \[CMake]: https\://cmake.org/ ## Configure CMake for flavors (optional) {: #configure-cmake } Passing \`--flavor\` works on Linux without any project configuration. Existing Flutter projects support flavors with no template updates or configuration changes required. When you pass \`--flavor\`, Flutter isolates the build directory, populates \`appFlavor\`, and filters flavor-specific assets automatically. Complete the following optional steps only if you want to differentiate the native runner per flavor—for example, its GTK application ID or window title. This example configures two flavors called \`staging\` and \`production\`. 1. Create a new Flutter project called \`flavors\_example\`: \`\`\`console $ flutter create flavors\_example $ cd flavors\_example \`\`\` 1. Configure flavor-specific application IDs in \`linux/CMakeLists.txt\`: Open \`linux/CMakeLists.txt\` and find the \`add\_subdirectory(${FLUTTER\_MANAGED\_DIR})\` line. Set the \`APPLICATION\_ID\` per flavor immediately after that line: \`\`\`cmake title="linux/CMakeLists.txt" # Flutter library and tool build rules. set(FLUTTER\_MANAGED\_DIR "${CMAKE\_CURRENT\_SOURCE\_DIR}/flutter") add\_subdirectory(${FLUTTER\_MANAGED\_DIR}) if(DEFINED FLUTTER\_APP\_FLAVOR) if(FLUTTER\_APP\_FLAVOR STREQUAL "staging") set(APPLICATION\_ID "com.example.staging") elseif(FLUTTER\_APP\_FLAVOR STREQUAL "production") set(APPLICATION\_ID "com.example.production") endif() endif() \`\`\` \`APPLICATION\_ID\` must be configured after \`add\_subdirectory(${FLUTTER\_MANAGED\_DIR})\` because \`FLUTTER\_APP\_FLAVOR\` only becomes available at this point, and before \`add\_subdirectory("runner")\` where \`APPLICATION\_ID\` is consumed. :::important Leave \`set(BINARY\_NAME ...)\` unchanged. The Flutter CLI resolves the executable path by parsing that line literally, so renaming the binary per flavor prevents \`flutter run\` from launching the app. ::: 1. Verify that your flavors run correctly: Run the \`staging\` flavor: \`\`\`console $ flutter run -d linux --flavor staging \`\`\` Run the \`production\` flavor: \`\`\`console $ flutter run -d linux --flavor production \`\`\` ## Launch a flavor After you configure flavors for your app, run, build, or test a specific flavor using the \`--flavor\` flag with the Flutter CLI. ### Run a flavor in debug mode {: #run-a-flavor } To run a specific flavor during development, pass the \`--flavor\` option to \`flutter run\`: \`\`\`console $ flutter run -d linux --flavor \`\`\` Replace \`\` with the name of your flavor (for example, \`staging\` or \`production\`). ### Build a release binary {: #build-a-flavor } To build a release executable for a specific flavor, pass the \`--flavor\` option to \`flutter build\`: \`\`\`console $ flutter build linux --flavor \`\`\` Flutter outputs the compiled bundle to \`build/linux///release/bundle/\`. ### Run tests with a flavor {: #test-a-flavor } The \`--flavor\` option also works with \`flutter test\` and \`flutter drive\`, allowing you to run unit, widget, or integration tests against a specific flavor configuration: \`\`\`console $ flutter test --flavor \`\`\` ## Use flavors in Flutter code {: #use-flavors-in-code } After adding flavors, you can adjust app behavior—such as selecting API endpoints, toggling features, or setting analytics keys—based on the active flavor. The Flutter framework provides the \`appFlavor\` constant in the \`services\` library, which identifies the flavor used to run or build your app. 1. \*\*Import the services library:\*\* Add the following import to your Dart file: \`\`\`dart import 'package:flutter/services.dart'; \`\`\` 1. \*\*Read the flavor value:\*\* Use the \`appFlavor\` constant in your application logic (often in \`main()\`) to handle flavor-specific configuration: \`\`\`dart void main() { if (appFlavor == 'production') { Config.apiUrl = 'https\://api.example.com'; } else if (appFlavor == 'staging') { Config.apiUrl = 'https\://staging.api.example.com'; } runApp(const MyApp()); } \`\`\` :::note The value of \`appFlavor\` matches the string passed to the \`--flavor\` flag. If you omit \`--flavor\`, \`appFlavor\` matches the \[default flavor]\(#set-default-flavor) configured in \`pubspec.yaml\`. If neither is specified, \`appFlavor\` is \`null\`. ::: ## Customize configurations After adding flavors, you can customize native settings and assets for each configuration. ### Create distinct window titles To help distinguish between different flavors at runtime, customize the native window title for each flavor. 1. In \`linux/runner/CMakeLists.txt\`, pass \`FLUTTER\_APP\_FLAVOR\` as a preprocessor definition directly after the existing \`APPLICATION\_ID\` definition: \`\`\`cmake title="linux/runner/CMakeLists.txt" # Add preprocessor definitions for the application ID. add\_definitions(-DAPPLICATION\_ID="${APPLICATION\_ID}") if(DEFINED FLUTTER\_APP\_FLAVOR AND NOT FLUTTER\_APP\_FLAVOR STREQUAL "") add\_definitions(-DFLUTTER\_APP\_FLAVOR="${FLUTTER\_APP\_FLAVOR}") endif() \`\`\` 1. In \`linux/runner/my\_application.cc\`, update the window title in the \`my\_application\_activate\` function: \`\`\`c title="linux/runner/my\_application.cc" const gchar\* window\_title = "flavors\_example"; #ifdef FLUTTER\_APP\_FLAVOR if (g\_strcmp0(FLUTTER\_APP\_FLAVOR, "staging") == 0) { window\_title = "Staging App"; } else if (g\_strcmp0(FLUTTER\_APP\_FLAVOR, "production") == 0) { window\_title = "Production App"; } #endif \`\`\` Then update \`my\_application\_activate\` to use \`window\_title\` instead of the hardcoded string literals: \`\`\`c title="linux/runner/my\_application.cc" gtk\_header\_bar\_set\_title(header\_bar, window\_title); // ... gtk\_window\_set\_title(window, window\_title); \`\`\` ### Configure app icons On Linux, application icons are not embedded directly in the compiled executable binary. Instead, the desktop environment resolves app icons through \`.desktop\` launcher files and installed icon themes. To provide different icons or launcher entries for each flavor, configure your Linux package format—such as Snap, Flatpak, or Debian packages—to install distinct desktop entries and icon assets. To learn more about packaging Linux apps, refer to \[Build and release a Linux desktop app]\[]. ### Bundle assets by flavor {: #bundle-assets } If you have assets that only apply to a specific flavor, configure Flutter to only bundle those assets when building that flavor. This prevents unused assets from increasing your application bundle size. To bundle assets conditionally, add the \`flavors\` list to an asset entry in \`pubspec.yaml\`: \`\`\`yaml title="pubspec.yaml" flutter: assets: - assets/common/ - path: assets/staging/ flavors: - staging - path: assets/production/ flavors: - production \`\`\` To learn more, consult the \[\`assets\` field]\[] in \[Flutter pubspec options]\[]. \[\`assets\` field]: /tools/pubspec#assets \[Flutter pubspec options]: /tools/pubspec ### Set a default flavor {: #set-default-flavor } To specify a flavor to use when running or building without the \`--flavor\` flag, add the \`default-flavor\` property to \`pubspec.yaml\`: \`\`\`yaml title="pubspec.yaml" flutter: default-flavor: staging \`\`\` To learn more, consult the \[\`default-flavor\` field]\[] in \[Flutter pubspec options]\[]. \[\`default-flavor\` field]: /tools/pubspec#default-flavor ## More information For more information on flavors and desktop deployment, consult the following resources: \* \[Set up Flutter flavors for Android]\[Android] \* \[Set up Flutter flavors for iOS and macOS]\[iOS and macOS] \* \[Set up Flutter flavors for Windows]\[Windows] \* \[Build and release a Linux desktop app]\[] \[Android]: /deployment/flavors \[iOS and macOS]: /deployment/flavors-ios \[Windows]: /deployment/flavors-windows \[Build and release a Linux desktop app]: /deployment/linux

----
url: https://docs.flutter.dev/tools/property-editor.md
----

\# Flutter Property Editor > Learn how to use the Flutter Property Editor to view and modify the properties of your widgets. :::note The Flutter Property Editor requires Flutter version 3.32 or higher. ::: ## What is it? The Flutter Property Editor is a powerful IDE-tool that lets you view and modify widget properties directly from a visual interface. It allows you to quickly discover and modify your widgets' existing and available constructor arguments, eliminating the need to jump-to-definition or manually edit the source code. Furthermore, its integration with the Flutter inspector and hot reload enables you to view changes in real time, speeding up UI development and iteration. !\[Flutter Property Editor]\(/assets/images/docs/tools/devtools/property-editor-text-widget.png){:width="500px"} ## How to access the Flutter Property Editor 1. Open the Flutter Property Editor in your supported IDE (\[VS Code]\[], \[Android Studio/IntelliJ]\[]). 2. Locate a \[widget constructor invocation]\[] in your Flutter code. 3. Move your cursor anywhere inside the widget constructor invocation. For example, in the following \`build\` method, place your cursor anywhere between the \`T\` of \`Text\` and the ending parenthesis \`)\` after \`TextOverflow\.clip\`: \`\`\`dart @override Widget build(BuildContext context) { return Text( 'Hello World!', overflow: TextOverflow\.clip, ); } \`\`\` 4. The Flutter Property Editor panel automatically updates to display the properties of the widget at your cursor location. \[VS Code]: /tools/vs-code#property-editor \[Android Studio/IntelliJ]: /tools/android-studio#property-editor \[widget constructor invocation]: /learn/pathway/tutorial/widget-fundamentals ### Runtime usage #### Integration with the Flutter inspector The Flutter Property Editor can be used in conjunction with the \[Flutter inspector]\[] to inspect your widgets simultaneously in both tools. 1. From your preferred IDE, run and debug your Flutter application. \* \[VS Code instructions]\[] \* \[Android Studio/IntelliJ instructions]\[] 2. Open the \[Flutter inspector]\[] in your IDE. You can then use the Flutter inspector to load a widget in the Flutter Property Editor by either: 1. Selecting a widget in tree: \* Click on a widget in the \[inspector's widget tree]\[]. 2. Selecting a widget in your app: \* Enable \["Select Widget Mode"]\[] in the inspector. \* Click on a widget in your running application. Both actions will automatically: - Jump to the widget's declaration in your source code. - Load the selected widget in the Flutter Property Editor. \[VS Code instructions]: /tools/devtools/vscode/#run-and-debug \[Android Studio/IntelliJ instructions]: /tools/devtools/android-studio/#run-and-debug \[Flutter inspector]: /tools/devtools/inspector \[inspector's widget tree]: /tools/devtools/inspector#flutter-widget-tree \["Select Widget Mode"]: /tools/devtools/inspector#inspecting-a-widget #### Integration with hot reload The Flutter Property Editor can be used in conjunction with hot reload to view changes in real time. 1. From your preferred IDE, enable autosave and hot reloads on save. \*\*VS Code\*\* Add the following to your \`.vscode/settings.json\` file: \`\`\`json "files.autoSave": "afterDelay", "dart.flutterHotReloadOnSave": "all", \`\`\` \*\*Android Studio and IntelliJ\*\* \* Open \`Settings > Tools > Actions on Save\` and select \`Configure autosave options\`. - Check the option to \`Save files if the IDE is idle for X seconds\`. - \*\*Recommended:\*\* Set a small delay duration. For example, 2 seconds. \* Open \`Settings > Languages & Frameworks > Flutter\`. - Check the option to \`Perform hot reload on save\`. 2. Run and debug your Flutter application. \* \[VS Code instructions]\[] \* \[Android Studio/IntelliJ instructions]\[] 3. Any changes you make from the Flutter Property Editor are automatically reflected in your running app. ## Feature set The Flutter Property Editor comes equipped with several features designed to speed up the development process. ### Viewing widget documentation When a widget is selected in the Flutter Property Editor, its documentation is displayed at the top. This allows you to quickly read the widget documentation, without needing to jump-to-definition or search online. By default, the widget documentation is truncated. Click on "Show more" to expand the widget documentation. :::tip To see the documentation for your app's custom widgets in the Flutter Property Editor, make sure to follow the \[Dart style guide]\[]. ::: !\[Flutter Property Editor gif displaying the documentation for a Text widget]\(/assets/images/docs/tools/devtools/property-editor-documentation.gif) \[Dart style guide]: https\://dart.dev/effective-dart/documentation ### Editing widget properties The Flutter Property Editor contains input fields tailored to the type of each constructor argument. - \*\*string, double, and int properties:\*\* \* These are represented by text input fields. \* Simply type the new value into the field. \* Press ••Tab•• or ••Enter•• to apply the edit directly to your source code. - \*\*boolean and enum properties:\*\* \* These are represented by dropdown menus. \* Click the dropdown to see the available options (\`true\`/\`false\` for booleans, or the various enum values). \* Select the desired value from the list to apply it to your code. - \*\*object properties (for example, \`TextStyle\`, \`EdgeInsets\`, \`Color\`):\*\* \* Currently not supported. The Flutter Property Editor does not yet allow direct editing of complex object properties. You will need to edit these directly in your source code. ### Understanding the property inputs Each property input in the Flutter Property Editor is accompanied by information to help you understand its usage. - \*\*Type and name:\*\* The \*\*type\*\* (for example, \`StackFit\`) and the \*\*name\*\* (for example, \`fit\`) of the constructor parameter are displayed as a label for each input field. !\[Type and name label for a property input]\(/assets/images/docs/tools/devtools/property-editor-name-type.png){:width="500px"} - \*\*Info tooltip (ⓘ):\*\* \* Hovering over the info icon next to a property input displays a tooltip. \* The information in the tooltip includes: \* The default value of the property, if one is defined in the widget's constructor. \* Any documentation for that property. !\[Info tooltip for a property input]\(/assets/images/docs/tools/devtools/property-editor-tooltip.png){:width="600px"} \* \*\*"Set" and "default" labels:\*\* \* The \*\*"set"\*\* label appears next to an input if the property has been explicitly set in your source code. This means there is a corresponding argument provided in the widget constructor call. \* The \*\*"default"\*\* label appears next to an input if the current property value matches the default parameter value as defined in the widget. :::tip If a property input has both a "set" and a "default" label, it means you have explicitly provided a value in your code, but this value is the same as the widget's default value for that property. In such cases, you can safely remove this argument from your code to make it more concise, as the widget will use the default value anyway. ::: !\["Set" and "default" labels for a property input]\(/assets/images/docs/tools/devtools/property-editor-labels.png){:width="500px"} ### Filtering properties For widgets with many properties, the filter bar can help to quickly locate properties of interest. \* \*\*Filter by text:\*\* \* Simply type into the filter bar. The list of properties will dynamically update to show only those that match your input. \* You can filter by a property's name, its current value, or its type. For example: \* Typing "main" would filter to \`mainAxisAlignment\`, \`mainAxisSize\`, or other properties with "main" in their name. \* Typing "true" would filter to all boolean properties currently set to \`true\`. \* Typing "double" would filter to all properties of type \`double\`. !\[Filter input with filtering by text highlighted]\(/assets/images/docs/tools/devtools/property-editor-filter-text.png){:width="500px"} \* \*\*Filter by "set" properties:\*\* \* Use the filter menu button to open the filter options. Check "Only include properties that are set in the code." \* This hides all properties that have not been explicitly set in your code, allowing you to focus only on the properties you have explicitly set. !\[Filter input with filter menu button highlighted]\(/assets/images/docs/tools/devtools/property-editor-filter-menu-button.png){:width="500px"} \* \*\*Filter with a regex:\*\* \* The regex toggle (an \`\*\` icon button) allows you to toggle on regex mode for the filter input. \* When enabled, your filter text will be interpreted as a regular expression. !\[Filter input with regex toggle highlighted]\(/assets/images/docs/tools/devtools/property-editor-filter-regex-toggle.png){:width="500px"} \* \*\*Clear the current filter:\*\* \* The clear button (an \`X\` icon button) clears out any active filters, displaying all properties of the widget again. !\[Filter input with clear button highlighted]\(/assets/images/docs/tools/devtools/property-editor-filter-clear-button.png){:width="500px"}

----
url: https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.26.1.md
----

\# DevTools 2.26.1 release notes > Release notes for Dart and Flutter DevTools version 2.26.1. The 2.26.1 release of the Dart and Flutter DevTools includes the following changes among other general improvements. To learn more about DevTools, check out the \[DevTools overview]\(https\://docs.flutter.dev/tools/devtools). ## General updates - Added a new "Home" screen in DevTools that either shows the "Connect" dialog or a summary of your connected app, depending on the connection status in DevTools. Keep an eye on this screen for cool new features in the future. This change also enables support for static tooling (tools that don't require a connected app) in DevTools - \[#6010]\(https\://github.com/flutter/devtools/pull/6010) !\[home screen]\(/assets/images/docs/tools/devtools/release-notes/images-2.26.1/home\_screen.png "DevTools home screen") - Fixed overlay notifications so that they cover the area that their background blocks - \[#5975]\(https\://github.com/flutter/devtools/pull/5975) ## Memory updates - Added a context menu to rename or delete a heap snapshot from the list - \[#5997]\(https\://github.com/flutter/devtools/pull/5997) - Warn users when HTTP logging may be affecting their app's memory consumption - \[#5998]\(https\://github.com/flutter/devtools/pull/5998) ## Debugger updates - Improvements to text selection and copy behavior in the code view, console, and variables windows - \[#6020]\(https\://github.com/flutter/devtools/pull/6020) ## Network profiler updates - Added a selector to customize the display type of text and json responses (thanks to @hhacker1999!) - \[#5816]\(https\://github.com/flutter/devtools/pull/5816) ## Full commit history To find a complete list of changes since the previous release, check out \[the diff on GitHub]\(https\://github.com/flutter/devtools/compare/v2.25.0...v2.26.1).

----
url: https://docs.flutter.dev/add-to-app/ios/add-flutter-screen
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Add a Flutter screen to an iOS app](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

This guide describes how to add a single Flutter screen to an existing iOS app.

## Start a FlutterEngine and FlutterViewController

[#](#start-a-flutterengine-and-flutterviewcontroller)

To launch a Flutter screen from an existing iOS app, you start a [`FlutterEngine`](https://api.flutter.dev/ios-embedder/interface_flutter_engine.html) and a [`FlutterViewController`](https://api.flutter.dev/ios-embedder/interface_flutter_view_controller.html).

The `FlutterEngine` might have the same lifespan as your `FlutterViewController` or outlive your `FlutterViewController`.

See [Loading sequence and performance](/add-to-app/performance) for more analysis on the latency and memory trade-offs of pre-warming an engine.

### Create a FlutterEngine

[#](#create-a-flutterengine)

Where you create a `FlutterEngine` depends on your host app.

* [SwiftUI](#126-tab-panel)
* [UIKit-Swift](#127-tab-panel)
* [UIKit-ObjC](#128-tab-panel)

The following example creates a `FlutterEngine` object inside a SwiftUI [`Observable`](https://developer.apple.com/documentation/observation/observable) object called `FlutterDependencies`. Pre-warm the engine by calling `run()`, and then inject this object into a `ContentView` using the `environment()` view modifier.

MyApp.swift

swift

```
import SwiftUI
import Flutter
// The following library connects plugins with iOS platform code to this app.
import FlutterPluginRegistrant
​
@Observable
class FlutterDependencies {
 let flutterEngine = FlutterEngine(name: "my flutter engine")
 init() {
   // Runs the default Dart entrypoint with a default Flutter route.
   flutterEngine.run()
   // Connects plugins with iOS platform code to this app.
   GeneratedPluginRegistrant.register(with: self.flutterEngine);
 }
}
​
@main
struct MyApp: App {
   // flutterDependencies will be injected through the view environment.
   @State var flutterDependencies = FlutterDependencies()
   var body: some Scene {
     WindowGroup {
       ContentView()
         .environment(flutterDependencies)
     }
   }
}
```

The following example demonstrates creating a `FlutterEngine`, exposed as a property, on app startup in the app delegate.

AppDelegate.swift

swift

```
import UIKit
import Flutter
// The following library connects plugins with iOS platform code to this app.
import FlutterPluginRegistrant
​
@UIApplicationMain
class AppDelegate: FlutterAppDelegate { // More on FlutterAppDelegate and FlutterSceneDelegate below.
  lazy var flutterEngine = FlutterEngine(name: "my flutter engine")
​
  override func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    // Runs the default Dart entrypoint with a default Flutter route.
    flutterEngine.run();
    // Connects plugins with iOS platform code to this app.
    GeneratedPluginRegistrant.register(with: self.flutterEngine);
    return super.application(application, didFinishLaunchingWithOptions: launchOptions);
  }
}
```

The following example demonstrates creating a `FlutterEngine`, exposed as a property, on app startup in the app delegate.

AppDelegate.h

objc

```
@import UIKit;
@import Flutter;
​
@interface AppDelegate : FlutterAppDelegate // More on FlutterAppDelegate and FlutterSceneDelegate below.
@property (nonatomic,strong) FlutterEngine *flutterEngine;
@end
```

AppDelegate.m

objc

```
// The following library connects plugins with iOS platform code to this app.
#import <FlutterPluginRegistrant/GeneratedPluginRegistrant.h>
​
#import "AppDelegate.h"
​
@implementation AppDelegate
​
- (BOOL)application:(UIApplication *)application
    didFinishLaunchingWithOptions:(NSDictionary<UIApplicationLaunchOptionsKey, id> *)launchOptions {
  self.flutterEngine = [[FlutterEngine alloc] initWithName:@"my flutter engine"];
  // Runs the default Dart entrypoint with a default Flutter route.
  [self.flutterEngine run];
  // Connects plugins with iOS platform code to this app.
  [GeneratedPluginRegistrant registerWithRegistry:self.flutterEngine];
  return [super application:application didFinishLaunchingWithOptions:launchOptions];
}
​
@end
```

### Show a FlutterViewController with your FlutterEngine

[#](#show-a-flutterviewcontroller-with-your-flutterengine)

* [SwiftUI](#129-tab-panel)
* [UIKit-Swift](#130-tab-panel)
* [UIKit-ObjC](#131-tab-panel)

The following example shows a generic `ContentView` with a [`NavigationLink`](https://developer.apple.com/documentation/swiftui/navigationlink) hooked to a flutter screen. First, create a `FlutterViewControllerRepresentable` to represent the `FlutterViewController`. The `FlutterViewController` constructor takes the pre-warmed `FlutterEngine` as an argument, which is injected through the view environment.

ContentView\.swift

swift

```
import SwiftUI
import Flutter
​
struct FlutterViewControllerRepresentable: UIViewControllerRepresentable {
  // Flutter dependencies are passed in through the view environment.
  @Environment(FlutterDependencies.self) var flutterDependencies
​
  func makeUIViewController(context: Context) -> some UIViewController {
    return FlutterViewController(
      engine: flutterDependencies.flutterEngine,
      nibName: nil,
      bundle: nil)
  }
​
  func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {}
}
​
struct ContentView: View {
  var body: some View {
    NavigationStack {
      NavigationLink("My Flutter Feature") {
        FlutterViewControllerRepresentable()
      }
    }
  }
}
```

Now, you have a Flutter screen embedded in your iOS app.

The following example shows a generic `ViewController` with a `UIButton` hooked to present a [`FlutterViewController`](https://api.flutter.dev/ios-embedder/interface_flutter_view_controller.html). The `FlutterViewController` uses the `FlutterEngine` instance created in the `AppDelegate`.

ViewController.swift

swift

```
import UIKit
import Flutter
​
class ViewController: UIViewController {
  override func viewDidLoad() {
    super.viewDidLoad()
​
    // Make a button to call the showFlutter function when pressed.
    let button = UIButton(type:UIButton.ButtonType.custom)
    button.addTarget(self, action: #selector(showFlutter), for: .touchUpInside)
    button.setTitle("Show Flutter!", for: UIControl.State.normal)
    button.frame = CGRect(x: 80.0, y: 210.0, width: 160.0, height: 40.0)
    button.backgroundColor = UIColor.blue
    self.view.addSubview(button)
  }
​
  @objc func showFlutter() {
    let flutterEngine = (UIApplication.shared.delegate as! AppDelegate).flutterEngine
    let flutterViewController =
        FlutterViewController(engine: flutterEngine, nibName: nil, bundle: nil)
    present(flutterViewController, animated: true, completion: nil)
  }
}
```

Now, you have a Flutter screen embedded in your iOS app.

The following example shows a generic `ViewController` with a `UIButton` hooked to present a [`FlutterViewController`](https://api.flutter.dev/ios-embedder/interface_flutter_view_controller.html). The `FlutterViewController` uses the `FlutterEngine` instance created in the `AppDelegate`.

ViewController.m

objc

```
@import Flutter;
#import "AppDelegate.h"
#import "ViewController.h"
​
@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
​
    // Make a button to call the showFlutter function when pressed.
    UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
    [button addTarget:self
               action:@selector(showFlutter)
     forControlEvents:UIControlEventTouchUpInside];
    [button setTitle:@"Show Flutter!" forState:UIControlStateNormal];
    button.backgroundColor = UIColor.blueColor;
    button.frame = CGRectMake(80.0, 210.0, 160.0, 40.0);
    [self.view addSubview:button];
}
​
- (void)showFlutter {
    FlutterEngine *flutterEngine =
        ((AppDelegate *)UIApplication.sharedApplication.delegate).flutterEngine;
    FlutterViewController *flutterViewController =
        [[FlutterViewController alloc] initWithEngine:flutterEngine nibName:nil bundle:nil];
    [self presentViewController:flutterViewController animated:YES completion:nil];
}
@end
```

Now, you have a Flutter screen embedded in your iOS app.

### *Alternatively* - Create a FlutterViewController with an implicit FlutterEngine

[#](#alternatively-create-a-flutterviewcontroller-with-an-implicit-flutterengine)

As an alternative to the previous example, you can let the `FlutterViewController` implicitly create its own `FlutterEngine` without pre-warming one ahead of time.

This is not usually recommended because creating a `FlutterEngine` on-demand could introduce a noticeable latency between when the `FlutterViewController` is presented and when it renders its first frame. This could, however, be useful if the Flutter screen is rarely shown, when there are no good heuristics to determine when the Dart VM should be started, and when Flutter doesn't need to persist state between view controllers.

To let the `FlutterViewController` present without an existing `FlutterEngine`, omit the `FlutterEngine` construction, and create the `FlutterViewController` without an engine reference.

* [SwiftUI](#132-tab-panel)
* [UIKit-Swift](#133-tab-panel)
* [UIKit-ObjC](#134-tab-panel)

ContentView\.swift

swift

```
import SwiftUI
import Flutter
​
struct FlutterViewControllerRepresentable: UIViewControllerRepresentable {
  func makeUIViewController(context: Context) -> some UIViewController {
    return FlutterViewController(
      project: nil,
      nibName: nil,
      bundle: nil)
  }
​
  func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {}
}
​
struct ContentView: View {
  var body: some View {
    NavigationStack {
      NavigationLink("My Flutter Feature") {
        FlutterViewControllerRepresentable()
      }
    }
  }
}
```

ViewController.swift

swift

```
// Existing code omitted.
func showFlutter() {
  let flutterViewController = FlutterViewController(project: nil, nibName: nil, bundle: nil)
  present(flutterViewController, animated: true, completion: nil)
}
```

ViewController.m

objc

```
// Existing code omitted.
- (void)showFlutter {
  FlutterViewController *flutterViewController =
      [[FlutterViewController alloc] initWithProject:nil nibName:nil bundle:nil];
  [self presentViewController:flutterViewController animated:YES completion:nil];
}
@end
```

See [Loading sequence and performance](/add-to-app/performance) for more explorations on latency and memory usage.

[]()

## Using FlutterAppDelegate and FlutterSceneDelegate

[#](#using-flutterappdelegate-and-flutterscenedelegate)

As of Flutter 3.41, `UIScene` support is the default for iOS apps, and Apple requires UIKit apps built with recent iOS SDKs to use the `UIScene` lifecycle.

In this architecture, responsibilities are split between two delegates:

* `FlutterAppDelegate`: Handles process events and the overall application lifecycle (such as remote notifications and background execution), and keeps the Flutter debug connection open when the device locks.
* `FlutterSceneDelegate`: Handles UI and scene lifecycle events, forwarding callbacks such as [`scene(_:openURLContexts:)`](https://developer.apple.com/documentation/uikit/uiscenedelegate/scene\(_:openurlcontexts:\)) and [`scene(_:continue:)`](https://developer.apple.com/documentation/uikit/uiscenedelegate/scene\(_:continue:\)) to plugins such as [`local_auth`](https://pub.dev/packages/local_auth).

To ensure Flutter plugins receive all necessary lifecycle events, adopt both `FlutterAppDelegate` (or `FlutterAppLifeCycleProvider`) and `FlutterSceneDelegate` (or `FlutterSceneLifeCycleProvider`).

For more details on the `UIScene` lifecycle migration, see the [UIScene migration guide](/release/breaking-changes/uiscenedelegate#migration-guide-for-adding-flutter-to-existing-app-add-to-app).

### Using the FlutterAppDelegate

[#](#using-the-flutterappdelegate)

Subclassing `FlutterAppDelegate` in your `UIApplicationDelegate` is recommended but not required.

#### Create a FlutterAppDelegate subclass

[#](#create-a-flutterappdelegate-subclass)

Creating a subclass of `FlutterAppDelegate` in UIKit apps was shown in the [Start a FlutterEngine and FlutterViewController section](/add-to-app/ios/add-flutter-screen#start-a-flutterengine-and-flutterviewcontroller).

In a SwiftUI app, you can create a subclass of `FlutterAppDelegate`, implement `application(_:configurationForConnecting:options:)` to specify `FlutterSceneDelegate`, and annotate the class with the [`Observable()`](https://developer.apple.com/documentation/observation/observable\(\)) macro:

MyApp.swift

swift

```
import SwiftUI
import Flutter
import FlutterPluginRegistrant
​
@Observable
class AppDelegate: FlutterAppDelegate {
  let flutterEngine = FlutterEngine(name: "my flutter engine")
​
  override func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
  ) -> Bool {
    // Runs the default Dart entrypoint with a default Flutter route.
    flutterEngine.run()
    // Used to connect plugins (only if you have plugins with iOS platform code).
    GeneratedPluginRegistrant.register(with: self.flutterEngine)
    return true
  }
​
  override func application(
    _ application: UIApplication,
    configurationForConnecting connectingSceneSession: UISceneSession,
    options: UIScene.ConnectionOptions
  ) -> UISceneConfiguration {
    let configuration = UISceneConfiguration(
      name: nil,
      sessionRole: connectingSceneSession.role
    )
    configuration.delegateClass = FlutterSceneDelegate.self
    return configuration
  }
}
​
@main
struct MyApp: App {
  // Use this property wrapper to tell SwiftUI
  // it should use the AppDelegate class for the application delegate.
  @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
​
  var body: some Scene {
    WindowGroup {
      ContentView()
    }
  }
}
```

Then, in your view, the `AppDelegate` is accessible through the view environment.

ContentView\.swift

swift

```
import SwiftUI
import Flutter
​
struct FlutterViewControllerRepresentable: UIViewControllerRepresentable {
  // Access the AppDelegate through the view environment.
  @Environment(AppDelegate.self) var appDelegate
​
  func makeUIViewController(context: Context) -> some UIViewController {
    return FlutterViewController(
      engine: appDelegate.flutterEngine,
      nibName: nil,
      bundle: nil
    )
  }
​
  func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {}
}
​
struct ContentView: View {
  var body: some View {
    NavigationStack {
      NavigationLink("My Flutter Feature") {
        FlutterViewControllerRepresentable()
      }
    }
  }
}
```

#### If you can't subclass FlutterAppDelegate

[#](#if-you-cant-subclass-flutterappdelegate)

If your app delegate cannot subclass `FlutterAppDelegate`, conform to the `FlutterAppLifeCycleProvider` protocol and use a `FlutterPluginAppLifeCycleDelegate` to ensure plugins receive application-level callbacks.

* [Swift](#135-tab-panel)
* [Objective-C](#136-tab-panel)

AppDelegate.swift

swift

```
import Foundation
import Flutter
​
@Observable
class AppDelegate: UIResponder, UIApplicationDelegate, FlutterAppLifeCycleProvider {
​
  private let lifecycleDelegate = FlutterPluginAppLifeCycleDelegate()
​
  let flutterEngine = FlutterEngine(name: "my flutter engine")
​
  func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
  ) -> Bool {
    flutterEngine.run()
    return lifecycleDelegate.application(
      application,
      didFinishLaunchingWithOptions: launchOptions ?? [:]
    )
  }
​
  func application(
    _ application: UIApplication,
    didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data
  ) {
    lifecycleDelegate.application(
      application,
      didRegisterForRemoteNotificationsWithDeviceToken: deviceToken
    )
  }
​
  func application(
    _ application: UIApplication,
    didFailToRegisterForRemoteNotificationsWithError error: Error
  ) {
    lifecycleDelegate.application(
      application,
      didFailToRegisterForRemoteNotificationsWithError: error
    )
  }
​
  func application(
    _ application: UIApplication,
    didReceiveRemoteNotification userInfo: [AnyHashable: Any],
    fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void
  ) {
    lifecycleDelegate.application(
      application,
      didReceiveRemoteNotification: userInfo,
      fetchCompletionHandler: completionHandler
    )
  }
​
  func application(
    _ app: UIApplication,
    open url: URL,
    options: [UIApplication.OpenURLOptionsKey: Any] = [:]
  ) -> Bool {
    return lifecycleDelegate.application(app, open: url, options: options)
  }
​
  func application(_ application: UIApplication, handleOpen url: URL) -> Bool {
    return lifecycleDelegate.application(application, handleOpen: url)
  }
​
  func application(
    _ application: UIApplication,
    open url: URL,
    sourceApplication: String?,
    annotation: Any
  ) -> Bool {
    return lifecycleDelegate.application(
      application,
      open: url,
      sourceApplication: sourceApplication ?? "",
      annotation: annotation
    )
  }
​
  func application(
    _ application: UIApplication,
    performActionFor shortcutItem: UIApplicationShortcutItem,
    completionHandler: @escaping (Bool) -> Void
  ) {
    lifecycleDelegate.application(
      application,
      performActionFor: shortcutItem,
      completionHandler: completionHandler
    )
  }
​
  func application(
    _ application: UIApplication,
    handleEventsForBackgroundURLSession identifier: String,
    completionHandler: @escaping () -> Void
  ) {
    lifecycleDelegate.application(
      application,
      handleEventsForBackgroundURLSession: identifier,
      completionHandler: completionHandler
    )
  }
​
  func application(
    _ application: UIApplication,
    performFetchWithCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void
  ) {
    lifecycleDelegate.application(
      application,
      performFetchWithCompletionHandler: completionHandler
    )
  }
​
  func add(_ delegate: FlutterApplicationLifeCycleDelegate) {
    lifecycleDelegate.add(delegate)
  }
}
```

AppDelegate.h

objc

```
@import Flutter;
@import UIKit;
@import FlutterPluginRegistrant;
​
@interface AppDelegate : UIResponder <UIApplicationDelegate, FlutterAppLifeCycleProvider>
@property (strong, nonatomic) UIWindow *window;
@property (nonatomic, strong) FlutterEngine *flutterEngine;
@end
```

AppDelegate.m

objc

```
#import "AppDelegate.h"
​
@interface AppDelegate ()
@property (nonatomic, strong) FlutterPluginAppLifeCycleDelegate *lifeCycleDelegate;
@end
​
@implementation AppDelegate
​
- (instancetype)init {
  if (self = [super init]) {
    _lifeCycleDelegate = [[FlutterPluginAppLifeCycleDelegate alloc] init];
  }
  return self;
}
​
- (BOOL)application:(UIApplication *)application
    didFinishLaunchingWithOptions:(NSDictionary<UIApplicationLaunchOptionsKey, id> *)launchOptions {
  self.flutterEngine = [[FlutterEngine alloc] initWithName:@"io.flutter" project:nil];
  [self.flutterEngine runWithEntrypoint:nil];
  [GeneratedPluginRegistrant registerWithRegistry:self.flutterEngine];
  return [_lifeCycleDelegate application:application didFinishLaunchingWithOptions:launchOptions];
}
​
// Returns the key window's rootViewController, if it's a FlutterViewController.
// Otherwise, returns nil.
- (FlutterViewController *)rootFlutterViewController {
  UIViewController *viewController = [UIApplication sharedApplication].keyWindow.rootViewController;
  if ([viewController isKindOfClass:[FlutterViewController class]]) {
    return (FlutterViewController *)viewController;
  }
  return nil;
}
​
- (void)application:(UIApplication *)application
    didRegisterUserNotificationSettings:(UIUserNotificationSettings *)notificationSettings {
  [_lifeCycleDelegate application:application
      didRegisterUserNotificationSettings:notificationSettings];
}
​
- (void)application:(UIApplication *)application
    didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
  [_lifeCycleDelegate application:application
      didRegisterForRemoteNotificationsWithDeviceToken:deviceToken];
}
​
- (void)application:(UIApplication *)application
    didReceiveRemoteNotification:(NSDictionary *)userInfo
          fetchCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler {
  [_lifeCycleDelegate application:application
     didReceiveRemoteNotification:userInfo
           fetchCompletionHandler:completionHandler];
}
​
- (BOOL)application:(UIApplication *)application
            openURL:(NSURL *)url
            options:(NSDictionary<UIApplicationOpenURLOptionsKey, id> *)options {
  return [_lifeCycleDelegate application:application openURL:url options:options];
}
​
- (BOOL)application:(UIApplication *)application handleOpenURL:(NSURL *)url {
  return [_lifeCycleDelegate application:application handleOpenURL:url];
}
​
- (BOOL)application:(UIApplication *)application
            openURL:(NSURL *)url
  sourceApplication:(NSString *)sourceApplication
         annotation:(id)annotation {
  return [_lifeCycleDelegate application:application
                                 openURL:url
                       sourceApplication:sourceApplication
                              annotation:annotation];
}
​
- (void)application:(UIApplication *)application
    performActionForShortcutItem:(UIApplicationShortcutItem *)shortcutItem
               completionHandler:(void (^)(BOOL succeeded))completionHandler {
  [_lifeCycleDelegate application:application
     performActionForShortcutItem:shortcutItem
                completionHandler:completionHandler];
}
​
- (void)application:(UIApplication *)application
    handleEventsForBackgroundURLSession:(nonnull NSString *)identifier
                      completionHandler:(nonnull void (^)(void))completionHandler {
  [_lifeCycleDelegate application:application
      handleEventsForBackgroundURLSession:identifier
                        completionHandler:completionHandler];
}
​
- (void)application:(UIApplication *)application
    performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler {
  [_lifeCycleDelegate application:application performFetchWithCompletionHandler:completionHandler];
}
​
- (void)addApplicationLifeCycleDelegate:(NSObject<FlutterPlugin> *)delegate {
  [_lifeCycleDelegate addDelegate:delegate];
}
@end
```

### Using the FlutterSceneDelegate

[#](#using-the-flutterscenedelegate)

Similar to `FlutterAppDelegate`, subclassing `FlutterSceneDelegate` is recommended but not required. The `FlutterSceneDelegate` forwards scene callbacks, such as [`scene(_:openURLContexts:)`](https://developer.apple.com/documentation/uikit/uiscenedelegate/scene\(_:openurlcontexts:\)) and [`scene(_:continue:)`](https://developer.apple.com/documentation/uikit/uiscenedelegate/scene\(_:continue:\)), to plugins such as [`local_auth`](https://pub.dev/packages/local_auth).

#### Create or update a SceneDelegate

[#](#create-or-update-a-scenedelegate)

In UIKit apps, update your `SceneDelegate` to subclass `FlutterSceneDelegate`:

* [UIKit-Swift](#137-tab-panel)
* [UIKit-ObjC](#138-tab-panel)
* [SwiftUI](#139-tab-panel)

SceneDelegate.swift

swift

```
import UIKit
import Flutter
​
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
class SceneDelegate: FlutterSceneDelegate {
```

SceneDelegate.h

objc

```
#import <UIKit/UIKit.h>
#import <Flutter/Flutter.h>
​
@interface SceneDelegate : UIResponder <UIWindowSceneDelegate>
@interface SceneDelegate : FlutterSceneDelegate
```

In SwiftUI apps, configure the scene delegate class in `application(_:configurationForConnecting:options:)` as shown in the [FlutterAppDelegate SwiftUI example](#create-a-flutterappdelegate-subclass).

If your app does not support multiple scenes, set **Enable Multiple Scenes** (`UIApplicationSupportsMultipleScenes`) to **NO** under **Application Scene Manifest** in your target's `Info.plist`.

#### If you can't subclass FlutterSceneDelegate

[#](#if-you-cant-subclass-flutterscenedelegate)

If your `SceneDelegate` cannot subclass `FlutterSceneDelegate`, adopt the `FlutterSceneLifeCycleProvider` protocol and use a `FlutterPluginSceneLifeCycleDelegate` object to forward scene lifecycle events to Flutter:

* [Swift](#140-tab-panel)
* [Objective-C](#141-tab-panel)

SceneDelegate.swift

swift

```
import UIKit
import Flutter
​
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
class SceneDelegate: UIResponder, UIWindowSceneDelegate, FlutterSceneLifeCycleProvider {
  var sceneLifeCycleDelegate = FlutterPluginSceneLifeCycleDelegate()
​
  var window: UIWindow?
​
  func scene(
    _ scene: UIScene,
    willConnectTo session: UISceneSession,
    options connectionOptions: UIScene.ConnectionOptions
  ) {
    sceneLifeCycleDelegate.scene(
      scene,
      willConnectTo: session,
      options: connectionOptions
    )
  }
​
  func sceneDidDisconnect(_ scene: UIScene) {
    sceneLifeCycleDelegate.sceneDidDisconnect(scene)
  }
​
  func sceneWillEnterForeground(_ scene: UIScene) {
    sceneLifeCycleDelegate.sceneWillEnterForeground(scene)
  }
​
  func sceneDidBecomeActive(_ scene: UIScene) {
    sceneLifeCycleDelegate.sceneDidBecomeActive(scene)
  }
​
  func sceneWillResignActive(_ scene: UIScene) {
    sceneLifeCycleDelegate.sceneWillResignActive(scene)
  }
​
  func sceneDidEnterBackground(_ scene: UIScene) {
    sceneLifeCycleDelegate.sceneDidEnterBackground(scene)
  }
​
  func scene(
    _ scene: UIScene,
    openURLContexts URLContexts: Set<UIOpenURLContext>
  ) {
    sceneLifeCycleDelegate.scene(scene, openURLContexts: URLContexts)
  }
​
  func scene(_ scene: UIScene, continue userActivity: NSUserActivity) {
    sceneLifeCycleDelegate.scene(scene, continue: userActivity)
  }
​
  func windowScene(
    _ windowScene: UIWindowScene,
    performActionFor shortcutItem: UIApplicationShortcutItem,
    completionHandler: @escaping (Bool) -> Void
  ) {
    sceneLifeCycleDelegate.windowScene(
      windowScene,
      performActionFor: shortcutItem,
      completionHandler: completionHandler
    )
  }
}
```

SceneDelegate.h

objc

```
#import <UIKit/UIKit.h>
#import <Flutter/Flutter.h>
​
@interface SceneDelegate : UIResponder <UIWindowSceneDelegate>
@interface SceneDelegate : UIResponder <UIWindowSceneDelegate, FlutterSceneLifeCycleProvider>
​
@property (strong, nonatomic) UIWindow *window;
​
@property (nonatomic, strong) FlutterPluginSceneLifeCycleDelegate *sceneLifeCycleDelegate;
​
@end
```

SceneDelegate.m

objc

```
#import "SceneDelegate.h"
​
@implementation SceneDelegate
​
- (instancetype)init {
  if (self = [super init]) {
    _sceneLifeCycleDelegate = [[FlutterPluginSceneLifeCycleDelegate alloc] init];
  }
  return self;
}
​
- (void)scene:(UIScene *)scene
    willConnectToSession:(UISceneSession *)session
                 options:(UISceneConnectionOptions *)connectionOptions {
  [self.sceneLifeCycleDelegate scene:scene willConnectToSession:session options:connectionOptions];
}
​
- (void)sceneDidDisconnect:(UIScene *)scene {
  [self.sceneLifeCycleDelegate sceneDidDisconnect:scene];
}
​
- (void)sceneDidBecomeActive:(UIScene *)scene {
  [self.sceneLifeCycleDelegate sceneDidBecomeActive:scene];
}
​
- (void)sceneWillResignActive:(UIScene *)scene {
  [self.sceneLifeCycleDelegate sceneWillResignActive:scene];
}
​
- (void)sceneWillEnterForeground:(UIScene *)scene {
  [self.sceneLifeCycleDelegate sceneWillEnterForeground:scene];
}
​
- (void)sceneDidEnterBackground:(UIScene *)scene {
  [self.sceneLifeCycleDelegate sceneDidEnterBackground:scene];
}
​
- (void)scene:(UIScene *)scene openURLContexts:(NSSet<UIOpenURLContext *> *)URLContexts {
  [self.sceneLifeCycleDelegate scene:scene openURLContexts:URLContexts];
}
​
- (void)scene:(UIScene *)scene continueUserActivity:(NSUserActivity *)userActivity {
  [self.sceneLifeCycleDelegate scene:scene continueUserActivity:userActivity];
}
​
- (void)windowScene:(UIWindowScene *)windowScene
    performActionForShortcutItem:(UIApplicationShortcutItem *)shortcutItem
               completionHandler:(void (^)(BOOL))completionHandler {
  [self.sceneLifeCycleDelegate windowScene:windowScene
              performActionForShortcutItem:shortcutItem
                         completionHandler:completionHandler];
}
​
@end
```

## Launch options

[#](#launch-options)

The examples demonstrate running Flutter using the default launch settings.

In order to customize your Flutter runtime, you can also specify the Dart entrypoint, library, and route.

### Dart entrypoint

[#](#dart-entrypoint)

Calling `run` on a `FlutterEngine`, by default, runs the `main()` Dart function of your `lib/main.dart` file.

You can also run a different entrypoint function by using [`runWithEntrypoint`](https://api.flutter.dev/ios-embedder/interface_flutter_engine.html#a019d6b3037eff6cfd584fb2eb8e9035e) with an `NSString` specifying a different Dart function.

### Dart library

[#](#dart-library)

In addition to specifying a Dart function, you can specify an entrypoint function in a specific file.

For instance the following runs `myOtherEntrypoint()` in `lib/other_file.dart` instead of `main()` in `lib/main.dart`:

* [Swift](#142-tab-panel)
* [Objective-C](#143-tab-panel)

swift

```
flutterEngine.run(withEntrypoint: "myOtherEntrypoint", libraryURI: "other_file.dart")
```

objc

```
[flutterEngine runWithEntrypoint:@"myOtherEntrypoint" libraryURI:@"other_file.dart"];
```

### Route

[#](#route)

Starting in Flutter version 1.22, an initial route can be set for your Flutter [`WidgetsApp`](https://api.flutter.dev/flutter/widgets/WidgetsApp-class.html) when constructing the FlutterEngine or the FlutterViewController.

* [Swift](#144-tab-panel)
* [Objective-C](#145-tab-panel)

swift

```
let flutterEngine = FlutterEngine()
// FlutterDefaultDartEntrypoint is the same as nil, which will run main().
engine.run(
  withEntrypoint: "main", initialRoute: "/onboarding")
```

objc

```
FlutterEngine *flutterEngine = [[FlutterEngine alloc] init];
// FlutterDefaultDartEntrypoint is the same as nil, which will run main().
[flutterEngine runWithEntrypoint:FlutterDefaultDartEntrypoint
                    initialRoute:@"/onboarding"];
```

This code sets your `dart:ui`'s [`PlatformDispatcher.defaultRouteName`](https://api.flutter.dev/flutter/dart-ui/PlatformDispatcher/defaultRouteName.html) to `"/onboarding"` instead of `"/"`.

Alternatively, to construct a FlutterViewController directly without pre-warming a FlutterEngine:

* [Swift](#146-tab-panel)
* [Objective-C](#147-tab-panel)

swift

```
let flutterViewController = FlutterViewController(
      project: nil, initialRoute: "/onboarding", nibName: nil, bundle: nil)
```

objc

```
FlutterViewController* flutterViewController =
      [[FlutterViewController alloc] initWithProject:nil
                                        initialRoute:@"/onboarding"
                                             nibName:nil
                                              bundle:nil];
```

See [Navigation and routing](/ui/navigation) for more about Flutter's routes.

### Other

[#](#other)

The previous example only illustrates a few ways to customize how a Flutter instance is initiated. Using [platform channels](/platform-integration/platform-channels), you're free to push data or prepare your Flutter environment in any way you'd like, before presenting the Flutter UI using a `FlutterViewController`.

## Content-sized views

[#](#content-sized-views)

On iOS, you can also set your embedded `FlutterView` to size itself based off its content.

* [Swift](#148-tab-panel)
* [Objective-C](#149-tab-panel)

swift

```
let flutterViewController = FlutterViewController(engine: engine, nibName: nil, bundle: nil)
flutterViewController.isAutoResizable = true
```

objc

```
_flutterViewController = [[FlutterViewController alloc] initWithEngine:engine nibName:nil bundle:nil];
_flutterViewController.autoResizable = YES;
```

### Restrictions

[#](#restrictions)

To use this, your root widget must support unbounded constraints. Avoid using widgets that require bounded constraints (like `ListView` or `LayoutBuilder`) at the top of your tree, as they can conflict with the dynamic sizing logic.

In practice, this means that quite a few common widgets are not supported, such as `ScaffoldBuilder`, `CupertinoTimerPicker`, or any widget that internally relies on a `LayoutBuilder`. When in doubt, you can use an `UnconstrainedBox` to test the usability of a widget for a content-sized view, as in the following example:

dart

```
import 'package:material_ui/material_ui.dart';
​
void main() => runApp(MyApp());
​
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context)
  => MaterialApp(home: MyPage());
}
​
class MyPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
        body: UnconstrainedBox(
          // TODO: Edit this line to check if a widget
          // can cause problems with content-sized views.
          child: Text('This works!'),
          // child: Column(children: [Column(children: [Expanded(child: Text('This blows up!'))])]),
          // child: ListView(children: [Text('This blows up!')]),
        )
    );
  }
}
```

For a working example, refer to this [sample project](https://github.com/flutter/samples/tree/main/add_to_app/ios_content_resizing).

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-09-29. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/add-to-app/ios/add-flutter-screen.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/add-to-app/ios/add-flutter-screen\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/add-to-app/ios/add-flutter-screen.md "Report an issue with this page").

----
url: https://docs.flutter.dev/add-to-app/android/add-flutter-view
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Add a Flutter View to an Android app](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

Integrating via a [FlutterView](https://api.flutter.dev/javadoc/io/flutter/embedding/android/FlutterView.html) requires a bit more work than via FlutterActivity and FlutterFragment previously described.

Fundamentally, the Flutter framework on the Dart side requires access to various activity-level events and lifecycles to function. Since the FlutterView (which is an [android.view.View](https://developer.android.com/reference/android/view/View.html)) can be added to any activity which is owned by the developer's application and since the FlutterView doesn't have access to activity level events, the developer must bridge those connections manually to the [FlutterEngine](https://api.flutter.dev/javadoc/io/flutter/embedding/engine/FlutterEngine.html).

How you choose to feed your application's activities' events to the FlutterView will be specific to your application.

## A sample

[#](#a-sample)

Unlike the guides for FlutterActivity and FlutterFragment, the FlutterView integration could be better demonstrated with a sample project.

A sample project is at <https://github.com/flutter/samples/tree/main/add_to_app/android_view> to document a simple FlutterView integration where FlutterViews are used for some of the cells in a RecycleView list of cards as seen in the gif above.

## General approach

[#](#general-approach)

The general gist of the FlutterView-level integration is that you must recreate the various interactions between your Activity, the [`FlutterView`](https://api.flutter.dev/javadoc/io/flutter/embedding/android/FlutterView.html) and the [`FlutterEngine`](https://api.flutter.dev/javadoc/io/flutter/embedding/engine/FlutterEngine.html) present in the [`FlutterActivityAndFragmentDelegate`](https://cs.opensource.google/flutter/engine/+/main:shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java) in your own application's code. The connections made in the [`FlutterActivityAndFragmentDelegate`](https://cs.opensource.google/flutter/engine/+/main:shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java) are done automatically when using a [`FlutterActivity`](https://api.flutter.dev/javadoc/io/flutter/embedding/android/FlutterActivity.html) or a [`FlutterFragment`](https://api.flutter.dev/javadoc/io/flutter/embedding/android/FlutterFragment.html), but since the [`FlutterView`](https://api.flutter.dev/javadoc/io/flutter/embedding/android/FlutterView.html) in this case is being added to an `Activity` or `Fragment` in your application, you must recreate the connections manually. Otherwise, the [`FlutterView`](https://api.flutter.dev/javadoc/io/flutter/embedding/android/FlutterView.html) won't render anything or have other missing functionalities.

A sample [`FlutterViewEngine`](https://github.com/flutter/samples/blob/main/add_to_app/android_view/android_view/app/src/main/java/dev/flutter/example/androidView/FlutterViewEngine.kt) class shows one such possible implementation of an application-specific connection between an `Activity`, a [`FlutterView`](https://api.flutter.dev/javadoc/io/flutter/embedding/android/FlutterView.html) and a [FlutterEngine](https://api.flutter.dev/javadoc/io/flutter/embedding/engine/FlutterEngine.html).

### APIs to implement

[#](#apis-to-implement)

The absolute minimum implementation needed for Flutter to draw anything at all is to:

* Call [`attachToFlutterEngine`](https://api.flutter.dev/javadoc/io/flutter/embedding/android/FlutterView.html#attachToFlutterEngine-io.flutter.embedding.engine.FlutterEngine-) when the [`FlutterView`](https://api.flutter.dev/javadoc/io/flutter/embedding/android/FlutterView.html) is added to a resumed `Activity`'s view hierarchy and is visible; and
* Call [`appIsResumed`](https://api.flutter.dev/javadoc/io/flutter/embedding/engine/systemchannels/LifecycleChannel.html#appIsResumed--) on the [`FlutterEngine`](https://api.flutter.dev/javadoc/io/flutter/embedding/engine/FlutterEngine.html)'s `lifecycleChannel` field when the `Activity` hosting the [`FlutterView`](https://api.flutter.dev/javadoc/io/flutter/embedding/android/FlutterView.html) is visible.

The reverse [`detachFromFlutterEngine`](https://api.flutter.dev/javadoc/io/flutter/embedding/android/FlutterView.html#detachFromFlutterEngine--) and other lifecycle methods on the [`LifecycleChannel`](https://api.flutter.dev/javadoc/io/flutter/embedding/engine/systemchannels/LifecycleChannel.html) class must also be called to not leak resources when the `FlutterView` or `Activity` is no longer visible.

In addition, see the remaining implementation in the [`FlutterViewEngine`](https://github.com/flutter/samples/blob/main/add_to_app/android_view/android_view/app/src/main/java/dev/flutter/example/androidView/FlutterViewEngine.kt) demo class or in the [`FlutterActivityAndFragmentDelegate`](https://cs.opensource.google/flutter/engine/+/main:shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java) to ensure a correct functioning of other features such as clipboards, system UI overlay, plugins, and so on.

## Content-sized views

[#](#content-sized-views)

Usually, a [`FlutterView`](https://api.flutter.dev/javadoc/io/flutter/embedding/android/FlutterView.html) needs fixed dimensions either through its own dimensions or by matching a parent's dimensions. This can be seen in the [sample project](https://github.com/flutter/samples/tree/main/add_to_app/android_view/android_view). However, it's now possible to allow `FlutterView` to size itself based on its content. By using, `content_wrap` for either the height or the width a `FlutterView` can size itself, as shown in the [content sized sample project](https://github.com/flutter/samples/tree/main/add_to_app/android_view/content_sizing_android_view).

* To *enable* Content-sized view when deploying your app, add the following setting to your project's `AndroidManifest.xml` file under the `<application>` tag:

xml

```
<meta-data
  android:name="io.flutter.embedding.android.EnableContentSizing"
  android:value="true" />
```

### Restrictions

[#](#restrictions)

Since content-sized Flutter views require your Flutter app to be able to size itself, some widgets are not supported.

* A widget with unbounded size, like a `ListView`.
* A widget that defers to its child for the size, like `LayoutBuilder`.

In practice, this means that quite a few common widgets are not supported, such as `ScaffoldBuilder`, `CupertinoTimerPicker`, or any widget that internally relies on a `LayoutBuilder`. When in doubt, you can use an `UnconstrainedBox` to test the usability of a widget for a content-sized view, as shown in the following example:

dart

```
import 'package:material_ui/material_ui.dart';
​
void main() => runApp(MyApp());
​
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context)
  => MaterialApp(home: MyPage());
}
​
class MyPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
        body: UnconstrainedBox(
          // TODO: Edit this line to check if a widget
          // can cause problems with content-sized views.
          child: Text('This works!'),
          // child: Column(children: [Column(children: [Expanded(child: Text('This blows up!'))])]),
          // child: ListView(children: [Text('This blows up!')]),
        )
    );
  }
}
```

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-09-29. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/add-to-app/android/add-flutter-view.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/add-to-app/android/add-flutter-view\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/add-to-app/android/add-flutter-view.md "Report an issue with this page").

----
url: https://docs.flutter.dev/cookbook/forms/validation
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Build a form with validation](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

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.

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, process the information. If the user submits incorrect information, display a friendly error message letting them know what went wrong.

In this example, learn how to add validation to a form that has a single text field using the following steps:

1. Create a `Form` with a `GlobalKey`.
2. Add a `TextFormField` with validation logic.
3. Create a button to validate and submit the form.

## 1. Create a `Form` with a `GlobalKey`

[#](#1-create-a-form-with-a-globalkey)

Create a [`Form`](https://api.flutter.dev/flutter/widgets/Form-class.html). The `Form` widget acts as a container for grouping and validating multiple form fields.

When creating the form, provide a [`GlobalKey`](https://api.flutter.dev/flutter/widgets/GlobalKey-class.html). This assigns a unique identifier to your `Form`. It also allows you to validate the form later.

Create the form as a `StatefulWidget`. This allows you to create a unique `GlobalKey<FormState>()` once. You can then store it as a variable and access it at different points.

If you made this a `StatelessWidget`, you'd need to store this key *somewhere*. As it is resource expensive, you wouldn't want to generate a new `GlobalKey` each time you run the `build` method.

dart

```
import 'package:flutter/material.dart';
​
// Define a custom Form widget.
class MyCustomForm extends StatefulWidget {
  const MyCustomForm({super.key});
​
  @override
  State<MyCustomForm> createState() => _MyCustomFormState();
}
​
// Define a corresponding State class.
// This class holds data related to the form.
class _MyCustomFormState extends State<MyCustomForm> {
  // Create a global key that uniquely identifies the Form widget
  // and allows validation of the form.
  //
  // Note: This is a `GlobalKey<FormState>`,
  // not a GlobalKey<_MyCustomFormState>.
  final _formKey = GlobalKey<FormState>();
​
  @override
  Widget build(BuildContext context) {
    // Build a Form widget using the _formKey created above.
    return Form(
      key: _formKey,
      child: const Column(
        children: [
          // Add TextFormFields and ElevatedButton here.
        ],
      ),
    );
  }
}
```

## 2. Add a `TextFormField` with validation logic

[#](#2-add-a-textformfield-with-validation-logic)

Although the `Form` is in place, it doesn't have a way for users to enter text. That's the job of a [`TextFormField`](https://pub.dev/documentation/material_ui/latest/material_ui/TextFormField-class.html). The `TextFormField` widget renders a Material Design text field and can display validation errors when they occur.

Validate the input by providing a `validator()` function to the `TextFormField`. If the user's input isn't valid, the `validator` function returns a `String` containing an error message. If there are no errors, the validator must return `null`.

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 that the user has entered.
  validator: (value) {
    if (value == null || value.isEmpty) {
      return 'Please enter some text';
    }
    return null;
  },
),
```

## 3. Create a button to validate and submit the form

[#](#3-create-a-button-to-validate-and-submit-the-form)

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, check if the form is valid. If it is, display a success message. If it isn't (the text field has no content) display the error message.

dart

```
ElevatedButton(
  onPressed: () {
    // Validate returns true if the form is valid, or false otherwise.
    if (_formKey.currentState!.validate()) {
      // 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.
      ScaffoldMessenger.of(context).showSnackBar(
        const SnackBar(content: Text('Processing Data')),
      );
    }
  },
  child: const Text('Submit'),
),
```

### How does this work?

[#](#how-does-this-work)

To validate the form, use the `_formKey` created in step 1. You can use the `_formKey.currentState` accessor to access the [`FormState`](https://api.flutter.dev/flutter/widgets/FormState-class.html), which is automatically created by Flutter when building a `Form`.

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 `validate()` method returns `true`. If any text field contains errors, the `validate()` method rebuilds the form to display any error messages and returns `false`.

## Interactive example

[#](#interactive-example)

```
import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    const appTitle = 'Form Validation Demo';

    return MaterialApp(
      title: appTitle,
      home: Scaffold(
        appBar: AppBar(title: const Text(appTitle)),
        body: const MyCustomForm(),
      ),
    );
  }
}

// Create a Form widget.
class MyCustomForm extends StatefulWidget {
  const MyCustomForm({super.key});

  @override
  State<MyCustomForm> createState() => _MyCustomFormState();
}

// Create a corresponding State class.
// This class holds data related to the form.
class _MyCustomFormState extends State<MyCustomForm> {
  // Create a global key that uniquely identifies the Form widget
  // and allows validation of the form.
  //
  // Note: This is a GlobalKey<FormState>,
  // not a GlobalKey<_MyCustomFormState>.
  final _formKey = GlobalKey<FormState>();

  @override
  Widget build(BuildContext context) {
    // Build a Form widget using the _formKey created above.
    return Form(
      key: _formKey,
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          TextFormField(
            // The validator receives the text that the user has entered.
            validator: (value) {
              if (value == null || value.isEmpty) {
                return 'Please enter some text';
              }
              return null;
            },
          ),
          Padding(
            padding: const EdgeInsets.symmetric(vertical: 16),
            child: ElevatedButton(
              onPressed: () {
                // Validate returns true if the form is valid, or false otherwise.
                if (_formKey.currentState!.validate()) {
                  // 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.
                  ScaffoldMessenger.of(context).showSnackBar(
                    const SnackBar(content: Text('Processing Data')),
                  );
                }
              },
              child: const Text('Submit'),
            ),
          ),
        ],
      ),
    );
  }
}
```

To learn how to retrieve these values, consult the [Retrieve the value of a text field](/cookbook/forms/retrieve-input) recipe.

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-10-06. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/forms/validation.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/cookbook/forms/validation\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/forms/validation.md "Report an issue with this page").

----
url: https://docs.flutter.dev/cookbook/plugins/google-mobile-ads.md
----

\# Add ads to your mobile Flutter app or game > How to use the google\_mobile\_ads package to show ads in Flutter.

<!--?code-excerpt path-base="cookbook/plugins/google_mobile_ads"?-->

Many developers use advertising to monetize their mobile apps and games. This allows their app to be downloaded free of charge, which improves the app's popularity. !\[An illustration of a smartphone showing an ad]\(/assets/images/docs/cookbook/ads-device.jpg){:.site-illustration} To add ads to your Flutter project, use \[AdMob]\(https\://admob.google.com/home/), Google's mobile advertising platform. This recipe demonstrates how to use the \[\`google\_mobile\_ads\`]\(https\://pub.dev/packages/google\_mobile\_ads) package to add a banner ad to your app or game. :::note Apart from AdMob, the \`google\_mobile\_ads\` package also supports Ad Manager, a platform intended for large publishers. Integrating Ad Manager resembles integrating AdMob, but it won't be covered in this cookbook recipe. To use Ad Manager, follow the \[Ad Manager documentation]\(https\://developers.google.com/ad-manager/mobile-ads-sdk/flutter/quick-start). ::: ## 1. Get AdMob App IDs 1. Go to \[AdMob]\(https\://admob.google.com/) and set up an account. This could take some time because you need to provide banking information, sign contracts, and so on. 2. With the AdMob account ready, create two \*Apps\* in AdMob: one for Android and one for iOS. 3. Open the \*\*App settings\*\* section. 4. Get the AdMob \*App IDs\* for both the Android app and the iOS app. They resemble \`ca-app-pub-1234567890123456\~1234567890\`. Note the tilde (\`\~\`) between the two numbers. !\[Screenshot from AdMob showing the location of the App ID]\(/assets/images/docs/cookbook/ads-app-id.png) ## 2. Platform-specific setup Update your Android and iOS configurations to include your App IDs. ### Android Add your AdMob app ID to your Android app. 1. Open the app's \`android/app/src/main/AndroidManifest.xml\` file. 2. Add a new \`\` tag. 3. Set the \`android:name\` element with a value of \`com.google.android.gms.ads.APPLICATION\_ID\`. 4. Set the \`android:value\` element with the value to your own AdMob app ID that you got in the previous step. Include them in quotes as shown: \`\`\`xml ... \`\`\` ### iOS Add your AdMob app ID to your iOS app. 1. Open your app's \`ios/Runner/Info.plist\` file. 2. Enclose \`GADApplicationIdentifier\` with a \`key\` tag. 3. Enclose your AdMob app ID with a \`string\` tag. You created this AdMob App ID in \[step 1]\(#1-get-admob-app-ids). \`\`\`xml GADApplicationIdentifier ca-app-pub-################\~########## \`\`\` ## 3. Add the \`google\_mobile\_ads\` plugin To add the \`google\_mobile\_ads\` plugin as a dependency, run \`flutter pub add\`: \`\`\`console $ flutter pub add google\_mobile\_ads \`\`\` :::note Once you add the plugin, your Android app might fail to build with a \`DexArchiveMergerException\`: \`\`\`plaintext Error while merging dex archives: The number of method references in a .dex file cannot exceed 64K. \`\`\` To resolve this, execute the \`flutter run\` command in the terminal, not through an IDE plugin. The \`flutter\` tool can detect the issue and ask whether it should try to solve it. Answer \`y\`, and the problem goes away. You can return to running your app from an IDE after that. !\[Screenshot of the \`flutter\` tool asking about multidex support]\(/assets/images/docs/cookbook/ads-multidex.png) ::: ## 4. Initialize the Mobile Ads SDK You need to initialize the Mobile Ads SDK before loading ads. 1. Call \`MobileAds.instance.initialize()\` to initialize the Mobile Ads SDK.

<!--?code-excerpt "lib/main.dart (main)"?-->

\`\`\`dart void main() { WidgetsFlutterBinding.ensureInitialized(); unawaited(MobileAds.instance.initialize()); runApp(const MyApp()); } \`\`\` Run the initialization step at startup, as shown above, so that the AdMob SDK has enough time to initialize before it is needed. :::note \`MobileAds.instance.initialize()\` returns a \`Future\` but, the way the SDK is built, you don't need to \`await\` it. If you try to load an ad before that \`Future\` is completed, the SDK will gracefully wait until the initialization, and \_then\_ load the ad. You can await the \`Future\` if you want to know the exact time when the AdMob SDK is ready. ::: ## 5. Load a banner ad To show an ad, you need to request it from AdMob. To load a banner ad, construct a \`BannerAd\` instance, and call \`load()\` on it. :::note The following code snippet refers to fields such a \`adSize\`, \`adUnitId\` and \`\_bannerAd\`. This will all make more sense in a later step. :::

<!--?code-excerpt "lib/my_banner_ad.dart (loadAd)"?-->

\`\`\`dart /// Loads a banner ad. void \_loadAd() { final bannerAd = BannerAd( size: widget.adSize, adUnitId: widget.adUnitId, request: const AdRequest(), listener: BannerAdListener( // Called when an ad is successfully received. onAdLoaded: (ad) { if (!mounted) { ad.dispose(); return; } setState(() { \_bannerAd = ad as BannerAd; }); }, // Called when an ad request failed. onAdFailedToLoad: (ad, error) { debugPrint('BannerAd failed to load: $error'); ad.dispose(); }, ), ); // Start loading. bannerAd.load(); } \`\`\` To view a complete example, check out the last step of this recipe. ## 6. Show banner ad Once you have a loaded instance of \`BannerAd\`, use \`AdWidget\` to show it. \`\`\`dart AdWidget(ad: \_bannerAd) \`\`\` It's a good idea to wrap the widget in a \`SafeArea\` (so that no part of the ad is obstructed by device notches) and a \`SizedBox\` (so that it has its specified, constant size before and after loading).

<!--?code-excerpt "lib/my_banner_ad.dart (build)"?-->

\`\`\`dart @override Widget build(BuildContext context) { return SafeArea( child: SizedBox( width: widget.adSize.width.toDouble(), height: widget.adSize.height.toDouble(), child: \_bannerAd == null // Nothing to render yet. ? const SizedBox() // The actual ad. : AdWidget(ad: \_bannerAd!), ), ); } \`\`\` You must dispose of an ad when you no longer need to access it. The best practice for when to call \`dispose()\` is either after the \`AdWidget\` is removed from the widget tree or in the \`BannerAdListener.onAdFailedToLoad()\` callback.

<!--?code-excerpt "lib/my_banner_ad.dart (dispose)"?-->

\`\`\`dart \_bannerAd?.dispose(); \`\`\` ## 7. Configure ads To show anything beyond test ads, you have to register ad units. 1. Open \[AdMob]\(https\://admob.google.com/). 2. Create an \*Ad unit\* for each of the AdMob apps. !\[Screenshot of the location of Ad Units in AdMob web UI]\(/assets/images/docs/cookbook/ads-ad-unit.png) This asks for the Ad unit's format. AdMob provides many formats beyond banner ads --- interstitials, rewarded ads, app open ads, and so on. The API for those is similar, and documented in the \[AdMob documentation]\(https\://developers.google.com/admob/flutter/quick-start) and through \[official samples]\(https\://github.com/googleads/googleads-mobile-flutter/tree/main/samples/admob). 3. Choose banner ads. 4. Get the \*Ad unit IDs\* for both the Android app and the iOS app. You can find these in the \*\*Ad units\*\* section. They look something like \`ca-app-pub-1234567890123456/1234567890\`. The format resembles the \*App ID\* but with a slash (\`/\`) between the two numbers. This distinguishes an \*Ad unit ID\* from an \*App ID\*. !\[Screenshot of an Ad Unit ID in AdMob web UI]\(/assets/images/docs/cookbook/ads-ad-unit-id.png) 5. Add these \*Ad unit IDs\* to the constructor of \`BannerAd\`, depending on the target app platform.

<!--?code-excerpt "lib/my_banner_ad.dart (adUnitId)"?-->

\`\`\`dart final String adUnitId = Platform.isAndroid // Use this ad unit on Android... ? 'ca-app-pub-3940256099942544/6300978111' // ... or this one on iOS. : 'ca-app-pub-3940256099942544/2934735716'; \`\`\` ## 8. Final touches To display the ads in a published app or game (as opposed to debug or testing scenarios), your app must meet additional requirements: 1. Your app must be reviewed and approved before it can fully serve ads. Follow AdMob's \[app readiness guidelines]\(https\://support.google.com/admob/answer/10564477). For example, your app must be listed on at least one of the supported stores such as Google Play Store or Apple App Store. 2. You must \[create an \`app-ads.txt\`]\(https\://support.google.com/admob/answer/9363762) file and publish it on your developer website. !\[An illustration of a smartphone showing an ad]\(/assets/images/docs/cookbook/ads-device.jpg){:.site-illustration} To learn more about app and game monetization, visit the official sites of \[AdMob]\(https\://admob.google.com/) and \[Ad Manager]\(https\://admanager.google.com/). ## 9. Complete example The following code implements a simple stateful widget that loads a banner ad and shows it.

<!--?code-excerpt "lib/my_banner_ad.dart"?-->

\`\`\`dart import 'dart:io'; import 'package:flutter/widgets.dart'; import 'package:google\_mobile\_ads/google\_mobile\_ads.dart'; class MyBannerAdWidget extends StatefulWidget { /// The requested size of the banner. Defaults to \[AdSize.banner]. final AdSize adSize; /// The AdMob ad unit to show. /// /// TODO: replace this test ad unit with your own ad unit final String adUnitId = Platform.isAndroid // Use this ad unit on Android... ? 'ca-app-pub-3940256099942544/6300978111' // ... or this one on iOS. : 'ca-app-pub-3940256099942544/2934735716'; MyBannerAdWidget({super.key, this.adSize = AdSize.banner}); @override State createState() => \_MyBannerAdWidgetState(); } class \_MyBannerAdWidgetState extends State { /// The banner ad to show. This is \`null\` until the ad is actually loaded. BannerAd? \_bannerAd; @override Widget build(BuildContext context) { return SafeArea( child: SizedBox( width: widget.adSize.width.toDouble(), height: widget.adSize.height.toDouble(), child: \_bannerAd == null // Nothing to render yet. ? const SizedBox() // The actual ad. : AdWidget(ad: \_bannerAd!), ), ); } @override void initState() { super.initState(); \_loadAd(); } @override void dispose() { \_bannerAd?.dispose(); super.dispose(); } /// Loads a banner ad. void \_loadAd() { final bannerAd = BannerAd( size: widget.adSize, adUnitId: widget.adUnitId, request: const AdRequest(), listener: BannerAdListener( // Called when an ad is successfully received. onAdLoaded: (ad) { if (!mounted) { ad.dispose(); return; } setState(() { \_bannerAd = ad as BannerAd; }); }, // Called when an ad request failed. onAdFailedToLoad: (ad, error) { debugPrint('BannerAd failed to load: $error'); ad.dispose(); }, ), ); // Start loading. bannerAd.load(); } } \`\`\` :::tip In many cases, you will want to load the ad \_outside\_ a widget. For example, you can load it in a \`ChangeNotifier\`, a BLoC, a controller, or whatever else you are using for app-level state. This way, you can preload a banner ad in advance, and have it ready to show for when the user navigates to a new screen. Verify that you have loaded the \`BannerAd\` instance before showing it with an \`AdWidget\`, and that you dispose of the instance when it is no longer needed. :::

----
url: https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.21.1
----

The 2.21.1 release of the Dart and Flutter DevTools includes the following changes among other general improvements. To learn more about DevTools, check out the [DevTools overview](https://docs.flutter.dev/tools/devtools).

## Performance updates

[#](#performance-updates)

* Replace the DevTools timeline trace viewer with the [Perfetto](https://perfetto.dev/) trace viewer - [#5142](https://github.com/flutter/devtools/pull/5142)

* Fix several issues with loading a Performance snapshot into DevTools - [#5048](https://github.com/flutter/devtools/pull/5048), [#4929](https://github.com/flutter/devtools/pull/4929)

* UI polish and cleanup - [#4889](https://github.com/flutter/devtools/pull/4889)

## Memory updates

[#](#memory-updates)

* Improve usability of snapshot diffing - [#5015](https://github.com/flutter/devtools/pull/5015)
* UI polish and cleanup - [#4855](https://github.com/flutter/devtools/pull/4855)
* Color code classes based on where they are defined (SDK, your package, dependencies, etc.) - [#5030](https://github.com/flutter/devtools/pull/5030)
* Fix state management issue for tracing - [#5062](https://github.com/flutter/devtools/pull/5062)
* Improve the performance of taking a heap snapshot - [#5134](https://github.com/flutter/devtools/pull/5134)
* Retire broken import/export feature - [#5135](https://github.com/flutter/devtools/pull/5135)

## Debugger updates

[#](#debugger-updates)

* Added support for viewing profiler hits in the debugger script viewer - [#4831](https://github.com/flutter/devtools/pull/4831)
* Added support for inspecting records - [#5084](https://github.com/flutter/devtools/pull/5084)

## General updates

[#](#general-updates)

* Fix several issues in syntax highlighting that would color variable names that contain reserved words incorrectly and leave `extends`/`implements` clauses uncolored for some classes - [#4948](https://github.com/flutter/devtools/pull/4948)
* Fix an issue in Safari, and other browsers that do not support RegExp negative lookbehind, that prevented DevTools from loading - [#4938](https://github.com/flutter/devtools/pull/4938)
* Fix an issue that would prevent DevTools connecting to the backend server that would disable some functionality - [#5016](https://github.com/flutter/devtools/pull/5016)
* Add a link to the DevTools [contribution guide](https://github.com/flutter/devtools/blob/master/CONTRIBUTING.md) to the About menu, and fixed the Discord link - [#4926](https://github.com/flutter/devtools/pull/4926)
* Fix conflicting colors in light theme - [#5067](https://github.com/flutter/devtools/pull/5067)

## Full commit history

[#](#full-commit-history)

To find a complete list of changes since the previous release, check out [the diff on GitHub](https://github.com/flutter/devtools/compare/v2.20.0...v2.21.1).

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-07-20. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/release-notes/release-notes-2.21.1.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.21.1\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/release-notes/release-notes-2.21.1.md "Report an issue with this page").

----
url: https://docs.flutter.dev/app-architecture/guide
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Guide to app architecture](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

The following pages demonstrate how to build an app using best practices. The recommendations in this guide can be applied to most apps, making them easier to scale, test, and maintain. However, they're guidelines, not steadfast rules, and you should adapt them to your unique requirements.

This section provides a high-level overview of how Flutter applications can be architected. It explains the layers of an application, along with the classes that exist within each layer. The section after this provides concrete code samples and walks through a Flutter application that's implemented these recommendations.

## Overview of project structure

[#](#overview-of-project-structure)

[Separation-of-concerns](https://en.wikipedia.org/wiki/Separation_of_concerns) is the most important principle to follow when designing your Flutter app. Your Flutter application should split into two broad layers, the UI layer and the Data layer.

Each layer is further split into different components, each of which has distinct responsibilities, a well-defined interface, boundaries and dependencies. This guide recommends you split your application into the following components:

* Views
* View models
* Repositories
* Services

### MVVM

[#](#mvvm)

If you've encountered the [Model-View-ViewModel architectural pattern](https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93viewmodel) (MVVM), this will be familiar. MVVM is an architectural pattern that separates a feature of an application into three parts: the `Model`, the `ViewModel` and the `View`. Views and view models make up the UI layer of an application. Repositories and services represent the data of an application, or the model layer of MVVM. Each of these components is defined in the next section.

Every feature in an application will contain one view to describe the UI and one view model to handle logic, one or more repositories as the sources of truth for your application data, and zero or more services that interact with external APIs, like client servers and platform plugins.

A single feature of an application might require all of the following objects:

Each of these objects and the arrows that connect them will be explained thoroughly by the end of this page. Throughout this guide, the following simplified version of that diagram will be used as an anchor.

## UI layer

[#](#ui-layer)

An application's UI layer is responsible for interacting with the user. It displays an application's data to the user and receives user input, such as tap events and form inputs.

The UI reacts to data changes or user input. When the UI receives new data from a Repository, it should re-render to display that new data. When the user interacts with the UI, it should change to reflect that interaction.

The UI layer is made up of two architectural components, based on the MVVM design pattern:

* **Views** describe how to present application data to the user. Specifically, they refer to *compositions of widgets* that make a feature. For instance, a view is often (but not always) a screen that has a `Scaffold` widget, along with all of the widgets below it in the widget tree. Views are also responsible for passing events to the view model in response to user interactions.
* **View models** contain the logic that converts app data into *UI State*, because data from repositories is often formatted differently from the data that needs to be displayed. For example, you might need to combine data from multiple repositories, or you might want to filter a list of data records.

Views and view models should have a one-to-one relationship.

In the simplest terms, a view model manages the UI state and the view displays that state. Using views and view models, your UI layer can maintain state during configuration changes (such as screen rotations), and you can test the logic of your UI independently of Flutter widgets.

A feature of an application is user centric, and therefore defined by the UI layer. Every instance of a paired *view* and *view model* defines one feature in your app. This is often a screen in your app, but it doesn't have to be. For example, consider logging in and out. Logging in is generally done on a specific screen whose only purpose is to provide the user with a way to log in. In the application code, the login screen would be made up of a `LoginViewModel` class and a `LoginView` class.

On the other hand, logging out of an app is generally not done on a dedicated screen. The ability to log out is generally presented to the user as a button in a menu, a user account screen, or any number of different locations. It's often presented in multiple locations. In such scenarios, you might have a `LogoutViewModel` and a `LogoutView` which only contains a single button that can be dropped into other widgets.

### Views

[#](#views)

In Flutter, views are the widget classes of your application. Views are the primary method of rendering UI, and shouldn't contain any business logic. They should be passed all data they need to render from the view model.

The only logic a view should contain is:

* Simple if-statements to show and hide widgets based on a flag or nullable field in the view model
* Animation logic
* Layout logic based on device information, like screen size or orientation.
* Simple routing logic

All logic related to data should be handled in the view model.

### View models

[#](#view-models)

A view model exposes the application data necessary to render a view. In the architecture design described on this page, most of the logic in your Flutter application lives in view models.

A view model's main responsibilities include:

* Retrieving application data from repositories and transforming it into a format suitable for presentation in the view. For example, it might filter, sort, or aggregate data.
* Maintaining the current state needed in the view, so that the view can rebuild without losing data. For example, it might contain boolean flags to conditionally render widgets in the view, or a field that tracks which section of a carousel is active on screen.
* Exposes callbacks (called **commands**) to the view that can be attached to an event handler, like a button press or form submission.

Commands are named for the [command pattern](https://en.wikipedia.org/wiki/Command_pattern), and are Dart functions that allow views to execute complex logic without knowledge of its implementation. Commands are written as members of the view model class to be called by the gesture handlers in the view class.

You can find examples of views, view models, and commands on the [UI layer](/app-architecture/case-study/ui-layer) portion of the [App architecture case study](/app-architecture/case-study).

For a gentle introduction to MVVM in Flutter, check out the [state management fundamentals](/data-and-backend/state-mgmt/intro).

## Data layer

[#](#data-layer)

The data layer of an app handles your business data and logic. Two pieces of architecture make up the data layer: services and repositories. These pieces should have well-defined inputs and outputs to simplify their reusability and testability.

Using MVVM language, services and repositories make up your *model layer*.

### Repositories

[#](#repositories)

[Repository](https://martinfowler.com/eaaCatalog/repository.html) classes are the source of truth for your model data. They're responsible for polling data from services, and transforming that raw data into **domain models**. Domain models represent the data that the application needs, formatted in a way that your view model classes can consume. There should be a repository class for each different type of data handled in your app.

Repositories handle the business logic associated with services, such as:

* Caching
* Error handling
* Retry logic
* Refreshing data
* Polling services for new data
* Refreshing data based on user actions

Repositories output application data as domain models. For example, a social media app might have a `UserProfileRepository` class that exposes a `Stream<UserProfile?>`, which emits a new value whenever the user signs in or out.

The models output by repositories are consumed by view models. Repositories and view models have a many-to-many relationship. A view model can use many repositories to get the data it needs, and a repository can be used by many view models.

Repositories should never be aware of each other. If your application has business logic that needs data from two repositories, you should combine the data in the view model or in the domain layer, especially if your repository-to-view-model relationship is complex.

#### Managing app-wide session state

[#](#managing-app-wide-session-state)

Because repositories are the single source of truth for application data, they are also the ideal place to manage **app-wide lifecycle state**—state that needs to be shared across multiple view models but shouldn't persist beyond the current application session.

Examples of app-wide lifecycle state include an active user session, in-memory data caches, or transient application settings. Because view models and repositories have a many-to-many relationship, multiple view models can depend on the same repository instance (typically managed through a service locator or dependency injection container). This allows distinct features to reactively observe and modify the same shared state through streams and methods exposed by the repository, without violating the clean one-to-one boundary between a view and its view model.

### Services

[#](#services)

Services are in the lowest layer of your application. They wrap API endpoints and expose asynchronous response objects, such as `Future` and `Stream` objects. They're only used to isolate data-loading, and they hold no state. Your app should have one service class per data source. Examples of endpoints that services might wrap include:

* The underlying platform, like iOS and Android APIs
* REST endpoints
* Local files

As a rule of thumb, services are most helpful when the necessary data lives outside of your application's Dart code - which is true of each of the preceding examples.

Services and repositories have a many-to-many relationship. A single Repository can use several services, and a service can be used by multiple repositories.

## Optional: Domain layer

[#](#optional-domain-layer)

As your app grows and adds features, you might need to abstract away logic that adds too much complexity to your view models. These classes are often called interactors or **use-cases**.

Use-cases are responsible for making interactions between the UI and Data layers simpler and more reusable. They take data from repositories and make it suitable for the UI layer.

Use-cases are primarily used to encapsulate business logic that would otherwise live in the view model and meets one or more of the following conditions:

1. Requires merging data from multiple repositories
2. Is exceedingly complex
3. The logic will be reused by different view models

This layer is optional because not all applications or features within an application have these requirements. If you suspect your application would benefit from this additional layer, consider the pros and cons:

| Pros                                                                     | Cons                                                                                       |
| ------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------ |
| ✅ Avoid code duplication in view models                                  | ❌ Increases complexity of your architecture, adding more classes and higher cognitive load |
| ✅ Improve testability by separating complex business logic from UI logic | ❌ Testing requires additional mocks                                                        |
| ✅ Improve code readability in view models                                | ❌ Adds additional boilerplate to your code                                                 |

### Data access with use-cases

[#](#data-access-with-use-cases)

Another consideration when adding a Domain layer is whether view models will continue to have access to repository data directly, or if you'll enforce view models to go through use-cases to get their data. Put another way, will you add use-cases as you need them? Perhaps when you notice repeated logic in your view models? Or, will you create a use-case each time a view model needs data, even if the logic in the use-case is simple?

If you choose to do the latter, it intensifies the earlier outlined pros and cons. Your application code will be extremely modular and testable, but it also adds a significant amount of unnecessary overhead.

A good approach is to add use-cases only when needed. If you find that your view models are accessing data through use-cases most of the time, you can always refactor your code to utilize use-cases exclusively. The example app used later in this guide has use-cases for some features, but also has view models that interact with repositories directly. A complex feature might ultimately end up looking like this:

This method of adding use-cases is defined by the following rules:

* Use-cases depend on repositories
* Use-cases and repositories have a many-to-many relationship
* View models depend on one or more use-cases *and* one or more repositories

This method of using use-cases ends up looking less like a layered lasagna, and more like a plated dinner with two mains (UI and data layers) and a side (domain layer). Use-cases are just utility classes that have well-defined inputs and outputs. This approach is flexible and extendable, but it requires greater diligence to maintain order.

## Feedback

[#](#feedback)

As this section of the website is evolving, we [welcome your feedback](https://google.qualtrics.com/jfe/form/SV_4T0XuR9Ts29acw6?page=%22guide%22)!

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/app-architecture/guide.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/app-architecture/guide\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/app-architecture/guide.md "Report an issue with this page").

----
url: https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.14.0
----

The 2.14.0 release of the Dart and Flutter DevTools includes the following changes among other general improvements. To learn more about DevTools, check out the [DevTools overview](https://docs.flutter.dev/tools/devtools).

## General updates

[#](#general-updates)

* Added a link to the new DevTools [Discord channel](https://discord.com/channels/608014603317936148/958862085297672282) in the About DevTools dialog - [#4102](https://github.com/flutter/devtools/pull/4102)

## Network updates

[#](#network-updates)

* Added "Copy as URL" and "Copy as cURL" actions for selected requests in the network profiler (special thanks to [@jankuss](https://github.com/jankuss)!) - [#4113](https://github.com/flutter/devtools/pull/4113)

## Flutter inspector updates

[#](#flutter-inspector-updates)

* Added a setting to control whether hovering over a widget in the inspector displays its properties and values in a hover card - [#4090](https://github.com/flutter/devtools/pull/4090)

## Debugger updates

[#](#debugger-updates)

* Added auto complete suggestions in the console (special thanks to [@jankuss](https://github.com/jankuss)!) - [#4062](https://github.com/flutter/devtools/pull/4062)

* Added the option to copy the full file path for a selected library - [#4147](https://github.com/flutter/devtools/pull/4147)

* Fixed formatting in the debugger exception menu - [#4066](https://github.com/flutter/devtools/pull/4066)

## Memory updates

[#](#memory-updates)

* Fixed formatting for memory values in the heap tree view - [#4153](https://github.com/flutter/devtools/pull/4153)
* Fixed a bug that was preventing GC events from showing up in the memory chart - [#4131](https://github.com/flutter/devtools/pull/4131)

## Performance updates

[#](#performance-updates)

* Warn users that the rendering layer toggles in the "More Debugging Options" menu are not available for profile mode apps - [#4075](https://github.com/flutter/devtools/pull/4075)

## Full commit history

[#](#full-commit-history)

To find a complete list of changes since the previous release, check out [the diff on GitHub](https://github.com/flutter/devtools/compare/v2.13.1...v2.14.0).

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/release-notes/release-notes-2.14.0.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.14.0\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/release-notes/release-notes-2.14.0.md "Report an issue with this page").

----
url: https://docs.flutter.dev/cookbook/gestures
----

[Add Material touch ripples](/cookbook/gestures/ripples)

[How to implement ripple animations.](/cookbook/gestures/ripples)

[Implement swipe to dismiss](/cookbook/gestures/dismissible)

[How to implement swiping to dismiss or delete.](/cookbook/gestures/dismissible)

[Handle taps](/cookbook/gestures/handling-taps)

[How to handle tapping and dragging.](/cookbook/gestures/handling-taps)

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/gestures/index.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/cookbook/gestures\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/gestures/index.md "Report an issue with this page").

----
url: https://docs.flutter.dev/ui/assets
----

[Transforming assets at build time](/ui/assets/asset-transformation)

[How to set up automatic transformation of images (and other assets) in your Flutter app.](/ui/assets/asset-transformation)

[Adding assets and images](/ui/assets/assets-and-images)

[How to use images (and other assets) in your Flutter app.](/ui/assets/assets-and-images)

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/ui/assets/index.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/ui/assets\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/ui/assets/index.md "Report an issue with this page").

----
url: https://docs.flutter.dev/platform-integration/linux/building.md
----

\# Build Linux apps with Flutter > Platform-specific considerations when building for Linux with Flutter. This page discusses considerations unique to building Linux apps with Flutter, including shell integration and preparation of apps for distribution. ## Integrate with Linux The Linux programming interface, comprising library functions and system calls, is designed around the C language and ABI. Fortunately, Dart provides the \`dart:ffi\` package, which enables Dart programs to call into C libraries. Foreign Function Interfaces (FFI) allow Flutter apps to perform the following with native libraries: \* allocate native memory with \`malloc\` or \`calloc\` \* support pointers, structs, and callbacks \* support Application Binary Interface (ABI) types like \`long\` and \`size\_t\` To learn more about calling C libraries from Flutter, consult \[C interop using \`dart:ffi\`]\[]. To bundle and bind your own native C or C++ code with your app, see \[Bind to native code using FFI]\[]. Many apps benefit from using a package that wraps the underlying library calls in a more convenient, idiomatic Dart API. \[Canonical has built a series of packages]\[Canonical] with a focus on enabling Dart and Flutter on Linux, including support for desktop notifications, dbus, network management, and Bluetooth. In general, many other \[packages support creating Linux apps]\[support-linux], including common packages such as \[\`url\_launcher\`], \[\`shared\_preferences\`], \[\`file\_selector\`], and \[\`path\_provider\`]. \[C interop using \`dart:ffi\`]: https\://dart.dev/guides/libraries/c-interop \[Bind to native code using FFI]: /platform-integration/bind-native-code \[Canonical]: https\://pub.dev/publishers/canonical.com/packages \[support-linux]: https\://pub.dev/packages?q=platform%3Alinux \[\`url\_launcher\`]: https\://pub.dev/packages/url\_launcher \[\`shared\_preferences\`]: https\://pub.dev/packages/shared\_preferences \[\`file\_selector\`]: https\://pub.dev/packages/file\_selector \[\`path\_provider\`]: https\://pub.dev/packages/path\_provider ## Prepare Linux apps for distribution The executable binary can be found in your project under \`build/linux/x64//bundle/\`. Alongside your executable binary in the \`bundle\` directory, you can find two directories: \* \`lib\` contains the required \`.so\` library files \* \`data\` contains the application's data assets, such as fonts or images In addition to these files, your application also relies on various operating system libraries against which it's been compiled. To see the full list of libraries, use the \`ldd\` command on your application's directory. For example, to create a new Flutter desktop application called \`linux\_desktop\_test\`, build it, and inspect its system library dependencies, run the following commands: \`\`\`console $ flutter create linux\_desktop\_test $ cd linux\_desktop\_test $ flutter build linux --release $ ldd build/linux/x64/release/bundle/linux\_desktop\_test \`\`\` To wrap up this application for distribution, include everything in the \`bundle\` directory and verify the target Linux system has all required system libraries. This might only require using the following command. \`\`\`console $ sudo apt-get install libgtk-3-0 libblkid1 liblzma5 \`\`\` To learn how to publish a Linux application to the \[Snap Store], consult \[Build and release a Linux application to the Snap Store]\[]. ## Additional resources To learn how to create Linux Debian (\`.deb\`) and RPM (\`.rpm\`) builds of your Flutter desktop app, consult the step-by-step \[Linux packaging guide]\[linux\_packaging\_guide]. \[Snap Store]: https\://snapcraft.io/store \[Build and release a Linux application to the Snap Store]: /deployment/linux \[linux\_packaging\_guide]: https\://medium.com/@fluttergems/packaging-and-distributing-flutter-desktop-apps-the-missing-guide-part-3-linux-24ef8d30a5b4

----
url: https://docs.flutter.dev/cookbook/networking/update-data.md
----

\# Update data over the internet > How to use the http package to update data over the internet.

<!--?code-excerpt path-base="cookbook/networking/update_data/"?-->

Updating data over the internet is necessary for most apps. The \`http\` package has got that covered! This recipe uses the following steps: 1. Add the \`http\` package. 2. Update data over the internet using the \`http\` package. 3. Convert the response into a custom Dart object. 4. Get the data from the internet. 5. Update the existing \`title\` from user input. 6. Update and display the response on screen. ## 1. Add the \`http\` package To add the \`http\` package as a dependency, run \`flutter pub add\`: \`\`\`console $ flutter pub add http \`\`\` Import the \`http\` package.

<!--?code-excerpt "lib/main.dart (Http)"?-->

\`\`\`dart import 'package:http/http.dart' as http; import 'package:material\_ui/material\_ui.dart'; \`\`\` If you are deploying to Android, edit your \`AndroidManifest.xml\` file to add the Internet permission. \`\`\`xml \`\`\` Likewise, if you are deploying to macOS, edit your \`macos/Runner/DebugProfile.entitlements\` and \`macos/Runner/Release.entitlements\` files to include the network client entitlement. \`\`\`xml com.apple.security.network.client \`\`\` ## 2. Updating data over the internet using the \`http\` package This recipe covers how to update an album title to the \[JSONPlaceholder]\[] using the \[\`http.put()\`]\[] method.

<!--?code-excerpt "lib/main_step2.dart (updateAlbum)"?-->

\`\`\`dart Future updateAlbum(String title) { return http.put( Uri.parse('https\://jsonplaceholder.typicode.com/albums/1'), headers: { 'Content-Type': 'application/json; charset=UTF-8', }, body: jsonEncode({'title': title}), ); } \`\`\` The \`http.put()\` method returns a \`Future\` that contains a \`Response\`. \* \[\`Future\`]\[] is a core Dart class for working with async operations. A \`Future\` object represents a potential value or error that will be available at some time in the future. \* The \`http.Response\` class contains the data received from a successful http call. \* The \`updateAlbum()\` method takes an argument, \`title\`, which is sent to the server to update the \`Album\`. ## 3. Convert the \`http.Response\` to a custom Dart object While it's easy to make a network request, working with a raw \`Future\` isn't very convenient. To make your life easier, convert the \`http.Response\` into a Dart object. ### Create an Album class First, create an \`Album\` class that contains the data from the network request. It includes a factory constructor that creates an \`Album\` from JSON. Converting JSON with \[pattern matching]\[] is only one option. For more information, see the full article on \[JSON and serialization]\[].

<!--?code-excerpt "lib/main.dart (Album)"?-->

\`\`\`dart class Album { final int id; final String title; const Album({required this.id, required this.title}); factory Album.fromJson(Map json) { return switch (json) { {'id': int id, 'title': String title} => Album(id: id, title: title), \_ => throw const FormatException('Failed to load album.'), }; } } \`\`\` ### Convert the \`http.Response\` to an \`Album\` Now, use the following steps to update the \`updateAlbum()\` function to return a \`Future\`: 1. Convert the response body into a JSON \`Map\` with the \`dart:convert\` package. 2. If the server returns an \`UPDATED\` response with a status code of 200, then convert the JSON \`Map\` into an \`Album\` using the \`fromJson()\` factory method. 3. If the server doesn't return an \`UPDATED\` response with a status code of 200, then throw an exception. (Even in the case of a "404 Not Found" server response, throw an exception. Do not return \`null\`. This is important when examining the data in \`snapshot\`, as shown below.)

<!--?code-excerpt "lib/main.dart (updateAlbum)"?-->

\`\`\`dart Future updateAlbum(String title) async { final response = await http.put( Uri.parse('https\://jsonplaceholder.typicode.com/albums/1'), headers: { 'Content-Type': 'application/json; charset=UTF-8', }, body: jsonEncode({'title': title}), ); if (response.statusCode == 200) { // If the server did return a 200 OK response, // then parse the JSON. return Album.fromJson(jsonDecode(response.body) as Map); } else { // If the server did not return a 200 OK response, // then throw an exception. throw Exception('Failed to update album.'); } } \`\`\` Hooray! Now you've got a function that updates the title of an album. ### Get the data from the internet Get the data from internet before you can update it. For a complete example, see the \[Fetch data]\[] recipe.

<!--?code-excerpt "lib/main.dart (fetchAlbum)"?-->

\`\`\`dart Future fetchAlbum() async { final response = await http.get( Uri.parse('https\://jsonplaceholder.typicode.com/albums/1'), ); if (response.statusCode == 200) { // If the server did return a 200 OK response, // then parse the JSON. return Album.fromJson(jsonDecode(response.body) as Map); } else { // If the server did not return a 200 OK response, // then throw an exception. throw Exception('Failed to load album'); } } \`\`\` Ideally, you will use this method to set \`\_futureAlbum\` during \`initState\` to fetch the data from the internet. ## 4. Update the existing title from user input Create a \`TextField\` to enter a title and a \`ElevatedButton\` to update the data on server. Also define a \`TextEditingController\` to read the user input from a \`TextField\`. When the \`ElevatedButton\` is pressed, the \`\_futureAlbum\` is set to the value returned by \`updateAlbum()\` method.

<!--?code-excerpt "lib/main_step5.dart (Column)"?-->

\`\`\`dart Column( mainAxisAlignment: MainAxisAlignment.center, children: \[ Padding( padding: const EdgeInsets.all(8), child: TextField( controller: \_controller, decoration: const InputDecoration(hintText: 'Enter Title'), ), ), ElevatedButton( onPressed: () { setState(() { \_futureAlbum = updateAlbum(\_controller.text); }); }, child: const Text('Update Data'), ), ], ); \`\`\` On pressing the \*\*Update Data\*\* button, a network request sends the data in the \`TextField\` to the server as a \`PUT\` request. The \`\_futureAlbum\` variable is used in the next step. ## 5. Display the response on screen To display the data on screen, use the \[\`FutureBuilder\`]\[] widget. The \`FutureBuilder\` widget comes with Flutter and makes it easy to work with async data sources. You must provide two parameters: 1. The \`Future\` you want to work with. In this case, the future returned from the \`updateAlbum()\` function. 2. A \`builder\` function that tells Flutter what to render, depending on the state of the \`Future\`: loading, success, or error. Note that \`snapshot.hasData\` only returns \`true\` when the snapshot contains a non-null data value. This is why the \`updateAlbum\` function should throw an exception even in the case of a "404 Not Found" server response. If \`updateAlbum\` returns \`null\` then \`CircularProgressIndicator\` will display indefinitely.

<!--?code-excerpt "lib/main_step5.dart (FutureBuilder)"?-->

\`\`\`dart FutureBuilder( future: \_futureAlbum, builder: (context, snapshot) { if (snapshot.hasData) { return Text(snapshot.data!.title); } else if (snapshot.hasError) { return Text('${snapshot.error}'); } return const CircularProgressIndicator(); }, ); \`\`\` ## Complete example

<!--?code-excerpt "lib/main.dart"?-->

\`\`\`dart import 'dart:async'; import 'dart:convert'; import 'package:http/http.dart' as http; import 'package:material\_ui/material\_ui.dart'; Future fetchAlbum() async { final response = await http.get( Uri.parse('https\://jsonplaceholder.typicode.com/albums/1'), ); if (response.statusCode == 200) { // If the server did return a 200 OK response, // then parse the JSON. return Album.fromJson(jsonDecode(response.body) as Map); } else { // If the server did not return a 200 OK response, // then throw an exception. throw Exception('Failed to load album'); } } Future updateAlbum(String title) async { final response = await http.put( Uri.parse('https\://jsonplaceholder.typicode.com/albums/1'), headers: { 'Content-Type': 'application/json; charset=UTF-8', }, body: jsonEncode({'title': title}), ); if (response.statusCode == 200) { // If the server did return a 200 OK response, // then parse the JSON. return Album.fromJson(jsonDecode(response.body) as Map); } else { // If the server did not return a 200 OK response, // then throw an exception. throw Exception('Failed to update album.'); } } class Album { final int id; final String title; const Album({required this.id, required this.title}); factory Album.fromJson(Map json) { return switch (json) { {'id': int id, 'title': String title} => Album(id: id, title: title), \_ => throw const FormatException('Failed to load album.'), }; } } void main() { runApp(const MyApp()); } class MyApp extends StatefulWidget { const MyApp({super.key}); @override State createState() { return \_MyAppState(); } } class \_MyAppState extends State { final TextEditingController \_controller = TextEditingController(); late Future \_futureAlbum; @override void initState() { super.initState(); \_futureAlbum = fetchAlbum(); } @override Widget build(BuildContext context) { return MaterialApp( title: 'Update Data Example', theme: ThemeData( colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), ), home: Scaffold( appBar: AppBar(title: const Text('Update Data Example')), body: Container( alignment: Alignment.center, padding: const EdgeInsets.all(8), child: FutureBuilder( future: \_futureAlbum, builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.done) { if (snapshot.hasData) { return Column( mainAxisAlignment: MainAxisAlignment.center, children: \[ Text(snapshot.data!.title), TextField( controller: \_controller, decoration: const InputDecoration( hintText: 'Enter Title', ), ), ElevatedButton( onPressed: () { setState(() { \_futureAlbum = updateAlbum(\_controller.text); }); }, child: const Text('Update Data'), ), ], ); } else if (snapshot.hasError) { return Text('${snapshot.error}'); } } return const CircularProgressIndicator(); }, ), ), ), ); } } \`\`\` \[ConnectionState]: https\://api.flutter.dev/flutter/widgets/ConnectionState-class.html \[\`didChangeDependencies()\`]: https\://api.flutter.dev/flutter/widgets/State/didChangeDependencies.html \[Fetch data]: /cookbook/networking/fetch-data \[\`Future\`]: https\://api.flutter.dev/flutter/dart-async/Future-class.html \[\`FutureBuilder\`]: https\://api.flutter.dev/flutter/widgets/FutureBuilder-class.html \[\`http\`]: https\://pub.dev/packages/http \[\`http.put()\`]: https\://pub.dev/documentation/http/latest/http/put.html \[\`http\` package]: https\://pub.dev/packages/http/install \[\`InheritedWidget\`]: https\://api.flutter.dev/flutter/widgets/InheritedWidget-class.html \[Introduction to unit testing]: /cookbook/testing/unit/introduction \[\`initState()\`]: https\://api.flutter.dev/flutter/widgets/State/initState.html \[JSONPlaceholder]: https\://jsonplaceholder.typicode.com/ \[JSON and serialization]: /data-and-backend/serialization/json \[Mock dependencies using Mockito]: /cookbook/testing/unit/mocking \[pattern matching]: https\://dart.dev/language/patterns \[\`State\`]: https\://api.flutter.dev/flutter/widgets/State-class.html

----
url: https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.28.5.md
----

\# DevTools 2.28.5 release notes > Release notes for Dart and Flutter DevTools version 2.28.5. The 2.28.5 release of the Dart and Flutter DevTools includes the following changes among other general improvements. To learn more about DevTools, check out the \[DevTools overview]\(https\://docs.flutter.dev/tools/devtools). This was a cherry-pick release on top of DevTools 2.28.4. To learn about the improvements included in DevTools 2.28.4, please read the \[release notes]\(/tools/devtools/release-notes/release-notes-2.28.4). ## Inspector updates \* Only cache pub root directories added by the user. - \[#6897]\(https\://github.com/flutter/devtools/pull/6897) \* Remove Flutter pub root if it was accidentally cached. - \[#6911]\(https\://github.com/flutter/devtools/pull/6911) ## DevTools Extension updates \* Fixed a couple bugs preventing Dart server apps from connecting to DevTools extensions. - \[#6982]\(https\://github.com/flutter/devtools/pull/6982), \[#6993]\(https\://github.com/flutter/devtools/pull/6993) ## Full commit history To find a complete list of changes in this release, check out the \[DevTools git log]\(https\://github.com/flutter/devtools/tree/v2.28.5).

----
url: https://docs.flutter.dev/platform-integration/ios/ios-app-clip.md
----

\# Adding an iOS App Clip target > How to add an iOS App Clip target to your Flutter project. :::important Targeting iOS 16 increases the uncompressed IPA payload size limit to 15MB. Depending on the size of your app, you might hit the limit. (\[#71098]\[]). ::: This guide describes how to manually add another Flutter-rendering iOS App Clip target to your existing Flutter project or \[add-to-app]\[] project. \[#71098]: https\://github.com/flutter/flutter/issues/71098 \[add-to-app]: /add-to-app :::warning This is an advanced guide and is best intended for audience with a working knowledge of iOS development. ::: To see a working sample, see the \[App Clip sample]\[] on GitHub. \[App Clip sample]: https\://github.com/flutter/samples/tree/main/ios\_app\_clip ## Step 1 - Open project Open your iOS Xcode project, such as \`ios/Runner.xcworkspace\` for full-Flutter apps. ## Step 2 - Add an App Clip target \*\*2.1\*\* Click on your project in the Project Navigator to show the project settings. Press \*\*+\*\* at the bottom of the target list to add a new target. \*\*2.2\*\* Select the \*\*App Clip\*\* type for your new target. \*\*2.3\*\* Enter your new target detail in the dialog. Select \*\*Storyboard\*\* for Interface. Select the same language as your original target for \*\*Language\*\*. (In other words, to simplify the setup, don't create a Swift App Clip target for an Objective-C main target, and vice versa.) \*\*2.4\*\* In the following dialog, activate the new scheme for the new target. \*\*2.5\*\* Back in the project settings, open the \*\*Build Phases\*\* tab. Drag \*\*Embedded App Clips\*\* to above \*\*Thin Binary\*\*. []()## Step 3 - Remove unneeded files \*\*3.1\*\* In the Project Navigator, in the newly created App Clip group, delete everything except \`Info.plist\` and \`.entitlements\`. :::tip For add-to-app users, it's up to the reader to decide how much of this template to keep to invoke \`FlutterViewController\` or \`FlutterEngine\` APIs from this code later. ::: Move files to trash. \*\*3.2\*\* If you don't use the \`SceneDelegate.swift\` file, remove the reference to it in the \`Info.plist\`. Open the \`Info.plist\` file in the App Clip group. Delete the entire dictionary entry for \*\*Application Scene Manifest\*\*. ## Step 4 - Share build configurations This step isn't necessary for add-to-app projects since add-to-app projects have their custom build configurations and versions. \*\*4.1\*\* Back in the project settings, select the project entry now rather than any targets. In the \*\*Info\*\* tab, under the \*\*Configurations\*\* expandable group, expand the \*\*Debug\*\*, \*\*Profile\*\*, and \*\*Release\*\* entries. For each, select the same value from the drop-down menu for the App Clip target as the entry selected for the normal app target. This gives your App Clip target access to Flutter's required build settings. Set \*\*iOS Deployment Target\*\* to at least \*\*16.0\*\* to take advantage of the 15MB size limit. \*\*4.2\*\* In the App Clip group's \`Info.plist\` file, set: \* \`Build version string (short)\` to \`$(FLUTTER\_BUILD\_NAME)\` \* \`Bundle version\` to \`$(FLUTTER\_BUILD\_NUMBER)\` ## Step 5 - Share code and assets ### Option 1 - Share everything Assuming the intent is to show the same Flutter UI in the standard app as in the App Clip, share the same code and assets. For each of the following: \`Main.storyboard\`, \`Assets.xcassets\`, \`LaunchScreen.storyboard\`, \`GeneratedPluginRegistrant.m\`, and \`AppDelegate.swift\` (and \`Supporting Files/main.m\` if using Objective-C), select the file, then in the first tab of the inspector, also include the App Clip target in the \`Target Membership\` checkbox group. ### Option 2 - Customize Flutter launch for App Clip In this case, do not delete everything listed in \[Step 3]\(#step-3). Instead, use the scaffolding and the \[iOS add-to-app APIs]\[] to perform a custom launch of Flutter. For example to show a \[custom Flutter route]\[]. \[custom Flutter route]: /add-to-app/ios/add-flutter-screen#route \[iOS add-to-app APIs]: /add-to-app/ios/add-flutter-screen ## Step 6 - Add App Clip associated domains This is a standard step for App Clip development. See the \[official Apple documentation]\[]. \[official Apple documentation]: https\://developer.apple.com/documentation/app\_clips/creating\_an\_app\_clip\_with\_xcode#3604097 \*\*6.1\*\* Open the \`.entitlements\` file. Add an \`Associated Domains\` Array type. Add a row to the array with \`appclips:\`. \*\*6.2\*\* The same associated domains entitlement needs to be added to your main app, as well. Copy the \`.entitlements\` file from your App Clip group to your main app group and rename it to the same name as your main target such as \`Runner.entitlements\`. Open the file and delete the \`Parent Application Identifiers\` entry for the main app's entitlement file (leave that entry for the App Clip's entitlement file). \*\*6.3\*\* Back in the project settings, select the main app's target, open the \*\*Build Settings\*\* tab. Set the \*\*Code Signing Entitlements\*\* setting to the relative path of the second entitlements file created for the main app. ## Step 7 - Integrate Flutter These steps are not necessary for add-to-app. \*\*7.1\*\* For the Swift target, set the \`Objective-C Bridging Header\` build setting to \`Runner/Runner-Bridging-Header.h\` In other words, the same as the main app target's build settings. \*\*7.2\*\* Now open the \*\*Build Phases\*\* tab. Press the \*\*+\*\* sign and select \*\*New Run Script Phase\*\*. Drag that new phase to below the \*\*Dependencies\*\* phase. Expand the new phase and add this line to the script content: \`\`\`bash /bin/sh "$FLUTTER\_ROOT/packages/flutter\_tools/bin/xcode\_backend.sh" build \`\`\` Uncheck \*\*Based on dependency analysis\*\*. In other words, the same as the main app target's build phases. This ensures that your Flutter Dart code is compiled when running the App Clip target. \*\*7.3\*\* Press the \*\*+\*\* sign and select \*\*New Run Script Phase\*\* again. Leave it as the last phase. This time, add: \`\`\`bash /bin/sh "$FLUTTER\_ROOT/packages/flutter\_tools/bin/xcode\_backend.sh" embed\_and\_thin \`\`\` Uncheck \*\*Based on dependency analysis\*\*. In other words, the same as the main app target's build phases. This ensures that your Flutter app and engine are embedded into the App Clip bundle. ## Step 8 - Integrate plugins \*\*8.1\*\* Open the \`Podfile\` for your Flutter project or add-to-app host project. For full-Flutter apps, replace the following section: \`\`\`ruby target 'Runner' do use\_frameworks! use\_modular\_headers! flutter\_install\_all\_ios\_pods File.dirname(File.realpath(\_\_FILE\_\_)) end \`\`\` with: \`\`\`ruby use\_frameworks! use\_modular\_headers! flutter\_install\_all\_ios\_pods File.dirname(File.realpath(\_\_FILE\_\_)) target 'Runner' target '' \`\`\` At the top of the file, also uncomment \`platform :ios, '13.0'\` and set the version to the lowest of the two target's iOS Deployment Target. For add-to-app, add to: \`\`\`ruby target 'MyApp' do install\_all\_flutter\_pods(flutter\_application\_path) end \`\`\` with: \`\`\`ruby target 'MyApp' do install\_all\_flutter\_pods(flutter\_application\_path) end target '' install\_all\_flutter\_pods(flutter\_application\_path) end \`\`\` \*\*8.2\*\* From the command line, enter your Flutter project directory and then install the pod: \`\`\`console cd ios pod install \`\`\` ## Run You can now run your App Clip target from Xcode by selecting your App Clip target from the scheme drop-down, selecting an iOS 16 or higher device and pressing run. To test launching an App Clip from the beginning, also consult Apple's doc on \[Testing Your App Clip's Launch Experience]\[testing]. \[testing]: https\://developer.apple.com/documentation/app\_clips/testing\_your\_app\_clip\_s\_launch\_experience ## Debugging, hot reload Unfortunately \`flutter attach\` cannot auto-discover the Flutter session in an App Clip due to networking permission restrictions. You must then copy and paste it back into the \`flutter attach\` command to connect. For example: \`\`\`console flutter attach --debug-uri \`\`\`

----
url: https://docs.flutter.dev/platform-integration/web/faq.md
----

\# Web FAQ > Some gotchas and differences when writing or running web apps in Flutter. ## Questions ### What scenarios are ideal for Flutter on the web? Not every web page makes sense in Flutter, but we think Flutter is particularly suited for app-centric experiences: \* Progressive Web Apps \* Single Page Apps \* Existing Flutter mobile apps At this time, Flutter is not suitable for static websites with text-rich flow-based content. For example, blog articles benefit from the document-centric model that the web is built around, rather than the app-centric services that a UI framework like Flutter can deliver. However, you \_can\_ use Flutter to embed interactive experiences into these websites. For more information on how you can use Flutter on the web, see \[Web support for Flutter]\[]. ### Search Engine Optimization (SEO) In general, Flutter is geared towards dynamic application experiences. Flutter's web support is no exception. Flutter web prioritizes performance, fidelity, and consistency. This means application output doesn't align with what search engines need to properly index. However, a community-released Dart package, \[Jaspr]\[] \_does\_ support static websites. In fact, the \[Dart documentation]\[], \[Flutter documentation]\[], and \[Flutter marketing]\[] websites were migrated to using the Jaspr package. To summarize, for web content that is static or document-like, we recommend \_either\_ using: 1. \[Jaspr]\[], if you want to use Dart but want a more traditional DOM-based website. Also note that Jaspr makes SEO work in the same way a traditional website would. 1. HTML—in this case, consider separating your primary application experience (created in Flutter), from your landing page, marketing content, and help content (created using search engine optimized HTML). \[Dart documentation]: https\://dart.dev \[Flutter documentation]: / \[Flutter marketing]: https\://flutter.dev \[Jaspr]: https\://jaspr.site/ ### Does hot reload work with a web app? Yes! For more information, check out \[hot reload on the web]\[]. \[hot reload on the web]: /platform-integration/web/building#hot-reload-web ### Which web browsers are supported by Flutter? Flutter web apps can run on the following browsers: \* Chrome (mobile & desktop) \* Safari (mobile & desktop) \* Edge (mobile & desktop) \* Firefox (mobile & desktop) During development, Chrome (on macOS, Windows, and Linux), and Edge (on Windows) are supported as the default browsers for debugging your app. ### Can I build, run, and deploy web apps in any of the IDEs? You can select \*\*Chrome\*\* or \*\*Edge\*\* as the target device in Android Studio/IntelliJ and VS Code. The device pulldown should now include the \*\*Chrome (web)\*\* option for all channels. ### How do I build a responsive app for the web? See \[Creating responsive apps]\[]. ### Can I use \`dart:io\` with a web app? No. The file system is not accessible from the browser. For network functionality, use the \[\`http\`]\[] package. Note that security works somewhat differently because the browser (and not the app) controls the headers on an HTTP request. ### How do I handle web-specific imports? Some plugins require platform-specific imports, particularly if they use the file system, which is not accessible from the browser. To use these plugins in your app, use conditional imports. When compiling for WebAssembly, check for \`dart.library.js\_interop\` (rather than \`dart.library.js\` or \`dart.library.html\`, which are not supported on Wasm): \`\`\`dart import 'fallback.dart' if (dart.library.js) 'legacy\_web\_interop.dart' if (dart.library.js\_interop) 'wasm\_web\_interop.dart'; \`\`\` For more details, see the \[documentation for conditional imports]\[] on \[dart.dev]\(https\://dart.dev). For help dealing with Wasm compilation failures due to unsupported imports, check out \[Diagnosing Wasm compilation errors]\(/platform-integration/web/wasm#diagnosing-wasm-compilation-errors). ### Does Flutter web support concurrency? Dart's concurrency support that uses \[isolates]\[] is not currently supported in Flutter web. Flutter web apps can potentially work around this by using \[web workers]\[], although such support isn't built in. ### How do I deploy a web app? See \[Preparing a web app for release]\[]. ### Does \`Platform.is\` work on the web? No. While you can technically import \`dart:io\` when compiling for the web, calling any \`Platform.isXYZ\` method throws an \`UnsupportedError\`. Furthermore, importing \`dart:io\` in a package (except through conditional imports) causes pub.dev to score the package as not supporting the web. \* If you are developing a Flutter app, consider using \[\`kIsWeb\`]\[]. \* If you are developing a package (especially one without a Flutter dependency), consider using the \[\`os\_detect\`]\[] package. \[\`kIsWeb\`]: https\://api.flutter.dev/flutter/foundation/kIsWeb-constant.html ### Why doesn't my app update immediately after it's deployed? You might need to configure the \`Cache-Control\` header returned by your web server. For example, if this header is set to 3600, then the browser and CDN will cache the asset for 1 hour, and your users might see an out-of-date version of your app up to 1 hour after you deploy a new version. For more information about caching on the web, check out \[Prevent unnecessary network requests with the HTTP Cache]\[http-cache]. It's a good idea to be aware of this behavior to avoid an undesirable user experience. After you deploy your app, users might use a cached version of your app (cached by the browser or CDN) for the duration defined by your cache headers. This can lead to using a version of your app that is incompatible with changes that have been deployed to backend services. ### How do I clear the web cache after a deployment and force an app download? If you wish to defeat these cache headers after each deployment, a common technique is to append a build ID of some sort to the links of your static resources, or update the filenames themselves. For example, \`logo.png\` might become \`logo.v123.png\`. \`\`\`html \`\`\` Flutter doesn't currently support appending build IDs to resources automatically. ### How do I configure my cache headers? If you are using Firebase Hosting, the shared cache (CDN) is invalidated when you deploy a new version of your app. However, by default Firebase Hosting sets \`Cache-Control: max-age=3600\` on static files, meaning browsers can serve stale \`index.html\`, \`flutter\_bootstrap.js\`, or manifest files for up to an hour after a deploy. To make sure that browsers revalidate your app's files on each load (receiving fast \`304 Not Modified\` responses from the CDN edge when unchanged) and don't cache missing assets (\`404.html\`), configure your \`firebase.json\` file as follows: \`\`\`json { "hosting": { "public": "build/web", "headers": \[ { "source": "\*\*", "headers": \[ { "key": "Cache-Control", "value": "max-age=0, must-revalidate" } ] }, { "source": "404.html", "headers": \[ { "key": "Cache-Control", "value": "max-age=0, must-revalidate" } ] } ], "rewrites": \[ { "source": "!(/assets/\*\*|/canvaskit/\*\*|/icons/\*\*|/main.dart.\*)", "destination": "/index.html" } ] } } \`\`\` The \`/index.html\` rewrite exclusion (\`!(/assets/\*\*|/canvaskit/\*\*|/icons/\*\*|/main.dart.\*)\`) ensures that requests for missing assets or compiled entry points return a real \`404\` status rather than \`index.html\` with a \`200 OK\` status, and the \`404.html\` rule prevents the CDN from caching 404 responses under wildcard TTLs. ### How do I configure a service worker? Flutter no longer generates or manages a caching service worker by default (in newer versions, it writes a self-cleaning stub to remove legacy service workers). If your application requires offline support or advanced caching, you need to configure a service worker yourself using standard web tooling or third-party solutions such as \[Workbox]\[workbox]. For more information on building custom service workers, check out \[Using service workers]\[]. \[building a web app with Flutter]: /platform-integration/web/building \[Creating responsive apps]: /ui/adaptive-responsive \[documentation for conditional imports]: https\://dart.dev/interop/js-interop/package-web#conditional-imports \[Embedding Flutter web]: /platform-integration/web/embedding-flutter-web \[file an issue]: https\://github.com/flutter/flutter/issues/new?title=\[web]:+%3Cdescribe+issue+here%3E\&labels=%E2%98%B8+platform-web\&body=Describe+your+issue+and+include+the+command+you%27re+running,+flutter\_web%20version,+browser+version \[\`http\`]: https\://pub.dev/packages/http \[http-cache]: https\://web.dev/articles/http-cache \[\`iframe\`]: https\://html.com/tags/iframe/ \[isolates]: https\://dart.dev/guides/language/concurrency \[Issue 32248]: https\://github.com/flutter/flutter/issues/32248 \[Preparing a web app for release]: /deployment/web \[roadmap]: https\://github.com/flutter/flutter/blob/master/docs/roadmap/Roadmap.md#web-platform \[run your web apps in any supported browser]: /platform-integration/web/building#create-and-run \[Using service workers]: https\://developer.mozilla.org/en-US/docs/Web/API/Service\_Worker\_API/Using\_Service\_Workers \[Web content in Flutter]: /platform-integration/web/web-content-in-flutter \[Web support for Flutter]: /platform-integration/web \[web workers]: https\://developer.mozilla.org/en-US/docs/Web/API/Web\_Workers\_API/Using\_web\_workers \[workbox]: https\://github.com/GoogleChrome/workbox \[\`os\_detect\`]: https\://pub.dev/packages/os\_detect

----
url: https://docs.flutter.dev/learn/pathway/tutorial/user-input.md
----

\# User input > Accept input from the user with buttons and text fields. Learn to build text inputs, manage text with controllers, and handle user actions with buttons. title: What you'll accomplish items: - title: Build a text input widget with TextField icon: text\_fields - title: Manage text with TextEditingController icon: edit\_note - title: Control input focus for a better user experience icon: center\_focus\_strong - title: Handle user actions with callbacks and buttons icon: touch\_app --- ### Introduction The app will display the user's guesses in the \`Tile\` widgets, but it needs a way for the user to input those guesses. In this lesson, build that functionality with two interaction widgets: \[\`TextField\`]\[] and \[\`IconButton\`]\[]. \[\`TextField\`]: https\://pub.dev/documentation/material\_ui/latest/material\_ui/TextField-class.html \[\`IconButton\`]: https\://pub.dev/documentation/material\_ui/latest/material\_ui/IconButton-class.html ### Implement callback functions To allow users to type in their guesses, you'll create a dedicated widget named \`GuessInput\`. First, create the basic structure for your \`GuessInput\` widget that requires a callback function as an argument. Name the callback function \`onSubmitGuess\`. Add the following code to your \`main.dart\` file.

<!--?code-excerpt "fwe/birdle/lib/step4a_main.dart (GuessInput)"?-->

\`\`\`dart class GuessInput extends StatelessWidget { GuessInput({super.key, required this.onSubmitGuess}); final void Function(String) onSubmitGuess; @override Widget build(BuildContext context) { // You'll build the UI in the next steps. return Container(); // Placeholder } } \`\`\` The line \`final void Function(String) onSubmitGuess;\` declares a \`final\` member of the class called \`onSubmitGuess\` that has the type \`void Function(String)\`. This function takes a single \`String\` argument (the user's guess) and doesn't return any value (denoted by \`void\`). This callback tells us that the logic that actually handles the user's guess will be written elsewhere. It's a good practice for interactive widgets to use callback functions to keep the widget that handles interactions reusable and decoupled from any specific functionality. By the end of this lesson, the passed-in \`onSubmitGuess\` function is called when a user enters a guess. First, you'll need to build the visual parts of this widget. This is what the widget will look like. ### The \`TextField\` widget Given that the text field and button are displayed side-by-side, create them as a \`Row\` widget. Replace the \`Container\` placeholder in your \`build\` method with a \`Row\` containing an \`Expanded\` \`TextField\`:

<!--?code-excerpt "fwe/birdle/lib/step4b_main.dart (GuessInput)"?-->

\`\`\`dart class GuessInput extends StatelessWidget { GuessInput({super.key, required this.onSubmitGuess}); final void Function(String) onSubmitGuess; @override Widget build(BuildContext context) { return Row( children: \[ Expanded( child: Padding( padding: const EdgeInsets.all(8.0), child: TextField( maxLength: 5, decoration: InputDecoration( border: OutlineInputBorder( borderRadius: BorderRadius.all(Radius.circular(35)), ), ), ), ), ), ], ); } } \`\`\` You have seen some of these widgets in previous lessons: \`Row\` and \`Padding\`. New, though, is the \[\`Expanded\`]\[] widget. When a child of a \`Row\` (or \`Column\`) is wrapped in \`Expanded\`, it tells that child to fill all the available space along the main axis (horizontal for\`Row\`, vertical for \`Column\`) that hasn't been taken by other children. This makes the \`TextField\` stretch to take up all the space \*except\* what's taken by other widgets in the row. :::tip \`Expanded\` is often the solution to "\[unbounded width/height]\[]" exceptions. ::: The \`TextField\` widget is also new in this lesson and is the star of the show. This is the basic Flutter widget for text input. Thus far, \`TextField\` has the following configuration. - It's decorated with a rounded border. Notice that the decoration configuration is very similar to how a \`Container\` and boxes are decorated. - Its \`maxLength\` property is set to 5 because the game only allows guesses of 5-letter words. \[\`Expanded\`]: https\://api.flutter.dev/flutter/widgets/Expanded-class.html \[unbounded width/height]: https\://www\.youtube.com/watch?v=jckqXR5CrPI ### Handle text with \`TextEditingController\` Next, you need a way to manage the text that the user types into the input field. For this, use a \[\`TextEditingController\`]\[].

<!--?code-excerpt "fwe/birdle/lib/step4c_main.dart (GuessInput)"?-->

\`\`\`dart class GuessInput extends StatelessWidget { GuessInput({super.key, required this.onSubmitGuess}); final void Function(String) onSubmitGuess; // NEW final TextEditingController \_textEditingController = TextEditingController(); @override Widget build(BuildContext context) { return Row( children: \[ Expanded( child: Padding( padding: const EdgeInsets.all(8.0), child: TextField( maxLength: 5, decoration: InputDecoration( border: OutlineInputBorder( borderRadius: BorderRadius.all(Radius.circular(35)), ), ), ), ), ), // ], ); } } \`\`\` A \`TextEditingController\` is used to read, clear, and modify the text in a \`TextField\`. To use it, pass it into the \`TextField\`.

<!--?code-excerpt "fwe/birdle/lib/step4d_main.dart (GuessInput)"?-->

\`\`\`dart class GuessInput extends StatelessWidget { GuessInput({super.key, required this.onSubmitGuess}); final void Function(String) onSubmitGuess; final TextEditingController \_textEditingController = TextEditingController(); @override Widget build(BuildContext context) { return Row( children: \[ Expanded( child: Padding( padding: const EdgeInsets.all(8.0), child: TextField( maxLength: 5, decoration: const InputDecoration( border: OutlineInputBorder( borderRadius: BorderRadius.all(Radius.circular(35)), ), ), controller: \_textEditingController, // NEW ), ), ), ], ); } } \`\`\` Now, when a user inputs text, you can capture it with the \`\_textEditingController\`, but you'll need to know \_when\_ to capture it. The simplest way to react to input is by using the \`TextField.onSubmitted\` argument. This argument accepts a callback, and the callback is triggered whenever the user presses the "Enter" key on the keyboard while the text field has focus. For now, ensure that this works by adding the following callback to \`TextField.onSubmitted\`:

<!--?code-excerpt "fwe/birdle/lib/step4e_main.dart (GuessInput)"?-->

\`\`\`dart class GuessInput extends StatelessWidget { GuessInput({super.key, required this.onSubmitGuess}); final void Function(String) onSubmitGuess; final TextEditingController \_textEditingController = TextEditingController(); @override Widget build(BuildContext context) { return Row( children: \[ Expanded( child: Padding( padding: const EdgeInsets.all(8.0), child: TextField( maxLength: 5, decoration: const InputDecoration( border: OutlineInputBorder( borderRadius: BorderRadius.all(Radius.circular(35)), ), ), controller: \_textEditingController, onSubmitted: (input) { // NEW print(\_textEditingController.text); // Temporary }, ), ), ), ], ); } } \`\`\` In this case, you could print the \`input\` passed to the \`onSubmitted\` callback directly, but a better user experience clears the text after each guess: You need a \`TextEditingController\` to do that. Update the code as follows:

<!--?code-excerpt "fwe/birdle/lib/step4f_main.dart (GuessInput)"?-->

\`\`\`dart class GuessInput extends StatelessWidget { GuessInput({super.key, required this.onSubmitGuess}); final void Function(String) onSubmitGuess; final TextEditingController \_textEditingController = TextEditingController(); @override Widget build(BuildContext context) { return Row( children: \[ Expanded( child: Padding( padding: const EdgeInsets.all(8.0), child: TextField( maxLength: 5, decoration: const InputDecoration( border: OutlineInputBorder( borderRadius: BorderRadius.all(Radius.circular(35)), ), ), controller: \_textEditingController, onSubmitted: (\_) { // UPDATED print(\_textEditingController.text); // Temporary \_textEditingController.clear(); // NEW }, ), ), ), ], ); } } \`\`\` :::note In Dart, it's good practice to use the \`\_\` \[wildcard]\[] to hide the input to a function that'll never be used. The preceding example does so. ::: \[\`TextEditingController\`]: https\://api.flutter.dev/flutter/widgets/TextEditingController-class.html \[wildcard]: https\://dart.dev/language/variables#wildcard-variables ### Gain input focus Often, you want a specific input or widget to automatically gain focus without the user taking action. In this app, for example, the only thing a user can do is enter a guess, so the \`TextField\` should be focused automatically when the app launches. And after the user enters a guess, the focus should stay in the \`TextField\` so they can enter their next guess. To resolve the first focus issue, set up the \`autofocus\` property on the \`TextField\`.

<!--?code-excerpt "fwe/birdle/lib/step4g_main.dart (GuessInput)"?-->

\`\`\`dart class GuessInput extends StatelessWidget { GuessInput({super.key, required this.onSubmitGuess}); final void Function(String) onSubmitGuess; final TextEditingController \_textEditingController = TextEditingController(); @override Widget build(BuildContext context) { return Row( children: \[ Expanded( child: Padding( padding: const EdgeInsets.all(8.0), child: TextField( maxLength: 5, decoration: const InputDecoration( border: OutlineInputBorder( borderRadius: BorderRadius.all(Radius.circular(35)), ), ), controller: \_textEditingController, autofocus: true, // NEW onSubmitted: (input) { print(input); // Temporary \_textEditingController.clear(); }, ), ), ), ], ); } } \`\`\` The second issue requires you to use a \[\`FocusNode\`]\[] to manage the keyboard focus. You can use \`FocusNode\` to request that a \`TextField\` gain focus, (making the keyboard appear on mobile), or to know when a field has focus. First, create a \`FocusNode\` in the \`GuessInput\` class:

<!--?code-excerpt "fwe/birdle/lib/step4h_main.dart (GuessInput)"?-->

\`\`\`dart class GuessInput extends StatelessWidget { GuessInput({super.key, required this.onSubmitGuess}); final void Function(String) onSubmitGuess; final TextEditingController \_textEditingController = TextEditingController(); final FocusNode \_focusNode = FocusNode(); // NEW @override Widget build(BuildContext context) { // ... return Container(); } } \`\`\` Then, use the \`FocusNode\` to request focus whenever the \`TextField\` is submitted after the controller is cleared:

<!--?code-excerpt "fwe/birdle/lib/step4i_main.dart (GuessInput)"?-->

\`\`\`dart class GuessInput extends StatelessWidget { GuessInput({super.key, required this.onSubmitGuess}); final void Function(String) onSubmitGuess; final TextEditingController \_textEditingController = TextEditingController(); final FocusNode \_focusNode = FocusNode(); @override Widget build(BuildContext context) { return Row( children: \[ Expanded( child: Padding( padding: const EdgeInsets.all(8.0), child: TextField( maxLength: 5, decoration: const InputDecoration( border: OutlineInputBorder( borderRadius: BorderRadius.all(Radius.circular(35)), ), ), controller: \_textEditingController, autofocus: true, focusNode: \_focusNode, // NEW onSubmitted: (input) { print(input); // Temporary \_textEditingController.clear(); \_focusNode.requestFocus(); // NEW }, ), ), ), ], ); } } \`\`\` Now, when you press `Enter` after inputting text, you can continue typing. \[\`FocusNode\`]: https\://api.flutter.dev/flutter/widgets/FocusNode-class.html ### Use the input Finally, you need to handle the text that the user enters. Recall that the constructor for \`GuessInput\` requires a callback called \`onSubmitGuess\`. In \`GuessInput\`, you need to use that callback. Replace the \`print\` statement with a call to that function.

<!--?code-excerpt "fwe/birdle/lib/step4j_main.dart (GuessInput)"?-->

\`\`\`dart class GuessInput extends StatelessWidget { GuessInput({super.key, required this.onSubmitGuess}); final void Function(String) onSubmitGuess; final TextEditingController \_textEditingController = TextEditingController(); final FocusNode \_focusNode = FocusNode(); @override Widget build(BuildContext context) { return Row( children: \[ Expanded( child: Padding( padding: const EdgeInsets.all(8.0), child: TextField( maxLength: 5, decoration: const InputDecoration( border: OutlineInputBorder( borderRadius: BorderRadius.all(Radius.circular(35)), ), ), controller: \_textEditingController, autofocus: true, focusNode: \_focusNode, onSubmitted: (input) { onSubmitGuess(\_textEditingController.text.trim()); \_textEditingController.clear(); \_focusNode.requestFocus(); }, ), ), ), ], ); } } \`\`\` :::note The \`trim\` function prevents whitespace from being entered; otherwise, the user could enter a four-letter word plus a space character. ::: The remaining functionality is handled in the parent widget, \`GamePage\`. In the \`build\` method of that class, under the \`Row\` widgets in the \`Column\` widget's children, add the \`GuessInput\` widget:

<!--?code-excerpt "fwe/birdle/lib/step4k_main.dart (GamePage)"?-->

\`\`\`dart class GamePage extends StatelessWidget { GamePage({super.key}); final Game \_game = Game(); @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.all(8.0), child: Column( spacing: 5.0, children: \[ for (final guess in \_game.guesses) Row( spacing: 5.0, children: \[ for (final letter in guess) Tile(letter.char, letter.type), ], ), GuessInput( onSubmitGuess: (guess) { // TODO, handle guess print(guess); // Temporary }, ), ], ), ); } } \`\`\` For the moment, this only prints the guess to prove that it's wired up correctly. Submitting the guess requires using the functionality of a \`StatefulWidget\`, which you'll do in the next lesson. ### Buttons To improve the UX on mobile and reflect well-known UI practices, there should also be a button that can submit the guess. There are many button widgets built into Flutter, like \[\`TextButton\`]\[], \[\`ElevatedButton\`]\[], and the button you'll use now: \[\`IconButton\`]\[]. All of these buttons (and many other interaction widgets) require two arguments (in addition to their optional arguments): - A callback function passed to \`onPressed\`. - A widget that makes up the content of the button (often \`Text\` or an \`Icon\`). Add an icon button to the row widget's children list in the \`GuessInput\` widget, and give it an \[\`Icon\`]\[] widget to display. The \`Icon\` widget requires configuration; in this case, the \`padding\` property sets the padding between the edge of the button and the icon it wraps to zero. This removes the default padding and makes the button smaller.

<!--?code-excerpt "fwe/birdle/lib/step4l_main.dart (GuessInput)"?-->

\`\`\`dart class GuessInput extends StatelessWidget { GuessInput({super.key, required this.onSubmitGuess}); final void Function(String) onSubmitGuess; final TextEditingController \_textEditingController = TextEditingController(); final FocusNode \_focusNode = FocusNode(); @override Widget build(BuildContext context) { return Row( children: \[ Expanded(child: Container()), IconButton( padding: EdgeInsets.zero, icon: const Icon(Icons.arrow\_circle\_up), onPressed: null, ), ], ); } } \`\`\` The \`IconButton.onPressed\` callback should look familiar:

<!--?code-excerpt "fwe/birdle/lib/step4m_main.dart (GuessInput)"?-->

\`\`\`dart class GuessInput extends StatelessWidget { GuessInput({super.key, required this.onSubmitGuess}); final void Function(String) onSubmitGuess; final TextEditingController \_textEditingController = TextEditingController(); final FocusNode \_focusNode = FocusNode(); @override Widget build(BuildContext context) { return Row( children: \[ Expanded(child: Container()), IconButton( padding: EdgeInsets.zero, icon: const Icon(Icons.arrow\_circle\_up), onPressed: () { onSubmitGuess(\_textEditingController.text.trim()); \_textEditingController.clear(); \_focusNode.requestFocus(); }, ), ], ); } } \`\`\` This method does the same as the \`onSubmitted\` callback on the \`TextField\`. \[\`Icon\`]: https\://pub.dev/documentation/material\_ui/latest/material\_ui/Icons-class.html \[\`TextButton\`]: https\://pub.dev/documentation/material\_ui/latest/material\_ui/TextButton-class.html \[\`ElevatedButton\`]: https\://pub.dev/documentation/material\_ui/latest/material\_ui/ElevatedButton-class.html \[\`IconButton\`]: https\://pub.dev/documentation/material\_ui/latest/material\_ui/IconButton-class.html :::note Challenge - Share "on submitted" logic. You might be thinking, "Shouldn't we abstract these methods into one function and pass it to both inputs?" You could, and as your app grows in complexity, you probably should. That said, the callbacks \`IconButton.onPressed\` and \`TextField.onSubmitted\` have different signatures, so it's not completely straight-forward. Refactor the code such that the logic inside this method isn't repeated. \*\*Solution:\*\*

<!--?code-excerpt "fwe/birdle/lib/step4_main.dart (GuessInput)"?-->

\`\`\`dart title="solution.dart" collapsed class GuessInput extends StatelessWidget { GuessInput({super.key, required this.onSubmitGuess}); final void Function(String) onSubmitGuess; final TextEditingController \_textEditingController = TextEditingController(); final FocusNode \_focusNode = FocusNode(); void \_onSubmit() { onSubmitGuess(\_textEditingController.text); \_textEditingController.clear(); \_focusNode.requestFocus(); } @override Widget build(BuildContext context) { return Row( children: \[ Expanded( child: Padding( padding: const EdgeInsets.all(8.0), child: TextField( maxLength: 5, focusNode: \_focusNode, autofocus: true, decoration: const InputDecoration( border: OutlineInputBorder( borderRadius: BorderRadius.all(Radius.circular(35)), ), ), controller: \_textEditingController, onSubmitted: (value) { \_onSubmit(); }, ), ), ), IconButton( padding: EdgeInsets.zero, icon: const Icon(Icons.arrow\_circle\_up), onPressed: \_onSubmit, ), ], ); } } \`\`\` ::: ### Review title: What you accomplished subtitle: Here's a summary of what you built and learned in this lesson. completed: true items: - title: Built a text input widget with TextField icon: text\_fields details: >- You created a \`GuessInput\` widget with a \`TextField\` for text entry. You configured it with a rounded border, character limit, and used \`Expanded\` to make it fill available space in the row. - title: Managed text with TextEditingController icon: edit\_note details: >- \`TextEditingController\` lets you read and modify text field content. You used it to capture the user's input with \`.text\` and clear the field after submission with \`.clear()\`. - title: Controlled input focus for a polished UX icon: center\_focus\_strong details: >- You used \`autofocus\` to focus the text field on launch and \`FocusNode\` with \`requestFocus()\` to maintain focus after each guess. These details make your app feel responsive and well-built. - title: Handled user actions with callbacks and buttons icon: touch\_app details: >- To respond to user input, you specified callback functions like \`onSubmitted\` and \`onPressed\`. Passing callback functions as constructor arguments keeps your widgets reusable and decoupled from specific logic. ### Test yourself - question: How do you programmatically read or clear the text in a TextField? options: - text: Access the TextField's text property directly. correct: false explanation: TextField doesn't expose a text property; you need a controller. - text: Use the TextEditingController attached to the TextField. correct: true explanation: TextEditingController provides the text property to read the value and clear() method to reset it. - text: Listen to the onChanged callback and store the value in a variable. correct: false explanation: While onChanged works for reading, clearing requires a TextEditingController. - text: Call TextField.getText() method. correct: false explanation: TextField doesn't have a getText method; use TextEditingController instead. - question: How do you programmatically move focus to a specific TextField? options: - text: "Call \`TextField.focus()\` directly." correct: false explanation: TextField doesn't have a focus method; you use a FocusNode. - text: "Set the \`autofocus\` property to true at runtime." correct: false explanation: The 'autofocus' property only works on initial build, not for moving focus later. - text: "Use a FocusNode and call \`requestFocus()\` on it." correct: true explanation: "A FocusNode gives you control over focus, and calling \`requestFocus()\` moves focus to its associated widget." - text: Wrap the TextField in a GestureDetector and tap programmatically. correct: false explanation: This is not how focus is managed; FocusNode is the proper approach.

----
url: https://docs.flutter.dev/ui/widgets
----

Create beautiful apps faster with Flutter's collection of visual, structural, platform, and interactive widgets. In addition to browsing widgets by category, you can also see all the widgets in the [widget index](/reference/widgets).

## Design systems

[#](#design-systems)

The style-neutral [`widgets`](https://api.flutter.dev/flutter/widgets/) library ships with the Flutter SDK. Two UI design packages ([`material_ui`](https://pub.dev/packages/material_ui) and [`cupertino_ui`](https://pub.dev/packages/cupertino_ui)) are built on top of `widgets` and are available on [pub.dev](https://pub.dev), the package repository for Dart and Flutter.

[Beautiful and high-fidelity widgets that align with Apple's Human Interface Guidelines for iOS and macOS.](/ui/widgets/cupertino)

[Visual, behavioral, and motion-rich widgets implementing the Material 3 design specification.](/ui/widgets/material)

You can find more design systems created by the Flutter community on pub.dev. For example, the Windows-inspired [fluent\_ui](https://pub.dev/packages/fluent_ui), macOS-inspired [macos\_ui](https://pub.dev/packages/macos_ui), and the Ubuntu-inspired [yaru](https://pub.dev/packages/yaru) widgets.

## Base widgets

[#](#base-widgets)

Base widgets support a range of common rendering options like input, layout, and text.

[Accessibility](/ui/widgets/accessibility)

[Make your app accessible.](/ui/widgets/accessibility)

[Animation and motion](/ui/widgets/animation)

[Bring animations to your app.](/ui/widgets/animation)

[Assets, images, and icons](/ui/widgets/assets)

[Manage assets, display images, and show icons.](/ui/widgets/assets)

[Async](/ui/widgets/async)

[Widgets supporting async patterns in your Flutter apps.](/ui/widgets/async)

[Basics](/ui/widgets/basics)

[Widgets to know before building your first Flutter app.](/ui/widgets/basics)

[Input](/ui/widgets/input)

[Take user input in addition to input widgets in Material components and Cupertino.](/ui/widgets/input)

[Interaction models](/ui/widgets/interaction)

[Respond to touch events and route users to different views.](/ui/widgets/interaction)

[Layout](/ui/widgets/layout)

[Arrange other widgets columns, rows, grids, and many other layouts.](/ui/widgets/layout)

[Painting and effects](/ui/widgets/painting)

[These widgets apply visual effects to the children without changing their layout, size, or position.](/ui/widgets/painting)

[Scrolling](/ui/widgets/scrolling)

[Scroll multiple widgets as children of the parent.](/ui/widgets/scrolling)

[Styling](/ui/widgets/styling)

[Manage the theme of your app, make your app responsive to screen sizes, or add padding.](/ui/widgets/styling)

[Text](/ui/widgets/text)

[Display and style text.](/ui/widgets/text)

## Widget of the Week

[#](#widget-of-the-week)

100+ short, 1-minute explainer videos to help you quickly get started with Flutter widgets.

[Watch on YouTube in a new tab: "CupertinoRadio - Flutter widget of the week"](https://www.youtube.com/watch/D0xwcz2IqAY)

[Watch on YouTube in a new tab: "CupertinoSheetRoute - Flutter widget of the week"](https://www.youtube.com/watch/5H-WvH5O29I)

[Watch on YouTube in a new tab: "CupertinoSlidingSegmentedControl - Flutter widget of the week"](https://www.youtube.com/watch/esnBf6V4C34)

[Watch on YouTube in a new tab: "CupertinoCheckbox - Flutter widget of the week"](https://www.youtube.com/watch/ua54JU7k1Us)

[Watch on YouTube in a new tab: "CupertinoSwitch - Flutter widget of the week"](https://www.youtube.com/watch/24tg_N4sdMQ)

[Watch on YouTube in a new tab: "CarouselView - Flutter widget of the week"](https://www.youtube.com/watch/GQ8ajYVF0bo)

[Watch more widget of the week videos](https://www.youtube.com/playlist?list=PLjxrf2q8roU23XGwz3Km7sQZFTdB996iG)

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-09-29. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/ui/widgets/index.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/ui/widgets\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/ui/widgets/index.md "Report an issue with this page").

----
url: https://docs.flutter.dev/ui/design/graphics/fragment-shaders
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Writing and using fragment shaders](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

Custom shaders can be used to provide rich graphical effects beyond those provided by the Flutter SDK. A shader is a program authored in a small, Dart-like language, known as GLSL, and executed on the user's GPU.

Custom shaders are added to a Flutter project by listing them in the `pubspec.yaml` file, and obtained using the [`FragmentProgram`](https://api.flutter.dev/flutter/dart-ui/FragmentProgram-class.html) API.

## Adding shaders to an application

[#](#adding-shaders-to-an-application)

Shaders, in the form of GLSL files with the `.frag` extension, must be declared in the `shaders` section of your project's `pubspec.yaml` file. The Flutter command-line tool compiles the shader to its appropriate backend format, and generates its necessary runtime metadata. The compiled shader is then included in the application just like an asset.

yaml

```
flutter:
  shaders:
    - shaders/myshader.frag
```

When running in debug mode, changes to a shader program trigger recompilation and update the shader during hot reload or hot restart.

Shaders from packages are added to a project with `packages/$pkgname` prefixed to the shader program's name (where `$pkgname` is the name of the package).

### Loading shaders at runtime

[#](#loading-shaders-at-runtime)

To load a shader into a `FragmentProgram` object at runtime, use the [`FragmentProgram.fromAsset`](https://api.flutter.dev/flutter/dart-ui/FragmentProgram/fromAsset.html) constructor. The asset's name is the same as the path to the shader given in the `pubspec.yaml` file.

dart

```
void loadMyShader() async {
  var program = await FragmentProgram.fromAsset('shaders/myshader.frag');
}
```

The `FragmentProgram` object can be used to create one or more [`FragmentShader`](https://api.flutter.dev/flutter/dart-ui/FragmentShader-class.html) instances. A `FragmentShader` object represents a fragment program along with a particular set of *uniforms* (configuration parameters). The available uniforms depends on how the shader was defined.

dart

```
void updateShader(Canvas canvas, Rect rect, FragmentProgram program) {
  var shader = program.fragmentShader();
  shader.setFloat(0, 42.0);
  canvas.drawRect(rect, Paint()..shader = shader);
}
```

### Canvas API

[#](#canvas-api)

Fragment shaders can be used with most Canvas APIs by setting [`Paint.shader`](https://api.flutter.dev/flutter/dart-ui/Paint/shader.html). For example, when using [`Canvas.drawRect`](https://api.flutter.dev/flutter/dart-ui/Canvas/drawRect.html) the shader is evaluated for all fragments within the rectangle. For an API like [`Canvas.drawPath`](https://api.flutter.dev/flutter/dart-ui/Canvas/drawPath.html) with a stroked path, the shader is evaluated for all fragments within the stroked line. Some APIs, such as [`Canvas.drawImage`](https://api.flutter.dev/flutter/dart-ui/Canvas/drawImage.html), ignore the value of the shader.

dart

```
void paint(Canvas canvas, Size size, FragmentShader shader) {
  // Draws a rectangle with the shader used as a color source.
  canvas.drawRect(
    Rect.fromLTWH(0, 0, size.width, size.height),
    Paint()..shader = shader,
  );
​
  // Draws a stroked rectangle with the shader only applied to the fragments
  // that lie within the stroke.
  canvas.drawRect(
    Rect.fromLTWH(0, 0, size.width, size.height),
    Paint()
      ..style = PaintingStyle.stroke
      ..shader = shader,
  )
}
```

### ImageFilter API

[#](#imagefilter-api)

Fragment shaders can also be used with the [`ImageFilter`](https://api.flutter.dev/flutter/dart-ui/ImageFilter-class.html) API. This allows using custom fragment shaders with the [`ImageFiltered`](https://api.flutter.dev/flutter/widgets/ImageFiltered-class.html) class or the [`BackdropFilter`](https://api.flutter.dev/flutter/widgets/BackdropFilter-class.html) class to apply shaders to already rendered content. [`ImageFilter`](https://api.flutter.dev/flutter/dart-ui/ImageFilter-class.html) provides a constructor, [`ImageFilter.shader`](https://api.flutter.dev/flutter/dart-ui/ImageFilter/ImageFilter.shader.html), for creating an [`ImageFilter`](https://api.flutter.dev/flutter/dart-ui/ImageFilter-class.html) with a custom fragment shader.

dart

```
Widget build(BuildContext context, FragmentShader shader) {
  return ClipRect(
    child: SizedBox(
      width: 300,
      height: 300,
      child: BackdropFilter(
        filter: ImageFilter.shader(shader),
        child: Container(
          color: Colors.transparent,
        ),
      ),
    ),
  );
}
```

When using [`ImageFilter`](https://api.flutter.dev/flutter/dart-ui/ImageFilter-class.html) with [`BackdropFilter`](https://api.flutter.dev/flutter/widgets/BackdropFilter-class.html), a [`ClipRect`](https://api.flutter.dev/flutter/widgets/ClipRect-class.html) can be used to limit the area that is affected by the [`ImageFilter`](https://api.flutter.dev/flutter/dart-ui/ImageFilter-class.html). Without a [`ClipRect`](https://api.flutter.dev/flutter/widgets/ClipRect-class.html) the [`BackdropFilter`](https://api.flutter.dev/flutter/widgets/BackdropFilter-class.html) will be applied to the whole screen.

`ImageFilter` fragment shaders receive some uniforms automatically from the engine. The `sampler2D` value at index 0 is set to the filter input image, and the `float` values at indices 0 and 1 are set to the image's width and height. Your shader must specify this constructor to accept these values (for example, a `sampler2D` and a `vec2`), but you should not set them from your Dart code.

When targeting OpenGLES the y-coordinates of the texture will be flipped so the fragment shader should un-flip the UVs when sampling from textures provided by the engine.

glsl

```
#version 460 core
#include <flutter/runtime_effect.glsl>
​
out vec4 fragColor;
​
// These uniforms are automatically set by the engine.
uniform vec2 u_size;
uniform sampler2D u_texture;
​
void main() {
  vec2 uv = FlutterFragCoord().xy / u_size;
#ifdef IMPELLER_TARGET_OPENGLES
  // When sampling from u_texture on OpenGLES the y-coordinates will be flipped.
  uv.y = 1.0 - uv.y;
#endif
  vec4 color = texture(u_texture, uv);
  float gray = dot(color.rgb, vec3(0.299, 0.587, 0.114));
  fragColor = vec4(vec3(gray), color.a);
}
```

## Authoring shaders

[#](#authoring-shaders)

Fragment shaders are authored as GLSL source files. By convention, these files have the `.frag` extension. (Flutter doesn't support vertex shaders, which would have the `.vert` extension.)

Any GLSL version from 460 down to 100 is supported, though some available features are restricted. The rest of the examples in this document use version `460 core`.

Shaders are subject to the following limitations when used with Flutter:

* UBOs and SSBOs aren't supported
* `sampler2D` is the only supported sampler type
* Only the two-argument version of `texture` (sampler and uv) is supported
* No additional varying inputs can be declared
* All precision hints are ignored when targeting Skia
* Unsigned integers and booleans aren't supported

### Uniforms

[#](#uniforms)

A fragment program can be configured by defining `uniform` values in the GLSL shader source and then setting these values in Dart for each fragment shader instance.

Floating point uniforms with the GLSL types `float`, `vec2`, `vec3`, and `vec4` are set using the [`FragmentShader.setFloat`](https://api.flutter.dev/flutter/dart-ui/FragmentShader/setFloat.html) or [`FragmentShader.getUniformFloat`](https://api.flutter.dev/flutter/dart-ui/FragmentShader/getUniformFloat.html) method. GLSL sampler values, which use the `sampler2D` type, are set using the [`FragmentShader.setImageSampler`](https://api.flutter.dev/flutter/dart-ui/FragmentShader/setImageSampler.html) or [`FragmentShader.getImageSampler`](https://api.flutter.dev/flutter/dart-ui/FragmentShader/getImageSampler.html) method.

The correct index for each `uniform` value is determined by the order that the uniform values are defined in the fragment program. For data types composed of multiple floats, such as a `vec4`, you must call [`FragmentShader.setFloat`](https://api.flutter.dev/flutter/dart-ui/FragmentShader/setFloat.html) or [`UniformFloatSlot.set`](https://api.flutter.dev/flutter/dart-ui/UniformFloatSlot/set.html) once for each value.

For example, given the following uniforms declarations in a GLSL fragment program:

glsl

```
uniform float uScale;
uniform sampler2D uTexture;
uniform vec2 uMagnitude;
uniform vec4 uColor;
```

The corresponding Dart code to initialize these `uniform` values is as follows:

dart

```
class Foobar {
  late final UniformFloatSlot _scale;
  late final List<UniformFloatSlot> _magnitude;
  late final List<UniformFloatSlot> _color;
  late final ImageSamplerSlot _texture;
​
  void setUp(FragmentShader shader) {
    _scale = shader.getUniformFloat('uScale');
    _magnitude = List<UniformFloatSlot>.generate(2, (int index) {
      return shader.getUniformFloat('uMagnitude', index);
    });
    _color = List<UniformFloatSlot>.generate(4, (int index) {
      return shader.getUniformFloat('uColor', index);
    });
    _texture = shader.getImageSampler('uTexture');
  }
​
  void update(Color color, Image image) {
    _scale.set(23);
    _magnitude[0].set(114);
    _magnitude[1].set(83);
    _color[0].set(color.r * color.a);
    _color[1].set(color.g * color.a);
    _color[2].set(color.b * color.a);
    _color[3].set(color.a);
    _texture.set(image);
  }
}
```

When using [`FragmentShader.setFloat`](https://api.flutter.dev/flutter/dart-ui/FragmentShader/setFloat.html) note that the indices do not count the `sampler2D` uniform. This uniform is set separately with [`FragmentShader.setImageSampler`](https://api.flutter.dev/flutter/dart-ui/FragmentShader/setImageSampler.html), with the index starting over at 0.

Any float uniforms that are left uninitialized will default to `0.0`.

The reflection data generated by the Flutter's shader compiler can be audited with the following commands in order to see things like uniform offsets.

shell

```
cd $FLUTTER
# Generate the .sl file.
`find bin/ -name impellerc` \
  --runtime-stage-metal \
  --iplr \
  --input=path/to/myshader.frag \
  --sl=foo.sl \
  --spirv=foo.spirv \
  --include=engine/src/flutter/impeller/compiler/shader_lib/ \
  --input-type=frag
# Convert the .sl file to .json
flatc \
  --json \
  ./engine/src/flutter/impeller/runtime_stage/runtime_stage.fbs \
  -- ./foo.sl
# View results
cat foo.json
```

#### Current position

[#](#current-position)

The shader has access to a `varying` value that contains the local coordinates for the particular fragment being evaluated. Use this feature to compute effects that depend on the current position, which can be accessed by importing the `flutter/runtime_effect.glsl` library and calling the `FlutterFragCoord` function. For example:

glsl

```
#include <flutter/runtime_effect.glsl>
​
void main() {
  vec2 currentPos = FlutterFragCoord().xy;
}
```

The value returned from `FlutterFragCoord` is distinct from `gl_FragCoord`. `gl_FragCoord` provides the screen space coordinates and should generally be avoided to ensure that shaders are consistent across backends. When targeting a Skia backend, the calls to `gl_FragCoord` are rewritten to access local coordinates but this rewriting isn't possible with Impeller.

#### Colors

[#](#colors)

There isn't a built-in data type for colors. Instead they are commonly represented as a `vec4` with each component corresponding to one of the RGBA color channels.

The single output `fragColor` expects that the color value is normalized to be in the range of `0.0` to `1.0` and that it has premultiplied alpha. This is different than typical Flutter colors which use a `0-255` value encoding and have unpremultipled alpha.

#### Samplers

[#](#samplers)

A sampler provides access to a `dart:ui` `Image` object. This image can be acquired either from a decoded image or from part of the application using [`Scene.toImageSync`](https://api.flutter.dev/flutter/dart-ui/Scene/toImageSync.html) or [`Picture.toImageSync`](https://api.flutter.dev/flutter/dart-ui/Picture/toImageSync.html).

##### Sampler usage in GLSL example

[#](#sampler-usage-in-glsl-example)

glsl

```
#include <flutter/runtime_effect.glsl>
​
uniform vec2 uSize;
uniform sampler2D uTexture;
​
out vec4 fragColor;
​
void main() {
  vec2 uv = FlutterFragCoord().xy / uSize;
  fragColor = texture(uTexture, uv);
}
```

By default, the image uses [`TileMode.clamp`](https://api.flutter.dev/flutter/dart-ui/TileMode.html) to determine how values outside of the range of `[0, 1]` behave. Customization of the tile mode is not supported and needs to be emulated in the shader.

##### `toImageSync` example

[#](#toimagesync-example)

dart

```
class SDFPainter {
  SDFPainter(this.sdfShader, this.renderShader);
​
  FragmentShader sdfShader;
  FragmentShader renderShader;
  Image? _sdf;
  bool isDirty = false;
  double radius = 0.5;
​
  void paint(Canvas canvas, Size size) {
    if (_sdf == null || isDirty) {
      final recorder = PictureRecorder();
      final subCanvas = Canvas(recorder);
      final paint = Paint()..shader = sdfShader;
      sdfShader.setFloat(0, size.width);
      sdfShader.setFloat(1, size.height);
      sdfShader.setFloat(2, radius);
      subCanvas.drawRect(Rect.fromLTWH(0, 0, size.width, size.height), paint);
      final picture = recorder.endRecording();
      _sdf = picture.toImageSync(size.width.toInt(), size.height.toInt());
      isDirty = false;
    }
​
    renderShader.setFloat(0, size.width);
    renderShader.setFloat(1, size.height);
    renderShader.setImageSampler(0, _sdf!);
​
    canvas.drawRect(
      Rect.fromLTWH(0, 0, size.width, size.height),
      Paint()..shader = renderShader,
    );
  }
}
```

## Performance considerations

[#](#performance-considerations)

When targeting the Skia backend, loading the shader might be expensive since it must be compiled to the appropriate platform-specific shader at runtime. If you intend to use one or more shaders during an animation, consider precaching the fragment program objects before starting the animation.

You can reuse a `FragmentShader` object across frames; this is more efficient than creating a new `FragmentShader` for each frame.

For a more detailed guide on writing performant shaders, check out [Writing efficient shaders](https://github.com/flutter/flutter/blob/main/docs/engine/impeller/docs/shader_optimization.md) on GitHub.

## Other resources

[#](#other-resources)

For more information, here are a few resources.

* [The Book of Shaders](https://thebookofshaders.com/) by Patricio Gonzalez Vivo and Jen Lowe
* [Shader toy](https://www.shadertoy.com/), a collaborative shader playground
* [`simple_shader`](https://github.com/flutter/samples/tree/main/simple_shader), a simple Flutter fragment shaders sample project
* [`flutter_shaders`](https://pub.dev/packages/flutter_shaders), a package that simplifies using fragment shaders in Flutter

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/ui/design/graphics/fragment-shaders.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/ui/design/graphics/fragment-shaders\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/ui/design/graphics/fragment-shaders.md "Report an issue with this page").

----
url: https://docs.flutter.dev/ui/interactivity/focus
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Understanding Flutter's keyboard focus system](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

This article explains how to control where keyboard input is directed. If you are implementing an application that uses a physical keyboard, such as most desktop and web applications, this page is for you. If your app won't be used with a physical keyboard, you can skip this.

## Overview

[#](#overview)

Flutter comes with a focus system that directs the keyboard input to a particular part of an application. In order to do this, users "focus" the input onto that part of an application by tapping or clicking the desired UI element. Once that happens, text entered with the keyboard flows to that part of the application until the focus moves to another part of the application. Focus can also be moved by pressing a particular keyboard shortcut, which is typically bound to `Tab`, so it is sometimes called "tab traversal".

This page explores the APIs used to perform these operations on a Flutter application, and how the focus system works. We have noticed that there is some confusion among developers about how to define and use [`FocusNode`](https://api.flutter.dev/flutter/widgets/FocusNode-class.html) objects. If that describes your experience, skip ahead to the [best practices for creating `FocusNode` objects](#best-practices-for-creating-focusnode-objects).

### Focus use cases

[#](#focus-use-cases)

Some examples of situations where you might need to know how to use the focus system:

* [Receiving/handling key events](#key-events)
* [Implementing a custom component that needs to be focusable](#focus-widget)
* [Receiving notifications when the focus changes](#change-notifications)
* [Changing or defining the "tab order" of focus traversal in an application](#focustraversalpolicy)
* [Defining groups of controls that should be traversed together](#focustraversalgroup-widget)
* [Preventing some controls in an application from being focusable](#controlling-what-gets-focus)

## Glossary

[#](#glossary)

Below are terms, as Flutter uses them, for elements of the focus system. The various classes that implement some of these concepts are introduced below.

* **Focus tree** - A tree of focus nodes that typically sparsely mirrors the widget tree, representing all the widgets that can receive focus.
* **Focus node** - A single node in a focus tree. This node can receive the focus, and is said to "have focus" when it is part of the focus chain. It participates in handling key events only when it has focus.
* **Primary focus** - The farthest focus node from the root of the focus tree that has focus. This is the focus node where key events start propagating to the primary focus node and its ancestors.
* **Focus chain** - An ordered list of focus nodes that starts at the primary focus node and follows the branches of the focus tree to the root of the focus tree.
* **Focus scope** - A special focus node whose job is to contain a group of other focus nodes, and allow only those nodes to receive focus. It contains information about which nodes were previously focused in its subtree.
* **Focus traversal** - The process of moving from one focusable node to another in a predictable order. This is typically seen in applications when the user presses `Tab` to move to the next focusable control or field.

## FocusNode and FocusScopeNode

[#](#focusnode-and-focusscopenode)

The `FocusNode` and [`FocusScopeNode`](https://api.flutter.dev/flutter/widgets/FocusScopeNode-class.html) objects implement the mechanics of the focus system. They are long-lived objects (longer than widgets, similar to render objects) that hold the focus state and attributes so that they are persistent between builds of the widget tree. Together, they form the focus tree data structure.

They were originally intended to be developer-facing objects used to control some aspects of the focus system, but over time they have evolved to mostly implement details of the focus system. In order to prevent breaking existing applications, they still contain public interfaces for their attributes. But, in general, the thing for which they are most useful is to act as a relatively opaque handle, passed to a descendant widget in order to call `requestFocus()` on an ancestor widget, which requests that a descendant widget obtain focus. Setting of the other attributes is best managed by a [`Focus`](https://api.flutter.dev/flutter/widgets/Focus-class.html) or [`FocusScope`](https://api.flutter.dev/flutter/widgets/FocusScope-class.html) widget, unless you are not using them, or implementing your own version of them.

### Best practices for creating FocusNode objects

[#](#best-practices-for-creating-focusnode-objects)

Some dos and don'ts around using these objects include:

* Don't allocate a new `FocusNode` for each build. This can cause memory leaks, and occasionally causes a loss of focus when the widget rebuilds while the node has focus.
* Do create `FocusNode` and `FocusScopeNode` objects in a stateful widget. `FocusNode` and `FocusScopeNode` need to be disposed of when you're done using them, so they should only be created inside of a stateful widget's state object, where you can override `dispose` to dispose of them.
* Don't use the same `FocusNode` for multiple widgets. If you do, the widgets will fight over managing the attributes of the node, and you probably won't get what you expect.
* Do set the `debugLabel` of a focus node widget to help with diagnosing focus issues.
* Don't set the `onKeyEvent` callback on a `FocusNode` or `FocusScopeNode` if they are being managed by a `Focus` or `FocusScope` widget. If you want an `onKeyEvent` handler, then add a new `Focus` widget around the widget subtree you would like to listen to, and set the `onKeyEvent` attribute of the widget to your handler. Set `canRequestFocus: false` on the widget if you also don't want it to be able to take primary focus. This is because the `onKeyEvent` attribute on the `Focus` widget can be set to something else in a subsequent build, and if that happens, it overwrites the `onKeyEvent` handler you set on the node.
* Do call `requestFocus()` on a node to request that it receives the primary focus, especially from an ancestor that has passed a node it owns to a descendant where you want to focus.
* Do use `focusNode.requestFocus()`. It is not necessary to call `FocusScope.of(context).requestFocus(focusNode)`. The `focusNode.requestFocus()` method is equivalent and more performant.

### Unfocusing

[#](#unfocusing)

There is an API for telling a node to "give up the focus", named `FocusNode.unfocus()`. While it does remove focus from the node, it is important to realize that there really is no such thing as "unfocusing" all nodes. If a node is unfocused, then it must pass the focus somewhere else, since there is *always* a primary focus. The node that receives the focus when a node calls `unfocus()` is either the nearest `FocusScopeNode`, or a previously focused node in that scope, depending upon the `disposition` argument given to `unfocus()`. If you would like more control over where the focus goes when you remove it from a node, explicitly focus another node instead of calling `unfocus()`, or use the focus traversal mechanism to find another node with the `focusInDirection`, `nextFocus`, or `previousFocus` methods on `FocusNode`.

When calling `unfocus()`, the `disposition` argument allows two modes for unfocusing: [`UnfocusDisposition.scope`](https://api.flutter.dev/flutter/widgets/UnfocusDisposition.html) and `UnfocusDisposition.previouslyFocusedChild`. The default is `scope`, which gives the focus to the nearest parent focus scope. This means that if the focus is thereafter moved to the next node with `FocusNode.nextFocus`, it starts with the "first" focusable item in the scope.

The `previouslyFocusedChild` disposition will search the scope to find the previously focused child and request focus on it. If there is no previously focused child, it is equivalent to `scope`.

## Focus widget

[#](#focus-widget)

The `Focus` widget owns and manages a focus node, and is the workhorse of the focus system. It manages the attaching and detaching of the focus node it owns from the focus tree, manages the attributes and callbacks of the focus node, and has static functions to enable discovery of focus nodes attached to the widget tree.

In its simplest form, wrapping the `Focus` widget around a widget subtree allows that widget subtree to obtain focus as part of the focus traversal process, or whenever `requestFocus` is called on the `FocusNode` passed to it. When combined with a gesture detector that calls `requestFocus`, it can receive focus when tapped or clicked.

You might pass a `FocusNode` object to the `Focus` widget to manage, but if you don't, it creates its own. The main reason to create your own `FocusNode` is to be able to call `requestFocus()` on the node to control the focus from a parent widget. Most of the other functionality of a `FocusNode` is best accessed by changing the attributes of the `Focus` widget itself.

The `Focus` widget is used in most of Flutter's own controls to implement their focus functionality.

Here is an example showing how to use the `Focus` widget to make a custom control focusable. It creates a container with text that reacts to receiving the focus.

dart

```
import 'package:material_ui/material_ui.dart';
​
void main() => runApp(const MyApp());
​
class MyApp extends StatelessWidget {
  const MyApp({super.key});
  static const String _title = 'Focus Sample';
​
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: _title,
      home: Scaffold(
        appBar: AppBar(title: const Text(_title)),
        body: const Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[MyCustomWidget(), MyCustomWidget()],
        ),
      ),
    );
  }
}
​
class MyCustomWidget extends StatefulWidget {
  const MyCustomWidget({super.key});
​
  @override
  State<MyCustomWidget> createState() => _MyCustomWidgetState();
}
​
class _MyCustomWidgetState extends State<MyCustomWidget> {
  Color _color = Colors.white;
  String _label = 'Unfocused';
​
  @override
  Widget build(BuildContext context) {
    return Focus(
      onFocusChange: (focused) {
        setState(() {
          _color = focused ? Colors.black26 : Colors.white;
          _label = focused ? 'Focused' : 'Unfocused';
        });
      },
      child: Center(
        child: Container(
          width: 300,
          height: 50,
          alignment: Alignment.center,
          color: _color,
          child: Text(_label),
        ),
      ),
    );
  }
}
```

### Key events

[#](#key-events)

If you wish to listen for key events in a subtree, set the `onKeyEvent` attribute of the `Focus` widget to be a handler that either just listens to the key, or handles the key and stops its propagation to other widgets.

Key events start at the focus node with primary focus. If that node doesn't return `KeyEventResult.handled` from its `onKeyEvent` handler, then its parent focus node is given the event. If the parent doesn't handle it, it goes to its parent, and so on, until it reaches the root of the focus tree. If the event reaches the root of the focus tree without being handled, then it is returned to the platform to give to the next native control in the application (in case the Flutter UI is part of a larger native application UI). Events that are handled are not propagated to other Flutter widgets, and they are also not propagated to native widgets.

Here's an example of a `Focus` widget that absorbs every key that its subtree doesn't handle, without being able to be the primary focus:

dart

```
@override
Widget build(BuildContext context) {
  return Focus(
    onKeyEvent: (node, event) => KeyEventResult.handled,
    canRequestFocus: false,
    child: child,
  );
}
```

Focus key events are processed before text entry events, so handling a key event when the focus widget surrounds a text field prevents that key from being entered into the text field.

Here's an example of a widget that won't allow the letter "a" to be typed into the text field:

dart

```
@override
Widget build(BuildContext context) {
  return Focus(
    onKeyEvent: (node, event) {
      return (event.logicalKey == LogicalKeyboardKey.keyA)
          ? KeyEventResult.handled
          : KeyEventResult.ignored;
    },
    child: const TextField(),
  );
}
```

If the intent is input validation, this example's functionality would probably be better implemented using a `TextInputFormatter`, but the technique can still be useful: the `Shortcuts` widget uses this method to handle shortcuts before they become text input, for instance.

### Controlling what gets focus

[#](#controlling-what-gets-focus)

One of the main aspects of focus is controlling what can receive focus and how. The attributes `canRequestFocus`, `skipTraversal,` and `descendantsAreFocusable` control how this node and its descendants participate in the focus process.

If the `skipTraversal` attribute true, then this focus node doesn't participate in focus traversal. It is still focusable if `requestFocus` is called on its focus node, but is otherwise skipped when the focus traversal system is looking for the next thing to focus on.

The `canRequestFocus` attribute, unsurprisingly, controls whether or not the focus node that this `Focus` widget manages can be used to request focus. If this attribute is false, then calling `requestFocus` on the node has no effect. It also implies that this node is skipped for focus traversal, since it can't request focus.

The `descendantsAreFocusable` attribute controls whether the descendants of this node can receive focus, but still allows this node to receive focus. This attribute can be used to turn off focusability for an entire widget subtree. This is how the `ExcludeFocus` widget works: it's just a `Focus` widget with this attribute set.

### Autofocus

[#](#autofocus)

Setting the `autofocus` attribute of a `Focus` widget tells the widget to request the focus the first time the focus scope it belongs to is focused. If more than one widget has `autofocus` set, then it is arbitrary which one receives the focus, so try to only set it on one widget per focus scope.

The `autofocus` attribute only takes effect if there isn't already a focus in the scope that the node belongs to.

Setting the `autofocus` attribute on two nodes that belong to different focus scopes is well defined: each one becomes the focused widget when their corresponding scopes are focused.

### Change notifications

[#](#change-notifications)

The `Focus.onFocusChanged` callback can be used to get notifications that the focus state for a particular node has changed. It notifies if the node is added to or removed from the focus chain, which means it gets notifications even if it isn't the primary focus. If you only want to know if you have received the primary focus, check and see if `hasPrimaryFocus` is true on the focus node.

### Obtaining the FocusNode

[#](#obtaining-the-focusnode)

Sometimes, it is useful to obtain the focus node of a `Focus` widget to interrogate its attributes.

To access the focus node from an ancestor of the `Focus` widget, create and pass in a `FocusNode` as the `Focus` widget's `focusNode` attribute. Because it needs to be disposed of, the focus node you pass needs to be owned by a stateful widget, so don't just create one each time it is built.

If you need access to the focus node from the descendant of a `Focus` widget, you can call `Focus.of(context)` to obtain the focus node of the nearest `Focus `widget to the given context. If you need to obtain the `FocusNode` of a `Focus` widget within the same build function, use a [`Builder`](https://api.flutter.dev/flutter/widgets/Builder-class.html) to make sure you have the correct context. This is shown in the following example:

dart

```
@override
Widget build(BuildContext context) {
  return Focus(
    child: Builder(
      builder: (context) {
        final bool hasPrimary = Focus.of(context).hasPrimaryFocus;
        print('Building with primary focus: $hasPrimary');
        return const SizedBox(width: 100, height: 100);
      },
    ),
  );
}
```

### Timing

[#](#timing)

One of the details of the focus system is that when focus is requested, it only takes effect after the current build phase completes. This means that focus changes are always delayed by one frame, because changing focus can cause arbitrary parts of the widget tree to rebuild, including ancestors of the widget currently requesting focus. Because descendants cannot dirty their ancestors, it has to happen between frames, so that any needed changes can happen on the next frame.

## FocusScope widget

[#](#focusscope-widget)

The `FocusScope` widget is a special version of the `Focus` widget that manages a `FocusScopeNode` instead of a `FocusNode`. The `FocusScopeNode` is a special node in the focus tree that serves as a grouping mechanism for the focus nodes in a subtree. Focus traversal stays within a focus scope unless a node outside of the scope is explicitly focused.

The focus scope also keeps track of the current focus and history of the nodes focused within its subtree. That way, if a node releases focus or is removed when it had focus, the focus can be returned to the node that had focus previously.

Focus scopes also serve as a place to return focus to if none of the descendants have focus. This allows the focus traversal code to have a starting context for finding the next (or first) focusable control to move to.

If you focus a focus scope node, it first attempts to focus the current, or most recently focused node in its subtree, or the node in its subtree that requested autofocus (if any). If there is no such node, it receives the focus itself.

## FocusableActionDetector widget

[#](#focusableactiondetector-widget)

The [`FocusableActionDetector`](https://api.flutter.dev/flutter/widgets/FocusableActionDetector-class.html) is a widget that combines the functionality of [`Actions`](https://api.flutter.dev/flutter/widgets/Actions-class.html), [`Shortcuts`](https://api.flutter.dev/flutter/widgets/Shortcuts-class.html), [`MouseRegion`](https://api.flutter.dev/flutter/widgets/MouseRegion-class.html) and a `Focus` widget to create a detector that defines actions and key bindings, and provides callbacks for handling focus and hover highlights. It is what Flutter controls use to implement all of these aspects of the controls. It is just implemented using the constituent widgets, so if you don't need all of its functionality, you can just use the ones you need, but it is a convenient way to build these behaviors into your custom controls.

## Controlling focus traversal

[#](#controlling-focus-traversal)

Once an application has the ability to focus, the next thing many apps want to do is to allow the user to control the focus using the keyboard or another input device. The most common example of this is "tab traversal" where the user presses `Tab` to go to the "next" control. Controlling what "next" means is the subject of this section. This kind of traversal is provided by Flutter by default.

In a simple grid layout, it's fairly easy to decide which control is next. If you're not at the end of the row, then it's the one to the right (or left for right-to-left locales). If you are at the end of a row, it's the first control in the next row. Unfortunately, applications are rarely laid out in grids, so more guidance is often needed.

The default algorithm in Flutter ([`ReadingOrderTraversalPolicy`](https://api.flutter.dev/flutter/widgets/ReadingOrderTraversalPolicy-class.html)) for focus traversal is pretty good: It gives the right answer for most applications. However, there are always pathological cases, or cases where the context or design requires a different order than the one the default ordering algorithm arrives at. For those cases, there are other mechanisms for achieving the desired order.

### FocusTraversalGroup widget

[#](#focustraversalgroup-widget)

The [`FocusTraversalGroup`](https://api.flutter.dev/flutter/widgets/FocusTraversalGroup-class.html) widget should be placed in the tree around widget subtrees that should be fully traversed before moving on to another widget or group of widgets. Just grouping widgets into related groups is often enough to resolve many tab traversal ordering problems. If not, the group can also be given a [`FocusTraversalPolicy`](https://api.flutter.dev/flutter/widgets/FocusTraversalPolicy-class.html) to determine the ordering within the group.

The default [`ReadingOrderTraversalPolicy`](https://api.flutter.dev/flutter/widgets/ReadingOrderTraversalPolicy-class.html) is usually sufficient, but in cases where more control over ordering is needed, an [`OrderedTraversalPolicy`](https://api.flutter.dev/flutter/widgets/OrderedTraversalPolicy-class.html) can be used. The `order` argument of the [`FocusTraversalOrder`](https://api.flutter.dev/flutter/widgets/FocusTraversalOrder-class.html) widget wrapped around the focusable components determines the order. The order can be any subclass of [`FocusOrder`](https://api.flutter.dev/flutter/widgets/FocusOrder-class.html), but [`NumericFocusOrder`](https://api.flutter.dev/flutter/widgets/NumericFocusOrder-class.html) and [`LexicalFocusOrder`](https://api.flutter.dev/flutter/widgets/LexicalFocusOrder-class.html) are provided.

If none of the provided focus traversal policies are sufficient for your application, you could also write your own policy and use it to determine any custom ordering you want.

Here's an example of how to use the `FocusTraversalOrder` widget to traverse a row of buttons in the order TWO, ONE, THREE using `NumericFocusOrder`.

dart

```
class OrderedButtonRow extends StatelessWidget {
  const OrderedButtonRow({super.key});
​
  @override
  Widget build(BuildContext context) {
    return FocusTraversalGroup(
      policy: OrderedTraversalPolicy(),
      child: Row(
        children: <Widget>[
          const Spacer(),
          FocusTraversalOrder(
            order: const NumericFocusOrder(2),
            child: TextButton(child: const Text('ONE'), onPressed: () {}),
          ),
          const Spacer(),
          FocusTraversalOrder(
            order: const NumericFocusOrder(1),
            child: TextButton(child: const Text('TWO'), onPressed: () {}),
          ),
          const Spacer(),
          FocusTraversalOrder(
            order: const NumericFocusOrder(3),
            child: TextButton(child: const Text('THREE'), onPressed: () {}),
          ),
          const Spacer(),
        ],
      ),
    );
  }
}
```

### FocusTraversalPolicy

[#](#focustraversalpolicy)

The `FocusTraversalPolicy` is the object that determines which widget is next, given a request and the current focus node. The requests (member functions) are things like `findFirstFocus`, `findLastFocus`, `next`, `previous`, and `inDirection`.

`FocusTraversalPolicy` is the abstract base class for concrete policies, like `ReadingOrderTraversalPolicy`, `OrderedTraversalPolicy` and the [`DirectionalFocusTraversalPolicyMixin`](https://api.flutter.dev/flutter/widgets/DirectionalFocusTraversalPolicyMixin-mixin.html) classes.

In order to use a `FocusTraversalPolicy`, you give one to a `FocusTraversalGroup`, which determines the widget subtree in which the policy will be effective. The member functions of the class are rarely called directly: they are meant to be used by the focus system.

## The focus manager

[#](#the-focus-manager)

The [`FocusManager`](https://api.flutter.dev/flutter/widgets/FocusManager-class.html) maintains the current primary focus for the system. It only has a few pieces of API that are useful to users of the focus system. One is the `FocusManager.instance.primaryFocus` property, which contains the currently focused focus node and is also accessible from the global `primaryFocus` field.

Other useful properties are `FocusManager.instance.highlightMode` and `FocusManager.instance.highlightStrategy`. These are used by widgets that need to switch between a "touch" mode and a "traditional" (mouse and keyboard) mode for their focus highlights. When a user is using touch to navigate, the focus highlight is usually hidden, and when they switch to a mouse or keyboard, the focus highlight needs to be shown again so they know what is focused. The `highlightStrategy` tells the focus manager how to interpret changes in the usage mode of the device: it can either automatically switch between the two based on the most recent input events, or it can be locked in touch or traditional modes. The provided widgets in Flutter already know how to use this information, so you only need it if you're writing your own controls from scratch. You can use `addHighlightModeListener` callback to listen for changes in the highlight mode.

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-09-29. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/ui/interactivity/focus.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/ui/interactivity/focus\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/ui/interactivity/focus.md "Report an issue with this page").

----
url: https://docs.flutter.dev/cookbook/effects/download-button.md
----

\# Create a download button > How to implement a download button.

<!--?code-excerpt path-base="cookbook/effects/download_button"?-->

Apps are filled with buttons that execute long-running behaviors. For example, a button might trigger a download, which starts a download process, receives data over time, and then provides access to the downloaded asset. It's helpful to show the user the progress of a long-running process, and the button itself is a good place to provide this feedback. In this recipe, you'll build a download button that transitions through multiple visual states, based on the status of an app download. The following animation shows the app's behavior: !\[The download button cycles through its stages]\(/assets/images/docs/cookbook/effects/DownloadButton.webp){:.site-mobile-screenshot} ## Define a new stateless widget Your button widget needs to change its appearance over time. Therefore, you need to implement your button with a custom stateless widget. Define a new stateless widget called \`DownloadButton\`.

<!--?code-excerpt "lib/stateful_widget.dart (DownloadButton)"?-->

\`\`\`dart @immutable class DownloadButton extends StatelessWidget { const DownloadButton({super.key}); @override Widget build(BuildContext context) { // TODO: return const SizedBox(); } } \`\`\` ## Define the button's possible visual states The download button's visual presentation is based on a given download status. Define the possible states of the download, and then update \`DownloadButton\` to accept a \`DownloadStatus\` and a \`Duration\` for how long the button should take to animate from one status to another.

<!--?code-excerpt "lib/visual_states.dart (VisualStates)"?-->

\`\`\`dart enum DownloadStatus { notDownloaded, fetchingDownload, downloading, downloaded } @immutable class DownloadButton extends StatelessWidget { const DownloadButton({ super.key, required this.status, this.transitionDuration = const Duration(milliseconds: 500), }); final DownloadStatus status; final Duration transitionDuration; @override Widget build(BuildContext context) { // TODO: We'll add more to this later. return const SizedBox(); } } \`\`\` :::note Each time you define a custom widget, you must decide whether all relevant information is provided to that widget from its parent or if that widget orchestrates the application behavior within itself. For example, \`DownloadButton\` could receive the current \`DownloadStatus\` from its parent, or the \`DownloadButton\` could orchestrate the download process itself within its \`State\` object. For most widgets, the best answer is to pass the relevant information into the widget from its parent, rather than manage behavior within the widget. By passing in all the relevant information, you ensure greater reusability for the widget, easier testing, and easier changes to application behavior in the future. ::: ## Display the button shape The download button changes its shape based on the download status. The button displays a grey, rounded rectangle during the \`notDownloaded\` and \`downloaded\` states. The button displays a transparent circle during the \`fetchingDownload\` and \`downloading\` states. Based on the current \`DownloadStatus\`, build an \`AnimatedContainer\` with a \`ShapeDecoration\` that displays a rounded rectangle or a circle. Consider defining the shape's widget tree in a separated \`Stateless\` widget so that the main \`build()\` method remains simple, allowing for the additions that follow. Instead of creating a function to return a widget, like \`Widget \_buildSomething() {}\`, always prefer creating a \`StatelessWidget\` or a \`StatefulWidget\` which is more performant. More considerations on this can be found in the \[documentation]\(https\://api.flutter.dev/flutter/widgets/StatelessWidget-class.html) or in a dedicated video in the Flutter \[YouTube channel]\(https\://www\.youtube.com/watch?v=IOyq-eTRhvo). For now, the \`AnimatedContainer\` child is just a \`SizedBox\` because we will come back at it in another step.

<!--?code-excerpt "lib/display.dart (Display)"?-->

\`\`\`dart @immutable class DownloadButton extends StatelessWidget { const DownloadButton({ super.key, required this.status, this.transitionDuration = const Duration(milliseconds: 500), }); final DownloadStatus status; final Duration transitionDuration; bool get \_isDownloading => status == DownloadStatus.downloading; bool get \_isFetching => status == DownloadStatus.fetchingDownload; bool get \_isDownloaded => status == DownloadStatus.downloaded; @override Widget build(BuildContext context) { return ButtonShapeWidget( transitionDuration: transitionDuration, isDownloaded: \_isDownloaded, isDownloading: \_isDownloading, isFetching: \_isFetching, ); } } @immutable class ButtonShapeWidget extends StatelessWidget { const ButtonShapeWidget({ super.key, required this.isDownloading, required this.isDownloaded, required this.isFetching, required this.transitionDuration, }); final bool isDownloading; final bool isDownloaded; final bool isFetching; final Duration transitionDuration; @override Widget build(BuildContext context) { final ShapeDecoration shape; if (isDownloading || isFetching) { shape = const ShapeDecoration( shape: CircleBorder(), color: Colors.transparent, ); } else { shape = const ShapeDecoration( shape: StadiumBorder(), color: CupertinoColors.lightBackgroundGray, ); } return AnimatedContainer( duration: transitionDuration, curve: Curves.ease, width: double.infinity, decoration: shape, child: const SizedBox(), ); } } \`\`\` You might wonder why you need a \`ShapeDecoration\` widget for a transparent circle, given that it's invisible. The purpose of the invisible circle is to orchestrate the desired animation. The \`AnimatedContainer\` begins with a rounded rectangle. When the \`DownloadStatus\` changes to \`fetchingDownload\`, the \`AnimatedContainer\` needs to animate from a rounded rectangle to a circle, and then fade out as the animation takes place. The only way to implement this animation is to define both the beginning shape of a rounded rectangle and the ending shape of a circle. But, you don't want the final circle to be visible, so you make it transparent, which causes an animated fade-out. ## Display the button text The \`DownloadButton\` displays \`GET\` during the \`notDownloaded\` phase, \`OPEN\` during the \`downloaded\` phase, and no text in between. Add widgets to display text during each download phase, and animate the text's opacity in between. Add the text widget tree as a child of the \`AnimatedContainer\` in the button wrapper widget.

<!--?code-excerpt "lib/display_text.dart (DisplayText)"?-->

\`\`\`dart @immutable class ButtonShapeWidget extends StatelessWidget { const ButtonShapeWidget({ super.key, required this.isDownloading, required this.isDownloaded, required this.isFetching, required this.transitionDuration, }); final bool isDownloading; final bool isDownloaded; final bool isFetching; final Duration transitionDuration; @override Widget build(BuildContext context) { final ShapeDecoration shape; if (isDownloading || isFetching) { shape = const ShapeDecoration( shape: CircleBorder(), color: Colors.transparent, ); } else { shape = const ShapeDecoration( shape: StadiumBorder(), color: CupertinoColors.lightBackgroundGray, ); } return AnimatedContainer( duration: transitionDuration, curve: Curves.ease, width: double.infinity, decoration: shape, child: Padding( padding: const EdgeInsets.symmetric(vertical: 6), child: AnimatedOpacity( duration: transitionDuration, opacity: isDownloading || isFetching ? 0.0 : 1.0, curve: Curves.ease, child: Text( isDownloaded ? 'OPEN' : 'GET', textAlign: TextAlign.center, style: Theme.of(context).textTheme.labelLarge?.copyWith( fontWeight: FontWeight.bold, color: CupertinoColors.activeBlue, ), ), ), ), ); } } \`\`\` ## Display a spinner while fetching download During the \`fetchingDownload\` phase, the \`DownloadButton\` displays a radial spinner. This spinner fades in from the \`notDownloaded\` phase and fades out to the \`fetchingDownload\` phase. Implement a radial spinner that sits on top of the button shape and fades in and out at the appropriate times. We have removed the \`ButtonShapeWidget\`'s constructor to keep the focus on its build method and the \`Stack\` widget we've added.

<!--?code-excerpt "lib/spinner.dart (Spinner)"?-->

\`\`\`dart @override Widget build(BuildContext context) { return GestureDetector( onTap: \_onPressed, child: Stack( children: \[ ButtonShapeWidget( transitionDuration: transitionDuration, isDownloaded: \_isDownloaded, isDownloading: \_isDownloading, isFetching: \_isFetching, ), Positioned.fill( child: AnimatedOpacity( duration: transitionDuration, opacity: \_isDownloading || \_isFetching ? 1.0 : 0.0, curve: Curves.ease, child: ProgressIndicatorWidget( downloadProgress: downloadProgress, isDownloading: \_isDownloading, isFetching: \_isFetching, ), ), ), ], ), ); } \`\`\` ## Display the progress and a stop button while downloading After the \`fetchingDownload\` phase is the \`downloading\` phase. During the \`downloading\` phase, the \`DownloadButton\` replaces the radial progress spinner with a growing radial progress bar. The \`DownloadButton\` also displays a stop button icon so that the user can cancel an in-progress download. Add a progress property to the \`DownloadButton\` widget, and then update the progress display to switch to a radial progress bar during the \`downloading\` phase. Next, add a stop button icon at the center of the radial progress bar.

<!--?code-excerpt "lib/stop.dart (StopIcon)"?-->

\`\`\`dart @override Widget build(BuildContext context) { return GestureDetector( onTap: \_onPressed, child: Stack( children: \[ ButtonShapeWidget( transitionDuration: transitionDuration, isDownloaded: \_isDownloaded, isDownloading: \_isDownloading, isFetching: \_isFetching, ), Positioned.fill( child: AnimatedOpacity( duration: transitionDuration, opacity: \_isDownloading || \_isFetching ? 1.0 : 0.0, curve: Curves.ease, child: Stack( alignment: Alignment.center, children: \[ ProgressIndicatorWidget( downloadProgress: downloadProgress, isDownloading: \_isDownloading, isFetching: \_isFetching, ), if (\_isDownloading) const Icon( Icons.stop, size: 14.0, color: CupertinoColors.activeBlue, ), ], ), ), ), ], ), ); } \`\`\` ## Add button tap callbacks The last detail that your \`DownloadButton\` needs is the button behavior. The button must do things when the user taps it. Add widget properties for callbacks to start a download, cancel a download, and open a download. Finally, wrap \`DownloadButton\`'s existing widget tree with a \`GestureDetector\` widget, and forward the tap event to the corresponding callback property.

<!--?code-excerpt "lib/button_taps.dart (TapCallbacks)"?-->

\`\`\`dart @immutable class DownloadButton extends StatelessWidget { const DownloadButton({ super.key, required this.status, this.downloadProgress = 0, required this.onDownload, required this.onCancel, required this.onOpen, this.transitionDuration = const Duration(milliseconds: 500), }); final DownloadStatus status; final double downloadProgress; final VoidCallback onDownload; final VoidCallback onCancel; final VoidCallback onOpen; final Duration transitionDuration; bool get \_isDownloading => status == DownloadStatus.downloading; bool get \_isFetching => status == DownloadStatus.fetchingDownload; bool get \_isDownloaded => status == DownloadStatus.downloaded; void \_onPressed() { switch (status) { case DownloadStatus.notDownloaded: onDownload(); case DownloadStatus.fetchingDownload: // do nothing. break; case DownloadStatus.downloading: onCancel(); case DownloadStatus.downloaded: onOpen(); } } @override Widget build(BuildContext context) { return GestureDetector( onTap: \_onPressed, child: const Stack( children: \[ /\* ButtonShapeWidget and progress indicator \*/ ], ), ); } } \`\`\` Congratulations! You have a button that changes its display depending on which phase the button is in: not downloaded, fetching download, downloading, and downloaded. Now, the user can tap to start a download, tap to cancel an in-progress download, and tap to open a completed download. ## Interactive example Run the app: \* Click the \*\*GET\*\* button to kick off a simulated download. \* The button changes to a progress indicator to simulate an in-progress download. \* When the simulated download is complete, the button transitions to \*\*OPEN\*\*, to indicate that the app is ready for the user to open the downloaded asset.

<!--?code-excerpt "lib/main.dart"?-->

\`\`\`dartpad title="Flutter download button hands-on example in DartPad" run="true" import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; void main() { runApp( const MaterialApp( home: ExampleCupertinoDownloadButton(), debugShowCheckedModeBanner: false, ), ); } @immutable class ExampleCupertinoDownloadButton extends StatefulWidget { const ExampleCupertinoDownloadButton({super.key}); @override State createState() => \_ExampleCupertinoDownloadButtonState(); } class \_ExampleCupertinoDownloadButtonState extends State { late final List \_downloadControllers; @override void initState() { super.initState(); \_downloadControllers = List.generate( 20, (index) => SimulatedDownloadController( onOpenDownload: () { \_openDownload(index); }, ), ); } void \_openDownload(int index) { ScaffoldMessenger.of(context) .showSnackBar(SnackBar(content: Text('Open App ${index + 1}'))); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('Apps')), body: ListView\.separated( itemCount: \_downloadControllers.length, separatorBuilder: (\_, \_) => const Divider(), itemBuilder: \_buildListItem, ), ); } Widget \_buildListItem(BuildContext context, int index) { final theme = Theme.of(context); final downloadController = \_downloadControllers\[index]; return ListTile( leading: const DemoAppIcon(), title: Text( 'App ${index + 1}', overflow: TextOverflow\.ellipsis, style: theme.textTheme.titleLarge, ), subtitle: Text( 'Lorem ipsum dolor #${index + 1}', overflow: TextOverflow\.ellipsis, style: theme.textTheme.bodySmall, ), trailing: SizedBox( width: 96, child: AnimatedBuilder( animation: downloadController, builder: (context, child) { return DownloadButton( status: downloadController.downloadStatus, downloadProgress: downloadController.progress, onDownload: downloadController.startDownload, onCancel: downloadController.stopDownload, onOpen: downloadController.openDownload, ); }, ), ), ); } } @immutable class DemoAppIcon extends StatelessWidget { const DemoAppIcon({super.key}); @override Widget build(BuildContext context) { return const AspectRatio( aspectRatio: 1, child: FittedBox( child: SizedBox( width: 80, height: 80, child: DecoratedBox( decoration: BoxDecoration( gradient: LinearGradient(colors: \[Colors.red, Colors.blue]), borderRadius: BorderRadius.all(Radius.circular(20)), ), child: Center( child: Icon(Icons.ac\_unit, color: Colors.white, size: 40), ), ), ), ), ); } } enum DownloadStatus { notDownloaded, fetchingDownload, downloading, downloaded } abstract class DownloadController implements ChangeNotifier { DownloadStatus get downloadStatus; double get progress; void startDownload(); void stopDownload(); void openDownload(); } class SimulatedDownloadController extends DownloadController with ChangeNotifier { SimulatedDownloadController({ this.\_downloadStatus = DownloadStatus.notDownloaded, this.\_progress = 0.0, required this.\_onOpenDownload, }); DownloadStatus \_downloadStatus; @override DownloadStatus get downloadStatus => \_downloadStatus; double \_progress; @override double get progress => \_progress; final VoidCallback \_onOpenDownload; bool \_isDownloading = false; @override void startDownload() { if (downloadStatus == DownloadStatus.notDownloaded) { \_doSimulatedDownload(); } } @override void stopDownload() { if (\_isDownloading) { \_isDownloading = false; \_downloadStatus = DownloadStatus.notDownloaded; \_progress = 0.0; notifyListeners(); } } @override void openDownload() { if (downloadStatus == DownloadStatus.downloaded) { \_onOpenDownload(); } } Future \_doSimulatedDownload() async { \_isDownloading = true; \_downloadStatus = DownloadStatus.fetchingDownload; notifyListeners(); // Wait a second to simulate fetch time. await Future.delayed(const Duration(seconds: 1)); // If the user chose to cancel the download, stop the simulation. if (!\_isDownloading) { return; } // Shift to the downloading phase. \_downloadStatus = DownloadStatus.downloading; notifyListeners(); const downloadProgressStops = \[0.0, 0.15, 0.45, 0.8, 1.0]; for (final stop in downloadProgressStops) { // Wait a second to simulate varying download speeds. await Future.delayed(const Duration(seconds: 1)); // If the user chose to cancel the download, stop the simulation. if (!\_isDownloading) { return; } // Update the download progress. \_progress = stop; notifyListeners(); } // Wait a second to simulate a final delay. await Future.delayed(const Duration(seconds: 1)); // If the user chose to cancel the download, stop the simulation. if (!\_isDownloading) { return; } // Shift to the downloaded state, completing the simulation. \_downloadStatus = DownloadStatus.downloaded; \_isDownloading = false; notifyListeners(); } } @immutable class DownloadButton extends StatelessWidget { const DownloadButton({ super.key, required this.status, this.downloadProgress = 0.0, required this.onDownload, required this.onCancel, required this.onOpen, this.transitionDuration = const Duration(milliseconds: 500), }); final DownloadStatus status; final double downloadProgress; final VoidCallback onDownload; final VoidCallback onCancel; final VoidCallback onOpen; final Duration transitionDuration; bool get \_isDownloading => status == DownloadStatus.downloading; bool get \_isFetching => status == DownloadStatus.fetchingDownload; bool get \_isDownloaded => status == DownloadStatus.downloaded; void \_onPressed() { switch (status) { case DownloadStatus.notDownloaded: onDownload(); case DownloadStatus.fetchingDownload: // do nothing. break; case DownloadStatus.downloading: onCancel(); case DownloadStatus.downloaded: onOpen(); } } @override Widget build(BuildContext context) { return GestureDetector( onTap: \_onPressed, child: Stack( children: \[ ButtonShapeWidget( transitionDuration: transitionDuration, isDownloaded: \_isDownloaded, isDownloading: \_isDownloading, isFetching: \_isFetching, ), Positioned.fill( child: AnimatedOpacity( duration: transitionDuration, opacity: \_isDownloading || \_isFetching ? 1.0 : 0.0, curve: Curves.ease, child: Stack( alignment: Alignment.center, children: \[ ProgressIndicatorWidget( downloadProgress: downloadProgress, isDownloading: \_isDownloading, isFetching: \_isFetching, ), if (\_isDownloading) const Icon( Icons.stop, size: 14, color: CupertinoColors.activeBlue, ), ], ), ), ), ], ), ); } } @immutable class ButtonShapeWidget extends StatelessWidget { const ButtonShapeWidget({ super.key, required this.isDownloading, required this.isDownloaded, required this.isFetching, required this.transitionDuration, }); final bool isDownloading; final bool isDownloaded; final bool isFetching; final Duration transitionDuration; @override Widget build(BuildContext context) { final ShapeDecoration shape; if (isDownloading || isFetching) { shape = const ShapeDecoration( shape: CircleBorder(), color: Colors.transparent, ); } else { shape = const ShapeDecoration( shape: StadiumBorder(), color: CupertinoColors.lightBackgroundGray, ); } return AnimatedContainer( duration: transitionDuration, curve: Curves.ease, width: double.infinity, decoration: shape, child: Padding( padding: const EdgeInsets.symmetric(vertical: 6), child: AnimatedOpacity( duration: transitionDuration, opacity: isDownloading || isFetching ? 0.0 : 1.0, curve: Curves.ease, child: Text( isDownloaded ? 'OPEN' : 'GET', textAlign: TextAlign.center, style: Theme.of(context).textTheme.labelLarge?.copyWith( fontWeight: FontWeight.bold, color: CupertinoColors.activeBlue, ), ), ), ), ); } } @immutable class ProgressIndicatorWidget extends StatelessWidget { const ProgressIndicatorWidget({ super.key, required this.downloadProgress, required this.isDownloading, required this.isFetching, }); final double downloadProgress; final bool isDownloading; final bool isFetching; @override Widget build(BuildContext context) { return AspectRatio( aspectRatio: 1, child: TweenAnimationBuilder( tween: Tween(begin: 0, end: downloadProgress), duration: const Duration(milliseconds: 200), builder: (context, progress, child) { return CircularProgressIndicator( backgroundColor: isDownloading ? CupertinoColors.lightBackgroundGray : Colors.transparent, valueColor: AlwaysStoppedAnimation( isFetching ? CupertinoColors.lightBackgroundGray : CupertinoColors.activeBlue, ), strokeWidth: 2, value: isFetching ? null : progress, ); }, ), ); } } \`\`\`

----
url: https://docs.flutter.dev/cookbook/navigation/set-up-app-links
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Set up app links for Android](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

Deep linking is a mechanism for launching an app with a URI. This URI contains scheme, host, and path, and opens the app to a specific screen.

An *app link* is a type of deep link that uses `http` or `https` and is exclusive to Android devices.

Setting up app links requires one to own a web domain. Otherwise, consider using [Firebase Hosting](https://firebase.google.com/docs/hosting) or [GitHub Pages](https://pages.github.com) as a temporary solution.

Once you've set up your deep links, you can validate them. To learn more, see [Validate deep links](/tools/devtools/deep-links).

## 1. Customize a Flutter application

[#](#1-customize-a-flutter-application)

Write a Flutter app that can handle an incoming URL. This example uses the [go\_router](https://pub.dev/packages/go_router) package to handle the routing. The Flutter team maintains the `go_router` package. It provides a simple API to handle complex routing scenarios.

1. To create a new application, type `flutter create <app-name>`:

   ```
   $ flutter create deeplink_cookbook
   ```

2. To include `go_router` package in your app, add a dependency for `go_router` to the project:

   To add the `go_router` package as a dependency, run `flutter pub add`:

   ```
   $ flutter pub add go_router
   ```

3. To handle the routing, create a `GoRouter` object in the `main.dart` file:

   main.dart

   dart

   ```
   import 'package:material_ui/material_ui.dart';
   import 'package:go_router/go_router.dart';
   ​
   void main() => runApp(MaterialApp.router(routerConfig: router));
   ​
   /// This handles '/' and '/details'.
   final router = GoRouter(
     routes: [
       GoRoute(
         path: '/',
         builder: (_, _) => Scaffold(
           appBar: AppBar(title: const Text('Home Screen')),
         ),
         routes: [
           GoRoute(
             path: 'details',
             builder: (_, _) => Scaffold(
               appBar: AppBar(title: const Text('Details Screen')),
             ),
           ),
         ],
       ),
     ],
   );
   ```

## 2. Modify AndroidManifest.xml

[#](#2-modify-androidmanifest-xml)

1. Open the Flutter project with VS Code or Android Studio.

2. Navigate to `android/app/src/main/AndroidManifest.xml` file.

3. Add the following metadata tag and intent filter inside the `<activity>` tag with `.MainActivity`.

   Replace `example.com` with your own web domain.

   xml

   ```
   <intent-filter android:autoVerify="true">
       <action android:name="android.intent.action.VIEW" />
       <category android:name="android.intent.category.DEFAULT" />
       <category android:name="android.intent.category.BROWSABLE" />
       <data android:scheme="http" android:host="example.com" />
       <data android:scheme="https" />
   </intent-filter>
   ```

## 3. Hosting assetlinks.json file

[#](#3-hosting-assetlinks-json-file)

Host an `assetlinks.json` file in using a web server with a domain that you own. This file tells the mobile browser which Android application to open instead of the browser. To create the file, get the package name of the Flutter app you created in the previous step and the sha256 fingerprint of the signing key you will be using to build the APK.

### Package name

[#](#package-name)

Locate the package name in `AndroidManifest.xml`, the `package` property under `<manifest>` tag. Package names are usually in the format of `com.example.*`.

### sha256 fingerprint

[#](#sha256-fingerprint)

The process might differ depending on how the apk is signed.

#### Using google play app signing

[#](#using-google-play-app-signing)

You can find the sha256 fingerprint directly from play developer console. Open your app in the play console, under **Release> Setup > App Integrity> App Signing tab**:

#### Using local keystore

[#](#using-local-keystore)

If you are storing the key locally, you can generate sha256 using the following command:

```
keytool -list -v -keystore <path-to-keystore>
```

### assetlinks.json

[#](#assetlinks-json)

The hosted file should look similar to this:

json

```
[{
  "relation": ["delegate_permission/common.handle_all_urls"],
  "target": {
    "namespace": "android_app",
    "package_name": "com.example.deeplink_cookbook",
    "sha256_cert_fingerprints":
    ["FF:2A:CF:7B:DD:CC:F1:03:3E:E8:B2:27:7C:A2:E3:3C:DE:13:DB:AC:8E:EB:3A:B9:72:A1:0E:26:8A:F5:EC:AF"]
  }
}]
```

1. Set the `package_name` value to your Android application ID.

2. Set sha256\_cert\_fingerprints to the value you got from the previous step.

3. Host the file at a URL that resembles the following: `<webdomain>/.well-known/assetlinks.json`

4. Verify that your browser can access this file.

## Testing

[#](#testing)

You can use a real device or the Emulator to test an app link, but first make sure you have executed `flutter run` at least once on the devices. This ensures that the Flutter application is installed.

To test **only** the app setup, use the adb command:

```
adb shell 'am start -a android.intent.action.VIEW \
    -c android.intent.category.BROWSABLE \
    -d "http://<web-domain>/details"' \
    <package name>
```

To test **both** web and app setup, you must click a link directly through web browser or another app. One way is to create a Google Doc, add the link, and tap on it.

If everything is set up correctly, the Flutter application launches and displays the details screen:

## Appendix

[#](#appendix)

Source code: [deeplink\_cookbook](https://github.com/flutter/codelabs/tree/main/deeplink_cookbook)

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-09-29. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/navigation/set-up-app-links.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/cookbook/navigation/set-up-app-links\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/navigation/set-up-app-links.md "Report an issue with this page").

----
url: https://docs.flutter.dev/ui/layout/tutorial
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Build a Flutter layout](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

This tutorial explains how to design and build layouts in Flutter.

If you use the example code provided, you can build the following app.

The finished app.

Photo by [Dino Reichmuth](https://unsplash.com/photos/red-and-gray-tents-in-grass-covered-mountain-5Rhl-kSRydQ) on [Unsplash](https://unsplash.com). Text by [Switzerland Tourism](https://www.myswitzerland.com/en-us/destinations/lake-oeschinen).

To get a better overview of the layout mechanism, start with [Flutter's approach to layout](/ui/layout).

## Diagram the layout

[#](#diagram-the-layout)

In this section, consider what type of user experience you want for your app users.

Consider how to position the components of your user interface. A layout consists of the total end result of these positionings. Consider planning your layout to speed up your coding. Using visual cues to know where something goes on screen can be a great help.

Use whichever method you prefer, like an interface design tool or a pencil and a sheet of paper. Figure out where you want to place elements on your screen before writing code. It's the programming version of the adage: "Measure twice, cut once."

1. Ask these questions to break the layout down to its basic elements.

   * Can you identify the rows and columns?
   * Does the layout include a grid?
   * Are there overlapping elements?
   * Does the UI need tabs?
   * What do you need to align, pad, or border?

2. Identify the larger elements. In this example, you arrange the image, title, buttons, and description into a column.

   Major elements in the layout: image, row, row, and text block

3. Diagram each row.

   1. Row 1, the **Title** section, has three children: a column of text, a star icon, and a number. Its first child, the column, contains two lines of text. That first column might need more space.

      Title section with text blocks and an icon

   2. Row 2, the **Button** section, has three children: each child contains a column which then contains an icon and text.

      The Button section with three labeled buttons

After diagramming the layout, consider how you would code it.

Would you write all the code in one class? Or, would you create one class for each part of the layout?

To follow Flutter best practices, create one class, or Widget, to contain each part of your layout. When Flutter needs to re-render part of a UI, it updates the smallest part that changes. This is why Flutter makes "everything a widget". If only the text changes in a `Text` widget, Flutter redraws only that text. Flutter changes the least amount of the UI possible in response to user input.

For this tutorial, write each element you have identified as its own widget.

## Create the app base code

[#](#create-the-app-base-code)

In this section, shell out the basic Flutter app code to start your app.

1. [Set up your Flutter environment](/install).

2. [Create a new Flutter app](/reference/create-new-app).

3. Replace the contents of `lib/main.dart` with the following code. This app uses a parameter for the app title and the title shown on the app's `appBar`. This decision simplifies the code.

   dart

   ```
   import 'package:material_ui/material_ui.dart';
   ​
   void main() => runApp(const MyApp());
   ​
   class MyApp extends StatelessWidget {
     const MyApp({super.key});
   ​
     @override
     Widget build(BuildContext context) {
       const String appTitle = 'Flutter layout demo';
       return MaterialApp(
         title: appTitle,
         home: Scaffold(
           appBar: AppBar(title: const Text(appTitle)),
           body: const Center(
             child: Text('Hello World'),
           ),
         ),
       );
     }
   }
   ```

## Add the Title section

[#](#add-the-title-section)

In this section, create a `TitleSection` widget that resembles the following layout.

The Title section as sketch and prototype UI

### Add the `TitleSection` Widget

[#](#add-the-titlesection-widget)

Add the following code after the `MyApp` class.

dart

```
class TitleSection extends StatelessWidget {
  const TitleSection({super.key, required this.name, required this.location});
​
  final String name;
  final String location;
​
  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(32),
      child: Row(
        children: [
          Expanded(
            /*1*/
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                /*2*/
                Padding(
                  padding: const EdgeInsets.only(bottom: 8),
                  child: Text(
                    name,
                    style: const TextStyle(fontWeight: FontWeight.bold),
                  ),
                ),
                Text(location, style: TextStyle(color: Colors.grey[500])),
              ],
            ),
          ),
          /*3*/
          Icon(Icons.star, color: Colors.red[500]),
          const Text('41'),
        ],
      ),
    );
  }
}
```

1. To use all remaining free space in the row, use the `Expanded` widget to stretch the `Column` widget. To place the column at the start of the row, set the `crossAxisAlignment` property to `CrossAxisAlignment.start`.
2. To add space between the rows of text, put those rows in a `Padding` widget.
3. The title row ends with a red star icon and the text `41`. The entire row falls inside a `Padding` widget and pads each edge by 32 pixels.

### Change the app body to a scrolling view

[#](#change-the-app-body-to-a-scrolling-view)

In the `body` property, replace the `Center` widget with a `SingleChildScrollView` widget. Within the [`SingleChildScrollView`](https://api.flutter.dev/flutter/widgets/SingleChildScrollView-class.html) widget, replace the `Text` widget with a `Column` widget.

dart

```
body: const Center(
  child: Text('Hello World'),
body: const SingleChildScrollView(
  child: Column(
    children: [
```

These code updates change the app in the following ways.

* A `SingleChildScrollView` widget can scroll. This allows elements that don't fit on the current screen to display.
* A `Column` widget displays any elements within its `children` property in the order listed. The first element listed in the `children` list displays at the top of the list. Elements in the `children` list display in array order on the screen from top to bottom.

### Update the app to display the title section

[#](#update-the-app-to-display-the-title-section)

Add the `TitleSection` widget as the first element in the `children` list. This places it at the top of the screen. Pass the provided name and location to the `TitleSection` constructor.

dart

```
children: [
  TitleSection(
    name: 'Oeschinen Lake Campground',
    location: 'Kandersteg, Switzerland',
  ),
],
```

## Add the Button section

[#](#add-the-button-section)

In this section, add the buttons that will add functionality to your app.

The **Button** section contains three columns that use the same layout: an icon over a row of text.

The Button section as sketch and prototype UI

Plan to distribute these columns in one row so each takes the same amount of space. Paint all text and icons with the primary color.

### Add the `ButtonSection` widget

[#](#add-the-buttonsection-widget)

Add the following code after the `TitleSection` widget to contain the code to build the row of buttons.

dart

```
class ButtonSection extends StatelessWidget {
  const ButtonSection({super.key});
​
  @override
  Widget build(BuildContext context) {
    final Color color = Theme.of(context).primaryColor;
    // ···
  }
​
}
```

### Create a widget to make buttons

[#](#create-a-widget-to-make-buttons)

As the code for each column could use the same syntax, create a widget named `ButtonWithText`. The widget's constructor accepts a color, icon data, and a label for the button. Using these values, the widget builds a `Column` with an `Icon` and a stylized `Text` widget as its children. To help separate these children, a `Padding` widget the `Text` widget is wrapped with a `Padding` widget.

Add the following code after the `ButtonSection` class.

dart

```
class ButtonSection extends StatelessWidget {
  const ButtonSection({super.key});
  // ···
}
​
class ButtonWithText extends StatelessWidget {
  const ButtonWithText({
    super.key,
    required this.color,
    required this.icon,
    required this.label,
  });
​
  final Color color;
  final IconData icon;
  final String label;
​
  @override
  Widget build(BuildContext context) {
    return Column(
      mainAxisSize: MainAxisSize.min,
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        Icon(icon, color: color),
        Padding(
          padding: const EdgeInsets.only(top: 8),
          child: Text(
            label,
            style: TextStyle(
              fontSize: 12,
              fontWeight: FontWeight.w400,
              color: color,
            ),
          ),
        ),
      ],
    );
  }
}
```

### Position the buttons with a `Row` widget

[#](#position-the-buttons-with-a-row-widget)

Add the following code into the `ButtonSection` widget.

1. Add three instances of the `ButtonWithText` widget, once for each button.
2. Pass the color, `Icon`, and text for that specific button.
3. Align the columns along the main axis with the `MainAxisAlignment.spaceEvenly` value. The main axis for a `Row` widget is horizontal and the main axis for a `Column` widget is vertical. This value, then, tells Flutter to arrange the free space in equal amounts before, between, and after each column along the `Row`.

dart

```
class ButtonSection extends StatelessWidget {
  const ButtonSection({super.key});
​
  @override
  Widget build(BuildContext context) {
    final Color color = Theme.of(context).primaryColor;
    return SizedBox(
      child: Row(
        mainAxisAlignment: MainAxisAlignment.spaceEvenly,
        children: [
          ButtonWithText(color: color, icon: Icons.call, label: 'CALL'),
          ButtonWithText(color: color, icon: Icons.near_me, label: 'ROUTE'),
          ButtonWithText(color: color, icon: Icons.share, label: 'SHARE'),
        ],
      ),
    );
  }
​
}
​
class ButtonWithText extends StatelessWidget {
  const ButtonWithText({
    super.key,
    required this.color,
    required this.icon,
    required this.label,
  });
​
  final Color color;
  final IconData icon;
  final String label;
​
  @override
  Widget build(BuildContext context) {
    return Column(
      // ···
    );
  }
}
```

### Update the app to display the button section

[#](#update-the-app-to-display-the-button-section)

Add the button section to the `children` list.

dart

```
  TitleSection(
    name: 'Oeschinen Lake Campground',
    location: 'Kandersteg, Switzerland',
  ),
  ButtonSection(),
],
```

## Add the Text section

[#](#add-the-text-section)

In this section, add the text description to this app.

The text block as sketch and prototype UI

### Add the `TextSection` widget

[#](#add-the-textsection-widget)

Add the following code as a separate widget after the `ButtonSection` widget.

dart

```
class TextSection extends StatelessWidget {
  const TextSection({super.key, required this.description});
​
  final String description;
​
  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(32),
      child: Text(description, softWrap: true),
    );
  }
}
```

By setting [`softWrap`](https://api.flutter.dev/flutter/widgets/Text/softWrap.html) to `true`, text lines fill the column width before wrapping at a word boundary.

### Update the app to display the text section

[#](#update-the-app-to-display-the-text-section)

Add a new `TextSection` widget as a child after the `ButtonSection`. When adding the `TextSection` widget, set its `description` property to the text of the location description.

dart

```
    location: 'Kandersteg, Switzerland',
  ),
  ButtonSection(),
  TextSection(
    description:
        'Lake Oeschinen lies at the foot of the Blüemlisalp in the '
        'Bernese Alps. Situated 1,578 meters above sea level, it '
        'is one of the larger Alpine Lakes. A gondola ride from '
        'Kandersteg, followed by a half-hour walk through pastures '
        'and pine forest, leads you to the lake, which warms to 20 '
        'degrees Celsius in the summer. Activities enjoyed here '
        'include rowing, and riding the summer toboggan run.',
  ),
],
```

## Add the Image section

[#](#add-the-image-section)

In this section, add the image file to complete your layout.

### Configure your app to use supplied images

[#](#configure-your-app-to-use-supplied-images)

To configure your app to reference images, modify its `pubspec.yaml` file.

1. Create an `images` directory at the top of the project.

2. Download the [`lake.jpg`](https://raw.githubusercontent.com/flutter/website/main/examples/layout/lakes/step5/images/lake.jpg) image and add it to the new `images` directory.

3. To include images, add an `assets` tag to the `pubspec.yaml` file at the root directory of your app. When you add `assets`, it serves as the set of pointers to the images available to your code.

   pubspec.yaml

   yaml

   ```
   flutter:
     uses-material-design: true
     assets:
       - images/lake.jpg
   ```

### Create the `ImageSection` widget

[#](#create-the-imagesection-widget)

Define the following `ImageSection` widget after the other declarations.

dart

```
class ImageSection extends StatelessWidget {
  const ImageSection({super.key, required this.image});
​
  final String image;
​
  @override
  Widget build(BuildContext context) {
    return Image.asset(image, width: 600, height: 240, fit: BoxFit.cover);
  }
}
```

The `BoxFit.cover` value tells Flutter to display the image with two constraints. First, display the image as small as possible. Second, cover all the space that the layout allotted, called the render box.

### Update the app to display the image section

[#](#update-the-app-to-display-the-image-section)

Add an `ImageSection` widget as the first child in the `children` list. Set the `image` property to the path of the image you added in [Configure your app to use supplied images](#configure-your-app-to-use-supplied-images).

dart

```
children: [
  ImageSection(
    image: 'images/lake.jpg',
  ),
  TitleSection(
    name: 'Oeschinen Lake Campground',
    location: 'Kandersteg, Switzerland',
```

## Congratulations

[#](#congratulations)

That's it! When you hot reload the app, your app should look like this.

The finished app

## Resources

[#](#resources)

You can access the resources used in this tutorial from these locations:

**Dart code:** [`main.dart`](https://github.com/flutter/website/blob/main/examples/layout/lakes/step6/lib/main.dart)\
**Image:** [ch-photo](https://unsplash.com/photos/red-and-gray-tents-in-grass-covered-mountain-5Rhl-kSRydQ)\
**Pubspec:** [`pubspec.yaml`](https://github.com/flutter/website/blob/main/examples/layout/lakes/step6/pubspec.yaml)

## Next Steps

[#](#next-steps)

To add interactivity to this layout, follow the [interactivity tutorial](/ui/interactivity).

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-09-29. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/ui/layout/tutorial.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/ui/layout/tutorial\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/ui/layout/tutorial.md "Report an issue with this page").

----
url: https://docs.flutter.dev/cookbook/networking/fetch-data.md
----

\# Fetch data from the internet > How to fetch data over the internet using the http package.

<!--?code-excerpt path-base="cookbook/networking/fetch_data/"?-->

Fetching data from the internet is necessary for most apps. Luckily, Dart and Flutter provide tools, such as the \`http\` package, for this type of work. :::note You should avoid directly using \`dart:io\` or \`dart:html\` to make HTTP requests. Those libraries are platform-dependent and tied to a single implementation. ::: This recipe uses the following steps: 1. Add the \`http\` package. 2. Make a network request using the \`http\` package. 3. Convert the response into a custom Dart object. 4. Fetch and display the data with Flutter. ## 1. Add the \`http\` package The \[\`http\`]\[] package provides the simplest way to fetch data from the internet. To add the \`http\` package as a dependency, run \`flutter pub add\`: \`\`\`console $ flutter pub add http \`\`\` Import the http package.

<!--?code-excerpt "lib/main.dart (Http)"?-->

\`\`\`dart import 'package:http/http.dart' as http; import 'package:material\_ui/material\_ui.dart'; \`\`\` If you are deploying to Android, edit your \`AndroidManifest.xml\` file to add the Internet permission. \`\`\`xml \`\`\` Likewise, if you are deploying to macOS, edit your \`macos/Runner/DebugProfile.entitlements\` and \`macos/Runner/Release.entitlements\` files to include the network client entitlement. \`\`\`xml com.apple.security.network.client \`\`\` ## 2. Make a network request This recipe covers how to fetch a sample album from the \[JSONPlaceholder]\[] using the \[\`http.get()\`]\[] method.

<!--?code-excerpt "lib/main_step1.dart (fetchAlbum)"?-->

\`\`\`dart Future fetchAlbum() { return http.get(Uri.parse('https\://jsonplaceholder.typicode.com/albums/1')); } \`\`\` The \`http.get()\` method returns a \`Future\` that contains a \`Response\`. \* \[\`Future\`]\[] is a core Dart class for working with async operations. A Future object represents a potential value or error that will be available at some time in the future. \* The \`http.Response\` class contains the data received from a successful http call. ## 3. Convert the response into a custom Dart object While it's easy to make a network request, working with a raw \`Future\` isn't very convenient. To make your life easier, convert the \`http.Response\` into a Dart object. ### Create an \`Album\` class First, create an \`Album\` class that contains the data from the network request. It includes a factory constructor that creates an \`Album\` from JSON. Converting JSON using \[pattern matching]\[] is only one option. For more information, see the full article on \[JSON and serialization]\[].

<!--?code-excerpt "lib/main.dart (Album)"?-->

\`\`\`dart class Album { final int userId; final int id; final String title; const Album({required this.userId, required this.id, required this.title}); factory Album.fromJson(Map json) { return switch (json) { {'userId': int userId, 'id': int id, 'title': String title} => Album( userId: userId, id: id, title: title, ), \_ => throw const FormatException('Failed to load album.'), }; } } \`\`\` ### Convert the \`http.Response\` to an \`Album\` Now, use the following steps to update the \`fetchAlbum()\` function to return a \`Future\`: 1. Convert the response body into a JSON \`Map\` with the \`dart:convert\` package. 2. If the server does return an OK response with a status code of 200, then convert the JSON \`Map\` into an \`Album\` using the \`fromJson()\` factory method. 3. If the server does not return an OK response with a status code of 200, then throw an exception. (Even in the case of a "404 Not Found" server response, throw an exception. Do not return \`null\`. This is important when examining the data in \`snapshot\`, as shown below.)

<!--?code-excerpt "lib/main.dart (fetchAlbum)"?-->

\`\`\`dart Future fetchAlbum() async { final response = await http.get( Uri.parse('https\://jsonplaceholder.typicode.com/albums/1'), headers: {'Accept': 'application/json'}, ); if (response.statusCode == 200) { // If the server did return a 200 OK response, // then parse the JSON. return Album.fromJson(jsonDecode(response.body) as Map); } else { // If the server did not return a 200 OK response, // then throw an exception. throw Exception('Failed to load album'); } } \`\`\` Hooray! Now you've got a function that fetches an album from the internet. ## 4. Fetch the data Call the \`fetchAlbum()\` method in either the \[\`initState()\`]\[] or \[\`didChangeDependencies()\`]\[] methods. 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\`]\[] changing, put the call into the \`didChangeDependencies()\` method. See \[\`State\`]\[] for more details.

<!--?code-excerpt "lib/main.dart (State)"?-->

\`\`\`dart class \_MyAppState extends State { late Future futureAlbum; @override void initState() { super.initState(); futureAlbum = fetchAlbum(); } // ··· } \`\`\` This Future is used in the next step. ## 5. Display the data To display the data on screen, use the \[\`FutureBuilder\`]\[] widget. The \`FutureBuilder\` widget comes with Flutter and makes it easy to work with asynchronous data sources. You must provide two parameters: 1. The \`Future\` you want to work with. In this case, the future returned from the \`fetchAlbum()\` function. 2. A \`builder\` function that tells Flutter what to render, depending on the state of the \`Future\`: loading, success, or error. Note that \`snapshot.hasData\` only returns \`true\` when the snapshot contains a non-null data value. Because \`fetchAlbum\` can only return non-null values, the function should throw an exception even in the case of a "404 Not Found" server response. Throwing an exception sets the \`snapshot.hasError\` to \`true\` which can be used to display an error message. Otherwise, the spinner will be displayed.

<!--?code-excerpt "lib/main.dart (FutureBuilder)" replace="/^child: //g;/^\),$/)/g"?-->

\`\`\`dart FutureBuilder( future: futureAlbum, builder: (context, snapshot) { if (snapshot.hasData) { return Text(snapshot.data!.title); } else if (snapshot.hasError) { return Text('${snapshot.error}'); } // By default, show a loading spinner. return const CircularProgressIndicator(); }, ) \`\`\` ## Why is fetchAlbum() called in initState()? Although it's convenient, it's not recommended to put an API call in a \`build()\` method. Flutter calls the \`build()\` method every time it needs to change anything in the view, and this happens surprisingly often. The \`fetchAlbum()\` method, if placed inside \`build()\`, is repeatedly called on each rebuild causing the app to slow down. Storing the \`fetchAlbum()\` result in a state variable ensures that the \`Future\` is executed only once and then cached for subsequent rebuilds. ## Testing For information on how to test this functionality, see the following recipes: \* \[Introduction to unit testing]\[] \* \[Mock dependencies using Mockito]\[] ## Complete example

<!--?code-excerpt "lib/main.dart"?-->

\`\`\`dart import 'dart:async'; import 'dart:convert'; import 'package:http/http.dart' as http; import 'package:material\_ui/material\_ui.dart'; Future fetchAlbum() async { final response = await http.get( Uri.parse('https\://jsonplaceholder.typicode.com/albums/1'), headers: {'Accept': 'application/json'}, ); if (response.statusCode == 200) { // If the server did return a 200 OK response, // then parse the JSON. return Album.fromJson(jsonDecode(response.body) as Map); } else { // If the server did not return a 200 OK response, // then throw an exception. throw Exception('Failed to load album'); } } class Album { final int userId; final int id; final String title; const Album({required this.userId, required this.id, required this.title}); factory Album.fromJson(Map json) { return switch (json) { {'userId': int userId, 'id': int id, 'title': String title} => Album( userId: userId, id: id, title: title, ), \_ => throw const FormatException('Failed to load album.'), }; } } void main() => runApp(const MyApp()); class MyApp extends StatefulWidget { const MyApp({super.key}); @override State createState() => \_MyAppState(); } class \_MyAppState extends State { late Future futureAlbum; @override void initState() { super.initState(); futureAlbum = fetchAlbum(); } @override Widget build(BuildContext context) { return MaterialApp( title: 'Fetch Data Example', theme: ThemeData( colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), ), home: Scaffold( appBar: AppBar(title: const Text('Fetch Data Example')), body: Center( child: FutureBuilder( future: futureAlbum, builder: (context, snapshot) { if (snapshot.hasData) { return Text(snapshot.data!.title); } else if (snapshot.hasError) { return Text('${snapshot.error}'); } // By default, show a loading spinner. return const CircularProgressIndicator(); }, ), ), ), ); } } \`\`\` \[\`didChangeDependencies()\`]: https\://api.flutter.dev/flutter/widgets/State/didChangeDependencies.html \[\`Future\`]: https\://api.flutter.dev/flutter/dart-async/Future-class.html \[\`FutureBuilder\`]: https\://api.flutter.dev/flutter/widgets/FutureBuilder-class.html \[JSONPlaceholder]: https\://jsonplaceholder.typicode.com/ \[\`http\`]: https\://pub.dev/packages/http \[\`http.get()\`]: https\://pub.dev/documentation/http/latest/http/get.html \[\`http\` package]: https\://pub.dev/packages/http/install \[\`InheritedWidget\`]: https\://api.flutter.dev/flutter/widgets/InheritedWidget-class.html \[Introduction to unit testing]: /cookbook/testing/unit/introduction \[\`initState()\`]: https\://api.flutter.dev/flutter/widgets/State/initState.html \[Mock dependencies using Mockito]: /cookbook/testing/unit/mocking \[JSON and serialization]: /data-and-backend/serialization/json \[pattern matching]: https\://dart.dev/language/patterns \[\`State\`]: https\://api.flutter.dev/flutter/widgets/State-class.html

----
url: https://docs.flutter.dev/cookbook/navigation/navigate-with-arguments
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Pass arguments to a named route](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

The [`Navigator`](https://api.flutter.dev/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 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.

You can accomplish this task using the `arguments` parameter of the [`Navigator.pushNamed()`](https://api.flutter.dev/flutter/widgets/Navigator/pushNamed.html) method. Extract the arguments using the [`ModalRoute.of()`](https://api.flutter.dev/flutter/widgets/ModalRoute/of.html) method or inside an [`onGenerateRoute()`](https://api.flutter.dev/flutter/widgets/WidgetsApp/onGenerateRoute.html) function provided to the [`MaterialApp`](https://pub.dev/documentation/material_ui/latest/material_ui/MaterialApp-class.html) or [`CupertinoApp`](https://pub.dev/documentation/cupertino_ui/latest/cupertino_ui/CupertinoApp-class.html) constructor.

This recipe demonstrates how to pass arguments to a named route and read the arguments using `ModalRoute.of()` and `onGenerateRoute()` using the following steps:

1. Define the arguments you need to pass.
2. Create a widget that extracts the arguments.
3. Register the widget in the `routes` table.
4. Navigate to the widget.

## 1. Define the arguments you need to pass

[#](#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`.

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.
class ScreenArguments {
  final String title;
  final String message;
​
  ScreenArguments(this.title, this.message);
}
```

## 2. Create a widget that extracts the arguments

[#](#2-create-a-widget-that-extracts-the-arguments)

Next, create a widget that extracts and displays the `title` and `message` from the `ScreenArguments`. To access the `ScreenArguments`, use the [`ModalRoute.of()`](https://api.flutter.dev/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.
class ExtractArgumentsScreen extends StatelessWidget {
  const ExtractArgumentsScreen({super.key});
​
  static const routeName = '/extractArguments';
​
  @override
  Widget build(BuildContext context) {
    // Extract the arguments from the current ModalRoute
    // settings and cast them as ScreenArguments.
    final args = ModalRoute.of(context)!.settings.arguments as ScreenArguments;
​
    return Scaffold(
      appBar: AppBar(title: Text(args.title)),
      body: Center(child: Text(args.message)),
    );
  }
}
```

## 3. Register the widget in the `routes` table

[#](#3-register-the-widget-in-the-routes-table)

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.

dart

```
MaterialApp(
  routes: {
    ExtractArgumentsScreen.routeName: (context) =>
        const ExtractArgumentsScreen(),
  },
)
```

## 4. Navigate to the widget

[#](#4-navigate-to-the-widget)

Finally, navigate to the `ExtractArgumentsScreen` when a user taps a button using [`Navigator.pushNamed()`](https://api.flutter.dev/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.
// The named route extracts the arguments
// by itself.
ElevatedButton(
  onPressed: () {
    // When the user taps the button,
    // navigate to a named route and
    // provide the arguments as an optional
    // parameter.
    Navigator.pushNamed(
      context,
      ExtractArgumentsScreen.routeName,
      arguments: ScreenArguments(
        'Extract Arguments Screen',
        'This message is extracted in the build method.',
      ),
    );
  },
  child: const Text('Navigate to screen that extracts arguments'),
),
```

## Alternatively, extract the arguments using `onGenerateRoute`

[#](#alternatively-extract-the-arguments-using-ongenerateroute)

Instead of extracting the arguments directly inside the widget, you can also extract the arguments inside an [`onGenerateRoute()`](https://api.flutter.dev/flutter/widgets/WidgetsApp/onGenerateRoute.html) function and pass them to a widget.

The `onGenerateRoute()` function creates the correct route based on the given [`RouteSettings`](https://api.flutter.dev/flutter/widgets/RouteSettings-class.html).

dart

```
MaterialApp(
  // Provide a function to handle named routes.
  // Use this function to identify the named
  // route being pushed, and create the correct
  // Screen.
  onGenerateRoute: (settings) {
    // If you push the PassArguments route
    if (settings.name == PassArgumentsScreen.routeName) {
      // Cast the arguments to the correct
      // type: ScreenArguments.
      final args = settings.arguments as ScreenArguments;
​
      // Then, extract the required data from
      // the arguments and pass the data to the
      // correct screen.
      return MaterialPageRoute(
        builder: (context) {
          return PassArgumentsScreen(
            title: args.title,
            message: args.message,
          );
        },
      );
    }
    // The code only supports
    // PassArgumentsScreen.routeName right now.
    // Other values need to be implemented if we
    // add them. The assertion here will help remind
    // us of that higher up in the call stack, since
    // this assertion would otherwise fire somewhere
    // in the framework.
    assert(false, 'Need to implement ${settings.name}');
    return null;
  },
)
```

## Interactive example

[#](#interactive-example)

```
import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      routes: {
        ExtractArgumentsScreen.routeName: (context) =>
            const ExtractArgumentsScreen(),
      },
      // Provide a function to handle named routes.
      // Use this function to identify the named
      // route being pushed, and create the correct
      // Screen.
      onGenerateRoute: (settings) {
        // If you push the PassArguments route
        if (settings.name == PassArgumentsScreen.routeName) {
          // Cast the arguments to the correct
          // type: ScreenArguments.
          final args = settings.arguments as ScreenArguments;

          // Then, extract the required data from
          // the arguments and pass the data to the
          // correct screen.
          return MaterialPageRoute(
            builder: (context) {
              return PassArgumentsScreen(
                title: args.title,
                message: args.message,
              );
            },
          );
        }
        // The code only supports
        // PassArgumentsScreen.routeName right now.
        // Other values need to be implemented if we
        // add them. The assertion here will help remind
        // us of that higher up in the call stack, since
        // this assertion would otherwise fire somewhere
        // in the framework.
        assert(false, 'Need to implement ${settings.name}');
        return null;
      },
      title: 'Navigation with Arguments',
      home: const HomeScreen(),
    );
  }
}

class HomeScreen extends StatelessWidget {
  const HomeScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Home Screen')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            // A button that navigates to a named route.
            // The named route extracts the arguments
            // by itself.
            ElevatedButton(
              onPressed: () {
                // When the user taps the button,
                // navigate to a named route and
                // provide the arguments as an optional
                // parameter.
                Navigator.pushNamed(
                  context,
                  ExtractArgumentsScreen.routeName,
                  arguments: ScreenArguments(
                    'Extract Arguments Screen',
                    'This message is extracted in the build method.',
                  ),
                );
              },
              child: const Text('Navigate to screen that extracts arguments'),
            ),
            // A button that navigates to a named route.
            // For this route, extract the arguments in
            // the onGenerateRoute function and pass them
            // to the screen.
            ElevatedButton(
              onPressed: () {
                // When the user taps the button, navigate
                // to a named route and provide the arguments
                // as an optional parameter.
                Navigator.pushNamed(
                  context,
                  PassArgumentsScreen.routeName,
                  arguments: ScreenArguments(
                    'Accept Arguments Screen',
                    'This message is extracted in the onGenerateRoute '
                        'function.',
                  ),
                );
              },
              child: const Text('Navigate to a named that accepts arguments'),
            ),
          ],
        ),
      ),
    );
  }
}

// A Widget that extracts the necessary arguments from
// the ModalRoute.
class ExtractArgumentsScreen extends StatelessWidget {
  const ExtractArgumentsScreen({super.key});

  static const routeName = '/extractArguments';

  @override
  Widget build(BuildContext context) {
    // Extract the arguments from the current ModalRoute
    // settings and cast them as ScreenArguments.
    final args = ModalRoute.of(context)!.settings.arguments as ScreenArguments;

    return Scaffold(
      appBar: AppBar(title: Text(args.title)),
      body: Center(child: Text(args.message)),
    );
  }
}

// A Widget that accepts the necessary arguments via the
// constructor.
class PassArgumentsScreen extends StatelessWidget {
  static const routeName = '/passArguments';

  final String title;
  final String message;

  // This Widget accepts the arguments as constructor
  // parameters. It does not extract the arguments from
  // the ModalRoute.
  //
  // The arguments are extracted by the onGenerateRoute
  // function provided to the MaterialApp widget.
  const PassArgumentsScreen({
    super.key,
    required this.title,
    required this.message,
  });

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text(title)),
      body: Center(child: Text(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;

  ScreenArguments(this.title, this.message);
}
```

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-09-29. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/navigation/navigate-with-arguments.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/cookbook/navigation/navigate-with-arguments\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/navigation/navigate-with-arguments.md "Report an issue with this page").

----
url: https://docs.flutter.dev/platform-integration/web
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Web support for Flutter](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

Flutter delivers the same experiences on the web as on mobile.

Building on the portability of Dart, the power of the web platform, the flexibility of the Flutter framework, and the performance of WebAssembly, you can build apps for iOS, Android, and the browser from the same codebase. The web is just another device target for your app.

To get started, visit [Building a web application with Flutter](/platform-integration/web/building).

## Powered by WebAssembly

[#](#powered-by-webassembly)

Dart and Flutter can compile to WebAssembly, a binary instruction format that enables fast apps on all major browsers.

For a glimpse into the benefits of using WebAssembly, check out the following video.

[Watch on YouTube in a new tab: "What's new in Flutter - WebAssembly"](https://www.youtube.com/watch/lpnKWK-KEYs)

## How it works

[#](#how-it-works)

Adding web support to Flutter involved implementing Flutter's core drawing layer on top of standard browser APIs, in addition to compiling Dart to JavaScript, instead of the ARM machine code that is used for mobile applications. Using a combination of DOM, Canvas, and WebAssembly, Flutter can provide a portable, high-quality, and performant user experience across modern browsers. We implemented the core drawing layer completely in Dart and used Dart's optimized JavaScript compiler to compile the Flutter core and framework along with your application into a single, minified source file that can be deployed to any web server.

## What types of apps can I build?

[#](#what-types-of-apps-can-i-build)

While you can do a lot on the web, Flutter's web support is most valuable in the following scenarios:

* **Single Page Application**

  Flutter's web support enables complex standalone web apps that are rich with graphics and interactive content to reach end users on a wide variety of devices.

* **Existing mobile applications**

  Web support for Flutter provides a browser-based delivery model for existing Flutter mobile apps.

Not every HTML scenario is ideally suited for Flutter at this time. For example, text-rich, flow-based, static content such as blog articles benefit from the document-centric model that the web is built around, rather than the app-centric services that a UI framework like Flutter can deliver. However, you *can* use Flutter to embed interactive experiences into these websites.

However, if you want to use Dart and wish to implement a traditional DOM-based website, a community-released Dart package, [Jaspr](https://jaspr.site/), supports static websites; in fact, the [Dart documentation](https://dart.dev) and [Flutter documentation](https://flutter.dev) and [marketing](https://flutter.dev) websites were migrated to use the Jaspr package. Note that Jaspr uses Dart (but not Flutter), and makes SEO work in the same way a traditional website would.

## Get started

[#](#get-started)

The following resources can help you get started:

* To add web support to an existing app, or to create a new app that includes web support, visit [Building a web application with Flutter](/platform-integration/web/building).
* To configure web development server settings in a centralized file, check out [Set up a web development configuration file](/platform-integration/web/web-dev-config-file).
* To learn how to create a responsive Flutter app, check out [Creating responsive apps](/ui/adaptive-responsive).
* To view commonly asked questions and answers, visit the [web FAQ](/platform-integration/web/faq).
* For code examples, check out the [web samples for Flutter](https://github.com/flutter/samples/#?platform=web).
* For a Flutter web app demo, check out the [Wonderous app](https://wonderous.app//web).
* To learn about deploying a web app, visit [Preparing an app for web release](/deployment/web).
* [File an issue](https://goo.gle/flutter_web_issue) on the main Flutter repo.
* You can chat and ask web-related questions on the **#help** channel on [Discord](https://discordapp.com/invite/yeZ6s7k).

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-07-23. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/platform-integration/web/index.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/platform-integration/web\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/platform-integration/web/index.md "Report an issue with this page").

----
url: https://docs.flutter.dev/perf/antialiasing
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Impeller anti-aliasing](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

Aliasing is the visual artifacts that result from drawing geometry to a grid of pixels (rasterization). The artifacts show up as jagged or rough edges on shapes. Impeller employs a couple techniques to smooth out the mapping to raster graphics (anti-aliasing).

## Techniques

[#](#techniques)

### Multisample anti-aliasing (MSAA)

[#](#multisample-anti-aliasing-msaa)

[MSAA](https://en.wikipedia.org/wiki/Multisample_anti-aliasing) is a global anti-aliasing technique that operates on the whole contents of the screen. It is an optimization over rendering the whole screen at a larger scale and shrinking it down ([SSAA](https://en.wikipedia.org/wiki/Supersampling)). Instead of doing the fragment operation for each sub-sample, the calculation for the pixel is duplicated across the sub-samples where a bounds check happens. This limits smoothing to edges. Mobile phone GPUs have special hardware to optimize this process ( [Tiled rendering](https://en.wikipedia.org/wiki/Tiled_rendering)). It comes in varying degrees of how many samples to consider.

On desktop and mobile 4x MSAA is used for all rendering calls.

### Signed distance fields ([SDFs](https://en.wikipedia.org/wiki/Signed_distance_function))

[#](#signed-distance-fields-sdfs)

Typically, hardware accelerated computer graphics define a series of points and edges (a [mesh](https://en.wikipedia.org/wiki/Polygon_mesh)) and [shaders](https://en.wikipedia.org/wiki/Shader). Instead, SDF renders shapes in the fragment shader program as signed distance fields. Since the shape is defined in the fragment shader the edges can be smoothed at the fragment level instead of relying on the rasterization of a mesh.

On desktop, rendering with SDFs is enabled by default. On mobile platforms, SDFs are an option that defaults to false.

This technique is prioritized on desktop because SDF rendering puts more demand on the GPU and Flutter supports older mobile phones. Also, the physical pixel sizes on desktop computers are typically bigger than those of mobile phones. So any imperfection will be more evident there.

### Examples

[#](#examples)

| No AA | MSAA 4x | MSAA 4x + SDF |
| ----- | ------- | ------------- |
|       |         |               |

## Working with anti-aliasing

[#](#working-with-anti-aliasing)

### SDFs with the FragmentShader API

[#](#sdfs-with-the-fragmentshader-api)

Standard primitive shapes in Flutter are drawn by default with SDFs. If a Flutter developer wants to define their own custom graphics with SDFs they can do so with the [FragmentShader API](/ui/design/graphics/fragment-shaders). Using the [drawPath()](https://api.flutter.dev/flutter/dart-ui/Canvas/drawPath.html) is sufficient for most use cases without resorting to high quality SDF rendering. Not all drawn paths are guaranteed to result in SDF rendering though.

An example of rendering SDFs with the FragmentShader API can be found at [`simple_sdf`](https://github.com/flutter/samples/tree/main/simple_sdf).

### Enabling SDFs on iOS

[#](#enabling-sdfs-on-ios)

SDFs can be enabled on iOS by adding a new field to the `Info.plist` for the project.

xml

```
  <key>FLTEnableSDFs</key>
  <true/>
```

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-08-21. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/perf/antialiasing.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/perf/antialiasing\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/perf/antialiasing.md "Report an issue with this page").

----
url: https://docs.flutter.dev/reference/learning-resources
----

search

Showing 0 / 0

[Demo](https://github.com/flutter/samples/tree/main/add_to_app)

[Add-to-app](https://github.com/flutter/samples/tree/main/add_to_app)

[Recommended approaches for adding Flutter to existing apps.](https://github.com/flutter/samples/tree/main/add_to_app)

[Demo](https://github.com/flutter/samples/tree/main/android_splash_screen)

[Android splash screen](https://github.com/flutter/samples/tree/main/android_splash_screen)

[A Flutter sample app that exemplifies how to implement an animated splash screen for Android devices.](https://github.com/flutter/samples/tree/main/android_splash_screen)

[Demo](https://github.com/flutter/samples/tree/main/ios_app_clip)

[iOS app clip](https://github.com/flutter/samples/tree/main/ios_app_clip)

[A sample project demonstrating integration with iOS App Clip.](https://github.com/flutter/samples/tree/main/ios_app_clip)

[Demo](https://github.com/flutter/samples/tree/main/platform_view_swift)

[Swift platform view](https://github.com/flutter/samples/tree/main/platform_view_swift)

[A Flutter sample app that combines a native iOS UIViewController with a full-screen Flutter view.](https://github.com/flutter/samples/tree/main/platform_view_swift)

[Workshop](https://www.youtube.com/watch?v=8sAyPDLorek)

[Your first Flutter app workshop](https://www.youtube.com/watch?v=8sAyPDLorek)

[An instructor-led version of our very popular 'Write your first Flutter app' codelab.](https://www.youtube.com/watch?v=8sAyPDLorek)

[Codelab](https://codelabs.developers.google.com/codelabs/flutter-codelab-first)

[Your first Flutter app](https://codelabs.developers.google.com/codelabs/flutter-codelab-first)

[Create a simple random-name generator app. This app is responsive and runs on mobile, desktop, and web.](https://codelabs.developers.google.com/codelabs/flutter-codelab-first)

[Codelab](https://codelabs.developers.google.com/codelabs/dart-patterns-records)

[Records and Patterns in Dart](https://codelabs.developers.google.com/codelabs/dart-patterns-records)

[Discover Dart 3's new records and patterns features. Learn how you can use them in a Flutter app to help you write more readable and maintainable Dart code.](https://codelabs.developers.google.com/codelabs/dart-patterns-records)

[Codelab](https://codelabs.developers.google.com/advanced-flutter-animations)

[Animations in Flutter](https://codelabs.developers.google.com/advanced-flutter-animations)

[Learn how to build animated effects in Flutter. You'll learn how to build implicit and explicit animations, and customize navigation transition animations the animations package and predictive back on Android.](https://codelabs.developers.google.com/advanced-flutter-animations)

[Codelab](https://codelabs.developers.google.com/codelabs/material-motion-flutter)

[Building Beautiful Transitions with Material Motion for Flutter](https://codelabs.developers.google.com/codelabs/material-motion-flutter)

[Learn how to use the Material animations package to add pre-built transitions to a Material app called Reply.](https://codelabs.developers.google.com/codelabs/material-motion-flutter)

[Codelab](https://blog.flutter.dev/how-to-debug-layout-issues-with-the-flutter-inspector-87460a7b9db)

[How to debug layout issues with the Flutter Inspector](https://blog.flutter.dev/how-to-debug-layout-issues-with-the-flutter-inspector-87460a7b9db)

[Step-by-step instructions on how to debug common layout problems using the Flutter Inspector and Layout Explorer.](https://blog.flutter.dev/how-to-debug-layout-issues-with-the-flutter-inspector-87460a7b9db)

[Codelab](https://codelabs.developers.google.com/codelabs/mdc-101-flutter)

[MDC-101 - Material Components (MDC) Basics](https://codelabs.developers.google.com/codelabs/mdc-101-flutter)

[Learn the basics of using Material Components by building a simple app with core components.](https://codelabs.developers.google.com/codelabs/mdc-101-flutter)

[Codelab](https://codelabs.developers.google.com/codelabs/mdc-102-flutter)

[MDC-102 - Material Structure and Layout](https://codelabs.developers.google.com/codelabs/mdc-102-flutter)

[Learn how to use Material for structure and layout in Flutter. Continue building the e-commerce app, introduced in MDC-101, by adding navigation, structure, and data.](https://codelabs.developers.google.com/codelabs/mdc-102-flutter)

[Codelab](https://codelabs.developers.google.com/codelabs/mdc-103-flutter)

[MDC-103 - Material Theming with Color, Shape, Elevation, and Type](https://codelabs.developers.google.com/codelabs/mdc-103-flutter)

[Discover how Material Components for Flutter make it easy to differentiate your product, and express your brand through design.](https://codelabs.developers.google.com/codelabs/mdc-103-flutter)

[Codelab](https://codelabs.developers.google.com/codelabs/mdc-104-flutter)

[MDC-104 - Material Advanced Components](https://codelabs.developers.google.com/codelabs/mdc-104-flutter)

[Improve your design and learn to use our advanced component backdrop menu.](https://codelabs.developers.google.com/codelabs/mdc-104-flutter)

[Codelab](https://codelabs.developers.google.com/codelabs/admob-ads-in-flutter)

[Adding AdMob Ads to a Flutter app](https://codelabs.developers.google.com/codelabs/admob-ads-in-flutter)

[Learn how to add an AdMob banner, an interstitial ad, and a rewarded ad to an app called Awesome Drawing Quiz, a game that lets players guess the name of the drawing.](https://codelabs.developers.google.com/codelabs/admob-ads-in-flutter)

[Codelab](https://codelabs.developers.google.com/codelabs/admob-inline-ads-in-flutter)

[Adding an AdMob banner and native inline ads to a Flutter app](https://codelabs.developers.google.com/codelabs/admob-inline-ads-in-flutter)

[Learn how to implement inline banner and native ads to a travel booking app that lists possible flight destinations.](https://codelabs.developers.google.com/codelabs/admob-inline-ads-in-flutter)

[Codelab](https://codelabs.developers.google.com/codelabs/flutter-in-app-purchases)

[Adding in-app purchases to your Flutter app](https://codelabs.developers.google.com/codelabs/flutter-in-app-purchases)

[Extend a simple gaming app that uses the Dash mascot as currency to offer three types of in-app purchases: consumable, non-consumable, and subscription.](https://codelabs.developers.google.com/codelabs/flutter-in-app-purchases)

[Codelab](https://firebase.google.com/codelabs/firebase-auth-in-flutter-apps)

[Add a user authentication flow using FirebaseUI](https://firebase.google.com/codelabs/firebase-auth-in-flutter-apps)

[Learn how to add Firebase authentication to a Flutter app with only a few lines of code.](https://firebase.google.com/codelabs/firebase-auth-in-flutter-apps)

[Codelab](https://firebase.google.com/codelabs/firebase-get-to-know-flutter)

[Get to know Firebase for Flutter](https://firebase.google.com/codelabs/firebase-get-to-know-flutter)

[Build an event RSVP and guestbook chat app on both Android and iOS using Flutter, authenticating users with Firebase Authentication, and sync data using Cloud Firestore.](https://firebase.google.com/codelabs/firebase-get-to-know-flutter)

[Codelab](https://firebase.google.com/codelabs/firebase-fcm-flutter)

[Notifications with Firebase Cloud Messaging](https://firebase.google.com/codelabs/firebase-fcm-flutter)

[Learn how to develop a multi-platform app with Flutter and Firebase Cloud Messaging, integrating FCM to send and receive messages on Android, iOS, and web.](https://firebase.google.com/codelabs/firebase-fcm-flutter)

[Codelab](https://codelabs.developers.google.com/codelabs/flutter-word-puzzle)

[Build a word puzzle with Flutter](https://codelabs.developers.google.com/codelabs/flutter-word-puzzle)

[This codelab focuses on building word puzzle games, and dives into using Flutter's background processing to generate expansive crossword-style grids of interlocking words.](https://codelabs.developers.google.com/codelabs/flutter-word-puzzle)

[Codelab](https://codelabs.developers.google.com/codelabs/flutter-flame-brick-breaker)

[Introduction to Flame with Flutter](https://codelabs.developers.google.com/codelabs/flutter-flame-brick-breaker)

[Build a Breakout clone using the Flame 2D game engine and embed it in a Flutter wrapper.](https://codelabs.developers.google.com/codelabs/flutter-flame-brick-breaker)

[Codelab](https://codelabs.developers.google.com/codelabs/google-maps-in-flutter)

[Adding Google Maps to a Flutter app](https://codelabs.developers.google.com/codelabs/google-maps-in-flutter)

[Display a Google map in an app, retrieve data from a web service, and display the data as markers on the map.](https://codelabs.developers.google.com/codelabs/google-maps-in-flutter)

[Codelab](https://codelabs.developers.google.com/codelabs/flutter-webview)

[Adding WebView to your Flutter app](https://codelabs.developers.google.com/codelabs/flutter-webview)

[With the WebView Flutter plugin you can add a WebView widget to your Android or iOS Flutter app.](https://codelabs.developers.google.com/codelabs/flutter-webview)

[Codelab](https://codelabs.developers.google.com/flutter-home-screen-widgets)

[Adding a Home Screen widget to your Flutter app](https://codelabs.developers.google.com/flutter-home-screen-widgets)

[Learn how to add a Home Screen widget to your Flutter app on iOS. This applies to your home screen, lock screen, or the today view.](https://codelabs.developers.google.com/flutter-home-screen-widgets)

[Quickstart](https://github.com/dart-lang/samples/tree/main/command_line)

[Command-line app](https://github.com/dart-lang/samples/tree/main/command_line)

[A command line app that parses command-line options and fetches from GitHub.](https://github.com/dart-lang/samples/tree/main/command_line)

[Quickstart](https://github.com/dart-lang/samples/tree/main/extension_methods)

[Extension methods](https://github.com/dart-lang/samples/tree/main/extension_methods)

[Demonstrates Dart's extensions method syntax.](https://github.com/dart-lang/samples/tree/main/extension_methods)

[Quickstart](https://github.com/dart-lang/samples/tree/main/ffi)

[FFI](https://github.com/dart-lang/samples/tree/main/ffi)

[A series of simple examples demonstrating how to call C libraries from Dart.](https://github.com/dart-lang/samples/tree/main/ffi)

[Quickstart](https://github.com/dart-lang/samples/tree/main/ffi)

[Isolates (in a CLI)](https://github.com/dart-lang/samples/tree/main/ffi)

[Command line applications that demonstrate how to work with Concurrency in Dart using isolates.](https://github.com/dart-lang/samples/tree/main/ffi)

[Quickstart](https://github.com/dart-lang/samples/tree/main/native_app)

[Native Dart app](https://github.com/dart-lang/samples/tree/main/native_app)

[A command line application that can be compiled to native code using \`dart compile exe\`.](https://github.com/dart-lang/samples/tree/main/native_app)

[Quickstart](https://github.com/dart-lang/samples/tree/main/server)

[Server side Dart](https://github.com/dart-lang/samples/tree/main/server)

[Examples of running Dart on the server.](https://github.com/dart-lang/samples/tree/main/server)

[Quickstart](https://github.com/dart-lang/samples/tree/main/server)

[Package constraint solver](https://github.com/dart-lang/samples/tree/main/server)

[Demonstrates best-practices for publishing packages on pub.dev.](https://github.com/dart-lang/samples/tree/main/server)

[Recipe](/cookbook/animation/page-route-animation)

[Animate a page route transition](/cookbook/animation/page-route-animation)

[Transition between routes by animating the new route into view from the bottom of the screen.](/cookbook/animation/page-route-animation)

[Recipe](/cookbook/animation/physics-simulation)

[Animate a widget using a physics simulation](/cookbook/animation/physics-simulation)

[Learn how to move a widget from a dragged point back to the center using a spring simulation.](/cookbook/animation/physics-simulation)

[Recipe](/cookbook/animation/animated-container)

[Animate the properties of a container](/cookbook/animation/animated-container)

[Use AnimatedContainer to animate the size, background color, and border radius of a Container.](/cookbook/animation/animated-container)

[Recipe](/cookbook/animation/opacity-animation)

[Fade a widget in and out](/cookbook/animation/opacity-animation)

[The AnimatedOpacity widget makes it easy to perform opacity animations.](/cookbook/animation/opacity-animation)

[Recipe](/cookbook/design/drawer)

[Add a drawer to a screen](/cookbook/design/drawer)

[Use the Drawer widget in combination with a Scaffold to create a layout with a Material Design drawer.](/cookbook/design/drawer)

[Recipe](/cookbook/design/snackbars)

[Display a snackbar](/cookbook/design/snackbars)

[Use the Snackbar widget to display messages to your users.](/cookbook/design/snackbars)

[Recipe](/cookbook/design/package-fonts)

[Export fonts from a package](/cookbook/design/package-fonts)

[Use a font across multiple apps.](/cookbook/design/package-fonts)

[Recipe](/cookbook/design/orientation)

[Update the UI based on orientation](/cookbook/design/orientation)

[Build a list that displays two columns in portrait mode and three columns in landscape mode.](/cookbook/design/orientation)

[Recipe](/cookbook/design/fonts)

[Use a custom font](/cookbook/design/fonts)

[Apply fonts to your entire app or individual widgets.](/cookbook/design/fonts)

[Recipe](/cookbook/design/themes)

[Use themes to share colors and font styles](/cookbook/design/themes)

[To share styles across your app, use Themes.](/cookbook/design/themes)

[Recipe](/cookbook/design/tabs)

[Work with tabs](/cookbook/design/tabs)

[Working with tabs is a common pattern in mobile apps that follow the Material Design or Cupertino guidelines.](/cookbook/design/tabs)

[Recipe](/cookbook/effects/download-button)

[Create a download button](/cookbook/effects/download-button)

[Build a download button that transitions through multiple visual states, based on the status of an app download.](/cookbook/effects/download-button)

[Recipe](/cookbook/effects/nested-nav)

[Create a nested navigation flow](/cookbook/effects/nested-nav)

[Create top level routes, and routes nested below specific widgets.](/cookbook/effects/nested-nav)

[Recipe](/cookbook/effects/parallax-scrolling)

[Create a scrolling parallax effect](/cookbook/effects/parallax-scrolling)

[Create the parallax effect by building a list of cards with images that 'move'.](/cookbook/effects/parallax-scrolling)

[Recipe](/cookbook/effects/shimmer-loading)

[Create a shimmer loading effect](/cookbook/effects/shimmer-loading)

[Communicate that data is loading with a chrome color shimmer on the screen.](/cookbook/effects/shimmer-loading)

[Recipe](/cookbook/effects/staggered-menu-animation)

[Create a staggered menu animation](/cookbook/effects/staggered-menu-animation)

[Build a drawer menu with animated content that is staggered and has a button that pops in at the bottom](/cookbook/effects/staggered-menu-animation)

[Recipe](/cookbook/effects/expandable-fab)

[Create an expandable FAB](/cookbook/effects/expandable-fab)

[Create a floating action button that spawns other action buttons.](/cookbook/effects/expandable-fab)

[Recipe](/cookbook/effects/drag-a-widget)

[Drag a UI element](/cookbook/effects/drag-a-widget)

[Build a drag-and-drop interaction when the user long presses.](/cookbook/effects/drag-a-widget)

[Recipe](/cookbook/forms/validation)

[Build a form with validation](/cookbook/forms/validation)

[Learn how to add validation to a form.](/cookbook/forms/validation)

[Recipe](/cookbook/forms/text-input)

[Create and style a text field](/cookbook/forms/text-input)

[In this recipe, explore how to create and style text fields.](/cookbook/forms/text-input)

[Recipe](/cookbook/forms/focus)

[Focus and text fields](/cookbook/forms/focus)

[Shift focus to a text field programmatically.](/cookbook/forms/focus)

[Recipe](/cookbook/forms/text-field-changes)

[Handle changes to a text field](/cookbook/forms/text-field-changes)

[Listen for changes to a TextField using a callback.](/cookbook/forms/text-field-changes)

[Recipe](/cookbook/forms/retrieve-input)

[Retrieve the value of a text field](/cookbook/forms/retrieve-input)

[Learn how to retrieve the text a user has entered into a text field.](/cookbook/forms/retrieve-input)

[Recipe](/cookbook/games/achievements-leaderboard)

[Add achievements and leaderboards to your game](/cookbook/games/achievements-leaderboard)

[Use the games\_services package to add leaderboard functionality to your mobile game.](/cookbook/games/achievements-leaderboard)

[Recipe](/cookbook/games/firestore-multiplayer)

[Add multiplayer support to your Flutter game](/cookbook/games/firestore-multiplayer)

[Use the cloud\_firestore package to implement multiplayer capabilities in your game.](/cookbook/games/firestore-multiplayer)

[Recipe](/cookbook/plugins/google-mobile-ads)

[Add ads to your Flutter game](/cookbook/plugins/google-mobile-ads)

[Use the google\_mobile\_ads package to add a banner ad to your app or game.](/cookbook/plugins/google-mobile-ads)

[Recipe](/cookbook/gestures/ripples)

[Add Material touch ripples](/cookbook/gestures/ripples)

[Use the Inkwell widget to display a ripple animation.](/cookbook/gestures/ripples)

[Recipe](/cookbook/gestures/handling-taps)

[Handle taps](/cookbook/gestures/handling-taps)

[Use the GestureDetector widget to respond to fundamental actions, such as tapping and dragging.](/cookbook/gestures/handling-taps)

[Recipe](/cookbook/gestures/dismissible)

[Implement swipe to dismiss](/cookbook/gestures/dismissible)

[Learn how to use the Dismissible widget.](/cookbook/gestures/dismissible)

[Recipe](/cookbook/images/network-image)

[Display images from the internet](/cookbook/images/network-image)

[To work with images from a URL, use the Image.network() constructor.](/cookbook/images/network-image)

[Recipe](/cookbook/images/fading-in-images)

[Fade in images with a placeholder](/cookbook/images/fading-in-images)

[Use the FadeInImage widget to show a visual placeholder before an image loads.](/cookbook/images/fading-in-images)

[Recipe](/cookbook/lists/grid-lists)

[Grid lists](/cookbook/lists/grid-lists)

[Learn to use a GridView widget.](/cookbook/lists/grid-lists)

[Recipe](/cookbook/lists/horizontal-list)

[Horizontal lists](/cookbook/lists/horizontal-list)

[Learn to display items horizontally in a scrollable list.](/cookbook/lists/horizontal-list)

[Recipe](/cookbook/lists/mixed-list)

[Lists with different types of items](/cookbook/lists/mixed-list)

[Create a list with headers followed by a few list items.](/cookbook/lists/mixed-list)

[Recipe](/cookbook/lists/floating-app-bar)

[Lists and floating app bars](/cookbook/lists/floating-app-bar)

[Place a floating app bar or navigation bar above a list.](/cookbook/lists/floating-app-bar)

[Recipe](/cookbook/lists/basic-list)

[Basic lists](/cookbook/lists/basic-list)

[Learn to display items with the ListView widget.](/cookbook/lists/basic-list)

[Recipe](/cookbook/lists/long-lists)

[Long lists](/cookbook/lists/long-lists)

[Work with longer lists with the Listview.builder constructor.](/cookbook/lists/long-lists)

[Recipe](/cookbook/lists/spaced-items)

[Lists with spaced items](/cookbook/lists/spaced-items)

[Create a list with padding between items.](/cookbook/lists/spaced-items)

[Recipe](/cookbook/navigation/hero-animations)

[Animate a widget across screens](/cookbook/navigation/hero-animations)

[Use the Hero widget to animate a widget from one screen to the next.](/cookbook/navigation/hero-animations)

[Recipe](/cookbook/navigation/navigation-basics)

[Navigate to a new screen and back](/cookbook/navigation/navigation-basics)

[This recipe uses the Navigator to navigate to a new route.](/cookbook/navigation/navigation-basics)

[Recipe](/cookbook/navigation/named-routes)

[Named routes](/cookbook/navigation/named-routes)

[Create named routes and navigate to them.](/cookbook/navigation/named-routes)

[Recipe](/cookbook/navigation/navigate-with-arguments)

[Arguments and named routes](/cookbook/navigation/navigate-with-arguments)

[Pass arguments to a named route and read the arguments on that route.](/cookbook/navigation/navigate-with-arguments)

[Recipe](/cookbook/navigation/set-up-app-links)

[Android app links](/cookbook/navigation/set-up-app-links)

[Set up deep linking on Android](/cookbook/navigation/set-up-app-links)

[Recipe](/cookbook/navigation/set-up-universal-links)

[iOS universal links](/cookbook/navigation/set-up-universal-links)

[Set up universal links for iOS](/cookbook/navigation/set-up-universal-links)

[Recipe](/cookbook/navigation/returning-data)

[Return data from a screen](/cookbook/navigation/returning-data)

[Return data from one screen to another with the Navigator.pop method.](/cookbook/navigation/returning-data)

[Recipe](/cookbook/navigation/passing-data)

[Send data to a new screen](/cookbook/navigation/passing-data)

[Send data from one screen to new one.](/cookbook/navigation/passing-data)

[Recipe](/cookbook/networking/fetch-data)

[Fetch data from the internet](/cookbook/networking/fetch-data)

[Learn to use HTTP in your app.](/cookbook/networking/fetch-data)

[Recipe](/cookbook/networking/authenticated-requests)

[Make authenticated requests](/cookbook/networking/authenticated-requests)

[Authorization headers in HTTP](/cookbook/networking/authenticated-requests)

[Recipe](/cookbook/networking/send-data)

[Send data to the internet](/cookbook/networking/send-data)

[Send HTTP POST requests in your app.](/cookbook/networking/send-data)

[Recipe](/cookbook/networking/update-data)

[Update data over the internet](/cookbook/networking/update-data)

[Send an HTTP put request.](/cookbook/networking/update-data)

[Recipe](/cookbook/networking/delete-data)

[Delete data on the internet](/cookbook/networking/delete-data)

[Send an HTTP delete request.](/cookbook/networking/delete-data)

[Recipe](/cookbook/networking/web-sockets)

[WebSockets](/cookbook/networking/web-sockets)

[Connect to and communicate with a websocket.](/cookbook/networking/web-sockets)

[Recipe](/cookbook/networking/background-parsing)

[Parse JSON in the background](/cookbook/networking/background-parsing)

[Learn to use Dart's Isolate objects](/cookbook/networking/background-parsing)

[Recipe](/cookbook/persistence/sqlite)

[Persist data with SQLite](/cookbook/persistence/sqlite)

[Use the sqflite package.](/cookbook/persistence/sqlite)

[Recipe](/cookbook/persistence/reading-writing-files)

[Read and write files](/cookbook/persistence/reading-writing-files)

[Use the dart:io library and path\_provider plugin to save files to disk.](/cookbook/persistence/reading-writing-files)

[Recipe](/cookbook/persistence/key-value)

[Store key-value data on disk](/cookbook/persistence/key-value)

[Persist data with shared\_preferences](/cookbook/persistence/key-value)

[Recipe](/cookbook/plugins/play-video)

[Play and pause a video](/cookbook/plugins/play-video)

[Play videos stored on the file system, as an asset, or from the internet.](/cookbook/plugins/play-video)

[Recipe](/cookbook/plugins/picture-using-camera)

[Use the camera](/cookbook/plugins/picture-using-camera)

[Learn to use a devices camera.](/cookbook/plugins/picture-using-camera)

[Recipe](/cookbook/maintenance/error-reporting)

[Report errors to a service](/cookbook/maintenance/error-reporting)

[Report errors to Sentry crash reporting.](/cookbook/maintenance/error-reporting)

[Recipe](/cookbook/testing/integration/profiling)

[Performance profiling](/cookbook/testing/integration/profiling)

[Write a test that records a performance timeline.](/cookbook/testing/integration/profiling)

[Recipe](/cookbook/testing/unit/introduction)

[Write unit tests](/cookbook/testing/unit/introduction)

[An introduction to writing unit tests.](/cookbook/testing/unit/introduction)

[Recipe](/cookbook/testing/widget/introduction)

[Write widget tests](/cookbook/testing/widget/introduction)

[An introduction to writing widget tests.](/cookbook/testing/widget/introduction)

[Recipe](/cookbook/testing/unit/mocking)

[Mock dependencies in tests](/cookbook/testing/unit/mocking)

[The basics of mocking with the Mockito package.](/cookbook/testing/unit/mocking)

[Recipe](/cookbook/testing/widget/finders)

[Find widgets in tests](/cookbook/testing/widget/finders)

[This recipe looks at the 'find' constant provided by the flutter\_test package.](/cookbook/testing/widget/finders)

[Recipe](/cookbook/testing/widget/scrolling)

[Handle scrolling](/cookbook/testing/widget/scrolling)

[Learn how to scroll in widget tests.](/cookbook/testing/widget/scrolling)

[Recipe](/cookbook/testing/widget/orientation)

[App orientation](/cookbook/testing/widget/orientation)

[Learn how to check app orientation in widget tests.](/cookbook/testing/widget/orientation)

[Recipe](/cookbook/testing/widget/tap-drag)

[Tap, drag, and enter text](/cookbook/testing/widget/tap-drag)

[Interact with widgets in widget tests.](/cookbook/testing/widget/tap-drag)

[Recipe](/app-architecture/design-patterns/sql)

[Persistent storage architecture - SQL](/app-architecture/design-patterns/sql)

[Save complex application data to a user's device with SQL.](/app-architecture/design-patterns/sql)

[Recipe](/app-architecture/design-patterns/result)

[Error handling with Result objects](/app-architecture/design-patterns/result)

[Improve error handling across classes with Result objects.](/app-architecture/design-patterns/result)

[Recipe](/app-architecture/design-patterns/optimistic-state)

[Optimistic state](/app-architecture/design-patterns/optimistic-state)

[Improve the perception of responsiveness of an application by implementing optimistic state.](/app-architecture/design-patterns/optimistic-state)

[Recipe](/app-architecture/design-patterns/offline-first)

[Offline First](/app-architecture/design-patterns/offline-first)

[Implement offline-first support for one feature in an application.](/app-architecture/design-patterns/offline-first)

[Recipe](/app-architecture/design-patterns/key-value-data)

[Persistent storage architecture - Key-value data](/app-architecture/design-patterns/key-value-data)

[Save application data to a user's on-device key-value store.](/app-architecture/design-patterns/key-value-data)

[Recipe](/app-architecture/design-patterns/command)

[Command pattern](/app-architecture/design-patterns/command)

[Simplify view model logic by implementing a Command class.](/app-architecture/design-patterns/command)

[Quickstart](https://github.com/flutter/samples/tree/main/asset_transformation)

[Asset transformation](https://github.com/flutter/samples/tree/main/asset_transformation)

[Demonstrates how to transform images' color scales and formats.](https://github.com/flutter/samples/tree/main/asset_transformation)

[Quickstart](https://github.com/flutter/samples/tree/main/background_isolate_channels)

[Background isolate channels](https://github.com/flutter/samples/tree/main/background_isolate_channels)

[Demonstrates how to use long-lived isolates.](https://github.com/flutter/samples/tree/main/background_isolate_channels)

[Quickstart](https://github.com/flutter/samples/tree/main/desktop_photo_search)

[Desktop UI](https://github.com/flutter/samples/tree/main/desktop_photo_search)

[Demonstrates desktop features in both Material and FluentUI design systems.](https://github.com/flutter/samples/tree/main/desktop_photo_search)

[Quickstart](https://github.com/flutter/samples/tree/main/dynamic_theme)

[AI generated dynamic theme](https://github.com/flutter/samples/tree/main/dynamic_theme)

[Demonstrates how to call on-device Flutter APIs based on output from the Gemini API.](https://github.com/flutter/samples/tree/main/dynamic_theme)

[Quickstart](https://github.com/flutter/samples/tree/main/form_app)

[Form app](https://github.com/flutter/samples/tree/main/form_app)

[A sample demonstrating different types of forms and best practices.](https://github.com/flutter/samples/tree/main/form_app)

[Quickstart](https://github.com/flutter/samples/tree/main/gemini_tasks)

[AI todo list](https://github.com/flutter/samples/tree/main/gemini_tasks)

[A developer sample written in Flutter demonstrating how to interact with a to-do list in natural language using the Gemini API.](https://github.com/flutter/samples/tree/main/gemini_tasks)

[Quickstart](https://github.com/flutter/samples/tree/main/google_maps)

[Google Maps plugin](https://github.com/flutter/samples/tree/main/google_maps)

[Demonstrates the Google Maps for Flutter plugin.](https://github.com/flutter/samples/tree/main/google_maps)

[Quickstart](https://github.com/flutter/samples/tree/main/navigation_and_routing)

[Navigation and routing](https://github.com/flutter/samples/tree/main/navigation_and_routing)

[A sample that shows how to use \`go\_router\` API to handle common navigation scenarios.](https://github.com/flutter/samples/tree/main/navigation_and_routing)

[Quickstart](https://github.com/flutter/samples/tree/main/platform_design)

[Platform adaptive design](https://github.com/flutter/samples/tree/main/platform_design)

[This sample project shows a Flutter app that maximizes application code reuse while adhering to different design patterns on Android and iOS.](https://github.com/flutter/samples/tree/main/platform_design)

[Quickstart](https://github.com/flutter/samples/tree/main/simple_shader)

[Simple shaders](https://github.com/flutter/samples/tree/main/simple_shader)

[A simple Flutter fragment shaders project.](https://github.com/flutter/samples/tree/main/simple_shader)

[Quickstart](https://github.com/flutter/samples/tree/main/testing_app)

[Testing app](https://github.com/flutter/samples/tree/main/testing_app)

[A sample app that shows different types of testing in Flutter.](https://github.com/flutter/samples/tree/main/testing_app)

[Quickstart](https://github.com/flutter/samples/tree/main/web_embedding/element_embedding_demo)

[Web element embedding](https://github.com/flutter/samples/tree/main/web_embedding/element_embedding_demo)

[Modifies the index.html of a flutter app so it is launched in a custom hostElement. This is the most basic embedding example.](https://github.com/flutter/samples/tree/main/web_embedding/element_embedding_demo)

[Quickstart](https://github.com/flutter/samples/tree/main/web_embedding/ng-flutter)

[ng-flutter](https://github.com/flutter/samples/tree/main/web_embedding/ng-flutter)

[A simple Angular app (and component) that replicates the element embedding example, but in an Angular app.](https://github.com/flutter/samples/tree/main/web_embedding/ng-flutter)

[Quickstart](https://github.com/flutter/samples/tree/main/platform_channels)

[Platform channels](https://github.com/flutter/samples/tree/main/platform_channels)

[A sample Flutter app which demonstrates how to use \`MethodChannel\`, \`EventChannel\`, \`BasicMessageChannel\` and \`MessageCodec\`.](https://github.com/flutter/samples/tree/main/platform_channels)

Filter by

close

#### Subject

* AI
* Animation
* Architecture
* Cupertino
* Design
* Desktop
* Firebase
* Good for beginners
* Google APIs
* iOS
* Layout
* Material
* Routing and navigation
* State management
* Testing
* Web
* Widgets

#### Type

* Tutorial
* Sample code
* Workshop
* Recipe

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/reference/learning-resources.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/reference/learning-resources\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/reference/learning-resources.md "Report an issue with this page").

----
url: https://docs.flutter.dev/platform-integration/android/call-jetpack-apis.md
----

\# Calling JetPack APIs > Use the latest Android APIs from your Dart code

<!--?code-excerpt path-base="platform_integration"?-->

Flutter apps running on Android can always make use of the latest APIs on the first day they are released on Android, no matter what. This page outlines available ways to invoke Android-specific APIs. ## Use an existing solution In most scenarios, you can use a plugin (as shown in the next section) to invoke native APIs without writing any custom boilerplate or glue code yourself. ### Use a plugin Using a plugin is often the easiest way to access native APIs, regardless of where your Flutter app is running. To use plugins, visit \[pub.dev]\[pub] and search for the topic you need. Most native features, including accessing common hardware like GPS, the camera, or step counters are supported by robust plugins. For complete guidance on adding plugins to your Flutter app, see the \[Using packages documentation]\[packages]. \[packages]: /packages-and-plugins/using-packages \[pub]: https\://pub.dev Not all native features are supported by plugins, especially immediately after their release. In any scenario where your desired native feature is not covered by a package on \[pub.dev]\[pub], continue on to the following sections. ## Creating a custom solution Not all scenarios and APIs will be supported by existing solutions; but luckily, you can always add whatever support you need. The next sections describe two different ways to call native code from Dart. :::note Neither solution below is inherently better or worse than existing plugins, because all plugins use one of the following two options. ::: ### Call native code directly via FFI The most direct and efficient way to invoke native APIs is by calling the API directly, via FFI. This links your Dart executable to any specified native code at compile-time, allowing you to call it directly from the UI thread through a small amount of glue code. In most cases, \[ffigen]\[ffigen] or \[jnigen]\[jnigen] are helpful in writing this glue code. For complete guidance on directly calling native code from your Flutter app, see the \[FFI documentation]\[ffi]. In the coming months, the Dart team hopes to make this process easier with direct support for calling native APIs using the FFI approach, but without any need for the developer to write glue code. \[ffi]: https\://dart.dev/interop/c-interop \[ffigen]: https\://pub.dev/packages/ffigen \[jnigen]: https\://pub.dev/packages/jnigen ### Add a MethodChannel \[\`MethodChannel\`]\[methodchannels-api-docs]s are an alternate way Flutter apps can invoke arbitrary native code. Unlike the FFI solution described in the previous step, MethodChannels are always asynchronous, which might or might not matter to you, depending on your use case. As with FFI and direct calls to native code, using a \`MethodChannel\` requires a small amount of glue code to translate your Dart objects into native objects, and then back again. In most cases, \[\`pkg:pigeon\`]\[pigeon] is helpful in writing this glue code. For complete guidance on adding MethodChannels to your Flutter app, see the \[\`MethodChannel\`s documentation]\[methodchannels]. \[methodchannels]: /platform-integration/platform-channels \[methodchannels-api-docs]: https\://api.flutter.dev/flutter/services/MethodChannel-class.html \[pigeon]: https\://pub.dev/packages/pigeon

----
url: https://docs.flutter.dev/cookbook/testing/unit/mocking.md
----

\# Mock dependencies using Mockito > Use the Mockito package to mimic the behavior of services for testing.

<!--?code-excerpt path-base="cookbook/testing/unit/mocking"?-->

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 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 and 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 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. Write these alternative implementations by hand or make use of the \[Mockito package]\[] as a shortcut. This recipe demonstrates the basics of mocking with the Mockito package using the following steps: 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. For more information, see the \[Mockito package]\[] documentation. ## 1. Add the package dependencies To use the \`mockito\` package, add it to the \`pubspec.yaml\` file along with the \`flutter\_test\` dependency in the \`dev\_dependencies\` section. This example also uses the \`http\` package, so define that dependency in the \`dependencies\` section. \`mockito: 5.0.0\` supports Dart's null safety thanks to code generation. To run the required code generation, add the \`build\_runner\` dependency in the \`dev\_dependencies\` section. To add the dependencies, run \`flutter pub add\`: \`\`\`console $ flutter pub add http dev:mockito dev:build\_runner \`\`\` ## 2. Create a function to test In this example, unit test the \`fetchAlbum\` function from the \[Fetch data from the internet]\[] recipe. To test this function, make two changes: 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, 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. The function should now look like this:

<!--?code-excerpt "lib/main.dart (fetchAlbum)"?-->

\`\`\`dart Future fetchAlbum(http.Client client) async { final response = await client.get( Uri.parse('https\://jsonplaceholder.typicode.com/albums/1'), ); if (response.statusCode == 200) { // If the server did return a 200 OK response, // then parse the JSON. return Album.fromJson(jsonDecode(response.body) as Map); } else { // If the server did not return a 200 OK response, // then throw an exception. throw Exception('Failed to load album'); } } \`\`\` In your app code, you can provide an \`http.Client\` to the \`fetchAlbum\` method directly with \`fetchAlbum(http.Client())\`. \`http.Client()\` creates a default \`http.Client\`. ## 3. Create a test file with a mock \`http.Client\` Next, create a test file. Following the advice in the \[Introduction to unit testing]\[] recipe, create a file called \`fetch\_album\_test.dart\` in the root \`test\` folder. Add the annotation \`@GenerateMocks(\[], customMocks: \[MockSpec(as: #MockHttpClient)])\` to the main function to generate a \`MockHttpClient\` class with \`mockito\`. The generated \`MockHttpClient\` class implements the \`http.Client\` class. This allows you to pass the \`MockHttpClient\` to the \`fetchAlbum\` function, and return different http responses in each test. The generated mocks will be located in \`fetch\_album\_test.mocks.dart\`. Import this file to use them.

<!--?code-excerpt "test/fetch_album_test.dart (mockClient)" plaster="none"?-->

\`\`\`dart import 'package:http/http.dart' as http; import 'package:mocking/main.dart'; import 'package:mockito/annotations.dart'; // Generate a MockClient using the Mockito package. // Create new instances of this class in each test. // Note: Naming the generated mock \`MockHttpClient\` to avoid confusion with // \`MockClient\` from \`package:http/testing.dart\`. @GenerateMocks(\[], customMocks: \[MockSpec(as: #MockHttpClient)]) void main() { } \`\`\` Next, generate the mocks running the following command: \`\`\`console $ dart run build\_runner build \`\`\` ## 4. Write a test for each condition The \`fetchAlbum()\` function does one of two things: 1. Returns an \`Album\` if the http call succeeds 2. Throws an \`Exception\` if the http call fails Therefore, you want to test these two conditions. Use the \`MockHttpClient\` class to return an "Ok" response for the success test, and an error response for the unsuccessful test. Test these conditions using the \`when()\` function provided by Mockito:

<!--?code-excerpt "test/fetch_album_test.dart"?-->

\`\`\`dart import 'package:flutter\_test/flutter\_test.dart'; import 'package:http/http.dart' as http; import 'package:mocking/main.dart'; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; import 'fetch\_album\_test.mocks.dart'; // Generate a MockClient using the Mockito package. // Create new instances of this class in each test. // Note: Naming the generated mock \`MockHttpClient\` to avoid confusion with // \`MockClient\` from \`package:http/testing.dart\`. @GenerateMocks(\[], customMocks: \[MockSpec(as: #MockHttpClient)]) void main() { group('fetchAlbum', () { test('returns an Album if the http call completes successfully', () async { final client = MockHttpClient(); // Use Mockito to return a successful response when it calls the // provided http.Client. when( client.get(Uri.parse('https\://jsonplaceholder.typicode.com/albums/1')), ).thenAnswer( (\_) async => http.Response('{"userId": 1, "id": 2, "title": "mock"}', 200), ); expect(await fetchAlbum(client), isA()); }); test('throws an exception if the http call completes with an error', () { final client = MockHttpClient(); // Use Mockito to return an unsuccessful response when it calls the // provided http.Client. when( client.get(Uri.parse('https\://jsonplaceholder.typicode.com/albums/1')), ).thenAnswer((\_) async => http.Response('Not Found', 404)); expect(fetchAlbum(client), throwsException); }); }); } \`\`\` ## 5. Run the tests Now that you have a \`fetchAlbum()\` function with tests in place, run the tests. \`\`\`console $ flutter test test/fetch\_album\_test.dart \`\`\` You can also run tests inside your favorite editor by following the instructions in the \[Introduction to unit testing]\[] recipe. ## Complete example ##### lib/main.dart

<!--?code-excerpt "lib/main.dart"?-->

\`\`\`dart import 'dart:async'; import 'dart:convert'; import 'package:http/http.dart' as http; import 'package:material\_ui/material\_ui.dart'; Future fetchAlbum(http.Client client) async { final response = await client.get( Uri.parse('https\://jsonplaceholder.typicode.com/albums/1'), ); if (response.statusCode == 200) { // If the server did return a 200 OK response, // then parse the JSON. return Album.fromJson(jsonDecode(response.body) as Map); } else { // If the server did not return a 200 OK response, // then throw an exception. throw Exception('Failed to load album'); } } class Album { final int userId; final int id; final String title; const Album({required this.userId, required this.id, required this.title}); factory Album.fromJson(Map json) { return Album( userId: json\['userId'] as int, id: json\['id'] as int, title: json\['title'] as String, ); } } void main() => runApp(const MyApp()); class MyApp extends StatefulWidget { const MyApp({super.key}); @override State createState() => \_MyAppState(); } class \_MyAppState extends State { late final Future futureAlbum; @override void initState() { super.initState(); futureAlbum = fetchAlbum(http.Client()); } @override Widget build(BuildContext context) { return MaterialApp( title: 'Fetch Data Example', theme: ThemeData( colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), ), home: Scaffold( appBar: AppBar(title: const Text('Fetch Data Example')), body: Center( child: FutureBuilder( future: futureAlbum, builder: (context, snapshot) { if (snapshot.hasData) { return Text(snapshot.data!.title); } else if (snapshot.hasError) { return Text('${snapshot.error}'); } // By default, show a loading spinner. return const CircularProgressIndicator(); }, ), ), ), ); } } \`\`\` ##### test/fetch\_album\_test.dart

<!--?code-excerpt "test/fetch_album_test.dart"?-->

\`\`\`dart import 'package:flutter\_test/flutter\_test.dart'; import 'package:http/http.dart' as http; import 'package:mocking/main.dart'; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; import 'fetch\_album\_test.mocks.dart'; // Generate a MockClient using the Mockito package. // Create new instances of this class in each test. // Note: Naming the generated mock \`MockHttpClient\` to avoid confusion with // \`MockClient\` from \`package:http/testing.dart\`. @GenerateMocks(\[], customMocks: \[MockSpec(as: #MockHttpClient)]) void main() { group('fetchAlbum', () { test('returns an Album if the http call completes successfully', () async { final client = MockHttpClient(); // Use Mockito to return a successful response when it calls the // provided http.Client. when( client.get(Uri.parse('https\://jsonplaceholder.typicode.com/albums/1')), ).thenAnswer( (\_) async => http.Response('{"userId": 1, "id": 2, "title": "mock"}', 200), ); expect(await fetchAlbum(client), isA()); }); test('throws an exception if the http call completes with an error', () { final client = MockHttpClient(); // Use Mockito to return an unsuccessful response when it calls the // provided http.Client. when( client.get(Uri.parse('https\://jsonplaceholder.typicode.com/albums/1')), ).thenAnswer((\_) async => http.Response('Not Found', 404)); expect(fetchAlbum(client), throwsException); }); }); } \`\`\` ## Summary In this example, you've learned how to use Mockito to test functions or classes that depend on web services or databases. This is only a short introduction to the Mockito library and the concept of mocking. For more information, see the documentation provided by the \[Mockito package]\[]. \[Fetch data from the internet]: /cookbook/networking/fetch-data \[Introduction to unit testing]: /cookbook/testing/unit/introduction \[Mockito package]: https\://pub.dev/packages/mockito

----
url: https://docs.flutter.dev/app-architecture/design-patterns/offline-first.md
----

\# Offline-first support > Implement offline-first support for one feature in an application.

<!--?code-excerpt path-base="app-architecture/offline_first"?-->

An offline-first application is an app capable of offering most or all of its functionality while being disconnected from the internet. Offline-first applications usually rely on stored data to offer users temporary access to data that would otherwise only be available online. Some offline-first applications combine local and remote data seamlessly, while other applications inform the user when the application is using cached data. In the same way, some applications synchronize data in the background while others require the user to explicitly synchronize it. It all depends on the application requirements and the functionality it offers, and it’s up to the developer to decide which implementation fits their needs. In this guide, you will learn how to implement different approaches to offline-first applications in Flutter, following the \[Flutter Architecture guidelines]\[]. ## Offline-first architecture As explained in the common architecture concepts guide, repositories act as the single source of truth. They are responsible for presenting local or remote data, and should be the only place where data can be modified. In offline-first applications, repositories combine different local and remote data sources to present data in a single access point, independently of the connectivity state of the device. This example uses the \`UserProfileRepository\`, a repository that allows you to obtain and store \`UserProfile\` objects with offline-first support. The \`UserProfileRepository\` uses two different data services: one works with remote data, and the other works with a local database. The API client,\`ApiClientService\`, connects to a remote service using HTTP REST calls.

<!--?code-excerpt "lib/data/services/api_client_service.dart (ApiClientService)"?-->

\`\`\`dart class ApiClientService { /// performs GET network request to obtain a UserProfile Future getUserProfile() async { // ··· } /// performs PUT network request to update a UserProfile Future putUserProfile(UserProfile userProfile) async { // ··· } } \`\`\` The database service, \`DatabaseService\`, stores data using SQL, similar to the one found in the \[Persistent Storage Architecture: SQL]\[] recipe.

<!--?code-excerpt "lib/data/services/database_service.dart (DatabaseService)"?-->

\`\`\`dart class DatabaseService { /// Fetches the UserProfile from the database. /// Returns null if the user profile is not found. Future fetchUserProfile() async { // ··· } /// Update UserProfile in the database. Future updateUserProfile(UserProfile userProfile) async { // ··· } } \`\`\` This example also uses the \`UserProfile\` data class that has been created using the \[\`freezed\`]\[] package.

<!--?code-excerpt "lib/domain/model/user_profile.dart (UserProfile)" remove="@Default(false) bool synchronized,"?-->

\`\`\`dart @freezed abstract class UserProfile with \_$UserProfile { const factory UserProfile({ required String name, required String photoUrl, }) = \_UserProfile; } \`\`\` In apps that have complex data, such as when the remote data contains more fields than the needed by the UI, you might want to have one data class for the API and database services, and another for the UI. For example, \`UserProfileLocal\` for the database entity, \`UserProfileRemote\` for the API response object, and then \`UserProfile\` for the UI data model class. The \`UserProfileRepository\` would take care of converting from one to the other when necessary. This example also includes the \`UserProfileViewModel\`, a view model that uses the \`UserProfileRepository\` to display the \`UserProfile\` on a widget.

<!--?code-excerpt "lib/ui/user_profile/user_profile_viewmodel.dart (UserProfileViewModel)"?-->

\`\`\`dart class UserProfileViewModel extends ChangeNotifier { // ··· final UserProfileRepository \_userProfileRepository; UserProfile? get userProfile => \_userProfile; // ··· /// Load the user profile from the database or the network Future load() async { // ··· } /// Save the user profile with the new name Future save(String newName) async { // ··· } } \`\`\` ## Reading data Reading data is a fundamental part of any application that relies on remote API services. In offline-first applications, you want to ensure that the access to this data is as fast as possible, and that it doesn’t depend on the device being online to provide data to the user. This is similar to the \[Optimistic State design pattern]\[]. In this section, you will learn two different approaches, one that uses the database as a fallback, and one that combines local and remote data using a \`Stream\`. ### Using local data as a fallback As a first approach, you can implement offline support by having a fallback mechanism for when the user is offline or a network call fails. In this case, the \`UserProfileRepository\` attempts to obtain the \`UserProfile\` from the remote API server using the \`ApiClientService\`. If this request fails, then returns the locally stored \`UserProfile\` from the \`DatabaseService\`.

<!--?code-excerpt "lib/data/repositories/user_profile_repository.dart (getUserProfileFallback)" replace="/Fallback//g"?-->

\`\`\`dart Future getUserProfile() async { try { // Fetch the user profile from the API final apiUserProfile = await \_apiClientService.getUserProfile(); //Update the database with the API result await \_databaseService.updateUserProfile(apiUserProfile); return apiUserProfile; } catch (e) { // If the network call failed, // fetch the user profile from the database final databaseUserProfile = await \_databaseService.fetchUserProfile(); // If the user profile was never fetched from the API // it will be null, so throw an error if (databaseUserProfile != null) { return databaseUserProfile; } else { // Handle the error throw Exception('User profile not found'); } } } \`\`\` ### Using a Stream A better alternative presents the data using a \`Stream\`. In the best case scenario, the \`Stream\` emits two values, the locally stored data, and the data from the server. First, the stream emits the locally stored data using the \`DatabaseService\`. This call is generally faster and less error prone than a network call, and by doing it first the view model can already display data to the user. If the database does not contain any cached data, then the \`Stream\` relies completely on the network call, emitting only one value. Then, the method performs the network call using the \`ApiClientService\` to obtain up-to-date data. If the request was successful, it updates the database with the newly obtained data, and then yields the value to the view model, so it can be displayed to the user.

<!--?code-excerpt "lib/data/repositories/user_profile_repository.dart (getUserProfile)"?-->

\`\`\`dart Stream getUserProfile() async\* { // Fetch the user profile from the database final userProfile = await \_databaseService.fetchUserProfile(); // Returns the database result if it exists if (userProfile != null) { yield userProfile; } // Fetch the user profile from the API try { final apiUserProfile = await \_apiClientService.getUserProfile(); //Update the database with the API result await \_databaseService.updateUserProfile(apiUserProfile); // Return the API result yield apiUserProfile; } catch (e) { // Handle the error } } \`\`\` The view model must subscribe to this \`Stream\` and wait until it has completed. For that, call \`asFuture()\` with the \`Subscription\` object and await the result. For each obtained value, update the view model data and call \`notifyListeners()\` so the UI shows the latest data.

<!--?code-excerpt "lib/ui/user_profile/user_profile_viewmodel.dart (load)"?-->

\`\`\`dart Future load() async { await \_userProfileRepository .getUserProfile() .listen( (userProfile) { \_userProfile = userProfile; notifyListeners(); }, onError: (error) { // handle error }, ) .asFuture(); } \`\`\` ### Using only local data Another possible approach uses locally stored data for read operations. This approach requires that the data has been preloaded at some point into the database, and requires a synchronization mechanism that can keep the data up to date.

<!--?code-excerpt "lib/data/repositories/user_profile_repository.dart (getUserProfileLocal)" replace="/Local//g;/Read//g"?-->

\`\`\`dart Future getUserProfile() async { // Fetch the user profile from the database final userProfile = await \_databaseService.fetchUserProfile(); // Return the database result if it exists if (userProfile == null) { throw Exception('Data not found'); } return userProfile; } Future sync() async { try { // Fetch the user profile from the API final userProfile = await \_apiClientService.getUserProfile(); // Update the database with the API result await \_databaseService.updateUserProfile(userProfile); } catch (e) { // Try again later } } \`\`\` This approach can be useful for applications that don’t require data to be in sync with the server at all times. For example, a weather application where the weather data is only updated once a day. Synchronization could be done manually by the user, for example, a pull-to-refresh action that then calls the \`sync()\` method, or done periodically by a \`Timer\` or a background process. You can learn how to implement a synchronization task in the section about synchronizing state. ## Writing data Writing data in offline-first applications depends fundamentally on the application use case. Some applications might require the user input data to be immediately available on the server side, while other applications might be more flexible and allow data to be out-of-sync temporarily. This section explains two different approaches for implementing writing data in offline-first applications. ### Online-only writing One approach for writing data in offline-first applications is to enforce being online to write data. While this might sound counterintuitive, this ensures that the data the user has modified is fully synchronized with the server, and the application doesn’t have a different state than the server. In this case, you first attempt to send the data to the API service, and if the request succeeds, then store the data in the database.

<!--?code-excerpt "lib/data/repositories/user_profile_repository.dart (updateUserProfileOnline)" replace="/Online//g"?-->

\`\`\`dart Future updateUserProfile(UserProfile userProfile) async { try { // Update the API with the user profile await \_apiClientService.putUserProfile(userProfile); // Only if the API call was successful // update the database with the user profile await \_databaseService.updateUserProfile(userProfile); } catch (e) { // Handle the error } } \`\`\` The disadvantage in this case is that the offline-first functionality is only available for read operations, but not for write operations, as those require the user being online. ### Offline-first writing The second approach works the other way around. Instead of performing the network call first, the application first stores the new data in the database, and then attempts to send it to the API service once it has been stored locally.

<!--?code-excerpt "lib/data/repositories/user_profile_repository.dart (updateUserProfileOffline)" replace="/Offline//g"?-->

\`\`\`dart Future updateUserProfile(UserProfile userProfile) async { // Update the database with the user profile await \_databaseService.updateUserProfile(userProfile); try { // Update the API with the user profile await \_apiClientService.putUserProfile(userProfile); } catch (e) { // Handle the error } } \`\`\` This approach allows users to store data locally even when the application is offline, however, if the network call fails, the local database and the API service are no longer in sync. In the next section, you will learn different approaches to handle synchronization between local and remote data. ## Synchronizing state Keeping the local and remote data in sync is an important part of offline-first applications, as the changes that have been done locally need to be copied to the remote service. The app must also ensure that, when the user goes back to the application, the locally stored data is the same as in the remote service. ### Writing a synchronization task There are different approaches for implementing synchronization in a background task. A simple solution is to create a \`Timer\` in the \`UserProfileRepository\` that runs periodically, for example every five minutes.

<!--?code-excerpt "lib/data/repositories/user_profile_repository.dart (Timer)"?-->

\`\`\`dart Timer.periodic(const Duration(minutes: 5), (timer) => sync()); \`\`\` The \`sync()\` method then fetches the \`UserProfile\` from the database, and if it requires synchronization, it is then sent to the API service.

<!--?code-excerpt "lib/data/repositories/user_profile_repository.dart (sync)"?-->

\`\`\`dart Future sync() async { try { // Fetch the user profile from the database final userProfile = await \_databaseService.fetchUserProfile(); // Check if the user profile requires synchronization if (userProfile == null || userProfile.synchronized) { return; } // Update the API with the user profile await \_apiClientService.putUserProfile(userProfile); // Set the user profile as synchronized await \_databaseService.updateUserProfile( userProfile.copyWith(synchronized: true), ); } catch (e) { // Try again later } } \`\`\` A more complex solution uses background processes like the \[\`workmanager\`]\[] plugin. This allows your application to run the synchronization process in the background even when the application is not running. :::note Running background operations continuously can drain the device battery dramatically, and some devices limit the background processing capabilities, so this approach needs to be tuned to the application requirements and one solution might not fit all cases. ::: It’s also recommended to only perform the synchronization task when the network is available. For example, you can use the \[\`connectivity\_plus\`]\[] plugin to check if the device is connected to WiFi. You can also use \[\`battery\_plus\`]\[] to verify that the device is not running low on battery. In the previous example, the synchronization task runs every 5 minutes. In some cases, that might be excessive, while in others it might not be frequent enough. The actual synchronization period time for your application depends on your application needs and it’s something you will have to decide. ### Storing a synchronization flag To know if the data requires synchronization, add a flag to the data class indicating if the changes need to be synchronized. For example, \`bool synchronized\`:

<!--?code-excerpt "lib/domain/model/user_profile.dart (UserProfile)"?-->

\`\`\`dart @freezed abstract class UserProfile with \_$UserProfile { const factory UserProfile({ required String name, required String photoUrl, @Default(false) bool synchronized, }) = \_UserProfile; } \`\`\` Your synchronization logic should attempt to send it to the API service only when the \`synchronized\` flag is \`false\`. If the request is successful, then change it to \`true\`. ### Pushing data from server A different approach for synchronization is to use a push service to provide up-to-date data to the application. In this case, the server notifies the application when data has changed, instead of being the application asking for updates. For example, you can use \[Firebase messaging]\[], to push small payloads of data to the device, as well as trigger synchronization tasks remotely using background messages. Instead of having a synchronization task running in the background, the server notifies the application when the stored data needs to be updated with a push notification. You can combine both approaches together, having a background synchronization task and using background push messages, to keep the application database synchronized with the server. ## Putting it all together Writing an offline-first application requires making decisions regarding the way read, write and sync operations are implemented, which depend on the requirements from the application you are developing. The key takeaways are: - When reading data, you can use a \`Stream\` to combine locally stored data with remote data. - When writing data, decide if you need to be online or offline, and if you need synchronizing data later or not. - When implementing a background sync task, take into account the device status and your application needs, as different applications may have different requirements. \[Flutter Architecture guidelines]:/app-architecture \[Persistent Storage Architecture: SQL]:/app-architecture/design-patterns/sql \[\`freezed\`]:https\://pub.dev/packages/freezed \[Optimistic State design pattern]:/app-architecture/design-patterns/optimistic-state \[\`workmanager\`]:https\://pub.dev/packages/workmanager \[\`connectivity\_plus\`]:https\://pub.dev/packages/connectivity\_plus \[\`battery\_plus\`]:https\://pub.dev/packages/battery\_plus \[Firebase messaging]:https\://firebase.google.com/docs/cloud-messaging/flutter/client

----
url: https://docs.flutter.dev/cookbook/games/achievements-leaderboard.md
----

\# Add achievements and leaderboards to your mobile game > How to use the games\_services plugin to add functionality to your game.

<!--?code-excerpt path-base="cookbook/games/achievements_leaderboards"?-->

Gamers have various motivations for playing games. In broad strokes, there are four major motivations: \[immersion, achievement, cooperation, and competition]\[]. No matter the game you build, some players want to \*achieve\* in it. This could be trophies won or secrets unlocked. Some players want to \*compete\* in it. This could be hitting high scores or accomplishing speedruns. These two ideas map to the concepts of \*achievements\* and \*leaderboards\*. !\[A simple graphic representing the four types of motivation explained above]\(/assets/images/docs/cookbook/types-of-gamer-motivations.png){:.site-illustration} Ecosystems such as the App Store and Google Play provide centralized services for achievements and leaderboards. Players can view achievements from all their games in one place and developers don't need to re-implement them for every game. This recipe demonstrates how to use the \[\`games\_services\` package]\[] to add achievements and leaderboard functionality to your mobile game. \[\`games\_services\` package]: https\://pub.dev/packages/games\_services \[immersion, achievement, cooperation, and competition]: https\://meditations.metavert.io/p/game-player-motivations ## 1. Enable platform services To enable games services, set up \*Game Center\* on iOS and \*Google Play Games Services\* on Android. ### iOS To enable Game Center (GameKit) on iOS: 1. Open your Flutter project in Xcode. Open \`ios/Runner.xcworkspace\` 2. Select the root \*\*Runner\*\* project. 3. Go to the \*\*Signing & Capabilities\*\* tab. 4. Click the \`+\` button to add \*\*Game Center\*\* as a capability. 5. Close Xcode. 6. If you haven't already, register your game in \[App Store Connect]\[] and from the \*\*My App\*\* section press the \`+\` icon. !\[Screenshot of the + button in App Store Connect]\(/assets/images/docs/cookbook/app-store-add-app-button.png) 7. Still in App Store Connect, look for the \*Game Center\* section. You can find it in \*\*Services\*\* as of this writing. On the \*\*Game Center\*\* page, you might want to set up a leaderboard and several achievements, depending on your game. Take note of the IDs of the leaderboards and achievements you create. \[App Store Connect]: https\://appstoreconnect.apple.com/ ### Android To enable \*Play Games Services\* on Android: 1. If you haven't already, go to \[Google Play Console]\[] and register your game there. !\[Screenshot of the 'Create app' button in Google Play Console]\(/assets/images/docs/cookbook/google-play-create-app.png) 2. Still in Google Play Console, select \*Play Games Services\* → \*Setup and management\* → \*Configuration\* from the navigation menu and follow their instructions. \* This takes a significant amount of time and patience. Among other things, you'll need to set up an OAuth consent screen in Google Cloud Console. If at any point you feel lost, consult the official \[Play Games Services guide]\[]. !\[Screenshot showing the Games Services section in Google Play Console]\(/assets/images/docs/cookbook/play-console-play-games-services.png) 3. When done, you can start adding leaderboards and achievements in \*\*Play Games Services\*\* → \*\*Setup and management\*\*. Create the exact same set as you did on the iOS side. Make note of IDs. 4. Go to \*\*Play Games Services → Setup and management → Publishing\*\*. 5. Click \*\*Publish\*\*. Don't worry, this doesn't actually publish your game. It only publishes the achievements and leaderboard. Once a leaderboard, for example, is published this way, it cannot be unpublished. 6. Go to \*\*Play Games Services\*\* \*\*→ Setup and management → Configuration → Credentials\*\*. 7. Find the \*\*Get resources\*\* button. It returns an XML file with the Play Games Services IDs. \`\`\`xml

<!--?xml version="1.0" encoding="utf-8"?-->

424242424242 dev.flutter.tictactoe sOmEiDsTrInG sOmEiDsTrInG \`\`\` 8. Add a file at \`android/app/src/main/res/values/games-ids.xml\` containing the XML you received in the previous step. \[Google Play Console]: https\://play.google.com/console/ \[Play Games Services guide]: https\://developers.google.com/games/services/console/enabling ## 2. Sign in to the game service Now that you have set up \*Game Center\* and \*Play Games Services\*, and have your achievement & leaderboard IDs ready, it's finally Dart time. 1. Add a dependency on the \[\`games\_services\` package]\(https\://pub.dev/packages/games\_services). \`\`\`console $ flutter pub add games\_services \`\`\` 2. Before you can do anything else, you have to sign the player into the game service.

<!--?code-excerpt "lib/various.dart (signIn)"?-->

\`\`\`dart try { await GamesServices.signIn(); } on PlatformException catch (e) { // ... deal with failures ... } \`\`\` The sign in happens in the background. It takes several seconds, so don't call \`signIn()\` before \`runApp()\` or the players will be forced to stare at a blank screen every time they start your game. The API calls to the \`games\_services\` API can fail for a multitude of reasons. Therefore, every call should be wrapped in a try-catch block as in the previous example. The rest of this recipe omits exception handling for clarity. :::tip It's a good practice to create a controller. This would be a \`ChangeNotifier\`, a bloc, or some other piece of logic that wraps around the raw functionality of the \`games\_services\` plugin. ::: ## 3. Unlock achievements 1. Register achievements in Google Play Console and App Store Connect, and take note of their IDs. Now you can award any of those achievements from your Dart code:

<!--?code-excerpt "lib/various.dart (unlock)"?-->

\`\`\`dart await GamesServices.unlock( achievement: Achievement( androidID: 'your android id', iOSID: 'your ios id', ), ); \`\`\` The player's account on Google Play Games or Apple Game Center now lists the achievement. 2. To display the achievements UI from your game, call the \`games\_services\` API:

<!--?code-excerpt "lib/various.dart (showAchievements)"?-->

\`\`\`dart await GamesServices.showAchievements(); \`\`\` This displays the platform achievements UI as an overlay on your game. 3. To display the achievements in your own UI, use \[\`GamesServices.loadAchievements()\`]\[]. \[\`GamesServices.loadAchievements()\`]: https\://pub.dev/documentation/games\_services/latest/games\_services/GamesServices/loadAchievements.html ## 4. Submit scores When the player finishes a play-through, your game can submit the result of that play session into one or more leaderboards. For example, a platformer game like Super Mario can submit both the final score and the time taken to complete the level, to two separate leaderboards. 1. In the first step, you registered a leaderboard in Google Play Console and App Store Connect, and took note of its ID. Using this ID, you can submit new scores for the player:

<!--?code-excerpt "lib/various.dart (submitScore)"?-->

\`\`\`dart await GamesServices.submitScore( score: Score( iOSLeaderboardID: 'some\_id\_from\_app\_store', androidLeaderboardID: 'sOmE\_iD\_fRoM\_gPlAy', value: 100, ), ); \`\`\` You don't need to check whether the new score is the player's highest. The platform game services handle that for you. 2. To display the leaderboard as an overlay over your game, make the following call:

<!--?code-excerpt "lib/various.dart (showLeaderboards)"?-->

\`\`\`dart await GamesServices.showLeaderboards( iOSLeaderboardID: 'some\_id\_from\_app\_store', androidLeaderboardID: 'sOmE\_iD\_fRoM\_gPlAy', ); \`\`\` 3. If you want to display the leaderboard scores in your own UI, you can fetch them with \[\`GamesServices.loadLeaderboardScores()\`]\[]. \[\`GamesServices.loadLeaderboardScores()\`]: https\://pub.dev/documentation/games\_services/latest/games\_services/GamesServices/loadLeaderboardScores.html ## 5. Next steps There's more to the \`games\_services\` plugin. With this plugin, you can: - Get the player's icon, name or unique ID - Save and load game states - Sign out of the game service Some achievements can be incremental. For example: "You have collected all 10 pieces of the McGuffin." Each game has different needs from game services. To start, you might want to create this controller in order to keep all achievements & leaderboards logic in one place:

<!--?code-excerpt "lib/games_services_controller.dart"?-->

\`\`\`dart import 'dart:async'; import 'package:games\_services/games\_services.dart'; import 'package:logging/logging.dart'; /// Allows awarding achievements and leaderboard scores, /// and also showing the platforms' UI overlays for achievements /// and leaderboards. /// /// A facade of \`package:games\_services\`. class GamesServicesController { static final Logger \_log = Logger('GamesServicesController'); final Completer \_signedInCompleter = Completer(); Future get signedIn => \_signedInCompleter.future; /// Unlocks an achievement on Game Center / Play Games. /// /// You must provide the achievement ids via the \[iOS] and \[android] /// parameters. /// /// Does nothing when the game isn't signed into the underlying /// games service. Future awardAchievement({ required String iOS, required String android, }) async { if (!await signedIn) { \_log.warning('Trying to award achievement when not logged in.'); return; } try { await GamesServices.unlock( achievement: Achievement(androidID: android, iOSID: iOS), ); } catch (e) { \_log.severe('Cannot award achievement: $e'); } } /// Signs into the underlying games service. Future initialize() async { try { await GamesServices.signIn(); // The API is unclear so we're checking to be sure. The above call // returns a String, not a boolean, and there's no documentation // as to whether every non-error result means we're safely signed in. final signedIn = await GamesServices.isSignedIn; \_signedInCompleter.complete(signedIn); } catch (e) { \_log.severe('Cannot log into GamesServices: $e'); \_signedInCompleter.complete(false); } } /// Launches the platform's UI overlay with achievements. Future showAchievements() async { if (!await signedIn) { \_log.severe('Trying to show achievements when not logged in.'); return; } try { await GamesServices.showAchievements(); } catch (e) { \_log.severe('Cannot show achievements: $e'); } } /// Launches the platform's UI overlay with leaderboard(s). Future showLeaderboard() async { if (!await signedIn) { \_log.severe('Trying to show leaderboard when not logged in.'); return; } try { await GamesServices.showLeaderboards( // TODO: When ready, change both these leaderboard IDs. iOSLeaderboardID: 'some\_id\_from\_app\_store', androidLeaderboardID: 'sOmE\_iD\_fRoM\_gPlAy', ); } catch (e) { \_log.severe('Cannot show leaderboard: $e'); } } /// Submits \[score] to the leaderboard. Future submitLeaderboardScore(int score) async { if (!await signedIn) { \_log.warning('Trying to submit leaderboard when not logged in.'); return; } \_log.info('Submitting $score to leaderboard.'); try { await GamesServices.submitScore( score: Score( // TODO: When ready, change these leaderboard IDs. iOSLeaderboardID: 'some\_id\_from\_app\_store', androidLeaderboardID: 'sOmE\_iD\_fRoM\_gPlAy', value: score, ), ); } catch (e) { \_log.severe('Cannot submit leaderboard score: $e'); } } } \`\`\` ## More information The Flutter Casual Games Toolkit includes the following templates: \* \[basic]\[]: basic starter game \* \[card]\[]: starter card game \* \[endless runner]\[]: starter game (using Flame) where the player endlessly runs, avoiding pitfalls and gaining rewards \[basic]: https\://github.com/flutter/games/tree/main/templates/basic#readme \[card]: https\://github.com/flutter/games/tree/main/templates/card#readme \[endless runner]: https\://github.com/flutter/games/tree/main/templates/endless\_runner#readme

----
url: https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.28.2.md
----

\# DevTools 2.28.2 release notes > Release notes for Dart and Flutter DevTools version 2.28.2. The 2.28.2 release of the Dart and Flutter DevTools includes the following changes among other general improvements. To learn more about DevTools, check out the \[DevTools overview]\(https\://docs.flutter.dev/tools/devtools). This was a cherry-pick release on top of DevTools 2.28.1. To learn about the improvements included in DevTools 2.28.1, please read the \[release notes]\(/tools/devtools/release-notes/release-notes-2.28.1). ## DevTools Extension updates \* Enabled DevTools extensions when debugging a Dart entry point that is not under \`lib\` (e.g. a unit test or integration test). Thanks to \[@bartekpacia]\(https\://github.com/bartekpacia) for this change! - \[#6644]\(https\://github.com/flutter/devtools/pull/6644) ## Full commit history To find a complete list of changes in this release, check out the \[DevTools git log]\(https\://github.com/flutter/devtools/tree/v2.28.2).

----
url: https://docs.flutter.dev/platform-integration/ios/app-extensions.md
----

\# Adding iOS app extensions > Learn how to add app extensions to your Flutter apps This guide shows you how to use iOS app extensions with a Flutter app. ## Overview {: #overview } \[iOS app extensions]\[] allow you to expand functionality outside of your iOS app. Your app could appear as a home screen widget, or you can make portions of your app available within other apps. In the following example, when a user selects a photo to share in the iOS Photo app, a Flutter app called \`Example App With Extension\` is displayed in the Photo apps share sheet:\[iOS app extensions]: https\://developer.apple.com/app-extensions/ ## Add an iOS app extension to your Flutter app {: #add-extension } If you want to integrate your Flutter app with the iOS operating system, you can add iOS app extensions to your Flutter project. For a seamless workflow, the following steps show how to add a \[Share]\[] app extension to a new Flutter app called \`example\_app\_with\_extension\`, but you can always start with an existing project. 1. In the console, create a new Flutter project called \`example\_app\_with\_extension\`. \`\`\`console $ flutter create example\_app\_with\_extension \`\`\` 1. In the console, open the Xcode workspace for the \`example\_app\_with\_extension\` project. \`\`\`console $ cd example\_app\_with\_extension && open ios/Runner.xcworkspace \`\`\` 1. In Xcode, add an app extension called \`Share\` and call it \`ShareExtension\`. \* In the Xcode menu bar, select \*\*File\*\* > \*\*New\*\* > \*\*Target\*\*. \* Add \*\*Share Extension\*\*. \* In the \*\*Name field\*\*, enter \*\*ShareExtension\*\*. \* Click \*\*Finish\*\*. \* In the \*\*Activate … Scheme\*\* dialog box that appears, select \*\*Activate\*\*. 1. In Xcode, change the order of the build process. \* Open the \*\*project navigator\*\* (\*\*View\*\* > \*\*Navigators\*\* > \*\*Project\*\*). \* In the \*\*project navigator\*\*, at the top, select \*\*Runner\*\*. \* In the main window under \*\*TARGETS\*\*, select \*\*Runner\*\*. \* Open the \*\*Build Phases\*\* tab. \* Drag \*\*Embed Foundation Extensions\*\* above \*\*Run Script\*\*. 1. Make sure your \*\*Minimum Deployments\*\* iOS value is properly set and matches in both \*\*Runner\*\* and \*\*ShareExtension\*\* \* Open the \*\*project navigator\*\* (\*\*View\*\* > \*\*Navigators\*\* > \*\*Project\*\*). \* In the \*\*project navigator\*\*, at the top, select \*\*Runner\*\*. \* In the main window under \*\*TARGETS\*\*, select \*\*Runner\*\*. \* On the \*\*General\*\* tab check your \*\*Minimum Deployments\*\* dropdown value to match the one you have on \*\*ShareExtension\*\* > \*\*General\*\* tab. 1. In the console, run the following command to rebuild your iOS app: \`\`\`console $ flutter build ios --config-only \`\`\` 1. \[Test your app with the simulator]\[]. When you add a new app extension, Xcode generates sample code based on the template you selected. For more information about the generated code and WidgetKit, see \[Apple's app extension documentation]\[]. \[Apple's app extension documentation]: https\://developer.apple.com/app-extensions/ \[Test your app with the simulator]: #test-extensions \[Share]: https\://developer.apple.com/library/archive/documentation/General/Conceptual/ExtensibilityPG/Share.html ## Test an iOS app extension {: #test-extensions } After you've added an app extension to your Flutter project, you can test it, using a simulator or physical device. If you are testing you extension in debug mode, you must use the iOS simulator. The following steps assume you're using the sample application and Share extension from \[Adding iOS app extensions]\[]. 1. In Xcode, \[add an app extension to your project]\[]. 1. In the console, use the following command to run your iOS app: \`\`\`console $ flutter run \`\`\` 1. In the simulator, test your app extension. \* Launch an app that supports the Share extension, such as the Photos app. \* Select a photo, tap the share button, then tap on the share extension icon of your app. 1. Add an app extension to your project. 1. In the console, run your Flutter app in release mode: \`\`\`console $ flutter run --release \`\`\` 1. On your device, test your app extension. \* Launch an app that supports the Share extension, such as the Photos app. \* Select a photo, tap the share button, then tap on the share extension icon of your app. \[Adding iOS app extensions]: #add-extension \[add an app extension to your project]: #add-extension ## Additional ways to interact with iOS app extensions {: #interact-app-extensions } Flutter apps interact with iOS app extensions using the same techniques as UIKit or SwiftUI apps. The containing app and the app extension don't communicate directly. The containing app might not be running while the device user interacts with the extension. The app and your extension can read and write to shared resources or use higher-level APIs to communicate with each other. ### Use higher-level APIs {: #using-higher-level-apis } Some extensions have APIs. For example, the \[Core Spotlight]\[] framework indexes your app, allowing users to search from Spotlight and Safari. The \[WidgetKit]\[] framework can trigger an update of your home screen widget. To simplify how your app communicates with extensions, Flutter plugins wrap these APIs. To find plugins that wrap extension APIs, check out \[Leveraging Apple's System APIs and Frameworks]\[leverage] or search \[pub.dev]\[]. \[Core Spotlight]: https\://developer.apple.com/documentation/corespotlight \[leverage]: /platform-integration/ios/apple-frameworks \[pub.dev]: https\://pub.dev/packages \[WidgetKit]: https\://developer.apple.com/documentation/widgetkit ### Share resources {: #sharing-resources } To share resources between your Flutter app and your app extension, put the \`Runner\` app target and the extension target in the same \[App Group]\[]. :::note You must be signed in to your Apple Developer account. ::: To add a target to an App Group: 1. Open the target settings in Xcode. 1. Navigate to the \*\*Signing & Capabilities\*\* tab. 1. Select \*\*+ Capability\*\* then \*\*App Groups\*\*. 1. Choose which App Group you want to add the target from one of two options: {: type="a"} 1. Select an App Group from the list. 1. Click \*\*+\*\* to add a new App Group. When two targets belong to the same App Group, they can read from and write to the same source. Choose one of the following sources for your data. \* \*\*Key/value:\*\* Use the \[\`shared\_preference\_app\_group\`]\[] plugin to read or write to \`UserDefaults\` within the same App Group. \* \*\*File:\*\* Use the App Group container path from the \[\`path\_provider\`]\[] plugin to \[read and write files]\[]. \* \*\*Database:\*\* Use the App Group container path from the \[\`path\_provider\`]\[] plugin to create a database with the \[\`sqflite\`]\[] plugin. \[App Group]: https\://developer.apple.com/documentation/xcode/configuring-app-groups \[\`path\_provider\`]: https\://pub.dev/packages/path\_provider \[read and write files]: /cookbook/persistence/reading-writing-files \[\`shared\_preference\_app\_group\`]: https\://pub.dev/packages/shared\_preference\_app\_group \[\`sqflite\`]: https\://pub.dev/packages/sqflite ### Schedule background updates {: #background-updates } Background tasks provide a means to update your extension through code regardless of the status of your app. To schedule background work from your Flutter app, use the \[\`workmanager\`]\[] plugin. \[\`workmanager\`]: https\://pub.dev/packages/workmanager ### Add deep linking {: #deep-linking } You might want to direct users from an app extension to a specific page in your Flutter app. To open a specific route in your app, you can use \[Deep Linking]\[]. \[Deep Linking]:/ui/navigation/deep-linking ### Add a scrollable list {: #advanced-scrolling-behavior } By default, flutter view does not handle scroll gestures in a \[Share]\[] extension. To support a scrollable list in the Share extension, follow \[the instructions on GitHub]\[issue-164670]. \[Share]: https\://developer.apple.com/library/archive/documentation/General/Conceptual/ExtensibilityPG/Share.html \[issue-164670]: https\://github.com/flutter/flutter/issues/164670#issuecomment-2762124121 ### Open a Flutter app in an iOS app extension {: #creating-app-extension-uis-with-flutter } You can open your Flutter app directly in some iOS app extensions, such as the \[Share]\[] extension, with a \`FlutterViewController\`. In the following example, a Flutter app called \`Example App With Extension\` is opened in the Share extension, which lets users share content between apps:Use the following steps to display a Flutter app inside of a \[Share]\[] app extension. In this example the app extension scheme is called \`ShareExtension\`, the Flutter app scheme is called \`Runner\`, and the Flutter app is called \`Example App With Extension\`: 1. \[Add an extension to your Flutter app]\[] if you haven't already done so. 1. In the console, navigate to your Flutter project directory and then open your project in Xcode with the following command: \`\`\`console open ios/Runner.xcworkspace \`\`\` 1. In Xcode, disable user script sandboxing. \* Open the \*\*project navigator\*\* (\*\*View\*\* > \*\*Navigators\*\* > \*\*Project\*\*). \* In the main window under \*\*TARGETS\*\*, select \*\*ShareExtension\*\*. \* Open the \*\*Build Settings\*\* tab. \* Navigate to \*\*Build Options\*\*. \* Set \*\*User Script Sandboxing\*\* as \*\*No\*\*. 1. In Xcode, add the pre-action to the \`ShareExtension\` scheme. \* Open the \*\*Manage Schemes\*\* window (\*\*Product\*\* > \*\*Scheme\*\* > \*\*Manage Schemes\*\*). \* Select the \*\*ShareExtension\*\* scheme and edit it. \* Expand the \*\*Build\*\* tab. \* Select \*\*Pre-actions\*\*. \* Click \*\*+\*\* and select \*\*New Run Script Action\*\*. \* In the \*\*Provide build settings from\*\* drop-down list, select \*\*ShareExtension\*\*. \* In the \*\*Shell\*\* text field, enter: \`\`\`console /bin/sh "$FLUTTER\_ROOT/packages/flutter\_tools/bin/xcode\_backend.sh" prepare \`\`\` \* Click \*\*Close\*\*. 1. In Xcode, share the build configurations. \* Open the \*\*project navigator\*\* (\*\*View\*\* > \*\*Navigators\*\* > \*\*Project\*\*). \* In the main window under \*\*PROJECT\*\*, select \*\*Runner\*\*. \* Open the \*\*Info\*\* tab. \* Expand \*\*Configuration\*\*. \* Expand \*\*Debug\*\* and update the value for \*\*ShareExtension\*\* to match the value for \*\*Runner\*\*. \* Repeat the previous step for \*\*Profile\*\*, and \*\*Release\*\*. \* When you are finished, make sure that the configurations look similar to the following: !\[Xcode configurations]\(/assets/images/docs/development/platform-integration/app-extensions/xcode-configurations.png) 1. (Optional) In Xcode, replace any storyboard files with an extension class, if needed. \* Open the \*\*project navigator\*\* (\*\*View\*\* > \*\*Navigators\*\* > \*\*Project\*\*). \* Select \*\*Runner\*\* > \*\*ShareExtension\*\* > \*\*Info\*\*. \* Expand \*\*Information Property List\*\*. \* Delete the \*\*NSExtensionMainStoryboard\*\* key. \* Add the \*\*NSExtensionPrincipalClass\*\* key. \* Add one of these values for the \`NSExtensionPrincipalClass\` key: \* (Swift) \*\*ShareExtension.ShareViewController\*\* \* (Objective-C) \*\*ShareViewController\*\* 1. In Xcode, update the \`ShareViewController\` to use the \`FlutterViewController\`. \* Open the \*\*project navigator\*\* (\*\*View\*\* > \*\*Navigators\*\* > \*\*Project\*\*). \* Select \*\*Runner\*\* > \*\*ShareExtension\*\* > \*\*ShareViewController\*\*. \* Update \`ShareViewController\` to use the \`FlutterViewController\` class: \`\`\`swift title="ShareViewController.swift" import UIKit import Flutter class ShareViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() showFlutter() } func showFlutter() { let flutterEngine = FlutterEngine(name: "my flutter engine") flutterEngine.run() let flutterViewController = FlutterViewController(engine: flutterEngine, nibName: nil, bundle: nil) addChild(flutterViewController) view\.addSubview(flutterViewController.view) flutterViewController.view\.frame = view\.bounds } override func viewDidDisappear(\_ animated: Bool) { super.viewDidDisappear(animated) self.extensionContext?.cancelRequest( withError: NSError(domain: Bundle.main.bundleIdentifier!, code: 0)) } } \`\`\` \`\`\`objc title="ShareViewController.h" @import Flutter; @import UIKit; @interface ShareViewController : UIViewController @end \`\`\` \`\`\`objc title="ShareViewController.m" #import "ShareViewController.h" @import Flutter; @implementation ShareViewController - (void)viewDidLoad { \[super viewDidLoad]; \[self showFlutter]; } - (void)showFlutter { FlutterEngine \*flutterEngine = \[\[FlutterEngine alloc] initWithName:@"my flutter engine"]; \[flutterEngine run]; FlutterViewController \*flutterViewController = \[\[FlutterViewController alloc] initWithEngine:flutterEngine nibName:nil bundle:nil]; \[self addChildViewController:flutterViewController]; \[self.view addSubview:flutterViewController.view]; flutterViewController.view\.frame = self.view\.bounds; } - (void)viewDidDisappear:(BOOL)animated { \[super viewDidDisappear:animated]; \[self.extensionContext cancelRequestWithError:\[NSError errorWithDomain:NSBundle.mainBundle.bundleIdentifier code:0 userInfo:nil]]; } @end \`\`\` 8. \[Test your app with the simulator]\[]. \[Add an extension to your Flutter app]: #add-extension \[Share]: https\://developer.apple.com/library/archive/documentation/General/Conceptual/ExtensibilityPG/Share.html \[Test your app with the simulator]: #test-extensions ### Register plugins Use the following steps to register plugins for an app extension. In this example, the app extension scheme is called \`ShareExtension\`, the Flutter app scheme is called \`Runner\`, and the Flutter app is called \`Example App With Extension\`: 1. \[Add an extension to your Flutter app]\[] if you haven't already done so. 1. In Xcode, add \`GeneratedPluginRegistrant.m\` to the app extension target. \* Open the \*\*project navigator\*\* (\*\*View\*\* > \*\*Navigators\*\* > \*\*Project\*\*). \* In the main window under \*\*TARGETS\*\*, select \*\*ShareExtension\*\*. \* Open the \*\*Build Phases\*\* tab. \* Expand \*\*Compile Sources\*\*. \* Click \*\*+\*\*. \* From the list in the \_Choose item to add\_ dialog box, select \*\*GeneratedPluginRegistrant.m\*\*. \* Click \*\*Add\*\*. 1. (Swift only) In Xcode, update the \`SWIFT\_OBJC\_BRIDGING\_HEADER\` build setting. \* Open the \*\*project navigator\*\* (\*\*View\*\* > \*\*Navigators\*\* > \*\*Project\*\*). \* In the main window under \*\*TARGETS\*\*, select \*\*ShareExtension\*\*. \* Open the \*\*Build Settings\*\* tab. \* Select the \*\*All\*\* filter. \* Navigate to \*\*Swift Compiler - General\*\* and change the value for the \*\*Objective-C Bridging Header\*\* key to \*\*Runner/Runner-Bridging-Header.h\*\*. 1. In Xcode, update the code for \`ShareViewController\` to register \`GeneratedPluginRegistrant.h\`. \* Open the \*\*project navigator\*\* (\*\*View\*\* > \*\*Navigators\*\* > \*\*Project\*\*). \* Select \*\*Runner\*\* > \*\*ShareExtension\*\* > \*\*ShareViewController\*\*. \* Update the \`ShareViewController\` file to use the \`GeneratedPluginRegistrant.h\`: \`\`\`swift title="ShareViewController.swift" // Add this inside \`showFlutter()\` at the top GeneratedPluginRegistrant.register(with: flutterEngine) \`\`\` \`\`\`objc title="ShareViewController.m" // Add this import at the top #import "GeneratedPluginRegistrant.h" \`\`\` \`\`\`objc title="ShareViewController.m" // Add this after \[flutterEngine run] \[GeneratedPluginRegistrant registerWithRegistry:flutterEngine]; \`\`\` 5. (Xcode) \[Test your app with the simulator]\[]. \[Add an extension to your Flutter app]: #add-extension \[Share]: https\://developer.apple.com/library/archive/documentation/General/Conceptual/ExtensibilityPG/Share.html \[Test your app with the simulator]: #test-extensions ## Constraints {: #constraints } \* You must use an iOS simulator to test your extension in debug mode. \* Flutter doesn't fully support running app extensions in debug mode on physical devices when used to build extension UIs because a physical device might run out of memory. \* iOS app extensions have limited memory. It is advisable to only modify an app extension's UI if the app extension supports at least 100MB of memory. ## Call Dart code / render Flutter content in iOS app extensions The \[home\_widget]\[] package provides a large amount of functionality, which includes allowing the following: \* \[Respond to user input]\[] in app extensions using Dart Code. \* \[Render Flutter widgets]\[] in an app extension as an image. \* \[Save and retrieve data]\[] from \`UserDefaults\` on iOS. ## Other resources {: #other-resources } For step-by-step instruction for using app extensions with your Flutter iOS app, check out the \[Adding a Home Screen Widget to your Flutter app]\[lab] codelab. To learn more about the various ways you can add a Flutter Screen to an iOS app, see \[Adding a Flutter Screen to an iOS app]\[]. \[Adding a Flutter Screen to an iOS app]: /add-to-app/ios/add-flutter-screen \[lab]: https\://codelabs.developers.google.com/flutter-home-screen-widgets \[home\_widget]: https\://pub.dev/packages/home\_widget \[Save and retrieve data]: https\://docs.page/abausg/home\_widget/usage/sync-data \[Render Flutter widgets]: https\://docs.page/abausg/home\_widget/features/render-flutter-widgets \[Respond to user input]: https\://docs.page/abausg/home\_widget/features/interactive-widgets

----
url: https://docs.flutter.dev/cookbook/images/fading-in-images.md
----

\# Fade in images with a placeholder > How to fade images into view.

<!--?code-excerpt path-base="cookbook/images/fading_in_images"?-->

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 to display a placeholder at first, and images would fade in as they're loaded? Use the \[\`FadeInImage\`]\[] widget for exactly this purpose. \`FadeInImage\` works with images of any type: in-memory, local assets, or images from the internet. ## In-memory To use a single-pixel, transparent PNG as the placeholder image, complete the following steps: 1. \*\*Create the transparent image data.\*\* Import \`dart:convert\` and \`dart:typed\_data\`, then decode the image from a Base64-encoded string:

<!--?code-excerpt "lib/memory_main.dart (TransparentImage)" plaster="none"?-->

\`\`\`dart import 'dart:convert'; import 'dart:typed\_data'; final Uint8List transparentImage = base64Decode( 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4' 'nGMAAQAABQABDQottAAAAABJRU5ErkJggg==', ); \`\`\` 1. \*\*Use the image data as a placeholder.\*\* Pass the decoded image data to the \`placeholder\` parameter:

<!--?code-excerpt "lib/memory_main.dart (MemoryNetwork)" replace="/^child\: //g"?-->

\`\`\`dart FadeInImage.memoryNetwork( placeholder: transparentImage, image: 'https\://picsum.photos/250?image=9', ), \`\`\` {:.steps} ### Complete example

<!--?code-excerpt "lib/memory_main.dart"?-->

\`\`\`dart import 'dart:convert'; import 'dart:typed\_data'; import 'package:material\_ui/material\_ui.dart'; final Uint8List transparentImage = base64Decode( 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4' 'nGMAAQAABQABDQottAAAAABJRU5ErkJggg==', ); void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { const title = 'Fade in images'; return MaterialApp( title: title, home: Scaffold( appBar: AppBar(title: const Text(title)), body: Stack( children: \[ const Center(child: CircularProgressIndicator()), Center( child: FadeInImage.memoryNetwork( placeholder: transparentImage, image: 'https\://picsum.photos/250?image=9', ), ), ], ), ), ); } } \`\`\` !\[Fading In Image Demo]\(/assets/images/docs/cookbook/fading-in-images.webp){:.site-mobile-screenshot} ## From asset bundle To use a local image asset as the placeholder image, complete the following steps: 1. \*\*Add the placeholder to the asset bundle.\*\* Add a placeholder image to the project's \`assets\` directory, such as \`assets/loading.gif\`. Then, declare the asset in the project's \`pubspec.yaml\` file. For more details, see \[Adding assets and images]\[]. \`\`\`yaml diff flutter: assets: + - assets/loading.gif \`\`\` 1. \*\*Use the asset as a placeholder.\*\* Pass the asset path to the \`placeholder\` parameter of the \[\`FadeInImage.assetNetwork\`]\[] constructor:

<!--?code-excerpt "lib/asset_main.dart (AssetNetwork)" replace="/^child\: //g"?-->

\`\`\`dart FadeInImage.assetNetwork( placeholder: 'assets/loading.gif', image: 'https\://picsum.photos/250?image=9', ), \`\`\` {:.steps} ### Complete example

<!--?code-excerpt "lib/asset_main.dart"?-->

\`\`\`dart import 'package:material\_ui/material\_ui.dart'; void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { const title = 'Fade in images'; return MaterialApp( title: title, home: Scaffold( appBar: AppBar(title: const Text(title)), body: Center( child: FadeInImage.assetNetwork( placeholder: 'assets/loading.gif', image: 'https\://picsum.photos/250?image=9', ), ), ), ); } } \`\`\` !\[Asset fade-in]\(/assets/images/docs/cookbook/fading-in-asset-demo.webp){:.site-mobile-screenshot} \[Adding assets and images]: /ui/assets/assets-and-images \[\`FadeInImage\`]: https\://api.flutter.dev/flutter/widgets/FadeInImage-class.html \[\`FadeInImage.assetNetwork\`]: https\://api.flutter.dev/flutter/widgets/FadeInImage/FadeInImage.assetNetwork.html

----
url: https://docs.flutter.dev/app-architecture/design-patterns/offline-first
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Offline-first support](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

An offline-first application is an app capable of offering most or all of its functionality while being disconnected from the internet. Offline-first applications usually rely on stored data to offer users temporary access to data that would otherwise only be available online.

Some offline-first applications combine local and remote data seamlessly, while other applications inform the user when the application is using cached data. In the same way, some applications synchronize data in the background while others require the user to explicitly synchronize it. It all depends on the application requirements and the functionality it offers, and it’s up to the developer to decide which implementation fits their needs.

In this guide, you will learn how to implement different approaches to offline-first applications in Flutter, following the [Flutter Architecture guidelines](/app-architecture).

## Offline-first architecture

[#](#offline-first-architecture)

As explained in the common architecture concepts guide, repositories act as the single source of truth. They are responsible for presenting local or remote data, and should be the only place where data can be modified. In offline-first applications, repositories combine different local and remote data sources to present data in a single access point, independently of the connectivity state of the device.

This example uses the `UserProfileRepository`, a repository that allows you to obtain and store `UserProfile` objects with offline-first support.

The `UserProfileRepository` uses two different data services: one works with remote data, and the other works with a local database.

The API client,`ApiClientService`, connects to a remote service using HTTP REST calls.

dart

```
class ApiClientService {
  /// performs GET network request to obtain a UserProfile
  Future<UserProfile> getUserProfile() async {
    // ···
  }
​
  /// performs PUT network request to update a UserProfile
  Future<void> putUserProfile(UserProfile userProfile) async {
    // ···
  }
}
```

The database service, `DatabaseService`, stores data using SQL, similar to the one found in the [Persistent Storage Architecture: SQL](/app-architecture/design-patterns/sql) recipe.

dart

```
class DatabaseService {
  /// Fetches the UserProfile from the database.
  /// Returns null if the user profile is not found.
  Future<UserProfile?> fetchUserProfile() async {
    // ···
  }
​
  /// Update UserProfile in the database.
  Future<void> updateUserProfile(UserProfile userProfile) async {
    // ···
  }
}
```

This example also uses the `UserProfile` data class that has been created using the [`freezed`](https://pub.dev/packages/freezed) package.

dart

```
@freezed
abstract class UserProfile with _$UserProfile {
  const factory UserProfile({
    required String name,
    required String photoUrl,
  }) = _UserProfile;
}
```

In apps that have complex data, such as when the remote data contains more fields than the needed by the UI, you might want to have one data class for the API and database services, and another for the UI. For example, `UserProfileLocal` for the database entity, `UserProfileRemote` for the API response object, and then `UserProfile` for the UI data model class. The `UserProfileRepository` would take care of converting from one to the other when necessary.

This example also includes the `UserProfileViewModel`, a view model that uses the `UserProfileRepository` to display the `UserProfile` on a widget.

dart

```
class UserProfileViewModel extends ChangeNotifier {
  // ···
  final UserProfileRepository _userProfileRepository;
​
  UserProfile? get userProfile => _userProfile;
  // ···
​
  /// Load the user profile from the database or the network
  Future<void> load() async {
    // ···
  }
​
  /// Save the user profile with the new name
  Future<void> save(String newName) async {
    // ···
  }
}
```

## Reading data

[#](#reading-data)

Reading data is a fundamental part of any application that relies on remote API services.

In offline-first applications, you want to ensure that the access to this data is as fast as possible, and that it doesn’t depend on the device being online to provide data to the user. This is similar to the [Optimistic State design pattern](/app-architecture/design-patterns/optimistic-state).

In this section, you will learn two different approaches, one that uses the database as a fallback, and one that combines local and remote data using a `Stream`.

### Using local data as a fallback

[#](#using-local-data-as-a-fallback)

As a first approach, you can implement offline support by having a fallback mechanism for when the user is offline or a network call fails.

In this case, the `UserProfileRepository` attempts to obtain the `UserProfile` from the remote API server using the `ApiClientService`. If this request fails, then returns the locally stored `UserProfile` from the `DatabaseService`.

dart

```
Future<UserProfile> getUserProfile() async {
  try {
    // Fetch the user profile from the API
    final apiUserProfile = await _apiClientService.getUserProfile();
    //Update the database with the API result
    await _databaseService.updateUserProfile(apiUserProfile);
​
    return apiUserProfile;
  } catch (e) {
    // If the network call failed,
    // fetch the user profile from the database
    final databaseUserProfile = await _databaseService.fetchUserProfile();
​
    // If the user profile was never fetched from the API
    // it will be null, so throw an  error
    if (databaseUserProfile != null) {
      return databaseUserProfile;
    } else {
      // Handle the error
      throw Exception('User profile not found');
    }
  }
}
```

### Using a Stream

[#](#using-a-stream)

A better alternative presents the data using a `Stream`. In the best case scenario, the `Stream` emits two values, the locally stored data, and the data from the server.

First, the stream emits the locally stored data using the `DatabaseService`. This call is generally faster and less error prone than a network call, and by doing it first the view model can already display data to the user.

If the database does not contain any cached data, then the `Stream` relies completely on the network call, emitting only one value.

Then, the method performs the network call using the `ApiClientService` to obtain up-to-date data. If the request was successful, it updates the database with the newly obtained data, and then yields the value to the view model, so it can be displayed to the user.

dart

```
Stream<UserProfile> getUserProfile() async* {
  // Fetch the user profile from the database
  final userProfile = await _databaseService.fetchUserProfile();
  // Returns the database result if it exists
  if (userProfile != null) {
    yield userProfile;
  }
​
  // Fetch the user profile from the API
  try {
    final apiUserProfile = await _apiClientService.getUserProfile();
    //Update the database with the API result
    await _databaseService.updateUserProfile(apiUserProfile);
    // Return the API result
    yield apiUserProfile;
  } catch (e) {
    // Handle the error
  }
}
```

The view model must subscribe to this `Stream` and wait until it has completed. For that, call `asFuture()` with the `Subscription` object and await the result.

For each obtained value, update the view model data and call `notifyListeners()` so the UI shows the latest data.

dart

```
Future<void> load() async {
  await _userProfileRepository
      .getUserProfile()
      .listen(
        (userProfile) {
          _userProfile = userProfile;
          notifyListeners();
        },
        onError: (error) {
          // handle error
        },
      )
      .asFuture<void>();
}
```

### Using only local data

[#](#using-only-local-data)

Another possible approach uses locally stored data for read operations. This approach requires that the data has been preloaded at some point into the database, and requires a synchronization mechanism that can keep the data up to date.

dart

```
Future<UserProfile> getUserProfile() async {
  // Fetch the user profile from the database
  final userProfile = await _databaseService.fetchUserProfile();
​
  // Return the database result if it exists
  if (userProfile == null) {
    throw Exception('Data not found');
  }
​
  return userProfile;
}
​
Future<void> sync() async {
  try {
    // Fetch the user profile from the API
    final userProfile = await _apiClientService.getUserProfile();
​
    // Update the database with the API result
    await _databaseService.updateUserProfile(userProfile);
  } catch (e) {
    // Try again later
  }
}
```

This approach can be useful for applications that don’t require data to be in sync with the server at all times. For example, a weather application where the weather data is only updated once a day.

Synchronization could be done manually by the user, for example, a pull-to-refresh action that then calls the `sync()` method, or done periodically by a `Timer` or a background process. You can learn how to implement a synchronization task in the section about synchronizing state.

## Writing data

[#](#writing-data)

Writing data in offline-first applications depends fundamentally on the application use case.

Some applications might require the user input data to be immediately available on the server side, while other applications might be more flexible and allow data to be out-of-sync temporarily.

This section explains two different approaches for implementing writing data in offline-first applications.

### Online-only writing

[#](#online-only-writing)

One approach for writing data in offline-first applications is to enforce being online to write data. While this might sound counterintuitive, this ensures that the data the user has modified is fully synchronized with the server, and the application doesn’t have a different state than the server.

In this case, you first attempt to send the data to the API service, and if the request succeeds, then store the data in the database.

dart

```
Future<void> updateUserProfile(UserProfile userProfile) async {
  try {
    // Update the API with the user profile
    await _apiClientService.putUserProfile(userProfile);
​
    // Only if the API call was successful
    // update the database with the user profile
    await _databaseService.updateUserProfile(userProfile);
  } catch (e) {
    // Handle the error
  }
}
```

The disadvantage in this case is that the offline-first functionality is only available for read operations, but not for write operations, as those require the user being online.

### Offline-first writing

[#](#offline-first-writing)

The second approach works the other way around. Instead of performing the network call first, the application first stores the new data in the database, and then attempts to send it to the API service once it has been stored locally.

dart

```
Future<void> updateUserProfile(UserProfile userProfile) async {
  // Update the database with the user profile
  await _databaseService.updateUserProfile(userProfile);
​
  try {
    // Update the API with the user profile
    await _apiClientService.putUserProfile(userProfile);
  } catch (e) {
    // Handle the error
  }
}
```

This approach allows users to store data locally even when the application is offline, however, if the network call fails, the local database and the API service are no longer in sync. In the next section, you will learn different approaches to handle synchronization between local and remote data.

## Synchronizing state

[#](#synchronizing-state)

Keeping the local and remote data in sync is an important part of offline-first applications, as the changes that have been done locally need to be copied to the remote service. The app must also ensure that, when the user goes back to the application, the locally stored data is the same as in the remote service.

### Writing a synchronization task

[#](#writing-a-synchronization-task)

There are different approaches for implementing synchronization in a background task.

A simple solution is to create a `Timer` in the `UserProfileRepository` that runs periodically, for example every five minutes.

dart

```
Timer.periodic(const Duration(minutes: 5), (timer) => sync());
```

The `sync()` method then fetches the `UserProfile` from the database, and if it requires synchronization, it is then sent to the API service.

dart

```
Future<void> sync() async {
  try {
    // Fetch the user profile from the database
    final userProfile = await _databaseService.fetchUserProfile();
​
    // Check if the user profile requires synchronization
    if (userProfile == null || userProfile.synchronized) {
      return;
    }
​
    // Update the API with the user profile
    await _apiClientService.putUserProfile(userProfile);
​
    // Set the user profile as synchronized
    await _databaseService.updateUserProfile(
      userProfile.copyWith(synchronized: true),
    );
  } catch (e) {
    // Try again later
  }
}
```

A more complex solution uses background processes like the [`workmanager`](https://pub.dev/packages/workmanager) plugin. This allows your application to run the synchronization process in the background even when the application is not running.

It’s also recommended to only perform the synchronization task when the network is available. For example, you can use the [`connectivity_plus`](https://pub.dev/packages/connectivity_plus) plugin to check if the device is connected to WiFi. You can also use [`battery_plus`](https://pub.dev/packages/battery_plus) to verify that the device is not running low on battery.

In the previous example, the synchronization task runs every 5 minutes. In some cases, that might be excessive, while in others it might not be frequent enough. The actual synchronization period time for your application depends on your application needs and it’s something you will have to decide.

### Storing a synchronization flag

[#](#storing-a-synchronization-flag)

To know if the data requires synchronization, add a flag to the data class indicating if the changes need to be synchronized.

For example, `bool synchronized`:

dart

```
@freezed
abstract class UserProfile with _$UserProfile {
  const factory UserProfile({
    required String name,
    required String photoUrl,
    @Default(false) bool synchronized,
  }) = _UserProfile;
}
```

Your synchronization logic should attempt to send it to the API service only when the `synchronized` flag is `false`. If the request is successful, then change it to `true`.

### Pushing data from server

[#](#pushing-data-from-server)

A different approach for synchronization is to use a push service to provide up-to-date data to the application. In this case, the server notifies the application when data has changed, instead of being the application asking for updates.

For example, you can use [Firebase messaging](https://firebase.google.com/docs/cloud-messaging/flutter/client), to push small payloads of data to the device, as well as trigger synchronization tasks remotely using background messages.

Instead of having a synchronization task running in the background, the server notifies the application when the stored data needs to be updated with a push notification.

You can combine both approaches together, having a background synchronization task and using background push messages, to keep the application database synchronized with the server.

## Putting it all together

[#](#putting-it-all-together)

Writing an offline-first application requires making decisions regarding the way read, write and sync operations are implemented, which depend on the requirements from the application you are developing.

The key takeaways are:

* When reading data, you can use a `Stream` to combine locally stored data with remote data.
* When writing data, decide if you need to be online or offline, and if you need synchronizing data later or not.
* When implementing a background sync task, take into account the device status and your application needs, as different applications may have different requirements.

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/app-architecture/design-patterns/offline-first.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/app-architecture/design-patterns/offline-first\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/app-architecture/design-patterns/offline-first.md "Report an issue with this page").

----
url: https://docs.flutter.dev/cookbook/testing/widget
----

[Find widgets](/cookbook/testing/widget/finders)

[How to use the Finder classes for testing widgets.](/cookbook/testing/widget/finders)

[Tap, drag, and enter text](/cookbook/testing/widget/tap-drag)

[How to test widgets for user interaction.](/cookbook/testing/widget/tap-drag)

[Test orientation](/cookbook/testing/widget/orientation)

[How to test if an app is in portrait or landscape mode.](/cookbook/testing/widget/orientation)

[Handle scrolling](/cookbook/testing/widget/scrolling)

[How to handle scrolling in a widget test.](/cookbook/testing/widget/scrolling)

[An introduction to widget testing](/cookbook/testing/widget/introduction)

[Learn more about widget testing in Flutter.](/cookbook/testing/widget/introduction)

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/testing/widget/index.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/cookbook/testing/widget\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/testing/widget/index.md "Report an issue with this page").

----
url: https://docs.flutter.dev/platform-integration/windows/extern_win.md
----

\# External windows in Flutter Windows apps > Special considerations for adding external windows to Flutter apps # Windows lifecycle ## Who is affected Windows applications built against Flutter versions after 3.13 that open non-Flutter windows. ## Overview When adding a non-Flutter window to a Flutter Windows app, it will not be part of the logic for application lifecycle state updates by default. For example, this means that when the external window is shown or hidden, the app lifecycle state won't appropriately update to inactive or hidden. As a result, the app might receive incorrect lifecycle state changes through \[WidgetsBindingObserver.didChangeAppLifecycle]\[]. # What do I need to do? To add the external window to this application logic, the window's \`WndProc\` procedure must invoke \`FlutterEngine::ProcessExternalWindowMessage\`. To achieve this, add the following code to the message handler function of your custom external window. In C++ wrappers for the Win32 API, this is often a class method called from the window's \`WndProc\`. The exact file and class name depend on your app's implementation. \`\`\`cpp diff LRESULT MyExternalWindow::MessageHandler(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { + std::optional result = flutter\_controller\_->engine()->ProcessExternalWindowMessage(hwnd, msg, wparam, lparam); + if (result.has\_value()) { + return \*result; + } // Original contents of WndProc... } \`\`\` \[documentation of this breaking change.]: /release/breaking-changes/win\_lifecycle\_process\_function \[WidgetsBindingObserver.didChangeAppLifecycle]: https\://api.flutter.dev/flutter/widgets/WidgetsBindingObserver/didChangeAppLifecycleState.html

----
url: https://docs.flutter.dev/platform-integration/web/wasm.md
----

\# Support for WebAssembly (Wasm) > Current status of Flutter's support for WebAssembly (Wasm). Flutter and Dart support \[WebAssembly]\(https\://webassembly.org/) as a compilation target when building applications for the web. \[\`stable\`]: https\://github.com/flutter/flutter/blob/master/docs/releases/Flutter-build-release-channels.md#stable \[\`package:web\`]: https\://pub.dev/packages/web \[\`dart:js\_interop\`]: https\://api.dart.dev/dart-js\_interop/dart-js\_interop-library.html ## Get started To try a pre-built Flutter web app using Wasm, check out the \[Wonderous demo app]\(https\://wonderous.app/web/). To experiment with Wasm in your own apps, use the following steps. ### Switch to the latest version of Flutter Switch to Flutter version 3.24 or higher to run and compile Flutter applications to WebAssembly. To ensure you are running the latest version, run \`flutter upgrade\`. ### Ensure that your app's dependencies are compatible Try the default template \[sample app]\[], or choose any Flutter application that has been migrated to be \[compatible with Wasm]\(#js-interop-wasm). \[sample app]: /reference/create-new-app ### Modify the index page Make sure your app's \`web/index.html\` is updated to the latest \[Flutter web app initialization]\[] for Flutter 3.22 and later. If you would like to use the default, delete the contents of the \`web/\` directory and run the following command to regenerate them: \`\`\`console $ flutter create . --platforms web \`\`\` \[Flutter web app initialization]: /platform-integration/web/initialization ### Run or build your app To run the app with Wasm for development or testing, use the \`--wasm\` flag with the \`flutter run\` command. \`\`\`console $ flutter run -d chrome --wasm \`\`\` To build a web application with Wasm, add the \`--wasm\` flag to the existing \`flutter build web\` command. \`\`\`console $ flutter build web --wasm \`\`\` The command produces output into the \`build/web\` directory relative to the package root, just like \`flutter build web\`. :::note Enabling Wasm compilation for a Flutter web app does not use deferred loading by default. Deferred loading in Wasm is experimental and can be enabled using the \`--enable-wasm-deferred-loading\` flag: \`\`\`console $ flutter build web --wasm --enable-wasm-deferred-loading \`\`\` This is available on the flutter main branch and will become generally available later this year in the 3.50 flutter stable release. ::: #### Wasm production debugging By default, Wasm release builds strip debug symbols and omit source maps to minimize binary size. - \*\*For error monitoring (recommended)\*\*: Pass \`--source-maps\` to generate a \`main.dart.wasm.map\` file for symbolication in error tracking services. For security recommendations and deployment warnings, see \[Source maps]\(/deployment/web#source-maps). - \*\*For staging or QA builds\*\*: Pass \`--no-strip-wasm\` to preserve Wasm function names directly in browser console stack traces, at the cost of an approximate \*\*47% increase\*\* in Wasm binary size. \`\`\`console # Generate source maps for production error tracking: $ flutter build web --wasm --source-maps # Preserve Wasm function names for staging/QA debugging: $ flutter build web --wasm --no-strip-wasm \`\`\` ### Open the app in a compatible web browser Even with the \`--wasm\` flag, Flutter will still compile the application to JavaScript. If WasmGC support is not detected at runtime, the JavaScript output is used so the application will continue to work in all major browsers. You can verify whether the app is actually running with Wasm by checking for the \`dart2wasm\` environment variable, set during compilation (preferred). \`\`\`dart const isRunningWithWasm = bool.fromEnvironment('dart.tool.dart2wasm'); \`\`\` Alternatively, you can use differences in number representations to test whether the native (Wasm) number representation is used. \`\`\`dart final isRunningWithWasm = identical(double.nan, double.nan); \`\`\` ### Serve the built output with an HTTP server Flutter web WebAssembly can use multiple threads to render your application faster, with less jank. To do this, Flutter uses advanced browser features that require specific HTTP response headers. :::important Flutter web applications compiled with WebAssembly won't run with multiple-threads unless the server is configured to send specific HTTP headers. ::: | Name | Value | |-|-| | \`Cross-Origin-Embedder-Policy\` | \`credentialless\`\
or\
\`require-corp\` | | \`Cross-Origin-Opener-Policy\` | \`same-origin\` | {:.table} To learn more about these headers, check out \[Load cross-origin resources without CORP headers using COEP: credentialless]\[coep]. \[coep]: https\://developer.chrome.com/blog/coep-credentialless-origin-trial ## Learn more about browser compatibility To run a Flutter app that has been compiled to Wasm, you need a browser that supports \[WasmGC]\[]. \[Chromium and V8]\[] support WasmGC since version 119. Chrome on iOS uses WebKit, which doesn't yet \[support WasmGC]\[]. Firefox announced stable support for WasmGC in Firefox 120, but currently doesn't work due to a known limitation (see details below). \[WasmGC]: https\://github.com/WebAssembly/gc/tree/main/proposals/gc \[Chromium and V8]: https\://chromestatus.com/feature/6062715726462976 \[support WasmGC]: https\://bugs.webkit.org/show\_bug.cgi?id=247394 \[issue]: https\://bugzilla.mozilla.org/show\_bug.cgi?id=1788206 - \*\*Why not Firefox?\*\* Firefox versions 120 and later were previously able to run Flutter/Wasm, but they're experiencing a bug that is blocking compatibility with Flutter's Wasm renderer. Follow \[this bug]\[firefox-bug] for details. - \*\*Why not Safari?\*\* Safari now supports WasmGC, but is experiencing a similar bug that is blocking compatibility with Flutter's Wasm renderer. Follow \[this bug]\[safari-bug] for details. \[firefox-bug]: https\://bugzilla.mozilla.org/show\_bug.cgi?id=1788206 \[safari-bug]: https\://bugs.webkit.org/show\_bug.cgi?id=267291 :::warning Flutter compiled to Wasm can't run on the iOS version of any browser. All browsers on iOS are required to use WebKit, and can't use their own browser engine. ::: ## Using compatible JS interop libraries {:#js-interop-wasm} To support compilation to Wasm, Dart has changed how it enables interop with browser and JavaScript APIs. This prevents Dart code that uses \`dart:html\` or \`package:js\` from compiling to Wasm. Instead, Dart now provides new, lightweight interop solutions built around static JS interop: - \[\`package:web\`]\[], which replaces \`dart:html\` (and other web libraries) - \[\`dart:js\_interop\`]\[], which replaces \`package:js\` and \`dart:js\` For a detailed guide on migrating existing code, see the \[\`package:web\` migration guide]\[]. When compiling to Wasm, be aware of runtime differences in JS interop types (such as \`is\`/\`as\` type casts and Zone propagation in callbacks). For details, check out Dart's \[JS interop types documentation]\(https\://dart.dev/interop/js-interop/js-types#compatibility-type-checks-and-casts) and the \[package:web Zones section]\(https\://dart.dev/interop/js-interop/package-web#zones). To learn more about JS interop in Dart, see Dart's \[JS interop]\[] documentation page. ### Diagnosing Wasm compilation errors {:#diagnosing-wasm-compilation-errors} When compiling a Flutter web project with \`--wasm\`, if your application or its packages import unsupported web APIs (such as \`dart:html\` or \`package:js\`), the compilation will fail. By default, the Dart Wasm compiler emits a clean, structured dependency chain tree, but this output can be buried in a long terminal exception stack trace. #### 1. Perform early detection You can detect incompatibilities early with dry-run warnings. When you run \`flutter build web\` without the \`--wasm\` flag, a Wasm dry run is still performed automatically. If incompatibilities are found, you will see a non-fatal warning like this: \`\`\`console Wasm dry run failed: Found incompatibilities with WebAssembly. package:my\_app/main.dart 1:1 - dart:html unsupported (0) \`\`\` #### 2. Isolate the compiler dependency tree If you run \`flutter build web --wasm\` and it fails, \`flutter\_tools\` outputs a large exception stack trace (for example, \`Target dart2wasm failed...\`). \*\*Ignore the long stack trace and command string.\*\* Instead, scroll up to the top of the error output to find the structured \`Context\` tree. This clean tree tells you exactly which package and file imported the unsupported library: \`\`\`console Context: The unavailable library 'dart:html' is imported through these packages: main.dart => package:my\_app => dart:html Detailed import paths for (some of) these imports: main.dart => package:my\_app/main.dart => dart:html \`\`\` #### 3. Migrate legacy imports To fix these compilation errors, you must migrate from legacy JS interop packages to modern WebAssembly-compatible alternatives: - Replace \`dart:html\` and other legacy web libraries with \[\`package:web\`]\[]. - Replace \`dart:js\` and \`package:js\` with \[\`dart:js\_interop\`]\[]. For comprehensive migration instructions, check out the \[\`package:web\` migration guide on dart.dev]\(https\://dart.dev/interop/js-interop/package-web) and Dart's \[JS interop usage guide]\(https\://dart.dev/interop/js-interop). If you need to support both legacy and modern environments during your migration, use conditional imports by checking for \`dart.library.js\_interop\`: \`\`\`dart import 'fallback.dart' if (dart.library.js) 'legacy\_web\_interop.dart' if (dart.library.js\_interop) 'wasm\_web\_interop.dart'; \`\`\` To learn more about selecting implementations at compile time, see the \[conditional imports documentation on dart.dev]\(https\://dart.dev/interop/js-interop/package-web#conditional-imports). \[\`package:url\_launcher\`]: https\://pub.dev/packages/url\_launcher \[\`package:web\` migration guide]: https\://dart.dev/interop/js-interop/package-web \[JS interop]: https\://dart.dev/interop/js-interop \[\`wasm-ready\`]: https\://pub.dev/packages?q=is%3Awasm-ready \[pub.dev]: https\://pub.dev

----
url: https://docs.flutter.dev/ui/design
----

[Cupertino design for Flutter](/ui/design/cupertino)

[Learn about Cupertino design for Flutter.](/ui/design/cupertino)

[Custom drawing and graphics](/ui/design/graphics)

[Content covering how to create custom graphics and use your own shaders in Flutter apps.](/ui/design/graphics)

[Text](/ui/design/text)

[Content covering how to add and customize text in Flutter appw.](/ui/design/text)

[Material Design for Flutter](/ui/design/material)

[Learn about Material Design for Flutter.](/ui/design/material)

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/ui/design/index.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/ui/design\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/ui/design/index.md "Report an issue with this page").

----
url: https://docs.flutter.dev/platform-integration/bind-native-code.md
----

\# Bind to native code using FFI > To use native code in your Flutter program, use the dart:ffi library with the package\_ffi template. Flutter apps can use the \[dart:ffi]\[] library to call native APIs. \_FFI\_ stands for \[\_foreign function interface\_]\[FFI]. Other terms for similar functionality include \_native interface\_ and \_language bindings.\_ Since Flutter 3.38, the recommended way to bind to native code is to use the \`flutter create --template=package\_ffi\` command. This template uses \[build hooks]\[] to configure the native build in a \`build.dart\` script, and no longer requires OS-specific build files. This approach works for both Flutter and Dart standalone projects. If you need to use the Flutter Plugin API, or if you need to configure a Google Play services runtime on Android, use the standard plugin template (\`flutter create --template=plugin\`). :::note Looking for the previous FFI plugin workflow? See \[Bind to native code using the legacy FFI plugin template]\(/platform-integration/legacy-ffi-plugin), which documents the \`plugin\_ffi\` template and OS-specific build files. ::: \[build hooks]: https\://dart.dev/tools/hooks \[dart:ffi]: https\://api.dart.dev/dart-ffi/dart-ffi-library.html \[FFI]: https\://en.wikipedia.org/wiki/Foreign\_function\_interface ## Create an FFI package To create an FFI package, run the following command: \`\`\`console $ flutter create --template=package\_ffi native\_add $ cd native\_add \`\`\` This creates a package with the following specialized content: - \*\*\`lib/native\_add.dart\`\*\*: The Dart code that defines the API of the package. - \*\*\`lib/native\_add\_bindings\_generated.dart\`\*\*: The generated Dart bindings for the native code. - \*\*\`src/native\_add.c\`\*\*: The native C source code. - \*\*\`src/native\_add.h\`\*\*: The C header file for the native code. - \*\*\`hook/build.dart\`\*\*: A script that is run by the Flutter SDK to compile the native code. - \*\*\`ffigen.yaml\`\*\*: The configuration file for \[\`package:ffigen\`]\[] to generate the Dart bindings. - \*\*\`pubspec.yaml\`\*\*: The package definition, which enables the \`build.dart\` hook. \[\`package:ffigen\`]: https\://pub.dev/packages/ffigen ## The native code The native code is located in \`src/native\_add.c\` and \`src/native\_add.h\`. The C function \`sum\` is defined in the \`.c\` file and its signature is in the header file. The function is marked to be exported so that it can be called from Dart. ## The build hook The native code is compiled and bundled with your app automatically. This is done by the \`hook/build.dart\` script, which is a \[build hook]\[build hooks]. This means you no longer need to write OS-specific build files (like \`CMakeLists.txt\` for Linux/Windows, \`.podspec\` for iOS/macOS, or \`build.gradle\` for Android) to compile your native code. The build hook uses \`package:native\_toolchain\_c\` to compile the C code into a dynamic library. You can customize this file to build other native languages or to download pre-compiled binaries. ## The Dart code The Dart code defines the public API of the package. ### Generating the bindings To bind to the native code, the template uses \[\`package:ffigen\`]\[] to generate bindings from the header file (\`src/native\_add.h\`). The generation is configured in \`ffigen.yaml\`. This generates \`lib/native\_add\_bindings\_generated.dart\`. ### Calling the native function The generated bindings in \`lib/native\_add\_bindings\_generated.dart\` contain \`@Native() external\` functions. These functions are automatically resolved at runtime against the code asset outputted by the build hook (which runs at build time). This means there is no OS-specific logic required for \`dlopen\`-ing the dynamic libraries, making the Dart code truly cross-platform. The main library file \`lib/native\_add.dart\` exposes these functions. Your app can then call these functions by importing \`package:native\_add/native\_add.dart\`. ## Testing The generated package includes a unit test in \`test/native\_add\_test.dart\` that shows how to test the native function. ## Other use cases ### System libraries To link against a system library, you modify the \`build.dart\` hook to specify the linking mode. Instead of compiling source code, you create a \`CodeAsset\` and set its \`linkMode\`. For many system libraries on Android, iOS, Linux, and macOS, you can use \`LookupInProcess()\` to find symbols in the main process. For Windows, you often use \`DynamicLoadingSystem()\` and provide the name of the DLL. :::note For desktop-specific guidance on calling system APIs—including wrapper packages like \[\`package:win32\`]\(https\://pub.dev/packages/win32) and \[Canonical's Linux packages]\(https\://pub.dev/publishers/canonical.com/packages)—see \[Integrating with Windows]\(/platform-integration/windows/building#integrating-with-windows) and \[Integrate with Linux]\(/platform-integration/linux/building#integrate-with-linux). ::: Here is an example \`build.dart\` that links against system libraries to get the host name: \`\`\`dart // hook/build.dart import 'package:hooks/hooks.dart'; import 'package:code\_assets/code\_assets.dart'; void main(List args) async { await build(args, (input, output) async { final targetOS = input.target.os; switch (targetOS) { case OS.android || OS.iOS || OS.linux || OS.macOS: output.assets.code.add( CodeAsset( package: 'host\_name', name: 'src/third\_party/unix.dart', linkMode: LookupInProcess(), ), ); case OS.windows: output.assets.code.add( CodeAsset( package: 'host\_name', name: 'src/third\_party/windows.dart', linkMode: DynamicLoadingSystem(Uri.file('ws2\_32.dll')), ), ); default: throw Exception('Unsupported target os: $targetOS'); } }); } \`\`\` The Dart files (\`unix.dart\`, \`windows.dart\`) would then contain the \`external\` functions that use the symbols from these system libraries. #### Bundling \`libc++\_shared.so\` on Android Although \`libc++\_shared.so\` ships with the Android NDK, it isn't a system library. If your app or package uses the \[C++ standard library]\[libcpp-support], or includes \[multiple shared libraries]\[shared-libraries] that depend on it, your app needs to bundle \`libc++\_shared.so\`. To bundle the library in your app, add a dependency on \[\`package:android\_libcpp\_shared\`]\[libcpp-shared], which uses its own build hook to bundle \`libc++\_shared.so\` from the locally installed NDK for each target architecture. \[libcpp-support]: https\://developer.android.com/ndk/guides/cpp-support#cs \[shared-libraries]: https\://developer.android.com/ndk/guides/cpp-support#shared\_runtimes \[libcpp-shared]: https\://pub.dev/packages/android\_libcpp\_shared ### Closed-source libraries You can also use build hooks to link against pre-compiled, closed-source libraries. The recommended approach is to download the pre-compiled binaries at build time and verify their integrity with a file hash. In your \`build.dart\` hook, you would: 1. Download the library from a URL. 2. Verify the hash of the downloaded file. 3. Place the library in the build output directory. 4. Create a \`CodeAsset\` with \`DynamicLoading\` pointing to the library. Here is a simplified example of the \`CodeAsset\` creation: \`\`\`dart // hook/build.dart import 'package:hooks/hooks.dart'; import 'package:code\_assets/code\_assets.dart'; void main(List args) async { await build(args, (input, output) async { // 1. Download the library from a URL. // 2. Verify the hash of the downloaded file. // 3. Place the library in the build output directory. output.assets.code.add( CodeAsset( package: input.packageName, name: 'src/my\_lib.dart', // Dart file with bindings linkMode: DynamicLoadingBundled(), file: input.outputDirectory.resolve('my\_lib.so'), ), ); }); } \`\`\` You would need to handle different architectures and platforms by having different versions of your pre-compiled library. For more examples, see the \[code\_assets package examples]\(https\://pub.dev/packages/code\_assets/example). ## Dynamic library naming guidelines When implementing \`build.dart\` hooks for packages that bundle code assets, it is critical to ensure consistent naming of your dynamic libraries across all target architectures and SDKs. On Apple platforms (iOS and macOS), dynamic libraries are bundled into frameworks. Flutter's build system relies on these names to generate metadata and package distributable formats like XCFrameworks. ### Consistency across architectures For a given asset ID, your hook will be invoked multiple times, once per architecture. Your hook must produce the same filename regardless of the target architecture (for example, \`arm64\` vs. \`x64\`). \* \*\*Why?\*\* Within a single SDK build, Flutter combines architecture-specific binaries into a single universal (fat) binary using \`lipo\`. If architectures have different filenames, the tool will pick one non-deterministically and issue a warning. Furthermore, error messages at runtime will be confusing for your users if dynamic libraries are renamed. \* \*\*Recommended action\*\*: Avoid adding architecture suffixes to your filenames (for example, use \`libsqlite3.dylib\` instead of \`libsqlite3\_arm64.dylib\`). Instead, write the file to \`input.outputDirectory\` (which is unique per architecture) or to an architecture-specific subdirectory of \`input.outputDirectoryShared\` (for example, \`input.outputDirectoryShared.resolve('$architecture/')\`). ### Consistency across SDKs (iOS) When building for iOS, your hook will be invoked multiple times with different values for the SDK and architecture. Both physical device (\`iphoneos\`) and simulator (\`iphonesimulator\`) invocations must produce the same framework name for the same asset ID. \* \*\*Why?\*\* Flutter uses \`xcodebuild -create-xcframework\` to combine these outputs. Xcode requires that all platform slices within an XCFramework share the same framework name to allow seamless linking. If filenames differ, the Flutter tool cannot create a correct XCFramework, and commands like \`flutter build ios-framework\` will fail. \* \*\*Recommended action\*\*: Do not use suffixes like \`\_sim\` or \`\_simulator\` for the simulator build. The XCFramework structure already handles the platform separation internally (for example, \`MyLib.xcframework/ios-arm64\_x86\_64-simulator/MyLib.framework\`). Instead, write the file to \`input.outputDirectory\` (which is unique per SDK) or to an SDK-specific subdirectory of \`input.outputDirectoryShared\`. ### Consistency in the set of assets Your hook must produce the same set of Asset IDs across all SDKs for a given target platform. \* \*\*Why?\*\* Apple's build system and App Store validation require that all frameworks included in an application are compatible with the target device. If you produce an asset for the simulator (\`iphonesimulator\`) but not for the physical device (\`iphoneos\`), the resulting XCFramework will contain a slice that has no counterpart for the device. This can lead to build failures or Apple rejecting the application for including simulator-only binaries in a device build. \* \*\*Recommended action\*\*: Ensure that your \`build.dart\` hook logic handles all supported SDKs consistently. If you produce an asset for one SDK, you must produce a corresponding asset for all other SDKs for that platform. For SDK-specific code, you can use stub implementations for other SDKs.

----
url: https://docs.flutter.dev/packages-and-plugins/swift-package-manager/for-app-developers.md
----

\# Swift Package Manager for app developers > How to use Swift Package Manager for native iOS or macOS dependencies :::note As of the 3.44 release, Flutter uses \[Swift Package Manager]\[] to manage iOS and macOS native dependencies. Flutter continues to support CocoaPods in maintenance mode, however, the CocoaPods registry permanently becomes \[read-only on December 2, 2026]\[cocoapods]. ::: \[cocoapods]: https\://blog.cocoapods.org/CocoaPods-Specs-Repo/ \[Swift Package Manager]: https\://www\.swift.org/documentation/package-manager/ ## How to turn on Swift Package Manager As of the 3.44 release, Flutter's Swift Package Manager (SwiftPM) support is on by default. Upgrading Flutter and running your app automatically adds SwiftPM integration. This makes your project download the Swift packages that your Flutter plugins depend on. To use an older Flutter version, you might need to \[remove Swift Package Manager integration]\[removeSPM] from the app. Note that Flutter falls back to CocoaPods for dependencies that don't yet support Swift Package Manager. \[Optional] To check if your project is using SwiftPM: 1. In Xcode, run the app. 1. Ensure that \*\*Run Prepare Flutter Framework Script\*\* runs as a pre-action and that \`FlutterGeneratedPluginSwiftPackage\` is a target dependency. If you previously disabled SwiftPM, you might need to enable it with \`flutter config --enable-swift-package-manager\`. If automatic migration works for you and all of your dependencies support Swift Package Manager, you can \[remove CocoaPods integration]\[removeCocoaPods]. ## How to remove CocoaPods integration \*\*Flutter falls back to CocoaPods if any of your project dependencies do not support Swift Package Manager.\*\* Ensure that all plugins in your project support Swift Package Manager before removing CocoaPods. :::note If you have manually edited your \`Podfile\` to add CocoaPods dependencies or logic other than Flutter plugins, you must manually remove those changes. ::: To remove CocoaPods from your project, use the following steps. 1. To de-integrate CocoaPods from your Xcode projects, run: \`\`\`sh cd ios pod deintegrate cd .. \`\`\` 2. Delete CocoaPods-related files and directories: Delete \`ios/Podfile\`, \`ios/Podfile.lock\`, the \`ios/Pods/\` directory, and the \`ios/.symlinks/\` directory (if present). 3. Check your configuration files: In \`ios/Flutter/Debug.xcconfig\` and \`ios/Flutter/Release.xcconfig\`, remove any \`#include\` lines referencing \`Pods/Target Support Files\` or CocoaPods \`.xcconfig\` files if they are still present. 4. Clean and build your project to verify the migration: \`\`\`sh flutter clean flutter pub get flutter run \`\`\` To remove CocoaPods from your project, use the following steps. 1. To de-integrate CocoaPods from your Xcode projects, run: \`\`\`sh cd macos pod deintegrate cd .. \`\`\` 2. Delete CocoaPods-related files and directories: Delete \`macos/Podfile\`, the \`macos/Pods/\` directory, and the \`macos/.symlinks/\` directory (if present). 3. Check your configuration files: In \`macos/Flutter/Flutter-Debug.xcconfig\` and \`macos/Flutter/Flutter-Release.xcconfig\` remove any \`#include\` lines referencing \`Pods/Target Support Files\` or CocoaPods \`.xcconfig\` files if they are still present. 4. Clean and build your project to verify the migration: \`\`\`sh flutter clean flutter pub get flutter run \`\`\` ## How to add Swift Package Manager integration manually When you upgrade to Flutter 3.44 or later and run your app, SwiftPM integration is automatically added. You only need these instructions if problems occurred and you need to manually add SwiftPM integration to your project. Most developers do not need to do this. If you experience a problem automatically migrating your project to SwiftPM, please \[file an issue]\[]. Include the error message and, if possible, include a copy of the following files in your issue: \* \`ios/Runner.xcodeproj/project.pbxproj\` \* \`ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme\` (or the xcsheme for the flavor used) Migrating to SwiftPM requires updating the \`ios/Runner.xcodeproj/project.pbxproj\` and \`ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme\` files. ### Step 1: Add FlutterGeneratedPluginSwiftPackage package dependency {:.no\_toc} 1. In Xcode, open \`ios/Runner.xcworkspace\`. 1. Navigate to \*\*Package Dependencies\*\* for the project. 1. Click the button. 1. In the dialog that opens, click \*\*Add Local...\*\*. 1. Navigate to \`ios/Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage\` and click \*\*Add Package\*\*. 1. Ensure that it's added to the \`Runner\` target and click \*\*Add Package\*\*. 1. Ensure that \`FlutterGeneratedPluginSwiftPackage\` was added to \*\*Frameworks, Libraries, and Embedded Content\*\*. ### Step 2: Add Run Prepare Flutter Framework Script Pre-action {:.no\_toc} \*\*The following steps must be completed for each flavor.\*\* 1. Go to \*\*Product > Scheme > Edit Scheme\*\*. 1. Expand the \*\*Build\*\* section in the left sidebar. 1. Click \*\*Pre-actions\*\*. 1. Click the button and select \*\*New Run Script Action\*\* from the menu. 1. Click the \*\*Run Script\*\* title and change it to: \`\`\`plaintext Run Prepare Flutter Framework Script \`\`\` 1. Change the \*\*Provide build settings from\*\* to the \`Runner\` app. 1. Input the following in the text box: \`\`\`sh "$FLUTTER\_ROOT/packages/flutter\_tools/bin/xcode\_backend.sh" prepare \`\`\` ### Step 3: Run app {:.no\_toc} 1. In Xcode, run the app. 1. Ensure that \*\*Run Prepare Flutter Framework Script\*\* runs as a pre-action and that \`FlutterGeneratedPluginSwiftPackage\` is a target dependency. 1. Ensure that the app runs on the command line with \`flutter run\`. \[turn on Swift Package Manager]: /packages-and-plugins/swift-package-manager/for-app-developers/#how-to-turn-on-swift-package-manager \[file an issue]: https\://github.com/flutter/flutter/issues/new?template=2\_bug.yml Migrating to SwiftPM requires updating the \`macos/Runner.xcodeproj/project.pbxproj\` and \`macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme\` files. ### Step 1: Add FlutterGeneratedPluginSwiftPackage package dependency {:.no\_toc} 1. In Xcode, open \`macos/Runner.xcworkspace\`. 1. Navigate to \*\*Package Dependencies\*\* for the project. 1. Click the button. 1. In the dialog that opens, click the \*\*Add Local...\*\*. 1. Navigate to \`macos/Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage\` and click the \*\*Add Package\*\*. 1. Ensure that it's added to the Runner Target and click \*\*Add Package\*\*. 1. Ensure that \`FlutterGeneratedPluginSwiftPackage\` was added to \*\*Frameworks, Libraries, and Embedded Content\*\*. ### Step 2: Add Run Prepare Flutter Framework Script Pre-action {:.no\_toc} \*\*The following steps must be completed for each flavor.\*\* 1. Go to \*\*Product > Scheme > Edit Scheme\*\*. 1. Expand the \*\*Build\*\* section in the left sidebar. 1. Click \*\*Pre-actions\*\*. 1. Click the button and select \*\*New Run Script Action\*\* from the menu. 1. Click the \*\*Run Script\*\* title and change it to: \`\`\`plaintext Run Prepare Flutter Framework Script \`\`\` 1. Change the \*\*Provide build settings from\*\* to the \`Runner\` target. 1. Input the following in the text box: \`\`\`sh "$FLUTTER\_ROOT"/packages/flutter\_tools/bin/macos\_assemble.sh prepare \`\`\` ### Step 3: Run app {:.no\_toc} 1. In Xcode, run the app. 1. Ensure that \*\*Run Prepare Flutter Framework Script\*\* runs as a pre-action and that \`FlutterGeneratedPluginSwiftPackage\` is a target dependency. 1. Ensure that the app runs on the command line with \`flutter run\`. \[file an issue]: https\://github.com/flutter/flutter/issues/new?template=2\_bug.yml \[file an issue]: https\://github.com/flutter/flutter/issues/new?template=02\_bug.yml ### Add to an existing app (add-to-app) To use SwiftPM, consult one of the following pages, as appropriate: \* \[Integrate a Flutter app into your iOS project]\[ios-add-2-app] \* \[Integrate a Flutter app into your macOS project]\[macos-add-2-app] \[ios-add-2-app]: /add-to-app/ios/project-setup \[macos-add-2-app]: /add-to-app/macos/project-setup ### Add to a custom Xcode target Your Flutter Xcode project can have custom \[Xcode targets]\[] to build additional products, like frameworks or unit tests. You can add Swift Package Manager integration to these custom Xcode targets. Follow the steps in \[How to add Swift Package Manager integration]\[manualIntegration]. In \[Step 1]\[] for list item 6, use your custom target instead of the \`Flutter\` target. In \[Step 2]\[] for list item 6, use your custom target instead of the \`Flutter\` target. \[Xcode targets]: https\://developer.apple.com/documentation/xcode/configuring-a-new-target-in-your-project \[manualIntegration]: #how-to-add-swift-package-manager-integration-manually \[Step 1]: #step-1-add-fluttergeneratedpluginswiftpackage-package-dependency \[Step 2]: #step-2-add-run-prepare-flutter-framework-script-pre-action ## How to remove Swift Package Manager integration When your app is modified to support SwiftPM, the Xcode project is updated to add Flutter plugin dependencies. To undo this migration: 1. \[Turn off Swift Package Manager]\[]. 1. Clean your project: \`\`\`sh flutter clean \`\`\` 1. Open your app (\`ios/Runner.xcworkspace\` or \`macos/Runner.xcworkspace\`) in Xcode. 1. Navigate to \*\*Package Dependencies\*\* for the project. 1. Click the \`FlutterGeneratedPluginSwiftPackage\` package, then click the button. 1. Navigate to \*\*Frameworks, Libraries, and Embedded Content\*\* for the \`Runner\` target. 1. Click \`FlutterGeneratedPluginSwiftPackage\`, then click the button. 1. Go to \*\*Product > Scheme > Edit Scheme\*\*. 1. Expand the \*\*Build\*\* section in the left sidebar. 1. Click \*\*Pre-actions\*\*. 1. Expand \*\*Run Prepare Flutter Framework Script\*\*. 1. Click the button. \[Turn off Swift Package Manager]: #how-to-turn-off-swift-package-manager ## How to use a Swift Package Manager Flutter plugin that requires a higher OS version If a Swift Package Flutter Manager plugin requires a higher OS version than the project, you might get an error like this: \`\`\`plaintext Target Integrity (Xcode): The package product 'plugin\_name\_ios' requires minimum platform version 14.0 for the iOS platform, but this target supports 12.0 \`\`\` To use the plugin: 1. Open your app (\`ios/Runner.xcworkspace\` or \`macos/Runner.xcworkspace\`) in Xcode. 1. Increase your app's target \*\*Minimum Deployments\*\*. 1. If you updated your iOS app's \*\*Minimum Deployments\*\*, regenerate the iOS project's configuration files: \`\`\`sh flutter build ios --config-only \`\`\` 1. If you updated your macOS app's \*\*Minimum Deployments\*\*, regenerate the macOS project's configuration files: \`\`\`sh flutter build macos --config-only \`\`\` ## How to turn off Swift Package Manager In general, don't do this. Remember that the CocoaPods registry becomes read-only on December 2, 2026 and disabling SwiftPM won't be allowed in the future. Disabling Swift Package Manager causes Flutter to use CocoaPods for all dependencies. However, SwiftPM remains integrated with your project. To remove Swift Package Manager integration completely from your project, follow the \[How to remove Swift Package Manager integration]\[removeSPM] instructions. ### Turn off SwiftPM for a single project In the project's \`pubspec.yaml\` file, under the \`flutter\` section, set \`enable-swift-package-manager\` to \`false\` in the \`config\` subsection. \`\`\`yaml title="pubspec.yaml" # The following section is specific to Flutter packages. flutter: config: enable-swift-package-manager: false \`\`\` This turns off Swift Package Manager for all contributors to this project. ### Turn off SwiftPM globally for all projects Run the following command: \`\`\`sh flutter config --no-enable-swift-package-manager \`\`\` This turns off Swift Package Manager for the current user. If a project is incompatible with Swift Package Manager, all contributors need to run this command. \[removeCocoaPods]: #how-to-remove-cocoapods-integration \[removeSPM]: #how-to-remove-swift-package-manager-integration

----
url: https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.60.0.md
----

\# DevTools 2.60.0 release notes > Release notes for Dart and Flutter DevTools version 2.60.0. The 2.60.0 release of the Dart and Flutter DevTools includes the following changes among other general improvements. To learn more about DevTools, check out the \[DevTools overview]\(/tools/devtools). ## General updates - Fixed an issue where DevTools could get stuck in a disconnected state (such as after a Mac goes to sleep) by adding a manual "Reconnect" button to the disconnected screen. - \[#9838]\(https\://github.com/flutter/devtools/issues/9838) - Resolved several memory leaks. - \[#9857]\(https\://github.com/flutter/devtools/pull/9857) - Fixed a bug where highlighted search matches in tables were unreadable in dark mode because the highlight color had become fully opaque. - \[#9863]\(https\://github.com/flutter/devtools/pull/9863) - Rejected absolute paths in DevTools server file reads so they stay within the \`\~/.flutter-devtools/\` directory and can't resolve to arbitrary files on disk. - \[#9844]\(https\://github.com/flutter/devtools/pull/9844) - Validated the \`devtoolsOptionsUri\` parameter in the extension enabled-state handler so it must be a \`file:\` URI named \`devtools\_options.yaml\`, preventing the DevTools server from writing to arbitrary file paths. - \[#9834]\(https\://github.com/flutter/devtools/pull/9834) ## Inspector updates - Fixed an issue where the Inspector error badge count would improperly increase or disappear during navigation. - \[#9524]\(https\://github.com/flutter/devtools/issues/9524) ## Performance updates - Fixed a bug where the selected feature tab wasn't restored when loading exported Performance data. - \[#9861]\(https\://github.com/flutter/devtools/pull/9861) ## Network profiler updates - Fixed the Network tab search field becoming disabled after clearing all requests, so the search query can now be edited at any time. - \[#9855]\(https\://github.com/flutter/devtools/pull/9855) ## Deep links tool updates - Only validate deep links when connected to a Flutter app. - \[#8081]\(https\://github.com/flutter/devtools/issues/8081) ## Full commit history To find a complete list of changes in this release, check out the \[DevTools git log]\(https\://github.com/flutter/devtools/tree/v2.60.0).

----
url: https://docs.flutter.dev/cookbook/testing/widget/introduction.md
----

\# An introduction to widget testing > Learn more about widget testing in Flutter.

<!--?code-excerpt path-base="cookbook/testing/widget/introduction/"?-->

In the \[introduction to unit testing]\[] 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\`]\[] package, which ships with the Flutter SDK. The \`flutter\_test\` package provides the following tools for testing widgets: \* The \[\`WidgetTester\`]\[] allows building and interacting with widgets in a test environment. \* The \[\`testWidgets()\`]\[] function automatically creates a new \`WidgetTester\` for each test case, and is used in place of the normal \`test()\` function. \* The \[\`Finder\`]\[] classes allow searching for widgets in the test environment. \* Widget-specific \[\`Matcher\`]\[] constants 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, which uses the following steps: 1. Add the \`flutter\_test\` dependency. 2. Create a widget to test. 3. Create a \`testWidgets\` test. 4. Build the widget using the \`WidgetTester\`. 5. Search for the widget using a \`Finder\`. 6. Verify the widget using a \`Matcher\`. ## 1. Add the \`flutter\_test\` dependency 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: flutter\_test: sdk: flutter \`\`\` ## 2. Create a widget to test Next, create a widget for testing. For this recipe, create a widget that displays a \`title\` and \`message\`.

<!--?code-excerpt "test/main_test.dart (widget)"?-->

\`\`\`dart class MyWidget extends StatelessWidget { const MyWidget({super.key, required this.title, required this.message}); final String title; final String message; @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', home: Scaffold( appBar: AppBar(title: Text(title)), body: Center(child: Text(message)), ), ); } } \`\`\` ## 3. Create a \`testWidgets\` test With a widget to test, begin by writing your first test. Use the \[\`testWidgets()\`]\[] 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. This test verifies that \`MyWidget\` displays a given title and message. It is titled accordingly, and it will be populated in the next section.

<!--?code-excerpt "test/main_step3_test.dart (main)"?-->

\`\`\`dart void main() { // 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', (tester) async { // Test code goes here. }); } \`\`\` ## 4. Build the widget using the \`WidgetTester\` Next, build \`MyWidget\` inside the test environment by using the \[\`pumpWidget()\`]\[] method provided by \`WidgetTester\`. The \`pumpWidget\` method builds and renders the provided widget. Create a \`MyWidget\` instance that displays "T" as the title and "M" as the message.

<!--?code-excerpt "test/main_step4_test.dart (main)"?-->

\`\`\`dart void main() { testWidgets('MyWidget has a title and message', (tester) async { // Create the widget by telling the tester to build it. await tester.pumpWidget(const MyWidget(title: 'T', message: 'M')); }); } \`\`\` ### Notes about the pump() methods 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, 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(Duration duration)\`]\[] : Schedules a frame and triggers a rebuild of the widget. If a \`Duration\` is specified, it advances the clock by that amount and schedules a frame. It does not schedule multiple frames even if the duration is longer than a single frame. :::note To kick off the animation, you need to call \`pump()\` once (with no duration specified) to start the ticker. Without it, the animation does not start. ::: \[\`tester.pumpAndSettle()\`]\[] : 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. ## 5. Search for our widget using a \`Finder\` With a widget in the test environment, search through the widget tree for the \`title\` and \`message\` Text widgets using a \`Finder\`. This allows verification that the widgets are being displayed correctly. For this purpose, use the top-level \[\`find()\`]\[] method provided by the \`flutter\_test\` package to create the \`Finders\`. Since you know you're looking for \`Text\` widgets, use the \[\`find.text()\`]\[] method. For more information about \`Finder\` classes, see the \[Finding widgets in a widget test]\[] recipe.

<!--?code-excerpt "test/main_step5_test.dart (main)"?-->

\`\`\`dart void main() { testWidgets('MyWidget has a title and message', (tester) async { await tester.pumpWidget(const MyWidget(title: 'T', message: 'M')); // Create the Finders. final titleFinder = find.text('T'); final messageFinder = find.text('M'); }); } \`\`\` ## 6. Verify the widget using a \`Matcher\` 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. Ensure that the widgets appear on screen exactly one time. For this purpose, use the \[\`findsOneWidget\`]\[] \`Matcher\`.

<!--?code-excerpt "test/main_step6_test.dart (main)"?-->

\`\`\`dart void main() { testWidgets('MyWidget has a title and message', (tester) async { await tester.pumpWidget(const MyWidget(title: 'T', message: 'M')); final titleFinder = find.text('T'); final messageFinder = find.text('M'); // 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); }); } \`\`\` ### Additional Matchers In addition to \`findsOneWidget\`, \`flutter\_test\` provides additional matchers for common cases. \[\`findsNothing\`]\[] : Verifies that no widgets are found. \[\`findsWidgets\`]\[] : Verifies that one or more widgets are found. \[\`findsNWidgets\`]\[] : Verifies that a specific number of widgets are found. \[\`matchesGoldenFile\`]\[] : Verifies that a widget's rendering matches a particular bitmap image ("golden file" testing). ## Complete example

<!--?code-excerpt "test/main_test.dart"?-->

\`\`\`dart import 'package:flutter\_test/flutter\_test.dart'; import 'package:material\_ui/material\_ui.dart'; void main() { // 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', (tester) async { // Create the widget by telling the tester to build it. await tester.pumpWidget(const MyWidget(title: 'T', message: 'M')); // Create the Finders. final titleFinder = find.text('T'); final messageFinder = find.text('M'); // 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); }); } class MyWidget extends StatelessWidget { const MyWidget({super.key, required this.title, required this.message}); final String title; final String message; @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', home: Scaffold( appBar: AppBar(title: Text(title)), body: Center(child: Text(message)), ), ); } } \`\`\` \[\`find()\`]: https\://api.flutter.dev/flutter/flutter\_test/find-constant.html \[\`find.text()\`]: https\://api.flutter.dev/flutter/flutter\_test/CommonFinders/text.html \[\`findsNothing\`]: https\://api.flutter.dev/flutter/flutter\_test/findsNothing-constant.html \[\`findsOneWidget\`]: https\://api.flutter.dev/flutter/flutter\_test/findsOneWidget-constant.html \[\`findsNWidgets\`]: https\://api.flutter.dev/flutter/flutter\_test/findsNWidgets.html \[\`findsWidgets\`]: https\://api.flutter.dev/flutter/flutter\_test/findsWidgets-constant.html \[\`matchesGoldenFile\`]: https\://api.flutter.dev/flutter/flutter\_test/matchesGoldenFile.html \[\`Finder\`]: https\://api.flutter.dev/flutter/flutter\_test/Finder-class.html \[Finding widgets in a widget test]: /cookbook/testing/widget/finders \[\`flutter\_test\`]: https\://api.flutter.dev/flutter/flutter\_test/flutter\_test-library.html \[introduction to unit testing]: /cookbook/testing/unit/introduction \[\`Matcher\`]: https\://api.flutter.dev/flutter/package-matcher\_matcher/Matcher-class.html \[\`pumpWidget()\`]: https\://api.flutter.dev/flutter/flutter\_test/WidgetTester/pumpWidget.html \[\`tester.pump(Duration duration)\`]: https\://api.flutter.dev/flutter/flutter\_test/TestWidgetsFlutterBinding/pump.html \[\`tester.pumpAndSettle()\`]: https\://api.flutter.dev/flutter/flutter\_test/WidgetTester/pumpAndSettle.html \[\`testWidgets()\`]: https\://api.flutter.dev/flutter/flutter\_test/testWidgets.html \[\`WidgetTester\`]: https\://api.flutter.dev/flutter/flutter\_test/WidgetTester-class.html

----
url: https://docs.flutter.dev/add-to-app/ios/add-flutter-screen.md
----

\# Add a Flutter screen to an iOS app > Learn how to add a single Flutter screen to your existing iOS app. This guide describes how to add a single Flutter screen to an existing iOS app. ## Start a FlutterEngine and FlutterViewController To launch a Flutter screen from an existing iOS app, you start a \[\`FlutterEngine\`]\[] and a \[\`FlutterViewController\`]\[]. :::note The \`FlutterEngine\` serves as a host to the Dart VM and your Flutter runtime, and the \`FlutterViewController\` attaches to a \`FlutterEngine\` to pass input events into Flutter and to display frames rendered by the \`FlutterEngine\`. ::: The \`FlutterEngine\` might have the same lifespan as your \`FlutterViewController\` or outlive your \`FlutterViewController\`. :::tip It's generally recommended to pre-warm a long-lived \`FlutterEngine\` for your application because: \* The first frame appears faster when showing the \`FlutterViewController\`. \* Your Flutter and Dart state will outlive one \`FlutterViewController\`. \* Your application and your plugins can interact with Flutter and your Dart logic before showing the UI. ::: See \[Loading sequence and performance]\[] for more analysis on the latency and memory trade-offs of pre-warming an engine. ### Create a FlutterEngine Where you create a \`FlutterEngine\` depends on your host app. The following example creates a \`FlutterEngine\` object inside a SwiftUI \[\`Observable\`]\[] object called \`FlutterDependencies\`. Pre-warm the engine by calling \`run()\`, and then inject this object into a \`ContentView\` using the \`environment()\` view modifier. \`\`\`swift title="MyApp.swift" import SwiftUI import Flutter // The following library connects plugins with iOS platform code to this app. import FlutterPluginRegistrant @Observable class FlutterDependencies { let flutterEngine = FlutterEngine(name: "my flutter engine") init() { // Runs the default Dart entrypoint with a default Flutter route. flutterEngine.run() // Connects plugins with iOS platform code to this app. GeneratedPluginRegistrant.register(with: self.flutterEngine); } } @main struct MyApp: App { // flutterDependencies will be injected through the view environment. @State var flutterDependencies = FlutterDependencies() var body: some Scene { WindowGroup { ContentView() .environment(flutterDependencies) } } } \`\`\` The following example demonstrates creating a \`FlutterEngine\`, exposed as a property, on app startup in the app delegate. \`\`\`swift title="AppDelegate.swift" import UIKit import Flutter // The following library connects plugins with iOS platform code to this app. import FlutterPluginRegistrant @UIApplicationMain class AppDelegate: FlutterAppDelegate { // More on FlutterAppDelegate and FlutterSceneDelegate below. lazy var flutterEngine = FlutterEngine(name: "my flutter engine") override func application(\_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: \[UIApplication.LaunchOptionsKey: Any]?) -> Bool { // Runs the default Dart entrypoint with a default Flutter route. flutterEngine.run(); // Connects plugins with iOS platform code to this app. GeneratedPluginRegistrant.register(with: self.flutterEngine); return super.application(application, didFinishLaunchingWithOptions: launchOptions); } } \`\`\` The following example demonstrates creating a \`FlutterEngine\`, exposed as a property, on app startup in the app delegate. \`\`\`objc title="AppDelegate.h" @import UIKit; @import Flutter; @interface AppDelegate : FlutterAppDelegate // More on FlutterAppDelegate and FlutterSceneDelegate below. @property (nonatomic,strong) FlutterEngine \*flutterEngine; @end \`\`\` \`\`\`objc title="AppDelegate.m" // The following library connects plugins with iOS platform code to this app. #import #import "AppDelegate.h" @implementation AppDelegate - (BOOL)application:(UIApplication \*)application didFinishLaunchingWithOptions:(NSDictionary \*)launchOptions { self.flutterEngine = \[\[FlutterEngine alloc] initWithName:@"my flutter engine"]; // Runs the default Dart entrypoint with a default Flutter route. \[self.flutterEngine run]; // Connects plugins with iOS platform code to this app. \[GeneratedPluginRegistrant registerWithRegistry:self.flutterEngine]; return \[super application:application didFinishLaunchingWithOptions:launchOptions]; } @end \`\`\` ### Show a FlutterViewController with your FlutterEngine The following example shows a generic \`ContentView\` with a \[\`NavigationLink\`]\[] hooked to a flutter screen. First, create a \`FlutterViewControllerRepresentable\` to represent the \`FlutterViewController\`. The \`FlutterViewController\` constructor takes the pre-warmed \`FlutterEngine\` as an argument, which is injected through the view environment. \`\`\`swift title="ContentView\.swift" import SwiftUI import Flutter struct FlutterViewControllerRepresentable: UIViewControllerRepresentable { // Flutter dependencies are passed in through the view environment. @Environment(FlutterDependencies.self) var flutterDependencies func makeUIViewController(context: Context) -> some UIViewController { return FlutterViewController( engine: flutterDependencies.flutterEngine, nibName: nil, bundle: nil) } func updateUIViewController(\_ uiViewController: UIViewControllerType, context: Context) {} } struct ContentView: View { var body: some View { NavigationStack { NavigationLink("My Flutter Feature") { FlutterViewControllerRepresentable() } } } } \`\`\` Now, you have a Flutter screen embedded in your iOS app. :::note In this example, your Dart \`main()\` entrypoint function runs when the \`FlutterDependencies\` observable is initialized. ::: The following example shows a generic \`ViewController\` with a \`UIButton\` hooked to present a \[\`FlutterViewController\`]\[]. The \`FlutterViewController\` uses the \`FlutterEngine\` instance created in the \`AppDelegate\`. \`\`\`swift title="ViewController.swift" import UIKit import Flutter class ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() // Make a button to call the showFlutter function when pressed. let button = UIButton(type:UIButton.ButtonType.custom) button.addTarget(self, action: #selector(showFlutter), for: .touchUpInside) button.setTitle("Show Flutter!", for: UIControl.State.normal) button.frame = CGRect(x: 80.0, y: 210.0, width: 160.0, height: 40.0) button.backgroundColor = UIColor.blue self.view\.addSubview(button) } @objc func showFlutter() { let flutterEngine = (UIApplication.shared.delegate as! AppDelegate).flutterEngine let flutterViewController = FlutterViewController(engine: flutterEngine, nibName: nil, bundle: nil) present(flutterViewController, animated: true, completion: nil) } } \`\`\` Now, you have a Flutter screen embedded in your iOS app. :::note Using the previous example, the default \`main()\` entrypoint function of your default Dart library would run when calling \`run\` on the \`FlutterEngine\` created in the \`AppDelegate\`. ::: The following example shows a generic \`ViewController\` with a \`UIButton\` hooked to present a \[\`FlutterViewController\`]\[]. The \`FlutterViewController\` uses the \`FlutterEngine\` instance created in the \`AppDelegate\`. \`\`\`objc title="ViewController.m" @import Flutter; #import "AppDelegate.h" #import "ViewController.h" @implementation ViewController - (void)viewDidLoad { \[super viewDidLoad]; // Make a button to call the showFlutter function when pressed. UIButton \*button = \[UIButton buttonWithType:UIButtonTypeCustom]; \[button addTarget:self action:@selector(showFlutter) forControlEvents:UIControlEventTouchUpInside]; \[button setTitle:@"Show Flutter!" forState:UIControlStateNormal]; button.backgroundColor = UIColor.blueColor; button.frame = CGRectMake(80.0, 210.0, 160.0, 40.0); \[self.view addSubview:button]; } - (void)showFlutter { FlutterEngine \*flutterEngine = ((AppDelegate \*)UIApplication.sharedApplication.delegate).flutterEngine; FlutterViewController \*flutterViewController = \[\[FlutterViewController alloc] initWithEngine:flutterEngine nibName:nil bundle:nil]; \[self presentViewController:flutterViewController animated:YES completion:nil]; } @end \`\`\` Now, you have a Flutter screen embedded in your iOS app. :::note Using the previous example, the default \`main()\` entrypoint function of your default Dart library would run when calling \`run\` on the \`FlutterEngine\` created in the \`AppDelegate\`. ::: ### \_Alternatively\_ - Create a FlutterViewController with an implicit FlutterEngine As an alternative to the previous example, you can let the \`FlutterViewController\` implicitly create its own \`FlutterEngine\` without pre-warming one ahead of time. This is not usually recommended because creating a \`FlutterEngine\` on-demand could introduce a noticeable latency between when the \`FlutterViewController\` is presented and when it renders its first frame. This could, however, be useful if the Flutter screen is rarely shown, when there are no good heuristics to determine when the Dart VM should be started, and when Flutter doesn't need to persist state between view controllers. To let the \`FlutterViewController\` present without an existing \`FlutterEngine\`, omit the \`FlutterEngine\` construction, and create the \`FlutterViewController\` without an engine reference. \`\`\`swift title="ContentView\.swift" import SwiftUI import Flutter struct FlutterViewControllerRepresentable: UIViewControllerRepresentable { func makeUIViewController(context: Context) -> some UIViewController { return FlutterViewController( project: nil, nibName: nil, bundle: nil) } func updateUIViewController(\_ uiViewController: UIViewControllerType, context: Context) {} } struct ContentView: View { var body: some View { NavigationStack { NavigationLink("My Flutter Feature") { FlutterViewControllerRepresentable() } } } } \`\`\` \`\`\`swift title="ViewController.swift" // Existing code omitted. func showFlutter() { let flutterViewController = FlutterViewController(project: nil, nibName: nil, bundle: nil) present(flutterViewController, animated: true, completion: nil) } \`\`\` \`\`\`objc title="ViewController.m" // Existing code omitted. - (void)showFlutter { FlutterViewController \*flutterViewController = \[\[FlutterViewController alloc] initWithProject:nil nibName:nil bundle:nil]; \[self presentViewController:flutterViewController animated:YES completion:nil]; } @end \`\`\` See \[Loading sequence and performance]\[] for more explorations on latency and memory usage. []()## Using FlutterAppDelegate and FlutterSceneDelegate As of Flutter 3.41, \`UIScene\` support is the default for iOS apps, and Apple requires UIKit apps built with recent iOS SDKs to use the \`UIScene\` lifecycle. In this architecture, responsibilities are split between two delegates: \* \`FlutterAppDelegate\`: Handles process events and the overall application lifecycle (such as remote notifications and background execution), and keeps the Flutter debug connection open when the device locks. \* \`FlutterSceneDelegate\`: Handles UI and scene lifecycle events, forwarding callbacks such as \[\`scene(\_:openURLContexts:)\`]\[] and \[\`scene(\_:continue:)\`]\[] to plugins such as \[\`local\_auth\`]\[]. To ensure Flutter plugins receive all necessary lifecycle events, adopt both \`FlutterAppDelegate\` (or \`FlutterAppLifeCycleProvider\`) and \`FlutterSceneDelegate\` (or \`FlutterSceneLifeCycleProvider\`). For more details on the \`UIScene\` lifecycle migration, see the \[UIScene migration guide]\[]. ### Using the FlutterAppDelegate Subclassing \`FlutterAppDelegate\` in your \`UIApplicationDelegate\` is recommended but not required. #### Create a FlutterAppDelegate subclass Creating a subclass of \`FlutterAppDelegate\` in UIKit apps was shown in the \[Start a FlutterEngine and FlutterViewController section]\[]. In a SwiftUI app, you can create a subclass of \`FlutterAppDelegate\`, implement \`application(\_:configurationForConnecting:options:)\` to specify \`FlutterSceneDelegate\`, and annotate the class with the \[\`Observable()\`]\[] macro: \`\`\`swift title="MyApp.swift" import SwiftUI import Flutter import FlutterPluginRegistrant @Observable class AppDelegate: FlutterAppDelegate { let flutterEngine = FlutterEngine(name: "my flutter engine") override func application( \_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: \[UIApplication.LaunchOptionsKey: Any]? ) -> Bool { // Runs the default Dart entrypoint with a default Flutter route. flutterEngine.run() // Used to connect plugins (only if you have plugins with iOS platform code). GeneratedPluginRegistrant.register(with: self.flutterEngine) return true } override func application( \_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions ) -> UISceneConfiguration { let configuration = UISceneConfiguration( name: nil, sessionRole: connectingSceneSession.role ) configuration.delegateClass = FlutterSceneDelegate.self return configuration } } @main struct MyApp: App { // Use this property wrapper to tell SwiftUI // it should use the AppDelegate class for the application delegate. @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate var body: some Scene { WindowGroup { ContentView() } } } \`\`\` Then, in your view, the \`AppDelegate\` is accessible through the view environment. \`\`\`swift title="ContentView\.swift" import SwiftUI import Flutter struct FlutterViewControllerRepresentable: UIViewControllerRepresentable { // Access the AppDelegate through the view environment. @Environment(AppDelegate.self) var appDelegate func makeUIViewController(context: Context) -> some UIViewController { return FlutterViewController( engine: appDelegate.flutterEngine, nibName: nil, bundle: nil ) } func updateUIViewController(\_ uiViewController: UIViewControllerType, context: Context) {} } struct ContentView: View { var body: some View { NavigationStack { NavigationLink("My Flutter Feature") { FlutterViewControllerRepresentable() } } } } \`\`\` #### If you can't subclass FlutterAppDelegate If your app delegate cannot subclass \`FlutterAppDelegate\`, conform to the \`FlutterAppLifeCycleProvider\` protocol and use a \`FlutterPluginAppLifeCycleDelegate\` to ensure plugins receive application-level callbacks. \`\`\`swift title="AppDelegate.swift" import Foundation import Flutter @Observable class AppDelegate: UIResponder, UIApplicationDelegate, FlutterAppLifeCycleProvider { private let lifecycleDelegate = FlutterPluginAppLifeCycleDelegate() let flutterEngine = FlutterEngine(name: "my flutter engine") func application( \_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: \[UIApplication.LaunchOptionsKey: Any]? = nil ) -> Bool { flutterEngine.run() return lifecycleDelegate.application( application, didFinishLaunchingWithOptions: launchOptions ?? \[:] ) } func application( \_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data ) { lifecycleDelegate.application( application, didRegisterForRemoteNotificationsWithDeviceToken: deviceToken ) } func application( \_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error ) { lifecycleDelegate.application( application, didFailToRegisterForRemoteNotificationsWithError: error ) } func application( \_ application: UIApplication, didReceiveRemoteNotification userInfo: \[AnyHashable: Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void ) { lifecycleDelegate.application( application, didReceiveRemoteNotification: userInfo, fetchCompletionHandler: completionHandler ) } func application( \_ app: UIApplication, open url: URL, options: \[UIApplication.OpenURLOptionsKey: Any] = \[:] ) -> Bool { return lifecycleDelegate.application(app, open: url, options: options) } func application(\_ application: UIApplication, handleOpen url: URL) -> Bool { return lifecycleDelegate.application(application, handleOpen: url) } func application( \_ application: UIApplication, open url: URL, sourceApplication: String?, annotation: Any ) -> Bool { return lifecycleDelegate.application( application, open: url, sourceApplication: sourceApplication ?? "", annotation: annotation ) } func application( \_ application: UIApplication, performActionFor shortcutItem: UIApplicationShortcutItem, completionHandler: @escaping (Bool) -> Void ) { lifecycleDelegate.application( application, performActionFor: shortcutItem, completionHandler: completionHandler ) } func application( \_ application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: @escaping () -> Void ) { lifecycleDelegate.application( application, handleEventsForBackgroundURLSession: identifier, completionHandler: completionHandler ) } func application( \_ application: UIApplication, performFetchWithCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void ) { lifecycleDelegate.application( application, performFetchWithCompletionHandler: completionHandler ) } func add(\_ delegate: FlutterApplicationLifeCycleDelegate) { lifecycleDelegate.add(delegate) } } \`\`\` \`\`\`objc title="AppDelegate.h" @import Flutter; @import UIKit; @import FlutterPluginRegistrant; @interface AppDelegate : UIResponder @property (strong, nonatomic) UIWindow \*window; @property (nonatomic, strong) FlutterEngine \*flutterEngine; @end \`\`\` \`\`\`objc title="AppDelegate.m" #import "AppDelegate.h" @interface AppDelegate () @property (nonatomic, strong) FlutterPluginAppLifeCycleDelegate \*lifeCycleDelegate; @end @implementation AppDelegate - (instancetype)init { if (self = \[super init]) { \_lifeCycleDelegate = \[\[FlutterPluginAppLifeCycleDelegate alloc] init]; } return self; } - (BOOL)application:(UIApplication \*)application didFinishLaunchingWithOptions:(NSDictionary \*)launchOptions { self.flutterEngine = \[\[FlutterEngine alloc] initWithName:@"io.flutter" project:nil]; \[self.flutterEngine runWithEntrypoint:nil]; \[GeneratedPluginRegistrant registerWithRegistry:self.flutterEngine]; return \[\_lifeCycleDelegate application:application didFinishLaunchingWithOptions:launchOptions]; } // Returns the key window's rootViewController, if it's a FlutterViewController. // Otherwise, returns nil. - (FlutterViewController \*)rootFlutterViewController { UIViewController \*viewController = \[UIApplication sharedApplication].keyWindow\.rootViewController; if (\[viewController isKindOfClass:\[FlutterViewController class]]) { return (FlutterViewController \*)viewController; } return nil; } - (void)application:(UIApplication \*)application didRegisterUserNotificationSettings:(UIUserNotificationSettings \*)notificationSettings { \[\_lifeCycleDelegate application:application didRegisterUserNotificationSettings:notificationSettings]; } - (void)application:(UIApplication \*)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData \*)deviceToken { \[\_lifeCycleDelegate application:application didRegisterForRemoteNotificationsWithDeviceToken:deviceToken]; } - (void)application:(UIApplication \*)application didReceiveRemoteNotification:(NSDictionary \*)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler { \[\_lifeCycleDelegate application:application didReceiveRemoteNotification:userInfo fetchCompletionHandler:completionHandler]; } - (BOOL)application:(UIApplication \*)application openURL:(NSURL \*)url options:(NSDictionary \*)options { return \[\_lifeCycleDelegate application:application openURL:url options:options]; } - (BOOL)application:(UIApplication \*)application handleOpenURL:(NSURL \*)url { return \[\_lifeCycleDelegate application:application handleOpenURL:url]; } - (BOOL)application:(UIApplication \*)application openURL:(NSURL \*)url sourceApplication:(NSString \*)sourceApplication annotation:(id)annotation { return \[\_lifeCycleDelegate application:application openURL:url sourceApplication:sourceApplication annotation:annotation]; } - (void)application:(UIApplication \*)application performActionForShortcutItem:(UIApplicationShortcutItem \*)shortcutItem completionHandler:(void (^)(BOOL succeeded))completionHandler { \[\_lifeCycleDelegate application:application performActionForShortcutItem:shortcutItem completionHandler:completionHandler]; } - (void)application:(UIApplication \*)application handleEventsForBackgroundURLSession:(nonnull NSString \*)identifier completionHandler:(nonnull void (^)(void))completionHandler { \[\_lifeCycleDelegate application:application handleEventsForBackgroundURLSession:identifier completionHandler:completionHandler]; } - (void)application:(UIApplication \*)application performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler { \[\_lifeCycleDelegate application:application performFetchWithCompletionHandler:completionHandler]; } - (void)addApplicationLifeCycleDelegate:(NSObject \*)delegate { \[\_lifeCycleDelegate addDelegate:delegate]; } @end \`\`\` ### Using the FlutterSceneDelegate Similar to \`FlutterAppDelegate\`, subclassing \`FlutterSceneDelegate\` is recommended but not required. The \`FlutterSceneDelegate\` forwards scene callbacks, such as \[\`scene(\_:openURLContexts:)\`]\[] and \[\`scene(\_:continue:)\`]\[], to plugins such as \[\`local\_auth\`]\[]. #### Create or update a SceneDelegate In UIKit apps, update your \`SceneDelegate\` to subclass \`FlutterSceneDelegate\`: \`\`\`swift title="SceneDelegate.swift" diff import UIKit + import Flutter - class SceneDelegate: UIResponder, UIWindowSceneDelegate { + class SceneDelegate: FlutterSceneDelegate { \`\`\` \`\`\`objc title="SceneDelegate.h" diff #import + #import - @interface SceneDelegate : UIResponder + @interface SceneDelegate : FlutterSceneDelegate \`\`\` In SwiftUI apps, configure the scene delegate class in \`application(\_:configurationForConnecting:options:)\` as shown in the \[FlutterAppDelegate SwiftUI example]\[FlutterAppDelegate SwiftUI example]. If your app does not support multiple scenes, set \*\*Enable Multiple Scenes\*\* (\`UIApplicationSupportsMultipleScenes\`) to \*\*NO\*\* under \*\*Application Scene Manifest\*\* in your target's \`Info.plist\`. #### If you can't subclass FlutterSceneDelegate If your \`SceneDelegate\` cannot subclass \`FlutterSceneDelegate\`, adopt the \`FlutterSceneLifeCycleProvider\` protocol and use a \`FlutterPluginSceneLifeCycleDelegate\` object to forward scene lifecycle events to Flutter: \`\`\`swift title="SceneDelegate.swift" diff import UIKit + import Flutter - class SceneDelegate: UIResponder, UIWindowSceneDelegate { + class SceneDelegate: UIResponder, UIWindowSceneDelegate, FlutterSceneLifeCycleProvider { + var sceneLifeCycleDelegate = FlutterPluginSceneLifeCycleDelegate() var window: UIWindow? func scene( \_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions ) { + sceneLifeCycleDelegate.scene( + scene, + willConnectTo: session, + options: connectionOptions + ) } func sceneDidDisconnect(\_ scene: UIScene) { + sceneLifeCycleDelegate.sceneDidDisconnect(scene) } func sceneWillEnterForeground(\_ scene: UIScene) { + sceneLifeCycleDelegate.sceneWillEnterForeground(scene) } func sceneDidBecomeActive(\_ scene: UIScene) { + sceneLifeCycleDelegate.sceneDidBecomeActive(scene) } func sceneWillResignActive(\_ scene: UIScene) { + sceneLifeCycleDelegate.sceneWillResignActive(scene) } func sceneDidEnterBackground(\_ scene: UIScene) { + sceneLifeCycleDelegate.sceneDidEnterBackground(scene) } func scene( \_ scene: UIScene, openURLContexts URLContexts: Set ) { + sceneLifeCycleDelegate.scene(scene, openURLContexts: URLContexts) } func scene(\_ scene: UIScene, continue userActivity: NSUserActivity) { + sceneLifeCycleDelegate.scene(scene, continue: userActivity) } func windowScene( \_ windowScene: UIWindowScene, performActionFor shortcutItem: UIApplicationShortcutItem, completionHandler: @escaping (Bool) -> Void ) { + sceneLifeCycleDelegate.windowScene( + windowScene, + performActionFor: shortcutItem, + completionHandler: completionHandler + ) } } \`\`\` \`\`\`objc title="SceneDelegate.h" diff #import + #import - @interface SceneDelegate : UIResponder + @interface SceneDelegate : UIResponder @property (strong, nonatomic) UIWindow \*window; + @property (nonatomic, strong) FlutterPluginSceneLifeCycleDelegate \*sceneLifeCycleDelegate; @end \`\`\` \`\`\`objc title="SceneDelegate.m" diff #import "SceneDelegate.h" @implementation SceneDelegate - (instancetype)init { if (self = \[super init]) { + \_sceneLifeCycleDelegate = \[\[FlutterPluginSceneLifeCycleDelegate alloc] init]; } return self; } - (void)scene:(UIScene \*)scene willConnectToSession:(UISceneSession \*)session options:(UISceneConnectionOptions \*)connectionOptions { + \[self.sceneLifeCycleDelegate scene:scene willConnectToSession:session options:connectionOptions]; } - (void)sceneDidDisconnect:(UIScene \*)scene { + \[self.sceneLifeCycleDelegate sceneDidDisconnect:scene]; } - (void)sceneDidBecomeActive:(UIScene \*)scene { + \[self.sceneLifeCycleDelegate sceneDidBecomeActive:scene]; } - (void)sceneWillResignActive:(UIScene \*)scene { + \[self.sceneLifeCycleDelegate sceneWillResignActive:scene]; } - (void)sceneWillEnterForeground:(UIScene \*)scene { + \[self.sceneLifeCycleDelegate sceneWillEnterForeground:scene]; } - (void)sceneDidEnterBackground:(UIScene \*)scene { + \[self.sceneLifeCycleDelegate sceneDidEnterBackground:scene]; } - (void)scene:(UIScene \*)scene openURLContexts:(NSSet \*)URLContexts { + \[self.sceneLifeCycleDelegate scene:scene openURLContexts:URLContexts]; } - (void)scene:(UIScene \*)scene continueUserActivity:(NSUserActivity \*)userActivity { + \[self.sceneLifeCycleDelegate scene:scene continueUserActivity:userActivity]; } - (void)windowScene:(UIWindowScene \*)windowScene performActionForShortcutItem:(UIApplicationShortcutItem \*)shortcutItem completionHandler:(void (^)(BOOL))completionHandler { + \[self.sceneLifeCycleDelegate windowScene:windowScene + performActionForShortcutItem:shortcutItem + completionHandler:completionHandler]; } @end \`\`\` ## Launch options The examples demonstrate running Flutter using the default launch settings. In order to customize your Flutter runtime, you can also specify the Dart entrypoint, library, and route. ### Dart entrypoint Calling \`run\` on a \`FlutterEngine\`, by default, runs the \`main()\` Dart function of your \`lib/main.dart\` file. You can also run a different entrypoint function by using \[\`runWithEntrypoint\`]\[] with an \`NSString\` specifying a different Dart function. :::note Dart entrypoint functions other than \`main()\` must be annotated with the following in order to not be \[tree-shaken]\[] away when compiling: \`\`\`dart @pragma('vm:entry-point') void myOtherEntrypoint() { ... }; \`\`\` ::: ### Dart library In addition to specifying a Dart function, you can specify an entrypoint function in a specific file. For instance the following runs \`myOtherEntrypoint()\` in \`lib/other\_file.dart\` instead of \`main()\` in \`lib/main.dart\`: \`\`\`swift flutterEngine.run(withEntrypoint: "myOtherEntrypoint", libraryURI: "other\_file.dart") \`\`\` \`\`\`objc \[flutterEngine runWithEntrypoint:@"myOtherEntrypoint" libraryURI:@"other\_file.dart"]; \`\`\` ### Route Starting in Flutter version 1.22, an initial route can be set for your Flutter \[\`WidgetsApp\`]\[] when constructing the FlutterEngine or the FlutterViewController. \`\`\`swift let flutterEngine = FlutterEngine() // FlutterDefaultDartEntrypoint is the same as nil, which will run main(). engine.run( withEntrypoint: "main", initialRoute: "/onboarding") \`\`\` \`\`\`objc FlutterEngine \*flutterEngine = \[\[FlutterEngine alloc] init]; // FlutterDefaultDartEntrypoint is the same as nil, which will run main(). \[flutterEngine runWithEntrypoint:FlutterDefaultDartEntrypoint initialRoute:@"/onboarding"]; \`\`\` This code sets your \`dart:ui\`'s \[\`PlatformDispatcher.defaultRouteName\`]\[] to \`"/onboarding"\` instead of \`"/"\`. Alternatively, to construct a FlutterViewController directly without pre-warming a FlutterEngine: \`\`\`swift let flutterViewController = FlutterViewController( project: nil, initialRoute: "/onboarding", nibName: nil, bundle: nil) \`\`\` \`\`\`objc FlutterViewController\* flutterViewController = \[\[FlutterViewController alloc] initWithProject:nil initialRoute:@"/onboarding" nibName:nil bundle:nil]; \`\`\` :::tip In order to imperatively change your current Flutter route from the platform side after the \`FlutterEngine\` is already running, use \[\`pushRoute()\`]\[] or \[\`popRoute()\`] on the \`FlutterViewController\`. To pop the iOS route from the Flutter side, call \[\`SystemNavigator.pop()\`]\[]. ::: See \[Navigation and routing]\[] for more about Flutter's routes. ### Other The previous example only illustrates a few ways to customize how a Flutter instance is initiated. Using \[platform channels]\[], you're free to push data or prepare your Flutter environment in any way you'd like, before presenting the Flutter UI using a \`FlutterViewController\`. ## Content-sized views On iOS, you can also set your embedded \`FlutterView\` to size itself based off its content. \`\`\`swift let flutterViewController = FlutterViewController(engine: engine, nibName: nil, bundle: nil) flutterViewController.isAutoResizable = true \`\`\` \`\`\`objc \_flutterViewController = \[\[FlutterViewController alloc] initWithEngine:engine nibName:nil bundle:nil]; \_flutterViewController.autoResizable = YES; \`\`\` ### Restrictions To use this, your root widget must support unbounded constraints. Avoid using widgets that require bounded constraints (like \`ListView\` or \`LayoutBuilder\`) at the top of your tree, as they can conflict with the dynamic sizing logic. In practice, this means that quite a few common widgets are not supported, such as \`ScaffoldBuilder\`, \`CupertinoTimerPicker\`, or any widget that internally relies on a \`LayoutBuilder\`. When in doubt, you can use an \`UnconstrainedBox\` to test the usability of a widget for a content-sized view, as in the following example: \`\`\`dart import 'package:material\_ui/material\_ui.dart'; void main() => runApp(MyApp()); class MyApp extends StatelessWidget { @override Widget build(BuildContext context) => MaterialApp(home: MyPage()); } class MyPage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( body: UnconstrainedBox( // TODO: Edit this line to check if a widget // can cause problems with content-sized views. child: Text('This works!'), // child: Column(children: \[Column(children: \[Expanded(child: Text('This blows up!'))])]), // child: ListView(children: \[Text('This blows up!')]), ) ); } } \`\`\` For a working example, refer to this \[sample project]\[]. \[\`FlutterEngine\`]: https\://api.flutter.dev/ios-embedder/interface\_flutter\_engine.html \[\`FlutterViewController\`]: https\://api.flutter.dev/ios-embedder/interface\_flutter\_view\_controller.html \[Loading sequence and performance]: /add-to-app/performance \[local\_auth]: https\://pub.dev/packages/local\_auth \[Navigation and routing]: /ui/navigation \[Navigator]: https\://api.flutter.dev/flutter/widgets/Navigator-class.html \[\`NavigatorState\`]: https\://api.flutter.dev/flutter/widgets/NavigatorState-class.html \[\`openURL\`]: https\://developer.apple.com/documentation/uikit/uiapplicationdelegate/1623112-application \[platform channels]: /platform-integration/platform-channels \[\`popRoute()\`]: https\://api.flutter.dev/ios-embedder/interface\_flutter\_view\_controller.html#ac89c8010fbf7a39f7aaab64f68c013d2 \[\`pushRoute()\`]: https\://api.flutter.dev/ios-embedder/interface\_flutter\_view\_controller.html#ac7cffbf03f9c8c0b28d1f0dafddece4e \[\`runApp\`]: https\://api.flutter.dev/flutter/widgets/runApp.html \[\`runWithEntrypoint\`]: https\://api.flutter.dev/ios-embedder/interface\_flutter\_engine.html#a019d6b3037eff6cfd584fb2eb8e9035e \[\`SystemNavigator.pop()\`]: https\://api.flutter.dev/flutter/services/SystemNavigator/pop.html \[\`scene(\_:openURLContexts:)\`]: https\://developer.apple.com/documentation/uikit/uiscenedelegate/scene(\_:openurlcontexts:) \[\`scene(\_:continue:)\`]: https\://developer.apple.com/documentation/uikit/uiscenedelegate/scene(\_:continue:) \[UIScene migration guide]: /release/breaking-changes/uiscenedelegate#migration-guide-for-adding-flutter-to-existing-app-add-to-app \[FlutterAppDelegate SwiftUI example]: #create-a-flutterappdelegate-subclass \[\`local\_auth\`]: https\://pub.dev/packages/local\_auth \[tree-shaken]: https\://en.wikipedia.org/wiki/Tree\_shaking \[\`WidgetsApp\`]: https\://api.flutter.dev/flutter/widgets/WidgetsApp-class.html \[\`PlatformDispatcher.defaultRouteName\`]: https\://api.flutter.dev/flutter/dart-ui/PlatformDispatcher/defaultRouteName.html \[Start a FlutterEngine and FlutterViewController section]: /add-to-app/ios/add-flutter-screen#start-a-flutterengine-and-flutterviewcontroller \[\`Observable\`]: https\://developer.apple.com/documentation/observation/observable \[\`NavigationLink\`]: https\://developer.apple.com/documentation/swiftui/navigationlink \[\`Observable()\`]: https\://developer.apple.com/documentation/observation/observable() \[sample project]: https\://github.com/flutter/samples/tree/main/add\_to\_app/ios\_content\_resizing

----
url: https://docs.flutter.dev/cookbook/forms/retrieve-input
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Retrieve the value of a text field](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

In this recipe, learn how to retrieve the text a user has entered into a text field using the following steps:

1. Create a `TextEditingController`.
2. Supply the `TextEditingController` to a `TextField`.
3. Display the current value of the text field.

## 1. Create a `TextEditingController`

[#](#1-create-a-texteditingcontroller)

To retrieve the text a user has entered into a text field, create a [`TextEditingController`](https://api.flutter.dev/flutter/widgets/TextEditingController-class.html) and supply it to a `TextField` or `TextFormField`.

dart

```
// Define a custom Form widget.
class MyCustomForm extends StatefulWidget {
  const MyCustomForm({super.key});
​
  @override
  State<MyCustomForm> createState() => _MyCustomFormState();
}
​
// Define a corresponding State class.
// This class holds the data related to the Form.
class _MyCustomFormState extends State<MyCustomForm> {
  // 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.
    myController.dispose();
    super.dispose();
  }
​
  @override
  Widget build(BuildContext context) {
    // Fill this out in the next step.
  }
}
```

## 2. Supply the `TextEditingController` to a `TextField`

[#](#2-supply-the-texteditingcontroller-to-a-textfield)

Now that you have a `TextEditingController`, wire it up to a text field using the `controller` property:

dart

```
return TextField(controller: myController);
```

## 3. Display the current value of the text field

[#](#3-display-the-current-value-of-the-text-field)

After supplying the `TextEditingController` to the text field, begin reading values. Use the [`text`](https://api.flutter.dev/flutter/widgets/TextEditingController/text.html) property provided by the `TextEditingController` to retrieve the `String` that the user has entered into the text field.

The following code displays 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 containing
  // the text that the user has entered into the text field.
  onPressed: () {
    showDialog<void>(
      context: context,
      builder: (context) {
        return AlertDialog(
          // Retrieve the text that the user has entered by
          // using the TextEditingController.
          content: Text(myController.text),
        );
      },
    );
  },
  tooltip: 'Show me the value!',
  child: const Icon(Icons.text_fields),
),
```

## Interactive example

[#](#interactive-example)

```
import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      title: 'Retrieve Text Input',
      home: MyCustomForm(),
    );
  }
}

// Define a custom Form widget.
class MyCustomForm extends StatefulWidget {
  const MyCustomForm({super.key});

  @override
  State<MyCustomForm> createState() => _MyCustomFormState();
}

// Define a corresponding State class.
// This class holds the data related to the Form.
class _MyCustomFormState extends State<MyCustomForm> {
  // 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.
    myController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Retrieve Text Input')),
      body: Padding(
        padding: const EdgeInsets.all(16),
        child: TextField(controller: myController),
      ),
      floatingActionButton: FloatingActionButton(
        // When the user presses the button, show an alert dialog containing
        // the text that the user has entered into the text field.
        onPressed: () {
          showDialog<void>(
            context: context,
            builder: (context) {
              return AlertDialog(
                // Retrieve the text that the user has entered by using the
                // TextEditingController.
                content: Text(myController.text),
              );
            },
          );
        },
        tooltip: 'Show me the value!',
        child: const Icon(Icons.text_fields),
      ),
    );
  }
}
```

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-10-06. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/forms/retrieve-input.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/cookbook/forms/retrieve-input\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/forms/retrieve-input.md "Report an issue with this page").

----
url: https://docs.flutter.dev/app-architecture/design-patterns/command.md
----

\# The command pattern > Simplify view model logic by implementing a Command class.

<!--?code-excerpt path-base="app-architecture/command"?-->

\[Model-View-ViewModel (MVVM)]\[] is a design pattern that separates a feature of an application into three parts: the model, the view model, and the view. Views and view models make up the UI layer of an application. Repositories and services represent the data layer of an application, or the model layer of MVVM. A command is a class that wraps a method and helps to handle the different states of that method, such as running, complete, and error. \[View models]\[] can use commands to handle interaction and run actions. You can also use them to display different UI states, like loading indicators when an action is running, or display an error dialog when an action failed. View models can become very complex as an application grows and features become bigger. Commands can help to simplify view models and reuse code. In this guide, you will learn how to use the command pattern to improve your view models. ## Challenges when implementing view models View model classes in Flutter are typically implemented by extending the \[\`ChangeNotifier\`]\[] class. This allows view models to call \`notifyListeners()\` to refresh views when data is updated.

<!--?code-excerpt "lib/no_command.dart (HomeViewModel2)" replace="/2//g"?-->

\`\`\`dart class HomeViewModel extends ChangeNotifier { // ··· } \`\`\` View models contain a representation of the UI state, including the data being displayed. For example, this \`HomeViewModel\` exposes the \`User\` instance to the view.

<!--?code-excerpt "lib/no_command.dart (getUser)" replace="/null;/\/\/ .../g;/2//g"?-->

\`\`\`dart class HomeViewModel extends ChangeNotifier { User? get user => // ... // ··· } \`\`\` View models also contain actions typically triggered by the view, such as a \`load\` action in charge of loading the \`user\`.

<!--?code-excerpt "lib/no_command.dart (load1)" replace="/null;/\/\/ .../g;/2//g"?-->

\`\`\`dart class HomeViewModel extends ChangeNotifier { User? get user => // ... // ··· void load() { // load user } // ··· } \`\`\` ### UI state in view models A view model also contains UI state besides data, such as whether the view is running or has experienced an error. This allows the app to tell the user if the action has completed successfully.

<!--?code-excerpt "lib/no_command.dart (UiState1)" replace="/(null|false);/\/\/ .../g;/2//g"?-->

\`\`\`dart class HomeViewModel extends ChangeNotifier { User? get user => // ... bool get running => // ... Exception? get error => // ... void load() { // load user } // ··· } \`\`\` You can use the running state to display a progress indicator in the view:

<!--?code-excerpt "lib/no_command.dart (ListenableBuilder)" replace="/\.load//g;/body: //g;/^\),$/)/g"?-->

\`\`\`dart ListenableBuilder( listenable: widget.viewModel, builder: (context, \_) { if (widget.viewModel.running) { return const Center(child: CircularProgressIndicator()); } // ··· }, ) \`\`\` Or use the running state to avoid executing the action multiple times:

<!--?code-excerpt "lib/no_command.dart (load2)" replace="/2//g"?-->

\`\`\`dart void load() { if (running) { return; } // load user } \`\`\` Managing the state of an action can get complicated if the view model contains multiple actions. For example, adding an \`edit()\` action to the \`HomeViewModel\` can lead the following outcome:

<!--?code-excerpt "lib/no_command.dart (HomeViewModel3)" replace="/(null|false);/\/\/ .../g;/3//g"?-->

\`\`\`dart class HomeViewModel extends ChangeNotifier { User? get user => // ... bool get runningLoad => // ... Exception? get errorLoad => // ... bool get runningEdit => // ... Exception? get errorEdit => // ... void load() { // load user } void edit(String name) { // edit user } } \`\`\` Sharing the running state between the \`load()\` and \`edit()\` actions might not always work, because you might want to show a different UI component when the \`load()\` action runs than when the \`edit()\` action runs; you'll have the same problem with the \`error\` state. ### Triggering UI actions from view models View model classes can run into problems when executing UI actions and the view model's state changes. For example, you might want to show a \`SnackBar\` when an error occurs, or navigate to a different screen when an action completes. To implement this, listen for changes in the view model, and perform the action depending on the state. In the view:

<!--?code-excerpt "lib/no_command.dart (addListener)"?-->

\`\`\`dart @override void initState() { super.initState(); widget.viewModel.addListener(\_onViewModelChanged); } @override void dispose() { widget.viewModel.removeListener(\_onViewModelChanged); super.dispose(); } \`\`\`

<!--?code-excerpt "lib/no_command.dart (_onViewModelChanged)" remove="widget.viewModel.clearError();"?-->

\`\`\`dart void \_onViewModelChanged() { if (widget.viewModel.error != null) { // Show Snackbar } } \`\`\` You need to clear the error state each time you execute this action, otherwise this action happens each time \`notifyListeners()\` is called.

<!--?code-excerpt "lib/no_command.dart (_onViewModelChanged)"?-->

\`\`\`dart void \_onViewModelChanged() { if (widget.viewModel.error != null) { widget.viewModel.clearError(); // Show Snackbar } } \`\`\` ## Command pattern You might find yourself repeating the above code over and over, implementing a different running state for each action in every view model. At that point, it makes sense to extract this code into a reusable pattern called a \_command\_. A command is a class that encapsulates a view model action, and exposes the different states that an action can have.

<!--?code-excerpt "lib/simple_command.dart (Command)" replace="/(null|false);/\/\/ .../g;"?-->

\`\`\`dart class Command extends ChangeNotifier { Command(this.\_action); bool get running => // ... Exception? get error => // ... bool get completed => // ... void Function() \_action; void execute() { // run \_action } void clear() { // clear state } } \`\`\` In the view model, instead of defining an action directly with a method, you create a command object:

<!--?code-excerpt "lib/simple_command.dart (ViewModel)" replace="/(null|false);/\/\/ .../g;"?-->

\`\`\`dart class HomeViewModel extends ChangeNotifier { HomeViewModel() { load = Command(\_load)..execute(); } User? get user => // ... late final Command load; void \_load() { // load user } } \`\`\` The previous \`load()\` method becomes \`\_load()\`, and instead the command \`load\` gets exposed to the \`View\`. The previous \`running\` and \`error\` states can be removed, as they are now part of the command. ### Executing a command Instead of calling \`viewModel.load()\` to run the load action, now you call \`viewModel.load.execute()\`. The \`execute()\` method can also be called from within the view model. The following line of code runs the \`load\` command when the view model is created.

<!--?code-excerpt "lib/main.dart (ViewModelInit)"?-->

\`\`\`dart HomeViewModel() { load = Command(\_load)..execute(); } \`\`\` The \`execute()\` method sets the running state to \`true\` and resets the \`error\` and \`completed\` states. When the action finishes, the \`running\` state changes to \`false\` and the \`completed\` state to \`true\`. If the \`running\` state is \`true\`, the command cannot begin executing again. This prevents users from triggering a command multiple times by pressing a button rapidly. The command’s \`execute()\` method captures any thrown \`Exceptions\` automatically and exposes them in the \`error\` state. The following code shows a sample \`Command\` class that has been simplified for demo purposes. You can see a full implementation at the end of this page.

<!--?code-excerpt "lib/main.dart (Command)"?-->

\`\`\`dart class Command extends ChangeNotifier { Command(this.\_action); bool \_running = false; bool get running => \_running; Exception? \_error; Exception? get error => \_error; bool \_completed = false; bool get completed => \_completed; final Future Function() \_action; Future execute() async { if (\_running) { return; } \_running = true; \_completed = false; \_error = null; notifyListeners(); try { await \_action(); \_completed = true; } on Exception catch (error) { \_error = error; } finally { \_running = false; notifyListeners(); } } void clear() { \_running = false; \_error = null; \_completed = false; } } \`\`\` ### Listening to the command state The \`Command\` class extends from \`ChangeNotifier\`, allowing Views to listen to its states. In the \`ListenableBuilder\`, instead of passing the view model to \`ListenableBuilder.listenable\`, pass the command:

<!--?code-excerpt "lib/main.dart (CommandListenable)" replace="/body: //g;/^\),$/)/g"?-->

\`\`\`dart ListenableBuilder( listenable: widget.viewModel.load, builder: (context, child) { if (widget.viewModel.load.running) { return const Center(child: CircularProgressIndicator()); } // ··· ) \`\`\` And listen to changes in the command state in order to run UI actions:

<!--?code-excerpt "lib/main.dart (addListener)"?-->

\`\`\`dart @override void initState() { super.initState(); widget.viewModel.addListener(\_onViewModelChanged); } @override void dispose() { widget.viewModel.removeListener(\_onViewModelChanged); super.dispose(); } \`\`\`

<!--?code-excerpt "lib/main.dart (_onViewModelChanged)"?-->

\`\`\`dart void \_onViewModelChanged() { if (widget.viewModel.load.error != null) { widget.viewModel.load.clear(); // Show Snackbar } } \`\`\` ### Combining command and ViewModel You can stack multiple \`ListenableBuilder\` widgets to listen to \`running\` and \`error\` states before showing the view model data.

<!--?code-excerpt "lib/main.dart (ListenableBuilder)"?-->

\`\`\`dart body: ListenableBuilder( listenable: widget.viewModel.load, builder: (context, child) { if (widget.viewModel.load.running) { return const Center(child: CircularProgressIndicator()); } if (widget.viewModel.load.error != null) { return Center( child: Text('Error: ${widget.viewModel.load.error}'), ); } return child!; }, child: ListenableBuilder( listenable: widget.viewModel, builder: (context, \_) { // ··· }, ), ), \`\`\` You can define multiple commands classes in a single view model, simplifying its implementation and minimizing the amount of repeated code.

<!--?code-excerpt "lib/main.dart (HomeViewModel2)" replace="/null;/\/\/ .../g"?-->

\`\`\`dart class HomeViewModel2 extends ChangeNotifier { HomeViewModel2() { load = Command(\_load)..execute(); delete = Command(\_delete); } User? get user => // ... late final Command load; late final Command delete; Future \_load() async { // load user } Future \_delete() async { // delete user } } \`\`\` ### Extending the command pattern The command pattern can be extended in multiple ways. For example, to support a different number of arguments.

<!--?code-excerpt "lib/extended_command.dart (HomeViewModel)" replace="/null;/\/\/ .../g"?-->

\`\`\`dart class HomeViewModel extends ChangeNotifier { HomeViewModel() { load = Command0(\_load)..execute(); edit = Command1(\_edit); } User? get user => // ... // Command0 accepts 0 arguments late final Command0 load; // Command1 accepts 1 argument late final Command1 edit; Future \_load() async { // load user } Future \_edit(String name) async { // edit user } } \`\`\` ## Putting it all together In this guide, you learned how to use the command design pattern to improve the implementation of view models when using the MVVM design pattern. Below, you can find the full \`Command\` class as implemented in the \[Compass App example]\[] for the Flutter architecture guidelines. It also uses the \[\`Result\` class]\[] to determine if the action completed successfully or with an error. This implementation also includes two types of commands, a \`Command0\`, for actions without parameters, and a \`Command1\`, for actions that take one parameter. :::note Check \[pub.dev]\[] for other ready-to-use implementations of the command pattern, such as the \[\`command\_it\`]\[] package. :::

<!--?code-excerpt "lib/command.dart"?-->

\`\`\`dart // Copyright 2024 The Flutter team. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'dart:async'; import 'package:flutter/foundation.dart'; import 'result.dart'; /// Defines a command action that returns a \[Result] of type \[T]. /// Used by \[Command0] for actions without arguments. typedef CommandAction0 = Future> Function(); /// Defines a command action that returns a \[Result] of type \[T]. /// Takes an argument of type \[A]. /// Used by \[Command1] for actions with one argument. typedef CommandAction1 = Future> Function(A); /// Facilitates interaction with a view model. /// /// Encapsulates an action, /// exposes its running and error states, /// and ensures that it can't be launched again until it finishes. /// /// Use \[Command0] for actions without arguments. /// Use \[Command1] for actions with one argument. /// /// Actions must return a \[Result] of type \[T]. /// /// Consume the action result by listening to changes, /// then call to \[clearResult] when the state is consumed. abstract class Command extends ChangeNotifier { bool \_running = false; /// Whether the action is running. bool get running => \_running; Result? \_result; /// Whether the action completed with an error. bool get error => \_result is Error; /// Whether the action completed successfully. bool get completed => \_result is Ok; /// The result of the most recent action. /// /// Returns \`null\` if the action is running or completed with an error. Result? get result => \_result; /// Clears the most recent action's result. void clearResult() { \_result = null; notifyListeners(); } /// Execute the provided \[action], notifying listeners and /// setting the running and result states as necessary. Future \_execute(CommandAction0 action) async { // Ensure the action can't launch multiple times. // e.g. avoid multiple taps on button if (\_running) return; // Notify listeners. // e.g. button shows loading state \_running = true; \_result = null; notifyListeners(); try { \_result = await action(); } finally { \_running = false; notifyListeners(); } } } /// A \[Command] that accepts no arguments. final class Command0 extends Command { /// Creates a \[Command0] with the provided \[CommandAction0]. Command0(this.\_action); final CommandAction0 \_action; /// Executes the action. Future execute() async { await \_execute(\_action); } } /// A \[Command] that accepts one argument. final class Command1 extends Command { /// Creates a \[Command1] with the provided \[CommandAction1]. Command1(this.\_action); final CommandAction1 \_action; /// Executes the action with the specified \[argument]. Future execute(A argument) async { await \_execute(() => \_action(argument)); } } \`\`\` \[Compass App example]: https\://github.com/flutter/samples/tree/main/compass\_app \[\`Result\` class]: /app-architecture/design-patterns/result \[pub.dev]: https\://pub.dev \[\`command\_it\`]: https\://pub.dev/packages/command\_it \[\`ChangeNotifier\`]: /learn/pathway/tutorial/change-notifier \[Model-View-ViewModel (MVVM)]: /app-architecture/guide#view-models \[View models]: /app-architecture/guide#view-models

----
url: https://docs.flutter.dev/cookbook/gestures/ripples
----

Widgets that follow the Material Design guidelines display a ripple animation when tapped.

Flutter provides the [`InkWell`](https://pub.dev/documentation/material_ui/latest/material_ui/InkWell-class.html) widget to perform this effect. Create a ripple effect using the following steps:

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 the custom flat button widget.
InkWell(
  // When the user taps the button, show a snackbar.
  onTap: () {
    ScaffoldMessenger.of(context)
        .showSnackBar(const SnackBar(content: Text('Tap')));
  },
  child: const Padding(
    padding: EdgeInsets.all(12),
    child: Text('Flat Button'),
  ),
)
```

## Interactive example

[#](#interactive-example)

```
import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    const title = 'InkWell Demo';

    return const MaterialApp(
      title: title,
      home: MyHomePage(title: title),
    );
  }
}

class MyHomePage extends StatelessWidget {
  final String title;

  const MyHomePage({super.key, required this.title});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text(title)),
      body: const Center(child: MyButton()),
    );
  }
}

class MyButton extends StatelessWidget {
  const MyButton({super.key});

  @override
  Widget build(BuildContext context) {
    // The InkWell wraps the custom flat button widget.
    return InkWell(
      // When the user taps the button, show a snackbar.
      onTap: () {
        ScaffoldMessenger.of(context)
            .showSnackBar(const SnackBar(content: Text('Tap')));
      },
      child: const Padding(
        padding: EdgeInsets.all(12),
        child: Text('Flat Button'),
      ),
    );
  }
}
```

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-09-29. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/gestures/ripples.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/cookbook/gestures/ripples\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/gestures/ripples.md "Report an issue with this page").

----
url: https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.10.0.md
----

\# DevTools 2.10.0 release notes > Release notes for Dart and Flutter DevTools version 2.10.0. The 2.10.0 release of the Dart and Flutter DevTools includes the following changes among other general improvements. To learn more about DevTools, check out the \[DevTools overview]\(https\://docs.flutter.dev/tools/devtools). ## Flutter inspector updates \* Added search support to the Widget Tree, and added a breadcrumb navigator to the Widget Details Tree to allow for quickly navigating through the tree hierarchy - \[#3525]\(https\://github.com/flutter/devtools/pull/3525) !\[inspector search]\(/assets/images/docs/tools/devtools/release-notes/images-2.10.0/image1.png "inspector\_search") ## CPU profiler updates \* Fix a null reference in the CPU profiler when loading an offline snapshot - \[#3596]\(https\://github.com/flutter/devtools/pull/3596) ## Debugger updates \* Added support for multi-token file search, and improved search match prioritization to rank file name matches over full path matches - \[#3582]\(https\://github.com/flutter/devtools/pull/3582) \* Fix some focus-related issues - \[#3602]\(https\://github.com/flutter/devtools/pull/3602) ## Logging view updates \* Fix a fatal error that occurred when filtering logs more than once - \[#3588]\(https\://github.com/flutter/devtools/pull/3588) ## Full commit history To find a complete list of changes since the previous release, check out \[the diff on GitHub]\(https\://github.com/flutter/devtools/compare/v2.9.2...v2.10.0).

----
url: https://docs.flutter.dev/cookbook/testing/integration/profiling
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Measure performance with an integration test](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

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.

This recipe uses the following steps:

1. Write a test that scrolls through a list of items.
2. Record the performance of the app.
3. Save the results to disk.
4. Run the test.
5. Review the results.

## 1. Write a test that scrolls through a list of items

[#](#1-write-a-test-that-scrolls-through-a-list-of-items)

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](/cookbook/testing/widget/scrolling) recipe in widget tests.

Follow the instructions in that recipe to create an app and write a test to verify that everything works as expected.

## 2. Record the performance of the app

[#](#2-record-the-performance-of-the-app)

Next, record the performance of the app as it scrolls through the list. Perform this task using the [`traceAction()`](https://api.flutter.dev/flutter/flutter_driver/FlutterDriver/traceAction.html) method provided by the [`IntegrationTestWidgetsFlutterBinding`](https://api.flutter.dev/flutter/package-integration_test_integration_test/IntegrationTestWidgetsFlutterBinding-class.html) class.

This method runs the provided function and records a [`Timeline`](https://api.flutter.dev/flutter/flutter_driver/Timeline-class.html) 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()` creates a report data `Map` that contains the `Timeline`.

Specify the `reportKey` when running more than one `traceAction`. By default all `Timelines` are stored with the key `timeline`, in this example the `reportKey` is changed to `scrolling_timeline`.

dart

```
await binding.traceAction(() async {
  // Scroll until the item to be found appears.
  await tester.scrollUntilVisible(
    itemFinder,
    500.0,
    scrollable: listFinder,
  );
}, reportKey: 'scrolling_timeline');
```

## 3. Save the results to disk

[#](#3-save-the-results-to-disk)

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, convert the `Timeline` into a [`TimelineSummary`](https://api.flutter.dev/flutter/flutter_driver/TimelineSummary-class.html). The `TimelineSummary` can perform two tasks that make it easier to review the results:

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`.

To capture the results, create a file named `perf_driver.dart` in the `test_driver` folder and add the following code:

dart

```
import 'package:flutter_driver/flutter_driver.dart' as driver;
import 'package:integration_test/integration_test_driver.dart';
​
Future<void> main() {
  return integrationDriver(
    responseDataCallback: (data) async {
      if (data != null) {
        final timeline = driver.Timeline.fromJson(
          data['scrolling_timeline'] as Map<String, dynamic>,
        );
​
        // Convert the Timeline into a TimelineSummary that's easier to
        // read and understand.
        final summary = driver.TimelineSummary.summarize(timeline);
​
        // Then, 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, save the summary to disk by setting includeSummary
        // to true
        await summary.writeTimelineToFile(
          'scrolling_timeline',
          pretty: true,
          includeSummary: true,
        );
      }
    },
  );
}
```

The `integrationDriver` function has a `responseDataCallback` which you can customize. By default, it writes the results to the `integration_response_data.json` file, but you can customize it to generate a summary like in this example.

## 4. Run the test

[#](#4-run-the-test)

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 \
  --driver=test_driver/perf_driver.dart \
  --target=integration_test/scrolling_test.dart \
  --profile
```

The `--profile` option means to compile the app for the "profile mode" rather than the "debug mode", so that the benchmark result is closer to what will be experienced by end users.

## 5. Review the results

[#](#5-review-the-results)

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, 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`. 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

```
{
  "average_frame_build_time_millis": 4.2592592592592595,
  "worst_frame_build_time_millis": 21.0,
  "missed_frame_build_budget_count": 2,
  "average_frame_rasterizer_time_millis": 5.518518518518518,
  "worst_frame_rasterizer_time_millis": 51.0,
  "missed_frame_rasterizer_budget_count": 10,
  "frame_count": 54,
  "frame_build_times": [
    6874,
    5019,
    3638
  ],
  "frame_rasterizer_times": [
    51955,
    8468,
    3129
  ]
}
```

## Complete example

[#](#complete-example)

**integration\_test/scrolling\_test.dart**

dart

```
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'package:material_ui/material_ui.dart';
import 'package:your_package/main.dart';
​
void main() {
  final binding = IntegrationTestWidgetsFlutterBinding.ensureInitialized();
​
  testWidgets('Counter increments smoke test', (tester) async {
    // Build our app and trigger a frame.
    await tester.pumpWidget(
      MyApp(items: List<String>.generate(10000, (i) => 'Item $i')),
    );
​
    final listFinder = find.byType(Scrollable);
    final itemFinder = find.byKey(const ValueKey('item_50_text'));
​
    await binding.traceAction(() async {
      // Scroll until the item to be found appears.
      await tester.scrollUntilVisible(
        itemFinder,
        500.0,
        scrollable: listFinder,
      );
    }, reportKey: 'scrolling_timeline');
  });
}
```

**test\_driver/perf\_driver.dart**

dart

```
import 'package:flutter_driver/flutter_driver.dart' as driver;
import 'package:integration_test/integration_test_driver.dart';
​
Future<void> main() {
  return integrationDriver(
    responseDataCallback: (data) async {
      if (data != null) {
        final timeline = driver.Timeline.fromJson(
          data['scrolling_timeline'] as Map<String, dynamic>,
        );
​
        // Convert the Timeline into a TimelineSummary that's easier to
        // read and understand.
        final summary = driver.TimelineSummary.summarize(timeline);
​
        // Then, 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, save the summary to disk by setting includeSummary
        // to true
        await summary.writeTimelineToFile(
          'scrolling_timeline',
          pretty: true,
          includeSummary: true,
        );
      }
    },
  );
}
```

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-09-29. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/testing/integration/profiling.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/cookbook/testing/integration/profiling\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/testing/integration/profiling.md "Report an issue with this page").

----
url: https://docs.flutter.dev/ai/best-practices
----

Flutter and AI go well together on multiple levels. If you're using AI to generate Flutter code, you only have to generate the code for a single app to target multiple platforms. And if you're harnessing Gemini to implement features in your app, the Firebase AI Logic SDK makes that simple, with an easy-to-use API, and secure, by keeping the API keys out of your code.

If you're new to AI for either of these two use cases, you should know: as good as it is (and the Gemini 3 Pro Preview is *very* good), AI still makes mistakes. If you're using AI to write your code, then you can use guardrails to keep AI on track using tools like the Flutter analyzer and unit tests.

But what do you do when you're using AI to implement the features in your app, knowing that sometimes it's going to get things wrong? Or, to quote a friend of mine:

***Morgan's Law***\
*"Eventually, due to the nature of sampling from a probability distribution, \[AI] will fail to do the thing that must be done."*\
*–Brett Morgan, Flutter Developer Relations Engineer, July, 2025.*

The good news is that, just as you can use developer tools to build guardrails around the AI writing your code, you can use Flutter to build guardrails around the AI you use to implement your features. The [Crossword Companion app](https://github.com/flutter/demos/tree/main/crossword_companion) was built to demonstrate these techniques.\
\
The goal of the Crossword Companion app is not to help you cheat at mini-crosswords – although it's darn good at that – but to illustrate how to channel the power of AI using Flutter. As an example, the first thing you do when running the app is upload the screenshot of a mini-crossword puzzle. When you press the **Next** button, the AI uses that image to infer the size, contents and clues of the puzzle:\
\
Notice that while the crossword puzzle is a 5x5 grid, the AI says it's 4x5. Because we know that mistakes happen (apparently AIs are only human, too), we built the app to allow the user to verify and correct the AI-generated data. That's important; bad data leads to bad results.

So this write-up is not about the app in detail but rather about the best practices to use when you're building your own AI apps with Flutter. So let's get to it!

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-08-19. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/ai/best-practices/index.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/ai/best-practices\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/ai/best-practices/index.md "Report an issue with this page").

----
url: https://docs.flutter.dev/platform-integration/android/restore-state-android
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Restore state on Android](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

When a user runs a mobile app and then selects another app to run, the first app is moved to the background, or *backgrounded*. The operating system (both iOS and Android) might kill the backgrounded app to release memory and improve performance for the app running in the foreground.

When the user selects the app again, bringing it back to the foreground, the OS relaunches it. But, unless you've set up a way to save the state of the app before it was killed, you've lost the state and the app starts from scratch. The user has lost the continuity they expect, which is clearly not ideal. (Imagine filling out a lengthy form and being interrupted by a phone call *before* clicking **Submit**.)

So, how can you restore the state of the app so that it looks like it did before it was sent to the background?

Flutter has a solution for this with the [`RestorationManager`](https://api.flutter.dev/flutter/services/RestorationManager-class.html) (and related classes) in the [services](https://api.flutter.dev/flutter/services/services-library.html) library. With the `RestorationManager`, the Flutter framework provides the state data to the engine *as the state changes*, so that the app is ready when the OS signals that it's about to kill the app, giving the app only moments to prepare.

## Overview

[#](#overview)

You can enable state restoration with just a few tasks:

1. Define a `restorationScopeId` for classes like `CupertinoApp`, `MaterialApp`, or `WidgetsApp`.

2. Define a `restorationId` for widgets that support it, such as [`TextField`](https://pub.dev/documentation/material_ui/latest/material_ui/TextField/restorationId.html) and [`ScrollView`](https://api.flutter.dev/flutter/widgets/ScrollView/restorationId.html). This automatically enables built-in state restoration for those widgets.

3. For custom widgets, you must decide what state you want to restore and hold that state in a [`RestorableProperty`](https://api.flutter.dev/flutter/widgets/RestorableProperty-class.html). (The Flutter API provides various subclasses for different data types.) Define those `RestorableProperty` widgets in a `State` class that uses the [`RestorationMixin`](https://api.flutter.dev/flutter/widgets/RestorationMixin-mixin.html). Register those widgets with the mixin in a `restoreState` method.

4. If you use any Navigator API (like `push`, `pushNamed`, and so on) migrate to the API that has "restorable" in the name (`restorablePush`, `restorablePushNamed`, and so on) to restore the navigation stack.

Other considerations:

* Providing a `restorationScopeId` to `MaterialApp`, `CupertinoApp`, or `WidgetsApp` automatically enables state restoration by injecting a `RootRestorationScope`. If you need to restore state *above* the app class, inject a `RootRestorationScope` manually.

* **The difference between a `restorationId` and a `restorationScopeId`:** Widgets that take a `restorationScopeId` create a new `restorationScope` (a new `RestorationBucket`) into which all children store their state. A `restorationId` means the widget (and its children) store the data in the surrounding bucket.

## Restoring navigation state

[#](#restoring-navigation-state)

If you want your app to return to a particular route that the user was most recently viewing (the shopping cart, for example), then you must implement restoration state for navigation, as well.

If you use the Navigator API directly, migrate the standard methods to restorable methods (that have "restorable" in the name). For example, replace `push` with [`restorablePush`](https://api.flutter.dev/flutter/widgets/Navigator/restorablePush.html).

## Testing state restoration

[#](#testing-state-restoration)

To test state restoration, set up your mobile device so that it doesn't save state once an app is backgrounded. To learn how to do this for both iOS and Android, check out [Testing state restoration](https://api.flutter.dev/flutter/services/RestorationManager-class.html#testing-state-restoration) on the [`RestorationManager`](https://api.flutter.dev/flutter/services/RestorationManager-class.html) page.

## Other resources

[#](#other-resources)

For further information on state restoration, check out the following resources.

* To learn more about short term and long term state, check out [Differentiate between ephemeral state and app state](/data-and-backend/state-mgmt/ephemeral-vs-app).

* For more information on navigation and the [`go_router`](https://pub.dev/packages/go_router) package, check out [Navigation and routing](/ui/navigation) and the [State restoration](https://pub.dev/documentation/go_router/latest/topics/State%20restoration-topic.html) topic on pub.dev.

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-09-29. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/platform-integration/android/restore-state-android.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/platform-integration/android/restore-state-android\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/platform-integration/android/restore-state-android.md "Report an issue with this page").

----
url: https://docs.flutter.dev/contribute
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Contribute to Flutter](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

If you would like to contribute to the Flutter project and its surrounding ecosystem, we're happy to have your help!

Flutter is an open-source project that thrives on community contributions. No matter whether you're fixing a bug, proposing a new feature, improving documentation, or helping others in the community, your efforts are valuable and appreciated.

This page provides a non-exhaustive overview of how you can get involved. If you need help contributing or would like more suggestions on getting started, consider reaching out on the [Flutter contributors Discord](https://flutter.dev/chat).

## Develop with Flutter

[#](#develop-with-flutter)

Even just using Flutter and providing feedback is a valuable contribution!

### Provide feedback

[#](#provide-feedback)

Sharing your feedback and experiences helps the Flutter team understand and prioritize developer needs and pain points.

You can provide valuable feedback through many avenues, including:

* Upvoting existing issues

  If you're experiencing an issue that has already been reported, or want to indicate interest in an issue tracking a new feature, consider upvoting it to help the Flutter team understand its importance. You can upvote an issue by locating it in the [GitHub issue tracker](https://github.com/flutter/flutter/issues), and pressing the "thumbs up" reaction on the issue's topmost comment.

  Avoid otherwise empty thumbs up, +1, or similar comments. However, if you have additional information, such as reproduction steps or additional version information, do consider providing those details in a new comment.

* Reporting new bugs

  If you experience a bug with Flutter that hasn't yet been reported, do [open a new issue](https://github.com/flutter/flutter/issues/new) with reproduction information.

* Requesting features

  If there's a feature you think Flutter should add or implement but hasn't yet been suggested, do [open a new issue](https://github.com/flutter/flutter/issues/new) with all relevant information as well as your use case.

* Partaking in surveys

  Occasionally, the Flutter team will run developer surveys and studies. To help understand pain points and improve the Flutter developer experience, consider responding with as much feedback and details as possible.

  To sign up for future UX research studies, visit [flutter.dev/research-signup](https://flutter.dev/research-signup).

* Discussing proposals

  Architectural proposals and major changes to Flutter are authored, reviewed, and preserved as Requests for Comments (RFCs) in the [`flutter/rfc` repository](https://github.com/flutter/rfc). Consider reading and providing feedback on proposals that affect you or your apps.

  To find active proposals and join the discussion, browse [open RFC pull requests](https://github.com/flutter/rfc/pulls) in `flutter/rfc` or track [issues with the `design doc` label](https://github.com/flutter/flutter/issues?q=is%3Aopen+is%3Aissue+label%3A%22design+doc%22). If you want to author a proposal yourself, consult [Architectural proposals (RFCs)](#architectural-proposals-rfcs).

* Reviewing pull requests

  If you're familiar with a particular area of Flutter or a solution to a particular issue is important to you, consider reviewing open pull requests, trying them with your app, and providing any relevant feedback.

### Try out the beta channel

[#](#try-out-the-beta-channel)

To help ensure Flutter's stability and improve upcoming features, help test upcoming releases before they reach the stable channel.

Consider testing releases on the `beta` channel, both for general development and for testing compatibility with your apps.

Any feedback you have or regressions you encounter, make sure to [report them](https://github.com/flutter/flutter/issues/new/choose) to the Flutter team.

To get started, [switch](/install/upgrade#change-channels) to the [`beta` channel](/install/upgrade#the-beta-channel) today and account for any [necessary migrations](/release/breaking-changes).

## Contribute code

[#](#contribute-code)

Directly improve Flutter's codebase and related tools.

### Architectural proposals (RFCs)

[#](#architectural-proposals-rfcs)

Planning a major architectural change, cross-subsystem feature, new primitive, or breaking change? Before writing implementation code, propose and align on the design through a Request for Comments (RFC) in the [`flutter/rfc` repository](https://github.com/flutter/rfc). Routine bug fixes and localized changes don't require an RFC; open an issue or pull request directly instead.

To learn when an RFC is required and how to submit one, consult the [Flutter RFCs & Design Documents guide](https://github.com/flutter/flutter/blob/main/docs/contributing/Design-Documents.md) and [RFC 000.0002 (RFC Review & Decision Process)](https://github.com/flutter/rfc/blob/main/rfc/000.0002-flutter-rfc-review-process.md).

### Flutter framework

[#](#flutter-framework)

Found a bug in a built-in widget, have an idea for a new one, love adding tests, or just interested in the internals of Flutter? Consider contributing to the Flutter framework itself and improving the core of Flutter for everyone.

To learn about contributing to the Flutter framework, check out the Flutter [contribution guide](https://github.com/flutter/flutter/blob/main/CONTRIBUTING.md).

### Flutter engine

[#](#flutter-engine)

Interested in implementing the primitives and platform integrations that underlay Flutter or have a knack for graphics programming? Consider contributing to the Flutter engine and making Flutter even more portable, performant, and powerful.

To learn about contributing to the Flutter engine, check out the Flutter [contribution guide](https://github.com/flutter/flutter/blob/main/CONTRIBUTING.md) and how to [Set up the engine development environment](https://github.com/flutter/flutter/blob/main/docs/engine/contributing/Setting-up-the-Engine-development-environment.md).

### Flutter packages

[#](#flutter-packages)

Contribute to first-party packages that are maintained by the Flutter team. The first-party packages provide essential functionality for apps as well as encapsulate various platform-specific functionality.

To learn about contributing to the first-party packages, check out the Flutter [contribution guide](https://github.com/flutter/flutter/blob/main/CONTRIBUTING.md) as well as the packages-specific [contribution guide](https://github.com/flutter/packages/blob/main/CONTRIBUTING.md).

### DevTools

[#](#devtools)

Contributing to the [Dart and Flutter DevTools](/tools/devtools) is a great place to begin contributing due to its relatively low-cost setup. Enhance and fixes can greatly impact the developer experience for Flutter developers and perhaps help you develop your own apps.

To get started, check out the [DevTools `CONTRIBUTING.md` guide](https://github.com/flutter/devtools/blob/master/CONTRIBUTING.md).

### Site infrastructure

[#](#site-infrastructure)

Fix bugs, improve accessibility, or add features to the Dart and Flutter websites.

If you're familiar with web development or site generation, contributing to the Dart and Flutter websites can be a great avenue to improve the learning experience of Flutter developers.

Depending on your interests, you might want to contribute to:

* The pub.dev site

  * **Live site:** [`pub.dev`](https://pub.dev)
  * **Repository:** [`dart-lang/pub-dev`](https://github.com/dart-lang/pub-dev)
  * **Contribution guide:** [`CONTRIBUTING.md`](https://github.com/dart-lang/pub-dev/blob/master/CONTRIBUTING.md)

* The Flutter documentation site

  * **Live site:** [`docs.flutter.dev`](https://docs.flutter.dev)
  * **Repository:** [`flutter/website`](https://github.com/flutter/website)
  * **Contribution guide:** [`CONTRIBUTING.md`](https://github.com/flutter/website/blob/main/CONTRIBUTING.md)

* The Dart documentation site

  * **Live site:** [`dart.dev`](https://dart.dev)
  * **Repository:** [`dart-lang/site-www`](https://github.com/dart-lang/site-www)
  * **Contribution guide:** [`CONTRIBUTING.md`](https://github.com/dart-lang/site-www/blob/main/CONTRIBUTING.md)

* DartPad

  * **Live site:** [`dartpad.dev`](https://dartpad.dev)
  * **Repository:** [`dart-lang/dart-pad`](https://github.com/dart-lang/dart-pad)
  * **Contribution guide:** [`CONTRIBUTING.md`](https://github.com/dart-lang/dart-pad/blob/main/CONTRIBUTING.md)

* The `dartdoc` tool

  * **Live site:** [`api.flutter.dev`](https://api.flutter.dev)
  * **Repository:** [`dart-lang/dartdoc`](https://github.com/dart-lang/dartdoc)
  * **Contribution guide:** [`CONTRIBUTING.md`](https://github.com/dart-lang/dartdoc/blob/main/CONTRIBUTING.md)

### Dart SDK

[#](#dart-sdk)

Contribute to the Dart language and surrounding tooling, improving the client-optimized language that forms the foundation of Flutter's excellent developer experience.

Dart's contribution workflow is slightly different, so if you're interested, make sure to check out its [contribution](https://github.com/dart-lang/sdk/blob/main/CONTRIBUTING.md) and [building](https://github.com/dart-lang/sdk/blob/main/docs/Building.md) guides.

### Code samples

[#](#code-samples)

Improve or add samples demonstrating Flutter features, helping developers that prefer to learn by example.

You can always share your own samples or templates, or you can contribute to Flutter-maintained samples:

* Full project samples

  * **Location:** [`flutter/samples`](https://github.com/flutter/samples)
  * **Contribution guide:** [`CONTRIBUTING.md`](https://github.com/flutter/samples/blob/main/CONTRIBUTING.md)

* API doc samples

  * **Location:** [`flutter/flutter/packages/flutter`](https://github.com/flutter/flutter/tree/main/packages/flutter)
  * **Contribution guide:** [`README.md`](https://github.com/flutter/flutter/tree/main/dev/snippets)

* Website code snippets

  * **Location:** [`flutter/website/examples`](https://github.com/flutter/website/tree/main/examples)
  * **Contribution guide:** [`CONTRIBUTING.md`](https://github.com/flutter/website/blob/main/CONTRIBUTING.md)

* Flutter repo samples

  * **Location:** [`flutter/flutter/examples`](https://github.com/flutter/flutter/tree/main/examples)
  * **Contribution guide:** [`CONTRIBUTING.md`](https://github.com/flutter/flutter/blob/main/CONTRIBUTING.md)

## Write documentation

[#](#write-documentation)

Contributing to Flutter documentation, no matter the form, is one of the most impactful ways you can help Flutter.

### Flutter API docs

[#](#flutter-api-docs)

The API docs are heavily relied on by many Flutter developers, both online and in their code editors.

Whether you're interested in writing new docs, updating existing ones, adding relevant code snippets, or even creating new visuals like diagrams, your contribution to the API docs will be appreciated by every Flutter developer.

To get started, check out the [Flutter SDK contribution guide](https://github.com/flutter/flutter/blob/main/CONTRIBUTING.md), particularly its section on [API documentation](https://github.com/flutter/flutter/blob/main/CONTRIBUTING.md#api-documentation)

### Documentation website

[#](#documentation-website)

Consider contributing to this very site, guiding developers as they learn and explore Flutter.

To learn about contributing to the Flutter documentation website, check out the website's [contribution documentation](https://github.com/flutter/website/blob/main/CONTRIBUTING.md).

You can also contribute to the [Dart website](https://dart.dev), enhancing the documentation for the client-optimized language that forms the foundation of Flutter. To learn how to contribute, check out the [`dart-lang/site-www` contribution docs](https://github.com/dart-lang/site-www/tree/main?tab=readme-ov-file#getting-started).

## Triage issues

[#](#triage-issues)

Help the Flutter team by triaging incoming bug reports and feature requests.

There are plenty of ways to help in [Flutter's issue database](https://github.com/flutter/flutter/issues), including but not limited to:

* Determining issue validity
* Ensuring actionability
* Documenting affected versions
* Adding reproduction steps
* Identifying duplicate or resolved issues
* Solving or redirecting support queries

To get started helping with issues, read about [helping out in the issue database](https://github.com/flutter/flutter/blob/main/CONTRIBUTING.md#helping-out-in-the-issue-database) and learn about Flutter's approach to [issue triage](https://github.com/flutter/flutter/blob/main/docs/triage/README.md) and [issue hygiene](https://github.com/flutter/flutter/tree/main/docs/contributing/issue_hygiene).

## Strengthen the package ecosystem

[#](#strengthen-the-package-ecosystem)

Help grow and support the collection of available Dart and Flutter packages on [pub.dev](https://pub.dev/).

### Contribute to packages you use

[#](#contribute-to-packages-you-use)

To give back to packages you depend on and potentially even help your own apps, find packages you rely on and contribute back to them.

To contribute to a package, navigate to its page on the [pub.dev site](https://pub.dev) and find the repository linked in the page's sidenav.

Before contributing, do make sure to follow each package's contribution guide, discuss your contribution with the maintainers, and keep in mind Flutter's [Code of conduct](https://github.com/flutter/flutter/blob/main/CODE_OF_CONDUCT.md).

### Open source reusable functionality from your app

[#](#open-source-reusable-functionality-from-your-app)

If you've built a cool, generic widget or utility in your app, consider extracting it into a package and publishing it to pub.dev.

To get started, learn about [Creating Dart packages](https://dart.dev/tools/pub/create-packages) and [Developing Flutter packages](/packages-and-plugins/developing-packages). Then, when you're ready to publish your package to the [pub.dev site](https://pub.dev), follow the guide and best practices on [Publishing packages](https://dart.dev/tools/pub/publishing).

### Add Dart or Flutter support to popular SDKs

[#](#add-dart-or-flutter-support-to-popular-sdks)

Create or contribute to packages that wrap native SDKs or web APIs.

Before creating a new package, first try to find any existing wrappers that you could use or contribute to on the [pub.dev site](https://pub.dev).

Depending on the SDK and platform, you might need to [Write platform-specific code](/platform-integration/platform-channels), use [JS interop](https://dart.dev/interop/js-interop), wrap a REST API using [`package:http`](https://pub.dev/packages/http), or reimplement the required functionality in Dart.

If you're planning to create a new package, learn about [Creating Dart packages](https://dart.dev/tools/pub/create-packages) and [Developing Flutter packages](/packages-and-plugins/developing-packages). Then, when you're ready to publish your package to the [pub.dev site](https://pub.dev), follow the guide and best practices on [Publishing packages](https://dart.dev/tools/pub/publishing).

## Support the community

[#](#support-the-community)

Help other developers learn Flutter and succeed as they build their own apps.

### Help other developers

[#](#help-other-developers)

Share your Flutter knowledge and expertise to help your fellow Flutter developers succeed.

This can take many forms from starting a Flutter help channel in your company to answering questions on public forums.

Some common locations Flutter developers look for help include:

* [Stack Overflow](https://stackoverflow.com/questions/tagged/flutter)
* [Flutter Dev Discord](https://discord.com/invite/rflutterdev)
* [Dart Community Discord](https://discord.com/invite/Qt6DgfAWWx)
* [r/FlutterDev on Reddit](https://www.reddit.com/r/FlutterDev)
* [GitHub issues](https://github.com/flutter/flutter/issues)
* [Flutter Forum](https://forum.itsallwidgets.com/)

### Host events

[#](#host-events)

Connect with other Flutter enthusiasts and organize local, national, and even virtual events. Events can be anything, from study groups and simple meetups, to workshops and hackathons.

For inspiration and support, check out existing [Flutter events](https://flutter.dev/events), learn more about the [Flutter community](https://flutter.dev/community), and explore the [Flutter Meetup Network](https://www.meetup.com/pro/flutter/).

### Post about Flutter

[#](#post-about-flutter)

Share your insights and projects with the wider Flutter community.

There are endless options for sharing about Flutter and connecting with the developer community. Some common outlets include:

* Blog posts
* Video tutorials
* Short-form posts
* Forum threads
* GitHub discussions
* Link aggregation boards

Post or share about whatever you're passionate about, but if you're not sure what to post, consider posting about topics that developers often ask about.

If the platform you're posting on supports tagging posts, consider adding the `#Flutter` and `#FlutterDev` hashtags to help other developers find your content.

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-10-08. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/contribute/index.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/contribute\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/contribute/index.md "Report an issue with this page").

----
url: https://docs.flutter.dev/install/uninstall
----

To remove the Flutter SDK from your development machine, delete the directories that store Flutter and its configuration files.

## Choose your development platform

[#](#dev-platform)

The instructions on this page are configured to cover uninstall Flutter on a **Windows** device.

If you'd like to follow the instructions for a different OS, please select one of the following.

## Uninstall the Flutter SDK

[#](#uninstall)

1. ### Determine your Flutter SDK installation location

   Copy the absolute path to the directory that you downloaded and extracted the Flutter SDK into.

2. ### Remove the installation directory

   To uninstall the Flutter SDK, delete the `flutter` directory you installed Flutter to.

   For example, if you downloaded Flutter into a `develop\flutter` folder inside your user directory, run the following command to delete the SDK:

   ```
   $ Remove-Item -Recurse -Force -Path (Join-Path $env:USERPROFILE "develop\flutter")
   ```

1) ### Determine your Flutter SDK installation location

   Copy the absolute path to the directory that you downloaded and extracted the Flutter SDK into.

2) ### Remove the installation directory

   To uninstall the Flutter SDK, delete the `flutter` directory you installed Flutter to.

   For example, if you downloaded Flutter into a `develop/flutter` folder inside your user directory, run the following command to delete the SDK:

   ```
   $ rm -rf ~/develop/flutter
   ```

## Clean up installation and configuration files

[#](#cleanup)

Flutter and Dart add to additional directories in your home directory. These contain configuration files and package downloads. The following cleanup is optional.

1. ### Remove Flutter configuration directories

   If you don't want to preserve your Flutter tooling configuration, remove the following directories from your device.

   * `%APPDATA%\.flutter-devtools`

   To remove these directories, run the following command:

   ```
   $ Remove-Item -Recurse -Force -Path (Join-Path $env:APPDATA ".flutter-devtools")
   ```

   * `~/.flutter`
   * `~/.flutter-devtools`
   * `~/.flutter_settings`

   To remove these directories, run the following command:

   ```
   $ rm -rf  ~/.flutter ~/.flutter-devtools ~/.flutter_settings
   ```

2. ### Remove Dart configuration directories

   If you don't want to preserve your Dart tooling configuration, remove the following directories from your device.

   * `%APPDATA%\.dart`
   * `%APPDATA%\.dart-tool`
   * `%LOCALAPPDATA%\.dartServer`

   To remove these directories, run the following command:

   ```
   $ Remove-Item -Recurse -Force -Path (Join-Path $env:APPDATA ".dart"), (Join-Path $env:APPDATA ".dart-tool"), (Join-Path $env:LOCALAPPDATA ".dartServer")
   ```

   * `~/.dart`
   * `~/.dart-tool`
   * `~/.dartServer`

   To remove these directories, run the following command:

   ```
   $ rm -rf  ~/.dart ~/.dart-tool ~/.dartServer
   ```

3. ### Remove pub package directories

   If you don't want to preserve your locally installed pub packages, remove the [pub system cache](https://dart.dev/tools/pub/glossary#system-cache) directory from your device.

   If you didn't change the location of the pub system cache, run the following command to delete the `%LOCALAPPDATA%\Pub\Cache` directory:

   ```
   $ Remove-Item -Recurse -Force -Path (Join-Path $env:LOCALAPPDATA "Pub\Cache")
   ```

   If you didn't change the location of the pub system cache, run the following command to delete the `~/.pub-cache` directory:

   ```
   $ rm -rf ~/.pub-cache
   ```

## Reinstall Flutter

[#](#reinstall)

You can [reinstall Flutter](/install) or [just Dart](https://dart.dev/get-dart) at any time. If you removed any configuration directories, reinstalling Flutter restores them to default settings.

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/install/uninstall.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/install/uninstall\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/install/uninstall.md "Report an issue with this page").

----
url: https://docs.flutter.dev/resources/courses.md
----

\# Online courses > An index of online courses teaching Flutter development. Learn how to build Flutter apps with these video courses. Before signing up for a course, verify that it includes up-to-date information, such as null-safe Dart code. These courses are grouped by language and listed alphabetically. To include your course, \[submit a PR]\[]: ## Arabic \* \[Flutter Master Course From Zero To Hero]\[] by Usama Elgendy ## English \* \[20 Hour Dart & Flutter YT Course For Beginners]\[] by Rivaan Ranawat \* \[Best Dart Course]\[] by Tadas Petra & Robert Brunhage \* \[Best Flutter Course]\[] by Tadas Petra & Robert Brunhage \* \[Dart & Flutter - Zero to Mastery 2023 + Clean Architecture]\[] by Max Berktold & Max Steffen \* \[Dart & Flutter - Zero to Mastery 2023 - Keiko Corp. Food Reviews App]\[] by Marco Napoli \* \[Flutter & Dart - The Complete Guide, 2023 Edition]\[] \* \[Flutter & Firebase Masterclass]\[] by Andrea Bizzotto \* \[Flutter and Dart Full Course]\[] by Mouaz M. Alshahmeh \* \[Flutter Animations Masterclass]\[] by Andrea Bizzotto \* \[Flutter Crash Course]\[] \* \[Flutter Foundations]\[] by Andrea Bizzotto \* \[Flutter in Production]\[] by Andrea Bizzotto \* \[FlutterCraft]\[] by Muhammad Haris Shahzad \* \[Sticky Grouped Headers in Flutter]\[] by Marco Napoli \* \[The Complete 2021 Flutter Development Bootcamp Using Dart]\[] by App Brewery \* \[The Complete Dart Developer Guide]\[] by Andrea Bizzotto ## German \* \[Flutter leicht gemacht 2022 - Zero to Mastery!]\[] by Max Berktold ## Italian \* \[Flutter University - From Zero to Mastery]\[] by Fudeo ## Portuguese \* \[Flutter Bootcamp]\[] by Rubens de Melo \* \[Flutter para iniciantes]\[] by Rubens de Melo ## Turkish \* \[Flutter Zero to Hero]\[] by Veli Bacik ## Urdu \* \[Tech Idara - Flutter from Basic to Advanced]\[] by Ishaq Hassan \[20 Hour Dart & Flutter YT Course For Beginners]: https\://youtu.be/CzRQ9mnmh44 \[Best Dart Course]: https\://www\.hungrimind.com/learn/dart \[Best Flutter Course]: https\://www\.hungrimind.com/learn/flutter \[Flutter in Production]: https\://codewithandrea.com/courses/flutter-in-production/ \[Flutter Foundations]: https\://codewithandrea.com/courses/flutter-foundations/ \[Flutter & Firebase Masterclass]: https\://codewithandrea.com/courses/flutter-firebase-masterclass/ \[Flutter and Dart Full Course]: https\://www\.youtube.com/watch?v=\_FDJ1of5YPM \[Flutter Animations Masterclass]: https\://codewithandrea.com/courses/flutter-animations-masterclass/ \[The Complete Dart Developer Guide]: https\://codewithandrea.com/courses/complete-dart-guide/ \[Flutter & Dart - The Complete Guide, 2023 Edition]: https\://www\.udemy.com/course/learn-flutter-dart-to-build-ios-android-apps/ \[The Complete 2021 Flutter Development Bootcamp Using Dart]: https\://www\.appbrewery.com/p/flutter-development-bootcamp-with-dart/ \[Flutter Crash Course]: https\://fluttercrashcourse.com/ \[Flutter leicht gemacht 2022 - Zero to Mastery!]: https\://www\.udemy.com/course/dart-flutter-leicht-gemacht/ \[Flutter Zero to Hero]: https\://www\.youtube.com/playlist?list=PL1k5oWAuBhgXdw1BbxVGxxWRmkGB1C11l \[Flutter Bootcamp]: https\://flutterbootcamp.com.br \[Flutter para iniciantes]: https\://www\.youtube.com/playlist?list=PLS4cqF1\_X2syzBpkoSwtmKoREgnp1MhTn \[Dart & Flutter - Zero to Mastery 2023 + Clean Architecture]: https\://www\.udemy.com/course/flutter-made-easy-zero-to-mastery/?referralCode=CCBFCD16CC71F359EE3C \[Dart & Flutter - Zero to Mastery 2023 - Keiko Corp. Food Reviews App]: https\://academy.zerotomastery.io/courses/2092303/lectures/47623876 \[Sticky Grouped Headers in Flutter]: https\://academy.droidcon.com/course/sticky-grouped-headers-in-flutter \[Flutter University - From Zero to Mastery]: https\://www\.fudeo.it/?utm\_source=flutter\_dev \[Tech Idara - Flutter from Basic to Advanced]: https\://www\.youtube.com/playlist?list=PLX97VxArfzkmXeUqUxeKW7XS8oYraH7A5 \[FlutterCraft]: https\://fluttercraft.app \[Flutter Master Course From Zero To Hero]: https\://usamaelgendy.com/courses/flutter-beginners/ \[submit a PR]: https\://github.com/flutter/website/pulls

----
url: https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.20.0.md
----

\# DevTools 2.20.0 release notes > Release notes for Dart and Flutter DevTools version 2.20.0. The 2.20.0 release of the Dart and Flutter DevTools includes the following changes among other general improvements. To learn more about DevTools, check out the \[DevTools overview]\(https\://docs.flutter.dev/tools/devtools). ## CPU profiler updates \* Add support for grouping samples by tag - \[#4693]\(https\://github.com/flutter/devtools/pull/4693) !\[samples by tag]\(/assets/images/docs/tools/devtools/release-notes/images-2.20.0/4693.png "samples by tag") \* Enable guidelines for tree view - \[#4722]\(https\://github.com/flutter/devtools/pull/4722) !\[guidelines]\(/assets/images/docs/tools/devtools/release-notes/images-2.20.0/4722.png "guidelines") \* Rename "Profile granularity" to "CPU sampling rate" and move down to the area it relates to - \[#4803]\(https\://github.com/flutter/devtools/pull/4722) !\[sampling rate]\(/assets/images/docs/tools/devtools/release-notes/images-2.20.0/4803.png "sampling rate") ## Memory updates \* Retire the \*\*Analysis\*\* tab - \[#4714]\(https\://github.com/flutter/devtools/pull/4714) \* Add a new tab, \*\*Diff\*\*, to enable memory leak detection and troubleshooting by comparing heap snapshots, providing insights about the number of instances, shallow size, retained size, and retaining paths - \[#4714]\(https\://github.com/flutter/devtools/pull/4714) !\[diff]\(/assets/images/docs/tools/devtools/release-notes/images-2.20.0/4714.png "Diff in Memory tab") ## Debugger updates \* Support for inspecting more types of instances in the variables viewer (Expandos, Types, TypeArguments, Parameters, Closures + closure Contexts, WeakProperty, Function, FunctionType, ReceivePort, Closure, RegExp) - \[#4760]\(https\://github.com/flutter/devtools/pull/4760) \* Add support for displaying coverage in CodeView - \[#4700]\(https\://github.com/flutter/devtools/pull/4700) !\[coverage]\(/assets/images/docs/tools/devtools/release-notes/images-2.20.0/4700.png "coverage in CodeView") ## Network updates \* Display request data if content type is not json (thanks to @leungpuikuen!) - \[#4602]\(https\://github.com/flutter/devtools/pull/4602) ## Full commit history To find a complete list of changes since the previous release, check out \[the diff on GitHub]\(https\://github.com/flutter/devtools/compare/v2.19.0...v2.20.0).

----
url: https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.61.0
----

The 2.61.0 release of the Dart and Flutter DevTools includes the following changes among other general improvements. To learn more about DevTools, check out the [DevTools overview](/tools/devtools).

## General updates

[#](#general-updates)

* Fixed unreadable text in the release notes panel, where blockquotes were drawn on a hard coded light blue background in the dark theme. [#9957](https://github.com/flutter/devtools/pull/9957)
* Added support for proxying the VM service connection over a `MessagePort` using `?uri=messageport:<origin>`. [#9964](https://github.com/flutter/devtools/pull/9964)

## Inspector updates

[#](#inspector-updates)

* Added the widget source file path to the Inspector details pane (`filename.dart:line:column`), matching legacy Inspector behavior. [#9972](https://github.com/flutter/devtools/pull/9972), [#9922](https://github.com/flutter/devtools/issues/9922)

## Performance updates

[#](#performance-updates)

* Fixed a bug where the Flutter frames chart could appear frozen after switching away from the Performance screen and coming back. [#9960](https://github.com/flutter/devtools/pull/9960)
* Added a message in the Performance panel when widget rebuild tracking is unavailable because the app is running in profile mode. [#9755](https://github.com/flutter/devtools/pull/9755)

## CPU profiler updates

[#](#cpu-profiler-updates)

* Fixed a bug where resizing the CPU flame chart changes the timing values across the top of the chart. [#9915](https://github.com/flutter/devtools/pull/9915)

## Memory updates

[#](#memory-updates)

* Added the ability to pin classes to the top of the Profile Memory table. [#8898](https://github.com/flutter/devtools/issues/8898)

## Debugger updates

[#](#debugger-updates)

* Prevented values from being garbage-collected, while being evaluated. [#9885](https://github.com/flutter/devtools/pull/9885)
* Updated to latest version of the Dart syntax highlighting grammar [#9920](https://github.com/flutter/devtools/pull/9920)
* Fixed a bug in the TextMate grammar parser that could result in code after comments being classified as comments. [#9921](https://github.com/flutter/devtools/pull/9921)
* Fixed an overflow in the debugging controls when the Debugger screen is narrow, such as when DevTools is embedded in an IDE side panel. The controls now scroll horizontally instead of overflowing. [#9949](https://github.com/flutter/devtools/pull/9949)

## Network profiler updates

[#](#network-profiler-updates)

* Fixed Copy as cURL omitting request headers and body for failed or timed-out requests. [#9963](https://github.com/flutter/devtools/pull/9963)
* Added WebSocket support to the Network profiler, including WebSocket connection details, lifecycle events, frame-level inspection, and connection timing information. [#9968](https://github.com/flutter/devtools/pull/9968)
* Fixed an issue where the Network tab would stop capturing HTTP requests after a hot restart. [#9856](https://github.com/flutter/devtools/pull/9856)
* Fixed an issue where the Network tab would stop capturing new HTTP requests after pressing Clear while recording. [#9856](https://github.com/flutter/devtools/pull/9856)

## Logging updates

[#](#logging-updates)

* Corrected time units and cumulative nature of GC events. [#9890](https://github.com/flutter/devtools/pull/9890)
* Fixed a bug where long `dart:developer` log messages lost their structured Details view. [#10011](https://github.com/flutter/devtools/pull/10011)

## Deep links tool updates

[#](#deep-links-tool-updates)

* Added a "Watch tutorial" link to the status line that points to the [deep links video tutorial](https://youtu.be/d7sZL6h1Elw). [#9925](https://github.com/flutter/devtools/pull/9925)
* Validated build option parameters and surfaced iOS universal link settings errors in the Deep Links tool. [#10022](https://github.com/flutter/devtools/pull/10022)

## DevTools extension updates

[#](#devtools-extension-updates)

* Hide the DevTools extensions menu button in single-screen embedded mode (`EmbedMode.embedOne`) on standard screens. [#8507](https://github.com/flutter/devtools/issues/8507)
* Added iframe sandboxing for embedded DevTools extensions to enforce origin isolation. [#9967](https://github.com/flutter/devtools/pull/9967)
* Improved DevTools extension isolation by tracking the providing package name for enablement, deduplication, and asset loading. [#9981](https://github.com/flutter/devtools/pull/9981)

## Full commit history

[#](#full-commit-history)

To find a complete list of changes in this release, check out the [DevTools git log](https://github.com/flutter/devtools/tree/v2.61.0).

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-10-20. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/release-notes/release-notes-2.61.0.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.61.0\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/release-notes/release-notes-2.61.0.md "Report an issue with this page").

----
url: https://docs.flutter.dev/app-architecture/design-patterns
----

If you've already read through the [architecture guide](/app-architecture/guide) page, or if you're comfortable with Flutter and the MVVM pattern, the following articles are for you.

These articles aren't about high-level app architecture, rather they're about solving specific design problems that improve your application's code base regardless of how you've architected your app. That said, the articles do assume the MVVM pattern laid out on the previous pages in the code examples.

[Optimistic state](#design-patterns-expansion-0)

[* user experience* asynchronous dart](#design-patterns-expansion-0)

[Improve the perception of responsiveness of an application by implementing optimistic state.](#design-patterns-expansion-0)

When building user experiences, the perception of performance is sometimes just as important as the actual performance of the code. In general, users don’t like waiting for an action to finish to see the result, and anything that takes more than a few milliseconds could be considered “slow” or “unresponsive” from the user’s perspective.

Developers can help mitigate this negative perception by presenting a successful UI state before the background task is fully completed. An example of this would be tapping a “Subscribe” button, and seeing it change to “Subscribed” instantly, even if the background call to the ...

[Read full article](/app-architecture/design-patterns/optimistic-state)

[Persistent storage architecture: Key-value data](#design-patterns-expansion-1)

[* data* shared-preferences* dark mode](#design-patterns-expansion-1)

[Save application data to a user's on-device key-value store.](#design-patterns-expansion-1)

Most Flutter applications, no matter how small or big they are, require storing data on the user’s device at some point, such as API keys, user preferences or data that should be available offline.

In this recipe, you will learn how to integrate persistent storage for key-value data in a Flutter application that uses the recommended [Flutter architecture design](/app-architecture). If you aren’t familiar with storing data to disk at all, you can read the [Store key-value data on disk](/cookbook/persistence/key-value) recipe.

Key-value stores are often used for saving simple data, such as app configuration, and in this recipe you’ll ...

[Read full article](/app-architecture/design-patterns/key-value-data)

[Persistent storage architecture: SQL](#design-patterns-expansion-2)

[* data* SQL](#design-patterns-expansion-2)

[Save complex application data to a user's device with SQL.](#design-patterns-expansion-2)

Most Flutter applications, no matter how small or big they are, might require storing data on the user’s device at some point. For example, API keys, user preferences or data that should be available offline.

In this recipe, you will learn how to integrate persistent storage for complex data using SQL in a Flutter application following the Flutter Architecture design pattern.

To learn how to store simpler key-value data, take a look at the Cookbook recipe: [Persistent storage architecture: Key-value data](/app-architecture/design-patterns/key-value-data).

To read this recipe, you should be familiar with SQL and SQLite. If you need help, you ...

[Read full article](/app-architecture/design-patterns/sql)

[Offline-first support](#design-patterns-expansion-3)

[* data* user experience* repository pattern](#design-patterns-expansion-3)

[Implement offline-first support for one feature in an application.](#design-patterns-expansion-3)

An offline-first application is an app capable of offering most or all of its functionality while being disconnected from the internet. Offline-first applications usually rely on stored data to offer users temporary access to data that would otherwise only be available online.

Some offline-first applications combine local and remote data seamlessly, while other applications inform the user when the application is using cached data. In the same way, some applications synchronize data in the background while others require the user to explicitly synchronize it. It all depends on the application requirements and the functionality it offers, and it’s ...

[Read full article](/app-architecture/design-patterns/offline-first)

[The command pattern](#design-patterns-expansion-4)

[* mvvm* asynchronous dart* state](#design-patterns-expansion-4)

[Simplify view model logic by implementing a Command class.](#design-patterns-expansion-4)

[Model-View-ViewModel (MVVM)](/app-architecture/guide#view-models) is a design pattern that separates a feature of an application into three parts: the model, the view model, and the view. Views and view models make up the UI layer of an application. Repositories and services represent the data layer of an application, or the model layer of MVVM.

A command is a class that wraps a method and helps to handle the different states of that method, such as running, complete, and error.

[View models](/app-architecture/guide#view-models) can use commands to handle interaction and run actions. You can also use them to display different UI states, ...

[Read full article](/app-architecture/design-patterns/command)

[Error handling with Result objects](#design-patterns-expansion-5)

[* error handling* services](#design-patterns-expansion-5)

[Improve error handling across classes with Result objects.](#design-patterns-expansion-5)

Dart provides a built-in error handling mechanism with the ability to throw and catch exceptions.

As mentioned in the [Error handling documentation](https://dart.dev/language/error-handling), Dart's exceptions are unhandled exceptions. This means that methods that throw exceptions don’t need to declare them, and calling methods aren't required to catch them either.

This can lead to situations where exceptions are not handled properly. In large projects, developers might forget to catch exceptions, and the different application layers and components could throw exceptions that aren’t documented. This can lead to errors and crashes.

In this guide, you will learn about this limitation and ...

[Read full article](/app-architecture/design-patterns/result)

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/app-architecture/design-patterns.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/app-architecture/design-patterns\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/app-architecture/design-patterns.md "Report an issue with this page").

----
url: https://docs.flutter.dev/packages-and-plugins/swift-package-manager/for-plugin-authors
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Swift Package Manager for plugin authors](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

For information on how to turn SwiftPM off and on, visit [Swift Package Manager for app developers](/packages-and-plugins/swift-package-manager/for-app-developers).

## How to add Swift Package Manager support to an existing Flutter plugin

[#](#how-to-add-swift-package-manager-support-to-an-existing-flutter-plugin)

This guide shows how to add Swift Package Manager support to a plugin that already supports CocoaPods. This ensures the plugin is usable by all Flutter projects.

Flutter plugins should support *both* Swift Package Manager and CocoaPods until further notice.

Swift Package Manager adoption will be gradual. As of Flutter 3.44, plugins that don't support CocoaPods aren't usable by projects that haven't migrated to Swift Package Manager yet. Plugins that don't support Swift Package Manager can cause problems for projects that have migrated. Please migrate your plugins as soon as possible.

* [Swift plugin](#14-tab-panel)
* [Objective-C plugin](#15-tab-panel)

Replace `plugin_name` throughout this guide with the name of your plugin. The example below uses `ios`, replace `ios` with `macos` or `darwin`, as applicable.

1. Ensure that you are using Flutter 3.44 or later. This enables SwiftPM by default.

2. Start by creating a directory under the `ios`, `macos`, and/or `darwin` directories. Name this new directory the name of the platform package.

   * plugin\_name/

     * ios/

       * …
       * plugin\_name/

3. Within this new directory, create the following files/directories:

   * `Package.swift` (file)
   * `Sources` (directory)
   * `Sources/plugin_name` (directory)

   Your plugin should look like:

   * plugin\_name/

     * ios/

       * …

       * plugin\_name/

         * Package.swift

         * Sources/

           * plugin\_name/

4. Use the following template in the `Package.swift` file:

   Package.swift

   swift

   ```
   // swift-tools-version: 5.9
   // The swift-tools-version declares the minimum version of Swift required to build this package.
   ​
   import PackageDescription
   ​
   let package = Package(
       // TODO: Update your plugin name.
       name: "plugin_name",
       platforms: [
           // TODO: Update the platforms your plugin supports.
           // If your plugin only supports iOS, remove `.macOS(...)`.
           // If your plugin only supports macOS, remove `.iOS(...)`.
           .iOS("13.0"),
           .macOS("10.15")
       ],
       products: [
           // TODO: Update your library and target names.
           // If the plugin name contains "_", replace with "-" for the library name.
           .library(name: "plugin-name", targets: ["plugin_name"])
       ],
       dependencies: [
           .package(name: "FlutterFramework", path: "../FlutterFramework")
       ],
       targets: [
           .target(
               // TODO: Update your target name.
               name: "plugin_name",
               dependencies: [
                   .product(name: "FlutterFramework", package: "FlutterFramework")
               ],
               resources: [
                   // TODO: If your plugin requires a privacy manifest
                   // (e.g. if it uses any required reason APIs), update the PrivacyInfo.xcprivacy file
                   // to describe your plugin's privacy impact, and then uncomment this line.
                   // For more information, visit:
                   // https://developer.apple.com/documentation/bundleresources/privacy_manifest_files
                   // .process("PrivacyInfo.xcprivacy"),
   ​
                   // TODO: If you have other resources that need to be bundled with your plugin, refer to
                   // the following instructions to add them:
                   // https://developer.apple.com/documentation/xcode/bundling-resources-with-a-swift-package
               ]
           )
       ]
   )
   ```

5. Update the [supported platforms](https://developer.apple.com/documentation/packagedescription/supportedplatform) in your `Package.swift` file.

   Package.swift

   swift

   ```
       platforms: [
           // TODO: Update the platforms your plugin supports.
           // If your plugin only supports iOS, remove `.macOS(...)`.
           // If your plugin only supports macOS, remove `.iOS(...)`.
           .iOS("13.0"),
           .macOS("10.15")
       ],
   ```

6. Update the package, library, and target names in your `Package.swift` file.

   Package.swift

   swift

   ```
   let package = Package(
       // TODO: Update your plugin name.
       name: "plugin_name",
       platforms: [
           .iOS("13.0"),
           .macOS("10.15")
       ],
       products: [
           // TODO: Update your library and target names.
           // If the plugin name contains "_", replace with "-" for the library name
           .library(name: "plugin-name", targets: ["plugin_name"])
       ],
       dependencies: [
           .package(name: "FlutterFramework", path: "../FlutterFramework")
       ],
       targets: [
           .target(
               // TODO: Update your target name.
               name: "plugin_name",
               dependencies: [
                   .product(name: "FlutterFramework", package: "FlutterFramework")
               ],
               resources: [
                   // TODO: If your plugin requires a privacy manifest
                   // (e.g. if it uses any required reason APIs), update the PrivacyInfo.xcprivacy file
                   // to describe your plugin's privacy impact, and then uncomment this line.
                   // For more information, visit:
                   // https://developer.apple.com/documentation/bundleresources/privacy_manifest_files
                   // .process("PrivacyInfo.xcprivacy"),
   ​
                   // TODO: If you have other resources that need to be bundled with your plugin, refer to
                   // the following instructions to add them:
                   // https://developer.apple.com/documentation/xcode/bundling-resources-with-a-swift-package
               ]
           )
       ]
   )
   ```

7. If your plugin has a [`PrivacyInfo.xcprivacy` file](https://developer.apple.com/documentation/bundleresources/privacy_manifest_files), move it to `ios/plugin_name/Sources/plugin_name/PrivacyInfo.xcprivacy` and uncomment the resource in the `Package.swift` file.

   Package.swift

   swift

   ```
               resources: [
                   // TODO: If your plugin requires a privacy manifest
                   // (e.g. if it uses any required reason APIs), update the PrivacyInfo.xcprivacy file
                   // to describe your plugin's privacy impact, and then uncomment this line.
                   // For more information, visit:
                   // https://developer.apple.com/documentation/bundleresources/privacy_manifest_files
                   .process("PrivacyInfo.xcprivacy"),
   ​
                   // TODO: If you have other resources that need to be bundled with your plugin, refer to
                   // the following instructions to add them:
                   // https://developer.apple.com/documentation/xcode/bundling-resources-with-a-swift-package
               ],
   ```

8. Move any resource files from `ios/Assets` to `ios/plugin_name/Sources/plugin_name` (or a subdirectory). Add the resource files to your `Package.swift` file, if applicable. For more instructions, visit [Bundling resources with a Swift package](https://developer.apple.com/documentation/xcode/bundling-resources-with-a-swift-package).

9. Move all files from `ios/Classes` to `ios/plugin_name/Sources/plugin_name`.

10. Add the `FlutterFramework` as a dependency.

    Update `Package.swift` to include `FlutterFramework`:

    Package.swift

    swift

    ```
    dependencies: [
        .package(name: "FlutterFramework", path: "../FlutterFramework")
    ],
    targets: [
        .target(
            // TODO: Update your target name.
            name: "plugin_name",
            dependencies: [
                .product(name: "FlutterFramework", package: "FlutterFramework")
            ],
       )
    ]
    ```

11. The `ios/Assets`, `ios/Resources`, and `ios/Classes` directories should now be empty and can be deleted.

12. If your plugin uses [Pigeon](https://pub.dev/packages/pigeon), update your Pigeon input file.

    pigeons/messages.dart

    dart

    ```
    kotlinOptions: KotlinOptions(),
    javaOut: 'android/app/src/main/java/io/flutter/plugins/Messages.java',
    javaOptions: JavaOptions(),
    swiftOut: 'ios/Classes/messages.g.swift',
    swiftOut: 'ios/plugin_name/Sources/plugin_name/messages.g.swift',
    swiftOptions: SwiftOptions(),
    ```

13. Update your `Package.swift` file with any customizations you might need.

    1. In Xcode, open the `ios/plugin_name/` directory.

    2. In Xcode, open your `Package.swift` file. Verify Xcode doesn't produce any warnings or errors for this file.

    3. If your `ios/plugin_name.podspec` file has [CocoaPods `dependency`](https://guides.cocoapods.org/syntax/podspec.html#dependency)s, add the corresponding [Swift Package Manager dependencies](https://developer.apple.com/documentation/packagedescription/package/dependency) to your `Package.swift` file.

    4. If your package must be linked explicitly `static` or `dynamic` ([not recommended by Apple](https://developer.apple.com/documentation/packagedescription/product/library\(name:type:targets:\))), update the [Product](https://developer.apple.com/documentation/packagedescription/product) to define the type:

       Package.swift

       swift

       ```
       products: [
           .library(name: "plugin-name", type: .static, targets: ["plugin_name"])
       ],
       ```

    5. Make any other customizations. For more information on how to write a `Package.swift` file, visit [`PackageDescription`](https://developer.apple.com/documentation/packagedescription).

14. Update your `ios/plugin_name.podspec` to point to new paths.

    ios/plugin\_name.podspec

    ruby

    ```
    s.source_files = 'Classes/**/*.swift'
    s.resource_bundles = {'plugin_name_privacy' => ['Resources/PrivacyInfo.xcprivacy']}
    s.source_files = 'plugin_name/Sources/plugin_name/**/*.swift'
    s.resource_bundles = {'plugin_name_privacy' => ['plugin_name/Sources/plugin_name/PrivacyInfo.xcprivacy']}
    ```

15. Update loading of resources from bundle to use [`Bundle.module`](https://developer.apple.com/documentation/xcode/bundling-resources-with-a-swift-package#Access-a-resource-in-code).

    swift

    ```
    #if SWIFT_PACKAGE
         let settingsURL = Bundle.module.url(forResource: "image", withExtension: "jpg")
    #else
         let settingsURL = Bundle(for: Self.self).url(forResource: "image", withExtension: "jpg")
    #endif
    ```

16. If your `.gitignore` doesn't include `.build/` and `.swiftpm/` directories, you'll want to update your `.gitignore` to include:

    .gitignore

    ```
    .build/
    .swiftpm/
    ```

    Commit your plugin's changes to your version control system.

17. Verify the plugin still works with CocoaPods.

    1. Turn off Swift Package Manager.

       sh

       ```
       flutter config --no-enable-swift-package-manager
       ```

    2. Navigate to the plugin's example app.

       sh

       ```
       cd path/to/plugin/example/
       ```

    3. Ensure the plugin's example app builds and runs.

       sh

       ```
       flutter run
       ```

    4. Navigate to the plugin's top-level directory.

       sh

       ```
       cd path/to/plugin/
       ```

    5. Run CocoaPods validation lints.

       sh

       ```
       pod lib lint ios/plugin_name.podspec  --configuration=Debug --skip-tests --use-modular-headers --use-libraries
       ```

       sh

       ```
       pod lib lint ios/plugin_name.podspec  --configuration=Debug --skip-tests --use-modular-headers
       ```

18. Verify the plugin works with Swift Package Manager.

    1. Turn on Swift Package Manager.

       sh

       ```
       flutter config --enable-swift-package-manager
       ```

    2. Navigate to the plugin's example app.

       sh

       ```
       cd path/to/plugin/example/
       ```

    3. Ensure the plugin's example app builds and runs.

       sh

       ```
       flutter run
       ```

    4. In Xcode, open the plugin's example app. Ensure that **Package Dependencies** shows in the left **Project Navigator**.

19. Verify tests pass.

    * **If your plugin has native unit tests (XCTest), make sure you also [update unit tests in the plugin's example app](/packages-and-plugins/swift-package-manager/for-plugin-authors/#how-to-update-unit-tests-in-a-plugins-example-app).**

    * Follow instructions for [testing plugins](/testing/testing-plugins).

Replace `plugin_name` throughout this guide with the name of your plugin. The example below uses `ios`, replace `ios` with `macos` or `darwin`, as applicable.

1. Ensure that you are running Flutter 3.44 or later. This enables SwiftPM by default.

2. Start by creating a directory under the `ios`, `macos`, and/or `darwin` directories. Name this new directory the name of the platform package.

   * plugin\_name/

     * ios/

       * …
       * plugin\_name/

3. Within this new directory, create the following files/directories:

   * `Package.swift` (file)
   * `Sources` (directory)
   * `Sources/plugin_name` (directory)
   * `Sources/plugin_name/include` (directory)
   * `Sources/plugin_name/include/plugin_name` (directory)
   * `Sources/plugin_name/include/plugin_name/.gitkeep` (file)
     * This file ensures the directory is committed. You can remove the `.gitkeep` file if other files are added to the directory.

   Your plugin should look like:

   * plugin\_name/

     * ios/

       * …

       * plugin\_name/

         * Package.swift

         * Sources/plugin\_name/include/plugin\_name/

           * .gitkeep/

4. Use the following template in the `Package.swift` file:

   Package.swift

   swift

   ```
   // swift-tools-version: 5.9
   // The swift-tools-version declares the minimum version of Swift required to build this package.
   ​
   import PackageDescription
   ​
   let package = Package(
       // TODO: Update your plugin name.
       name: "plugin_name",
       platforms: [
           // TODO: Update the platforms your plugin supports.
           // If your plugin only supports iOS, remove `.macOS(...)`.
           // If your plugin only supports macOS, remove `.iOS(...)`.
           .iOS("13.0"),
           .macOS("10.15")
       ],
       products: [
           // TODO: Update your library and target names.
           // If the plugin name contains "_", replace with "-" for the library name
           .library(name: "plugin-name", targets: ["plugin_name"])
       ],
       dependencies: [
           .package(name: "FlutterFramework", path: "../FlutterFramework")
       ],
       targets: [
           .target(
               // TODO: Update your target name.
               name: "plugin_name",
               dependencies: [
                   .product(name: "FlutterFramework", package: "FlutterFramework")
               ],
               resources: [
                   // TODO: If your plugin requires a privacy manifest
                   // (in other words, if it uses any required reason APIs),
                   // update the PrivacyInfo.xcprivacy file
                   // to describe your plugin's privacy impact, and then uncomment this line.
                   // For more information, visit:
                   // https://developer.apple.com/documentation/bundleresources/privacy_manifest_files
                   // .process("PrivacyInfo.xcprivacy"),
   ​
                   // TODO: If you have other resources that need to be bundled with your plugin, refer to
                   // the following instructions to add them:
                   // https://developer.apple.com/documentation/xcode/bundling-resources-with-a-swift-package
               ],
               cSettings: [
                   // TODO: Update your plugin name.
                   .headerSearchPath("include/plugin_name")
               ]
           )
       ]
   )
   ```

5. Update the [supported platforms](https://developer.apple.com/documentation/packagedescription/supportedplatform) in your `Package.swift` file.

   Package.swift

   swift

   ```
       platforms: [
           // TODO: Update the platforms your plugin supports.
           // If your plugin only supports iOS, remove `.macOS(...)`.
           // If your plugin only supports macOS, remove `.iOS(...)`.
           .iOS("13.0"),
           .macOS("10.15")
       ],
   ```

6. Update the package, library, and target names in your `Package.swift` file.

   Package.swift

   swift

   ```
   let package = Package(
       // TODO: Update your plugin name.
       name: "plugin_name",
       platforms: [
           .iOS("13.0"),
           .macOS("10.15")
       ],
       products: [
           // TODO: Update your library and target names.
           // If the plugin name contains "_", replace with "-" for the library name
           .library(name: "plugin-name", targets: ["plugin_name"])
       ],
       dependencies: [
           .package(name: "FlutterFramework", path: "../FlutterFramework")
       ],
       targets: [
           .target(
               // TODO: Update your target name.
               name: "plugin_name",
               dependencies: [
                   .product(name: "FlutterFramework", package: "FlutterFramework")
               ],
               resources: [
                   // TODO: If your plugin requires a privacy manifest
                   // (for example, if it uses any required reason APIs),
                   // update the PrivacyInfo.xcprivacy file
                   // to describe your plugin's privacy impact, and then uncomment this line.
                   // For more information, visit:
                   // https://developer.apple.com/documentation/bundleresources/privacy_manifest_files
                   // .process("PrivacyInfo.xcprivacy"),
   ​
                   // TODO: If you have other resources that need to be bundled with your plugin, refer to
                   // the following instructions to add them:
                   // https://developer.apple.com/documentation/xcode/bundling-resources-with-a-swift-package
               ],
               cSettings: [
                   // TODO: Update your plugin name.
                   .headerSearchPath("include/plugin_name")
               ]
           )
       ]
   )
   ```

7. If your plugin has a [`PrivacyInfo.xcprivacy` file](https://developer.apple.com/documentation/bundleresources/privacy_manifest_files), move it to `ios/plugin_name/Sources/plugin_name/PrivacyInfo.xcprivacy` and uncomment the resource in the `Package.swift` file.

   Package.swift

   swift

   ```
               resources: [
                   // TODO: If your plugin requires a privacy manifest
                   // (for example, if it uses any required reason APIs),
                   // update the PrivacyInfo.xcprivacy file
                   // to describe your plugin's privacy impact, and then uncomment this line.
                   // For more information, visit:
                   // https://developer.apple.com/documentation/bundleresources/privacy_manifest_files
                   .process("PrivacyInfo.xcprivacy"),
   ​
                   // TODO: If you have other resources that need to be bundled with your plugin, refer to
                   // the following instructions to add them:
                   // https://developer.apple.com/documentation/xcode/bundling-resources-with-a-swift-package
               ],
   ```

8. Move any resource files from `ios/Assets` to `ios/plugin_name/Sources/plugin_name` (or a subdirectory). Add the resource files to your `Package.swift` file, if applicable. For more instructions, visit <https://developer.apple.com/documentation/xcode/bundling-resources-with-a-swift-package>.

9. Move any public headers from `ios/Classes` to `ios/plugin_name/Sources/plugin_name/include/plugin_name`.

   * If you're unsure which headers are public, check your `podspec` file's [`public_header_files`](https://guides.cocoapods.org/syntax/podspec.html#public_header_files) attribute. If this attribute isn't specified, all of your headers were public. You should consider whether you want all of your headers to be public.

   * The `pluginClass` defined in your `pubspec.yaml` file must be public and within this directory.

10. Handling `modulemap`.

    Skip this step if your plugin does not have a `modulemap`.

    If you're using a `modulemap` for CocoaPods to create a Test submodule, consider removing it for Swift Package Manager. Note that this makes all public headers available through the module.

    To remove the `modulemap` for Swift Package Manager but keep it for CocoaPods, exclude the `modulemap` and umbrella header in the plugin's `Package.swift` file.

    The example below assumes the `modulemap` and umbrella header are located in the `ios/plugin_name/Sources/plugin_name/include` directory.

    Package.swift

    swift

    ```
    .target(
        name: "plugin_name",
        dependencies: [],
        exclude: ["include/cocoapods_plugin_name.modulemap", "include/plugin_name-umbrella.h"],
    ```

    If you want to keep your unit tests compatible with both CocoaPods and Swift Package Manager, you can try the following:

    Tests/TestFile.m

    objc

    ```
    @import plugin_name;
    @import plugin_name.Test;
    #if __has_include(<plugin_name/plugin_name-umbrella.h>)
      @import plugin_name.Test;
    #endif
    ```

    If you would like to use a custom `modulemap` with your Swift package, refer to [Swift Package Manager's documentation](https://github.com/apple/swift-package-manager/blob/main/Documentation/Usage.md#creating-c-language-targets).

11. Move all remaining files from `ios/Classes` to `ios/plugin_name/Sources/plugin_name`.

12. The `ios/Assets`, `ios/Resources`, and `ios/Classes` directories should now be empty and can be deleted.

13. Add the `FlutterFramework` as a dependency.

    Update `Package.swift` to include `FlutterFramework`:

    Package.swift

    swift

    ```
    dependencies: [
        .package(name: "FlutterFramework", path: "../FlutterFramework")
    ],
    targets: [
        .target(
            // TODO: Update your target name.
            name: "plugin_name",
            dependencies: [
                .product(name: "FlutterFramework", package: "FlutterFramework")
            ]
        )
    ]
    ```

14. If your header files are no longer in the same directory as your implementation files, you should update your import statements.

    For example, imagine the following migration:

    * Before:

      ```
      ios/Classes/
      ├── PublicHeaderFile.h
      └── ImplementationFile.m
      ```

    * After:

      ```
      ios/plugin_name/Sources/plugin_name/
      └── include/plugin_name/
         └── PublicHeaderFile.h
      └── ImplementationFile.m
      ```

    In this example, the import statements in `ImplementationFile.m` should be updated:

    Sources/plugin\_name/ImplementationFile.m

    objc

    ```
    #import "PublicHeaderFile.h"
    #import "./include/plugin_name/PublicHeaderFile.h"
    ```

15. If your plugin uses [Pigeon](https://pub.dev/packages/pigeon), update your Pigeon input file.

    pigeons/messages.dart

    dart

    ```
    javaOptions: JavaOptions(),
    objcHeaderOut: 'ios/Classes/messages.g.h',
    objcSourceOut: 'ios/Classes/messages.g.m',
    objcHeaderOut: 'ios/plugin_name/Sources/plugin_name/messages.g.h',
    objcSourceOut: 'ios/plugin_name/Sources/plugin_name/messages.g.m',
    copyrightHeader: 'pigeons/copyright.txt',
    ```

    If your `objcHeaderOut` file is no longer within the same directory as the `objcSourceOut`, you can change the `#import` using `ObjcOptions.headerIncludePath`:

    pigeons/messages.dart

    dart

    ```
    javaOptions: JavaOptions(),
    objcHeaderOut: 'ios/Classes/messages.g.h',
    objcSourceOut: 'ios/Classes/messages.g.m',
    objcHeaderOut: 'ios/plugin_name/Sources/plugin_name/include/plugin_name/messages.g.h',
    objcSourceOut: 'ios/plugin_name/Sources/plugin_name/messages.g.m',
    objcOptions: ObjcOptions(
      headerIncludePath: './include/plugin_name/messages.g.h',
    ),
    copyrightHeader: 'pigeons/copyright.txt',
    ```

    Run Pigeon to re-generate its code with the latest configuration.

16. Update your `Package.swift` file with any customizations you might need.

    1. In Xcode, open the `ios/plugin_name/` directory.

    2. In Xcode, open your `Package.swift` file. Verify that Xcode doesn't produce any warnings or errors for this file.

    3. If your `ios/plugin_name.podspec` file has [CocoaPods `dependency`](https://guides.cocoapods.org/syntax/podspec.html#dependency)s, add the corresponding [Swift Package Manager dependencies](https://developer.apple.com/documentation/packagedescription/package/dependency) to your `Package.swift` file.

    4. If your package must be linked explicitly `static` or `dynamic` ([not recommended by Apple](https://developer.apple.com/documentation/packagedescription/product/library\(name:type:targets:\))), update the [Product](https://developer.apple.com/documentation/packagedescription/product) to define the type:

       Package.swift

       swift

       ```
       products: [
           .library(name: "plugin-name", type: .static, targets: ["plugin_name"])
       ],
       ```

    5. Make any other customizations. For more information on how to write a `Package.swift` file, visit <https://developer.apple.com/documentation/packagedescription>.

17. Update your `ios/plugin_name.podspec` to point to new paths.

    ios/plugin\_name.podspec

    ruby

    ```
    s.source_files = 'Classes/**/*.{h,m}'
    s.public_header_files = 'Classes/**/*.h'
    s.module_map = 'Classes/cocoapods_plugin_name.modulemap'
    s.resource_bundles = {'plugin_name_privacy' => ['Resources/PrivacyInfo.xcprivacy']}
    s.source_files = 'plugin_name/Sources/plugin_name/**/*.{h,m}'
    s.public_header_files = 'plugin_name/Sources/plugin_name/include/**/*.h'
    s.module_map = 'plugin_name/Sources/plugin_name/include/cocoapods_plugin_name.modulemap'
    s.resource_bundles = {'plugin_name_privacy' => ['plugin_name/Sources/plugin_name/PrivacyInfo.xcprivacy']}
    ```

18. Update loading of resources from bundle to use `SWIFTPM_MODULE_BUNDLE`:

    objc

    ```
    #if SWIFT_PACKAGE
       NSBundle *bundle = SWIFTPM_MODULE_BUNDLE;
     #else
       NSBundle *bundle = [NSBundle bundleForClass:[self class]];
     #endif
     NSURL *imageURL = [bundle URLForResource:@"image" withExtension:@"jpg"];
    ```

19. If your `ios/plugin_name/Sources/plugin_name/include` directory only contains a `.gitkeep`, you'll want to update your `.gitignore` to include the following:

    .gitignore

    ```
    !.gitkeep
    ```

    Run `flutter pub publish --dry-run` to ensure the `include` directory is published.

20. Commit your plugin's changes to your version control system.

21. Verify the plugin still works with CocoaPods.

    1. Turn off Swift Package Manager:

       sh

       ```
       flutter config --no-enable-swift-package-manager
       ```

    2. Navigate to the plugin's example app.

       sh

       ```
       cd path/to/plugin/example/
       ```

    3. Ensure the plugin's example app builds and runs.

       sh

       ```
       flutter run
       ```

    4. Navigate to the plugin's top-level directory.

       sh

       ```
       cd path/to/plugin/
       ```

    5. Run CocoaPods validation lints:

       sh

       ```
       pod lib lint ios/plugin_name.podspec  --configuration=Debug --skip-tests --use-modular-headers --use-libraries
       ```

       sh

       ```
       pod lib lint ios/plugin_name.podspec  --configuration=Debug --skip-tests --use-modular-headers
       ```

22. Verify the plugin works with Swift Package Manager.

    1. Turn on Swift Package Manager:

       sh

       ```
       flutter config --enable-swift-package-manager
       ```

    2. Navigate to the plugin's example app.

       sh

       ```
       cd path/to/plugin/example/
       ```

    3. Ensure the plugin's example app builds and runs.

       sh

       ```
       flutter run
       ```

    4. In Xcode, open the plugin's example app. Ensure that **Package Dependencies** shows in the left **Project Navigator**.

23. Verify tests pass.

    * **If your plugin has native unit tests (XCTest), make sure you also [update unit tests in the plugin's example app](/packages-and-plugins/swift-package-manager/for-plugin-authors/#how-to-update-unit-tests-in-a-plugins-example-app).**

    * Follow instructions for [testing plugins](/testing/testing-plugins).

## (Optional, but recommended) Add plugin as local package in example app

[#](#optional-but-recommended-add-plugin-as-local-package-in-example-app)

If your plugin includes an example, it is recommended to add the plugin as a local package in the example app. This is not required, but provides better Xcode support when editing the plugin's source code in the example app. Visit [issue #179032](https://github.com/flutter/flutter/issues/179032).

### Add plugin as local package

[#](#add-plugin-as-local-package)

1. In a terminal navigate to `my_plugin`.

2. In Xcode, run the following command to open the example app's workspace, (replace `ios` with `macos` as appropriate):

bash

```
open example/ios/Runner.xcworkspace
```

1. Right click **Flutter** > **Add Files to “Runner”**.

2. Select `my_plugin/ios/my_plugin` (or `macos` or `darwin`, as appropriate).

3. Make sure “Reference files in place” is selected (it should be the default), and click **Finish**.

This adds the plugin as a local package, but it's referenced by absolute path, which isn't desirable for distribution. To change it to a relative path, use the following instructions.

### Change to relative path

[#](#change-to-relative-path)

1. Copy “Full Path” for plugin from the File Inspector.

2. In terminal: `open -a Xcode example/ios/Runner.xcodeproj/project.pbxproj`

3. Find the following:

   ```
   path = [COPIED FULL PATH]; sourceTree = "<absolute>"
   ```

   For example:

   ```
   path = /Users/username/path/to/my_plugin/ios/my_plugin; sourceTree = "<absolute>"
   ```

4. And replace with relative path:

   ```
   path = ../../ios/my_plugin; sourceTree = "<group>"
   ```

   (Adjust `ios` to `macos` or `darwin` as needed).

## How to update unit tests in a plugin's example app

[#](#how-to-update-unit-tests-in-a-plugins-example-app)

If your plugin has native XCTests, you might need to update them to work with Swift Package Manager if one of the following is true:

* You're using a CocoaPod dependency for the test.
* Your plugin is explicitly set to `type: .dynamic` in its `Package.swift` file.

To update your unit tests:

1. In Xcode, open your `example/ios/Runner.xcworkspace`.

2. If you were using a CocoaPod dependency for tests, such as `OCMock`, you'll want to remove it from your `Podfile` file.

   ios/Podfile

   ruby

   ```
   target 'RunnerTests' do
     inherit! :search_paths
   ​
     pod 'OCMock', '3.5'
   end
   ```

   Then in the terminal, run `pod install` in the `plugin_name_ios/example/ios` directory.

3. Navigate to **Package Dependencies** for the project.

   The project's package dependencies

4. Click the **+** button and add any test-only dependencies by searching for them in the top right search bar.

   Search for test-only dependencies

5. Ensure the dependency is added to the `RunnerTests` Target.

   Ensure the dependency is added to the `RunnerTests` target

6. Click the **Add Package** button.

7. If you've explicitly set your plugin's library type to `.dynamic` in its `Package.swift` file ([not recommended by Apple](https://developer.apple.com/documentation/packagedescription/product/library\(name:type:targets:\))), you'll also need to add it as a dependency to the `RunnerTests` target.

   1. Ensure `RunnerTests` **Build Phases** has a **Link Binary With Libraries** build phase:

      The `Link Binary With Libraries` Build Phase in the `RunnerTests` target

      If the build phase doesn't exist already, create one. Click the add button and then click **New Link Binary With Libraries Phase**.

      Add `Link Binary With Libraries` Build Phase

   2. Navigate to **Package Dependencies** for the project.

   3. Click the add button.

   4. In the dialog that opens, click the **Add Local...** button.

   5. Navigate to `plugin_name/plugin_name_ios/ios/plugin_name_ios` and click the **Add Package** button.

   6. Ensure that it's added to the `RunnerTests` target and click the **Add Package** button.

8. Ensure tests pass **Product > Test**.

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-06-08. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/packages-and-plugins/swift-package-manager/for-plugin-authors.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/packages-and-plugins/swift-package-manager/for-plugin-authors\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/packages-and-plugins/swift-package-manager/for-plugin-authors.md "Report an issue with this page").

----
url: https://docs.flutter.dev/cookbook/navigation/hero-animations.md
----

\# Animate a widget across screens > How to animate a widget from one screen to another

<!--?code-excerpt path-base="cookbook/navigation/hero_animations"?-->

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 the two screens. Use the \[\`Hero\`]\[] widget to animate a widget from one screen to the next. This recipe uses the following steps: 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. ## 1. Create two screens showing the same image 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; handle animations in the next steps. :::note This example builds upon the \[Navigate to a new screen and back]\[] and \[Handle taps]\[] recipes. :::

<!--?code-excerpt "lib/main_original.dart"?-->

\`\`\`dart import 'package:flutter/material.dart'; class MainScreen extends StatelessWidget { const MainScreen({super.key}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('Main Screen')), body: GestureDetector( onTap: () { Navigator.push( context, MaterialPageRoute( builder: (context) { return const DetailScreen(); }, ), ); }, child: Image.network('https\://picsum.photos/250?image=9'), ), ); } } class DetailScreen extends StatelessWidget { const DetailScreen({super.key}); @override Widget build(BuildContext context) { return Scaffold( body: GestureDetector( onTap: () { Navigator.pop(context); }, child: Center( child: Image.network('https\://picsum.photos/250?image=9'), ), ), ); } } \`\`\` ## 2. Add a \`Hero\` widget to the first screen 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: \`tag\` : An object that identifies the \`Hero\`. It must be the same on both screens. \`child\` : The widget to animate across screens.

<!--?code-excerpt "lib/main.dart (Hero1)" replace="/^child: //g;/^\),$/)/g"?-->

\`\`\`dart Hero( tag: 'imageHero', child: Image.network('https\://picsum.photos/250?image=9'), ) \`\`\` ## 3. Add a \`Hero\` widget to the second screen To complete the connection with the first screen, wrap the \`Image\` on the second screen with a \`Hero\` widget that has the same \`tag\` as the \`Hero\` in the first screen. After applying the \`Hero\` widget to the second screen, the animation between screens just works.

<!--?code-excerpt "lib/main.dart (Hero2)" replace="/^child: //g;/^\),$/)/g"?-->

\`\`\`dart Hero( tag: 'imageHero', child: Image.network('https\://picsum.photos/250?image=9'), ) \`\`\` :::note This code is identical to what you have on the first screen. As a best practice, create a reusable widget instead of repeating code. This example uses identical code for both widgets, for simplicity. ::: ## Interactive example

<!--?code-excerpt "lib/main.dart"?-->

\`\`\`dartpad title="Flutter Hero animation hands-on example in DartPad" run="true" import 'package:flutter/material.dart'; void main() => runApp(const HeroApp()); class HeroApp extends StatelessWidget { const HeroApp({super.key}); @override Widget build(BuildContext context) { return const MaterialApp(title: 'Transition Demo', home: MainScreen()); } } class MainScreen extends StatelessWidget { const MainScreen({super.key}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('Main Screen')), body: GestureDetector( onTap: () { Navigator.push( context, MaterialPageRoute( builder: (context) { return const DetailScreen(); }, ), ); }, child: Hero( tag: 'imageHero', child: Image.network('https\://picsum.photos/250?image=9'), ), ), ); } } class DetailScreen extends StatelessWidget { const DetailScreen({super.key}); @override Widget build(BuildContext context) { return Scaffold( body: GestureDetector( onTap: () { Navigator.pop(context); }, child: Center( child: Hero( tag: 'imageHero', child: Image.network('https\://picsum.photos/250?image=9'), ), ), ), ); } } \`\`\` \[Handle taps]: /cookbook/gestures/handling-taps \[\`Hero\`]: https\://api.flutter.dev/flutter/widgets/Hero-class.html \[Navigate to a new screen and back]: /cookbook/navigation/navigation-basics

----
url: https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.44.0.md
----

\# DevTools 2.44.0 release notes > Release notes for Dart and Flutter DevTools version 2.44.0. The 2.44.0 release of the Dart and Flutter DevTools includes the following changes among other general improvements. To learn more about DevTools, check out the \[DevTools overview]\(/tools/devtools/overview). ## General updates \* Fixed various memory leaks and lifecycle issues. - \[#8901]\(https\://github.com/flutter/devtools/pull/8901), \[#8902]\(https\://github.com/flutter/devtools/pull/8902), \[#8907]\(https\://github.com/flutter/devtools/pull/8907), \[#8917]\(https\://github.com/flutter/devtools/pull/8917), \[#8932]\(https\://github.com/flutter/devtools/pull/8932), \[#8933]\(https\://github.com/flutter/devtools/pull/8933), \[#8934]\(https\://github.com/flutter/devtools/pull/8934), \[#8935]\(https\://github.com/flutter/devtools/pull/8935), \[#8937]\(https\://github.com/flutter/devtools/pull/8937), \[#8953]\(https\://github.com/flutter/devtools/pull/8953), \[#8969]\(https\://github.com/flutter/devtools/pull/8969), \[#8970]\(https\://github.com/flutter/devtools/pull/8970), \[#8975]\(https\://github.com/flutter/devtools/pull/8975) ## CPU profiler updates \* Improved the load time and memory usage of CPU profiles. \* \[#8892]\(https\://github.com/flutter/devtools/pull/8892) \* \[#8878]\(https\://github.com/flutter/devtools/pull/8878) \* \[#8839]\(https\://github.com/flutter/devtools/pull/8839) \* Fixed incorrect duration calculations when there is time during which no samples were taken - \[#8941]\(https\://github.com/flutter/devtools/pull/8941). ## Memory updates \* Changed the memory heap snapshot tool so that references are included in snapshots by default. - \[#8899]\(https\://github.com/flutter/devtools/pull/8899) ## Debugger updates \* Added a tooltip to describe the exception mode drop-down. - \[#8849]\(https\://github.com/flutter/devtools/pull/8849) \* Updated syntax highlighting with support for digit separators and improved comment and string interpolation handling. - \[#8861]\(https\://github.com/flutter/devtools/pull/8861) \* Updated \`string\_scanner\` dependency to avoid some syntax highlighting issues when source contains \`\r\n\` in certain positions on Windows. - \[#8904]\(https\://github.com/flutter/devtools/pull/8904) \* Added soft line wrapping in the debugger console. \[#8855]\(https\://github.com/flutter/devtools/pull/8855). ## Network profiler updates \* Added offline support for the network screen (thanks to @hrajwade96!) - \[#8332]\(https\://github.com/flutter/devtools/pull/8332) !\[Network profiler controls]\(/assets/images/docs/tools/devtools/release-notes/images-2.44.0/network\_controls.png "Network profiler controls") !\[Network profiler open / save button]\(/assets/images/docs/tools/devtools/release-notes/images-2.44.0/network\_open\_save\_button.png "Network profiler open / save button") \* Changed the context menu style to be consistent with other screens \[#8859]\(https\://github.com/flutter/devtools/pull/8859). ## Full commit history To find a complete list of changes in this release, check out the \[DevTools git log]\(https\://github.com/flutter/devtools/tree/v2.44.0).

----
url: https://docs.flutter.dev/platform-integration/web/web-content-in-flutter
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Embedding web content into a Flutter web app](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

In some cases, Flutter web applications need to embed web content not rendered by Flutter. For example, embedding a `google_maps_flutter` view (which uses the Google Maps JavaScript SDK) or a `video_player` (which uses a standard `video` element).

Flutter web can render arbitrary web content within the boundaries of a `Widget`, and the primitives used to implement the example packages mentioned previously are available to all Flutter web applications.

## HtmlElementView

[#](#htmlelementview)

The `HtmlElementView` Flutter widget reserves a space in the layout to be filled with any HTML element. It has two constructors:

* `HtmlElementView.fromTagName`
* `HtmlElementView` and `registerViewFactory`

### `HtmlElementView.fromTagName`

[#](#htmlelementview-fromtagname)

The [`HtmlElementView.fromTagName` constructor](https://api.flutter.dev/flutter/widgets/HtmlElementView/HtmlElementView.fromTagName.html) creates an HTML element from its `tagName`, and provides an `onElementCreated` method to configure that element before it's injected into the DOM:

dart

```
// Create a `video` tag, and set its `src` and some `style` properties...
HtmlElementView.fromTagName(
  tagName: 'video',
  onElementCreated: (Object video) {
    video as web.HTMLVideoElement;
    video.src =
        'https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4';
    video.style.width = '100%';
    video.style.height = '100%';
    // other customizations to the element...
  },
);
```

To learn more about how to interact with DOM APIs, check out the [`HTMLVideoElement` class](https://pub.dev/documentation/web/latest/web/HTMLVideoElement-extension-type.html) in [`package:web`](https://pub.dev/packages/web).

To learn more about the video `Object` that is cast to `web.HTMLVideoElement`, check out Dart's [JS Interoperability](https://dart.dev/interop/js-interop) documentation.

### `HtmlElementView` and `registerViewFactory`

[#](#htmlelementview-and-registerviewfactory)

If you need more control over generating the HTML code you inject, you can use the primitives that Flutter uses to implement the `fromTagName` constructor. In this scenario, register your own HTML element factory for each type of HTML content that you add to your app.

The resulting code has two steps per platform view type:

1. Register the HTML element factory using `platformViewRegistry.registerViewFactory` provided by `dart:ui_web`.
2. Place the widget with the desired `viewType` using `HtmlElementView(viewType: 'viewType')` in your app's widget tree.

For more details about this approach, check out the [`HtmlElementView` widget](https://api.flutter.dev/flutter/widgets/HtmlElementView-class.html) docs.

## Fixing hit testing issues

[#](#fixing-hit-testing-issues)

Because of how Flutter web performs hit testing, underlying platform views might sometimes swallow pointer events before they can reach Flutter.

To prevent losing pointer events intended for Flutter widgets, use the [`package:pointer_interceptor`](https://pub.dev/packages/pointer_interceptor) package.

The [`PointerInterceptor`](https://pub.dev/documentation/pointer_interceptor/latest/pointer_interceptor/PointerInterceptor-class.html) widget creates an empty, transparent platform view and places it directly behind its child widget in paint order. This transparent element catches browser pointer events before they reach the underlying `HtmlElementView`, allowing Flutter's gesture framework to handle the interaction as expected.

### Add the dependency

[#](#add-the-dependency)

Add `pointer_interceptor` to your `pubspec.yaml` file:

yaml

```
dependencies:
  pointer_interceptor: ^0.10.1+2
```

### Wrap interactive widgets

[#](#wrap-interactive-widgets)

To make an individual interactive widget clickable over an `HtmlElementView` (such as a [`FloatingActionButton`](https://pub.dev/documentation/material_ui/latest/material_ui/FloatingActionButton-class.html) or [`ElevatedButton`](https://pub.dev/documentation/material_ui/latest/material_ui/ElevatedButton-class.html)), wrap the widget with `PointerInterceptor`:

dart

```
PointerInterceptor(
  child: FloatingActionButton(
    onPressed: () {
      // Handle button press
    },
    child: const Icon(Icons.add),
  ),
)
```

### Wrap layout and overlay containers

[#](#wrap-layout-and-overlay-containers)

When you use overlay components that cover a platform view, such as a [`Drawer`](https://pub.dev/documentation/material_ui/latest/material_ui/Drawer-class.html), dialog, or popup menu, wrap the container widget in a `PointerInterceptor`:

dart

```
Scaffold(
  drawer: PointerInterceptor(
    child: Drawer(
      child: ListView(
        children: [
          ListTile(
            title: const Text('Home'),
            onTap: () {
              // Handle navigation
            },
          ),
        ],
      ),
    ),
  ),
  body: HtmlElementView.fromTagName(tagName: 'iframe'),
)
```

### Conditionally intercept events

[#](#conditionally-intercept-events)

The `intercepting` property lets you enable or disable pointer interception based on a boolean condition:

dart

```
PointerInterceptor(
  intercepting: isOverlayVisible,
  child: ElevatedButton(
    onPressed: () {
      // Handle button press
    },
    child: const Text('Submit'),
  ),
)
```

When `intercepting` is `false`, `PointerInterceptor` renders its `child` directly without creating an extra platform view in the DOM.

### Debug interceptor bounds

[#](#debug-interceptor-bounds)

The `PointerInterceptor` widget includes a `debug` property. Setting `debug: true` renders a visible colored overlay over the interceptor area, helping you verify the size and position of the intercepted region:

dart

```
PointerInterceptor(
  debug: true,
  child: ElevatedButton(
    onPressed: () {},
    child: const Text('Click me'),
  ),
)
```

## `package:webview_flutter`

[#](#package-webview-flutter)

Embedding a full HTML page inside a Flutter app is a common requirement. The Flutter team offers a plugin for this purpose:

* [`package:webview_flutter`](https://pub.dev/packages/webview_flutter)

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-09-29. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/platform-integration/web/web-content-in-flutter.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/platform-integration/web/web-content-in-flutter\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/platform-integration/web/web-content-in-flutter.md "Report an issue with this page").

----
url: https://docs.flutter.dev/learn/pathway/tutorial/adaptive-layout.md
----

\# LayoutBuilder and adaptive layouts > Learn how to use the LayoutBuilder widget.

<!--?code-excerpt replace="/ *\/\/\s+ignore_for_file:[^\n]+\n//g;"?-->

Learn how to create layouts that adapt to different screen widths. title: What you'll accomplish items: - title: Create responsive layouts with LayoutBuilder icon: fit\_screen - title: Detect screen size to choose different layouts icon: devices - title: Build a sidebar and detail layout for large screens icon: view\_sidebar --- ### Introduction Modern apps need to work well on screens of all sizes. On this page, you'll learn how to create layouts that adapt to different screen widths. This app shows a sidebar on large screens and a navigation-based UI on small screens. Specifically, this app handles two screen sizes: - \*\*Large screens (tablets, desktop)\*\*: Shows contact groups and contact details side-by-side. - \*\*Small screens (phones)\*\*: Uses navigation to move between contact groups and details. ### Create the contact groups page First, create the basic structure of the \`ContactGroupsPage\` widget for your contact groups screen. Create \`lib/screens/contact\_groups.dart\` and add the following basic structure:

<!--?code-excerpt "fwe/rolodex/lib/step2_adaptive_layout/screens/contact_groups.dart"?-->

\`\`\`dart import 'package:cupertino\_ui/cupertino\_ui.dart'; class ContactGroupsPage extends StatelessWidget { const ContactGroupsPage({super.key}); @override Widget build(BuildContext context) { return const CupertinoPageScaffold( backgroundColor: CupertinoColors.extraLightBackgroundGray, child: Center(child: Text('Contact Groups will go here')), ); } } \`\`\` ### Create the contacts page Similarly, create \`lib/screens/contacts.dart\` to eventually display individual contacts:

<!--?code-excerpt "fwe/rolodex/lib/step2_adaptive_layout/screens/contacts.dart"?-->

\`\`\`dart import 'package:cupertino\_ui/cupertino\_ui.dart'; class ContactListsPage extends StatelessWidget { const ContactListsPage({super.key, required this.listId}); final int listId; @override Widget build(BuildContext context) { return const CupertinoPageScaffold( backgroundColor: CupertinoColors.extraLightBackgroundGray, child: Center(child: Text('Lists of contacts will go here')), ); } } \`\`\` The \`ContactListsPage\` widget and \`ContactGroupsPage\` widget are placeholder pages that are needed to implement the adaptive layout widget, which you'll do next. ### Build the adaptive layout foundation Create \`lib/screens/adaptive\_layout.dart\` and start with the following basic structure:

<!--?code-excerpt "fwe/rolodex/lib/step2_adaptive_layout/screens/adaptive_layout_v1.dart"?-->

\`\`\`dart import 'package:cupertino\_ui/cupertino\_ui.dart'; import 'contact\_groups.dart'; class AdaptiveLayout extends StatefulWidget { const AdaptiveLayout({super.key}); @override State createState() => \_AdaptiveLayoutState(); } class \_AdaptiveLayoutState extends State { @override Widget build(BuildContext context) { return const ContactGroupsPage(); // Temporary placeholder } } \`\`\` This is a \`StatefulWidget\` because the adaptive layout eventually manages which contact group is currently selected. Next, add the screen size detection logic to \`lib/screens/adaptive\_layout.dart\`:

<!--?code-excerpt "fwe/rolodex/lib/step2_adaptive_layout/screens/adaptive_layout_v2.dart"?-->

\`\`\`dart import 'package:cupertino\_ui/cupertino\_ui.dart'; import 'contact\_groups.dart'; const largeScreenMinWidth = 600; class AdaptiveLayout extends StatefulWidget { const AdaptiveLayout({super.key}); @override State createState() => \_AdaptiveLayoutState(); } class \_AdaptiveLayoutState extends State { @override Widget build(BuildContext context) { return LayoutBuilder( builder: (context, constraints) { final isLargeScreen = constraints.maxWidth > largeScreenMinWidth; if (isLargeScreen) { return const Text('Large screen layout'); // Temporary } else { return const ContactGroupsPage(); } }, ); } } \`\`\` The \`LayoutBuilder\` widget provides information about the parent's size constraints. In the \`builder\` callback, you receive a\`BoxConstraints\` object that tells you the maximum available width and height. By checking if \`constraints.maxWidth > largeScreenMinWidth\`, you can decide which layout to show. The 600-pixel threshold is a common breakpoint that separates phone-sized screens from tablet-sized screens. ### Update the main app Update \`main.dart\` to use the adaptive layout, so you can see your changes:

<!--?code-excerpt "fwe/rolodex/lib/step2_adaptive_layout/main.dart"?-->

\`\`\`dart import 'package:cupertino\_ui/cupertino\_ui.dart'; import 'data/contact\_group.dart'; import 'screens/adaptive\_layout.dart'; final contactGroupsModel = ContactGroupsModel(); void main() { runApp(const RolodexApp()); } class RolodexApp extends StatelessWidget { const RolodexApp({super.key}); @override Widget build(BuildContext context) { return const CupertinoApp( title: 'Rolodex', theme: CupertinoThemeData( barBackgroundColor: CupertinoDynamicColor.withBrightness( color: Color(0xFFF9F9F9), darkColor: Color(0xFF1D1D1D), ), ), home: AdaptiveLayout(), ); } } \`\`\` If you're running in Chrome, you can resize the browser window to see layout changes. ### Add list selection functionality The large screen layout needs to track which contact group is selected. Update the state object in \`lib/screens/adaptive\_layout.dart\` with the following code:

<!--?code-excerpt "fwe/rolodex/lib/step2_adaptive_layout/screens/adaptive_layout_v3.dart"?-->

\`\`\`dart import 'package:cupertino\_ui/cupertino\_ui.dart'; import 'contact\_groups.dart'; const largeScreenMinWidth = 600; class AdaptiveLayout extends StatefulWidget { const AdaptiveLayout({super.key}); @override State createState() => \_AdaptiveLayoutState(); } class \_AdaptiveLayoutState extends State { int selectedListId = 0; void \_onContactListSelected(int listId) { setState(() { selectedListId = listId; }); } @override Widget build(BuildContext context) { return LayoutBuilder( builder: (context, constraints) { final isLargeScreen = constraints.maxWidth > largeScreenMinWidth; if (isLargeScreen) { return const Text('Large screen layout'); } else { return const ContactGroupsPage(); } }, ); } } \`\`\` The \`selectedListId\` variable tracks the currently selected contact group, and \`\_onContactListSelected\` updates this value when the user makes a choice. ### Build the large screen layout Now, implement the side-by-side layout for large screens in \`lib/screens/adaptive\_layout.dart\`. First, replace the temporary text with a widget that contains the proper layout.

<!--?code-excerpt "fwe/rolodex/lib/step2_adaptive_layout/screens/adaptive_layout_v4.dart"?-->

\`\`\`dart import 'package:cupertino\_ui/cupertino\_ui.dart'; import 'contact\_groups.dart'; const largeScreenMinWidth = 600; class AdaptiveLayout extends StatefulWidget { const AdaptiveLayout({super.key}); @override State createState() => \_AdaptiveLayoutState(); } class \_AdaptiveLayoutState extends State { int selectedListId = 0; void \_onContactListSelected(int listId) { setState(() { selectedListId = listId; }); } @override Widget build(BuildContext context) { return LayoutBuilder( builder: (context, constraints) { final isLargeScreen = constraints.maxWidth > largeScreenMinWidth; if (isLargeScreen) { return \_buildLargeScreenLayout(); } else { // For small screens, use the original, navigation-style approach. return const ContactGroupsPage(); } }, ); } Widget \_buildLargeScreenLayout() { return const CupertinoPageScaffold( backgroundColor: CupertinoColors.extraLightBackgroundGray, child: SafeArea(child: Row(children: \[Text('Sidebar'), Text('Details')])), ); } } \`\`\` The large screen layout uses a \`Row\` to place the sidebar and details side-by-side. \`SafeArea\` ensures that the content doesn't overlap with system UI elements like the status bar. Now, set the sizes of the two panels and add a visual divider in \`lib/screens/adaptive\_layout.dart\`:

<!--?code-excerpt "fwe/rolodex/lib/step2_adaptive_layout/screens/adaptive_layout.dart (panel-and-divider)"?-->

\`\`\`dart Widget \_buildLargeScreenLayout() { return CupertinoPageScaffold( backgroundColor: CupertinoColors.extraLightBackgroundGray, child: SafeArea( child: Row( children: \[ const SizedBox(width: 320, child: Text('Sidebar placeholder')), Container(width: 1, color: CupertinoColors.separator), const Expanded(child: Text('Details placeholder')), ], ), ), ); } \`\`\` This layout creates the following: - A fixed-width sidebar (320 pixels) for contact groups. - A 1-pixel divider between the panels. - A details panel that uses an \`Expanded\` widget to take the remaining space. ### Test the adaptive layout Hot reload your app and test the responsive behavior. If you're running in Chrome, you can resize the browser window to see the layout change: - \*\*Wide window (> 600px)\*\*: Shows placeholder text for the sidebar and details side-by-side. - \*\*Narrow window (< 600px)\*\*: Shows only the contact groups page. Both the sidebar and main content area show placeholder text for now. In the next lesson, you'll implement slivers to fill in the contact list content. ### Review title: What you accomplished subtitle: Here's a summary of what you built and learned in this lesson. completed: true items: - title: Created responsive layouts with LayoutBuilder icon: fit\_screen details: >- \`LayoutBuilder\` provides the parent's size constraints in its builder callback. By checking \`constraints.maxWidth\`, you can decide which layout to show based on available space. - title: Detected screen size to choose different layouts icon: devices details: >- You used a 600-pixel breakpoint to distinguish phone-sized screens from tablet-sized screens. This common threshold helps your app adapt its UI to provide the best experience on each device. - title: Built a sidebar and detail layout for large screens icon: view\_sidebar details: >- On large screens, you displayed a fixed-width sidebar and an \`Expanded\` detail panel side-by-side using a \`Row\`. This classic pattern maximizes screen real estate on tablets and desktops. ### Test yourself - question: What information does LayoutBuilder provide to its builder callback? options: - text: The device's operating system and screen orientation. correct: false explanation: LayoutBuilder provides size constraints, not OS or orientation info. - text: The parent's size constraints, including maximum width and height. correct: true explanation: LayoutBuilder's builder receives BoxConstraints that tell you the available space from the parent. - text: The current theme colors and typography. correct: false explanation: Theme data comes from Theme.of(context), not LayoutBuilder. - text: The number of child widgets in the tree. correct: false explanation: LayoutBuilder provides layout constraints, not widget tree information. - question: In a large screen layout, which widget can be used to place a sidebar and details panel side-by-side? options: - text: Column correct: false explanation: Column arranges widgets vertically, not side-by-side. - text: Row correct: true explanation: Row arranges its children horizontally, making it ideal for placing a sidebar and details panel side-by-side. - text: Stack correct: false explanation: Stack overlaps widgets on top of each other, not side-by-side. - text: ListView correct: false explanation: ListView is for scrollable lists, not for side-by-side layout.

----
url: https://docs.flutter.dev/cookbook/design/cupertino-sheets.md
----

\# Display a Cupertino sheet > How to implement a Cupertino sheet to display messages and content.

<!--?code-excerpt path-base="cookbook/design/cupertino_sheets/"?-->

A Cupertino sheet is an iOS-style modal bottom sheet used to present content or options. It slides up from the bottom of the screen and can be pulled down to dismiss. In Flutter, this is the job of \[\`showCupertinoSheet\`]\[]. This recipe implements a Cupertino sheet using the following steps: 1. Create a \`CupertinoApp\` or \`MaterialApp\`. 2. Display the sheet content. ## 1. Create a \`CupertinoApp\` When creating apps that follow the iOS design guidelines, you can use \`CupertinoApp\`. The following example provides a button in the center of the screen that triggers the modal.

<!--?code-excerpt "lib/main.dart (CupertinoSheetDemo)"?-->

\`\`\`dart class CupertinoSheetDemo extends StatelessWidget { const CupertinoSheetDemo({super.key}); @override Widget build(BuildContext context) { return const CupertinoApp( title: 'CupertinoSheet Demo', home: CupertinoSheetPage(), ); } } \`\`\` ## 2. Display the sheet content With the basic app structure in place, display the sheet. To show it, call \`showCupertinoSheet\` and provide a \`scrollableBuilder\` that returns the content for the sheet, such as a \`SingleChildScrollView\`.

<!--?code-excerpt "lib/main.dart (ShowCupertinoSheet)"?-->

\`\`\`dart showCupertinoSheet( context: context, scrollableBuilder: (context, scrollController) { return SingleChildScrollView( controller: scrollController, child: Center( child: Column( mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.center, children: \[ const SizedBox(height: 100), const Text('This is a Cupertino sheet'), const SizedBox(height: 20), CupertinoButton( child: const Text('Close'), onPressed: () { Navigator.pop(context); }, ), ], ), ), ); }, ); \`\`\` ## Interactive example

<!--?code-excerpt "lib/main.dart"?-->

\`\`\`dartpad title="Flutter CupertinoSheet hands-on example in DartPad" run="true" import 'package:flutter/cupertino.dart'; void main() => runApp(const CupertinoSheetDemo()); class CupertinoSheetDemo extends StatelessWidget { const CupertinoSheetDemo({super.key}); @override Widget build(BuildContext context) { return const CupertinoApp( title: 'CupertinoSheet Demo', home: CupertinoSheetPage(), ); } } class CupertinoSheetPage extends StatelessWidget { const CupertinoSheetPage({super.key}); @override Widget build(BuildContext context) { return CupertinoPageScaffold( navigationBar: const CupertinoNavigationBar( middle: Text('CupertinoSheet Demo'), ), child: Center( child: CupertinoButton.filled( onPressed: () { showCupertinoSheet( context: context, scrollableBuilder: (context, scrollController) { return SingleChildScrollView( controller: scrollController, child: Center( child: Column( mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.center, children: \[ const SizedBox(height: 100), const Text('This is a Cupertino sheet'), const SizedBox(height: 20), CupertinoButton( child: const Text('Close'), onPressed: () { Navigator.pop(context); }, ), ], ), ), ); }, ); }, child: const Text('Show Sheet'), ), ), ); } } \`\`\` \[\`showCupertinoSheet\`]: https\://pub.dev/documentation/cupertino\_ui/latest/cupertino\_ui/showCupertinoSheet.html

----
url: https://docs.flutter.dev/ai/tools.md
----

\# How Flutter AI tools work > Learn how agent skills, the Dart and Flutter MCP server, and package skills interact to assist your development workflow. Flutter and Dart provide an integrated suite of tools designed to help AI coding assistants understand your codebase, follow best practices, and execute development tasks accurately. ## Tooling ecosystem overview The Flutter AI tooling ecosystem consists of six complementary components: 1. \*\*Agent skills\*\*: task-oriented blueprints that teach assistants how to perform specific workflows (such as creating responsive layouts or writing tests). 2. \*\*Dart and Flutter MCP server\*\*: a Model Context Protocol (MCP) server exposing real-time SDK diagnostics, symbol resolution, and runtime introspection to assistants. 3. \*\*Developer Knowledge MCP server\*\*: a cloud-hosted documentation search server giving assistants direct access to official Flutter and Dart docs. 4. \*\*Package skills\*\*: skills published directly inside third-party \`pub.dev\` packages, giving assistants instant expertise on specific libraries. 5. \*\*Specialized agents\*\*: dedicated agent personas equipped with focused tools and system instructions for isolated workflows, such as accessibility auditing. 6. \*\*Agent rules\*\*: persistent rules added to the context window every time you start a new session with your assistant (such as instructing the assistant to proactively trigger hot reload when modifying Flutter widgets). --- ## Agent skills \[Agent skills]\(https\://agentskills.io/) provide a standardized way to equip AI assistants with procedural domain expertise. ### Progressive disclosure Skills use \*\*progressive disclosure\*\* to conserve model context: \* \*\*Discovery phase\*\*: The assistant initially reads only skill metadata (name and short description) from the \`.agents/skills\` directory. \* \*\*Execution phase\*\*: When the assistant determines a skill is relevant to your prompt, it loads the complete instructions (\`SKILL.md\`) and supporting reference scripts. ### Official skills repositories \* \[\*\*flutter/agent-plugins\*\*]\(https\://github.com/flutter/agent-plugins): Provides skills for Flutter development, including widget construction, declarative navigation, responsive design, and state management. \* \[\*\*dart-lang/skills\*\*]\(https\://github.com/dart-lang/skills): Provides skills for Dart development, including unit test authoring, dependency resolution, and static analysis remediation. --- ## Dart and Flutter MCP server The \[Dart and Flutter MCP server]\( https\://github.com/dart-lang/ai/tree/main/pkgs/dart\_mcp\_server) implements the open Model Context Protocol (MCP), connecting AI assistants to live Dart SDK and Flutter tooling over standard I/O (stdio). Key capabilities provided by the MCP server include: \* \*\*Static analysis & diagnostics\*\*: Inspect and fix analyzer errors and warnings across your project. \* \*\*Symbol resolution\*\*: Resolve symbols to their definitions, signatures, and documentation. \* \*\*Runtime introspection\*\*: Query the state of a running Flutter application during active debugging sessions. \* \*\*Package management\*\*: Search \[pub.dev]\(https\://pub.dev) and manage dependencies in \`pubspec.yaml\`. \* \*\*Testing & formatting\*\*: Execute unit and widget tests and format code using \`dart format\` rules. --- ## Developer Knowledge MCP server While the local Dart and Flutter MCP server focuses on local codebase analysis and runtime debugging, you can also equip your assistant with search access to official online documentation. The \[Developer Knowledge MCP server]\( https\://developers.google.com/knowledge/mcp) connects AI assistants to Google's developer documentation corpus, including \[docs.flutter.dev]\(https\://docs.flutter.dev), \[dart.dev]\(https\://dart.dev), and official API references. This allows assistants to retrieve up-to-date guides, migration notes, and API references directly during conversation. --- ## Package skills In addition to core Flutter skills, library authors can bundle official skills directly inside their packages on \[pub.dev]\(https\://pub.dev). When you add a dependency to your project, you can discover and install its skills using the \`skills\` CLI: \`\`\`bash dart run skills@ get \`\`\` To learn how to discover and install package skills, check out \[Package skills]\(https\://dart.dev/ai/package-skills). To bundle skills with your own package, check out \[Ship skills with packages]\(https\://dart.dev/tools/pub/package-skills). --- ## Specialized agents \*\*Specialized agents\*\* (referred to as custom agents in platforms like Antigravity) are AI assistants configured with dedicated system prompts, curated tool sets, and focused skill subsets. While agent skills guide general-purpose assistants during active coding, specialized agents run in isolated contexts to perform deep, single-purpose workflows without cluttering your primary conversation history. ### Flutter Accessibility agent The \*\*Flutter Accessibility (\`a11y\`) agent\*\* is an official specialized agent designed to audit Flutter widget trees and recommend accessibility improvements. The accessibility agent is currently available in \[Google Antigravity]\(https\://antigravity.google). Key capabilities of the accessibility agent include: \* \*\*Semantic label inspection\*\*: identifies interactive widgets (such as \`IconButton\` or \`InkWell\`) that lack descriptive semantic labels. \* \*\*Touch target validation\*\*: flags interactive elements with hit test areas smaller than the recommended 48x48 logical pixels. \* \*\*Contrast and visual checks\*\*: highlights potential contrast ratio issues and missing focus indicators. \* \*\*Automated code fixes\*\*: generates idiomatic Flutter fixes (such as wrapping widgets in \`Semantics\` or adjusting tap bounds) that you can review and accept. ### Using the accessibility agent in Antigravity After installing the official \[Dart and Flutter plugin]\( https\://antigravity.google/docs/build-with-google/#dart-and-flutter) in Antigravity, you can invoke the accessibility agent directly when chatting with Antigravity by selecting the \*\*Flutter Accessibility Agent\*\* from the agent picker, mentioning \`@flutter\_a11y\_agent\`, or asking your assistant to review your code for accessibility: \`\`\`text @flutter\_a11y\_agent Audit this screen for accessibility issues. \`\`\` --- ## Agent rules \*\*Agent rules\*\* are persistent rules added to the context window every time you start a new session with your assistant. Rules establish project conventions and common constraints across your codebase. To explore official Flutter rules, visit the \[rules directory]\(https\://github.com/flutter/agent-plugins/tree/main/rules) in the flutter/agent-plugins repository. --- ## When to use what Use the following mental model to understand how different AI mechanisms interact: | Mechanism | Scope and behavior | Best suited for | | :--- | :--- | :--- | | \*\*Agent skill\*\* | On demand | Multi-step recipes, patterns | | \*\*Dart MCP server\*\* | Local SDK tools | Diagnostics, tests, runtime | | \*\*Developer Knowledge MCP\*\* | Online search | Live docs, API references | | \*\*Package skill\*\* | From dependencies | Library APIs, code generation | | \*\*Specialized agent\*\* | Isolated subagent | Accessibility audits | | \*\*Agent rule\*\* | Persistent context | Project conventions, constraints | {:.table .table-striped} ## Next steps \* Follow the \[Get started with AI]\(/ai/get-started) guide to install official plugins in your editor. \* Learn how to install skills with \[Package skills]\(https\://dart.dev/ai/package-skills). \* Learn how to distribute skills with \[Ship skills with packages]\(https\://dart.dev/tools/pub/package-skills).

----
url: https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.10.0
----

The 2.10.0 release of the Dart and Flutter DevTools includes the following changes among other general improvements. To learn more about DevTools, check out the [DevTools overview](https://docs.flutter.dev/tools/devtools).

## Flutter inspector updates

[#](#flutter-inspector-updates)

* Added search support to the Widget Tree, and added a breadcrumb navigator to the Widget Details Tree to allow for quickly navigating through the tree hierarchy - [#3525](https://github.com/flutter/devtools/pull/3525)

## CPU profiler updates

[#](#cpu-profiler-updates)

* Fix a null reference in the CPU profiler when loading an offline snapshot - [#3596](https://github.com/flutter/devtools/pull/3596)

## Debugger updates

[#](#debugger-updates)

* Added support for multi-token file search, and improved search match prioritization to rank file name matches over full path matches - [#3582](https://github.com/flutter/devtools/pull/3582)
* Fix some focus-related issues - [#3602](https://github.com/flutter/devtools/pull/3602)

## Logging view updates

[#](#logging-view-updates)

* Fix a fatal error that occurred when filtering logs more than once - [#3588](https://github.com/flutter/devtools/pull/3588)

## Full commit history

[#](#full-commit-history)

To find a complete list of changes since the previous release, check out [the diff on GitHub](https://github.com/flutter/devtools/compare/v2.9.2...v2.10.0).

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/release-notes/release-notes-2.10.0.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.10.0\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/release-notes/release-notes-2.10.0.md "Report an issue with this page").

----
url: https://docs.flutter.dev/learn/pathway/quick-install.md
----

\# Quick install > This pages walks you through the quick install process for Flutter. Learn how to use any Code OSS-based editor, such as VS Code, to set up your Flutter development environment and test drive Flutter's developer experience. If you've developed with Flutter before, or you prefer to use a different editor or IDE, you can follow the \[custom setup instructions]\[] instead. :::note What you'll achieve - Install the software prerequisites for Flutter. - Use VS Code to download and install Flutter. - Create a new Flutter app from a sample template. - Try out Flutter development features like stateful hot reload. ::: \[custom setup instructions]: /install/custom ## Confirm your development platform {: #dev-platform} The instructions on this page are configured to cover installing and trying out Flutter on a \*\*Windows\*\*{:.selected-os-text} device. If you'd like to follow the instructions for a different OS, please select one of the following. ## Download prerequisite software {: #download-prerequisites} For the smoothest Flutter setup, first install the following tools. 1.

If you encounter any issues during installation, check out \[Flutter installation troubleshooting]\[troubleshoot]. {:.steps} \[Install Flutter manually]: /install/manual \[flutter-vscode]: https\://marketplace.visualstudio.com/items?itemName=Dart-Code.flutter \[troubleshoot]: /install/troubleshoot ## Start learning! Installing the Flutter SDK automatically installs Dart as well. You can now start building Flutter apps!

If you're new to programming, start with the Dart Getting Started tutorial to learn the fundamentals of the Dart programming language. This tutorial was designed to lead into the Flutter Getting Started tutorial.

[Start learning](https://dart.dev/learn/tutorial) If you're already familiar with programming, you can skip the Dart tutorial and jump straight into the first Flutter lesson.

----
url: https://docs.flutter.dev/data-and-backend/persistence/choose-a-database.md
----

\# Choose a database for your Flutter app > Compare Cloud Firestore, Firebase SQL Connect, SQLite, and key-value storage to pick the right database for your Flutter app. Flutter doesn't include a built-in database, so you can choose one based on the shape of your data and where it needs to live. For most Flutter apps, start with one of these options: \* \*\*Cloud Firestore\*\* for scalable NoSQL data that syncs across devices in realtime. \* \*\*Firebase SQL Connect\*\* for relational data that needs a structured SQL schema. \* \*\*On-device SQLite\*\* or \*\*key-value storage\*\* for local data that doesn't need a cloud backend by default. ## Compare database options Here's a quick look at how each option stores and syncs data: | Option | Data model | Data location | Realtime updates | Offline access | |---|---|---|---|---| | \[Cloud Firestore]\[] | NoSQL documents and collections | Cloud, with a local cache | Yes | Built in (reads and writes) | | \[Firebase SQL Connect]\[] | Relational (PostgreSQL) | Cloud | Yes | Optional query cache | | \[On-device SQLite]\[sqlite-recipe] | Relational | Device by default | Local streams (with \`drift\`) | Always local | | \[Key-value storage]\[key-value-recipe] | Key-value pairs | Device only | No | Always local | {:.table .table-striped} ## Choose Cloud Firestore for NoSQL data \[Cloud Firestore]\[] is the recommended default when your app needs a scalable cloud database that keeps data in sync across users and devices—such as in chat apps, collaborative tools, live multiplayer games, or offline-first field service and inspection apps. It stores data as documents organized into collections, and the \[\`cloud\_firestore\`]\[] package lets your Flutter app read and write data directly from client code, secured with \[Cloud Firestore Security Rules]\[firestore-rules]. Cloud Firestore is a good fit when your app needs: \* \*\*Realtime sync:\*\* listen to a document or query and rebuild your UI whenever the data changes, for example, with a \`StreamBuilder\` widget. \* \*\*Offline support:\*\* read and write cached data while the device is offline, and automatically sync changes when the connection returns, without building a custom offline queue. Offline persistence is enabled by default on Android and Apple platforms, and can be enabled on the web. \* \*\*Flexible, evolving data:\*\* store nested or semi-structured data without defining a schema up front. \* \*\*Automatic scaling:\*\* handle growing traffic without provisioning or managing servers. To add Cloud Firestore to your project, run: \`\`\`console $ flutter pub add cloud\_firestore \`\`\` Then follow the \[Cloud Firestore quickstart]\[firestore-quickstart] to set up your database. :::note Cloud Firestore is Firebase's recommended NoSQL database for new apps instead of \[Firebase Realtime Database]\[realtime-db], unless your app only needs low-latency synchronization for small, frequent state updates like presence indicators. ::: ## Choose Firebase SQL Connect for SQL data For structured, relational data—such as in an ecommerce storefront, appointment booking system, personal finance tracker, or inventory system—\[Firebase SQL Connect]\[] (formerly Firebase Data Connect) is the recommended default. SQL Connect is backed by a fully managed PostgreSQL database on Cloud SQL. You define your schema, queries, and mutations in GraphQL, deploy them to the server, and call them from your Flutter app using a generated, type-safe Dart SDK powered by the \[\`firebase\_data\_connect\`]\[] package. Key capabilities include: \* \*\*Relational data:\*\* model entities with relationships, such as users, orders, and products, and query across them. \* \*\*A strict schema:\*\* enforce data types, relational constraints, and foreign keys in the database. \* \*\*Type-safe generated SDKs:\*\* call generated Dart methods for each predefined server query and mutation instead of writing raw queries or duplicating data models by hand. Because SQL Connect generates client SDKs directly from your backend schema, your Flutter app stays in sync with your database without cross-language schema drift. \* \*\*Realtime updates:\*\* subscribe to queries for automatic or configured refresh updates when data changes. \* \*\*Optional query caching:\*\* cache query responses on the client to reduce network requests and read cached results while offline. \* \*\*Vector search:\*\* build AI-powered features, such as semantic search, on top of your relational data. To add the SQL Connect plugin to your project, run: \`\`\`console $ flutter pub add firebase\_data\_connect \`\`\` Next, follow the guide to \[use generated Flutter SDKs]\[sql-connect-flutter] with SQL Connect. ## Use SQLite or key-value storage locally If your data doesn't need to leave the device, you can store it locally without a cloud backend: \* \*\*SQLite:\*\* store and query larger amounts of structured relational data on the device. Use \[\`sqflite\`]\[] for direct SQL queries, or \[\`drift\`]\[] for type-safe Dart queries, reactive data streams, and schema migrations across mobile, desktop, and web. For a step-by-step example, check out \[Persist data with SQLite]\[sqlite-recipe], or read \[Persistent storage architecture: SQL]\[sql-architecture] to learn how to structure a local database layer in a larger app. \* \*\*Key-value storage:\*\* save small amounts of simple data, such as user preferences and settings, with the \[\`shared\_preferences\`]\[] package. Get started with the \[Store key-value data on disk]\[key-value-recipe] recipe, or check out \[Persistent storage architecture: Key-value data]\[key-value-architecture]. To store sensitive data like authentication tokens or encrypted records, use \[\`flutter\_secure\_storage\`]\[] instead. By default, on-device SQLite and key-value packages store data only on the local device, so data isn't synced across devices or preserved if a user uninstalls your app without a backup or sync service. If users expect automatic access to their data across devices or after switching phones, use a cloud database like Cloud Firestore or Firebase SQL Connect instead. ## Combine cloud and local storage You don't have to pick just one option. For example, your app might use Cloud Firestore or Firebase SQL Connect for shared, synced data, alongside \`shared\_preferences\` for device-specific settings like a dark mode toggle. To learn how to combine local and remote data sources so your app keeps working without a network connection, check out \[Offline-first support]\[offline-first]. Both Cloud Firestore and Firebase SQL Connect also work alongside other Firebase services, including \[Firebase Authentication]\[firebase-auth] (\[\`firebase\_auth\`]\[]), \[Cloud Storage for Firebase]\[firebase-storage] (\[\`firebase\_storage\`]\[]), and \[Firebase AI Logic]\[firebase-ai] (\[\`firebase\_ai\`]\[]). For example, your app can capture receipt or inspection photos, upload them to Cloud Storage, extract structured fields with Firebase AI Logic, and sync the resulting records in Cloud Firestore or SQL Connect—all within a single project. If your app needs custom backend logic, you can also write \[Cloud Functions for Firebase or backend servers in Dart]\[dart-server] and share a single Dart package for your data models, serialization, and validation logic across both your Flutter client and backend. ## Next steps To start building with a cloud database, check out the following resources: \* \[Add Firebase to your Flutter app]\[firebase-setup] \* \[Firebase for Flutter]\[firebase-page] \* \[Get started with Cloud Firestore]\[firestore-quickstart] \* \[Firebase SQL Connect for Flutter]\[sql-connect-flutter] \[Cloud Firestore]: https\://firebase.google.com/docs/firestore \[\`cloud\_firestore\`]: https\://pub.dev/packages/cloud\_firestore \[dart-server]: https\://dart.dev/server#sharing-code-between-flutter-apps-and-dart-backends \[\`drift\`]: https\://pub.dev/packages/drift \[firebase-ai]: https\://firebase.google.com/docs/ai-logic/get-started \[\`firebase\_ai\`]: https\://pub.dev/packages/firebase\_ai \[firebase-auth]: https\://firebase.google.com/docs/auth/flutter/start \[\`firebase\_auth\`]: https\://pub.dev/packages/firebase\_auth \[\`firebase\_data\_connect\`]: https\://pub.dev/packages/firebase\_data\_connect \[firebase-page]: /data-and-backend/firebase \[firebase-setup]: https\://firebase.google.com/docs/flutter/setup \[Firebase SQL Connect]: https\://firebase.google.com/docs/sql-connect \[firebase-storage]: https\://firebase.google.com/docs/storage/flutter/start \[\`firebase\_storage\`]: https\://pub.dev/packages/firebase\_storage \[firestore-quickstart]: https\://firebase.google.com/docs/firestore/quickstart \[firestore-rules]: https\://firebase.google.com/docs/firestore/security/get-started \[\`flutter\_secure\_storage\`]: https\://pub.dev/packages/flutter\_secure\_storage \[key-value-architecture]: /app-architecture/design-patterns/key-value-data \[key-value-recipe]: /cookbook/persistence/key-value \[offline-first]: /app-architecture/design-patterns/offline-first \[realtime-db]: https\://firebase.google.com/docs/database/flutter/start \[\`shared\_preferences\`]: https\://pub.dev/packages/shared\_preferences \[\`sqflite\`]: https\://pub.dev/packages/sqflite \[sql-architecture]: /app-architecture/design-patterns/sql \[sql-connect-flutter]: https\://firebase.google.com/docs/sql-connect/flutter-sdk \[sqlite-recipe]: /cookbook/persistence/sqlite

----
url: https://docs.flutter.dev/platform-integration/ios/platform-views
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Host native iOS views in your Flutter app with platform views](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

Platform views allow you to embed native views in a Flutter app, so you can apply transforms, clips, and opacity to the native view from Dart.

This allows you, for example, to use the native Google Maps from the Android and iOS SDKs directly inside your Flutter app.

iOS only uses Hybrid composition, which means that the native `UIView` is appended to the view hierarchy.

To create a platform view on iOS, use the following instructions:

## On the Dart side

[#](#on-the-dart-side)

On the Dart side, create a `Widget` and add the build implementation, as shown in the following steps.

In the Dart widget file, make changes similar to those shown in `native_view_example.dart`:

1. Add the following imports:

   dart

   ```
   import 'package:flutter/foundation.dart';
   import 'package:flutter/services.dart';
   ```

2. Implement a `build()` method:

   dart

   ```
   Widget build(BuildContext context) {
     // This is used in the platform side to register the view.
     const String viewType = '<platform-view-type>';
     // Pass parameters to the platform side.
     final Map<String, dynamic> creationParams = <String, dynamic>{};
   ​
     return UiKitView(
       viewType: viewType,
       layoutDirection: TextDirection.ltr,
       creationParams: creationParams,
       creationParamsCodec: const StandardMessageCodec(),
     );
   }
   ```

For more information, see the API docs for: [`UIKitView`](https://api.flutter.dev/flutter/widgets/UiKitView-class.html).

## On the platform side

[#](#on-the-platform-side)

On the platform side, use either Swift or Objective-C:

* [Swift](#215-tab-panel)
* [Objective-C](#218-tab-panel)

Implement the factory and the platform view. The `FLNativeViewFactory` creates the platform view, and the platform view provides a reference to the `UIView`.

For example, implement the factory class:

swift

```
import Flutter
import UIKit
​
class FLNativeViewFactory: NSObject, FlutterPlatformViewFactory {
    private var messenger: FlutterBinaryMessenger
​
    init(messenger: FlutterBinaryMessenger) {
        self.messenger = messenger
        super.init()
    }
​
    func create(
        withFrame frame: CGRect,
        viewIdentifier viewId: Int64,
        arguments args: Any?
    ) -> FlutterPlatformView {
        return FLNativeView(
            frame: frame,
            viewIdentifier: viewId,
            arguments: args,
            binaryMessenger: messenger)
    }
​
    /// Implementing this method is only necessary when the `arguments` in `createWithFrame` is not `nil`.
    public func createArgsCodec() -> FlutterMessageCodec & NSObjectProtocol {
          return FlutterStandardMessageCodec.sharedInstance()
    }
}
```

Depending on the UI framework you are using, choose one of the following implementations for `FLNativeView`:

* [UIKit](#216-tab-panel)
* [SwiftUI](#217-tab-panel)

For UIKit, implement the `FLNativeView` class:

swift

```
class FLNativeView: NSObject, FlutterPlatformView {
    private var _view: UIView
​
    init(
        frame: CGRect,
        viewIdentifier viewId: Int64,
        arguments args: Any?,
        binaryMessenger messenger: FlutterBinaryMessenger?
    ) {
        _view = UIView()
        super.init()
        // iOS views can be created here
        createNativeView(view: _view)
    }
​
    func view() -> UIView {
        return _view
    }
​
    func createNativeView(view _view: UIView){
        _view.backgroundColor = UIColor.blue
        let nativeLabel = UILabel()
        nativeLabel.text = "Native text from iOS"
        nativeLabel.textColor = UIColor.white
        nativeLabel.textAlignment = .center
        nativeLabel.frame = CGRect(x: 0, y: 0, width: 180, height: 48.0)
        _view.addSubview(nativeLabel)
    }
}
```

To display SwiftUI views within a platform view on iOS, wrap the SwiftUI view inside a `UIHostingController`. Since `UIHostingController` is a view controller, you can retrieve its view using the `view` property, and return it from the platform view's `view()` method.

To prevent premature deallocation, store the `UIHostingController` instance as a property of `FLNativeView`. Use the `frame` passed to the initializer to set the frame of the hosting controller's view so Flutter sizes it correctly.

For SwiftUI, implement the `FLNativeView` class and its SwiftUI view:

swift

```
import SwiftUI
​
class FLNativeView: NSObject, FlutterPlatformView {
    private let hostingController: UIHostingController<MySwiftUIView>
​
    init(
        frame: CGRect,
        viewIdentifier viewId: Int64,
        arguments args: Any?,
        binaryMessenger messenger: FlutterBinaryMessenger?
    ) {
        hostingController = UIHostingController(rootView: MySwiftUIView())
        super.init()
        hostingController.view.frame = frame
    }
​
    func view() -> UIView {
        return hostingController.view
    }
}
​
struct MySwiftUIView: View {
    var body: some View {
        Text("Native text from iOS (SwiftUI)")
            .frame(maxWidth: .infinity, maxHeight: .infinity)
            .background(Color.blue)
            .foregroundColor(.white)
    }
}
```

Finally, register the platform view. This can be done in an app or a plugin.

For app registration, modify the App's `AppDelegate.swift`:

swift

```
import Flutter
import UIKit
​
@main
@objc class AppDelegate: FlutterAppDelegate, FlutterImplicitEngineDelegate {
​
    func didInitializeImplicitFlutterEngine(_ engineBridge: FlutterImplicitEngineBridge) {
        GeneratedPluginRegistrant.register(with: engineBridge.pluginRegistry)
​
        guard let pluginRegistrar = engineBridge.pluginRegistry.registrar(forPlugin: "plugin-name") else { return }
​
        let factory = FLNativeViewFactory(messenger: pluginRegistrar.messenger())
        pluginRegistrar.register(
            factory,
            withId: "<platform-view-type>")
    }
}
```

For plugin registration, modify the plugin's main file (for example, `FLPlugin.swift`):

swift

```
import Flutter
import UIKit
​
class FLPlugin: NSObject, FlutterPlugin {
    public static func register(with registrar: FlutterPluginRegistrar) {
        let factory = FLNativeViewFactory(messenger: registrar.messenger())
        registrar.register(factory, withId: "<platform-view-type>")
    }
}
```

In Objective-C, add the headers for the factory and the platform view. For example, as shown in `FLNativeView.h`:

objc

```
#import <Flutter/Flutter.h>
​
@interface FLNativeViewFactory : NSObject <FlutterPlatformViewFactory>
- (instancetype)initWithMessenger:(NSObject<FlutterBinaryMessenger>*)messenger;
@end
​
@interface FLNativeView : NSObject <FlutterPlatformView>
​
- (instancetype)initWithFrame:(CGRect)frame
               viewIdentifier:(int64_t)viewId
                    arguments:(id _Nullable)args
              binaryMessenger:(NSObject<FlutterBinaryMessenger>*)messenger;
​
- (UIView*)view;
@end
```

Implement the factory and the platform view. The `FLNativeViewFactory` creates the platform view, and the platform view provides a reference to the `UIView`. For example, `FLNativeView.m`:

objc

```
#import "FLNativeView.h"
​
@implementation FLNativeViewFactory {
  NSObject<FlutterBinaryMessenger>* _messenger;
}
​
- (instancetype)initWithMessenger:(NSObject<FlutterBinaryMessenger>*)messenger {
  self = [super init];
  if (self) {
    _messenger = messenger;
  }
  return self;
}
​
- (NSObject<FlutterPlatformView>*)createWithFrame:(CGRect)frame
                                   viewIdentifier:(int64_t)viewId
                                        arguments:(id _Nullable)args {
  return [[FLNativeView alloc] initWithFrame:frame
                              viewIdentifier:viewId
                                   arguments:args
                             binaryMessenger:_messenger];
}
​
/// Implementing this method is only necessary when the `arguments` in `createWithFrame` is not `nil`.
- (NSObject<FlutterMessageCodec>*)createArgsCodec {
    return [FlutterStandardMessageCodec sharedInstance];
}
​
@end
​
@implementation FLNativeView {
   UIView *_view;
}
​
- (instancetype)initWithFrame:(CGRect)frame
               viewIdentifier:(int64_t)viewId
                    arguments:(id _Nullable)args
              binaryMessenger:(NSObject<FlutterBinaryMessenger>*)messenger {
  if (self = [super init]) {
    _view = [[UIView alloc] init];
  }
  return self;
}
​
- (UIView*)view {
  return _view;
}
​
@end
```

Finally, register the platform view. This can be done in an app or a plugin.

For app registration, modify the App's `AppDelegate.m`:

objc

```
#import "AppDelegate.h"
#import "FLNativeView.h"
#import "GeneratedPluginRegistrant.h"
​
@implementation AppDelegate
​
- (void)didInitializeImplicitFlutterEngine:(NSObject<FlutterImplicitEngineBridge>*)engineBridge {
  [GeneratedPluginRegistrant registerWithRegistry:engineBridge.pluginRegistry];
​
  NSObject<FlutterPluginRegistrar>* registrar =
      [engineBridge.pluginRegistry registrarForPlugin:@"plugin-name"];
​
  FLNativeViewFactory* factory =
      [[FLNativeViewFactory alloc] initWithMessenger:registrar.messenger];
​
  [registrar registerViewFactory:factory withId:@"<platform-view-type>"];
}
​
@end
```

For plugin registration, modify the main plugin file (for example, `FLPlugin.m`):

objc

```
#import <Flutter/Flutter.h>
#import "FLNativeView.h"
​
@interface FLPlugin : NSObject<FlutterPlugin>
@end
​
@implementation FLPlugin
​
+ (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar>*)registrar {
  FLNativeViewFactory* factory =
      [[FLNativeViewFactory alloc] initWithMessenger:registrar.messenger];
  [registrar registerViewFactory:factory withId:@"<platform-view-type>"];
}
​
@end
```

For more information, see the API docs for:

```
Widget build(BuildContext context) {
  // This is used in the platform side to register the view.
  const String viewType = '<platform-view-type>';
  // Pass parameters to the platform side.
  final Map<String, dynamic> creationParams = <String, dynamic>{};
​
  switch (defaultTargetPlatform) {
    case TargetPlatform.android:
    // return widget on Android.
    case TargetPlatform.iOS:
    // return widget on iOS.
    case TargetPlatform.macOS:
    // return widget on macOS.
    default:
      throw UnsupportedError('Unsupported platform view');
  }
}
```

## Performance

[#](#performance)

Platform views in Flutter come with performance trade-offs.

For complex cases, there are some techniques that can be used to mitigate performance issues.

For example, you could use a placeholder texture while an animation is happening in Dart. In other words, if an animation is slow while a platform view is rendered, then consider taking a screenshot of the native view and rendering it as a texture.

## Composition limitations

[#](#composition-limitations)

There are some limitations when composing iOS Platform Views.

* The [`ShaderMask`](https://api.flutter.dev/flutter/foundation/ShaderMask.html) and [`ColorFiltered`](https://api.flutter.dev/flutter/foundation/ColorFiltered.html) widgets are not supported.
* The [`BackdropFilter`](https://api.flutter.dev/flutter/foundation/BackdropFilter.html) widget is supported, but there are some limitations on how it can be used. For more details, check out the [iOS Platform View Backdrop Filter Blur design doc](https://flutter.dev/go/ios-platformview-backdrop-filter-blur).

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-08-13. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/platform-integration/ios/platform-views.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/platform-integration/ios/platform-views\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/platform-integration/ios/platform-views.md "Report an issue with this page").

----
url: https://docs.flutter.dev/add-to-app/macos/project-setup
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Integrate a Flutter app into your macOS project](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

Flutter UI components can be incrementally added into your existing macOS application using Swift packages.

## Prerequisites

[#](#prerequisites)

* Flutter 3.44 or later
* Xcode 15.0 or later

### Migrate from legacy integration (if applicable)

[#](#migrate-legacy-integration)

If you've already integrated Flutter into your macOS app using embedded frameworks, you must first remove that integration before following the Swift Package Manager instructions below.

Expand to see instructions to migrate from embedded frameworks integration

If your app was previously integrated using frameworks generated by the `flutter build macos-framework` command, you must first remove the frameworks from your Xcode project.

1. Navigate to your target's **General** tab and remove all Flutter-related frameworks and libraries under **Frameworks, Libraries, and Embedded Content**.

   This includes the `App.xcframework`, `FlutterMacOS.xcframework`, `FlutterPluginRegistrant.xcframework`, and any Flutter plugins' `xcframework` files.

2. Remove the Flutter pod from your Podfile

   MyApp/Podfile

   ruby

   ```
   pod 'FlutterMacOS', :podspec => '/path/to/MyApp/Flutter/[build mode]/FlutterMacOS.podspec'
   ```

3. Run `pod install`.

### Organize your projects relative to each other

[#](#organize-projects-relatively)

This guide assumes that your existing macOS app and your Flutter app reside in sibling directories. If you have a different directory structure, you will need to adjust the example relative paths accordingly.

The example directory structure resembles the following:

* my\_flutter\_app/

  * macos/

  * lib/

    * main.dart

* MyNativeApp/

  * MyNativeApp.xcodeproj/

## Integrate with Swift Package Manager

[#](#integrate-with-swiftpm)

1. ### Build the FlutterNativeIntegration Swift package

   Within your Flutter application or module, run the following command:

   ```
   flutter build swift-package --platform macos
   ```

   This generates the following directories:

   * my\_flutter\_app/build/macos/SwiftPackages/

     * FlutterNativeIntegration/(A Swift package)
     * Scripts/(Directory of scripts and other files needed)

   You can optionally change the location of this output with the `--output` flag.

2. ### Add FlutterNativeIntegration to your Xcode project

   1. In the Project navigator, right click on your project and select **Add Files to "MyNativeApp"...**

   2. Navigate to and select the generated `FlutterNativeIntegration` Swift package and click **Add**.

   3. Select **Reference files in place** and click **Finish**.

   4. In the File inspector, verify the **Location** is **Relative to Project**. If it is not, you'll need to move the Flutter output directory to be a sibling directory of your native app.

      Relative location of FlutterNativeIntegration shown in Xcode's File inspector.

   5. Navigate to your target's **General** tab and add `FlutterNativeIntegration` under **Frameworks, Libraries, and Embedded Content**.

      FlutterNativeIntegration under Frameworks, Libraries, and Embedded Content.

3. ### Add build settings

   1. In the **Build Settings** tab, set the location of the Flutter app's Swift package output directory:

      ```
      FLUTTER_SWIFT_PACKAGE_OUTPUT=$SRCROOT/../my_flutter_app/build/macos/SwiftPackages
      ```

   2. For custom configurations, set the Flutter build mode.

      Flutter supports three [build modes](/testing/build-modes): Debug, Profile, and Release. The build mode is determined using the `CONFIGURATION` value. If your configuration does not match one of these, you can set the `FLUTTER_BUILD_MODE` build setting to one of these values.

      Setting `FLUTTER_BUILD_MODE` for custom configurations under **Build Settings**.

   3. For **Debug** configurations only, set the following build settings:

      ```
      ENABLE_APP_SANDBOX=YES
      ENABLE_INCOMING_NETWORK_CONNECTIONS=YES
      RUNTIME_EXCEPTION_ALLOW_JIT=YES
      ```

      Set **Allow JIT** (RUNTIME\_EXCEPTION\_ALLOW\_JIT) to **YES** in the target's **Build Settings** for **Debug** configurations only.

   4. (Optional) Allow Xcode to re-build your Flutter app.

      Add the following build settings to your target to allow Xcode to re-build your Flutter app as part of its build. This allows you to make changes to your Flutter application without needing to re-run `flutter build swift-package`. This requires a Flutter installation on the machine.

      ```
      FLUTTER_APPLICATION_PATH=$SRCROOT/../my_flutter_app
      ENABLE_USER_SCRIPT_SANDBOXING=NO
      ```

4. ### Add Pre-action Run Script to Scheme

   1. Open **Product** > **Scheme** > **Edit Scheme...** > **Build** (in left side bar) > **Pre-action** > **+** > **New Run Script Action**

   2. Select your project in the **Provide build settings from** dropdown.

   3. Set the script to the following:

      ```
      /bin/sh $FLUTTER_SWIFT_PACKAGE_OUTPUT/Scripts/flutter_integration.sh prebuild
      ```

   Pre-action Run Script in scheme editor.

5. ### Add new run script build phase to your target

   1. Navigate to your target's **Build Phases** > **+** > **New Run Script Phase**

   2. Set the script to the following:

      ```
      /bin/sh $FLUTTER_SWIFT_PACKAGE_OUTPUT/Scripts/flutter_integration.sh assemble
      ```

   3. Uncheck **Based on dependency analysis**

   4. Add the following to **Input File Lists**:

      ```
      $(FLUTTER_SWIFT_PACKAGE_OUTPUT)/Scripts/FlutterAssembleInputs.xcfilelist
      ```

   New Run Script Build Phase under Build Phases.

## Next steps

[#](#next-steps)

You can now [add a Flutter screen](/add-to-app/macos/add-flutter-screen) to your existing macOS app.

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-07-23. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/add-to-app/macos/project-setup.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/add-to-app/macos/project-setup\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/add-to-app/macos/project-setup.md "Report an issue with this page").

----
url: https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.52.0
----

The 2.52.0 release of the Dart and Flutter DevTools includes the following changes among other general improvements. To learn more about DevTools, check out the [DevTools overview](/tools/devtools/overview).

## General updates

[#](#general-updates)

* Added a horizontal scrollbar to data tables to help with navigation. - [#9482](https://github.com/flutter/devtools/pull/9482)
* Made it possible to resize data table columns by dragging the header separators. - [#9485](https://github.com/flutter/devtools/pull/9485)

To find a complete list of changes in this release, check out the [DevTools git log](https://github.com/flutter/devtools/tree/v2.52.0).

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/release-notes/release-notes-2.52.0.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.52.0\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/release-notes/release-notes-2.52.0.md "Report an issue with this page").

----
url: https://docs.flutter.dev/ui/widgets.md
----

\# Widget catalog > A catalog of some of Flutter's rich set of widgets. Create beautiful apps faster with Flutter's collection of visual, structural, platform, and interactive widgets. In addition to browsing widgets by category, you can also see all the widgets in the \[widget index]\[]. ## Design systems The style-neutral \[\`widgets\`]\[] library ships with the Flutter SDK. Two UI design packages (\[\`material\_ui\`]\[] and \[\`cupertino\_ui\`]\[]) are built on top of \`widgets\` and are available on \[pub.dev]\(https\://pub.dev), the package repository for Dart and Flutter. \[\`cupertino\_ui\`]: https\://pub.dev/packages/cupertino\_ui \[\`material\_ui\`]: https\://pub.dev/packages/material\_ui \[\`widgets\`]: https\://api.flutter.dev/flutter/widgets/

Beautiful and high-fidelity widgets that align with Apple's Human Interface Guidelines for iOS and macOS. Visual, behavioral, and motion-rich widgets implementing the Material 3 design specification.

You can find more design systems created by the Flutter community on pub.dev. For example, the Windows-inspired \[fluent\_ui]\[], macOS-inspired \[macos\_ui]\[], and the Ubuntu-inspired \[yaru]\[] widgets. \[fluent\_ui]: https\://pub.dev/packages/fluent\_ui \[macos\_ui]: https\://pub.dev/packages/macos\_ui \[yaru]: https\://pub.dev/packages/yaru ## Base widgets Base widgets support a range of common rendering options like input, layout, and text. ## Widget of the Week 100+ short, 1-minute explainer videos to help you quickly get started with Flutter widgets.[Watch more widget of the week videos](https://www.youtube.com/playlist?list=PLjxrf2q8roU23XGwz3Km7sQZFTdB996iG) \[widget index]: /reference/widgets

----
url: https://docs.flutter.dev/resources/architectural-overview.md
----

\# Flutter architectural overview > A high-level overview of the architecture of Flutter, including the core principles and concepts that form its design.

<!--?code-excerpt path-base="resources/architectural_overview/"?-->

This article is intended to provide a high-level overview of the architecture of Flutter, including the core principles and concepts that form its design. If you are interested in how to architect a Flutter app, check out \[Architecting Flutter apps]\[]. \[Architecting Flutter apps]: /app-architecture Flutter is a cross-platform UI toolkit that is designed to allow code reuse across operating systems such as iOS, Android, web, and desktop, while also allowing applications to interface directly with underlying platform services. The goal is to enable developers to deliver high-performance apps that feel natural on different platforms, embracing differences where they exist while sharing as much code as possible. During development, Flutter apps run in a VM that offers stateful hot reload of changes without needing a full recompile. For release, Flutter apps are compiled directly to machine code, whether Intel x64 or ARM instructions, or to JavaScript if targeting the web. The framework is open source, with a permissive BSD license, and has a thriving ecosystem of third-party packages that supplement the core library functionality. This overview is divided into a number of sections: 1. The \*\*layer model\*\*: The pieces from which Flutter is constructed. 1. \*\*Reactive user interfaces\*\*: A core concept for Flutter user interface development. 1. An introduction to \*\*widgets\*\*: The fundamental building blocks of Flutter user interfaces. 1. The \*\*rendering process\*\*: How Flutter turns UI code into pixels. 1. An overview of the \*\*platform embedders\*\*: The code that lets mobile and desktop OSes execute Flutter apps. 1. \*\*Integrating Flutter with other code\*\*: Information about different techniques available to Flutter apps. 1. \*\*Support for the web\*\*: Concluding remarks about the characteristics of Flutter in a browser environment. ## Architectural layers Flutter is designed as an extensible, layered system. It exists as a series of independent libraries that each depend on the underlying layer. No layer has privileged access to the layer below, and every part of the framework level is designed to be optional and replaceable. !\[Architectural diagram]\(/assets/images/docs/arch-overview/archdiagram.webp){:width="100%"} To the underlying operating system, Flutter applications are packaged in the same way as any other native application. A platform-specific embedder provides an entrypoint; coordinates with the underlying operating system for access to services like rendering surfaces, accessibility, and input; and manages the message event loop. The embedder is written in a language that is appropriate for the platform: currently Java and C++ for Android, Swift and Objective-C/Objective-C++ for iOS and macOS, and C++ for Windows and Linux. Using the embedder, Flutter code can be integrated into an existing application as a module, or the code might be the entire content of the application. Flutter includes a number of embedders for common target platforms, but other embedders also exist. At the core of Flutter is the \*\*Flutter engine\*\*, which is mostly written in C++ and supports the primitives necessary to support all Flutter applications. The engine is responsible for rasterizing composited scenes whenever a new frame needs to be painted. It provides the low-level implementation of Flutter's core API, including graphics text layout, file and network I/O, a Dart runtime, and compile toolchain. :::note If you have a question about which devices support Impeller, check out \[Impeller availability]\[] for detailed information. ::: \[Impeller availability]: https\://flutter.dev/go/can-i-use-impeller \[Skia]: https\://skia.org \[Impeller]: /perf/impeller The engine is exposed to the Flutter framework through \[\`dart:ui\`]\(https\://github.com/flutter/flutter/tree/main/engine/src/flutter/lib/ui), which wraps the underlying C++ code in Dart classes. This library exposes the lowest-level primitives, such as classes for driving input, graphics, and text rendering subsystems. Typically, developers interact with Flutter through the \*\*Flutter framework\*\*, which provides a modern, reactive framework written in the Dart language. It includes a rich set of platform, layout, and foundational libraries, composed of a series of layers. Working from the bottom to the top, we have: \* Basic \*\*\[foundational]\(https\://api.flutter.dev/flutter/foundation/foundation-library.html)\*\* classes, and building block services such as \*\*\[animation]\(https\://api.flutter.dev/flutter/animation/animation-library.html), \[painting]\(https\://api.flutter.dev/flutter/painting/painting-library.html), and \[gestures]\(https\://api.flutter.dev/flutter/gestures/gestures-library.html)\*\* that offer commonly used abstractions over the underlying foundation. \* The \*\*\[rendering layer]\(https\://api.flutter.dev/flutter/rendering/rendering-library.html)\*\* provides an abstraction for dealing with layout. With this layer, you can build a tree of renderable objects. You can manipulate these objects dynamically, with the tree automatically updating the layout to reflect your changes. \* The \*\*\[widgets layer]\(https\://api.flutter.dev/flutter/widgets/widgets-library.html)\*\*, part of the core Flutter SDK, is a composition abstraction. Each render object in the rendering layer has a corresponding class in the widgets layer. In addition, the widgets layer allows you to define combinations of classes that you can reuse. This is the layer at which the reactive programming model is introduced. \* The \*\*\[Material]\(https\://pub.dev/packages/material\_ui)\*\* and \*\*\[Cupertino]\(https\://pub.dev/packages/cupertino\_ui)\*\* packages offer comprehensive sets of controls that use the widget layer's composition primitives to implement the Material or iOS design languages. The Flutter framework is relatively small; many higher-level features that developers might use are implemented as packages, including platform plugins like \[camera]\(https\://pub.dev/packages/camera) and \[webview]\(https\://pub.dev/packages/webview\_flutter), as well as platform-agnostic features like \[characters]\(https\://pub.dev/packages/characters), \[http]\(https\://pub.dev/packages/http), and \[animations]\(https\://pub.dev/packages/animations) that build upon the core Dart and Flutter libraries. Additionally, the Material and Cupertino UI packages are found on pub.dev, allowing you to either import them or build your own design system on top of the core \`widgets\` library without bloating your app. Other packages come from the broader ecosystem, covering services like \[in-app payments]\(https\://pub.dev/packages/square\_in\_app\_payments), \[Apple authentication]\(https\://pub.dev/packages/sign\_in\_with\_apple), and \[animations]\(https\://pub.dev/packages/lottie). For more information about Flutter's approach to UI design, visit \[Buiding user interfaces with Flutter]\(/ui). The rest of this overview broadly navigates down the layers, starting with the reactive paradigm of UI development. Then, we describe how widgets are composed together and converted into objects that can be rendered as part of an application. We describe how Flutter interoperates with other code at a platform level, before giving a brief summary of how Flutter's web support differs from other targets. ## Anatomy of an app The following diagram gives an overview of the pieces that make up a regular Flutter app generated by \`flutter create\`. It shows where the Flutter Engine sits in this stack, highlights API boundaries, and identifies the repositories where the individual pieces live. The legend below clarifies some of the terminology commonly used to describe the pieces of a Flutter app. \*\*Dart App\*\* \* Composes widgets into the desired UI. \* Implements business logic. \* Owned by app developer. \*\*Framework\*\* (\[source code]\(https\://github.com/flutter/flutter/tree/main/packages/flutter/lib)) \* Provides higher-level API to build high-quality apps (for example, style-neutral widgets, hit-testing, gesture detection, accessibility, text input). \* Composites the app's widget tree into a scene. \*\*Engine\*\* (\[source code]\(https\://github.com/flutter/flutter/tree/main/engine/src/flutter/shell/common)) \* Responsible for rasterizing composited scenes. \* Provides low-level implementation of Flutter's core APIs (for example, graphics, text layout, Dart runtime). \* Exposes its functionality to the framework using the \*\*dart:ui API\*\*. \* Integrates with a specific platform using the Engine's \*\*Embedder API\*\*. \*\*Embedder\*\* (\[source code]\(https\://github.com/flutter/flutter/tree/main/engine/src/flutter/shell/platform)) \* Coordinates with the underlying operating system for access to services like rendering surfaces, accessibility, and input. \* Manages the event loop. \* Exposes \*\*platform-specific API\*\* to integrate the Embedder into apps. \*\*Runner\*\* \* Composes the pieces exposed by the platform-specific API of the Embedder into an app package runnable on the target platform. \* Part of app template generated by \`flutter create\`, owned by app developer. ## Reactive user interfaces On the surface, Flutter is \[a reactive, declarative UI framework]\[faq], in which the developer provides a mapping from application state to interface state, and the framework takes on the task of updating the interface at runtime when the application state changes. This model is inspired by \[work that came from Facebook for their own React framework]\[fb], which includes a rethinking of many traditional design principles. \[faq]: /resources/faq#what-programming-paradigm-does-flutters-framework-use \[fb]: https\://www\.youtube.com/watch?time\_continue=2\&v=x7cQ3mrcKaY\&feature=emb\_logo In most traditional UI frameworks, the user interface's initial state is described once and then separately updated by user code at runtime, in response to events. One challenge of this approach is that, as the application grows in complexity, the developer needs to be aware of how state changes cascade throughout the entire UI. For example, consider the following UI: !\[Color picker dialog]\(/assets/images/docs/arch-overview/color-picker.png){:width="66%"} There are many places where the state can be changed: the color box, the hue slider, the radio buttons. As the user interacts with the UI, changes must be reflected in every other place. Worse, unless care is taken, a minor change to one part of the user interface can cause ripple effects to seemingly unrelated pieces of code. One solution to this is an approach like MVC, where you push data changes to the model through the controller, and then the model pushes the new state to the view through the controller. However, this also is problematic, since creating and updating UI elements are two separate steps that can easily get out of sync. Flutter, along with other reactive frameworks, takes an alternative approach to this problem, by explicitly decoupling the user interface from its underlying state. With React-style APIs, you only create the UI description, and the framework takes care of using that one configuration to both create and/or update the user interface as appropriate. In Flutter, widgets (akin to components in React) are represented by immutable classes that are used to configure a tree of objects. These widgets are used to manage a separate tree of objects for layout, which is then used to manage a separate tree of objects for compositing. Flutter is, at its core, a series of mechanisms for efficiently walking the modified parts of trees, converting trees of objects into lower-level trees of objects, and propagating changes across these trees. A widget declares its user interface by overriding the \`build()\` method, which is a function that converts state to UI: \`\`\`plaintext UI = f(state) \`\`\` The \`build()\` method is by design fast to execute and should be free of side effects, allowing it to be called by the framework whenever needed (potentially as often as once per rendered frame). This approach relies on certain characteristics of a language runtime (in particular, fast object instantiation and deletion). Fortunately, \[Dart is particularly well suited for this task]\(https\://blog.flutter.dev/flutter-dont-fear-the-garbage-collector-d69b3ff1ca30). ## Widgets As mentioned, Flutter emphasizes widgets as a unit of composition. Widgets are the building blocks of a Flutter app's user interface, and each widget is an immutable declaration of part of the user interface. Widgets form a hierarchy based on composition. Each widget nests inside its parent and can receive context from the parent. This structure carries all the way up to the root widget (the container that hosts the Flutter app, typically \`MaterialApp\` or \`CupertinoApp\`), as this trivial example shows:

<!--?code-excerpt "lib/main.dart (main)"?-->

\`\`\`dart import 'package:flutter/services.dart'; import 'package:material\_ui/material\_ui.dart'; void main() => runApp(const MyApp()); class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( appBar: AppBar(title: const Text('My Home Page')), body: Center( child: Builder( builder: (context) { return Column( children: \[ const Text('Hello World'), const SizedBox(height: 20), ElevatedButton( onPressed: () { print('Click!'); }, child: const Text('A button'), ), ], ); }, ), ), ), ); } } \`\`\` In the preceding code, all instantiated classes are widgets. Apps update their user interface in response to events (such as a user interaction) by telling the framework to replace a widget in the hierarchy with another widget. The framework then compares the new and old widgets, and efficiently updates the user interface. Flutter has its own implementations of each UI control, rather than deferring to those provided by the system: for example, there is a pure \[Dart implementation]\(https\://pub.dev/documentation/cupertino\_ui/latest/cupertino\_ui/CupertinoSwitch-class.html) of both the \[iOS Toggle control]\(https\://developer.apple.com/design/human-interface-guidelines/toggles) and the \[one for]\(https\://pub.dev/documentation/material\_ui/latest/material\_ui/Switch-class.html) the \[Android equivalent]\(https\://m3.material.io/components/switch). This approach provides several benefits: \* Provides for unlimited extensibility. A developer who wants a variant of the Switch control can create one in any arbitrary way, and is not limited to the extension points provided by the OS. \* Avoids a significant performance bottleneck by allowing Flutter to composite the entire scene at once, without transitioning back and forth between Flutter code and platform code. \* Decouples the application behavior from any operating system dependencies. The application looks and feels the same on all versions of the OS, even if the OS changed the implementations of its controls. ### Composition Widgets are typically composed of many other small, single-purpose widgets that combine to produce powerful effects. Where possible, the number of design concepts is kept to a minimum while allowing the total vocabulary to be large. For example, in the widgets layer, Flutter uses the same core concept (a \`Widget\`) to represent drawing to the screen, layout (positioning and sizing), user interactivity, state management, theming, animations, and navigation. In the animation layer, a pair of concepts, \`Animation\`s and \`Tween\`s, cover most of the design space. In the rendering layer, \`RenderObject\`s are used to describe layout, painting, hit testing, and accessibility. In each of these cases, the corresponding vocabulary ends up being large: there are hundreds of widgets and render objects, and dozens of animation and tween types. The class hierarchy is deliberately shallow and broad to maximize the possible number of combinations, focusing on small, composable widgets that each do one thing well. Core features are abstract, with even basic features like padding and alignment being implemented as separate components rather than being built into the core. (This also contrasts with more traditional APIs where features like padding are built in to the common core of every layout component.) So, for example, to center a widget, rather than adjusting a notional \`Align\` property, you wrap it in a \[\`Center\`]\(https\://api.flutter.dev/flutter/widgets/Center-class.html) widget. There are widgets for padding, alignment, rows, columns, and grids. These layout widgets do not have a visual representation of their own. Instead, their sole purpose is to control some aspect of another widget's layout. Flutter also includes utility widgets that take advantage of this compositional approach. For example, \[\`Container\`]\(https\://api.flutter.dev/flutter/widgets/Container-class.html), a commonly used widget, is made up of several widgets responsible for layout, painting, positioning, and sizing. Specifically, \`Container\` is made up of the \[\`LimitedBox\`]\(https\://api.flutter.dev/flutter/widgets/LimitedBox-class.html), \[\`ConstrainedBox\`]\(https\://api.flutter.dev/flutter/widgets/ConstrainedBox-class.html), \[\`Align\`]\(https\://api.flutter.dev/flutter/widgets/Align-class.html), \[\`Padding\`]\(https\://api.flutter.dev/flutter/widgets/Padding-class.html), \[\`DecoratedBox\`]\(https\://api.flutter.dev/flutter/widgets/DecoratedBox-class.html), and \[\`Transform\`]\(https\://api.flutter.dev/flutter/widgets/Transform-class.html) widgets, as you can see by reading its source code. A defining characteristic of Flutter is that you can drill down into the source for any widget and examine it. So, rather than subclassing \`Container\` to produce a customized effect, you can compose it and other widgets in novel ways, or just create a new widget using \`Container\` as inspiration. ### Building widgets As mentioned earlier, you determine the visual representation of a widget by overriding the \[\`build()\`]\(https\://api.flutter.dev/flutter/widgets/StatelessWidget/build.html) function to return a new element tree. This tree represents the widget's part of the user interface in more concrete terms. For example, a toolbar widget might have a build function that returns a \[horizontal layout]\(https\://api.flutter.dev/flutter/widgets/Row-class.html) of some \[text]\(https\://api.flutter.dev/flutter/widgets/Text-class.html) and \[various]\(https\://pub.dev/documentation/material\_ui/latest/material\_ui/IconButton-class.html) \[buttons]\(https\://pub.dev/documentation/material\_ui/latest/material\_ui/PopupMenuButton-class.html). As needed, the framework recursively asks each widget to build until the tree is entirely described by \[concrete renderable objects]\(https\://api.flutter.dev/flutter/widgets/RenderObjectWidget-class.html). The framework then stitches together the renderable objects into a renderable object tree. A widget's build function should be free of side effects. Whenever the function is asked to build, the widget should return a new tree of widgets\[^1], regardless of what the widget previously returned. The framework does the heavy lifting work to determine which build methods need to be called based on the render object tree (described in more detail later). More information about this process can be found in the \[Inside Flutter topic]\(/resources/inside-flutter#linear-reconciliation). On each rendered frame, Flutter can recreate just the parts of the UI where the state has changed by calling that widget's \`build()\` method. Therefore it is important that build methods should return quickly, and heavy computational work should be done in some asynchronous manner and then stored as part of the state to be used by a build method. While relatively naive in approach, this automated comparison is quite effective, enabling high-performance, interactive apps. And, the design of the build function simplifies your code by focusing on declaring what a widget is made of, rather than the complexities of updating the user interface from one state to another. ### Widget state The framework introduces two major classes of widget: \_stateful\_ and \_stateless\_ widgets. Many widgets have no mutable state: they don't have any properties that change over time (for example, an icon or a label). These widgets subclass \[\`StatelessWidget\`]\(https\://api.flutter.dev/flutter/widgets/StatelessWidget-class.html). However, if the unique characteristics of a widget needs to change based on user interaction or other factors, that widget is \_stateful\_. For example, if a widget has a counter that increments whenever the user taps a button, then the value of the counter is the state for that widget. When that value changes, the widget needs to be rebuilt to update its part of the UI. These widgets subclass \[\`StatefulWidget\`]\(https\://api.flutter.dev/flutter/widgets/StatefulWidget-class.html), and (because the widget itself is immutable) they store mutable state in a separate class that subclasses \[\`State\`]\(https\://api.flutter.dev/flutter/widgets/State-class.html). \`StatefulWidget\`s don't have a build method; instead, their user interface is built through their \`State\` object. Whenever you mutate a \`State\` object (for example, by incrementing the counter), you must call \[\`setState()\`]\(https\://api.flutter.dev/flutter/widgets/State/setState.html) to signal the framework to update the user interface by calling the \`State\`'s build method again. Having separate state and widget objects lets other widgets treat both stateless and stateful widgets in exactly the same way, without being concerned about losing state. Instead of needing to hold on to a child to preserve its state, the parent can create a new instance of the child at any time without losing the child's persistent state. The framework does all the work of finding and reusing existing state objects when appropriate. ### State management So, if many widgets can contain state, how is state managed and passed around the system? As with any other class, you can use a constructor in a widget to initialize its data, so a \`build()\` method can ensure that any child widget is instantiated with the data it needs: \`\`\`dart @override Widget build(BuildContext context) { return ContentWidget(\[!importantState!]); } \`\`\` Where \`importantState\` is a placeholder for the class that contains the state important to the \`Widget\`. As widget trees get deeper, however, passing state information up and down the tree hierarchy becomes cumbersome. So, a third widget type, \[\`InheritedWidget\`]\[], provides an easy way to grab data from a shared ancestor. You can use \`InheritedWidget\` to create a state widget that wraps a common ancestor in the widget tree, as shown in this example: !\[Inherited widgets]\(/assets/images/docs/arch-overview/inherited-widget.png){:width="50%" .diagram-wrap} \[\`InheritedWidget\`]: https\://api.flutter.dev/flutter/widgets/InheritedWidget-class.html Whenever one of the \`ExamWidget\` or \`GradeWidget\` objects needs data from \`StudentState\`, it can now access it with a command such as: \`\`\`dart final studentState = StudentState.of(context); \`\`\` The \`of(context)\` call takes the build context (a handle to the current widget location), and returns \[the nearest ancestor in the tree]\[] that matches the \`StudentState\` type. \`InheritedWidget\`s also offer an \`updateShouldNotify()\` method, which Flutter calls to determine whether a state change should trigger a rebuild of child widgets that use it. \[the nearest ancestor in the tree]: https\://api.flutter.dev/flutter/widgets/BuildContext/dependOnInheritedWidgetOfExactType.html Flutter itself uses \`InheritedWidget\` extensively as part of the framework for shared state, such as the application's \_visual theme\_, which includes \[properties like color and type styles]\[] that are pervasive throughout an application. The \`MaterialApp\` \`build()\` method inserts a theme in the tree when it builds, and then deeper in the hierarchy a widget can use the \`.of()\` method to look up the relevant theme data. For example:

<!--?code-excerpt "lib/main.dart (container)"?-->

\`\`\`dart Container( color: Theme.of(context).secondaryHeaderColor, child: Text( 'Text with a background color', style: Theme.of(context).textTheme.titleLarge, ), ); \`\`\` \[properties like color and type styles]: https\://pub.dev/documentation/material\_ui/latest/material\_ui/ThemeData-class.html As applications grow, more advanced state management approaches that reduce the ceremony of creating and using stateful widgets become more attractive. Many Flutter apps use utility packages like \[provider]\(https\://pub.dev/packages/provider), which provides a wrapper around \`InheritedWidget\`. Flutter's layered architecture also enables alternative approaches to implement the transformation of state into UI, such as the \[flutter\_hooks]\(https\://pub.dev/packages/flutter\_hooks) package. ## Rendering and layout This section describes the rendering pipeline, which is the series of steps that Flutter takes to convert a hierarchy of widgets into the actual pixels painted onto a screen. ### Flutter's rendering model You might be wondering: if Flutter is a cross-platform framework, then how can it offer comparable performance to single-platform frameworks? It's useful to start by thinking about how traditional Android apps work. When drawing, you first call the Java code of the Android framework. The Android system libraries provide the components responsible for drawing themselves to a \`Canvas\` object, which Android can then render with \[Skia]\[], a graphics engine written in C/C++ that calls the CPU or GPU to complete the drawing on the device. Cross-platform frameworks \_typically\_ work by creating an abstraction layer over the underlying native Android and iOS UI libraries, attempting to smooth out the inconsistencies of each platform representation. App code is often written in an interpreted language like JavaScript, which must in turn interact with the Java-based Android or Objective-C-based iOS system libraries to display UI. All this adds overhead that can be significant, particularly where there is a lot of interaction between the UI and the app logic. By contrast, Flutter minimizes those abstractions, bypassing the system UI widget libraries in favor of its own widget set. The Dart code that paints Flutter's visuals is compiled into native code, which uses Impeller for rendering. Impeller is shipped along with the application, allowing the developer to upgrade their app to stay updated with the latest performance improvements even if the phone hasn't been updated with a new Android version. The same is true for Flutter on other native platforms, such as Windows or macOS. :::note If you want to know which devices Impeller supports, check out \[Impeller availability]\[]. For more information, visit \[Impeller rendering engine]\[] ::: \[Impeller rendering engine]: /perf/impeller ### From user input to the GPU The overriding principle that Flutter applies to its rendering pipeline is that \*\*simple is fast\*\*. Flutter has a straightforward pipeline for how data flows to the system, as shown in the following sequencing diagram: !\[Render pipeline sequencing diagram]\(/assets/images/docs/arch-overview/render-pipeline.png){:width="100%" .diagram-wrap} Let's take a look at some of these phases in greater detail. ### Build: from Widget to Element Consider this code fragment that demonstrates a widget hierarchy:

<!--?code-excerpt "lib/main.dart (widget-hierarchy)"?-->

\`\`\`dart Container( color: Colors.blue, child: Row( children: \[ Image.network('https\://www\.example.com/1.png'), const Text('A'), ], ), ); \`\`\` When Flutter needs to render this fragment, it calls the \`build()\` method, which returns a subtree of widgets that renders UI-based on the current app state. During this process, the \`build()\` method can introduce new widgets, as necessary, based on its state. As an example, in the preceding code fragment, \`Container\` has \`color\` and \`child\` properties. From looking at the \[source code]\(https\://github.com/flutter/flutter/blob/02efffc134ab4ce4ff50a9ddd86c832efdb80462/packages/flutter/lib/src/widgets/container.dart#L401) for \`Container\`, you can see that if the color is not null, it inserts a \`ColoredBox\` representing the color: \`\`\`dart if (color != null) current = ColoredBox(color: color!, child: current); \`\`\` Correspondingly, the \`Image\` and \`Text\` widgets might insert child widgets such as \`RawImage\` and \`RichText\` during the build process. The eventual widget hierarchy might therefore be deeper than what the code represents, as in this case\[^2]: !\[Render pipeline sequencing diagram]\(/assets/images/docs/arch-overview/widgets.png){:width="40%" .diagram-wrap} This explains why, when you examine the tree through a debug tool such as the \[Flutter inspector]\(/tools/devtools/inspector), part of the Flutter/Dart DevTools, you might see a structure that is considerably deeper than what is in your original code. During the build phase, Flutter translates the widgets expressed in code into a corresponding \*\*element tree\*\*, with one element for every widget. Each element represents a specific instance of a widget in a given location of the tree hierarchy. There are two basic types of elements: - \`ComponentElement\`, a host for other elements. - \`RenderObjectElement\`, an element that participates in the layout or paint phases. !\[Render pipeline sequencing diagram]\(/assets/images/docs/arch-overview/widget-element.png){:width="85%" .diagram-wrap} \`RenderObjectElement\`s are an intermediary between their widget analog and the underlying \`RenderObject\`, which we'll come to later. The element for any widget can be referenced through its \`BuildContext\`, which is a handle to the location of a widget in the tree. This is the \`context\` in a function call such as \`Theme.of(context)\`, and is supplied to the \`build()\` method as a parameter. Because widgets are immutable, including the parent/child relationship between nodes, any change to the widget tree (such as changing \`Text('A')\` to \`Text('B')\` in the preceding example) causes a new set of widget objects to be returned. But that doesn't mean the underlying representation must be rebuilt. The element tree is persistent from frame to frame, and therefore plays a critical performance role, allowing Flutter to act as if the widget hierarchy is fully disposable while caching its underlying representation. By only walking through the widgets that changed, Flutter can rebuild just the parts of the element tree that require reconfiguration. ### Layout and rendering It would be a rare application that drew only a single widget. An important part of any UI framework is therefore the ability to efficiently lay out a hierarchy of widgets, determining the size and position of each element before they are rendered on the screen. The base class for every node in the render tree is \[\`RenderObject\`]\(https\://api.flutter.dev/flutter/rendering/RenderObject-class.html), which defines an abstract model for layout and painting. This is extremely general: it does not commit to a fixed number of dimensions or even a Cartesian coordinate system (demonstrated by \[this example of a polar coordinate system]\(https\://dartpad.dev/?id=596b1d6331e3b9d7b00420085fab3e27)). Each \`RenderObject\` knows its parent, but knows little about its children other than how to \_visit\_ them and their constraints. This provides \`RenderObject\` with sufficient abstraction to be able to handle a variety of use cases. During the build phase, Flutter creates or updates an object that inherits from \`RenderObject\` for each \`RenderObjectElement\` in the element tree. \`RenderObject\`s are primitives: \[\`RenderParagraph\`]\(https\://api.flutter.dev/flutter/rendering/RenderParagraph-class.html) renders text, \[\`RenderImage\`]\(https\://api.flutter.dev/flutter/rendering/RenderImage-class.html) renders an image, and \[\`RenderTransform\`]\(https\://api.flutter.dev/flutter/rendering/RenderTransform-class.html) applies a transformation before painting its child. !\[Differences between the widgets hierarchy and the element and render trees]\(/assets/images/docs/arch-overview/trees.png){:width="100%" .diagram-wrap} Most Flutter widgets are rendered by an object that inherits from the \`RenderBox\` subclass, which represents a \`RenderObject\` of fixed size in a 2D Cartesian space. \`RenderBox\` provides the basis of a \_box constraint model\_, establishing a minimum and maximum width and height for each widget to be rendered. To perform layout, Flutter walks the render tree in a depth-first traversal and \*\*passes down size constraints\*\* from parent to child. In determining its size, the child \_must\_ respect the constraints given to it by its parent. Children respond by \*\*passing up a size\*\* to their parent object within the constraints the parent established. !\[Constraints go down, sizes go up]\(/assets/images/docs/arch-overview/constraints-sizes.png){:width="70%" .diagram-wrap} At the end of this single walk through the tree, every object has a defined size within its parent's constraints and is ready to be painted by calling the \[\`paint()\`]\(https\://api.flutter.dev/flutter/rendering/RenderObject/paint.html) method. The box constraint model is very powerful as a way to layout objects in \_O(n)\_ time: - Parents can dictate the size of a child object by setting maximum and minimum constraints to the same value. For example, the topmost render object in a phone app constrains its child to be the size of the screen. (Children can choose how to use that space. For example, they might just center what they want to render within the dictated constraints.) - A parent can dictate the child's width but give the child flexibility over height (or dictate height but offer flexibility over width). A real-world example is flow text, which might have to fit a horizontal constraint but vary vertically depending on the quantity of text. This model works even when a child object needs to know how much space it has available to decide how it will render its content. By using a \[\`LayoutBuilder\`]\(https\://api.flutter.dev/flutter/widgets/LayoutBuilder-class.html) widget, the child object can examine the passed-down constraints and use those to determine how it will use them, for example:

<!--?code-excerpt "lib/main.dart (layout-builder)"?-->

\`\`\`dart Widget build(BuildContext context) { return LayoutBuilder( builder: (context, constraints) { if (constraints.maxWidth < 600) { return const OneColumnLayout(); } else { return const TwoColumnLayout(); } }, ); } \`\`\` More information about the constraint and layout system, along with working examples, can be found in the \[Understanding constraints]\(/ui/layout/constraints) topic. The root of all \`RenderObject\`s is the \`RenderView\`, which represents the total output of the render tree. When the platform demands a new frame to be rendered (for example, because of a \[vsync]\(https\://source.android.com/devices/graphics/implement-vsync) or because a texture decompression/upload is complete), a call is made to the \`compositeFrame()\` method, which is part of the \`RenderView\` object at the root of the render tree. This creates a \`SceneBuilder\` to trigger an update of the scene. When the scene is complete, the \`RenderView\` object passes the composited scene to the \`Window\.render()\` method in \`dart:ui\`, which passes control to the GPU to render it. Further details of the composition and rasterization stages of the pipeline are beyond the scope of this high-level article, but more information can be found \[in this talk on the Flutter rendering pipeline]\(https\://www\.youtube.com/watch?v=UUfXWzp0-DU). ## Platform embedding As we've seen, rather than being translated into the equivalent OS widgets, Flutter user interfaces are built, laid out, composited, and painted by Flutter itself. The mechanism for obtaining the texture and participating in the app lifecycle of the underlying operating system inevitably varies depending on the unique concerns of that platform. The engine is platform-agnostic, presenting a \[stable ABI (Application Binary Interface)]\[ABI]. that provides a \_platform embedder\_ with a way to set up and use Flutter. \[ABI]: https\://github.com/flutter/flutter/blob/main/engine/src/flutter/shell/platform/embedder/embedder.h The platform embedder is the native OS application that hosts all Flutter content, and acts as the glue between the host operating system and Flutter. When you start a Flutter app, the embedder provides the entrypoint, initializes the Flutter engine, obtains threads for UI and rastering, and creates a texture that Flutter can write to. The embedder is also responsible for the app lifecycle, including input gestures (such as mouse, keyboard, touch), window sizing, thread management, and platform messages. Flutter includes platform embedders for Android, iOS, Windows, macOS, and Linux; you can also create a custom platform embedder, as in \[this worked example]\(https\://github.com/chinmaygarde/fluttercast) that supports remoting Flutter sessions through a VNC-style framebuffer or \[this worked example for Raspberry Pi]\(https\://github.com/ardera/flutter-pi). Each platform has its own set of APIs and constraints. Some brief platform-specific notes: - As of Flutter 3.29, the UI and platform threads are merged on iOS and Android. Specifically, the UI thread is removed and the Dart code runs on the native platform thread. For more information, see \[The great thread merge]\[] video. - On iOS and macOS, Flutter is loaded into the embedder as a \`UIViewController\` or \`NSViewController\`, respectively. The platform embedder creates a \`FlutterEngine\`, which serves as a host to the Dart VM and your Flutter runtime, and a \`FlutterViewController\`, which attaches to the \`FlutterEngine\` to pass UIKit or Cocoa input events into Flutter and to display frames rendered by the \`FlutterEngine\` using Metal or OpenGL. - On Android, Flutter is, by default, loaded into the embedder as an \`Activity\`. The view is controlled by a \[\`FlutterView\`]\(https\://api.flutter.dev/javadoc/io/flutter/embedding/android/FlutterView\.html), which renders Flutter content either as a view or a texture, depending on the composition and z-ordering requirements of the Flutter content. - On Windows, Flutter is hosted in a traditional Win32 app, and content is rendered using \[ANGLE]\(https\://chromium.googlesource.com/angle/angle/+/master/README.md), a library that translates OpenGL API calls to the DirectX 11 equivalents. \[The great thread merge]: https\://youtu.be/miW7vCmQwnw?si=9EYvRDxtkpkPrcSO ## Integrating with other code Flutter provides a variety of interoperability mechanisms, whether you're accessing code or APIs written in a language like Kotlin or Swift, calling a native C-based API, embedding native controls in a Flutter app, or embedding Flutter in an existing application. ### Platform channels For mobile and desktop apps, Flutter allows you to call into custom code through a \_platform channel\_, which is a mechanism for communicating between your Dart code and the platform-specific code of your host app. By creating a common channel (encapsulating a name and a codec), you can send and receive messages between Dart and a platform component written in a language like Kotlin or Swift. Data is serialized from a Dart type like \`Map\` into a standard format, and then deserialized into an equivalent representation in Kotlin (such as \`HashMap\`) or Swift (such as \`Dictionary\`). !\[How platform channels allow Flutter to communicate with host code]\(/assets/images/docs/arch-overview/platform-channels.png){:width="65%" .diagram-wrap} The following is a short platform channel example of a Dart call to a receiving event handler in Kotlin (Android) or Swift (iOS):

<!--?code-excerpt "lib/main.dart (method-channel)"?-->

\`\`\`dart // Dart side const channel = MethodChannel('foo'); final greeting = await channel.invokeMethod('bar', 'world') as String; print(greeting); \`\`\` \`\`\`kotlin // Android (Kotlin) val channel = MethodChannel(flutterView, "foo") channel.setMethodCallHandler { call, result -> when (call.method) { "bar" -> result.success("Hello, ${call.arguments}") else -> result.notImplemented() } } \`\`\` \`\`\`swift // iOS (Swift) let channel = FlutterMethodChannel(name: "foo", binaryMessenger: flutterView) channel.setMethodCallHandler { (call: FlutterMethodCall, result: FlutterResult) -> Void in switch (call.method) { case "bar": result("Hello, \\(call.arguments as! String)") default: result(FlutterMethodNotImplemented) } } \`\`\` Further examples of using platform channels, including examples for desktop platforms, can be found in the \[flutter/packages]\(https\://github.com/flutter/packages) repository. There are also \[thousands of plugins already available]\(https\://pub.dev/flutter) for Flutter that cover many common scenarios, ranging from Firebase to ads to device hardware like camera and Bluetooth. ### Foreign Function Interface (FFI) For C-based APIs, including those that can be generated for code written in modern languages like Rust or Go, Dart provides a direct mechanism for binding to native code using the \`dart:ffi\` library. The foreign function interface (FFI) model can be considerably faster than platform channels, because no serialization is required to pass data. Instead, the Dart runtime provides the ability to allocate memory on the heap that is backed by a Dart object and make calls to statically or dynamically linked libraries. FFI is available for all platforms other than web, where the \[JS interop libraries]\[] and \[\`package:web\`]\[] serve a similar purpose. To use FFI, you create a \`typedef\` for each of the Dart and unmanaged method signatures, and instruct the Dart VM to map between them. As an example, here's a fragment of code to call the traditional Win32 \`MessageBox()\` API:

<!--?code-excerpt "lib/ffi.dart" remove="ignore:"?-->

\`\`\`dart import 'dart:ffi'; import 'package:ffi/ffi.dart'; // contains .toNativeUtf16() extension method typedef MessageBoxNative = Int32 Function( IntPtr hWnd, Pointer lpText, Pointer lpCaption, Int32 uType, ); typedef MessageBoxDart = int Function( int hWnd, Pointer lpText, Pointer lpCaption, int uType, ); void exampleFfi() { final user32 = DynamicLibrary.open('user32.dll'); final messageBox = user32.lookupFunction( 'MessageBoxW', ); final result = messageBox( 0, // No owner window 'Test message'.toNativeUtf16(), // Message 'Window caption'.toNativeUtf16(), // Window title 0, // OK button only ); } \`\`\` \[JS interop libraries]: https\://dart.dev/interop/js-interop \[\`package:web\`]: https\://pub.dev/packages/web ### Rendering native controls in a Flutter app Because Flutter content is drawn to a texture and its widget tree is entirely internal, there's no place for something like an Android view to exist within Flutter's internal model or render interleaved within Flutter widgets. That's a problem for developers that would like to include existing platform components in their Flutter apps, such as a browser control. Flutter solves this by introducing platform view widgets (\[\`AndroidView\`]\(https\://api.flutter.dev/flutter/widgets/AndroidView-class.html) and \[\`UiKitView\`]\(https\://api.flutter.dev/flutter/widgets/UiKitView-class.html)) that let you embed this kind of content on each platform. Platform views can be integrated with other Flutter content\[^3]. Each of these widgets acts as an intermediary to the underlying operating system. For example, on Android, \`AndroidView\` serves three primary functions: - Making a copy of the graphics texture rendered by the native view and presenting it to Flutter for composition as part of a Flutter-rendered surface each time the frame is painted. - Responding to hit testing and input gestures, and translating those into the equivalent native input. - Creating an analog of the accessibility tree, and passing commands and responses between the native and Flutter layers. Inevitably, there is a certain amount of overhead associated with this synchronization. In general, therefore, this approach is best suited for complex controls like Google Maps where reimplementing in Flutter isn't practical. Typically, a Flutter app instantiates these widgets in a \`build()\` method based on a platform test. As an example, from the \[google\_maps\_flutter]\(https\://pub.dev/packages/google\_maps\_flutter) plugin: \`\`\`dart if (defaultTargetPlatform == TargetPlatform.android) { return AndroidView( viewType: 'plugins.flutter.io/google\_maps', onPlatformViewCreated: onPlatformViewCreated, gestureRecognizers: gestureRecognizers, creationParams: creationParams, creationParamsCodec: const StandardMessageCodec(), ); } else if (defaultTargetPlatform == TargetPlatform.iOS) { return UiKitView( viewType: 'plugins.flutter.io/google\_maps', onPlatformViewCreated: onPlatformViewCreated, gestureRecognizers: gestureRecognizers, creationParams: creationParams, creationParamsCodec: const StandardMessageCodec(), ); } return Text( '$defaultTargetPlatform is not yet supported by the maps plugin', ); \`\`\` Communicating with the native code underlying the \`AndroidView\` or \`UiKitView\` typically occurs using the platform channels mechanism, as previously described. At present, platform views aren't available for desktop platforms, but this is not an architectural limitation; support might be added in the future. ### Hosting Flutter content in a parent app The converse of the preceding scenario is embedding a Flutter widget in an existing Android or iOS app. As described in an earlier section, a newly created Flutter app running on a mobile device is hosted in an Android activity or iOS \`UIViewController\`. Flutter content can be embedded into an existing Android or iOS app using the same embedding API. The Flutter module template is designed for easy embedding; you can either embed it as a source dependency into an existing Gradle or Xcode build definition, or you can compile it into an Android Archive or iOS Framework binary for use without requiring every developer to have Flutter installed. The Flutter engine takes a short while to initialize, because it needs to load Flutter shared libraries, initialize the Dart runtime, create and run a Dart isolate, and attach a rendering surface to the UI. To minimize any UI delays when presenting Flutter content, it's best to initialize the Flutter engine during the overall app initialization sequence, or at least ahead of the first Flutter screen, so that users don't experience a sudden pause while the first Flutter code is loaded. In addition, separating the Flutter engine allows it to be reused across multiple Flutter screens and share the memory overhead involved with loading the necessary libraries. More information about how Flutter is loaded into an existing Android or iOS app can be found at the \[Load sequence, performance and memory topic]\(/add-to-app/performance). ## Flutter web support While the general architectural concepts apply to all platforms that Flutter supports, there are some unique characteristics of Flutter's web support that are worthy of comment. Dart has been compiling to JavaScript for as long as the language has existed, with a toolchain optimized for both development and production purposes. Many important apps compile from Dart to JavaScript and run in production today, including the \[advertiser tooling for Google Ads]\(https\://ads.google.com/home/). Because the Flutter framework is written in Dart, compiling it to JavaScript was relatively straightforward. However, the Flutter engine, written in C++, is designed to interface with the underlying operating system rather than a web browser. A different approach is therefore required. On the web, Flutter compiles your application code into either JavaScript or \[WebAssembly]\[Dart WebAssembly support] (when building with \`--wasm\`), rendering graphics using a WebAssembly build of the Skia engine. !\[Flutter web architecture]\(/assets/images/docs/resources/diagram-layercake.webp){:width="80%" .diagram-wrap} Perhaps the most notable difference compared to other platforms on which Flutter runs is that there is no need for Flutter to provide a Dart runtime. Instead, the Flutter framework (along with any code you write) is compiled to JavaScript or WebAssembly. It's also worthy to note that Dart has very few language semantic differences across all of its modes (JIT versus AOT, native versus web compilation), and most developers will never write a line of code that runs into such a difference. During development time, Flutter web uses \[\`dartdevc\`]\(https\://dart.dev/tools/dartdevc), a compiler that supports incremental compilation and therefore allows hot restart and \[hot reload behind a flag]\[]. Conversely, when you are ready to create a production app for the web, \[\`dart2js\`]\(https\://dart.dev/tools/dart2js) or \`dart2wasm\` compiles the Flutter core and framework along with your application into optimized output that can be deployed to any web server. Code can be offered in a single file or split into multiple files through \[deferred imports]\[]. For more information on Flutter web, check out \[Web support for Flutter]\[]. \[Dart WebAssembly support]: https\://dart.dev/web/wasm \[deferred imports]: https\://dart.dev/language/libraries#lazily-loading-a-library \[Web support for Flutter]: /platform-integration/web ## Further information For those interested in more information about the internals of Flutter, the \[Inside Flutter]\(/resources/inside-flutter) whitepaper provides a useful guide to the framework's design philosophy. \[^1]: While the \`build\` function returns a fresh tree, you only need to return something \_different\_ if there's some new configuration to incorporate. If the configuration is in fact the same, you can just return the same widget. \[^2]: This is a slight simplification for ease of reading. In practice, the tree might be more complex. \[^3]: There are some limitations with this approach, for example, transparency doesn't composite the same way for a platform view as it would for other Flutter widgets.

----
url: https://docs.flutter.dev/platform-integration/windows/extern_win
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top External windows in Flutter Windows apps](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

# Windows lifecycle

## Who is affected

[#](#who-is-affected)

Windows applications built against Flutter versions after 3.13 that open non-Flutter windows.

## Overview

[#](#overview)

When adding a non-Flutter window to a Flutter Windows app, it will not be part of the logic for application lifecycle state updates by default. For example, this means that when the external window is shown or hidden, the app lifecycle state won't appropriately update to inactive or hidden. As a result, the app might receive incorrect lifecycle state changes through [WidgetsBindingObserver.didChangeAppLifecycle](https://api.flutter.dev/flutter/widgets/WidgetsBindingObserver/didChangeAppLifecycleState.html).

# What do I need to do?

To add the external window to this application logic, the window's `WndProc` procedure must invoke `FlutterEngine::ProcessExternalWindowMessage`.

To achieve this, add the following code to the message handler function of your custom external window. In C++ wrappers for the Win32 API, this is often a class method called from the window's `WndProc`. The exact file and class name depend on your app's implementation.

cpp

```
LRESULT MyExternalWindow::MessageHandler(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) {
    std::optional<LRESULT> result = flutter_controller_->engine()->ProcessExternalWindowMessage(hwnd, msg, wparam, lparam);
    if (result.has_value()) {
        return *result;
    }
    // Original contents of WndProc...
}
```

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/platform-integration/windows/extern_win.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/platform-integration/windows/extern_win\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/platform-integration/windows/extern_win.md "Report an issue with this page").

----
url: https://docs.flutter.dev/install/with-vs-code.md
----

\# Install Flutter using VS Code > Learn how to use VS Code to quickly install and set up the Flutter SDK. Learn how to install and set up Flutter in a Code OSS-based editor. This includes (but is not limited to), \[VS Code]\[], \[Antigravity]\[], \[Cursor]\[], and \[Windsurf]\[]. \[VS Code]: https\://code.visualstudio.com \[Antigravity]: https\://antigravity.google/ \[Cursor]: https\://cursor.com/ \[Windsurf]: https\://windsurf.com/ :::tip If you've never set up or developed an app with Flutter before, follow \[Set up and test drive Flutter]\[] instead. ::: \[Set up and test drive Flutter]: /install/quick ## Choose your development platform {: #dev-platform} The instructions on this page are configured to cover installing Flutter on a \*\*Windows\*\*{:.selected-os-text} device. If you'd like to follow the instructions for a different OS, please select one of the following. ## Download prerequisite software {: #download-prerequisites} For the smoothest Flutter setup, first install the following tools. 1.

### Set up Linux support

If you haven't set up Linux support on your Chromebook before, \[Turn on Linux support]\[chromeos-linux]. If you've already turned on Linux support, ensure it's up to date following the \[Fix problems with Linux]\[chromeos-linux-update] instructions. 1.

### Download and install prerequisite packages

Using \`apt-get\` or your preferred installation mechanism, install the latest versions of the following packages: - \`curl\` - \`git\` - \`unzip\` - \`xz-utils\` - \`zip\` - \`libglu1-mesa\` If you want to use \`apt-get\`, install these packages using the following commands: \`\`\`console $ sudo apt-get update -y && sudo apt-get upgrade -y $ sudo apt-get install -y curl git unzip xz-utils zip libglu1-mesa \`\`\` 1.

### Download and install Visual Studio Code

To quickly install Flutter, then edit and debug your apps, \[install and set up Visual Studio Code]\[vscode-install]. {: .steps .chromeos-only}

:::warning \*\*Note for Intel Macs:\*\* Flutter is deprecating support for Intel-based Macs (x64). Future Flutter releases will require Apple Silicon. For details, check out the \[macOS Intel deprecation strategy]\(https\://docs.google.com/document/d/1ty3js\_Eg2sNIbDuyYS\_aV7h4jYdx1hEpX\_mV135gO4s/edit?tab=t.0#heading=h.cx7d8y57ce6p). :::

1\.

### Install the Xcode command-line tools

Download the Xcode command-line tools to get access to the command-line tools that Flutter relies on, including Git. To download the tools, run the following command in your preferred terminal: \`\`\`console $ xcode-select --install \`\`\` If you haven't installed the tools already, a dialog should open that confirms you'd like to install them. Click \*\*Install\*\*, then once the installation is complete, click \*\*Done\*\*. 1.

### Download and install Visual Studio Code

To quickly install Flutter, then edit and debug your apps, \[install and set up Visual Studio Code]\[vscode-install]. {: .steps .macos-only} 1.

### Install Git for Windows

Download and install the latest version of \[Git for Windows]\[]. For help installing or troubleshooting Git, reference the \[Git documentation]\[git-install]. 1.

### Download and install Visual Studio Code

To quickly install Flutter, then edit and debug your apps, \[install and set up Visual Studio Code]\[vscode-install]. {: .steps .windows-only} 1.

### Download and install prerequisite packages

Using your preferred package manager or mechanism, install the latest versions of the following packages: - \`curl\` - \`git\` - \`unzip\` - \`xz-utils\` - \`zip\` - \`libglu1-mesa\` On Debian-based distros with \`apt-get\`, such as Ubuntu, install these packages using the following commands: \`\`\`console $ sudo apt-get update -y && sudo apt-get upgrade -y $ sudo apt-get install -y curl git unzip xz-utils zip libglu1-mesa \`\`\` 1.

### Download and install Visual Studio Code

To quickly install Flutter, then edit and debug your apps, \[install and set up Visual Studio Code]\[vscode-install]. {: .steps .linux-only} \[chromeos-linux]: https\://support.google.com/chromebook/answer/9145439 \[chromeos-linux-update]: https\://support.google.com/chromebook/answer/9145439?hl=en#:\~:text=Fix%20problems%20with%20Linux \[Git for Windows]: https\://git-scm.com/downloads/win \[git-install]: https\://git-scm.com/book/en/v2/Getting-Started-Installing-Git \[vscode-install]: https\://code.visualstudio.com/docs/setup/setup-overview ## Install and set up Flutter {: #install-flutter} Now that you've installed Git and VS Code, follow these steps to use VS Code to install and set up Flutter. 1.

### Launch VS Code

If not already open, open VS Code by searching for it with Spotlight or opening it manually from the directory where it's installed. 1.

### Add the Flutter extension to VS Code

To add the Dart and Flutter extensions to VS Code, visit the \[Flutter extension's marketplace page]\[flutter-vscode], and click \*\*Install\*\*. If prompted by your browser, allow it to open VS Code. 1.

### Install Flutter with VS Code

1\. Open the command palette in VS Code. Go to \*\*View\*\* > \*\*Command Palette\*\* or press `Cmd/Ctrl` + `Shift` + `P`. 1. In the command palette, type \`flutter\`. 1. Select \*\*Flutter: New Project\*\*. 1. VS Code prompts you to locate the Flutter SDK on your computer. Select \*\*Download SDK\*\*. 1. When the \*\*Select Folder for Flutter SDK\*\* dialog displays, choose where you want to install Flutter. 1. Click \*\*Clone Flutter\*\*. While downloading Flutter, VS Code displays this pop-up notification: \`\`\`console Downloading the Flutter SDK. This may take a few minutes. \`\`\` This download takes a few minutes. If you suspect that the download has hung, click \*\*Cancel\*\* then start the installation again. 1. Click \*\*Add SDK to PATH\*\*. When successful, a notification displays: \`\`\`console The Flutter SDK was added to your PATH \`\`\` 1. VS Code might display a Google Analytics notice. If you agree, click \*\*OK\*\*. 1. To ensure that Flutter is available in all terminals: 1. Close, then reopen all terminal windows. 1. Restart VS Code. {:type="a"} 1.

### Validate your setup

To ensure you installed Flutter correctly, run \`flutter doctor -v\` in your preferred terminal. If the command isn't found or there's an error, check out \[Flutter installation troubleshooting]\[troubleshoot]. {:.steps} \[Install Flutter manually]: /install/manual \[flutter-vscode]: https\://marketplace.visualstudio.com/items?itemName=Dart-Code.flutter \[troubleshoot]: /install/troubleshoot ## Continue your Flutter journey {: #next-steps} Now that you've successfully installed Flutter, set up development for at least one target platform to continue your journey with Flutter. :::recommend If you don't yet have a preferred platform to target during development, the Flutter team recommends you first try out \[developing for the web]\[web-setup]! ::: \[web-setup]: /platform-integration/web/setup

Set up a target platform

* [Target the web](/platform-integration/web/setup)
* [Target Android](/platform-integration/android/setup)
* [Target iOS](/platform-integration/ios/setup)
* [Target macOS](/platform-integration/macos/setup)
* [Target Windows](/platform-integration/windows/setup)
* [Target Linux](/platform-integration/linux/setup)

Learn Flutter development

* [Learn the fundamentals](/learn/pathway)
* [Explore Flutter widgets](https://www.youtube.com/watch?v=b_sQ9bMltGU\&list=PLjxrf2q8roU23XGwz3Km7sQZFTdB996iG)

Stay up to date with Flutter

* [Update Flutter](/install/upgrade)
* [Find out what's new](/release/release-notes)
* [Subscribe on YouTube](https://www.youtube.com/@flutterdev)

----
url: https://docs.flutter.dev/ai/best-practices.md
----

\# Flutter AI best practices > Learn best practices for building AI-powered Flutter apps using guardrails to verify and correct AI-generated data. Flutter and AI go well together on multiple levels. If you're using AI to generate Flutter code, you only have to generate the code for a single app to target multiple platforms. And if you're harnessing Gemini to implement features in your app, the Firebase AI Logic SDK makes that simple, with an easy-to-use API, and secure, by keeping the API keys out of your code. If you're new to AI for either of these two use cases, you should know: as good as it is (and the Gemini 3 Pro Preview is \*very\* good), AI still makes mistakes. If you're using AI to write your code, then you can use guardrails to keep AI on track using tools like the Flutter analyzer and unit tests. But what do you do when you're using AI to implement the features in your app, knowing that sometimes it's going to get things wrong? Or, to quote a friend of mine: \*\*\*Morgan's Law\*\*\* \*"Eventually, due to the nature of sampling from a probability distribution, \[AI] will fail to do the thing that must be done."\* \*–Brett Morgan, Flutter Developer Relations Engineer, July, 2025\\.\* The good news is that, just as you can use developer tools to build guardrails around the AI writing your code, you can use Flutter to build guardrails around the AI you use to implement your features. The \[Crossword Companion app]\[crossword-app] was built to demonstrate these techniques. The goal of the Crossword Companion app is not to help you cheat at mini-crosswords – although it's darn good at that – but to illustrate how to channel the power of AI using Flutter. As an example, the first thing you do when running the app is upload the screenshot of a mini-crossword puzzle. When you press the \*\*Next\*\* button, the AI uses that image to infer the size, contents and clues of the puzzle: Notice that while the crossword puzzle is a 5x5 grid, the AI says it's 4x5. Because we know that mistakes happen (apparently AIs are only human, too), we built the app to allow the user to verify and correct the AI-generated data. That's important; bad data leads to bad results. So this write-up is not about the app in detail but rather about the best practices to use when you're building your own AI apps with Flutter. So let's get to it! \[crossword-app]: https\://github.com/flutter/demos/tree/main/crossword\_companion

----
url: https://docs.flutter.dev/ai/genui/input-events
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Input and events](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

This guide explains how user interactions are handled within the GenUI package, from the initial widget interaction to the AI agent receiving the event.

## Overview

[#](#overview)

In the GenUI architecture, the UI is driven by the AI, but user interactions (like clicking a button or submitting a form) must be communicated back to the AI agent. This allows the agent to update the UI or perform actions in response to user input.

The flow of an event is as follows:

1. Interaction: User interacts with a widget; for example, the user taps a button.
2. Capture: The widget implementation dispatches a `UiEvent`.
3. Processing: The framework adds context (such as a `surfaceId` or data model values) and forwards the event.
4. Transmission: The Flutter widget generates the event, adds the appropriate context, and routes it to the AI through the `ContentGenerator`, which forwards it to the AI agent.

## Defining events

[#](#defining-events)

### Protocol level

[#](#protocol-level)

The A2UI protocol defines an `action` message used to report events. An `action` contains:

* `name`: The name of the action (defined by the AI when generating the component).
* `surfaceId`: The ID of the UI surface where the event occurred.
* `sourceComponentId`: The ID of the component that triggered the event.
* `context`: A JSON object containing data relevant to the event.
* `timestamp`: When the event occurred.

### Dart implementation

[#](#dart-implementation)

In [`package:genui`](https://pub.dev/packages/genui), user events are represented by the `UiEvent` extension type and its concrete implementation `UserActionEvent`.

The following structures are defined in `lib/src/model/ui_models.dart`:

lib/src/model/ui\_models.dart

dart

```
/// A data object that represents a user interaction event in the UI.
extension type UiEvent.fromMap(JsonMap _json) { ... }
​
/// A UI event that represents a user action.
extension type UserActionEvent.fromMap(JsonMap _json) implements UiEvent {
  UserActionEvent({
    String? surfaceId,
    required String name,
    required String sourceComponentId,
    JsonMap? context,
    // ...
  }) : ...
}
```

## Capturing events in widgets

[#](#capturing-events-in-widgets)

Widgets in GenUI are defined in a `Catalog`, which includes information about what events the widget can send to the AI. The AI can then send information about how to communicate those events back. When you implement a custom widget (or use the standard widgets), you use the `dispatchEvent` method in `CatalogItemContext` to dispatch events.

### Example: Button implementation

[#](#button-example)

The following example shows how a `Button` widget typically captures a tap and dispatches an event. It retrieves the action definition (provided by the AI) from its properties, resolves any data bindings in the context, and sends the event.

dart

```
// Inside a CatalogItem widgetBuilder:
widgetBuilder: (itemContext) {
  // 1. Extract action data from the component properties.
  final buttonData = _ButtonData.fromMap(itemContext.data as JsonMap);
  final JsonMap actionData = buttonData.action;
  final actionName = actionData['name'] as String;
​
  // 2. Extract context definition (which data to send back).
  final List<Object?> contextDefinition =
      (actionData['context'] as List<Object?>?) ?? <Object?>[];
​
  return ElevatedButton(
    onPressed: () {
      // 3. Resolve the context values from the data model.
      final JsonMap resolvedContext = resolveContext(
        itemContext.dataContext,
        contextDefinition,
      );
​
      // 4. Dispatch the event.
      itemContext.dispatchEvent(
        UserActionEvent(
          name: actionName,
          sourceComponentId: itemContext.id,
          context: resolvedContext,
        ),
      );
    },
    child: /* ... */
  );
},
```

## Event processing pipeline

[#](#event-processing-pipeline)

Once `dispatchEvent` is called, the event travels through the GenUI core layers.

### Surface

[#](#surface)

The `Surface` widget (in `lib/src/core/surface.dart`) wraps the rendered widgets. It provides the dispatchEvent callback implementation.

When `_dispatchEvent` is called:

1. It automatically injects the `surfaceId` into the event, ensuring the AI knows which surface the interaction came from.
2. It delegates handling to the `SurfaceHost` (implemented by `SurfaceController`).

dart

```
// Surface implementation details
void _dispatchEvent(UiEvent event) {
  // ...
  final Map<String, Object?> eventMap = {
    ...event.toMap(),
    surfaceIdKey: widget.surfaceId, // Inject surfaceId
  };
  final UiEvent newEvent = UserActionEvent.fromMap(eventMap);
  widget.host.handleUiEvent(newEvent);
}
```

### SurfaceController

[#](#surfacecontroller)

The `SurfaceController` (in `lib/src/core/surface_controller.dart`) is the central hub for managing UI state.

When `handleUiEvent` is called, it does the following:

1. Verifies the event type.
2. Wraps the event in the `action` JSON envelope required by the protocol.
3. Emits a `UserUiInteractionMessage` on its `onSubmit` stream.

dart

```
// SurfaceController implementation details
@override
void handleUiEvent(UiEvent event) {
  if (event is! UserActionEvent) return;
​
  // Wrap in protocol 'action' envelope
  final String eventJsonString = jsonEncode({'action': event.toMap()});
​
  // Emit for listeners (like Conversation)
  _onSubmit.add(UserUiInteractionMessage.text(eventJsonString));
}
```

## Transmission to AI

[#](#transmission-to-ai)

The final step sends the event to the AI Agent. This is typically handled by `Conversation` (in `lib/src/facade/conversation.dart`). The `Conversation` listens to the `onSubmit` stream from the message processor.

dart

```
// Conversation constructor
_userEventSubscription = surfaceController.onSubmit.listen(sendRequest);
```

When an event is received, the `sendRequest` method:

1. Wraps the `UserUiInteractionMessage` back to the developer's client code.
2. The custom integration or predefined transport adapter forwards the message to the LLM agent network transport.

The AI Agent receives this JSON message, processes the user action, and might stream back new `surfaceUpdate` or `dataModelUpdate` messages to modify the UI, or some other action, completing the full interaction loop.

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-08-19. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/ai/genui/input-events.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/ai/genui/input-events\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/ai/genui/input-events.md "Report an issue with this page").

----
url: https://docs.flutter.dev/ui/assets/asset-transformation
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Transforming assets at build time](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

You can configure your project to automatically transform assets at build time using compatible Dart packages.

## Specifying asset transformations

[#](#specifying-asset-transformations)

In the `pubspec.yaml` file, list the assets to be transformed and the associated transformer package.

yaml

```
flutter:
  assets:
    - path: assets/logo.svg
      transformers:
        - package: vector_graphics_compiler
```

With this configuration, `assets/logo.svg` is transformed by the [`vector_graphics_compiler`](https://pub.dev/packages/vector_graphics_compiler) package as it is copied to the build output. This package precompiles SVG files into an optimized binary files that can be displayed using the [`vector_graphics`](https://pub.dev/packages/vector_graphics) package, like so:

dart

```
import 'package:vector_graphics/vector_graphics.dart';
​
const Widget logo = VectorGraphic(loader: AssetBytesLoader('assets/logo.svg'));
```

### Passing arguments to asset transformers

[#](#passing-arguments-to-asset-transformers)

To pass a string of arguments to an asset transformer, also specify that in the pubspec:

yaml

```
flutter:
  assets:
    - path: assets/logo.svg
      transformers:
        - package: vector_graphics_compiler
          args: ['--tessellate', '--font-size=14']
```

### Chaining asset transformers

[#](#chaining-asset-transformers)

Asset transformers can be chained and are applied in the order they are declared. Consider the following example using imaginary packages:

yaml

```
flutter:
  assets:
    - path: assets/bird.png
      transformers:
        - package: grayscale_filter
        - package: png_optimizer
```

Here, `bird.png` is transformed by the `grayscale_filter` package. The output is then transformed by the `png_optimizer` package before being bundled into the built app.

## Writing asset transformer packages

[#](#writing-asset-transformer-packages)

An asset transformer is a Dart [command-line app](https://dart.dev/tutorials/server/cmdline) that is invoked with `dart run` with at least two arguments: `--input`, which contains the path to the file to transform and `--output`, which is the location where the transformer code must write its output to.

If the transformer finishes with a non-zero exit code, the application build fails with an error message explaining that transformation of the asset failed. Anything written to the [`stderr`](https://api.flutter.dev/flutter/dart-io/Process/stderr.html) stream of the process by the transformer is included in the error message.

During the invocation of the transformer, the `FLUTTER_BUILD_MODE` environment variable will be set to the CLI name of the build mode being used. For example, if you run your app with `flutter run -d macos --release`, then `FLUTTER_BUILD_MODE` will be set to `release`.

## Sample

[#](#sample)

For a sample Flutter project that uses asset transformation and includes a custom Dart package that is used as a transformer, check out the [asset\_transformers project in the Flutter samples repo](https://github.com/flutter/samples/tree/main/asset_transformation).

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-11. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/ui/assets/asset-transformation.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/ui/assets/asset-transformation\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/ui/assets/asset-transformation.md "Report an issue with this page").

----
url: https://docs.flutter.dev/ai/tools
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top How Flutter AI tools work](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

Flutter and Dart provide an integrated suite of tools designed to help AI coding assistants understand your codebase, follow best practices, and execute development tasks accurately.

## Tooling ecosystem overview

[#](#tooling-ecosystem-overview)

The Flutter AI tooling ecosystem consists of six complementary components:

1. **Agent skills**: task-oriented blueprints that teach assistants how to perform specific workflows (such as creating responsive layouts or writing tests).
2. **Dart and Flutter MCP server**: a Model Context Protocol (MCP) server exposing real-time SDK diagnostics, symbol resolution, and runtime introspection to assistants.
3. **Developer Knowledge MCP server**: a cloud-hosted documentation search server giving assistants direct access to official Flutter and Dart docs.
4. **Package skills**: skills published directly inside third-party `pub.dev` packages, giving assistants instant expertise on specific libraries.
5. **Specialized agents**: dedicated agent personas equipped with focused tools and system instructions for isolated workflows, such as accessibility auditing.
6. **Agent rules**: persistent rules added to the context window every time you start a new session with your assistant (such as instructing the assistant to proactively trigger hot reload when modifying Flutter widgets).

***

## Agent skills

[#](#agent-skills)

[Agent skills](https://agentskills.io/) provide a standardized way to equip AI assistants with procedural domain expertise.

### Progressive disclosure

[#](#progressive-disclosure)

Skills use **progressive disclosure** to conserve model context:

* **Discovery phase**: The assistant initially reads only skill metadata (name and short description) from the `.agents/skills` directory.
* **Execution phase**: When the assistant determines a skill is relevant to your prompt, it loads the complete instructions (`SKILL.md`) and supporting reference scripts.

### Official skills repositories

[#](#official-skills-repositories)

* [**flutter/agent-plugins**](https://github.com/flutter/agent-plugins): Provides skills for Flutter development, including widget construction, declarative navigation, responsive design, and state management.
* [**dart-lang/skills**](https://github.com/dart-lang/skills): Provides skills for Dart development, including unit test authoring, dependency resolution, and static analysis remediation.

***

## Dart and Flutter MCP server

[#](#dart-and-flutter-mcp-server)

The [Dart and Flutter MCP server](https://github.com/dart-lang/ai/tree/main/pkgs/dart_mcp_server) implements the open Model Context Protocol (MCP), connecting AI assistants to live Dart SDK and Flutter tooling over standard I/O (stdio).

Key capabilities provided by the MCP server include:

* **Static analysis & diagnostics**: Inspect and fix analyzer errors and warnings across your project.
* **Symbol resolution**: Resolve symbols to their definitions, signatures, and documentation.
* **Runtime introspection**: Query the state of a running Flutter application during active debugging sessions.
* **Package management**: Search [pub.dev](https://pub.dev) and manage dependencies in `pubspec.yaml`.
* **Testing & formatting**: Execute unit and widget tests and format code using `dart format` rules.

***

## Developer Knowledge MCP server

[#](#developer-knowledge-mcp-server)

While the local Dart and Flutter MCP server focuses on local codebase analysis and runtime debugging, you can also equip your assistant with search access to official online documentation.

The [Developer Knowledge MCP server](https://developers.google.com/knowledge/mcp) connects AI assistants to Google's developer documentation corpus, including [docs.flutter.dev](https://docs.flutter.dev), [dart.dev](https://dart.dev), and official API references. This allows assistants to retrieve up-to-date guides, migration notes, and API references directly during conversation.

***

## Package skills

[#](#package-skills)

In addition to core Flutter skills, library authors can bundle official skills directly inside their packages on [pub.dev](https://pub.dev).

When you add a dependency to your project, you can discover and install its skills using the `skills` CLI:

bash

```
dart run skills@ get
```

To learn how to discover and install package skills, check out [Package skills](https://dart.dev/ai/package-skills). To bundle skills with your own package, check out [Ship skills with packages](https://dart.dev/tools/pub/package-skills).

***

## Specialized agents

[#](#specialized-agents)

**Specialized agents** (referred to as custom agents in platforms like Antigravity) are AI assistants configured with dedicated system prompts, curated tool sets, and focused skill subsets.

While agent skills guide general-purpose assistants during active coding, specialized agents run in isolated contexts to perform deep, single-purpose workflows without cluttering your primary conversation history.

### Flutter Accessibility agent

[#](#flutter-accessibility-agent)

The **Flutter Accessibility (`a11y`) agent** is an official specialized agent designed to audit Flutter widget trees and recommend accessibility improvements. The accessibility agent is currently available in [Google Antigravity](https://antigravity.google).

Key capabilities of the accessibility agent include:

* **Semantic label inspection**: identifies interactive widgets (such as `IconButton` or `InkWell`) that lack descriptive semantic labels.
* **Touch target validation**: flags interactive elements with hit test areas smaller than the recommended 48x48 logical pixels.
* **Contrast and visual checks**: highlights potential contrast ratio issues and missing focus indicators.
* **Automated code fixes**: generates idiomatic Flutter fixes (such as wrapping widgets in `Semantics` or adjusting tap bounds) that you can review and accept.

### Using the accessibility agent in Antigravity

[#](#using-the-accessibility-agent-in-antigravity)

After installing the official [Dart and Flutter plugin](https://antigravity.google/docs/build-with-google/#dart-and-flutter) in Antigravity, you can invoke the accessibility agent directly when chatting with Antigravity by selecting the **Flutter Accessibility Agent** from the agent picker, mentioning `@flutter_a11y_agent`, or asking your assistant to review your code for accessibility:

```
@flutter_a11y_agent Audit this screen for accessibility issues.
```

***

## Agent rules

[#](#agent-rules)

**Agent rules** are persistent rules added to the context window every time you start a new session with your assistant. Rules establish project conventions and common constraints across your codebase.

To explore official Flutter rules, visit the [rules directory](https://github.com/flutter/agent-plugins/tree/main/rules) in the flutter/agent-plugins repository.

***

## When to use what

[#](#when-to-use-what)

Use the following mental model to understand how different AI mechanisms interact:

| Mechanism                   | Scope and behavior | Best suited for                  |
| :-------------------------- | :----------------- | :------------------------------- |
| **Agent skill**             | On demand          | Multi-step recipes, patterns     |
| **Dart MCP server**         | Local SDK tools    | Diagnostics, tests, runtime      |
| **Developer Knowledge MCP** | Online search      | Live docs, API references        |
| **Package skill**           | From dependencies  | Library APIs, code generation    |
| **Specialized agent**       | Isolated subagent  | Accessibility audits             |
| **Agent rule**              | Persistent context | Project conventions, constraints |

## Next steps

[#](#next-steps)

* Follow the [Get started with AI](/ai/get-started) guide to install official plugins in your editor.
* Learn how to install skills with [Package skills](https://dart.dev/ai/package-skills).
* Learn how to distribute skills with [Ship skills with packages](https://dart.dev/tools/pub/package-skills).

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-09-14. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/ai/tools.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/ai/tools\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/ai/tools.md "Report an issue with this page").

----
url: https://docs.flutter.dev/perf/impeller
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Impeller rendering engine](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

## What is Impeller?

[#](#what-is-impeller)

Impeller provides a new rendering runtime for Flutter. Impeller precompiles a [smaller, simpler set of shaders](https://github.com/flutter/flutter/issues/77412) at engine-build time so they don't compile at runtime.

For a video introduction to Impeller, check out the following talk from Google I/O 2023.

[Watch on YouTube in a new tab: "Introducing Impeller, Flutter's new rendering engine"](https://www.youtube.com/watch/vd5NqS01rlA)

Impeller has the following objectives:

* **Predictable performance**: Impeller compiles all shaders and reflection offline at build time. It builds all pipeline state objects upfront. The engine controls caching and caches explicitly.
* **Instrumentable**: Impeller tags and labels all graphics resources, such as textures and buffers. It can capture and persist animations to disk without affecting per-frame rendering performance.
* **Portable**: Flutter doesn't tie Impeller to a specific client-rendering API. You can author shaders once and convert them to backend-specific formats, as necessary.
* **Leverages modern graphics APIs**: Impeller uses, but doesn't depend on, features available in modern APIs like Metal and Vulkan.
* **Leverages concurrency**: Impeller can distribute single-frame workloads across multiple threads, if necessary.

## Availability

[#](#availability)

Where can you use Impeller? For *detailed* info, check out the [Can I use Impeller?](https://flutter.dev/go/can-i-use-impeller) page.

### iOS

[#](#ios)

Impeller is the **only supported** rendering engine on iOS with no ability to switch to Skia.

### Android

[#](#android)

Impeller is **available and enabled by default on Android API 29+**. On devices running lower versions of Android or don't support Vulkan, Impeller falls back to the legacy OpenGL renderer. No action on your part is necessary for this fallback behavior.

* To *disable* Impeller when debugging, pass `--no-enable-impeller` to the `flutter run` command.

  ```
  flutter run --no-enable-impeller
  ```

* To *disable* Impeller when deploying your app, add the following setting to your project's `AndroidManifest.xml` file under the `<application>` tag:

xml

```
<meta-data
    android:name="io.flutter.embedding.android.EnableImpeller"
    android:value="false" />
```

### Web

[#](#web)

Flutter on the web currently uses Skia for rendering. It might use Impeller in the future.

### macOS

[#](#macos)

Impeller is **available and enabled by default as of Flutter 3.47**. In a future release, the ability to opt out of using Impeller will be removed.

To disable Impeller on macOS when debugging, pass `--no-enable-impeller` to the `flutter run` command:

```
flutter run --no-enable-impeller
```

To disable Impeller on macOS when deploying your app, add the following tags under the top-level `<dict>` tag in your app's `Info.plist` file:

xml

```
  <key>FLTEnableImpeller</key>
  <false />
```

### Linux

[#](#linux)

Impeller is **available and enabled by default as of Flutter 3.47**. In a future release, the ability to opt out of using Impeller will be removed.

To disable Impeller on Linux when debugging, pass `--no-enable-impeller` to the `flutter run` command:

```
flutter run --no-enable-impeller
```

To disable Impeller on Linux when deploying your app, add the following setup to your project in `linux/runner/my_application.cc`:

c

```
g_autoptr(FlDartProject) project = fl_dart_project_new();
fl_dart_project_set_enable_impeller(project, FALSE);
```

### Windows

[#](#windows)

Impeller is **available and enabled by default as of Flutter 3.47**. In a future release, the ability to opt out of using Impeller will be removed.

To disable Impeller on Windows when debugging, pass `--no-enable-impeller` to the `flutter run` command.

```
flutter run --no-enable-impeller
```

To disable Impeller on Windows when deploying your app, add the following setup to your project in `windows\runner\main.cpp`:

cpp

```
flutter::DartProject project(L"data");
project.set_impeller_switch(flutter::ImpellerSwitch::Disabled);
```

### Bugs and issues

[#](#bugs-and-issues)

The team continues to improve Impeller support. If you encounter performance or fidelity issues with Impeller on any platform, file an issue in the [GitHub tracker](https://github.com/flutter/flutter/issues/new/choose). Prefix the issue title with `[Impeller]` and include a small reproducible test case.

Please include the following information when submitting an issue for Impeller:

* The device you are running on, including the chip information.
* Screenshots or recordings of any visible issues.
* An [export of the performance trace](/tools/devtools/performance#import-and-export). Zip the file and attach it to the GitHub issue.

## Architecture

[#](#architecture)

To learn more details about Impeller's design and architecture, check out the [README.md](https://github.com/flutter/flutter/blob/main/engine/src/flutter/impeller/README.md) file in the source tree.

## Additional information

[#](#additional-information)

* [Impeller anti-aliasing](/perf/antialiasing)
* [Frequently asked questions](https://github.com/flutter/flutter/blob/main/docs/engine/impeller/docs/faq.md)
* [Impeller's coordinate system](https://github.com/flutter/flutter/blob/main/docs/engine/impeller/docs/coordinate_system.md)
* [How to set up Xcode for GPU frame captures with metal](https://github.com/flutter/flutter/blob/main/docs/engine/impeller/docs/xcode_frame_capture.md)
* [Learning to read GPU frame captures](https://github.com/flutter/flutter/blob/main/docs/engine/impeller/docs/read_frame_captures.md)
* [How to enable metal validation for command line apps](https://github.com/flutter/flutter/blob/main/docs/engine/impeller/docs/metal_validation.md)
* [How Impeller works around the lack of uniform buffers in Open GL ES 2.0](https://github.com/flutter/flutter/blob/main/docs/engine/impeller/docs/ubo_gles2.md)
* [Guidance for writing efficient shaders](https://github.com/flutter/flutter/blob/main/docs/engine/impeller/docs/shader_optimization.md)
* [How color blending works in Impeller](https://github.com/flutter/flutter/blob/main/docs/engine/impeller/docs/blending.md)

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-08-21. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/perf/impeller.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/perf/impeller\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/perf/impeller.md "Report an issue with this page").

----
url: https://docs.flutter.dev/add-to-app/android/add-flutter-screen.md
----

\# Add a Flutter screen to an Android app > Learn how to add a single Flutter screen to your existing Android app. This guide describes how to add a single Flutter screen to an existing Android app. A Flutter screen can be added as a normal, opaque screen, or as a see-through, translucent screen. Both options are described in this guide. ## Add a normal Flutter screen ### Step 1: Add FlutterActivity to AndroidManifest.xml Flutter provides \[\`FlutterActivity\`]\[] to display a Flutter experience within an Android app. Like any other \[\`Activity\`]\[], \`FlutterActivity\` must be registered in your \`AndroidManifest.xml\`. Add the following XML to your \`AndroidManifest.xml\` file under your \`application\` tag: \`\`\`xml \`\`\` The reference to \`@style/LaunchTheme\` can be replaced by any Android theme that want to apply to your \`FlutterActivity\`. The choice of theme dictates the colors applied to Android's system chrome, like Android's navigation bar, and to the background color of the \`FlutterActivity\` just before the Flutter UI renders itself for the first time. ### Step 2: Launch FlutterActivity With \`FlutterActivity\` registered in your manifest file, add code to launch \`FlutterActivity\` from whatever point in your app that you'd like. The following example shows \`FlutterActivity\` being launched from an \`OnClickListener\`. :::note Make sure to use the following import: \`\`\`java import io.flutter.embedding.android.FlutterActivity; \`\`\` ::: \`\`\`kotlin title="ExistingActivity.kt" MyButton(onClick = { startActivity( FlutterActivity.createDefaultIntent(this) ) }) @Composable fun MyButton(onClick: () -> Unit) { Button(onClick = onClick) { Text("Launch Flutter!") } } \`\`\` \`\`\`kotlin title="ExistingActivity.kt" myButton.setOnClickListener { startActivity( FlutterActivity.createDefaultIntent(this) ) } \`\`\` \`\`\`java title="ExistingActivity.java" myButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { startActivity( FlutterActivity.createDefaultIntent(currentActivity) ); } }); \`\`\` The previous example assumes that your Dart entrypoint is called \`main()\`, and your initial Flutter route is '/'. The Dart entrypoint can't be changed using \`Intent\`, but the initial route can be changed using \`Intent\`. The following example demonstrates how to launch a \`FlutterActivity\` that initially renders a custom route in Flutter. \`\`\`kotlin title="ExistingActivity.kt" MyButton(onClick = { startActivity( FlutterActivity .withNewEngine() .initialRoute("/my\_route") .build(this) ) }) @Composable fun MyButton(onClick: () -> Unit) { Button(onClick = onClick) { Text("Launch Flutter!") } } \`\`\` \`\`\`kotlin title="ExistingActivity.kt" myButton.setOnClickListener { startActivity( FlutterActivity .withNewEngine() .initialRoute("/my\_route") .build(this) ) } \`\`\` \`\`\`java title="ExistingActivity.java" myButton.addOnClickListener(new OnClickListener() { @Override public void onClick(View v) { startActivity( FlutterActivity .withNewEngine() .initialRoute("/my\_route") .build(currentActivity) ); } }); \`\`\` Replace \`"/my\_route"\` with your desired initial route. The use of the \`withNewEngine()\` factory method configures a \`FlutterActivity\` that internally create its own \[\`FlutterEngine\`]\[] instance. This comes with a non-trivial initialization time. The alternative approach is to instruct \`FlutterActivity\` to use a pre-warmed, cached \`FlutterEngine\`, which minimizes Flutter's initialization time. That approach is discussed next. ### Step 3: (Optional) Use a cached FlutterEngine Every \`FlutterActivity\` creates its own \`FlutterEngine\` by default. Each \`FlutterEngine\` has a non-trivial warm-up time. This means that launching a standard \`FlutterActivity\` comes with a brief delay before your Flutter experience becomes visible. To minimize this delay, you can warm up a \`FlutterEngine\` before arriving at your \`FlutterActivity\`, and then you can use your pre-warmed \`FlutterEngine\` instead. To pre-warm a \`FlutterEngine\`, find a reasonable location in your app to instantiate a \`FlutterEngine\`. The following example arbitrarily pre-warms a \`FlutterEngine\` in the \`Application\` class: \`\`\`kotlin title="MyApplication.kt" class MyApplication : Application() { lateinit var flutterEngine : FlutterEngine override fun onCreate() { super.onCreate() // Instantiate a FlutterEngine. flutterEngine = FlutterEngine(this) // Start executing Dart code to pre-warm the FlutterEngine. flutterEngine.dartExecutor.executeDartEntrypoint( DartExecutor.DartEntrypoint.createDefault() ) // Cache the FlutterEngine to be used by FlutterActivity. FlutterEngineCache .getInstance() .put("my\_engine\_id", flutterEngine) } } \`\`\` \`\`\`java title="MyApplication.java" public class MyApplication extends Application { public FlutterEngine flutterEngine; @Override public void onCreate() { super.onCreate(); // Instantiate a FlutterEngine. flutterEngine = new FlutterEngine(this); // Start executing Dart code to pre-warm the FlutterEngine. flutterEngine.getDartExecutor().executeDartEntrypoint( DartEntrypoint.createDefault() ); // Cache the FlutterEngine to be used by FlutterActivity. FlutterEngineCache .getInstance() .put("my\_engine\_id", flutterEngine); } } \`\`\` The ID passed to the \[\`FlutterEngineCache\`]\[] can be whatever you want. Make sure that you pass the same ID to any \`FlutterActivity\` or \[\`FlutterFragment\`]\[] that should use the cached \`FlutterEngine\`. Using \`FlutterActivity\` with a cached \`FlutterEngine\` is discussed next. :::note To warm up a \`FlutterEngine\`, you must execute a Dart entrypoint. Keep in mind that the moment \`executeDartEntrypoint()\` is invoked, your Dart entrypoint method begins executing. If your Dart entrypoint invokes \`runApp()\` to run a Flutter app, then your Flutter app behaves as if it were running in a window of zero size until this \`FlutterEngine\` is attached to a \`FlutterActivity\`, \`FlutterFragment\`, or \`FlutterView\`. Make sure that your app behaves appropriately between the time you warm it up and the time you display Flutter content. ::: With a pre-warmed, cached \`FlutterEngine\`, you now need to instruct your \`FlutterActivity\` to use the cached \`FlutterEngine\` instead of creating a new one. To accomplish this, use \`FlutterActivity\`'s \`withCachedEngine()\` builder: \`\`\`kotlin title="ExistingActivity.kt" myButton.setOnClickListener { startActivity( FlutterActivity .withCachedEngine("my\_engine\_id") .build(this) ) } \`\`\` \`\`\`java title="ExistingActivity.java" myButton.addOnClickListener(new OnClickListener() { @Override public void onClick(View v) { startActivity( FlutterActivity .withCachedEngine("my\_engine\_id") .build(currentActivity) ); } }); \`\`\` When using the \`withCachedEngine()\` factory method, pass the same ID that you used when caching the desired \`FlutterEngine\`. Now, when you launch \`FlutterActivity\`, there is significantly less delay in the display of Flutter content. :::note When using a cached \`FlutterEngine\`, that \`FlutterEngine\` outlives any \`FlutterActivity\` or \`FlutterFragment\` that displays it. Keep in mind that Dart code begins executing as soon as you pre-warm the \`FlutterEngine\`, and continues executing after the destruction of your \`FlutterActivity\`/\`FlutterFragment\`. To stop executing and clear resources, obtain your \`FlutterEngine\` from the \`FlutterEngineCache\` and destroy the \`FlutterEngine\` with \`FlutterEngine.destroy()\`. ::: :::note Runtime performance isn't the only reason that you might pre-warm and cache a \`FlutterEngine\`. A pre-warmed \`FlutterEngine\` executes Dart code independent from a \`FlutterActivity\`, which allows such a \`FlutterEngine\` to be used to execute arbitrary Dart code at any moment. Non-UI application logic can be executed in a \`FlutterEngine\`, like networking and data caching, and in background behavior within a \`Service\` or elsewhere. When using a \`FlutterEngine\` to execute behavior in the background, be sure to adhere to all Android restrictions on background execution. ::: :::note Flutter's debug/release builds have drastically different performance characteristics. To evaluate the performance of Flutter, use a release build. ::: #### Initial route with a cached engine The concept of an initial route is available when configuring a \`FlutterActivity\` or a \`FlutterFragment\` with a new \`FlutterEngine\`. However, \`FlutterActivity\` and \`FlutterFragment\` don't offer the concept of an initial route when using a cached engine. This is because a cached engine is expected to already be running Dart code, which means it's too late to configure the initial route. Developers that would like their cached engine to begin with a custom initial route can configure their cached \`FlutterEngine\` to use a custom initial route just before executing the Dart entrypoint. The following example demonstrates the use of an initial route with a cached engine: \`\`\`kotlin title="MyApplication.kt" class MyApplication : Application() { lateinit var flutterEngine : FlutterEngine override fun onCreate() { super.onCreate() // Instantiate a FlutterEngine. flutterEngine = FlutterEngine(this) // Configure an initial route. flutterEngine.navigationChannel.setInitialRoute("your/route/here"); // Start executing Dart code to pre-warm the FlutterEngine. flutterEngine.dartExecutor.executeDartEntrypoint( DartExecutor.DartEntrypoint.createDefault() ) // Cache the FlutterEngine to be used by FlutterActivity or FlutterFragment. FlutterEngineCache .getInstance() .put("my\_engine\_id", flutterEngine) } } \`\`\` \`\`\`java title="MyApplication.java" public class MyApplication extends Application { @Override public void onCreate() { super.onCreate(); // Instantiate a FlutterEngine. flutterEngine = new FlutterEngine(this); // Configure an initial route. flutterEngine.getNavigationChannel().setInitialRoute("your/route/here"); // Start executing Dart code to pre-warm the FlutterEngine. flutterEngine.getDartExecutor().executeDartEntrypoint( DartEntrypoint.createDefault() ); // Cache the FlutterEngine to be used by FlutterActivity or FlutterFragment. FlutterEngineCache .getInstance() .put("my\_engine\_id", flutterEngine); } } \`\`\` By setting the initial route of the navigation channel, the associated \`FlutterEngine\` displays the desired route upon initial execution of the \`runApp()\` Dart function. Changing the initial route property of the navigation channel after the initial execution of \`runApp()\` has no effect. Developers who would like to use the same \`FlutterEngine\` between different \`Activity\`s and \`Fragment\`s and switch the route between those displays need to set up a method channel and explicitly instruct their Dart code to change \`Navigator\` routes. ## Add a translucent Flutter screen Most full-screen Flutter experiences are opaque. However, some apps would like to deploy a Flutter screen that looks like a modal, for example, a dialog or bottom sheet. Flutter supports translucent \`FlutterActivity\`s out of the box. To make your \`FlutterActivity\` translucent, make the following changes to the regular process of creating and launching a \`FlutterActivity\`. ### Step 1: Use a theme with translucency Android requires a special theme property for \`Activity\`s that render with a translucent background. Create or update an Android theme with the following property: \`\`\`xml \`\`\` Then, apply the translucent theme to your \`FlutterActivity\`. \`\`\`xml \`\`\` Your \`FlutterActivity\` now supports translucency. Next, you need to launch your \`FlutterActivity\` with explicit transparency support. ### Step 2: Start FlutterActivity with transparency To launch your \`FlutterActivity\` with a transparent background, pass the appropriate \`BackgroundMode\` to the \`IntentBuilder\`: \`\`\`kotlin title="ExistingActivity.kt" // Using a new FlutterEngine. startActivity( FlutterActivity .withNewEngine() .backgroundMode(FlutterActivityLaunchConfigs.BackgroundMode.transparent) .build(this) ); // Using a cached FlutterEngine. startActivity( FlutterActivity .withCachedEngine("my\_engine\_id") .backgroundMode(FlutterActivityLaunchConfigs.BackgroundMode.transparent) .build(this) ); \`\`\` \`\`\`java title="ExistingActivity.java" // Using a new FlutterEngine. startActivity( FlutterActivity .withNewEngine() .backgroundMode(FlutterActivityLaunchConfigs.BackgroundMode.transparent) .build(context) ); // Using a cached FlutterEngine. startActivity( FlutterActivity .withCachedEngine("my\_engine\_id") .backgroundMode(FlutterActivityLaunchConfigs.BackgroundMode.transparent) .build(context) ); \`\`\` You now have a \`FlutterActivity\` with a transparent background. :::note Make sure that your Flutter content also includes a translucent background. If your Flutter UI paints a solid background color, then it still appears as though your \`FlutterActivity\` has an opaque background. ::: \[\`FlutterActivity\`]: https\://api.flutter.dev/javadoc/io/flutter/embedding/android/FlutterActivity.html \[\`Activity\`]: https\://developer.android.com/reference/android/app/Activity \[\`FlutterEngine\`]: https\://api.flutter.dev/javadoc/io/flutter/embedding/engine/FlutterEngine.html \[\`FlutterEngineCache\`]: https\://api.flutter.dev/javadoc/io/flutter/embedding/engine/FlutterEngineCache.html \[\`FlutterFragment\`]: https\://api.flutter.dev/javadoc/io/flutter/embedding/android/FlutterFragment.html

----
url: https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.17.0.md
----

\# DevTools 2.17.0 release notes > Release notes for Dart and Flutter DevTools version 2.17.0. The 2.17.0 release of the Dart and Flutter DevTools includes the following changes among other general improvements. To learn more about DevTools, check out the \[DevTools overview]\(https\://docs.flutter.dev/tools/devtools). ## Inspector updates \* Added support for manually setting the package directories for your app. If you've ever loaded the Inspector and noticed that some of your widgets aren't present in the widget tree, this might indicate that the package directories for your app haven't been set or detected properly. Your package directories determine which widgets the Inspector considers to be from \_your\_ application. If you see an empty Inspector widget tree, or if you develop widgets across multiple packages, and want widgets from all these locations to show up in your tree, check the \*\*Inspector Settings\*\* dialog to ensure that your package directories are properly configured - \[#4306]\(https\://github.com/flutter/devtools/pull/4306) !\[frame\_analysis]\(/assets/images/docs/tools/devtools/release-notes/images-2.17.0/package\_directories.png "package directories") ## Performance updates \* Added a \*\*Frame Analysis\*\* tab to the Performance page. When analyzing a janky Flutter frame, this view provides hints for how to diagnose the jank and detects expensive operations that might have contributed to the slow frame time. This view also shows a breakdown of your Flutter frame time per phase (\*\*Build\*\*, \*\*Layout\*\*, \*\*Paint\*\*, and \*\*Raster\*\*) to try to guide you in the right direction - \[#4339]\(https\://github.com/flutter/devtools/pull/4339) !\[frame\_analysis]\(/assets/images/docs/tools/devtools/release-notes/images-2.17.0/frame\_analysis.png "frame analysis") ## Full commit history To find a complete list of changes since the previous release, check out \[the diff on GitHub]\(https\://github.com/flutter/devtools/compare/v2.16.0...v2.17.0).

----
url: https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.37.2.md
----

\# DevTools 2.37.2 release notes > Release notes for Dart and Flutter DevTools version 2.37.2. The 2.37.2 release of the Dart and Flutter DevTools includes the following changes among other general improvements. To learn more about DevTools, check out the \[DevTools overview]\(/tools/devtools/overview). ## General updates \* Improved messaging when a screen is unavailable for the platform of the connected app. - \[#7958]\(https\://github.com/flutter/devtools/pull/7958) \* Fixed a bug where an infinite spinner was shown upon app disconnect. - \[#7992]\(https\://github.com/flutter/devtools/pull/7992) \* Fixed a bug where trying to reuse a disconnected DevTools instance would fail. - \[#8009]\(https\://github.com/flutter/devtools/pull/8009) ## Performance updates \* Removed the "Raster Stats" feature. This tool did not work for the Impeller rendering engine, and the information it gave for the SKIA rendering engine was often misleading and unactionable. Users should follow the official Flutter guidance for \[Performance and optimization]\(/perf) when debugging the rendering performance of their Flutter apps. - \[#7981]\(https\://github.com/flutter/devtools/pull/7981). ## Network profiler updates \* Fixed an issue where socket statistics were being reported as web sockets. - \[#8061]\(https\://github.com/flutter/devtools/pull/8061) !\[Network profiler correctly displaying socket statistics]\(/assets/images/docs/tools/devtools/release-notes/images-2.37.2/socket-profiling.png "Network profiler correctly displaying socket statistics") \* Added query parameters to the request details view. - \[#7825]\(https\://github.com/flutter/devtools/pull/7825) ## VS Code Sidebar updates \* Added buttons for all DevTools tools in the sidebar by default, even when there are no debug sessions available. - \[#7947]\(https\://github.com/flutter/devtools/pull/7947) !\[DevTools tools in the sidebar]\(/assets/images/docs/tools/devtools/release-notes/images-2.37.2/devtools\_in\_sidebar.png) ## Full commit history To find a complete list of changes in this release, check out the \[DevTools git log]\(https\://github.com/flutter/devtools/tree/v2.37.0).

----
url: https://docs.flutter.dev/cookbook/testing/integration/introduction.md
----

\# Integration testing concepts > Learn about integration testing in Flutter.

<!--?code-excerpt path-base="cookbook/testing/integration/introduction/"?-->

Unit tests and widget tests validate individual classes, functions, or widgets. They don't validate how individual pieces work together in whole or capture the performance of an app running on a real device. To perform these tasks, use \*integration tests\*. Integration tests verify the behavior of the complete app. This test can also be called end-to-end testing or GUI testing. ## Testing frameworks Two packages are commonly used to perform Flutter integration tests. These are: - \[integration\_test]\[integration\_test] package: The official integration test package that is part of the Flutter SDK. Tests written with \`integration\_test\` can perform the following tasks: run on the target device, run from the host machine with \`flutter test integration\_test\`, and use \`flutter\_test\` APIs. This makes integration tests similar to writing \[widget tests]\[]. However, \`integration\_test\` can't interact with native platform UI. - \[patrol]\[] package: A popular third-party integration test package that has many of the features supported by the \`integration\_test\` package, but can additionally interact with native platform UI such as permission dialogs, notifications, or the contents of platform views. ## Terminology \*\*host machine\*\* : The system on which you develop your app, like a desktop computer. \*\*target device\*\* : The mobile device, browser, or desktop application that runs your Flutter app. If you run your app in a web browser or as a desktop application, the host machine and the target device are the same. ## Getting started To use \`integration\_test\`, add it as a dependency for your Flutter app test file. To migrate existing projects that use \`flutter\_driver\`, consult the \[Migrating from flutter\_driver]\[] guide. To use \`patrol\`, follow the \[Patrol setup guide]\[]. ## Use cases for integration testing The other guides in this section explain how to use integration tests to validate \[functionality]\[] and \[performance]\[]. \[functionality]: /testing/integration-tests/ \[integration\_test]: https\://github.com/flutter/flutter/tree/main/packages/integration\_test \[Migrating from flutter\_driver]: /release/breaking-changes/flutter-driver-migration \[patrol]: https\://pub.dev/packages/patrol \[Patrol setup guide]: https\://patrol.leancode.co/getting-started \[performance]: /cookbook/testing/integration/profiling/ \[widget tests]: /testing/overview#widget-tests

----
url: https://docs.flutter.dev/reference/create-new-app.md
----

\# Create a new Flutter app > Learn how to bootstrap a new Flutter application from your command-line, different editors, and even in the cloud. This page provides step-by-step instructions on how to bootstrap a new Flutter app in your preferred development environment. To create a new Flutter app, first \[set up Flutter]\[flutter-setup], then choose your preferred environment and follow the corresponding instructions.

[Add to app](/add-to-app#get-started)

[Create a new Flutter module to embed in an existing app.](/add-to-app#get-started)

\## VS Code To create a Flutter app with \[VS Code]\[] and other Code OSS-based editors, you first need to \[install Flutter]\[flutter-setup] and \[set up VS Code]\[vscode-setup] for Flutter development. Then follow these steps: 1.

### Launch VS Code

Open VS Code or your preferred Code OSS-based editor. 1.

### Open the command palette

Go to \*\*View\*\* > \*\*Command Palette\*\* or press `Cmd/Ctrl` + `Shift` + `P`. 1.

### Find the Flutter commands

In the command palette, start typing \`flutter:\`. VS Code should surface commands from the Flutter plugin. 1.

### Run the new project command

Select the \*\*Flutter: New Project\*\* command. Your OS or VS Code might ask for access to your documents, agree to continue to the next step. 1.

### Choose a template

VS Code should prompt you with \*\*Which Flutter template?\*\*. Depending on what type of Flutter project you want to create, choose the corresponding template. For a new Flutter app, choose \*\*Application\*\*. 1.

### Select a project location

A file dialog should appear. Select or create the parent directory where you want the project to be created. Don't create the project folder itself, the Flutter tool does so. To confirm your selection, click \*\*Select a folder to create the project in\*\*. 1.

### Enter a project name

VS Code should prompt you to enter a name for your new project. Enter a name for your app that follows the \`lowercase\_with\_underscores\` naming convention, following the \[Effective Dart]\[package-name] guidelines. To confirm your selection, press `Enter`. 1.

### Wait for project initialization

Based on the information you entered, VS Code uses \`flutter create\` to bootstrap your app. Progress is often surfaced as a notification in the bottom right and can also be accessed from the \*\*Output\*\* panel. 1.

### Run your app

Your new app should now be created and open in VS Code. To try your new app, follow the steps to \[run and debug]\[vscode-run] in VS Code. {:.steps} You've successfully created a new Flutter app in VS Code! If you need more help with developing Flutter in VS Code, check out the \[VS Code for Flutter reference]\[vscode-more]. \[VS Code]: https\://code.visualstudio.com/ \[vscode-setup]: /tools/vs-code#installation-and-setup \[vscode-run]: /tools/vs-code#running-and-debugging \[vscode-more]: /tools/vs-code ## Android Studio To create a Flutter app with Android Studio, you first need to \[install Flutter]\[flutter-setup] and \[set up Android Studio]\[as-setup] for Flutter development. Then follow these steps: 1.

### Launch Android Studio

Open Android Studio with the Dart and Flutter plugins installed. 1.

### Begin project creation

If you're on the IDE welcome dialog that says \*\*Welcome to Android Studio\*\*, find and click the \*\*New Flutter Project\*\* button in the center. If you already have a project open, either close it or go to \*\*File\*\* > \*\*New\*\* > \*\*New Flutter Project...\*\*. 1.

### Choose a project type

In the \*\*New Project\*\* dialog, under \*\*Generators\*\* in the left panel, select \*\*Flutter\*\*. 1.

### Verify Flutter SDK setup

At the top of the right panel, ensure the \*\*Flutter SDK path\*\* value matches the location of the Flutter SDK you'd like to develop with. If not, update it by choosing or specifying the correct one. 1.

### Configure your project

Click \*\*Next\*\* to continue to project configuration. Multiple configuration options should appear. In the \*\*Project name\*\* field, enter a name for your app that follows the \`lowercase\_with\_underscores\` naming convention, following the \[Effective Dart]\[package-name] guidelines. If you're not creating an application, select another template from the \*\*Project type\*\* dropdown. If you're creating an app that you might publish in the future, set the \*\*Organization\*\* field \[to your company domain]\[as-set-org]. The other fields can be kept as is or configured according to your project's needs. 1.

### Finish project creation

Once you've completed the configuration of your project, click \*\*Create\*\* to begin project initialization. 1.

### Wait for workspace initialization

Android Studio will now initialize your workspace, bootstrap your project file structure, and retrieve your app's dependencies. This might take a while and can be tracked at the bottom of the window. 1.

### Run your app

Your new app should now be created and open in Android Studio. To try your new app, follow the steps to \[run and debug]\[as-run] in Android Studio. {:.steps} You've successfully created a new Flutter app in Android Studio! If you need more help with developing Flutter in Android Studio, check out the \[Android Studio for Flutter reference]\[as-more]. \[as-setup]: /tools/android-studio#installation-and-setup \[as-set-org]: /tools/android-studio#set-the-company-domain \[as-run]: /tools/android-studio#running-and-debugging \[as-more]: /tools/android-studio ## IntelliJ To create a Flutter app with IntelliJ or other JetBrains IDEs, you first need to \[install Flutter]\[flutter-setup] and \[set up IntelliJ]\[ij-setup] for Flutter development. Then follow these steps: 1.

### Launch IntelliJ

Open IntelliJ IDEA or your preferred IntelliJ-based IDE by JetBrains that has the Dart and Flutter plugins installed. 1.

### Begin project creation

If you're on the IDE welcome dialog that says \*\*Welcome to IntelliJ IDEA\*\*, find and click the \*\*New Project\*\* button in the upper right corner. If you already have a project open, either close it or go to \*\*File\*\* > \*\*New\*\* > \*\*New Project...\*\*. 1.

### Choose a project type

In the \*\*New Project\*\* dialog, under \*\*Generators\*\* in the left panel, select \*\*Flutter\*\*. 1.

### Verify Flutter SDK setup

At the top of the right panel, ensure the \*\*Flutter SDK path\*\* value matches the location of the Flutter SDK you'd like to develop with. If not, update it by choosing or specifying the correct one. 1.

### Configure your project

Click \*\*Next\*\* to continue to project configuration. Multiple configuration options should appear. In the \*\*Project name\*\* field, enter a name for your app that follows the \`lowercase\_with\_underscores\` naming convention, following the \[Effective Dart]\[package-name] guidelines. If you're not creating an application, select another template from the \*\*Project type\*\* dropdown. If you're creating an app that you might publish in the future, set the \*\*Organization\*\* field \[to your company domain]\[ij-set-org]. The other fields can be kept as is or configured according to your project's needs. 1.

### Finish project creation

Once you've completed the configuration of your project, click \*\*Create\*\* to begin project initialization. 1.

### Wait for workspace initialization

IntelliJ will now initialize your workspace, bootstrap your project file structure, and retrieve your app's dependencies. This might take a while and can be tracked at the bottom of the window. 1.

### Run your app

Your new app should now be created and open in IntelliJ. To try your new app, follow the steps to \[run and debug]\[ij-run] in IntelliJ. {:.steps} You've successfully created a new Flutter app in IntelliJ! If you need more help with developing Flutter in IntelliJ, check out the \[IntelliJ for Flutter reference]\[ij-more]. \[ij-setup]: /tools/android-studio#installation-and-setup \[ij-more]: /tools/android-studio \[ij-run]: /tools/android-studio#running-and-debugging []()## Antigravity To create a Flutter app with Antigravity, you first need to install and set up Antigravity as described on the \[Get started with AI]\(/ai/get-started). Then follow these steps: 1.

### Open Antigravity and create a Workspace

Launch the Antigravity IDE and create a new, sandboxed workspace for your project using the Agent Manager. 1.

### Use the Agent Manager

Switch to the Agent Manager interface, which is where you interact with the AI agents by using chat. 1.

### Write detailed prompts

Communicate your app requirements to the agent using detailed, structured prompts, much like providing instructions to a junior developer. For example, "Create a new Flutter project named my\_app. Add a home screen with a list of items and a floating action button". 1.

### Review and approve the plan

The AI agent will generate a detailed implementation plan, including folder structure, dependencies, and steps. You can review this plan and click \*\*Proceed\*\* or provide feedback to make changes. 1.

### Authorize actions

The agent will ask for permission before running terminal commands (like \`flutter pub add\` or \`flutter create\`) or accessing the browser/emulator. Review and \*\*Accept\*\* these actions to allow the agent to build the app. 1.

### Iterate and refine

Once the initial app is generated, you can provide further prompts to add features, refine the UI, implement logic, or add persistence (for example, "Add local storage using shared\_preferences"). 1.

### Test the app

Use the integrated emulator or connect a physical device to test the app. The agent can even run tests and provide video walkthroughs of the functionality. 1.

### Verify code in the editor

You can switch to the standard code editor view at any time to inspect the generated Dart and Flutter files, ensuring the code quality meets your standards. {:.steps} ## Terminal To create a Flutter app in your terminal, you first need to install and \[set up Flutter]\[flutter-setup]. Then follow these steps: 1.

### Open your terminal

Open your preferred method to access the command line, such as Terminal on macOS or PowerShell on Windows. 1.

### Navigate to the desired directory

Ensure your current working directory is the desired parent directory for your new app. Don't create the project folder, the \`flutter\` tool will do so. 1.

### Configure project creation

In your terminal, type out the \`flutter create\` command and pass in any desired flags and options to configure your project. For example, to create an app with a minimal \`main.dart\` file, you can add the \`--empty\` option: \`\`\`console $ flutter create --empty \`\`\` To learn about the available creation options, run \`flutter create --help\` in another terminal window. 1.

### Enter a project name

As the only non-option argument to \`flutter create\`, specify the directory and default name for your application. The name should follow the \`lowercase\_with\_underscores\` naming convention, following the \[Effective Dart]\[package-name] guidelines. For example, if you wanted to create an app named \`my\_app\`: \`\`\`console $ flutter create my\_app \`\`\` 1.

### Execute the configured command

To create a project with your specified configuration, run the command you built in the previous step. 1.

### Wait for project initialization

The \`flutter\` tool will now bootstrap your project's file structure and retrieve any necessary dependencies. This might take a while. 1.

### Navigate into the project directory

Now that your project has been created, you can navigate to it in your terminal or your preferred editor. For example, with a bash shell and a project named \`my\_app\`: \`\`\`console $ cd my\_app \`\`\` 1.

### Run your app

To try your new app, run the \`flutter run\` command in your terminal and respond to its prompts to select an output device. {:.steps} You've successfully created a new Flutter app in your terminal! If you need help configuring your project or with the \`flutter\` CLI tool, check out the \[Flutter CLI reference]\[cli-reference]. \[cli-reference]: /reference/flutter-cli \[flutter-setup]: /install \[package-name]: https\://dart.dev/effective-dart/style#do-name-packages-and-file-system-entities-using-lowercase-with-underscores

----
url: https://docs.flutter.dev/app-architecture/concepts
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Common architecture concepts](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

In this section, you'll find tried and true principles that guide architectural decisions in the larger world of app development, as well as information about how they fit into Flutter specifically. It's a gentle introduction to vocabulary and concepts related to the recommended architecture and best practices, so they can be explored in more detail throughout this guide.

## Separation of concerns

[#](#separation-of-concerns)

[Separation-of-concerns](https://en.wikipedia.org/wiki/Separation_of_concerns) is a core principle in app development that promotes modularity and maintainability by dividing an application's functionality into distinct, self-contained units. From a high-level, this means separating your UI logic from your business logic. This is often described as *layered* architecture. Within each layer, you should further separate your application by feature or functionality. For example, your application's authentication logic should be in a different class than the search logic.

In Flutter, this applies to [widgets](/resources/glossary#widget)WidgetThe basic building block of a Flutter user interface. [Learn more](/resources/glossary#widget "Learn more about 'Widget' and find related resources.") in the UI layer as well. You should write reusable, lean widgets that hold as little logic as possible.

## Layered architecture

[#](#layered-architecture)

Flutter applications should be written in *layers*. Layered architecture is a software design pattern that organizes an application into distinct layers, each with specific roles and responsibilities. Typically, applications are separated into 2 to 3 layers, depending on complexity.

* **UI layer** - Displays data to the user that is exposed by the business logic layer, and handles user interaction. This is also commonly referred to as the "presentation layer".
* **Logic layer** - Implements core business logic, and facilitates interaction between the data layer and UI layer. Commonly known as the "domain layer". The logic layer is optional, and only needs to be implemented if your application has complex business logic that happens on the client. Many apps are only concerned with presenting data to a user and allowing the user to change that data (colloquially known as CRUD apps). These apps might not need this optional layer.
* **Data layer** - Manages interactions with data sources, such as databases or platform plugins. Exposes data and methods to the business logic layer.

These are called "layers" because each layer can only communicate with the layers directly below or above it. The UI layer shouldn't know that the data layer exists, and vice versa.

## Single source of truth

[#](#single-source-of-truth)

Every data type in your app should have a [single source of truth](https://en.wikipedia.org/wiki/Single_source_of_truth) (SSOT). The source of truth is responsible for representing local or remote state. If the data can be modified in the app, the SSOT class should be the only class that can do so.

This can dramatically reduce the number of bugs in your application, and it can simplify code because you'll only ever have one copy of the same data.

Generally, the source of truth for any given type of data in your application is held in a class called a **Repository**, which is part of the data layer. There is typically one repository class for each type of data in your app.

This principle can be applied across layers and components in your application as well as within individual classes. For example, a Dart class might use [getters](https://dart.dev/effective-dart/design#do-use-getters-for-operations-that-conceptually-access-properties) to derive values from an SSOT field (instead of having multiple fields that need to be updated independently) or a list of [records](https://dart.dev/language/records) to group related values (instead of parallel lists whose indices might get out of sync).

## Unidirectional data flow

[#](#unidirectional-data-flow)

[Unidirectional data flow](https://en.wikipedia.org/wiki/Unidirectional_Data_Flow_\(computer_science\)) (UDF) refers to a design pattern that helps decouple state from the UI that displays that state. In the simplest terms, state flows from the data layer through the logic layer and eventually to the widgets in the UI layer. Events from user-interaction flow the opposite direction, from the presentation layer back through the logic layer and to the data layer.

In UDF, the update loop from user interaction to re-rendering the UI looks like this:

1. \[UI layer] An event occurs due to user interaction, such as a button being clicked. The widget's event handler callback invokes a method exposed by a class in the logic layer.
2. \[Logic layer] The logic class calls methods exposed by a repository that know how to mutate the data.
3. \[Data layer] The repository updates data (if necessary) and then provides the new data to the logic class.
4. \[Logic layer] The logic class saves its new state, which it sends to the UI.
5. \[UI layer] The UI displays the new state of the view model.

New data can also start at the data layer. For example, a repository might poll an HTTP server for new data. In this case, the data flow only makes the second half of the journey. The most important idea is that data changes always happen in the [SSOT](https://en.wikipedia.org/wiki/Single_source_of_truth), which is the data layer. This makes your code easier to understand, less error prone, and prevents malformed or unexpected data from being created.

## UI is a function of (immutable) state

[#](#ui-is-a-function-of-immutable-state)

Flutter is declarative, meaning that it builds its UI to reflect the current state of your app. When state changes, your app should trigger a rebuild of the UI that depends on that state. In Flutter, you'll often hear this described as "UI is a function of state".

It's crucial that your data drive your UI, and not the other way around. Data should be immutable and persistent, and views should contain as little logic as possible. This minimizes the possibility of data being lost when an app is closed, and makes your app more testable and resilient to bugs.

## Extensibility

[#](#extensibility)

Each piece of architecture should have a well defined list of inputs and outputs. For example, a view model in the logic layer should only take in data sources as inputs, such as repositories, and should only expose commands and data formatted for views.

Using clean interfaces in this way allows you to swap out concrete implementations of your classes without needing to change any of the code that consumes the interface.

## Testability

[#](#testability)

The principles that make software extensible also make software easier to test. For example, you can test the self-contained logic of a view model by mocking a repository. The view model tests don't require you to mock other parts of your application, and you can test your UI logic separate from Flutter widgets themselves.

Your app will also be more flexible. It will be straightforward and low risk to add new logic and new UI. For example, adding a new view model cannot break any logic from the data or business logic layers.

The next section explains the idea of inputs and outputs for any given component in your application's architecture.

## Feedback

[#](#feedback)

As this section of the website is evolving, we [welcome your feedback](https://google.qualtrics.com/jfe/form/SV_4T0XuR9Ts29acw6?page=%22concepts%22)!

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-11. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/app-architecture/concepts.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/app-architecture/concepts\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/app-architecture/concepts.md "Report an issue with this page").

----
url: https://docs.flutter.dev/cookbook/testing/widget/finders.md
----

\# Find widgets > How to use the Finder classes for testing widgets.

<!--?code-excerpt path-base="cookbook/testing/widget/finders/"?-->

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\`]\[] package. During a \`flutter run\` session on a widget test, you can also interactively tap parts of the screen for the Flutter tool to print the suggested \`Finder\`. This recipe looks at the \[\`find\`]\[] 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]\[]. If you're unfamiliar with widget testing and the role of \`Finder\` classes, review the \[Introduction to widget testing]\[] recipe. This recipe uses the following steps: 1. Find a \`Text\` widget. 2. Find a widget with a specific \`Key\`. 3. Find a specific widget instance. ## 1. Find a \`Text\` widget 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.

<!--?code-excerpt "test/finders_test.dart (test1)"?-->

\`\`\`dart testWidgets('finds a Text widget', (tester) async { // Build an App with a Text widget that displays the letter 'H'. await tester.pumpWidget(const MaterialApp(home: Scaffold(body: Text('H')))); // Find a widget that displays the letter 'H'. expect(find.text('H'), findsOneWidget); }); \`\`\` ## 2. Find a widget with a specific \`Key\` 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, 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.

<!--?code-excerpt "test/finders_test.dart (test2)"?-->

\`\`\`dart testWidgets('finds a widget using a Key', (tester) async { // Define the test key. const testKey = Key('K'); // Build a MaterialApp with the testKey. await tester.pumpWidget(MaterialApp(key: testKey, home: Container())); // Find the MaterialApp widget using the testKey. expect(find.byKey(testKey), findsOneWidget); }); \`\`\` ## 3. Find a specific widget instance 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.

<!--?code-excerpt "test/finders_test.dart (test3)"?-->

\`\`\`dart testWidgets('finds a specific instance', (tester) async { const childWidget = Padding(padding: EdgeInsets.zero); // Provide the childWidget to the Container. await tester.pumpWidget(Container(child: childWidget)); // 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 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, see the \[\`CommonFinders\` documentation]\[] to review all available methods. ## Complete example

<!--?code-excerpt "test/finders_test.dart"?-->

\`\`\`dart import 'package:flutter\_test/flutter\_test.dart'; import 'package:material\_ui/material\_ui.dart'; void main() { testWidgets('finds a Text widget', (tester) async { // Build an App with a Text widget that displays the letter 'H'. await tester.pumpWidget(const MaterialApp(home: Scaffold(body: Text('H')))); // Find a widget that displays the letter 'H'. expect(find.text('H'), findsOneWidget); }); testWidgets('finds a widget using a Key', (tester) async { // Define the test key. const testKey = Key('K'); // Build a MaterialApp with the testKey. await tester.pumpWidget(MaterialApp(key: testKey, home: Container())); // Find the MaterialApp widget using the testKey. expect(find.byKey(testKey), findsOneWidget); }); testWidgets('finds a specific instance', (tester) async { const childWidget = Padding(padding: EdgeInsets.zero); // Provide the childWidget to the Container. await tester.pumpWidget(Container(child: childWidget)); // Search for the childWidget in the tree and verify it exists. expect(find.byWidget(childWidget), findsOneWidget); }); } \`\`\` \[\`Finder\`]: https\://api.flutter.dev/flutter/flutter\_test/Finder-class.html \[\`CommonFinders\` documentation]: https\://api.flutter.dev/flutter/flutter\_test/CommonFinders-class.html \[\`find\`]: https\://api.flutter.dev/flutter/flutter\_test/find-constant.html \[\`flutter\_test\`]: https\://api.flutter.dev/flutter/flutter\_test/flutter\_test-library.html \[Introduction to widget testing]: /cookbook/testing/widget/introduction

----
url: https://docs.flutter.dev/platform-integration/windows/building
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Building Windows apps with Flutter](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

This page discusses considerations unique to building Windows apps with Flutter, including shell integration and distribution of Windows apps through the Microsoft Store on Windows.

## Integrating with Windows

[#](#integrating-with-windows)

The Windows programming interface combines traditional Win32 APIs, COM interfaces and more modern Windows Runtime libraries. As all these provide a C-based ABI, you can call into the services provided by the operating system using Dart's Foreign Function Interface library (`dart:ffi`). FFI is designed to enable Dart programs to efficiently call into C libraries. It provides Flutter apps with the ability to allocate native memory with `malloc` or `calloc`, support for pointers, structs and callbacks, and ABI types like `long` and `size_t`.

For more information about calling C libraries from Flutter, see [C interop using `dart:ffi`](https://dart.dev/guides/libraries/c-interop). To bundle and bind your own native C or C++ code with your app, see [Bind to native code using FFI](/platform-integration/bind-native-code).

In practice, while it is relatively straightforward to call basic Win32 APIs from Dart in this way, it is easier to use a wrapper library that abstracts the intricacies of the COM programming model. The [win32 package](https://pub.dev/packages/win32) provides a library for accessing thousands of common Windows APIs, using metadata provided by Microsoft for consistency and correctness. The package also includes examples of a variety of common use cases, such as WMI, disk management, shell integration, and system dialogs.

A number of other packages build on this foundation, providing idiomatic Dart access for the [Windows registry](https://pub.dev/packages/win32_registry), [gamepad support](https://pub.dev/packages/win32_gamepad), [biometric storage](https://pub.dev/packages/biometric_storage), [taskbar integration](https://pub.dev/packages/windows_taskbar), and [serial port access](https://pub.dev/packages/serial_port_win32), to name a few.

More generally, many other [packages support Windows](https://pub.dev/packages?q=platform%3Awindows), including common packages such as [`url_launcher`](https://pub.dev/packages/url_launcher), [`shared_preferences`](https://pub.dev/packages/shared_preferences), [`file_selector`](https://pub.dev/packages/file_selector), and [`path_provider`](https://pub.dev/packages/path_provider).

## Supporting Windows UI guidelines

[#](#supporting-windows-ui-guidelines)

While you can use any visual style or theme you choose, including Material, some app authors might wish to build an app that matches the conventions of Microsoft's [Fluent design system](https://docs.microsoft.com/en-us/windows/apps/design/). The [fluent\_ui](https://pub.dev/packages/fluent_ui) package, a [Flutter Favorite](/packages-and-plugins/favorites), provides support for visuals and common controls that are commonly found in modern Windows apps, including navigation views, content dialogs, flyouts, date pickers, and tree view widgets.

In addition, Microsoft offers [fluentui\_system\_icons](https://pub.dev/packages/fluentui_system_icons), a package that provides easy access to thousands of Fluent icons for use in your Flutter app.

Lastly, the [bitsdojo\_window](https://pub.dev/packages/bitsdojo_window) package provides support for "owner draw" title bars, allowing you to replace the standard Windows title bar with a custom one that matches the rest of your app.

## Customizing the Windows host application

[#](#customizing-the-windows-host-application)

When you create a Windows app, Flutter generates a small C++ application that hosts Flutter. This "runner app" is responsible for creating and sizing a traditional Win32 window, initializing the Flutter engine and any native plugins, and running the Windows message loop (passing relevant messages on to Flutter for further processing).

You can, of course, make changes to this code to suit your needs, including modifying the app name and icon, and setting the window's initial size and location. The relevant code is in main.cpp, where you will find code similar to the following:

cpp

```
Win32Window::Point origin(10, 10);
Win32Window::Size size(1280, 720);
if (!window.CreateAndShow(L"myapp", origin, size))
{
    return EXIT_FAILURE;
}
```

Replace `myapp` with the title you would like displayed in the Windows caption bar, as well as optionally adjusting the dimensions for size and the window coordinates.

To change the Windows application icon, replace the `app_icon.ico` file in the `windows\runner\resources` directory with an icon of your preference.

The generated Windows executable filename can be changed by editing the `BINARY_NAME` variable in `windows/CMakeLists.txt`:

cmake

```
cmake_minimum_required(VERSION 3.14)
project(windows_desktop_app LANGUAGES CXX)

# The name of the executable created for the application.
# Change this to change the on-disk name of your application.
set(BINARY_NAME "YourNewApp")

cmake_policy(SET CMP0063 NEW)
```

When you run `flutter build windows`, the executable file generated in the `build\windows\runner\Release` directory will match the newly given name.

Finally, further properties for the app executable itself can be found in the `Runner.rc` file in the `windows\runner` directory. Here you can change the copyright information and application version that is embedded in the Windows app, which is displayed in the Windows Explorer properties dialog box. To change the version number, edit the `VERSION_AS_NUMBER` and `VERSION_AS_STRING` properties; other information can be edited in the `StringFileInfo` block.

## Compiling with Visual Studio

[#](#compiling-with-visual-studio)

For most apps, it's sufficient to allow Flutter to handle the compilation process using the `flutter run` and `flutter build` commands. If you are making significant changes to the runner app or integrating Flutter into an existing app, you might want to load or compile the Flutter app in Visual Studio itself.

Follow these steps:

1. Run `flutter build windows` to create the `build\` directory.

2. Open the Visual Studio solution file for the Windows runner, which can now be found in the `build\windows` directory, named according to the parent Flutter app.

3. In Solution Explorer, you will see a number of projects. Right-click the one that has the same name as the Flutter app, and choose **Set as Startup Project**.

4. To generate the necessary dependencies, run **Build** > **Build Solution**

   You can also press/ `Ctrl` + `Shift` + `B`.

   To run the Windows app from Visual Studio, go to **Debug** > **Start Debugging**.

   You can also press `F5`.

5. Use the toolbar to switch between Debug and Release configurations as appropriate.

## Distributing Windows apps

[#](#distributing-windows-apps)

There are various approaches you can use for distributing your Windows application. Here are some options:

* Use tooling to construct an MSIX installer (described in the next section) for your application and distribute it through the Microsoft Windows App Store. You don't need to manually create a signing certificate for this option as it is handled for you.
* Construct an MSIX installer and distribute it through your own website. For this option, you need to give your application a digital signature in the form of a `.pfx` certificate.
* Collect all of the necessary pieces and build your own zip file.

### MSIX packaging

[#](#msix-packaging)

[MSIX](https://docs.microsoft.com/en-us/windows/msix/overview), the new Windows application package format, provides a modern packaging format and installer. This format can either be used to ship applications to the Microsoft Store on Windows, or you can distribute app installers directly.

The easiest way to create an MSIX distribution for a Flutter project is to use the [`msix` pub package](https://pub.dev/packages/msix). For an example of using the `msix` package from a Flutter desktop app, see the [Desktop Photo Search](https://github.com/flutter/samples/tree/main/desktop_photo_search) sample.

#### Create a self-signed .pfx certificate for local testing

[#](#create-a-self-signed-pfx-certificate-for-local-testing)

For private deployment and testing with the help of the MSIX installer, you need to give your application a digital signature in the form of a `.pfx` certificate.

For deployment through the Windows Store, generating a `.pfx` certificate is not required. The Windows Store handles creation and management of certificates for applications distributed through its store.

Distributing your application by self hosting it on a website requires a certificate signed by a Certificate Authority known to Windows.

Use the following instructions to generate a self-signed `.pfx` certificate.

1. If you haven't already, download the [OpenSSL](https://slproweb.com/products/Win32OpenSSL.html) toolkit to generate your certificates.
2. Go to where you installed OpenSSL, for example, `C:\Program Files\OpenSSL-Win64\bin`.
3. Set an environment variable so that you can access `OpenSSL` from anywhere:\
   `"C:\Program Files\OpenSSL-Win64\bin"`
4. Generate a private key as follows:\
   `openssl genrsa -out mykeyname.key 2048`
5. Generate a certificate signing request (CSR) file using the private key:\
   `openssl req -new -key mykeyname.key -out mycsrname.csr`
6. Generate the signed certificate (CRT) file using the private key and CSR file:\
   `openssl x509 -in mycsrname.csr -out mycrtname.crt -req -signkey mykeyname.key -days 10000`
7. Generate the `.pfx` file using the private key and CRT file:\
   `openssl pkcs12 -export -out CERTIFICATE.pfx -inkey mykeyname.key -in mycrtname.crt`
8. Install the `.pfx` certificate first on the local machine in `Certificate store` as `Trusted Root Certification Authorities` before installing the app.

### Building your own zip file for Windows

[#](#building-your-own-zip-file-for-windows)

The Flutter executable, `.exe`, can be found in your project under `build\windows\runner\<build mode>\`. In addition to that executable, you need the following:

* From the same directory:

  * all the `.dll` files
  * the `data` directory

* The Visual C++ redistributables. You can use any of the methods shown in the [deployment example walkthroughs](https://docs.microsoft.com/en-us/cpp/windows/deployment-examples) on the Microsoft site to ensure that end users have the C++ redistributables. If you use the `application-local` option, you need to copy:

  * `msvcp140.dll`
  * `vcruntime140.dll`
  * `vcruntime140_1.dll`

  Place the DLL files in the directory next to the executable and the other DLLs, and bundle them together in a zip file. The resulting structure looks something like this:

  ```
  Release
  │   flutter_windows.dll
  │   msvcp140.dll
  │   my_app.exe
  │   vcruntime140.dll
  │   vcruntime140_1.dll
  │
  └───data
  │   │   app.so
  │   │   icudtl.dat

  ...
  ```

At this point if desired it would be relatively simple to add this folder to a Windows installer such as Inno Setup, WiX, etc.

## Additional resources

[#](#additional-resources)

To learn how to build an `.exe` using Inno Setup to distribute your Flutter desktop app for Windows, check out the step-by-step [Windows packaging guide](https://medium.com/@fluttergems/packaging-and-distributing-flutter-desktop-apps-the-missing-guide-part-2-windows-0b468d5e9e70).

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-06-08. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/platform-integration/windows/building.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/platform-integration/windows/building\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/platform-integration/windows/building.md "Report an issue with this page").

----
url: https://docs.flutter.dev/cookbook/gestures/handling-taps.md
----

\# Handle taps > How to handle tapping and dragging.

<!--?code-excerpt path-base="cookbook/gestures/handling_taps/"?-->

You not only want to display information to users, you want users to interact with your app. Use the \[\`GestureDetector\`]\[] widget to respond to fundamental actions, such as tapping and dragging. :::note To learn more, watch this short Widget of the Week video on the \`GestureDetector\` widget: ::: This recipe shows how to make a custom button that shows a snackbar when tapped with the following steps: 1. Create the button. 2. Wrap it in a \`GestureDetector\` and provide an \`onTap()\` callback.

<!--?code-excerpt "lib/main.dart (GestureDetector)" replace="/return //g;/^\);$/)/g"?-->

\`\`\`dart // The GestureDetector wraps the button. GestureDetector( // When the child is tapped, show a snackbar. onTap: () { const snackBar = SnackBar(content: Text('Tap')); ScaffoldMessenger.of(context).showSnackBar(snackBar); }, // The custom button child: Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: Colors.lightBlue, borderRadius: BorderRadius.circular(8), ), child: const Text('My Button'), ), ) \`\`\` ## Notes 1. For information on adding the Material ripple effect to your button, see the \[Add Material touch ripples]\[] recipe. 2. Although this example creates a custom button, Flutter includes a handful of button implementations, such as: \[\`ElevatedButton\`]\[], \[\`TextButton\`]\[], and \[\`CupertinoButton\`]\[]. ## Interactive example

<!--?code-excerpt "lib/main.dart"?-->

\`\`\`dartpad title="Flutter tap handling hands-on example in DartPad" run="true" import 'package:flutter/material.dart'; void main() => runApp(const MyApp()); class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { const title = 'Gesture Demo'; return const MaterialApp( title: title, home: MyHomePage(title: title), ); } } class MyHomePage extends StatelessWidget { final String title; const MyHomePage({super.key, required this.title}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text(title)), body: const Center(child: MyButton()), ); } } class MyButton extends StatelessWidget { const MyButton({super.key}); @override Widget build(BuildContext context) { // The GestureDetector wraps the button. return GestureDetector( // When the child is tapped, show a snackbar. onTap: () { const snackBar = SnackBar(content: Text('Tap')); ScaffoldMessenger.of(context).showSnackBar(snackBar); }, // The custom button child: Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: Colors.lightBlue, borderRadius: BorderRadius.circular(8), ), child: const Text('My Button'), ), ); } } \`\`\` \[Add Material touch ripples]: /cookbook/gestures/ripples \[\`CupertinoButton\`]: https\://pub.dev/documentation/cupertino\_ui/latest/cupertino\_ui/CupertinoButton-class.html \[\`TextButton\`]: https\://pub.dev/documentation/material\_ui/latest/material\_ui/TextButton-class.html \[\`GestureDetector\`]: https\://api.flutter.dev/flutter/widgets/GestureDetector-class.html \[\`ElevatedButton\`]: https\://pub.dev/documentation/material\_ui/latest/material\_ui/ElevatedButton-class.html

----
url: https://docs.flutter.dev/flutter-for
----

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/flutter-for/index.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/flutter-for\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/flutter-for/index.md "Report an issue with this page").

----
url: https://docs.flutter.dev/cookbook/lists/spaced-items.md
----

\# List with spaced items > How to create a list with spaced or expanded items

<!--?code-excerpt path-base="cookbook/lists/spaced_items/"?-->

Perhaps you want to create a list where all list items are spaced evenly, so that the items take up the visible space. For example, the four items in the following image are spaced evenly, with "Item 0" at the top, and "Item 3" at the bottom. !\[Spaced items]\(/assets/images/docs/cookbook/spaced-items-1.png){:.site-mobile-screenshot} At the same time, you might want to allow users to scroll through the list when the list of items won't fit, maybe because a device is too small, a user resized a window, or the number of items exceeds the screen size. !\[Scrollable items]\(/assets/images/docs/cookbook/spaced-items-2.png){:.site-mobile-screenshot} Typically, you use \[\`Spacer\`]\[] to tune the spacing between widgets, or \[\`Expanded\`]\[] to expand a widget to fill the available space. However, these solutions are not possible inside scrollable widgets, because they need a finite height constraint. This recipe demonstrates how to use \[\`LayoutBuilder\`]\[] and \[\`ConstrainedBox\`]\[] to space out list items evenly when there is enough space, and to allow users to scroll when there is not enough space, using the following steps: 1. Add a \[\`LayoutBuilder\`]\[] with a \[\`SingleChildScrollView\`]\[]. 2. Add a \[\`ConstrainedBox\`]\[] inside the \[\`SingleChildScrollView\`]\[]. 3. Create a \[\`Column\`]\[] with spaced items. ## 1. Add a \`LayoutBuilder\` with a \`SingleChildScrollView\` Start by creating a \[\`LayoutBuilder\`]\[]. You need to provide a \`builder\` callback function with two parameters: 1. The \[\`BuildContext\`]\[] provided by the \[\`LayoutBuilder\`]\[]. 2. The \[\`BoxConstraints\`]\[] of the parent widget. In this recipe, you won't be using the \[\`BuildContext\`]\[], but you will need the \[\`BoxConstraints\`]\[] in the next step. Inside the \`builder\` function, return a \[\`SingleChildScrollView\`]\[]. This widget ensures that the child widget can be scrolled, even when the parent container is too small.

<!--?code-excerpt "lib/spaced_list.dart (builder)"?-->

\`\`\`dart LayoutBuilder( builder: (context, constraints) { return SingleChildScrollView(child: Placeholder()); }, ); \`\`\` ## 2. Add a \`ConstrainedBox\` inside the \`SingleChildScrollView\` In this step, add a \[\`ConstrainedBox\`]\[] as the child of the \[\`SingleChildScrollView\`]\[]. The \[\`ConstrainedBox\`]\[] widget imposes additional constraints to its child. Configure the constraint by setting the \`minHeight\` parameter to be the \`maxHeight\` of the \[\`LayoutBuilder\`]\[] constraints. This ensures that the child widget is constrained to have a minimum height equal to the available space provided by the \[\`LayoutBuilder\`]\[] constraints, namely the maximum height of the \[\`BoxConstraints\`]\[].

<!--?code-excerpt "lib/spaced_list.dart (constrainedBox)"?-->

\`\`\`dart LayoutBuilder( builder: (context, constraints) { return SingleChildScrollView( child: ConstrainedBox( constraints: BoxConstraints(minHeight: constraints.maxHeight), child: Placeholder(), ), ); }, ); \`\`\` However, you don't set the \`maxHeight\` parameter, because you need to allow the child to be larger than the \[\`LayoutBuilder\`]\[] size, in case the items don't fit the screen. ## 3. Create a \`Column\` with spaced items Finally, add a \[\`Column\`]\[] as the child of the \[\`ConstrainedBox\`]\[]. To space the items evenly, set the \`mainAxisAlignment\` to \`MainAxisAlignment.spaceBetween\`.

<!--?code-excerpt "lib/spaced_list.dart (column)"?-->

\`\`\`dart LayoutBuilder( builder: (context, constraints) { return SingleChildScrollView( child: ConstrainedBox( constraints: BoxConstraints(minHeight: constraints.maxHeight), child: Column( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: \[ ItemWidget(text: 'Item 1'), ItemWidget(text: 'Item 2'), ItemWidget(text: 'Item 3'), ], ), ), ); }, ); \`\`\` Alternatively, you can use the \[\`Spacer\`]\[] widget to tune the spacing between the items, or the \[\`Expanded\`]\[] widget, if you want one widget to take more space than others. For that, you have to wrap the \[\`Column\`] with an \[\`IntrinsicHeight\`]\[] widget, which forces the \[\`Column\`]\[] widget to size itself to a minimum height, instead of expanding infinitely.

<!--?code-excerpt "lib/spaced_list.dart (intrinsic)"?-->

\`\`\`dart LayoutBuilder( builder: (context, constraints) { return SingleChildScrollView( child: ConstrainedBox( constraints: BoxConstraints(minHeight: constraints.maxHeight), child: IntrinsicHeight( child: Column( children: \[ ItemWidget(text: 'Item 1'), Spacer(), ItemWidget(text: 'Item 2'), Expanded(child: ItemWidget(text: 'Item 3')), ], ), ), ), ); }, ); \`\`\` :::tip Play around with different devices, resizing the app, or resizing the browser window, and see how the item list adapts to the available space. ::: ## Interactive example This example shows a list of items that are spaced evenly within a column. The list can be scrolled up and down when the items don't fit the screen. The number of items is defined by the variable \`items\`, change this value to see what happens when the items won't fit the screen.

<!--?code-excerpt "lib/main.dart"?-->

\`\`\`dartpad title="Flutter Spaced Items hands-on example in DartPad" run="true" import 'package:flutter/material.dart'; void main() => runApp(const SpacedItemsList()); class SpacedItemsList extends StatelessWidget { const SpacedItemsList({super.key}); @override Widget build(BuildContext context) { const items = 4; return MaterialApp( title: 'Flutter Demo', debugShowCheckedModeBanner: false, theme: ThemeData( colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), cardTheme: CardThemeData(color: Colors.blue.shade50), ), home: Scaffold( body: LayoutBuilder( builder: (context, constraints) { return SingleChildScrollView( child: ConstrainedBox( constraints: BoxConstraints(minHeight: constraints.maxHeight), child: Column( mainAxisAlignment: MainAxisAlignment.spaceBetween, crossAxisAlignment: CrossAxisAlignment.stretch, children: List.generate( items, (index) => ItemWidget(text: 'Item $index'), ), ), ), ); }, ), ), ); } } class ItemWidget extends StatelessWidget { const ItemWidget({super.key, required this.text}); final String text; @override Widget build(BuildContext context) { return Card( child: SizedBox(height: 100, child: Center(child: Text(text))), ); } } \`\`\` \[\`BoxConstraints\`]: https\://api.flutter.dev/flutter/rendering/BoxConstraints-class.html \[\`BuildContext\`]: https\://api.flutter.dev/flutter/widgets/BuildContext-class.html \[\`Column\`]: https\://api.flutter.dev/flutter/widgets/Column-class.html \[\`ConstrainedBox\`]: https\://api.flutter.dev/flutter/widgets/ConstrainedBox-class.html \[\`Expanded\`]: https\://api.flutter.dev/flutter/widgets/Expanded-class.html \[\`IntrinsicHeight\`]: https\://api.flutter.dev/flutter/widgets/IntrinsicHeight-class.html \[\`LayoutBuilder\`]: https\://api.flutter.dev/flutter/widgets/LayoutBuilder-class.html \[\`SingleChildScrollView\`]: https\://api.flutter.dev/flutter/widgets/SingleChildScrollView-class.html \[\`Spacer\`]: https\://api.flutter.dev/flutter/widgets/Spacer-class.html

----
url: https://docs.flutter.dev/cookbook/testing/widget/orientation.md
----

\# Test orientation > How to test if an app is in portrait or landscape mode. In Flutter, you can build different layouts depending on a given \[orientation]\[]. For example, you could present data in two columns if the app is in portrait mode, and three columns if in landscape mode. Additionally, you can create tests that check if the correct number of columns are being shown for each orientation. In this recipe, you can learn how check if the orientation of an app is \`portrait\` or \`landscape\`, and also how many columns are displayed for each orientation. This recipe uses the following steps: 1. Create an app that updates the layout of the content, based on the orientation. 1. Create an orientation test group. 1. Create a portrait orientation test. 1. Create a landscape orientation test. 1. Run the tests. ## 1. Create an app that updates based on orientation Create a Flutter app that changes how many columns are shown when an app is in portrait or landscape mode: 1. Create a new Flutter project called \`orientation\_tests\`. \`\`\`console flutter create orientation\_tests \`\`\` 2. Follow the steps in \[Update the UI based on orientation]\[] to set up the project. ## 2. Create an orientation test group After you've set up your \`orientation\_tests\` project, complete these steps to group your future orientation tests: 1. In your Flutter project, open \`test/widget\_test.dart\`. 1. Replace the existing contents with the following:

<!--?code-excerpt "cookbook/testing/widget/orientation_tests/test/widget_test.dart (scaffolding)"?-->

\`\`\`dart title="widget\_test.dart" import 'package:flutter\_test/flutter\_test.dart'; import 'package:material\_ui/material\_ui.dart'; import 'package:orientation\_tests/main.dart'; void main() { group('Orientation', () { // ··· }); } \`\`\` ## 3. Create a portrait orientation test Add the portrait orientation test to the \`Orientation\` group. This test makes sure that the orientation is \`portrait\` and that only \`2\` columns of data appear in the app: 1. In \`test/widget\_test.dart\`, replace \`...\` inside of the \`Orientation\` group with the following test:

<!--?code-excerpt "cookbook/testing/widget/orientation_tests/test/widget_test.dart (portrait-mode-test)"?-->

\`\`\`dart title="widget\_test.dart" // Check if portrait mode displays correctly. testWidgets('Displays 2 columns in portrait mode', (tester) async { // Build the app. await tester.pumpWidget(const MyApp()); // Change to portrait. tester.view\.physicalSize = const Size(600, 800); tester.view\.devicePixelRatio = 1.0; addTearDown(() { tester.view\.resetPhysicalSize(); }); await tester.pump(); // Verify initial orientation is portrait. final orientation = MediaQuery.of( tester.element(find.byType(OrientationList)), ).orientation; expect(orientation, Orientation.portrait); // Verify there are only 2 columns in portrait mode. final gridViewFinder = find.byType(GridView); final gridView = tester.widget(gridViewFinder); final delegate = gridView\.gridDelegate as SliverGridDelegateWithFixedCrossAxisCount; expect(delegate.crossAxisCount, 2); }); \`\`\` ## 4. Create a landscape orientation test Add the landscape orientation test to the \`Orientation\` group. This test makes sure that the orientation is \`landscape\` and that only \`3\` columns of data appear in the app: 1. In \`test/widget\_test.dart\`, inside of the \`Orientation\` group, add the following test after the landscape test:

<!--?code-excerpt "cookbook/testing/widget/orientation_tests/test/widget_test.dart (landscape-mode-test)"?-->

\`\`\`dart title="widget\_test.dart" // Check if landscape mode displays correctly. testWidgets('Displays 3 columns in landscape mode', (tester) async { // Build the app. await tester.pumpWidget(const MyApp()); // Change to landscape. tester.view\.physicalSize = const Size(800, 600); tester.view\.devicePixelRatio = 1.0; addTearDown(() { tester.view\.resetPhysicalSize(); }); await tester.pump(); // Verify initial orientation is landscape. final orientation = MediaQuery.of( tester.element(find.byType(OrientationList)), ).orientation; expect(orientation, Orientation.landscape); // Verify there are only 3 columns in landscape mode. final gridViewFinder = find.byType(GridView); final gridView = tester.widget(gridViewFinder); final delegate = gridView\.gridDelegate as SliverGridDelegateWithFixedCrossAxisCount; expect(delegate.crossAxisCount, 3); }); \`\`\` ## 5. Run the tests Run the tests using the following command from the root of the project: \`\`\`console flutter test test/widget\_test.dart \`\`\` ## Complete example

<!--?code-excerpt "cookbook/testing/widget/orientation_tests/test/widget_test.dart"?-->

\`\`\`dart title="widget\_test.dart" import 'package:flutter\_test/flutter\_test.dart'; import 'package:material\_ui/material\_ui.dart'; import 'package:orientation\_tests/main.dart'; void main() { group('Orientation', () { // Check if portrait mode displays correctly. testWidgets('Displays 2 columns in portrait mode', (tester) async { // Build the app. await tester.pumpWidget(const MyApp()); // Change to portrait. tester.view\.physicalSize = const Size(600, 800); tester.view\.devicePixelRatio = 1.0; addTearDown(() { tester.view\.resetPhysicalSize(); }); await tester.pump(); // Verify initial orientation is portrait. final orientation = MediaQuery.of( tester.element(find.byType(OrientationList)), ).orientation; expect(orientation, Orientation.portrait); // Verify there are only 2 columns in portrait mode. final gridViewFinder = find.byType(GridView); final gridView = tester.widget(gridViewFinder); final delegate = gridView\.gridDelegate as SliverGridDelegateWithFixedCrossAxisCount; expect(delegate.crossAxisCount, 2); }); // Check if landscape mode displays correctly. testWidgets('Displays 3 columns in landscape mode', (tester) async { // Build the app. await tester.pumpWidget(const MyApp()); // Change to landscape. tester.view\.physicalSize = const Size(800, 600); tester.view\.devicePixelRatio = 1.0; addTearDown(() { tester.view\.resetPhysicalSize(); }); await tester.pump(); // Verify initial orientation is landscape. final orientation = MediaQuery.of( tester.element(find.byType(OrientationList)), ).orientation; expect(orientation, Orientation.landscape); // Verify there are only 3 columns in landscape mode. final gridViewFinder = find.byType(GridView); final gridView = tester.widget(gridViewFinder); final delegate = gridView\.gridDelegate as SliverGridDelegateWithFixedCrossAxisCount; expect(delegate.crossAxisCount, 3); }); }); } \`\`\`

<!--?code-excerpt "cookbook/testing/widget/orientation_tests/lib/main.dart"?-->

\`\`\`dart title="main.dart" import 'package:material\_ui/material\_ui.dart'; void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { const appTitle = 'Orientation Demo'; return const MaterialApp( title: appTitle, home: OrientationList(title: appTitle), ); } } class OrientationList extends StatelessWidget { final String title; const OrientationList({super.key, required this.title}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text(title)), body: OrientationBuilder( builder: (context, orientation) { return GridView\.count( // 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. children: List.generate(100, (index) { return Center( child: Text( 'Item $index', style: TextTheme.of(context).headlineLarge, ), ); }), ); }, ), ); } } \`\`\` \[orientation]: https\://api.flutter.dev/flutter/widgets/Orientation.html \[Update the UI based on orientation]: /cookbook/design/orientation

----
url: https://docs.flutter.dev/platform-integration/android
----

[Calling JetPack APIs](/platform-integration/android/call-jetpack-apis)

[Use the latest Android APIs from your Dart code](/platform-integration/android/call-jetpack-apis)

[Targeting ChromeOS with Android](/platform-integration/android/chromeos)

[Platform-specific considerations for building for ChromeOS with Flutter.](/platform-integration/android/chromeos)

[Adding a splash screen to your Android app](/platform-integration/android/splash-screen)

[Learn how to add a splash screen to your Android app.](/platform-integration/android/splash-screen)

[Add the predictive back gesture](/platform-integration/android/predictive-back)

[Learn how to enable and handle Android predictive back gestures in Flutter.](/platform-integration/android/predictive-back)

[Protect your app's sensitive content](/platform-integration/android/sensitive-content)

[Learn how to protect sensitive content in your Flutter app.](/platform-integration/android/sensitive-content)

[Launching a Jetpack Compose activity from your Flutter application](/platform-integration/android/compose-activity)

[Learn how to launch native Android activities in your Flutter app.](/platform-integration/android/compose-activity)

[Restore state on Android](/platform-integration/android/restore-state-android)

[How to restore the state of your Android app after it's been killed by the OS.](/platform-integration/android/restore-state-android)

[Hosting native Android views in your Flutter app with Platform Views](/platform-integration/android/platform-views)

[Learn how to host native Android views in your Flutter app with Platform Views.](/platform-integration/android/platform-views)

[Request local network permissions on Android](/platform-integration/android/local-network-permission)

[Learn how to declare and request Android local area network permissions before opening Dart sockets in Flutter.](/platform-integration/android/local-network-permission)

[Set up Android development](/platform-integration/android/setup)

[Configure your development environment to run, build, and deploy Flutter apps for Android devices.](/platform-integration/android/setup)

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/platform-integration/android/index.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/platform-integration/android\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/platform-integration/android/index.md "Report an issue with this page").

----
url: https://docs.flutter.dev/tools/devtools/debugger.md
----

\# Use the debugger > How to use DevTools' source-level debugger. :::note DevTools hides the Debugger tab if the app was launched from VS Code because VS Code has a built-in debugger. ::: ## Getting started DevTools includes a full source-level debugger, supporting breakpoints, stepping, and variable inspection. When you open the debugger tab, you should see the source for the main entry-point for your app loaded in the debugger. In order to browse around more of your application sources, click \*\*Libraries\*\* (top right) or press `Ctrl` / `Cmd` + `P`. This opens the libraries window and allows you to search for other source files. !\[Screenshot of the debugger tab]\(/assets/images/docs/tools/devtools/debugger\_screenshot.png){:width="100%"} ## Setting breakpoints To set a breakpoint, click the left margin (the line number ruler) in the source area. Clicking once sets a breakpoint, which should also show up in the \*\*Breakpoints\*\* area on the left. Clicking again removes the breakpoint. ## The call stack and variable areas When your application encounters a breakpoint, it pauses there, and the DevTools debugger shows the paused execution location in the source area. In addition, the \`Call stack\` and \`Variables\` areas populate with the current call stack for the paused isolate, and the local variables for the selected frame. Selecting other frames in the \`Call stack\` area changes the contents of the variables. Within the \`Variables\` area, you can inspect individual objects by toggling them open to see their fields. Hovering over an object in the \`Variables\` area calls \`toString()\` for that object and displays the result. ## Stepping through source code When paused, the three stepping buttons become active. \* Use \*\*Step in\*\* to step into a method invocation, stopping at the first executable line in that invoked method. \* Use \*\*Step over\*\* to step over a method invocation; this steps through source lines in the current method. \* Use \*\*Step out\*\* to step out of the current method, without stopping at any intermediary lines. In addition, the \*\*Resume\*\* button continues regular execution of the application. ## Console output Console output for the running app (stdout and stderr) is displayed in the console, below the source code area. You can also see the output in the \[Logging view]\[]. ## Breaking on exceptions To adjust the stop-on-exceptions behavior, toggle the \*\*Ignore\*\* dropdown at the top of the debugger view. Breaking on unhandled excepts only pauses execution if the breakpoint is considered uncaught by the application code. Breaking on all exceptions causes the debugger to pause whether or not the breakpoint was caught by application code. ## Known issues When performing a hot restart for a Flutter application, user breakpoints are cleared. \[Logging view]: /tools/devtools/logging ## Other resources For more information on debugging and profiling, see the \[Debugging]\[] page. \[Debugging]: /testing/debugging

----
url: https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.30.0.md
----

\# DevTools 2.30.0 release notes > Release notes for Dart and Flutter DevTools version 2.30.0. The 2.30.0 release of the Dart and Flutter DevTools includes the following changes among other general improvements. To learn more about DevTools, check out the \[DevTools overview]\(https\://docs.flutter.dev/tools/devtools). ## Performance updates \* Add an indicator of the rendering engine to the Flutter Frames chart. - \[#6771]\(https\://github.com/flutter/devtools/pull/6771) !\[Flutter rendering engine text]\(/assets/images/docs/tools/devtools/release-notes/images-2.30.0/flutter\_frames\_engine\_text.png "Text describing the current flutter rendering engine") \* Improve messaging when we do not have analysis data available for a Flutter frame. - \[#6768]\(https\://github.com/flutter/devtools/pull/6768) ## VS Code Sidebar updates \* The Flutter Sidebar provided to VS Code now has the ability to enable new platforms if a device is available for a platform that is not enabled for the current project. This also requires a corresponding Dart extension for VS Code update to appear. - \[#6688]\(https\://github.com/flutter/devtools/pull/6688) \* The DevTools menu in the sidebar now has an entry "Open in Browser" that opens DevTools in an external browser window even when VS Code settings are set to usually use embedded DevTools. - \[#6736]\(https\://github.com/flutter/devtools/pull/6736) ## Full commit history To find a complete list of changes in this release, check out the \[DevTools git log]\(https\://github.com/flutter/devtools/tree/v2.30.0).

----
url: https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.47.0.md
----

\# DevTools 2.47.0 release notes > Release notes for Dart and Flutter DevTools version 2.47.0. The 2.47.0 release of the Dart and Flutter DevTools includes the following changes among other general improvements. To learn more about DevTools, check out the \[DevTools overview]\(/tools/devtools/overview). ## General updates - Fixed an issue where copying all logs in the console would show \`null\` for any inspected widgets. - \[#9204]\(https\://github.com/flutter/devtools/pull/9204) ## Full commit history To find a complete list of changes in this release, check out the \[DevTools git log]\(https\://github.com/flutter/devtools/tree/v2.47.0).

----
url: https://docs.flutter.dev/resources/courses
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Online courses](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

Learn how to build Flutter apps with these video courses. Before signing up for a course, verify that it includes up-to-date information, such as null-safe Dart code.

These courses are grouped by language and listed alphabetically. To include your course, [submit a PR](https://github.com/flutter/website/pulls):

## Arabic

[#](#arabic)

* [Flutter Master Course From Zero To Hero](https://usamaelgendy.com/courses/flutter-beginners/) by Usama Elgendy

## English

[#](#english)

* [20 Hour Dart & Flutter YT Course For Beginners](https://youtu.be/CzRQ9mnmh44) by Rivaan Ranawat
* [Best Dart Course](https://www.hungrimind.com/learn/dart) by Tadas Petra & Robert Brunhage
* [Best Flutter Course](https://www.hungrimind.com/learn/flutter) by Tadas Petra & Robert Brunhage
* [Dart & Flutter - Zero to Mastery 2023 + Clean Architecture](https://www.udemy.com/course/flutter-made-easy-zero-to-mastery/?referralCode=CCBFCD16CC71F359EE3C) by Max Berktold & Max Steffen
* [Dart & Flutter - Zero to Mastery 2023 - Keiko Corp. Food Reviews App](https://academy.zerotomastery.io/courses/2092303/lectures/47623876) by Marco Napoli
* [Flutter & Dart - The Complete Guide, 2023 Edition](https://www.udemy.com/course/learn-flutter-dart-to-build-ios-android-apps/)
* [Flutter & Firebase Masterclass](https://codewithandrea.com/courses/flutter-firebase-masterclass/) by Andrea Bizzotto
* [Flutter and Dart Full Course](https://www.youtube.com/watch?v=_FDJ1of5YPM) by Mouaz M. Alshahmeh
* [Flutter Animations Masterclass](https://codewithandrea.com/courses/flutter-animations-masterclass/) by Andrea Bizzotto
* [Flutter Crash Course](https://fluttercrashcourse.com/)
* [Flutter Foundations](https://codewithandrea.com/courses/flutter-foundations/) by Andrea Bizzotto
* [Flutter in Production](https://codewithandrea.com/courses/flutter-in-production/) by Andrea Bizzotto
* [FlutterCraft](https://fluttercraft.app) by Muhammad Haris Shahzad
* [Sticky Grouped Headers in Flutter](https://academy.droidcon.com/course/sticky-grouped-headers-in-flutter) by Marco Napoli
* [The Complete 2021 Flutter Development Bootcamp Using Dart](https://www.appbrewery.com/p/flutter-development-bootcamp-with-dart/) by App Brewery
* [The Complete Dart Developer Guide](https://codewithandrea.com/courses/complete-dart-guide/) by Andrea Bizzotto

## German

[#](#german)

* [Flutter leicht gemacht 2022 - Zero to Mastery!](https://www.udemy.com/course/dart-flutter-leicht-gemacht/) by Max Berktold

## Italian

[#](#italian)

* [Flutter University - From Zero to Mastery](https://www.fudeo.it/?utm_source=flutter_dev) by Fudeo

## Portuguese

[#](#portuguese)

* [Flutter Bootcamp](https://flutterbootcamp.com.br) by Rubens de Melo
* [Flutter para iniciantes](https://www.youtube.com/playlist?list=PLS4cqF1_X2syzBpkoSwtmKoREgnp1MhTn) by Rubens de Melo

## Turkish

[#](#turkish)

* [Flutter Zero to Hero](https://www.youtube.com/playlist?list=PL1k5oWAuBhgXdw1BbxVGxxWRmkGB1C11l) by Veli Bacik

## Urdu

[#](#urdu)

* [Tech Idara - Flutter from Basic to Advanced](https://www.youtube.com/playlist?list=PLX97VxArfzkmXeUqUxeKW7XS8oYraH7A5) by Ishaq Hassan

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-10-01. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/resources/courses.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/resources/courses\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/resources/courses.md "Report an issue with this page").

----
url: https://docs.flutter.dev/tools/devtools/performance
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Use the Performance view](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

The performance page can help you diagnose performance problems and UI jank in your application. This page offers timing and performance information for activity in your application. It consists of several tools to help you identify the cause of poor performance in your app:

* Flutter frames chart (Flutter apps only)
* Frame analysis tab (Flutter apps only)
* Timeline events trace viewer (all native Dart applications)
* Advanced debugging tools (Flutter apps only)

The performance view also supports importing and exporting of data snapshots. For more information, check out the [Import and export](#import-and-export) section.

## What is a frame in Flutter?

[#](#what-is-a-frame-in-flutter)

Flutter is designed to render its UI at 60 frames per second (fps), or 120 fps on devices capable of 120Hz updates. Each render is called a *frame*. This means that, approximately every 16ms, the UI updates to reflect animations or other changes to the UI. A frame that takes longer than 16ms to render causes jank (jerky motion) on the display device.

## Flutter frames chart

[#](#flutter-frames-chart)

This chart contains Flutter frame information for your application. Each bar set in the chart represents a single Flutter frame. The bars are color-coded to highlight the different portions of work that occur when rendering a Flutter frame: work from the UI thread and work from the raster thread.

This chart contains Flutter frame timing information for your application. Each pair of bars in the chart represents a single Flutter frame. Selecting a frame from this chart updates the data that is displayed below in the [Frame analysis](#frame-analysis-tab) tab or the [Timeline events](#timeline-events-tab) tab.

The flutter frames chart updates when new frames are drawn in your app. To pause updates to this chart, click the pause button to the right of the chart. This chart can be collapsed to provide more viewing space for data below by clicking the **Flutter frames** button above the chart.

The pair of bars representing each Flutter frame are color-coded to highlight the different portions of work that occur when rendering a Flutter frame: work from the UI thread and work from the raster thread.

### UI

[#](#ui)

The UI thread executes Dart code in the Dart VM. This includes code from your application as well as the Flutter framework. When your app creates and displays a scene, the UI thread creates a layer tree, a lightweight object containing device-agnostic painting commands, and sends the layer tree to the raster thread to be rendered on the device. Do **not** block this thread.

### Raster

[#](#raster)

The raster thread executes graphics code from the Flutter Engine. This thread takes the layer tree and displays it by talking to the GPU (graphic processing unit). You can't directly access the raster thread or its data, but if this thread is slow, it's a result of something you've done in the Dart code. Skia, the graphics library, runs on this thread. [Impeller](/perf/impeller) also uses this thread.

Sometimes a scene results in a layer tree that is easy to construct, but expensive to render on the raster thread. In this case, you need to figure out what your code is doing that is causing rendering code to be slow. Specific kinds of workloads are more difficult for the GPU. They might involve unnecessary calls to `saveLayer()`, intersecting opacities with multiple objects, and clips or shadows in specific situations.

For more information on profiling, check out [Identifying problems in the GPU graph](/perf/ui-performance#identifying-problems-in-the-gpu-graph).

### Jank (slow frame)

[#](#jank-slow-frame)

The frame rendering chart shows jank with a red overlay. A frame is considered to be janky if it takes more than \~16 ms to complete (for 60 FPS devices). To achieve a frame rendering rate of 60 FPS (frames per second), each frame must render in \~16 ms or less. When this target is missed, you may experience UI jank or dropped frames.

For more information on how to analyze your app's performance, check out [Flutter performance profiling](/perf/ui-performance).

### Shader compilation

[#](#shader-compilation)

Shader compilation occurs when a shader is first used in your Flutter app. Frames that perform shader compilation are marked in dark red:

For more information on how to reduce shader compilation jank, check out [Reduce shader compilation jank on mobile](/perf/rendering-performance).

## Frame analysis tab

[#](#frame-analysis-tab)

Selecting a janky frame (slow, colored in red) from the Flutter frames chart above shows debugging hints in the Frame analysis tab. These hints help you diagnose jank in your app, and notify you of any expensive operations that we have detected that might have contributed to the slow frame time.

## Timeline events tab

[#](#timeline-events-tab)

The timeline events chart shows all event tracing from your application. The Flutter framework emits timeline events as it works to build frames, draw scenes, and track other activity such as HTTP request timings and garbage collection. These events show up here in the Timeline. You can also send your own Timeline events using the dart:developer [`Timeline`](https://api.flutter.dev/flutter/dart-developer/Timeline-class.html) and [`TimelineTask`](https://api.flutter.dev/flutter/dart-developer/TimelineTask-class.html) APIs.

For help with navigating and using the trace viewer, click the **?** button at the top right of the timeline events tab bar. To refresh the timeline with new events from your application, click the refresh button (also in the upper right corner of the tab controls).

## Advanced debugging tools

[#](#advanced-debugging-tools)

### Enhance tracing

[#](#enhance-tracing)

To view more detailed tracing in the timeline events chart, use the options in the enhance tracing dropdown:

To see the new timeline events, reproduce the activity in your app that you are interested in tracing, and then select a frame to inspect the timeline.

### Track widget builds

[#](#track-widget-builds)

To see the `build()` method events in the timeline, enable the **Track Widget Builds** option. The name of the widget is shown in the timeline event.

[Watch this video for an example of tracking widget builds](https://www.youtube.com/watch/_EYk-E29edo?t=623)

### Track layouts

[#](#track-layouts)

To see render object layout events in the timeline, enable the **Track Layouts** option:

[Watch this video for an example of tracking layouts](https://www.youtube.com/watch/_EYk-E29edo?t=676)

### Track paints

[#](#track-paints)

To see render object paint events in the timeline, enable the **Track Paints** option:

[Watch this video for an example of tracking paints](https://www.youtube.com/watch/_EYk-E29edo?t=748)

## More debugging options

[#](#more-debugging-options)

To diagnose performance problems related to rendering layers, toggle off a rendering layer. These options are enabled by default.

To see the effects on your app's performance, reproduce the activity in your app. Then select the new frames in the frames chart to inspect the timeline events with the layers disabled. If raster time has significantly decreased, excessive use of the effects you disabled might be contributing to the jank you saw in your app.

* **Render Clip layers**

  Disable this option to check whether excessive use of clipping is affecting performance. If performance improves with this option disabled, try to reduce the use of clipping effects in your app.

* **Render Opacity layers**

  Disable this option to check whether excessive use of opacity effects are affecting performance. If performance improves with this option disabled, try to reduce the use of opacity effects in your app.

* **Render Physical Shape layers**

  Disable this option to check whether excessive use of physical modeling effects are affecting performance, such as shadows or elevation. If performance improves with this option disabled, try to reduce the use of physical modeling effects in your app.

## Import and export

[#](#import-and-export)

DevTools supports importing and exporting performance snapshots. Clicking the export button (upper-right corner above the frame rendering chart) downloads a snapshot of the current data on the performance page. To import a performance snapshot, you can drag and drop the snapshot into DevTools from any page. **Note that DevTools only supports importing files that were originally exported from DevTools.**

## Other resources

[#](#other-resources)

To learn how to monitor an app's performance and detect jank using DevTools, check out a guided [Performance View tutorial](https://medium.com/@fluttergems/mastering-dart-flutter-devtools-performance-view-part-8-of-8-4ae762f91230).

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-07-31. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/performance.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/tools/devtools/performance\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/performance.md "Report an issue with this page").

----
url: https://docs.flutter.dev/resources/bug-reports.md
----

\# Create useful bug reports > Where to file bug reports and enhancement requests for flutter and the website. The instructions in this document detail the current steps required to provide the most actionable bug reports for crashes and other bad behavior. Each step is optional but will greatly improve how quickly issues are diagnosed and addressed. We appreciate your effort in sending us as much feedback as possible. ## Create an issue on GitHub \* To report a Flutter crash or bug, \[create an issue in the flutter/flutter project]\[Flutter issue]. \* To report a problem with the website, \[create an issue in the flutter/website project]\[Website issue]. ## Provide a minimal reproducible code sample Create a minimal Flutter app that shows the problem you are facing, and paste it into the GitHub issue. To create it you can use \`flutter create bug\` command and update the \`main.dart\` file. Alternatively, you can use \[DartPad]\[], which is capable of creating and running small Flutter apps. If your problem goes out of what can be placed in a single file, for example you have a problem with native channels, you can upload the full code of the reproduction into a separate repository and link it. ## Provide some Flutter diagnostics \* Run \`flutter doctor -v\` in your project directory and paste the results into the GitHub issue: \`\`\`plaintext \[✓] Flutter (Channel stable, 1.22.3, on Mac OS X 10.15.7 19H2, locale en-US) • Flutter version 1.22.3 at /Users/me/projects/flutter • Framework revision 8874f21e79 (5 days ago), 2020-10-29 14:14:35 -0700 • Engine revision a1440ca392 • Dart version 2.10.3 \[✓] Android toolchain - develop for Android devices (Android SDK version 29.0.2) • Android SDK at /Users/me/Library/Android/sdk • Platform android-30, build-tools 29.0.2 • Java binary at: /Applications/Android Studio.app/Contents/jre/jdk/Contents/Home/bin/java • Java version OpenJDK Runtime Environment (build 1.8.0\_242-release-1644-b3-6222593) • All Android licenses accepted. \[✓] Xcode - develop for iOS and macOS (Xcode 12.2) • Xcode at /Applications/Xcode.app/Contents/Developer • Xcode 12.2, Build version 12B5035g • CocoaPods version 1.9.3 \[✓] Android Studio (version 4.0) • Android Studio at /Applications/Android Studio.app/Contents • Flutter plugin version 50.0.1 • Dart plugin version 193.7547 • Java version OpenJDK Runtime Environment (build 1.8.0\_242-release-1644-b3-6222593) \[✓] VS Code (version 1.50.1) • VS Code at /Applications/Visual Studio Code.app/Contents • Flutter extension version 3.13.2 \[✓] Connected device (1 available) • iPhone (mobile) • 00000000-0000000000000000 • ios • iOS 14.0 \`\`\` ## Run the command in verbose mode Follow these steps only if your issue is related to the \`flutter\` tool. \* All Flutter commands accept the \`--verbose\` flag. If attached to the issue, the output from this command might aid in diagnosing the problem. \* Attach the results of the command to the GitHub issue. !\[flutter verbose]\(/assets/images/docs/verbose\_flag.png){:width="100%"} ## Provide the most recent logs \* Logs for the currently connected device are accessed using \`flutter logs\`. \* If the crash is reproducible, clear the logs (⌘ + k on Mac), reproduce the crash and copy the newly generated logs into a file attached to the bug report. \* If you are getting exceptions thrown by the framework, include all the output between and including the dashed lines of the first such exception. !\[flutter logs]\(/assets/images/docs/logs.png){:width="100%"} ## Provide the crash report \* When the iOS simulator crashes, a crash report is generated in \`\~/Library/Logs/DiagnosticReports/\`. \* When an iOS device crashes, a crash report is generated in \`\~/Library/Logs/CrashReporter/MobileDevice\`. \* Find the report corresponding to the crash (usually the latest) and attach it to the GitHub issue. !\[crash report]\(/assets/images/docs/crash\_reports.png){:width="100%"} \[DartPad]: https\://dartpad.dev \[Flutter issue]: https\://github.com/flutter/flutter/issues/new/choose \[Website issue]: https\://github.com/flutter/website/issues/new/choose

----
url: https://docs.flutter.dev/deployment/obfuscate.md
----

\# Obfuscate Dart code > How to remove function and class names from your Dart binary.

<!--?code-excerpt path-base="deployment/obfuscate"?-->

\## What is code obfuscation? \[Code obfuscation]\[] is the process of modifying an app's binary to make it harder for humans to understand. Obfuscation hides function and class names in your compiled Dart code, replacing each symbol with another symbol, making it difficult for an attacker to reverse engineer your proprietary app. \[Code obfuscation]: https\://en.wikipedia.org/wiki/Obfuscation\_(software) ## Limitations and warnings {: #limitations} \*\*Flutter's code obfuscation works only on a \[release build]\[].\*\* :::warning It is a \*\*poor security practice\*\* to store secrets in an app. ::: Obfuscating your code does \_not\_ encrypt resources nor does it protect against reverse engineering. It only renames symbols with more obscure names. Web apps don't support obfuscation. A web app can be \[minified]\[], which provides a similar result. When you build a release version of a Flutter web app, the web compiler minifies the app. To learn more, see \[Build and release a web app]\[]. \[release build]: /testing/build-modes#release \[Build and release a web app]: /deployment/web \[minified]: https\://en.wikipedia.org/wiki/Minification\_(programming) ## Supported targets The following build targets support the obfuscation process described on this page: \* \`aar\` \* \`apk\` \* \`appbundle\` \* \`ios\` \* \`ios-framework\` \* \`ipa\` \* \`linux\` \* \`macos\` \* \`macos-framework\` \* \`windows\` For detailed information about the command line options available for a build target, run the following command. The \`--obfuscate\` and \`--split-debug-info\` options should be listed in the output. If they aren't, you'll need to install a newer version of Flutter to obfuscate your code. \`\`\`console $ flutter build -h \`\`\` \* \`\`: The build target. For example, \`apk\`. ## Obfuscate your app To obfuscate your app and create a symbol map, use the \`flutter build\` command in release mode with the \`--obfuscate\` and \`--split-debug-info\` options. If you want to debug your obfuscated app in the future, you will need the symbol map. 1. Run the following command to obfuscate your app and generate a SYMBOLS file: \`\`\`console $ flutter build \ --obfuscate \ --split-debug-info=/ \`\`\` \* \`\`: The build target. For example, \`apk\`. \* \`\`: The directory where the SYMBOLS file should be placed. For example, \`out/android\`. :::note On Windows x64, the build generates a PDB file (\`app.windows-x64.pdb\`) instead of a SYMBOLS file. ::: 1. Once you've obfuscated your binary, \*\*backup the SYMBOLS file\*\* (or the PDB file on Windows x64). You might need this if you lose your original SYMBOLS file (or PDB file) and you want to de-obfuscate a stack trace. ## Read an obfuscated stack trace To debug a stack trace created by an obfuscated app, use the following steps to make it human readable: :::note On Windows x64, \`--split-debug-info\` generates a PDB file instead of a SYMBOLS file, and the \`flutter symbolize\` command doesn't support PDB files. To read a stack trace from an obfuscated Windows x64 build, load the PDB file in a Windows debugger such as WinDbg. ::: 1. Find the matching SYMBOLS file. For example, a crash from an Android arm64 device would need \`app.android-arm64.symbols\`. 1. Provide both the stack trace (stored in a file) and the SYMBOLS file to the \`flutter symbolize\` command. \`\`\`console $ flutter symbolize \ -i \ -d \`\`\` \* \`\`: The file path for the stacktrace. For example, \`???\`. \* \`\`: The file path for the symbols file that contains the obfuscated symbols. For example, \`out/android/app.android-arm64.symbols\`. For more information about the \`symbolize\` command, run \`flutter symbolize -h\`. ## Read an obfuscated name You can generate a JSON file that contains an obfuscation map. An obfuscation map is a JSON array with pairs of original names and obfuscated names. For example, \`\["MaterialApp", "ex", "Scaffold", "ey"]\`, where \`ex\` is the obfuscated name of \`MaterialApp\`. To generate an obfuscation map, use the following command: \`\`\`console $ flutter build \ --obfuscate \ --split-debug-info=/ \ --extra-gen-snapshot-options=--save-obfuscation-map=/ \`\`\` \* \`\`: The build target. For example, \`apk\`. \* \`\`: The directory where the symbols should be placed. For example, \`out/android\` \* \`\`: The file path where the JSON obfuscation map should be placed. For example, \`out/android/map.json\` ## Caveat Be aware of the following when coding an app that will eventually be an obfuscated binary. \* Code that relies on matching specific class, function, or library names will fail. For example, the following call to \`expect()\` won't work in an obfuscated binary:

<!--?code-excerpt "lib/main.dart (Expect)"?-->

\`\`\`dart expect(foo.runtimeType.toString(), equals('Foo')); \`\`\` \* Enum names are not obfuscated currently.

----
url: https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.59.0.md
----

\# DevTools 2.59.0 release notes > Release notes for Dart and Flutter DevTools version 2.59.0. The 2.59.0 release of the Dart and Flutter DevTools includes the following changes among other general improvements. To learn more about DevTools, check out the \[DevTools overview]\(/tools/devtools). ## General updates - Fixed a \`RangeError\` thrown by \`SplitPane\` when the parent rebuilt the widget with a different number of children. For example, when toggling a panel in or out of the layout. - \[#9822]\(https\://github.com/flutter/devtools/pull/9822) ## Inspector updates - Fixed an issue where hover tooltips in the widget tree were being clipped by the window boundaries. - \[#9823]\(https\://github.com/flutter/devtools/pull/9823) ## Full commit history To find a complete list of changes in this release, check out the \[DevTools git log]\(https\://github.com/flutter/devtools/tree/v2.59.0).

----
url: https://docs.flutter.dev/learn/pathway
----

[Watch on YouTube in a new tab: "Welcome to the Dart and Flutter Getting Started tutorial"](https://www.youtube.com/watch/2kjp9yAKpT4)

Welcome to the Dart and Flutter Getting Started pathway. In it, you'll set up your development environment, learn how to write Dart code, and build three small Flutter apps step-by-step. This learning pathway spans the Dart and Flutter websites, YouTube, and your IDE. By the end, you'll have a solid foundation in both Dart and Flutter.

1### Set up your development environmentkeyboard\_arrow\_up

Before you can start building Flutter apps, you'll need to set up your development environment. Follow the installation guide to get Flutter and all required dependencies installed on your machine.

[Get Started →](/learn/pathway/quick-install)

2### Complete the Dart Getting Started tutorialkeyboard\_arrow\_up

Flutter uses the Dart programming language. If you're new to Dart, complete this interactive tutorial to learn the fundamentals—variables, functions, classes, and more.

If you're in a hurry and already familiar with modern, object-oriented programming languages, you can safely skip this step.

[Dart Getting Started tutorial →](https://dart.dev/learn/tutorial)

3### Complete the Flutter Getting Started tutorialkeyboard\_arrow\_up

Now that you have Dart skills under your belt, dive into Flutter! This hands-on tutorial walks you through building three small Flutter apps step-by-step.

[Flutter Getting Started tutorial →](/learn/pathway/tutorial)

4### Understand how Flutter workskeyboard\_arrow\_up

Deepen your understanding of how Flutter works under the hood by watching this video series. Learn about the widget tree, rendering pipeline, and what makes Flutter unique. This series provides the knowledge you need to know to take full advantage of the framework.

[Watch: How Flutter Works →](/learn/pathway/how-flutter-works)

## Ready to start?

[#](#ready-to-start)

Get started by installing Flutter and setting up your development environment:

[Start with Step 1: Quick Install →](/learn/pathway/quick-install)

We're always looking for ways to improve the Getting Started experience. Moving forward, we plan to add more resources to this section of the website. We'd love to know what you think and what you'd like to see.

Please take a moment to share your feedback with us.

[Leave feedback](https://google.qualtrics.com/jfe/form/SV_6A9KxXR7XmMrNsy?page='index')

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-08-26. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/learn/pathway/index.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/learn/pathway\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/learn/pathway/index.md "Report an issue with this page").

----
url: https://docs.flutter.dev/data-and-backend/firebase
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Firebase](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

Firebase is a Backend-as-a-Service (BaaS) app development platform that provides hosted backend services such as cloud databases ([Cloud Firestore](https://firebase.google.com/docs/firestore) and [Firebase SQL Connect](https://firebase.google.com/docs/sql-connect)), cloud storage, authentication, crash reporting, AI features, remote configuration, and hosting for your static files.

## Flutter and Firebase resources

[#](#flutter-and-firebase-resources)

Firebase supports Flutter. To learn more, check out the following resources.

### Documentation

[#](#documentation)

* [Getting started with Firebase and Flutter](https://firebase.google.com/docs/flutter/setup)
* [Choose a database for your Flutter app](/data-and-backend/persistence/choose-a-database)
* [Firebase plugins](https://firebase.google.com/docs/flutter/setup#available-plugins)

### Blog Posts

[#](#blog-posts)

[Use Firebase to host your Flutter app on the web](https://blog.flutter.dev/must-try-use-firebase-to-host-your-flutter-app-on-the-web-852ee533a469)

### Tutorials

[#](#tutorials)

Get to know Firebase for Flutter

* [tutorial](https://codelabs.developers.google.com/codelabs/firebase-get-to-know-flutter)
* [video workshop](https://www.youtube.com/watch/4wunbF29Kkg)

## Flutter and Firebase community resources

[#](#flutter-and-firebase-community-resources)

The Flutter community created the following useful resources.

### Blog Posts

[#](#blog-posts-1)

[Building chat app with Flutter and Firebase](https://medium.com/flutter-community/building-a-chat-app-with-flutter-and-firebase-from-scratch-9eaa7f41782e)

### Videos

[#](#videos)

* [Using Firestore as a backend to your Flutter app](https://www.youtube.com/watch/DqJ_KjFzL9I\&t#38s)
* [Live Coding Firebase Authentication with Flutter](https://www.youtube.com/watch/OlcYP6UXlm8)
* [Flutter & Firebase Auth 01](https://www.youtube.com/watch/u_Lyx8KJWpg)
* [Flutter: Firebase Tutorial Part 1 - Auth and Sign in](https://www.youtube.com/watch/13-jNF984C0)

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-10-13. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/data-and-backend/firebase.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/data-and-backend/firebase\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/data-and-backend/firebase.md "Report an issue with this page").

----
url: https://docs.flutter.dev/learn/pathway/tutorial/create-an-app
----

Learn the first steps to building a Flutter app, from creating a project to understanding widgets and hot reload.

[Watch on YouTube in a new tab: "First steps with Flutter"](https://www.youtube.com/watch/sE1M2EayFes)

terminal Create a new Flutter project using the CLI

account\_tree Understand widgets and the widget tree

bolt Run your app and use hot reload

***

## Steps

1### What you'll buildkeyboard\_arrow\_up

In this first section of the Flutter tutorial, you'll build the core UI of an app called 'Birdle', a game similar to [Wordle, the popular New York Times game](https://www.nytimes.com/games/wordle/index.html).

By the end of this tutorial, you'll have learned the fundamentals of building Flutter UIs, and your app will look like the following screenshot (and it'll even mostly work 😀).

2### Create a new Flutter projectkeyboard\_arrow\_up

The first step to building Flutter apps is to create a new project. You create new apps with the [Flutter CLI tool](/reference/flutter-cli), installed as part of the Flutter SDK.

Open your terminal or command prompt and run the following command to create a new Flutter project:

```
$ flutter create birdle --empty
```

This creates a new Flutter project using the minimal "empty" template.

3### Examine the codekeyboard\_arrow\_up

In your IDE, open the file at `lib/main.dart`. Starting from the top, you'll see this code.

lib/main.dart

dart

```
void main() {
  runApp(const MainApp());
}
```

The `main` function is the entry point to any Dart program, and a Flutter app is just a **Dart** program. The `runApp` method is part of the Flutter SDK, and it takes a **widget** as an argument. In this case, an instance of the `MainApp` widget is being passed in.

Just below the `main` function, you'll find the `MainApp` class declaration.

dart

```
class MainApp extends StatelessWidget {
  const MainApp({super.key});
​
  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: Scaffold(
        body: Center(
          child: Text('Hello World!'),
        ),
      ),
    );
  }
}
```

`MainApp` is the **root widget**, as it's the widget that's passed into `runApp`. Within this widget, there's a `build` method that returns another widget called `MaterialApp`. Essentially, this is what a Flutter app is: a composition of widgets that make up a tree structure called the **widget tree.**

Your job as a Flutter developer is to compose widgets from the SDK into larger, custom widgets that display a UI.

At the moment, the widget tree is quite simple:

4### Run your appkeyboard\_arrow\_up

1. In your terminal, navigate to the root directory of your created Flutter app:

   ```
   $ cd birdle
   ```

2. Run the app using the Flutter CLI tool.

   ```
   $ flutter run -d chrome
   ```

   The app will build and launch in a new instance of Chrome.

5### Use hot reloadkeyboard\_arrow\_up

**Stateful hot reload**, if you haven't heard of it, allows a running Flutter app to re-render updated business logic or UI code in less than a second – all without losing your place in the app.

In your IDE, open the `main.dart` file and navigate to line \~15 and find this code:

dart

```
child: Text('Hello World!'),
```

Change the text inside the string to anything you want. Then, hot-reload your app by pressing `r` in the terminal where the app is running. The running app should instantly show your updated text.

6### Reviewkeyboard\_arrow\_up

terminal Created your first Flutter project

You used `flutter create` with the `--empty` flag to scaffold a minimal Flutter project. The CLI generates the project structure and boilerplate code needed to get started.

account\_tree Explored the widget tree

Flutter UIs are built by composing **widgets** into a tree structure. The `runApp` function takes a root widget, and that widget's `build` method returns other widgets, forming the **widget tree**. Your job as a Flutter developer is to compose these widgets into custom UIs.

bolt Ran your app with hot reload

You ran your app with `flutter run` and experienced **stateful hot reload**, which lets you see code changes reflected in under a second without losing app state. Press `r` in the terminal to trigger a hot reload.

7### Test yourselfkeyboard\_arrow\_up

### Create an App Quiz

1 / 2

**What is the purpose of the `runApp` function in a Flutter application?**

1. It compiles the Dart code into native machine code.

   Not quite.

   Compilation happens before the app runs; `runApp` starts the Flutter framework with a root widget.

2. It takes a widget as an argument and makes it the root of the widget tree.

   That's right!

   The `runApp` function inflates the given widget and attaches it to the screen, making it the root of the widget tree.

3. It creates the `main.dart` file for the project.

   Not quite.

   The file is created by `flutter create`; `runApp` is called at runtime.

4. It downloads Flutter dependencies from the internet.

   Not quite.

   Dependencies are managed by `flutter pub get`, not `runApp`.

**How do you trigger a hot reload while a Flutter app is running in the terminal?**

1. Press `h` in the terminal.

   Not quite.

   Pressing `h` shows help options; `r` triggers hot reload.

2. Press `r` in the terminal.

   That's right!

   Pressing `r` in the terminal where the app is running triggers a hot reload.

3. Stop and restart the app with `flutter run`.

   Not quite.

   A full restart is not needed; hot reload is faster.

4. Save the file and wait for automatic reload.

   Not quite.

   By default, you need to press `r` to trigger hot reload in the terminal.

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/learn/pathway/tutorial/create-an-app.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/learn/pathway/tutorial/create-an-app\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/learn/pathway/tutorial/create-an-app.md "Report an issue with this page").

----
url: https://docs.flutter.dev/tools/editors
----

You can build apps with Flutter using any text editor or integrated development environment (IDE) combined with Flutter's command-line tools.

The Flutter team recommends using an editor that supports a Flutter extension or plugin, like VS Code and Android Studio. These plugins provide code completion, syntax highlighting, widget editing assists, debug support, and much more.

## Local editors

[#](#local-editors)

The Flutter team supports plugins for VS Code, Android Studio, and IntelliJ. The plugins provide extensive development and debugging support as well as deep integrations with the [Dart analyzer](https://dart.dev/tools/analysis) and [Dart and Flutter DevTools](/tools/devtools).

[Visual Studio Code](/tools/vs-code)

[Develop and debug Flutter apps in a streamlined and customizable code editor.](/tools/vs-code)

[Android Studio and IntelliJ](/tools/android-studio)

[Develop and debug Flutter apps in an IDE with rich language support and integrated tooling.](/tools/android-studio)

## Online editors

[#](#online-editors)

You can quickly try Flutter online without any local setup with one of the following editors.

[DartPad open\_in\_new](https://dartpad.dev)

[Quickly build and run simple single-file Flutter apps on the web.](https://dartpad.dev)

## Other editors

[#](#other-editors)

You can develop Dart and Flutter apps using any other text editor and terminal.

Depending on the editor, you can integrate the Dart SDK's support for the [Language Server Protocol](https://github.com/dart-lang/sdk/tree/main/pkg/analysis_server/tool/lsp_spec/README.md) and the [Debug Adapter Protocol](https://github.com/dart-lang/sdk/blob/main/third_party/pkg/dap/tool/README.md) to enable rich code editing and debugging features for both Dart and Flutter.

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-07-31. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/editors.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/tools/editors\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/editors.md "Report an issue with this page").

----
url: https://docs.flutter.dev/ui/animations/hero-animations
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Hero animations](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

You've probably seen hero animations many times. For example, a screen displays a list of thumbnails representing items for sale. Selecting an item flies it to 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 might want to watch this one-minute video introducing the `Hero` widget:

[Watch on YouTube in a new tab: "Hero | Flutter widget of the week"](https://www.youtube.com/watch/Be9UH1kXFDw)

This guide demonstrates how to build standard hero animations, and hero animations that transform the image from a circular shape to a square shape during flight.

## Hero animation styles

[#](#hero-animation-styles)

You can create hero animations in Flutter with `Hero` widgets. As the hero animates from the source to the destination route, the destination route (minus the hero) fades into view. Typically, heroes are small parts of the UI, like images, that both routes have in common. From the user's perspective, the hero "flies" between the routes. This guide shows how to create the following hero animations:

### Standard animations

[#](#standard-hero-animation-preview)

A *standard hero animation* flies the hero from one route to a new route, usually landing at a different location and with a different size.

The following video (recorded at slow speed) shows a typical example. Tapping the flippers in the center of the route flies them to the upper left corner of a new, blue route, at a smaller size. Tapping the flippers in the blue route (or using the device's back-to-previous-route gesture) flies the flippers back to the original route.

[Watch on YouTube in a new tab: "Standard hero animation in Flutter"](https://www.youtube.com/watch/CEcFnqRDfgw)

### Radial animations

[#](#radial-hero-animation-preview)

In a *radial hero animation*, as the hero flies between routes its shape appears to change from circular to rectangular.

The following video (recorded at slow speed), shows an example of a radial hero animation. At the start, a row of three circular images appears at the bottom of the route. Tapping any of the circular images flies that image to a new route that displays it with a square shape. Tapping the square image flies the hero back to the original route, displayed with a circular shape.

[Watch on YouTube in a new tab: "Radial hero animation in Flutter"](https://www.youtube.com/watch/LWKENpwDKiM)

Before moving to the sections specific to [standard](#standard-hero-animations) or [radial](#radial-hero-animations) hero animations, read [basic structure of a hero animation](#basic-structure) to learn how to structure hero animation code, and [behind the scenes](#behind-the-scenes) to understand how Flutter performs a hero animation.

[]()

## Basic structure of a hero animation

[#](#basic-structure-of-a-hero-animation)

Hero animations are implemented using two [`Hero`](https://api.flutter.dev/flutter/widgets/Hero-class.html) widgets: one describing the widget in the source route, and another describing the widget in the destination route. From the user's point of view, the hero appears to be shared, and only the programmer needs to understand this implementation detail. Hero animation code has the following structure:

1. Define a starting `Hero` widget, referred to as the *source hero*. The hero specifies its graphical representation (typically an image), and an identifying tag, and is in the currently displayed widget tree as defined by the source route.
2. Define an ending `Hero` widget, referred to as the *destination hero*. This hero also specifies its graphical representation, and the same tag as the source hero. It's **essential that both hero widgets are created with the same tag**, typically an object that represents the underlying data. For best results, the heroes should have virtually identical widget trees.
3. Create a route that contains the destination hero. The destination route defines the widget tree that exists at the end of the animation.
4. Trigger the animation by pushing the destination route on the `Navigator` stack. The `Navigator` push and pop operations trigger a hero animation for each pair of heroes with matching tags in the source and destination routes.

Flutter calculates the tween that animates the `Hero` widget's bounds from the starting point to the endpoint (interpolating size and position), and performs the animation in an overlay.

The next section describes Flutter's process in greater detail.

## Behind the scenes

[#](#behind-the-scenes)

The following describes how Flutter performs the transition from one route to another.

Before transition, the source hero waits in the source route's widget tree. The destination route does not yet exist, and the overlay is empty.

***

Pushing a route to the `Navigator` triggers the animation. At `t=0.0`, Flutter does the following:

* Calculates the destination hero's path, offscreen, using the curved motion as described in the Material motion spec. Flutter now knows where the hero ends up.

* Places the destination hero in the overlay, at the same location and size as the *source* hero. Adding a hero to the overlay changes its Z-order so that it appears on top of all routes.

* Moves the source hero offscreen.

***

As the hero flies, its rectangular bounds are animated using [`Tween<Rect>`](https://api.flutter.dev/flutter/animation/Tween-class.html), specified by the [`createRectTween`](https://api.flutter.dev/flutter/widgets/CreateRectTween.html) property of the `Hero` widget. By default, Flutter uses an instance of [`MaterialRectArcTween`](https://pub.dev/documentation/material_ui/latest/material_ui/MaterialRectArcTween-class.html), which animates the rectangle's opposing corners along a curved path. (Refer to [Radial hero animations](#radial-hero-animations) for an example that uses a different `Tween` animation.)

***

When the flight completes:

* Flutter moves the hero widget from the overlay to the destination route. The overlay is now empty.

* The destination hero appears in its final position in the destination route.

* The source hero is restored to its route.

***

Popping the route performs the same process, animating the hero back to its size and location in the source route.

### Essential classes

[#](#essential-classes)

The examples in this guide use the following classes to implement hero animations:

* [`Hero`](https://api.flutter.dev/flutter/widgets/Hero-class.html)

  The widget that flies from the source to the destination route. Define one `Hero` for the source route and another for the destination route, and assign each the same tag. Flutter animates pairs of heroes with matching tags.

* [`InkWell`](https://pub.dev/documentation/material_ui/latest/material_ui/InkWell-class.html)

  Specifies what happens when tapping the hero. The `onTap()` callback of the `InkWell` widget builds the new route and pushes it to the `Navigator` stack.

* [`Navigator`](https://api.flutter.dev/flutter/widgets/Navigator-class.html)

  The `Navigator` manages a stack of routes. Pushing a route on or popping a route from the `Navigator` stack triggers the animation.

* [`Route`](https://api.flutter.dev/flutter/widgets/Route-class.html)

  Specifies a screen or page. Most apps, beyond the most basic, have multiple routes.

## Standard hero animations

[#](#standard-hero-animations)

[]()

### What's going on?

[#](#whats-going-on)

Flying an image from one route to another is straightforward to implement using Flutter's `Hero` widget. When using `MaterialPageRoute` to specify the new route, the image flies along a curved path, as described by the [Material Design motion spec](https://m2.material.io/design/motion/understanding-motion.html#principles).

The complete code and images for this section are in the [standard hero animation example](https://github.com/flutter/website/tree/main/examples/animation/hero_animation). To explore the animation yourself, [set up and run the example](#run-the-examples), then try the following:

* Tap on the home route's photo to fly the image to a new route showing the same photo at a different location and scale.
* Return to the previous route by tapping the image, or by using the device's back-to-the-previous-route gesture.
* Slow the transition further by increasing the [`timeDilation`](https://api.flutter.dev/flutter/scheduler/timeDilation.html) value.

### PhotoHero class

[#](#photohero-class)

The custom `PhotoHero` class maintains the hero, and its size, image, and behavior when tapped. The `PhotoHero` builds the following widget tree:

Here's the code:

dart

```
import 'package:flutter/scheduler.dart' show timeDilation;
import 'package:material_ui/material_ui.dart';
​
class PhotoHero extends StatelessWidget {
  const PhotoHero({
    super.key,
    required this.photo,
    this.onTap,
    required this.width,
  });
​
  final String photo;
  final VoidCallback? onTap;
  final double width;
​
  @override
  Widget build(BuildContext context) {
    return SizedBox(
      width: width,
      child: Hero(
        tag: photo,
        child: Material(
          color: Colors.transparent,
          child: InkWell(
            onTap: onTap,
            child: Image.asset(photo, fit: BoxFit.contain),
          ),
        ),
      ),
    );
  }
}
```

Key information:

* The starting route is implicitly pushed by `MaterialApp` when `HeroAnimation` is provided as the app's `home` property.
* An `InkWell` wraps the image, making it easy to add a tap gesture to both the source and destination heroes.
* Defining the `Material` widget with a transparent color enables the image to "pop out" of the background as it flies to its destination.
* The `SizedBox` specifies the hero's size at the start and end of the animation.
* Setting the `Image` widget's `fit` property to `BoxFit.contain` ensures that the image is as large as possible during the transition without changing its aspect ratio.

### HeroAnimation class

[#](#heroanimation-class)

The `HeroAnimation` class creates the source and destination `PhotoHero` widgets, and sets up the transition.

Here's the code:

dart

```
class HeroAnimation extends StatelessWidget {
  const HeroAnimation({super.key});
​
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Basic Hero Animation')),
      body: Center(
        child: PhotoHero(
          photo: 'images/flippers.png',
          width: 300,
          onTap: () {
            Navigator.of(context).push(
              MaterialPageRoute<void>(
                builder: (context) {
                  return Scaffold(
                    appBar: AppBar(title: const Text('Flippers Page')),
                    body: Container(
                      // Set background to blue to emphasize that it's a new route.
                      color: Colors.lightBlueAccent,
                      padding: const EdgeInsets.all(16),
                      alignment: Alignment.topLeft,
                      child: PhotoHero(
                        photo: 'images/flippers.png',
                        width: 100,
                        onTap: () {
                          Navigator.of(context).pop();
                        },
                      ),
                    ),
                  );
                },
              ),
            );
          },
        ),
      ),
    );
  }
}
​
void main() {
  timeDilation = 5; // 1 means normal animation speed.
  runApp(const MaterialApp(home: HeroAnimation()));
}
```

Key information:

* When the user taps the `InkWell` containing the source hero, the code creates the destination route using `MaterialPageRoute`. Pushing the destination route to the `Navigator` stack triggers the animation.
* The `Container` positions the `PhotoHero` in the destination route's top-left corner, below the `AppBar`.
* The `onTap()` method for the destination `PhotoHero` pops the `Navigator` stack, triggering the animation that flies the `Hero` back to the original route.
* Use the `timeDilation` property to slow the transition while debugging.

***

## Radial hero animations

[#](#radial-hero-animations)

Flying a hero from one route to another as it transforms from a circular shape to a rectangular shape is a visual effect that you can implement using `Hero` widgets. To accomplish this, the code animates the intersection of two clip shapes: a circle and a square. Throughout the animation, the circle clip (and the image) scales from `minRadius` to `maxRadius`, while the square clip maintains constant size. At the same time, the image flies from its position in the source route to its position in the destination route. For visual examples of this transition, consult [Radial transformation](https://web.archive.org/web/20180223140424/https://material.io/guidelines/motion/transforming-material.html) in the Material motion spec.

[]()

### What's going on?

[#](#whats-going-on-1)

The following diagram shows the clipped image at the beginning (`t = 0.0`), and the end (`t = 1.0`) of the animation.

The blue gradient (representing the image), indicates where the clip shapes intersect. At the beginning of the transition, the result of the intersection is a circular clip ([`ClipOval`](https://api.flutter.dev/flutter/widgets/ClipOval-class.html)). During the transformation, the `ClipOval` scales from `minRadius` to `maxRadius` while the [`ClipRect`](https://api.flutter.dev/flutter/widgets/ClipRect-class.html) maintains a constant size. At the end of the transition the intersection of the circular and rectangular clips yield a rectangle that's the same size as the hero widget. In other words, at the end of the transition the image is no longer clipped.

The complete code and images for this section, including the route setup that isn't shown on this page, are in the [radial hero animation example](https://github.com/flutter/website/tree/main/examples/animation/radial_hero_animation). To explore the animation yourself, [set up and run the example](#run-the-examples), then try the following:

* Tap on one of the three circular thumbnails to animate the image to a larger square positioned in the middle of a new route that obscures the original route.
* Return to the previous route by tapping the image, or by using the device's back-to-the-previous-route gesture.
* Slow the transition further by increasing the [`timeDilation`](https://api.flutter.dev/flutter/scheduler/timeDilation.html) value.

### Photo class

[#](#photo-class)

The `Photo` class builds the widget tree that holds the image:

dart

```
class Photo extends StatelessWidget {
  const Photo({super.key, required this.photo, this.onTap});
​
  final String photo;
  final VoidCallback? onTap;
​
  @override
  Widget build(BuildContext context) {
    return Material(
      // Slightly opaque color appears where the image has transparency.
      color: Theme.of(context).primaryColor.withValues(alpha: 0.25),
      child: InkWell(
        onTap: onTap,
        child: Image.asset(photo, fit: BoxFit.contain),
      ),
    );
  }
}
```

Key information:

* The `InkWell` captures the tap gesture. The calling function passes the `onTap()` function to the `Photo` constructor.
* During flight, the `InkWell` draws its splash on its first `Material` ancestor.
* The `Material` widget has a slightly opaque color, so the transparent portions of the image are rendered with color. This ensures that the circle-to-square transition is easy to see, even for images with transparency.
* The `Photo` class does not include the `Hero` in its widget tree. For the animation to work, the hero wraps the `RadialExpansion` widget.

### RadialExpansion class

[#](#radialexpansion-class)

The `RadialExpansion` widget, the core of the demo, builds the widget tree that clips the image during the transition. The clipped shape results from the intersection of a circular clip (that grows during flight), with a rectangular clip (that remains a constant size throughout).

To do this, it builds the following widget tree:

Here's the code:

dart

```
class RadialExpansion extends StatelessWidget {
  const RadialExpansion({super.key, required this.maxRadius, this.child})
    : clipRectSize = 2 * (maxRadius / math.sqrt2);
​
  final double maxRadius;
  final double clipRectSize;
  final Widget? child;
​
  @override
  Widget build(BuildContext context) {
    return ClipOval(
      child: Center(
        child: SizedBox(
          width: clipRectSize,
          height: clipRectSize,
          child: ClipRect(
            child: child, // Photo
          ),
        ),
      ),
    );
  }
}
```

Key information:

* The hero wraps the `RadialExpansion` widget.

* As the hero flies, its size changes and, because it constrains its child's size, the `RadialExpansion` widget changes size to match.

* The `RadialExpansion` animation is created by two overlapping clips.

* The example defines the tweening interpolation using [`MaterialRectCenterArcTween`](https://pub.dev/documentation/material_ui/latest/material_ui/MaterialRectCenterArcTween-class.html). The default flight path for a hero animation interpolates the tweens using the corners of the heroes. This approach affects the hero's aspect ratio during the radial transformation, so the new flight path uses `MaterialRectCenterArcTween` to interpolate the tweens using the center point of each hero.

  Here's the code:

  dart

  ```
  static RectTween _createRectTween(Rect? begin, Rect? end) {
    return MaterialRectCenterArcTween(begin: begin, end: end);
  }
  ```

  The hero's flight path still follows an arc, but the image's aspect ratio remains constant.

***

## Run the examples

[#](#run-the-examples)

Each example in this guide is a complete app: the [standard hero animation example](https://github.com/flutter/website/tree/main/examples/animation/hero_animation) and the [radial hero animation example](https://github.com/flutter/website/tree/main/examples/animation/radial_hero_animation). To run the examples yourself:

1. [Create a new Flutter app](/reference/create-new-app).

2. Add the [`material_ui`](https://pub.dev/packages/material_ui) package as a dependency by running `flutter pub add material_ui`.

3. Replace `lib/main.dart` with the example's `lib/main.dart` file.

4. Download the following images into an `images` directory at the root of your project: [`beachball.png`](https://raw.githubusercontent.com/flutter/website/main/examples/animation/radial_hero_animation/images/beachball.png), [`binoculars.png`](https://raw.githubusercontent.com/flutter/website/main/examples/animation/radial_hero_animation/images/binoculars.png), [`chair.png`](https://raw.githubusercontent.com/flutter/website/main/examples/animation/radial_hero_animation/images/chair.png), and [`flippers.png`](https://raw.githubusercontent.com/flutter/website/main/examples/animation/hero_animation/images/flippers.png).

5. Declare the images as [assets](/ui/assets/assets-and-images#specifying-assets) by adding an `assets` entry to the existing `flutter` section of the project's `pubspec.yaml` file:

   pubspec.yaml

   yaml

   ```
   flutter:
     uses-material-design: true
     assets:
       - images/
   ```

6. Run the app with `flutter run`.

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-10-02. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/ui/animations/hero-animations.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/ui/animations/hero-animations\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/ui/animations/hero-animations.md "Report an issue with this page").

----
url: https://docs.flutter.dev/ui/widgets/interaction
----

Respond to touch events and route users to different views.

## Touch interactions

[Absorb​Pointer](https://api.flutter.dev/flutter/widgets/AbsorbPointer-class.html)

[A widget that absorbs pointers during hit testing. When absorbing is true, this widget prevents its subtree from receiving pointer events by terminating hit testing...](https://api.flutter.dev/flutter/widgets/AbsorbPointer-class.html)

[Dismissible](https://api.flutter.dev/flutter/widgets/Dismissible-class.html)

[A widget that can be dismissed by dragging in the indicated direction. Dragging or flinging this widget in the DismissDirection causes the child to slide...](https://api.flutter.dev/flutter/widgets/Dismissible-class.html)

[Drag​Target](https://api.flutter.dev/flutter/widgets/DragTarget-class.html)

[A widget that receives data when a Draggable widget is dropped. When a draggable is dragged on top of a drag target, the drag target...](https://api.flutter.dev/flutter/widgets/DragTarget-class.html)

[Draggable](https://api.flutter.dev/flutter/widgets/Draggable-class.html)

[A widget that can be dragged from to a DragTarget. When a draggable widget recognizes the start of a drag gesture, it displays a feedback...](https://api.flutter.dev/flutter/widgets/Draggable-class.html)

[Draggable​Scrollable​Sheet](https://api.flutter.dev/flutter/widgets/DraggableScrollableSheet-class.html)

[A container for a Scrollable that responds to drag gestures by resizing the scrollable until a limit is reached, and then scrolling.](https://api.flutter.dev/flutter/widgets/DraggableScrollableSheet-class.html)

[Gesture​Detector](https://api.flutter.dev/flutter/widgets/GestureDetector-class.html)

[A widget that detects gestures. Attempts to recognize gestures that correspond to its non-null callbacks. If this widget has a child, it defers to that...](https://api.flutter.dev/flutter/widgets/GestureDetector-class.html)

[Ignore​Pointer](https://api.flutter.dev/flutter/widgets/IgnorePointer-class.html)

[A widget that is invisible during hit testing. When ignoring is true, this widget (and its subtree) is invisible to hit testing. It still consumes...](https://api.flutter.dev/flutter/widgets/IgnorePointer-class.html)

[Interactive​Viewer](https://api.flutter.dev/flutter/widgets/InteractiveViewer-class.html)

[A widget that enables pan and zoom interactions with its child.](https://api.flutter.dev/flutter/widgets/InteractiveViewer-class.html)

[Long​Press​Draggable](https://api.flutter.dev/flutter/widgets/LongPressDraggable-class.html)

[Makes its child draggable starting from long press.](https://api.flutter.dev/flutter/widgets/LongPressDraggable-class.html)

[Scrollable](https://api.flutter.dev/flutter/widgets/Scrollable-class.html)

[Scrollable implements the interaction model for a scrollable widget, including gesture recognition, but does not have an opinion about how the viewport, which actually displays...](https://api.flutter.dev/flutter/widgets/Scrollable-class.html)

## Routing

[Hero](https://api.flutter.dev/flutter/widgets/Hero-class.html)

[A widget that marks its child as being a candidate for hero animations.](https://api.flutter.dev/flutter/widgets/Hero-class.html)

[Navigator](https://api.flutter.dev/flutter/widgets/Navigator-class.html)

[A widget that manages a set of child widgets with a stack discipline. Many apps have a navigator near the top of their widget hierarchy...](https://api.flutter.dev/flutter/widgets/Navigator-class.html)

Find more widgets in the [widget catalog](/ui/widgets).

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-09-29. [Report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/ui/widgets/interaction "Report an issue with this page").

----
url: https://docs.flutter.dev/ui/design/cupertino.md
----

\# Cupertino design for Flutter > Learn about Cupertino design for Flutter. The Flutter Cupertino package is a collection of widgets that implement Apple's iOS design language for Flutter apps. The widgets have a similar look and feel to iOS, including rounded corners, gradients, and minimalistic design. They also include iOS interactions and animations. Previously, Cupertino was part of the core Flutter SDK, but it has been decoupled from the framework. You can access Cupertino from the \[\`cupertino\_ui\`]\[] package on pub.dev. \[\`cupertino\_ui\`]: https\://pub.dev/packages/cupertino\_ui The following 15-minute video provides a high-level glimpse of the Cupertino package: To see some Cupertino widgets in action, the following videos from the Widget of the Week series cover a few of them.## More information {:.no\_toc} To learn more about using the Cupertino package with Flutter, check out the following resources: \* The (mostly visual) \[Cupertino widget catalog]\[] \* The \[Cupertino library]\[] page on pub.dev \* The \[Cupertino API examples]\[] on the flutter/flutter repo. (\[Instructions]\[]) For example, to run \`CupertinoSwitch\`: \`\`\`console cd path/to/flutter cd examples/api flutter run lib/cupertino/switch/cupertino\_switch.0.dart \`\`\` \[Cupertino API examples]: https\://github.com/flutter/flutter/tree/main/examples/api/lib/cupertino \[Cupertino library]: https\://pub.dev/packages/cupertino\_ui \[Cupertino widget catalog]: /ui/widgets/cupertino \[Instructions]: https\://github.com/flutter/flutter/tree/main/examples/api#api-example-code

----
url: https://docs.flutter.dev/app-architecture/case-study.md
----

\# Architecture case study > A walk-through of a Flutter app that implements the MVVM architectural pattern. The code examples in this guide are from the \[Compass sample application]\[], an app that helps users build and book itineraries for trips. It's a robust sample application with many features, routes, and screens. The app communicates with an HTTP server, has development and production environments, includes brand-specific styling, and contains high test coverage. In these ways and more, it simulates a real-world, feature-rich Flutter application.The Compass app's architecture most resembles the \[MVVM architectural pattern]\[] as described in Flutter's \[app architecture guidelines]\[]. This architecture case study demonstrates how to implement those guidelines by walking through the "Home" feature of the compass app. If you aren't familiar with MVVM, you should read those guidelines first. The Home screen of the Compass app displays user account information and a list of the user's saved trips. From this screen you can log out, open detailed trip pages, delete saved trips, and navigate to the first page of the core app flow, which allows the user to build a new itinerary. In this case study, you'll learn the following: \* How to implement Flutter's \[app architecture guidelines]\[] using repositories and services in the \[data layer]\[] and the MVVM architectural pattern in the \[UI layer]\[] \* How to use the \[Command pattern]\[] to safely render UI as data changes \* How to use \[\`ChangeNotifier\`]\[] and \[\`Listenable\`]\[] objects to manage state \* How to implement \[Dependency Injection]\[] using \`package:provider\` \* How to \[set up tests]\[] when following the recommended architecture \* Effective \[package structure]\[] for large Flutter apps This case-study was written to be read in order. Any given page might reference the previous pages. The code examples in this case-study include all the details needed to understand the architecture, but they're not complete, runnable snippets. If you prefer to follow along with the full app, you can find it on \[GitHub]\[]. ## Package structure Well-organized code is easier for multiple engineers to work on with minimal code conflicts and is easier for new engineers to navigate and understand. Code organization both benefits and benefits from well-defined architecture. There are two popular means of organizing code: 1. By feature - The classes needed for each feature are grouped together. For example, you might have an \`auth\` directory, which would contain files like \`auth\_viewmodel.dart\`, \`login\_usecase.dart\`, \`logout\_usecase.dart\`, \`login\_screen.dart\`, \`logout\_button.dart\`, etc. 2. By type - Each "type" of architecture is grouped together. For example, you might have directories such as \`repositories\`, \`models\`, \`services\`, and \`viewmodels\`. The architecture recommended in this guide lends itself to a combination of the two. Data layer objects (repositories and services) aren't tied to a single feature, while UI layer objects (views and view models) are. The following is how the code is organized within the Compass application. - lib/ - ui/ - core/ - ui/ - - themes/ - / - view\_models/ - .dart - widgets/ - \_screen.dart - - domain/ - models/ - .dart - data/ - repositories/ - .dart - services/ - .dart - model/ - .dart - config/ - utils/ - routing/ - main\_staging.dart - main\_development.dart - main.dart - test/ // Contains unit and widget tests. - data/ - domain/ - ui/ - utils/ - testing/ // Contains mocks that other classes need to execute tests. - fakes/ - models/ Most of the application code lives in the \`data\`, \`domain\`, and \`ui\` folders. The data folder organizes code by type, because repositories and services can be used across different features and by multiple view models. The ui folder organizes the code by feature, because each feature has exactly one view and exactly one view model. Other notable features of this folder structure: \* The UI folder also contains a subdirectory named "core". Core contains widgets and theme logic that is shared by multiple views, such as buttons with your brand styling. \* The domain folder contains the application data types, because they're used by the data and ui layers. \* The app contains three "main" files, which act as different entry points to the application for development, staging, and production. \* There are two test-related directories at the same level as \`lib\`: \`test/\` has the test code, and its own structure matches \`lib/\`. \`testing/\` is a subpackage that contains mocks and other testing utilities which can be used in other packages' test code. The \`testing/\` folder could be described as a version of your app that you don't ship. It's the content that is tested. There's additional code in the compass app that doesn't pertain to architecture. For the full package structure, \[view it on GitHub]\[]. ## Other architecture options The example in this case-study demonstrates how one application abides by our recommended architectural rules, but there are many other example apps that could've been written. The UI of this app leans heavily on view models and \`ChangeNotifier\`, but it could've easily been written with streams, or with other libraries such as \[\`riverpod\`]\[], \[\`flutter\_bloc\`]\[], and \[\`signals\`]\[]. The communication between layers of this app handled everything with method calls, including polling for new data. It could've instead used streams to expose data from a repository to a view model and still abide by the rules covered in this guide. Even if you do follow this guide exactly, and choose not to introduce additional libraries, you have decisions to make: Will you have a domain layer? If so, how will you manage data access? The answer depends so much on an individual team's needs that there isn't a single right answer. Regardless of how you answer these questions, the principles in this guide will help you write scalable Flutter apps. And if you squint, aren't all architectures MVVM anyway? \[Compass sample application]: https\://github.com/flutter/samples/tree/main/compass\_app \[MVVM architectural pattern]: https\://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93viewmodel \[app architecture guidelines]: /app-architecture/guide \[data layer]: /app-architecture/case-study/data-layer \[UI layer]: /app-architecture/case-study/ui-layer \[Command pattern]: /app-architecture/case-study/ui-layer#command-objects \[\`ChangeNotifier\`]: https\://api.flutter.dev/flutter/foundation/ChangeNotifier-class.html \[\`Listenable\`]: https\://api.flutter.dev/flutter/foundation/Listenable-class.html \[Dependency Injection]: /app-architecture/case-study/dependency-injection \[set up tests]: /app-architecture/case-study/testing \[view it on GitHub]: https\://github.com/flutter/samples/tree/main/compass\_app \[GitHub]: https\://github.com/flutter/samples/tree/main/compass\_app \[\`riverpod\`]: https\://pub.dev/packages/riverpod \[\`flutter\_bloc\`]: https\://pub.dev/packages/flutter\_bloc \[\`signals\`]: https\://pub.dev/packages/signals \[package structure]: /app-architecture/case-study#package-structure ## Feedback As this section of the website is evolving, we \[welcome your feedback]\[]! \[welcome your feedback]: https\://google.qualtrics.com/jfe/form/SV\_4T0XuR9Ts29acw6?page="case-study/index"

----
url: https://docs.flutter.dev/platform-integration/android/chromeos.md
----

\# Targeting ChromeOS with Android > Platform-specific considerations for building for ChromeOS with Flutter. This page discusses considerations unique to building Android apps that support ChromeOS with Flutter. ## Flutter & ChromeOS tips & tricks For the current versions of ChromeOS, only certain ports from Linux are exposed to the rest of the environment. Here's an example of how to launch Flutter DevTools for an Android app with ports that will work: \`\`\`console $ flutter pub global run devtools --port 8000 $ cd path/to/your/app $ flutter run --observatory-port=8080 \`\`\` Then, navigate to http\://127.0.0.1:8000/# in your Chrome browser and enter the URL to your application. The last \`flutter run\` command you just ran should output a URL similar to the format of \`http\://127.0.0.1:8080/auth\_code=/\`. Use this URL and select "Connect" to start the Flutter DevTools for your Android app. #### Flutter ChromeOS lint analysis Flutter has ChromeOS-specific lint analysis checks to make sure that the app that you're building works well on ChromeOS. It looks for things like required hardware in your Android Manifest that aren't available on ChromeOS devices, permissions that imply requests for unsupported hardware, as well as other properties or code that would bring a lesser experience on these devices. To activate these, you need to create a new analysis\_options.yaml file in your project folder to include these options. (If you have an existing analysis\_options.yaml file, you can update it) \`\`\`yaml include: package:flutter/analysis\_options\_user.yaml analyzer: optional-checks: chrome-os-manifest-checks \`\`\` To run these from the command line, use the following command: \`\`\`console $ flutter analyze \`\`\` Sample output for this command might look like: \`\`\`console Analyzing ... warning • This hardware feature is not supported on ChromeOS • android/app/src/main/AndroidManifest.xml:4:33 • unsupported\_chrome\_os\_hardware \`\`\`

----
url: https://docs.flutter.dev/cookbook/forms/text-field-changes.md
----

\# Handle changes to a text field > How to detect changes to a text field.

<!--?code-excerpt path-base="cookbook/forms/text_field_changes/"?-->

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 where you want to update the results as the user types. How do you run a callback function every time the text changes? With Flutter, you have two options: 1. Supply an \`onChanged()\` callback to a \`TextField\` or a \`TextFormField\`. 1. Use a \`TextEditingController\`. ## 1. Supply an \`onChanged()\` callback to a \`TextField\` or a \`TextFormField\` The simplest approach is to supply an \[\`onChanged()\`]\[] callback to a \[\`TextField\`]\[] or a \[\`TextFormField\`]\[]. Whenever the text changes, the callback is invoked. In this example, print the current value and length of the text field to the console every time the text changes. It's important to use \[\`characters\`]\[characters] when dealing with user input, as text might contain complex characters. This ensures that every character is counted correctly as it appears to the user.

<!--?code-excerpt "lib/main.dart (TextField1)"?-->

\`\`\`dart TextField( onChanged: (text) { print('First text field: $text (${text.characters.length})'); }, ), \`\`\` ## 2. Use a \`TextEditingController\` A more powerful, but more elaborate approach, is to supply a \[\`TextEditingController\`]\[] as the \[\`controller\`]\[] property of the \`TextField\` or a \`TextFormField\`. To be notified when the text changes, listen to the controller using the \[\`addListener()\`]\[] method using the following steps: 1. Create a \`TextEditingController\`. 1. Connect the \`TextEditingController\` to a text field. 1. Create a function to print the latest value. 1. Listen to the controller for changes. ### Create a \`TextEditingController\` Create a \`TextEditingController\`:

<!--?code-excerpt "lib/main_step1.dart (Step1)" remove="return Container();"?-->

\`\`\`dart // Define a custom Form widget. class MyCustomForm extends StatefulWidget { const MyCustomForm({super.key}); @override State createState() => \_MyCustomFormState(); } // Define a corresponding State class. // This class holds data related to the Form. class \_MyCustomFormState extends State { // Create a text controller. Later, 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. myController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { // Fill this out in the next step. } } \`\`\` :::note Remember to dispose of the \`TextEditingController\` when it's no longer needed. This ensures that you discard any resources used by the object. ::: ### Connect the \`TextEditingController\` to a text field Supply the \`TextEditingController\` to either a \`TextField\` or a \`TextFormField\`. Once you wire these two classes together, you can begin listening for changes to the text field.

<!--?code-excerpt "lib/main.dart (TextField2)"?-->

\`\`\`dart TextField(controller: myController), \`\`\` ### Create a function to print the latest value You need a function to run every time the text changes. Create a method in the \`\_MyCustomFormState\` class that prints out the current value of the text field.

<!--?code-excerpt "lib/main.dart (printLatestValue)"?-->

\`\`\`dart void \_printLatestValue() { final text = myController.text; print('Second text field: $text (${text.characters.length})'); } \`\`\` ### Listen to the controller for changes Finally, listen to the \`TextEditingController\` and call the \`\_printLatestValue()\` method when the text changes. Use the \[\`addListener()\`]\[] method for this purpose. Begin listening for changes when the \`\_MyCustomFormState\` class is initialized, and stop listening when the \`\_MyCustomFormState\` is disposed.

<!--?code-excerpt "lib/main.dart (init-state)"?-->

\`\`\`dart @override void initState() { super.initState(); // Start listening to changes. myController.addListener(\_printLatestValue); } \`\`\`

<!--?code-excerpt "lib/main.dart (dispose)"?-->

\`\`\`dart @override void dispose() { // Clean up the controller when the widget is removed from the widget tree. // This also removes the \_printLatestValue listener. myController.dispose(); super.dispose(); } \`\`\` ## Interactive example

<!--?code-excerpt "lib/main.dart"?-->

\`\`\`dartpad title="Flutter text field change hands-on example in DartPad" run="true" import 'package:flutter/material.dart'; void main() => runApp(const MyApp()); class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return const MaterialApp( title: 'Handle Text Field Changes', home: MyCustomForm(), ); } } // Define a custom Form widget. class MyCustomForm extends StatefulWidget { const MyCustomForm({super.key}); @override State createState() => \_MyCustomFormState(); } // Define a corresponding State class. // This class holds data related to the Form. class \_MyCustomFormState extends State { // Create a text controller and use it to retrieve the current value // of the TextField. final myController = TextEditingController(); @override void initState() { super.initState(); // Start listening to changes. myController.addListener(\_printLatestValue); } @override void dispose() { // Clean up the controller when the widget is removed from the widget tree. // This also removes the \_printLatestValue listener. myController.dispose(); super.dispose(); } void \_printLatestValue() { final text = myController.text; print('Second text field: $text (${text.characters.length})'); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('Handle Text Field Changes')), body: Padding( padding: const EdgeInsets.all(16), child: Column( children: \[ TextField( onChanged: (text) { print('First text field: $text (${text.characters.length})'); }, ), TextField(controller: myController), ], ), ), ); } } \`\`\` \[\`addListener()\`]: https\://api.flutter.dev/flutter/foundation/ChangeNotifier/addListener.html \[\`controller\`]: https\://pub.dev/documentation/material\_ui/latest/material\_ui/TextField/controller.html \[\`onChanged()\`]: https\://pub.dev/documentation/material\_ui/latest/material\_ui/TextField/onChanged.html \[\`TextField\`]: https\://pub.dev/documentation/material\_ui/latest/material\_ui/TextField-class.html \[\`TextEditingController\`]: https\://api.flutter.dev/flutter/widgets/TextEditingController-class.html \[\`TextFormField\`]: https\://pub.dev/documentation/material\_ui/latest/material\_ui/TextFormField-class.html \[characters]: https\://pub.dev/packages/characters

----
url: https://docs.flutter.dev/cookbook/design/drawer
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Add a drawer to a screen](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

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, use the [`Drawer`](https://pub.dev/documentation/material_ui/latest/material_ui/Drawer-class.html) widget in combination with a [`Scaffold`](https://pub.dev/documentation/material_ui/latest/material_ui/Scaffold-class.html) to create a layout with a Material Design drawer. This recipe uses the following steps:

1. Create a `Scaffold`.
2. Add a drawer.
3. Populate the drawer with items.
4. Close the drawer programmatically.

## 1. Create a `Scaffold`

[#](#1-create-a-scaffold)

To add a drawer to the app, wrap it in a [`Scaffold`](https://pub.dev/documentation/material_ui/latest/material_ui/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 example, create a `Scaffold` with a `drawer`:

dart

```
Scaffold(
  appBar: AppBar(title: const Text('AppBar without hamburger button')),
  drawer: // Add a Drawer here in the next step.
);
```

## 2. Add a drawer

[#](#2-add-a-drawer)

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](https://pub.dev/documentation/material_ui/latest/material_ui), which adheres to the Material Design spec.

dart

```
Scaffold(
  appBar: AppBar(title: const Text('AppBar with hamburger button')),
  drawer: Drawer(
    child: // Populate the Drawer in the next step.
  ),
);
```

## 3. Populate the drawer with items

[#](#3-populate-the-drawer-with-items)

Now that you have a `Drawer` in place, add content to it. For this example, use a [`ListView`](https://pub.dev/documentation/material_ui/latest/material_ui/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.

Populate the `ListView` with a [`DrawerHeader`](https://pub.dev/documentation/material_ui/latest/material_ui/DrawerHeader-class.html) and two [`ListTile`](https://pub.dev/documentation/material_ui/latest/material_ui/ListTile-class.html) widgets. For more information on working with Lists, see the [list recipes](/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
  // space to fit everything.
  child: ListView(
    // Important: Remove any padding from the ListView.
    padding: EdgeInsets.zero,
    children: [
      const DrawerHeader(
        decoration: BoxDecoration(color: Colors.blue),
        child: Text('Drawer Header'),
      ),
      ListTile(
        title: const Text('Item 1'),
        onTap: () {
          // Update the state of the app.
          // ...
        },
      ),
      ListTile(
        title: const Text('Item 2'),
        onTap: () {
          // Update the state of the app.
          // ...
        },
      ),
    ],
  ),
);
```

## 4. Open the drawer programmatically

[#](#4-open-the-drawer-programmatically)

Typically, you don't need to write any code to open a `drawer`, Because when the `leading` widget is null, the default implementation in `AppBar` is `DrawerButton`.

But if you want to have free control of the `drawer`. You can do this by using the `Builder` call `Scaffold.of(context).openDrawer()`.

dart

```
Scaffold(
  appBar: AppBar(
    title: const Text('AppBar with hamburger button'),
    leading: Builder(
      builder: (context) {
        return IconButton(
          icon: const Icon(Icons.menu),
          onPressed: () {
            Scaffold.of(context).openDrawer();
          },
        );
      },
    ),
  ),
  drawer: Drawer(
    child: // Populate the Drawer in the last step.
  ),
);
```

## 5. Close the drawer programmatically

[#](#5-close-the-drawer-programmatically)

After a user taps an item, you might want to close the drawer. You can do this by using the [`Navigator`](https://pub.dev/documentation/material_ui/latest/material_ui/Navigator-class.html).

When a user opens the drawer, Flutter adds the drawer to the navigation stack. Therefore, to close the drawer, call `Navigator.pop(context)`.

dart

```
ListTile(
  title: const Text('Item 1'),
  onTap: () {
    // Update the state of the app
    // ...
    // Then close the drawer
    Navigator.pop(context);
  },
),
```

## Interactive example

[#](#interactive-example)

This example shows a [`Drawer`](https://pub.dev/documentation/material_ui/latest/material_ui/Drawer-class.html) as it is used within a [`Scaffold`](https://pub.dev/documentation/material_ui/latest/material_ui/Scaffold-class.html) widget. The [`Drawer`](https://pub.dev/documentation/material_ui/latest/material_ui/Drawer-class.html) has three [`ListTile`](https://pub.dev/documentation/material_ui/latest/material_ui/ListTile-class.html) items. The `_onItemTapped` function changes the selected item's index and displays the corresponding text in the center of the `Scaffold`.

```
import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  static const appTitle = 'Drawer Demo';

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      title: appTitle,
      home: MyHomePage(title: appTitle),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});

  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _selectedIndex = 0;
  static const TextStyle optionStyle = TextStyle(
    fontSize: 30,
    fontWeight: FontWeight.bold,
  );
  static const List<Widget> _widgetOptions = <Widget>[
    Text('Index 0: Home', style: optionStyle),
    Text('Index 1: Business', style: optionStyle),
    Text('Index 2: School', style: optionStyle),
  ];

  void _onItemTapped(int index) {
    setState(() {
      _selectedIndex = index;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
        leading: Builder(
          builder: (context) {
            return IconButton(
              icon: const Icon(Icons.menu),
              onPressed: () {
                Scaffold.of(context).openDrawer();
              },
            );
          },
        ),
      ),
      body: Center(child: _widgetOptions[_selectedIndex]),
      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
        // space to fit everything.
        child: ListView(
          // Important: Remove any padding from the ListView.
          padding: EdgeInsets.zero,
          children: [
            const DrawerHeader(
              decoration: BoxDecoration(color: Colors.blue),
              child: Text('Drawer Header'),
            ),
            ListTile(
              title: const Text('Home'),
              selected: _selectedIndex == 0,
              onTap: () {
                // Update the state of the app
                _onItemTapped(0);
                // Then close the drawer
                Navigator.pop(context);
              },
            ),
            ListTile(
              title: const Text('Business'),
              selected: _selectedIndex == 1,
              onTap: () {
                // Update the state of the app
                _onItemTapped(1);
                // Then close the drawer
                Navigator.pop(context);
              },
            ),
            ListTile(
              title: const Text('School'),
              selected: _selectedIndex == 2,
              onTap: () {
                // Update the state of the app
                _onItemTapped(2);
                // Then close the drawer
                Navigator.pop(context);
              },
            ),
          ],
        ),
      ),
    );
  }
}
```

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-09-29. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/design/drawer.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/cookbook/design/drawer\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/design/drawer.md "Report an issue with this page").

----
url: https://docs.flutter.dev/ai/ai-toolkit
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Flutter AI Toolkit](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

Hello and welcome to the Flutter AI Toolkit!

The AI Toolkit is a set of AI chat-related widgets that make it easy to add an AI chat window to your Flutter app. The AI Toolkit is organized around an abstract LLM provider API to make it easy to swap out the LLM provider that you'd like your chat provider to use. Out of the box, it comes with support for [Firebase AI Logic](https://firebase.google.com/docs/ai-logic).

## Key features

[#](#key-features)

* **Multiturn chat**: Maintains context across multiple interactions.
* **Streaming responses**: Displays AI responses in real-time as they are generated.
* **Rich text display**: Supports formatted text in chat messages.
* **Voice input**: Allows users to input prompts using speech.
* **Multimedia attachments**: Enables sending and receiving various media types.
* **Function calling**: Supports tool calls to the LLM provider.
* **Custom styling**: Offers extensive customization to match your app's design.
* **Chat serialization/deserialization**: Store and retrieve conversations between app sessions.
* **Custom response widgets**: Introduce specialized UI components to present LLM responses.
* **Pluggable LLM support**: Implement a simple interface to plug in your own LLM.
* **Cross-platform support**: Compatible with Android, iOS, web, and macOS platforms.

## Demo

[#](#demo)

Here's what the demo example looks like hosting the AI Toolkit:

The [source code for this demo](https://github.com/flutter/ai/blob/main/example/lib/demo/demo.dart) is available in the repo on GitHub.

## Get started

[#](#get-started)

1. **Installation**

   Add the following dependencies to your `pubspec.yaml` file:

   yaml

   ```
   dependencies:
     flutter_ai_toolkit: ^latest_version
     firebase_ai: ^latest_version
     firebase_core: ^latest_version
   ```

2. **Configuration**

   The AI Toolkit supports both the Gemini Developer API (for prototyping) and the Firebase AI Logic production endpoint. Both require a Firebase project and the `firebase_core` package to be initialized, as described in the [Get started with the Gemini API using the Firebase AI Logic SDKs](https://firebase.google.com/docs/ai-logic/get-started?platform=flutter) docs.

   Once that's complete, integrate the new Firebase project into your Flutter app using the `flutterfire CLI` tool, as described in the [Add Firebase to your Flutter app](https://firebase.google.com/docs/flutter/setup) docs.

   After following these instructions, you're ready to use Firebase to integrate AI in your Flutter app. Start by initializing Firebase:

   dart

   ```
   import 'package:firebase_core/firebase_core.dart';
   import 'package:firebase_ai/firebase_ai.dart';
   import 'package:flutter_ai_toolkit/flutter_ai_toolkit.dart';
   ​
   // ... other imports
   ​
   import 'firebase_options.dart'; // from `flutterfire config`
   ​
   void main() async {
     WidgetsFlutterBinding.ensureInitialized();
     await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
     runApp(const App());
   }
   ​
   // ...app stuff here
   ```

   With Firebase properly initialized in your Flutter app, you're now ready to create an instance of the Firebase provider. You can do this in two ways. For prototyping, consider the Gemini AI endpoint:

   dart

   ```
   import 'package:firebase_ai/firebase_ai.dart';
   import 'package:flutter_ai_toolkit/flutter_ai_toolkit.dart';
   ​
   // ... app stuff here
   ​
   class ChatPage extends StatelessWidget {
     const ChatPage({super.key});
   ​
     @override
     Widget build(BuildContext context) => Scaffold(
           appBar: AppBar(title: const Text(App.title)),
           // create the chat view, passing in the Firebase provider
           body: LlmChatView(
             provider: FirebaseProvider(
               // Use the Google AI endpoint
               model: FirebaseAI.googleAI().generativeModel(
                 model: 'gemini-2.5-flash',
               ),
             ),
           ),
         );
   }
   ```

   The `FirebaseProvider` class exposes the Firebase AI Logic SDK to the `LlmChatView`. Note that you provide a model name ([you have several options](https://firebase.google.com/docs/ai-logic/models) from which to choose), but you do not provide an API key. All of that is handled as part of the Firebase project.

   For production workloads, it's easy to swap in the Firebase AI Logic production endpoint:

   dart

   ```
   class ChatPage extends StatelessWidget {
     const ChatPage({super.key});
   ​
     @override
     Widget build(BuildContext context) => Scaffold(
           appBar: AppBar(title: const Text(App.title)),
           body: LlmChatView(
             provider: FirebaseProvider(
               // Use the production endpoint
               model: FirebaseAI.vertexAI().generativeModel(
                 model: 'gemini-2.5-flash',
               ),
             ),
           ),
         );
   }
   ```

   For a complete example, check out the [gemini.dart](https://github.com/flutter/ai/blob/main/example/lib/gemini/gemini.dart) and [vertex.dart](https://github.com/flutter/ai/blob/main/example/lib/vertex/vertex.dart) examples.

3. **Set up device permissions**

   To enable your users to take advantage of features like voice input and media attachments, ensure that your app has the necessary permissions:

   * **Network access:** To enable network access on macOS, add the following to your `*.entitlements` files:

     xml

     ```
     <plist version="1.0">
       <dict>
         ...
         <key>com.apple.security.network.client</key>
         <true/>
       </dict>
     </plist>
     ```

     To enable network access on Android, ensure that your `AndroidManifest.xml` file contains the following:

     xml

     ```
     <manifest xmlns:android="http://schemas.android.com/apk/res/android">
         ...
         <uses-permission android:name="android.permission.INTERNET"/>
     </manifest>
     ```

   * **Microphone access**: Configure according to the [record package's permission setup instructions](https://pub.dev/packages/record#setup-permissions-and-others).

   * **File selection**: Follow the [file\_selector plugin's instructions](https://pub.dev/packages/file_selector#usage).

   * **Image selection**: To take a picture on *or* select a picture from their device, refer to the [image\_picker plugin's installation instructions](https://pub.dev/packages/image_picker#installation).

   * **Web photo**: To take a picture on the web, configure the app according to the [camera plugin's setup instructions](https://pub.dev/packages/camera#setup).

## Examples

[#](#examples)

**firebase\_options.dart**

To use the [Firebase AI Logic example app](https://github.com/flutter/ai/blob/main/example/lib/vertex/vertex.dart), place your Firebase configuration details into the `example/lib/firebase_options.dart` file. You can do this with the `flutterfire CLI` tool as described in the [Add Firebase to your Flutter app](https://firebase.google.com/docs/flutter/setup) docs **from within the `example` directory**.

## Feedback

[#](#feedback)

Along the way, as you use this package, please [log issues and feature requests](https://github.com/flutter/ai/issues) as well as submit any [code you'd like to contribute](https://github.com/flutter/ai/pulls). We want your feedback and your contributions to ensure that the AI Toolkit is just as robust and useful as it can be for your real-world apps.

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-09-23. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/ai/ai-toolkit/index.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/ai/ai-toolkit\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/ai/ai-toolkit/index.md "Report an issue with this page").

----
url: https://docs.flutter.dev/install/quick.md
----

\# Set up and test drive Flutter > Set up Flutter on your device with a Code OSS-based editor, such as VS Code, and get started developing your first multi-platform app with Flutter! Learn how to use any Code OSS-based editor, such as VS Code, to set up your Flutter development environment and test drive Flutter's developer experience. If you've developed with Flutter before, or you prefer to use a different editor or IDE, you can follow the \[custom setup instructions]\[] instead. :::note What you'll achieve - Install the software prerequisites for Flutter. - Use VS Code to download and install Flutter. - Create a new Flutter app from a sample template. - Try out Flutter development features like stateful hot reload. ::: \[custom setup instructions]: /install/custom ## Confirm your development platform {: #dev-platform} The instructions on this page are configured to cover installing and trying out Flutter on a \*\*Windows\*\*{:.selected-os-text} device. If you'd like to follow the instructions for a different OS, please select one of the following. ## Download prerequisite software {: #download-prerequisites} For the smoothest Flutter setup, first install the following tools. 1.

If you encounter any issues during installation, check out \[Flutter installation troubleshooting]\[troubleshoot]. {:.steps} \[Install Flutter manually]: /install/manual \[flutter-vscode]: https\://marketplace.visualstudio.com/items?itemName=Dart-Code.flutter \[troubleshoot]: /install/troubleshoot ## Test drive Flutter {: #test-drive} Now that you've set up VS Code and Flutter, it's time to create an app and try out Flutter development! 1.

### Create a new Flutter app

1\. Open the command palette in VS Code. Go to \*\*View\*\* > \*\*Command Palette\*\* or press `Cmd/Ctrl` + `Shift` + `P`. 1. In the command palette, start typing \`flutter:\`. VS Code should surface commands from the Flutter plugin. 1. Select the \*\*Flutter: New Project\*\* command. Your OS or VS Code might ask for access to your documents, agree to continue to the next step. 1. Choose the \*\*Application\*\* template. VS Code should prompt you with \*\*Which Flutter template?\*\*. Choose \*\*Application\*\* to bootstrap a simple counter app. 1. Create or select the parent directory for your new app's folder. A file dialog should appear. 1. Select or create the parent directory where you want the project to be created. 1. To confirm your selection, click \*\*Select a folder to create the project in\*\*. 1. Enter a name for your app. VS Code should prompt you to enter a name for your new app. Enter \`trying\_flutter\` or a similar \`lowercase\_with\_underscores\` name. To confirm your selection, press `Enter`. 1. Wait for project initialization to complete. Task progress is often surfaced as a notification in the bottom right and can also be accessed from the \*\*Output\*\* panel. 1. Open the \`lib\` directory, then the \`main.dart\` file. If you're curious about what each portion of the code does, check out the preceding comments throughout the file. 1.

### Run your app on the web

While Flutter apps can run on many platforms, try running your new app on the web. 1. Open the command palette in VS Code. Go to \*\*View\*\* > \*\*Command Palette\*\* or press `Cmd/Ctrl` + `Shift` + `P`. 1. In the command palette, start typing \`flutter:\`. VS Code should surface commands from the Flutter plugin. 1. Select the \*\*Flutter: Select Device\*\* command. 1. From the \*\*Select Device\*\* prompt, select \*\*Chrome\*\*. 1. Run or start debugging your app. Go to \*\*Run\*\* > \*\*Start Debugging\*\* or press `F5`. \`flutter run\` is used to build and start your app, then a new instance of Chrome should open and start running your newly created app. 1.

### Try hot reload

Flutter offers a fast development cycle with \*\*stateful hot reload\*\*, the ability to reload the code of a live running app without restarting or losing app state. You can change your app's source code, run the hot reload command in VS Code, then see the change in your running app. 1. In the running app, try adding to the counter a few times by clicking the !\[increment (+)]\[increment-button]{: .text-icon} button. 1. With your app still running, make a change in the \`lib/main.dart\` file. Change the \`\_counter++\` line in the \`\_incrementCounter\` method to instead decrement the \`\_counter\` field. \`\`\`dart diff setState(() { // ... - \_counter++; + \_counter--; }); \`\`\` 1. Save your changes (\*\*File\*\* > \*\*Save All\*\*) or click the \*\*Hot Reload\*\* !\[hot reload icon]\[]{: .text-icon} button. Flutter updates the running app without losing any existing state. Notice the existing value stayed the same. 1. Try clicking the !\[increment (+)]\[increment-button]{: .text-icon} button again. Notice the value decreases instead of increases. 1.

### Explore the Flutter sidebar

The Flutter plugin adds a dedicated sidebar to VS Code for managing Flutter debug sessions and devices, viewing an outline of your code and widgets, as well as accessing the Dart and Flutter DevTools. 1. If your app isn't running, start debugging it again. Go to \*\*Run\*\* > \*\*Start Debugging\*\* or press `F5`. 1. Open the Flutter sidebar in VS Code. Either open it with the Flutter !\[Flutter logo]\[]{: .text-icon} button in the VS Code sidebar or open it from the command palette by running the \*\*Flutter: Focus on Flutter Sidebar View\*\* command. 1. In the Flutter sidebar, under \*\*DevTools\*\*, click the \*\*Flutter Inspector\*\* button. A separate \*\*Widget Inspector\*\* panel should open in VS Code. In the widget inspector, you can view your app's widget tree, view the properties and layout of each widget, and more. 1. In the widget inspector, try clicking the top-level \`MyHomePage\` widget. A view of its properties and layout should open, and the VS Code editor should navigate to and focus the line where the widget was included. 1. Explore and try out other features in the widget inspector and Flutter sidebar. {:.steps} \[increment-button]: /assets/images/docs/get-started/increment-button.png \[hot reload icon]: /assets/images/docs/get-started/hot-reload.svg \[Flutter logo]: /assets/images/branding/flutter/logo/square.svg ## Continue your Flutter journey {: #next-steps} \*\*Congratulations!\*\* Now that you've installed and tried out Flutter, follow the \[Flutter learning pathway]\[], set up development for an \[additional target platform]\[], or explore some of these resources to continue your Flutter learning journey.

----
url: https://docs.flutter.dev/ai/ai-toolkit/chat-client-sample.md
----

\# Chat client sample > Learn about the chat client sample included in the AI Toolkit. The AI Chat sample is meant to be a full-fledged chat app built using the Flutter AI Toolkit and the Firebase AI Logic SDK. In addition to all of the multi-shot, multi-media, streaming features that it gets from the AI Toolkit, the AI Chat sample shows how to store and manage multiple chats at once in your own apps. On desktop form-factors, the AI Chat sample looks like the following: !\[Desktop app UI]\(/assets/images/docs/ai-toolkit/desktop-pluto-convo.png) On mobile form-factors, it looks like this: !\[Mobile app UI]\(/assets/images/docs/ai-toolkit/mobile-pluto-convo.png) The chats are stored in an authenticated Cloud Firestore database; any authenticated user can have as many chats as they like. In addition, for each new chat, while the user can manually title it whatever they like, the initial prompt and response is used to ask the LLM what an appropriate title should be. In fact, the titles of the chats in the screenshots in this page were set automatically. To build and run the sample, follow the instructions in the \[AI Chat README]\[]. \[AI Chat README]: https\://github.com/csells/flutter\_ai\_chat

----
url: https://docs.flutter.dev/add-to-app/android/plugin-setup
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Manage plugins and dependencies in add-to-app](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

This guide describes how to set up your project to consume plugins and how to manage your Gradle library dependencies between your existing Android app and your Flutter module's plugins.

## A. Simple scenario

[#](#a-simple-scenario)

In the simple cases:

* Your Flutter module uses a plugin that has no additional Android Gradle dependency because it only uses Android OS APIs, such as the camera plugin.
* Your Flutter module uses a plugin that has an Android Gradle dependency, such as [ExoPlayer from the video\_player plugin](https://github.com/flutter/packages/blob/main/packages/video_player/video_player_android/android/build.gradle), but your existing Android app didn't depend on ExoPlayer.

There are no additional steps needed. Your add-to-app module will work the same way as a full-Flutter app. Whether you integrate using Android Studio, Gradle subproject or AARs, transitive Android Gradle libraries are automatically bundled as needed into your outer existing app.

## B. Plugins needing project edits

[#](#b-plugins-needing-project-edits)

Some plugins require you to make some edits to the Android side of your project.

For example, the integration instructions for the [firebase\_crashlytics](https://pub.dev/packages/firebase_crashlytics) plugin require manual edits to your Android wrapper project's `build.gradle` file.

For full-Flutter apps, these edits are done in your Flutter project's `/android/` directory.

In the case of a Flutter module, there are only Dart files in your module project. Perform those Android Gradle file edits on your outer, existing Android app rather than in your Flutter module.

## C. Merging libraries

[#](#c-merging-libraries)

The scenario that requires slightly more attention is if your existing Android application already depends on the same Android library that your Flutter module does (transitively via a plugin).

For instance, your existing app's Gradle might already have:

* [Kotlin](#75-tab-panel)
* [Groovy](#76-tab-panel)

ExistingApp/app/build.gradle.kts

kotlin

```
…
dependencies {
    …
    implementation("com.crashlytics.sdk.android:crashlytics:2.10.1")
    …
}
…
```

ExistingApp/app/build.gradle

groovy

```
…
dependencies {
    …
    implementation "com.crashlytics.sdk.android:crashlytics:2.10.1"
    …
}
…
```

And your Flutter module also depends on [firebase\_crashlytics](https://pub.dev/packages/firebase_crashlytics) via `pubspec.yaml`:

flutter\_module/pubspec.yaml

yaml

```
…
dependencies:
  …
  firebase_crashlytics: ^0.1.3
  …
…
```

This plugin usage transitively adds a Gradle dependency again via firebase\_crashlytics v0.1.3's own [Gradle file](https://github.com/firebase/flutterfire/blob/bdb95fcacf7cf077d162d2f267eee54a8b0be3bc/packages/firebase_crashlytics/android/build.gradle#L40):

"firebase\_crashlytics\_via\_pub/android/build.gradle

groovy

```
…
dependencies {
    …
    implementation "com.crashlytics.sdk.android:crashlytics:2.9.9"
    …
}
…
```

The two `com.crashlytics.sdk.android:crashlytics` dependencies might not be the same version. In this example, the host app requested v2.10.1 and the Flutter module plugin requested v2.9.9.

By default, Gradle v5 [resolves dependency version conflicts](https://docs.gradle.org/current/userguide/dependency_resolution.html#sub:resolution-strategy) by using the newest version of the library.

This is generally ok as long as there are no API or implementation breaking changes between the versions. For example, you might use the new Crashlytics library in your existing app as follows:

* [Kotlin](#77-tab-panel)
* [Groovy](#78-tab-panel)

ExistingApp/app/build.gradle.kts

kotlin

```
…
dependencies {
    …
    implementation("com.crashlytics.sdk.android:crashlytics:2.10.1")
    …
}
…
```

ExistingApp/app/build.gradle

groovy

```
…
dependencies {
    …
    implementation "com.google.firebase:firebase-crashlytics:17.0.0-beta03"
    …
}
…
```

This approach won't work since there are major API differences between the Crashlytics' Gradle library version v17.0.0-beta03 and v2.9.9.

For Gradle libraries that follow semantic versioning, you can generally avoid compilation and runtime errors by using the same major semantic version in your existing app and Flutter module plugin.

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-07-31. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/add-to-app/android/plugin-setup.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/add-to-app/android/plugin-setup\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/add-to-app/android/plugin-setup.md "Report an issue with this page").

----
url: https://docs.flutter.dev/search-all
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

Use this search when you want results from multiple Flutter-related sites.

Want only results from docs.flutter.dev? [Search docs.flutter.dev.](/search)

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/search-all.html) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/search-all\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/search-all.html "Report an issue with this page").

----
url: https://docs.flutter.dev/testing/errors.md
----

\# Handling errors in Flutter > How to control error messages and logging of errors

<!--?code-excerpt path-base="testing/errors"?-->

The Flutter framework catches errors that occur during callbacks triggered by the framework itself, including errors encountered during the build, layout, and paint phases. Errors that don't occur within Flutter's callbacks can't be caught by the framework, but you can handle them by setting up an error handler on the \[\`PlatformDispatcher\`]\[]. All errors caught by Flutter are routed to the \[\`FlutterError.onError\`]\[] handler. By default, this calls \[\`FlutterError.presentError\`]\[], which dumps the error to the device logs. When running from an IDE, the inspector overrides this behavior so that errors can also be routed to the IDE's console, allowing you to inspect the objects mentioned in the message. :::note Consider calling \[\`FlutterError.presentError\`]\[] from your custom error handler in order to see the logs in the console as well. ::: When an error occurs during the build phase, the \[\`ErrorWidget.builder\`]\[] callback is invoked to build the widget that is used instead of the one that failed. By default, in debug mode this shows an error message in red, and in release mode this shows a gray background. When errors occur without a Flutter callback on the call stack, they are handled by the \`PlatformDispatcher\`'s error callback. By default, this only prints errors and does nothing else. You can customize these behaviors, typically by setting them to values in your \`void main()\` function. Below each error type handling is explained. At the bottom there's a code snippet which handles all types of errors. Even though you can just copy-paste the snippet, we recommend you to first get acquainted with each of the error types. ## Errors caught by Flutter For example, to make your application quit immediately any time an error is caught by Flutter in release mode, you could use the following handler:

<!--?code-excerpt "lib/quit_immediate.dart (on-error-main)"?-->

\`\`\`dart import 'dart:io'; import 'package:flutter/foundation.dart'; import 'package:material\_ui/material\_ui.dart'; void main() { FlutterError.onError = (details) { FlutterError.presentError(details); if (kReleaseMode) exit(1); }; runApp(const MyApp()); } // The rest of the \`flutter create\` code... \`\`\` :::note The top-level \[\`kReleaseMode\`]\[] constant indicates whether the app was compiled in release mode. ::: This handler can also be used to report errors to a logging service. For more details, see our cookbook chapter for \[reporting errors to a service]\[]. ## Define a custom error widget for build phase errors To define a customized error widget that displays whenever the builder fails to build a widget, use \[\`MaterialApp.builder\`]\[].

<!--?code-excerpt "lib/excerpts.dart (custom-error)"?-->

\`\`\`dart class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( builder: (context, widget) { Widget error = const Text('...rendering error...'); if (widget is Scaffold || widget is Navigator) { error = Scaffold(body: Center(child: error)); } ErrorWidget.builder = (errorDetails) => error; if (widget != null) return widget; throw StateError('widget is null'); }, ); } } \`\`\` ## Errors not caught by Flutter Consider an \`onPressed\` callback that invokes an asynchronous function, such as \`MethodChannel.invokeMethod\` (or pretty much any plugin). For example:

<!--?code-excerpt "lib/excerpts.dart (on-pressed)" replace="/return //g;/^\);$/)/g"?-->

\`\`\`dart OutlinedButton( child: const Text('Click me!'), onPressed: () async { const channel = MethodChannel('crashy-custom-channel'); await channel.invokeMethod('blah'); }, ) \`\`\` If \`invokeMethod\` throws an error, it won't be forwarded to \`FlutterError.onError\`. Instead, it's forwarded to the \`PlatformDispatcher\`. To catch such an error, use \[\`PlatformDispatcher.instance.onError\`]\[].

<!--?code-excerpt "lib/excerpts.dart (catch-error)"?-->

\`\`\`dart import 'dart:ui'; import 'package:material\_ui/material\_ui.dart'; void main() { MyBackend myBackend = MyBackend(); PlatformDispatcher.instance.onError = (error, stack) { myBackend.sendError(error, stack); return true; }; runApp(const MyApp()); } \`\`\` ## Handling all types of errors Say you want to exit application on any exception and to display a custom error widget whenever a widget building fails - you can base your errors handling on next code snippet:

<!--?code-excerpt "lib/main.dart (all-errors)"?-->

\`\`\`dart import 'dart:ui'; import 'package:material\_ui/material\_ui.dart'; Future main() async { await myErrorsHandler.initialize(); FlutterError.onError = (details) { FlutterError.presentError(details); myErrorsHandler.onErrorDetails(details); }; PlatformDispatcher.instance.onError = (error, stack) { myErrorsHandler.onError(error, stack); return true; }; runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( builder: (context, widget) { Widget error = const Text('...rendering error...'); if (widget is Scaffold || widget is Navigator) { error = Scaffold(body: Center(child: error)); } ErrorWidget.builder = (errorDetails) => error; if (widget != null) return widget; throw StateError('widget is null'); }, ); } } \`\`\` \[\`ErrorWidget.builder\`]: https\://api.flutter.dev/flutter/widgets/ErrorWidget/builder.html \[\`FlutterError.onError\`]: https\://api.flutter.dev/flutter/foundation/FlutterError/onError.html \[\`FlutterError.presentError\`]: https\://api.flutter.dev/flutter/foundation/FlutterError/presentError.html \[\`kReleaseMode\`]: https\://api.flutter.dev/flutter/foundation/kReleaseMode-constant.html \[\`MaterialApp.builder\`]: https\://pub.dev/documentation/material\_ui/latest/material\_ui/MaterialApp/builder.html \[reporting errors to a service]: /cookbook/maintenance/error-reporting \[\`PlatformDispatcher.instance.onError\`]: https\://api.flutter.dev/flutter/dart-ui/PlatformDispatcher/onError.html \[\`PlatformDispatcher\`]: https\://api.flutter.dev/flutter/dart-ui/PlatformDispatcher-class.html

----
url: https://docs.flutter.dev/cookbook/games/achievements-leaderboard
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Add achievements and leaderboards to your mobile game](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

Gamers have various motivations for playing games. In broad strokes, there are four major motivations: [immersion, achievement, cooperation, and competition](https://meditations.metavert.io/p/game-player-motivations). No matter the game you build, some players want to *achieve* in it. This could be trophies won or secrets unlocked. Some players want to *compete* in it. This could be hitting high scores or accomplishing speedruns. These two ideas map to the concepts of *achievements* and *leaderboards*.

Ecosystems such as the App Store and Google Play provide centralized services for achievements and leaderboards. Players can view achievements from all their games in one place and developers don't need to re-implement them for every game.

This recipe demonstrates how to use the [`games_services` package](https://pub.dev/packages/games_services) to add achievements and leaderboard functionality to your mobile game.

## 1. Enable platform services

[#](#1-enable-platform-services)

To enable games services, set up *Game Center* on iOS and *Google Play Games Services* on Android.

### iOS

[#](#ios)

To enable Game Center (GameKit) on iOS:

1. Open your Flutter project in Xcode. Open `ios/Runner.xcworkspace`

2. Select the root **Runner** project.

3. Go to the **Signing & Capabilities** tab.

4. Click the `+` button to add **Game Center** as a capability.

5. Close Xcode.

6. If you haven't already, register your game in [App Store Connect](https://appstoreconnect.apple.com/) and from the **My App** section press the `+` icon.

7. Still in App Store Connect, look for the *Game Center* section. You can find it in **Services** as of this writing. On the **Game Center** page, you might want to set up a leaderboard and several achievements, depending on your game. Take note of the IDs of the leaderboards and achievements you create.

### Android

[#](#android)

To enable *Play Games Services* on Android:

1. If you haven't already, go to [Google Play Console](https://play.google.com/console/) and register your game there.

2. Still in Google Play Console, select *Play Games Services* → *Setup and management* → *Configuration* from the navigation menu and follow their instructions.

   * This takes a significant amount of time and patience. Among other things, you'll need to set up an OAuth consent screen in Google Cloud Console. If at any point you feel lost, consult the official [Play Games Services guide](https://developers.google.com/games/services/console/enabling).

3. When done, you can start adding leaderboards and achievements in **Play Games Services** → **Setup and management**. Create the exact same set as you did on the iOS side. Make note of IDs.

4. Go to **Play Games Services → Setup and management → Publishing**.

5. Click **Publish**. Don't worry, this doesn't actually publish your game. It only publishes the achievements and leaderboard. Once a leaderboard, for example, is published this way, it cannot be unpublished.

6. Go to **Play Games Services** **→ Setup and management → Configuration → Credentials**.

7. Find the **Get resources** button. It returns an XML file with the Play Games Services IDs.

   xml

   ```
   <!-- THIS IS JUST AN EXAMPLE -->
   <?xml version="1.0" encoding="utf-8"?>
   <resources>
       <!--app_id-->
       <string name="app_id" translatable="false">424242424242</string>
       <!--package_name-->
       <string name="package_name" translatable="false">dev.flutter.tictactoe</string>
       <!--achievement First win-->
       <string name="achievement_first_win" translatable="false">sOmEiDsTrInG</string>
       <!--leaderboard Highest Score-->
       <string name="leaderboard_highest_score" translatable="false">sOmEiDsTrInG</string>
   </resources>
   ```

8. Add a file at `android/app/src/main/res/values/games-ids.xml` containing the XML you received in the previous step.

## 2. Sign in to the game service

[#](#2-sign-in-to-the-game-service)

Now that you have set up *Game Center* and *Play Games Services*, and have your achievement & leaderboard IDs ready, it's finally Dart time.

1. Add a dependency on the [`games_services` package](https://pub.dev/packages/games_services).

   ```
   $ flutter pub add games_services
   ```

2. Before you can do anything else, you have to sign the player into the game service.

   dart

   ```
   try {
     await GamesServices.signIn();
   } on PlatformException catch (e) {
     // ... deal with failures ...
   }
   ```

The sign in happens in the background. It takes several seconds, so don't call `signIn()` before `runApp()` or the players will be forced to stare at a blank screen every time they start your game.

The API calls to the `games_services` API can fail for a multitude of reasons. Therefore, every call should be wrapped in a try-catch block as in the previous example. The rest of this recipe omits exception handling for clarity.

## 3. Unlock achievements

[#](#3-unlock-achievements)

1. Register achievements in Google Play Console and App Store Connect, and take note of their IDs. Now you can award any of those achievements from your Dart code:

   dart

   ```
   await GamesServices.unlock(
     achievement: Achievement(
       androidID: 'your android id',
       iOSID: 'your ios id',
     ),
   );
   ```

   The player's account on Google Play Games or Apple Game Center now lists the achievement.

2. To display the achievements UI from your game, call the `games_services` API:

   dart

   ```
   await GamesServices.showAchievements();
   ```

   This displays the platform achievements UI as an overlay on your game.

3. To display the achievements in your own UI, use [`GamesServices.loadAchievements()`](https://pub.dev/documentation/games_services/latest/games_services/GamesServices/loadAchievements.html).

## 4. Submit scores

[#](#4-submit-scores)

When the player finishes a play-through, your game can submit the result of that play session into one or more leaderboards.

For example, a platformer game like Super Mario can submit both the final score and the time taken to complete the level, to two separate leaderboards.

1. In the first step, you registered a leaderboard in Google Play Console and App Store Connect, and took note of its ID. Using this ID, you can submit new scores for the player:

   dart

   ```
   await GamesServices.submitScore(
     score: Score(
       iOSLeaderboardID: 'some_id_from_app_store',
       androidLeaderboardID: 'sOmE_iD_fRoM_gPlAy',
       value: 100,
     ),
   );
   ```

   You don't need to check whether the new score is the player's highest. The platform game services handle that for you.

2. To display the leaderboard as an overlay over your game, make the following call:

   dart

   ```
   await GamesServices.showLeaderboards(
     iOSLeaderboardID: 'some_id_from_app_store',
     androidLeaderboardID: 'sOmE_iD_fRoM_gPlAy',
   );
   ```

3. If you want to display the leaderboard scores in your own UI, you can fetch them with [`GamesServices.loadLeaderboardScores()`](https://pub.dev/documentation/games_services/latest/games_services/GamesServices/loadLeaderboardScores.html).

## 5. Next steps

[#](#5-next-steps)

There's more to the `games_services` plugin. With this plugin, you can:

* Get the player's icon, name or unique ID
* Save and load game states
* Sign out of the game service

Some achievements can be incremental. For example: "You have collected all 10 pieces of the McGuffin."

Each game has different needs from game services.

To start, you might want to create this controller in order to keep all achievements & leaderboards logic in one place:

dart

```
import 'dart:async';
​
import 'package:games_services/games_services.dart';
import 'package:logging/logging.dart';
​
/// Allows awarding achievements and leaderboard scores,
/// and also showing the platforms' UI overlays for achievements
/// and leaderboards.
///
/// A facade of `package:games_services`.
class GamesServicesController {
  static final Logger _log = Logger('GamesServicesController');
​
  final Completer<bool> _signedInCompleter = Completer();
​
  Future<bool> get signedIn => _signedInCompleter.future;
​
  /// Unlocks an achievement on Game Center / Play Games.
  ///
  /// You must provide the achievement ids via the [iOS] and [android]
  /// parameters.
  ///
  /// Does nothing when the game isn't signed into the underlying
  /// games service.
  Future<void> awardAchievement({
    required String iOS,
    required String android,
  }) async {
    if (!await signedIn) {
      _log.warning('Trying to award achievement when not logged in.');
      return;
    }
​
    try {
      await GamesServices.unlock(
        achievement: Achievement(androidID: android, iOSID: iOS),
      );
    } catch (e) {
      _log.severe('Cannot award achievement: $e');
    }
  }
​
  /// Signs into the underlying games service.
  Future<void> initialize() async {
    try {
      await GamesServices.signIn();
      // The API is unclear so we're checking to be sure. The above call
      // returns a String, not a boolean, and there's no documentation
      // as to whether every non-error result means we're safely signed in.
      final signedIn = await GamesServices.isSignedIn;
      _signedInCompleter.complete(signedIn);
    } catch (e) {
      _log.severe('Cannot log into GamesServices: $e');
      _signedInCompleter.complete(false);
    }
  }
​
  /// Launches the platform's UI overlay with achievements.
  Future<void> showAchievements() async {
    if (!await signedIn) {
      _log.severe('Trying to show achievements when not logged in.');
      return;
    }
​
    try {
      await GamesServices.showAchievements();
    } catch (e) {
      _log.severe('Cannot show achievements: $e');
    }
  }
​
  /// Launches the platform's UI overlay with leaderboard(s).
  Future<void> showLeaderboard() async {
    if (!await signedIn) {
      _log.severe('Trying to show leaderboard when not logged in.');
      return;
    }
​
    try {
      await GamesServices.showLeaderboards(
        // TODO: When ready, change both these leaderboard IDs.
        iOSLeaderboardID: 'some_id_from_app_store',
        androidLeaderboardID: 'sOmE_iD_fRoM_gPlAy',
      );
    } catch (e) {
      _log.severe('Cannot show leaderboard: $e');
    }
  }
​
  /// Submits [score] to the leaderboard.
  Future<void> submitLeaderboardScore(int score) async {
    if (!await signedIn) {
      _log.warning('Trying to submit leaderboard when not logged in.');
      return;
    }
​
    _log.info('Submitting $score to leaderboard.');
​
    try {
      await GamesServices.submitScore(
        score: Score(
          // TODO: When ready, change these leaderboard IDs.
          iOSLeaderboardID: 'some_id_from_app_store',
          androidLeaderboardID: 'sOmE_iD_fRoM_gPlAy',
          value: score,
        ),
      );
    } catch (e) {
      _log.severe('Cannot submit leaderboard score: $e');
    }
  }
}
```

## More information

[#](#more-information)

The Flutter Casual Games Toolkit includes the following templates:

* [basic](https://github.com/flutter/games/tree/main/templates/basic#readme): basic starter game
* [card](https://github.com/flutter/games/tree/main/templates/card#readme): starter card game
* [endless runner](https://github.com/flutter/games/tree/main/templates/endless_runner#readme): starter game (using Flame) where the player endlessly runs, avoiding pitfalls and gaining rewards

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-07-31. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/games/achievements-leaderboard.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/cookbook/games/achievements-leaderboard\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/games/achievements-leaderboard.md "Report an issue with this page").

----
url: https://docs.flutter.dev/add-to-app/ios
----

[Integrate a Flutter module into your iOS project (Legacy)](/add-to-app/ios/project-setup-legacy)

[Learn how to integrate a Flutter module into your existing iOS project.](/add-to-app/ios/project-setup-legacy)

[Integrate a Flutter app into your iOS project](/add-to-app/ios/project-setup)

[Learn how to integrate a Flutter app into your existing iOS project.](/add-to-app/ios/project-setup)

[Add a Flutter screen to an iOS app](/add-to-app/ios/add-flutter-screen)

[Learn how to add a single Flutter screen to your existing iOS app.](/add-to-app/ios/add-flutter-screen)

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/add-to-app/ios/index.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/add-to-app/ios\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/add-to-app/ios/index.md "Report an issue with this page").

----
url: https://docs.flutter.dev/install/custom.md
----

\# Set up Flutter for your particular needs > Install and set up Flutter for your preferred development environment and target platforms. To get started developing with Flutter, follow these steps to install and set up Flutter for your preferred development environment and target platform. If you plan to use VS Code or another Code - OSS derived editor like Antigravity, consider following the \[Flutter quick start]\[] instead. \[Flutter quick start]: /install/quick ## Install and set up Flutter {: #install} To get started developing apps with Flutter, install the Flutter SDK to your development device. Choose one of the following installation methods:

[Install with VS Code Recommended](/install/with-vs-code)

[Use VS Code to quickly set up your Flutter development environment.](/install/with-vs-code)

[Install manually For specific releases](/install/manual)

[Manually install and set up a specific Flutter SDK release.](/install/manual)

\## Set up an IDE or editor {: #editor} For the best development experience with Flutter, install an IDE or editor with support for Dart and Flutter. Some popular options include VS Code, Antigravity, Android Studio, and other Code OSS-based editors.

[VS Code](/tools/vs-code#setup)

[Set up Flutter support in VS Code.](/tools/vs-code#setup)

[Android Studio](/tools/android-studio#setup)

[Set up Flutter support in Android Studio.](/tools/android-studio#setup)

[IntelliJ](/tools/android-studio#setup)

[Set up Flutter support in an IntelliJ-based IDE.](/tools/android-studio#setup)

\## Set up a target platform {: #target-platform} Once you've successfully installed Flutter, set up development for at least one target platform to continue your journey with Flutter. We recommend \[developing for the web]\[web-setup] first as it requires no additional setup besides an appropriate browser. You can always set up development for additional target platforms later.

\[web-setup]: /platform-integration/web/setup ## Continue your Flutter journey {: #next-steps} \*\*Congratulations!\*\* Now that you've installed Flutter, set up an IDE or editor, and set up development for a target platform, you can continue your Flutter learning journey. Follow the \[Flutter learning pathway]\[], set up development for an \[additional target platform]\[], or explore some of these other learning resources.

----
url: https://docs.flutter.dev/app-architecture/case-study/ui-layer.md
----

\# UI layer case study > A walk-through of the UI layer of an app that implements MVVM architecture. The \[UI layer]\[] of each feature in your Flutter application should be made up of two components: a \*\*\[\`View\`]\[]\*\* and a \*\*\[\`ViewModel\`]\[].\*\* !\[A screenshot of the booking screen of the compass app.]\(/assets/images/docs/app-architecture/case-study/mvvm-case-study-ui-layer-highlighted.png) In the most general sense, view models manage UI state, and views display UI state. Views and view models have a one-to-one relationship; for each view, there's exactly one corresponding view model that manages that view's state. Each pair of view and view model make up the UI for a single feature. For example, an app might have classes called \`LogOutView\` and a \`LogOutViewModel\`. ## Define a view model A view model is a Dart class responsible for handling UI logic. View models take domain data models as input and expose that data as UI state to their corresponding views. They encapsulate logic that the view can attach to event handlers, like button presses, and manage sending these events to the data layer of the app, where data changes happen. The following code snippet is a class declaration for a view model class called the \`HomeViewModel\`. Its inputs are the \[repositories]\[] that provide its data. In this case, the view model is dependent on the \`BookingRepository\` and \`UserRepository\` as arguments. \`\`\`dart title=home\_viewmodel.dart class HomeViewModel { HomeViewModel({ required BookingRepository bookingRepository, required UserRepository userRepository, }) : // Repositories are manually assigned because they're private members. \_bookingRepository = bookingRepository, \_userRepository = userRepository; final BookingRepository \_bookingRepository; final UserRepository \_userRepository; // ... } \`\`\` View models are always dependent on data repositories, which are provided as arguments to the view model's constructor. View models and repositories have a many-to-many relationship, and most view models will depend on multiple repositories. As in the earlier \`HomeViewModel\` example declaration, repositories should be private members on the view model, otherwise views would have direct access to the data layer of the application. ### UI state The output of a view model is data that a view needs to render, generally referred to as \*\*UI State\*\*, or just state. UI state is an immutable snapshot of data that is required to fully render a view. !\[A screenshot of the booking screen of the compass app.]\(/assets/images/docs/app-architecture/case-study/mvvm-case-study-ui-state-highlighted.png) The view model exposes state as public members. On the view model in the following code example, the exposed data is a \`User\` object, as well as the user's saved itineraries which are exposed as an object of type \`List\`. \`\`\`dart title=home\_viewmodel.dart class HomeViewModel { HomeViewModel({ required BookingRepository bookingRepository, required UserRepository userRepository, }) : \_bookingRepository = bookingRepository, \_userRepository = userRepository; final BookingRepository \_bookingRepository; final UserRepository \_userRepository; User? \_user; User? get user => \_user; List \_bookings = \[]; /// Items in an \[UnmodifiableListView] can't be directly modified, /// but changes in the source list can be modified. Since \_bookings /// is private and bookings is not, the view has no way to modify the /// list directly. UnmodifiableListView get bookings => UnmodifiableListView(\_bookings); // ... } \`\`\` As mentioned, the UI state should be immutable. This is a crucial part of bug-free software. The compass app uses the \[\`package:freezed\`]\[] to enforce immutability on data classes. For example, the following code shows the \`User\` class definition. \`freezed\` provides deep immutability, and generates the implementation for useful methods like \`copyWith\` and \`toJson\`. \`\`\`dart title=user.dart @freezed class User with \_$User { const factory User({ /// The user's name. required String name, /// The user's picture URL. required String picture, }) = \_User; factory User.fromJson(Map json) => \_$UserFromJson(json); } \`\`\` :::note In the view model example, two objects are needed to render the view. As the UI state for any given model grows in complexity, a view model might have many more pieces of data from many more repositories exposed to the view. In some cases, you might want to create objects that specifically represent the UI state. For example, you could create a class named \`HomeUiState\`. ::: ### Updating UI state In addition to storing state, view models need to tell Flutter to re-render views when the data layer provides a new state. In the Compass app, view models extend \[\`ChangeNotifier\`]\[] to achieve this. \`\`\`dart title=home\_viewmodel.dart class HomeViewModel \[!extends ChangeNotifier!] { HomeViewModel({ required BookingRepository bookingRepository, required UserRepository userRepository, }) : \_bookingRepository = bookingRepository, \_userRepository = userRepository; final BookingRepository \_bookingRepository; final UserRepository \_userRepository; User? \_user; User? get user => \_user; List \_bookings = \[]; List get bookings => \_bookings; // ... } \`\`\` \`HomeViewModel.user\` is a public member that the view depends on. When new data flows from the data layer and new state needs to be emitted, \[\`notifyListeners\`]\[] is called.

!\[A screenshot of the booking screen of the compass app.]\(/assets/images/docs/app-architecture/case-study/mvvm-case-study-update-ui-steps.png)

This figure shows from a high-level how new data in the repository propagates up to the UI layer and triggers a re-build of your Flutter widgets.

1\. New state is provided to the view model from a Repository. 2. The view model updates its UI state to reflect the new data. 3. \`ViewModel.notifyListeners\` is called, alerting the View of new UI State. 4. The view (widget) re-renders. For example, when the user navigates to the Home screen and the view model is created, the \`\_load\` method is called. Until this method completes, the UI state is empty, the view displays a loading indicator. When the \`\_load\` method completes, if it's successful, there's new data in the view model, and it must notify the view that new data is available. \`\`\`dart title=home\_viewmodel.dart highlightLines=19 class HomeViewModel extends ChangeNotifier { // ... Future \_load() async { try { final userResult = await \_userRepository.getUser(); switch (userResult) { case Ok(): \_user = userResult.value; \_log.fine('Loaded user'); case Error(): \_log.warning('Failed to load user', userResult.error); } // ... return userResult; } finally { notifyListeners(); } } } \`\`\` :::note \`ChangeNotifier\` and \[\`ListenableBuilder\`]\[] (discussed later on this page) are part of the Flutter SDK, and provide a good solution for updating the UI when state changes. You can also use a robust third-party state management solution, such as \[\`package:riverpod\`]\[], \[\`package:flutter\_bloc\`]\[], or \[\`package:signals\`]\[]. These libraries offer different tools for handling UI updates. Read more about using \`ChangeNotifier\` in our \[state-management documentation]\[]. ::: \[\`package:riverpod\`]: https\://pub.dev/packages/riverpod \[\`package:flutter\_bloc\`]: https\://pub.dev/packages/flutter\_bloc \[\`package:signals\`]: https\://pub.dev/packages/signals \[state-management documentation]: /data-and-backend/state-mgmt/intro ## Define a view A view is a widget within your app. Often, a view represents one screen in your app that has its own route and includes a \[\`Scaffold\`]\[] at the top of the widget subtree, such as the \`HomeScreen\`, but this isn't always the case. Sometimes a view is a single UI element that encapsulates functionality that needs to be re-used throughout the app. For example, the Compass app has a view called \`LogoutButton\`, which can be dropped anywhere in the widget tree that a user might expect to find a logout button. The \`LogoutButton\` view has its own view model called \`LogoutViewModel\`. And on larger screens, there might be multiple views on screen that would take up the full screen on mobile. :::note "View" is an abstract term, and one view doesn't equal one widget. Widgets are composable, and several can be combined to create one view. Therefore, view models don't have a one-to-one relationship with widgets, but rather a one-to-one relation with a \*collection\* of widgets. ::: The widgets within a view have three responsibilities: \* They display the data properties from the view model. \* They listen for updates from the view model and re-render when new data is available. \* They attach callbacks from the view model to event handlers, if applicable. !\[A diagram showing a view's relationship to a view model.]\(/assets/images/docs/app-architecture/guide/feature-architecture-simplified-View-highlighted.png) Continuing the Home feature example, the following code shows the definition of the \`HomeScreen\` view. \`\`\`dart title=home\_screen.dart class HomeScreen extends StatelessWidget { const HomeScreen({super.key, required this.viewModel}); final HomeViewModel viewModel; @override Widget build(BuildContext context) { return Scaffold( // ... ); } } \`\`\` Most of the time, a view's only inputs should be a \`key\`, which all Flutter widgets take as an optional argument, and the view's corresponding view model. ### Display UI data in a view A view depends on a view model for its state. In the Compass app, the view model is passed in as an argument in the view's constructor. The following example code snippet is from the \`HomeScreen\` widget. \`\`\`dart title=home\_screen.dart class HomeScreen extends StatelessWidget { const HomeScreen({super.key, \[!required this.viewModel!]}); \[!final HomeViewModel viewModel;!] @override Widget build(BuildContext context) { // ... } } \`\`\` Within the widget, you can access the passed-in bookings from the \`viewModel\`. In the following code, the \`booking\` property is being provided to a sub-widget. \`\`\`dart title=home\_screen.dart @override Widget build(BuildContext context) { return Scaffold( // Some code was removed for brevity. body: SafeArea( child: ListenableBuilder( listenable: viewModel, builder: (context, \_) { return CustomScrollView( slivers: \[ SliverToBoxAdapter(...), SliverList.builder( itemCount: \[!viewModel.bookings.length!], itemBuilder: (\_, index) => \_Booking( key: ValueKey(\[!viewModel.bookings\[index].id!]), booking:viewModel.bookings\[index], onTap: () => context.push(Routes.bookingWithId( viewModel.bookings\[index].id)), onDismissed: (\_) => viewModel.deleteBooking.execute( viewModel.bookings\[index].id, ), ), ), ], ); }, ), ), \`\`\` ### Update the UI The \`HomeScreen\` widget listens for updates from the view model with the \[\`ListenableBuilder\`]\[] widget. Everything in the widget subtree under the \`ListenableBuilder\` widget re-renders when the provided \[\`Listenable\`]\[] changes. In this case, the provided \`Listenable\` is the view model. Recall that the view model is of type \[\`ChangeNotifier\`]\[] which is a subtype of the \`Listenable\` type. \`\`\`dart title=home\_screen.dart @override Widget build(BuildContext context) { return Scaffold( // Some code was removed for brevity. body: SafeArea( child: ListenableBuilder( listenable: viewModel, builder: (context, \_) { return CustomScrollView( slivers: \[ SliverToBoxAdapter(), SliverList.builder( itemCount: viewModel.bookings.length, itemBuilder: (\_, index) => \_Booking( key: ValueKey(viewModel.bookings\[index].id), booking: viewModel.bookings\[index], onTap: () => context.push(Routes.bookingWithId( viewModel.bookings\[index].id) ), onDismissed: (\_) => viewModel.deleteBooking.execute( viewModel.bookings\[index].id, ), ), ), ], ); } ) ) ); } \`\`\` ### Handling user events Finally, a view needs to listen for \*events\* from users, so the view model can handle those events. This is achieved by exposing a callback method on the view model class which encapsulates all the logic. !\[A diagram showing a view's relationship to a view model.]\(/assets/images/docs/app-architecture/guide/feature-architecture-simplified-UI-highlighted.png) On the \`HomeScreen\`, users can delete previously booked events by swiping a \[\`Dismissible\`]\[] widget. Recall this code from the previous snippet: \`\`\`dart title=home\_screen.dart highlightLines=9-10 SliverList.builder( itemCount: widget.viewModel.bookings.length, itemBuilder: (\_, index) => \_Booking( key: ValueKey(viewModel.bookings\[index].id), booking: viewModel.bookings\[index], onTap: () => context.push( Routes.bookingWithId(viewModel.bookings\[index].id) ), onDismissed: (\_) => viewModel.deleteBooking.execute(widget.viewModel.bookings\[index].id), ), ), \`\`\` On the \`HomeScreen\`, a user's saved trip is represented by the \`\_Booking\` widget. When a \`\_Booking\` is dismissed, the \`viewModel.deleteBooking\` method is executed. A saved booking is application state that persists beyond a session or the lifetime of a view, and only repositories should modify such application state. So, the \`HomeViewModel.deleteBooking\` method turns around and calls a method exposed by a repository in the data layer, as shown in the following code snippet. \`\`\`dart title=home\_viewmodel.dart highlightLines=3 Future> \_deleteBooking(int id) async { try { final resultDelete = await \_bookingRepository.delete(id); switch (resultDelete) { case Ok(): \_log.fine('Deleted booking $id'); case Error(): \_log.warning('Failed to delete booking $id', resultDelete.error); return resultDelete; } // Some code was omitted for brevity. // final resultLoadBookings = ...; return resultLoadBookings; } finally { notifyListeners(); } } \`\`\` In the Compass app, these methods that handle user events are called \*\*commands\*\*. ### Command objects Commands are responsible for the interaction that starts in the UI layer and flows back to the data layer. In this app specifically, a \`Command\` is also a type that helps update the UI safely, regardless of the response time or contents. The \`Command\` class wraps a method and helps handle the different states of that method, such as \`running\`, \`complete\`, and \`error\`. These states make it easy to display different UI, like loading indicators when \`Command.running\` is true. The following is code from the \`Command\` class. Some code has been omitted for demo purposes. \`\`\`dart title=command.dart abstract class Command extends ChangeNotifier { Command(); bool running = false; Result? \_result; /// true if action completed with error bool get error => \_result is Error; /// true if action completed successfully bool get completed => \_result is Ok; /// Internal execute implementation Future \_execute(action) async { if (\_running) return; // Emit running state - e.g. button shows loading state \_running = true; \_result = null; notifyListeners(); try { \_result = await action(); } finally { \_running = false; notifyListeners(); } } } \`\`\` The \`Command\` class itself extends \`ChangeNotifier\`, and within the method \`Command.execute\`, \`notifyListeners\` is called multiple times. This allows the view to handle different states with very little logic, which you'll see an example of later on this page. You may have also noticed that \`Command\` is an abstract class. It's implemented by concrete classes such as \`Command0\` \`Command1\`. The integer in the class name refers to the number of arguments that the underlying method expects. You can see examples of these implementation classes in the Compass app's \[\`utils\` directory]\[]. :::tip Package option To use this pattern without writing your own command classes, consider using a package such as \[\`command\_it\`]\[], which provides command types to wrap actions and track their running, completed, and error states. ::: \[\`command\_it\`]: https\://pub.dev/packages/command\_it ### Ensuring views can render before data exists In view model classes, commands are created in the constructor. \`\`\`dart title=home\_viewmodel.dart highlightLines=8-9,15-16,24-30 class HomeViewModel extends ChangeNotifier { HomeViewModel({ required BookingRepository bookingRepository, required UserRepository userRepository, }) : \_bookingRepository = bookingRepository, \_userRepository = userRepository { // Load required data when this screen is built. load = Command0(\_load)..execute(); deleteBooking = Command1(\_deleteBooking); } final BookingRepository \_bookingRepository; final UserRepository \_userRepository; late Command0 load; late Command1 deleteBooking; User? \_user; User? get user => \_user; List \_bookings = \[]; List get bookings => \_bookings; Future \_load() async { // ... } Future> \_deleteBooking(int id) async { // ... } // ... } \`\`\` The \`Command.execute\` method is asynchronous, so it can't guarantee that the data will be available when the view wants to render. This gets at \*why\* the Compass app uses \`Commands\`. In the view's \`Widget.build\` method, the command is used to conditionally render different widgets. \`\`\`dart title=home\_screen.dart // ... child: ListenableBuilder( listenable: \[!viewModel.load!], builder: (context, child) { if (\[!viewModel.load.running!]) { return const Center(child: CircularProgressIndicator()); } if (\[!viewModel.load.error!]) { return ErrorIndicator( title: AppLocalization.of(context).errorWhileLoadingHome, label: AppLocalization.of(context).tryAgain, onPressed: viewModel.load.execute, ); } // The command has completed without error. // Return the main view widget. return child!; }, ), // ... \`\`\` Because the \`load\` command is a property that exists on the view model rather than something ephemeral, it doesn't matter when the \`load\` method is called or when it resolves. For example, if the load command resolves before the \`HomeScreen\` widget was even created, it isn't a problem because the \`Command\` object still exists, and exposes the correct state. This pattern standardizes how common UI problems are solved in the app, making your codebase less error-prone and more scalable, but it's not a pattern that every app will want to implement. Whether you want to use it is highly dependent on other architectural choices you make. Many libraries that help you manage state have their own tools to solve these problems. For example, if you were to use \[streams]\[] and \[\`StreamBuilders\`]\[] in your app, the \[\`AsyncSnapshot\`]\[] classes provided by Flutter have this functionality built in. :::note Real world example While building the Compass app, we found a bug that was solved by using the Command pattern. \[Read about it on GitHub]\[]. ::: \[UI layer]: /app-architecture/guide#ui-layer \[\`View\`]: /app-architecture/guide#views \[\`ViewModel\`]: /app-architecture/guide#view-models \[repositories]: /app-architecture/guide#repositories \[commands]: /app-architecture/guide#command-objects \[\`package:freezed\`]: https\://pub.dev/packages/freezed \[\`ChangeNotifier\`]: https\://api.flutter.dev/flutter/foundation/ChangeNotifier-class.html \[\`Listenable\`]: https\://api.flutter.dev/flutter/foundation/Listenable-class.html \[\`ListenableBuilder\`]: https\://api.flutter.dev/flutter/widgets/ListenableBuilder-class.html \[\`notifyListeners\`]: https\://api.flutter.dev/flutter/foundation/ChangeNotifier/notifyListeners.html \[\`Scaffold\`]: https\://pub.dev/documentation/material\_ui/latest/material\_ui/Scaffold-class.html \[\`Dismissible\`]: https\://api.flutter.dev/flutter/widgets/Dismissible-class.html \[\`utils\` directory]: https\://github.com/flutter/samples/blob/main/compass\_app/app/lib/utils/command.dart \[streams]: https\://api.flutter.dev/flutter/dart-async/Stream-class.html \[\`StreamBuilders\`]: https\://api.flutter.dev/flutter/widgets/StreamBuilder-class.html \[\`AsyncSnapshot\`]: https\://api.flutter.dev/flutter/widgets/AsyncSnapshot-class.html \[Read about it on GitHub]: https\://github.com/flutter/samples/pull/2449#pullrequestreview-2328333146 ## Feedback As this section of the website is evolving, we \[welcome your feedback]\[]! \[welcome your feedback]: https\://google.qualtrics.com/jfe/form/SV\_4T0XuR9Ts29acw6?page="case-study/ui-layer"

----
url: https://docs.flutter.dev/cookbook/design/tabs
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Work with tabs](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

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](https://pub.dev/documentation/material_ui/latest/material_ui).

This recipe creates a tabbed example using the following steps;

1. Create a `TabController`.
2. Create the tabs.
3. Create content for each tab.

## 1. Create a `TabController`

[#](#1-create-a-tabcontroller)

For tabs to work, you need to keep the selected tab and content sections in sync. This is the job of the [`TabController`](https://pub.dev/documentation/material_ui/latest/material_ui/TabController-class.html).

Either create a `TabController` manually, or automatically by using a [`DefaultTabController`](https://pub.dev/documentation/material_ui/latest/material_ui/DefaultTabController-class.html) widget.

Using `DefaultTabController` is the simplest option, since it creates a `TabController` and makes it available to all descendant widgets.

dart

```
return MaterialApp(
  home: DefaultTabController(length: 3, child: Scaffold()),
);
```

## 2. Create the tabs

[#](#2-create-the-tabs)

When a tab is selected, it needs to display content. You can create tabs using the [`TabBar`](https://pub.dev/documentation/material_ui/latest/material_ui/TabBar-class.html) widget. In this example, create a `TabBar` with three [`Tab`](https://pub.dev/documentation/material_ui/latest/material_ui/Tab-class.html) widgets and place it within an [`AppBar`](https://pub.dev/documentation/material_ui/latest/material_ui/AppBar-class.html).

dart

```
return MaterialApp(
  home: DefaultTabController(
    length: 3,
    child: Scaffold(
      appBar: AppBar(
        bottom: const TabBar(
          tabs: [
            Tab(icon: Icon(Icons.directions_car)),
            Tab(icon: Icon(Icons.directions_transit)),
            Tab(icon: Icon(Icons.directions_bike)),
          ],
        ),
      ),
    ),
  ),
);
```

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

[#](#3-create-content-for-each-tab)

Now that you have tabs, display content when a tab is selected. For this purpose, use the [`TabBarView`](https://pub.dev/documentation/material_ui/latest/material_ui/TabBarView-class.html) widget.

dart

```
body: const TabBarView(
  children: [
    Icon(Icons.directions_car),
    Icon(Icons.directions_transit),
    Icon(Icons.directions_bike),
  ],
),
```

## Interactive example

[#](#interactive-example)

```
import 'package:flutter/material.dart';

void main() {
  runApp(const TabBarDemo());
}

class TabBarDemo extends StatelessWidget {
  const TabBarDemo({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: DefaultTabController(
        length: 3,
        child: Scaffold(
          appBar: AppBar(
            bottom: const TabBar(
              tabs: [
                Tab(icon: Icon(Icons.directions_car)),
                Tab(icon: Icon(Icons.directions_transit)),
                Tab(icon: Icon(Icons.directions_bike)),
              ],
            ),
            title: const Text('Tabs Demo'),
          ),
          body: const TabBarView(
            children: [
              Icon(Icons.directions_car),
              Icon(Icons.directions_transit),
              Icon(Icons.directions_bike),
            ],
          ),
        ),
      ),
    );
  }
}
```

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-09-29. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/design/tabs.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/cookbook/design/tabs\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/design/tabs.md "Report an issue with this page").

----
url: https://docs.flutter.dev/packages-and-plugins
----

[Flutter Favorite program](/packages-and-plugins/favorites)

[Guidelines for identifying a plugin or package as a Flutter Favorite.](/packages-and-plugins/favorites)

[Package dependency management](/packages-and-plugins/dependency-management)

[Learn how Flutter and Dart resolve package dependencies, and how to handle version conflicts.](/packages-and-plugins/dependency-management)

[Swift Package Manager for Flutter](/packages-and-plugins/swift-package-manager)

[Content covering Flutter's integration with Swift Package Manager](/packages-and-plugins/swift-package-manager)

[Using packages](/packages-and-plugins/using-packages)

[How to use packages in your Flutter app.](/packages-and-plugins/using-packages)

[Background processes](/packages-and-plugins/background-processes)

[Where to find more information on implementing background processes in Flutter.](/packages-and-plugins/background-processes)

[Developing packages & plugins](/packages-and-plugins/developing-packages)

[How to write packages and plugins for Flutter.](/packages-and-plugins/developing-packages)

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/packages-and-plugins/index.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/packages-and-plugins\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/packages-and-plugins/index.md "Report an issue with this page").

----
url: https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.17.0
----

The 2.17.0 release of the Dart and Flutter DevTools includes the following changes among other general improvements. To learn more about DevTools, check out the [DevTools overview](https://docs.flutter.dev/tools/devtools).

## Inspector updates

[#](#inspector-updates)

* Added support for manually setting the package directories for your app. If you've ever loaded the Inspector and noticed that some of your widgets aren't present in the widget tree, this might indicate that the package directories for your app haven't been set or detected properly. Your package directories determine which widgets the Inspector considers to be from *your* application. If you see an empty Inspector widget tree, or if you develop widgets across multiple packages, and want widgets from all these locations to show up in your tree, check the **Inspector Settings** dialog to ensure that your package directories are properly configured - [#4306](https://github.com/flutter/devtools/pull/4306)

## Performance updates

[#](#performance-updates)

* Added a **Frame Analysis** tab to the Performance page. When analyzing a janky Flutter frame, this view provides hints for how to diagnose the jank and detects expensive operations that might have contributed to the slow frame time. This view also shows a breakdown of your Flutter frame time per phase (**Build**, **Layout**, **Paint**, and **Raster**) to try to guide you in the right direction - [#4339](https://github.com/flutter/devtools/pull/4339)

## Full commit history

[#](#full-commit-history)

To find a complete list of changes since the previous release, check out [the diff on GitHub](https://github.com/flutter/devtools/compare/v2.16.0...v2.17.0).

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/release-notes/release-notes-2.17.0.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.17.0\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/release-notes/release-notes-2.17.0.md "Report an issue with this page").

----
url: https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.26.1
----

The 2.26.1 release of the Dart and Flutter DevTools includes the following changes among other general improvements. To learn more about DevTools, check out the [DevTools overview](https://docs.flutter.dev/tools/devtools).

## General updates

[#](#general-updates)

* Added a new "Home" screen in DevTools that either shows the "Connect" dialog or a summary of your connected app, depending on the connection status in DevTools. Keep an eye on this screen for cool new features in the future. This change also enables support for static tooling (tools that don't require a connected app) in DevTools - [#6010](https://github.com/flutter/devtools/pull/6010)

* Fixed overlay notifications so that they cover the area that their background blocks - [#5975](https://github.com/flutter/devtools/pull/5975)

## Memory updates

[#](#memory-updates)

* Added a context menu to rename or delete a heap snapshot from the list - [#5997](https://github.com/flutter/devtools/pull/5997)
* Warn users when HTTP logging may be affecting their app's memory consumption - [#5998](https://github.com/flutter/devtools/pull/5998)

## Debugger updates

[#](#debugger-updates)

* Improvements to text selection and copy behavior in the code view, console, and variables windows - [#6020](https://github.com/flutter/devtools/pull/6020)

## Network profiler updates

[#](#network-profiler-updates)

* Added a selector to customize the display type of text and json responses (thanks to @hhacker1999!) - [#5816](https://github.com/flutter/devtools/pull/5816)

## Full commit history

[#](#full-commit-history)

To find a complete list of changes since the previous release, check out [the diff on GitHub](https://github.com/flutter/devtools/compare/v2.25.0...v2.26.1).

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/release-notes/release-notes-2.26.1.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.26.1\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/release-notes/release-notes-2.26.1.md "Report an issue with this page").

----
url: https://docs.flutter.dev/community/china
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Using Flutter in China](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

To speed the download and installation of Flutter in China, consider using a [mirror site](https://en.wikipedia.org/wiki/Mirror_site) or *mirror*.

## Use a Flutter mirror site

[#](#use-a-flutter-mirror-site)

The [China Flutter User Group](https://github.com/cfug) (CFUG) maintains a Simplified Chinese Flutter website <https://flutter.cn> and a mirror. Other mirrors can be found at the [end of this guide](#known-trusted-community-run-mirror-sites).

### Configure your machine to use a mirror site

[#](#configure-your-machine-to-use-a-mirror-site)

To install or use Flutter in China, use a trustworthy Flutter mirror. This requires setting two environment variables on your machine.

*All examples that follow presume that you are using the CFUG mirror.*

To set your machine to use a mirror site:

* [Windows](#21-tab-panel)
* [macOS](#22-tab-panel)
* [Linux](#23-tab-panel)

These steps require using PowerShell.

1. Open a new window in PowerShell to prepare to run shell commands.

2. Set `PUB_HOSTED_URL` to your mirror site.

   ```
   $ $env:PUB_HOSTED_URL="https://pub.flutter-io.cn"
   ```

3. Set `FLUTTER_STORAGE_BASE_URL` to your mirror site.

   ```
   $ $env:FLUTTER_STORAGE_BASE_URL="https://storage.flutter-io.cn"
   ```

4. Download the Flutter archive from your preferred mirror site.

   For CFUG, visit their [Flutter SDK archive](https://docs.flutter.cn/install/archive/), and download the SDK for your platform and architecture.

5. Create a folder where you can install Flutter. Then change into it. Consider a path like `$env:USERPROFILE\dev`.

   ```
   $ New-Item -Path "$env:USERPROFILE\dev" -ItemType Directory; cd "$env:USERPROFILE\dev"
   ```

6. Extract the SDK from the zip archive file.

   This example assumes you downloaded the Windows version of the Flutter SDK. You'll need to replace the path to the archive with the path to the archive file and version you downloaded.

   ```
   $ Expand-Archive .\flutter_windows_3.35.5-stable.zip
   ```

7. Add Flutter to your `PATH` environment variable.

   ```
   $ $env:PATH = $pwd.PATH + "\flutter\bin",$env:PATH -join ";"
   ```

8. Begin developing with Flutter.

   After following these steps, Flutter fetches packages and artifacts from `flutter-io.cn` in the current terminal window.

   To set these values permanently across terminals, follow the instructions on adding [Flutter to your PATH](/install/add-to-path#windows), also adding the `PUB_HOSTED_URL` and `FLUTTER_STORAGE_BASE_URL` variables.

1) Open a new window in your terminal to prepare to run shell commands.

2) Set `PUB_HOSTED_URL` to your mirror site.

   ```
   $ export PUB_HOSTED_URL="https://pub.flutter-io.cn"
   ```

3) Set `FLUTTER_STORAGE_BASE_URL` to your mirror site.

   ```
   $ export FLUTTER_STORAGE_BASE_URL="https://storage.flutter-io.cn"
   ```

4) Download the Flutter archive from your preferred mirror site.

   For CFUG, visit their [Flutter SDK archive](https://docs.flutter.cn/install/archive/), and download the SDK for your platform and architecture.

5) Create a folder where you can install Flutter. Then change into it. Consider a path like `~/dev`.

   ```
   $ mkdir ~/dev; cd ~/dev
   ```

6) Extract the SDK from the zip archive file.

   This example assumes you downloaded the macOS version of the Flutter SDK. You'll need to replace the path to the archive with the path to the archive file and version you downloaded.

   ```
   $ unzip flutter_macos_3.35.5-stable.zip
   ```

7) Add Flutter to your `PATH` environment variable.

   ```
   $ export PATH="$PWD/flutter/bin:$PATH"
   ```

8) Begin developing with Flutter.

   After following these steps, Flutter fetches packages and artifacts from `flutter-io.cn` in the current terminal window.

   To set these values permanently across terminals, follow the instructions on adding [Flutter to your PATH](/install/add-to-path#macos), also adding the `PUB_HOSTED_URL` and `FLUTTER_STORAGE_BASE_URL` variables.

1. Open a new window in your terminal to prepare to run shell commands.

2. Set `PUB_HOSTED_URL` to your mirror site.

   ```
   $ export PUB_HOSTED_URL="https://pub.flutter-io.cn"
   ```

3. Set `FLUTTER_STORAGE_BASE_URL` to your mirror site.

   ```
   $ export FLUTTER_STORAGE_BASE_URL="https://storage.flutter-io.cn"
   ```

4. Download the Flutter archive from your preferred mirror site.

   For CFUG, visit their [Flutter SDK archive](https://docs.flutter.cn/install/archive/), and download the SDK for your platform and architecture.

5. Create a folder where you can install Flutter. Then change into it. Consider a path like `~/dev`.

   ```
   $ mkdir ~/dev; cd ~/dev
   ```

6. Extract the SDK from the tar archive file.

   This example assumes you downloaded the Linux version of the Flutter SDK. You'll need to replace the path to the archive with the path to the archive file and version you downloaded.

   ```
   $ tar -xf flutter_linux_3.35.5-stable.tar.xz
   ```

7. Add Flutter to your `PATH` environment variable.

   ```
   $ export PATH="$PWD/flutter/bin:$PATH"
   ```

8. Begin developing with Flutter.

   After following these steps, Flutter fetches packages and artifacts from `flutter-io.cn` in the current terminal window.

   To set these values permanently across terminals, follow the instructions on adding [Flutter to your PATH](/install/add-to-path#linux), also adding the `PUB_HOSTED_URL` and `FLUTTER_STORAGE_BASE_URL` variables.

### Download Flutter archives based on a mirror site

[#](#download-flutter-archives-based-on-a-mirror-site)

To download Flutter from the [SDK archive](/install/archive) from a mirror, replace `storage.googleapis.com` with the URL of your trusted mirror. Use your mirror site in the browser or in other applications like IDM or Thunder. This should improve download speed.

The following example shows how to change the URL for Flutter's download site from Google's archive to CFUG's mirror.

* [Windows](#24-tab-panel)
* [macOS](#25-tab-panel)
* [Linux](#26-tab-panel)

To download the x64, Windows version of the Flutter SDK, you would change the original URL from:

```
https://storage.googleapis.com/flutter_infra_release/releases/stable/windows/flutter_windows_3.35.5-stable.zip
```

to the mirror URL:

```
https://storage.flutter-io.cn/flutter_infra_release/releases/stable/windows/flutter_windows_3.35.5-stable.zip
```

To download the arm64, macOS version of the Flutter SDK, you would change the original URL from:

```
https://storage.googleapis.com/flutter_infra_release/releases/stable/macos/flutter_macos_arm64_3.35.5-stable.zip
```

to the mirror URL:

```
https://storage.flutter-io.cn/flutter_infra_release/releases/stable/macos/flutter_macos_arm64_3.35.5-stable.zip
```

To download the Linux version of the Flutter SDK, you would change the original URL from:

```
https://storage.googleapis.com/flutter_infra_release/releases/stable/linux/flutter_linux_3.35.5-stable.tar.xz
```

to the mirror URL:

```
https://storage.flutter-io.cn/flutter_infra_release/releases/stable/linux/flutter_linux_3.35.5-stable.tar.xz
```

## Configure your machine to publish your package

[#](#configure-your-machine-to-publish-your-package)

To publish your packages to `pub.dev`, you need to be able to access both Google Auth and the `pub.dev` site.

To enable access to `pub.dev`:

* [Windows](#27-tab-panel)
* [macOS](#28-tab-panel)
* [Linux](#29-tab-panel)

1. Configure a proxy. To configure a proxy, check out the [Dart documentation on proxies](https://dart.dev/tools/pub/troubleshoot#pub-get-fails-from-behind-a-corporate-firewall).

2. Verify that your `PUB_HOSTED_URL` environment variable is either unset or empty.

   ```
   $ echo $env:PUB_HOSTED_URL
   ```

   If this command returns any value, unset it.

   ```
   $ Remove-Item $env:PUB_HOSTED_URL
   ```

1) Configure a proxy. To configure a proxy, check out the [Dart documentation on proxies](https://dart.dev/tools/pub/troubleshoot#pub-get-fails-from-behind-a-corporate-firewall).

2) Verify that your `PUB_HOSTED_URL` environment variable is either unset or empty.

   ```
   $ echo $PUB_HOSTED_URL
   ```

   If this command returns any value, unset it.

   ```
   $ unset $PUB_HOSTED_URL
   ```

1. Configure a proxy. To configure a proxy, check out the [Dart documentation on proxies](https://dart.dev/tools/pub/troubleshoot#pub-get-fails-from-behind-a-corporate-firewall).

2. Verify that your `PUB_HOSTED_URL` environment variable is either unset or empty.

   ```
   $ echo $PUB_HOSTED_URL
   ```

   If this command returns any value, unset it.

   ```
   $ unset $PUB_HOSTED_URL
   ```

To learn more about publishing packages, check out the [Dart documentation on publishing packages](https://dart.dev/tools/pub/publishing).

## Known, trusted community-run mirror sites

[#](#known-trusted-community-run-mirror-sites)

The Flutter team can't guarantee the long-term availability of any mirrors. You can use other mirrors if they become available.

***

### China Flutter User Group

[#](#china-flutter-user-group)

[China Flutter User Group](https://github.com/cfug) maintains the `flutter-io.cn` mirror. It includes the Flutter SDK and pub packages.

#### Configure your machine to use this mirror

[#](#configure-your-machine-to-use-this-mirror)

To set your machine to use this mirror, use these commands.

On macOS, Linux, or ChromeOS:

```
export PUB_HOSTED_URL=https://pub.flutter-io.cn
export FLUTTER_STORAGE_BASE_URL=https://storage.flutter-io.cn
```

On Windows:

```
$env:PUB_HOSTED_URL="https://pub.flutter-io.cn"
$env:FLUTTER_STORAGE_BASE_URL="https://storage.flutter-io.cn"
```

#### Get support for this mirror

[#](#get-support-for-this-mirror)

If you're running into issues that only occur when using the `flutter-io.cn` mirror, report the issue to their [issue tracker](https://github.com/cfug/flutter.cn/issues/new/choose).

***

### Shanghai Jiao Tong University \*nix User Group

[#](#shanghai-jiao-tong-university-nix-user-group)

[Shanghai Jiao Tong University \*nix User Group](https://github.com/sjtug) maintains the `mirror.sjtu.edu.cn` mirror. It includes the Flutter SDK and pub packages.

#### Configure your machine to use this mirror

[#](#configure-your-machine-to-use-this-mirror-1)

To set your machine to use this mirror, use these commands.

On macOS, Linux, or ChromeOS:

```
export PUB_HOSTED_URL=https://mirror.sjtu.edu.cn/dart-pub
export FLUTTER_STORAGE_BASE_URL=https://mirror.sjtu.edu.cn
```

On Windows:

```
$env:PUB_HOSTED_URL="https://mirror.sjtu.edu.cn/dart-pub"
$env:FLUTTER_STORAGE_BASE_URL="https://mirror.sjtu.edu.cn"
```

#### Get support for this mirror

[#](#get-support-for-this-mirror-1)

If you're running into issues that only occur when using the `mirror.sjtu.edu.cn` mirror, report the issue to their [issue tracker](https://github.com/sjtug/mirror-requests).

***

### Tsinghua University TUNA Association

[#](#tsinghua-university-tuna-association)

[Tsinghua University TUNA Association](https://tuna.moe) maintains the `mirrors.tuna.tsinghua.edu.cn` mirror. It includes the Flutter SDK and pub packages.

#### Configure your machine to use this mirror

[#](#configure-your-machine-to-use-this-mirror-2)

To set your machine to use this mirror, use these commands.

On macOS, Linux, or ChromeOS:

```
export PUB_HOSTED_URL=https://mirrors.tuna.tsinghua.edu.cn/dart-pub
export FLUTTER_STORAGE_BASE_URL=https://mirrors.tuna.tsinghua.edu.cn/flutter
```

On Windows:

```
$env:PUB_HOSTED_URL="https://mirrors.tuna.tsinghua.edu.cn/dart-pub"
$env:FLUTTER_STORAGE_BASE_URL="https://mirrors.tuna.tsinghua.edu.cn/flutter"
```

#### Get support for this mirror

[#](#get-support-for-this-mirror-2)

If you're running into issues that only occur when using the `mirrors.tuna.tsinghua.edu.cn` mirror, report the issue to their [issue tracker](https://github.com/tuna/issues).

## Offer to host a new mirror site

[#](#offer-to-host-a-new-mirror-site)

If you're interested in setting up your own mirror, contact <flutter-dev@googlegroups.com> for assistance.

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/community/china/index.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/community/china\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/community/china/index.md "Report an issue with this page").

----
url: https://docs.flutter.dev/reference
----

[Flutter crash reporting](/reference/crash-reporting)

[How Google uses crash reporting, what is collected, and how to opt out.](/reference/crash-reporting)

[Create a new Flutter app](/reference/create-new-app)

[Learn how to bootstrap a new Flutter application from your command-line, different editors, and even in the cloud.](/reference/create-new-app)

[Supported deployment platforms](/reference/supported-platforms)

[The platforms that Flutter supports by platform version.](/reference/supported-platforms)

[flutter: The Flutter command-line tool](/reference/flutter-cli)

[The reference page for using 'flutter' in a terminal window.](/reference/flutter-cli)

[User surveys](/reference/user-surveys)

[Why users see a survey announcement, how the data is used, and how to disable.](/reference/user-surveys)

[Security false positives](/reference/security-false-positives)

[Security vulnerabilities incorrectly reported by automated static analysis tools.](/reference/security-false-positives)

[Flutter widget index](/reference/widgets)

[An alphabetical list of Flutter widgets.](/reference/widgets)

[Flutter learning resources](/reference/learning-resources)

[A catalog of Flutter tutorials, sample code, workshops, and recipes.](/reference/learning-resources)

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/reference/index.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/reference\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/reference/index.md "Report an issue with this page").

----
url: https://docs.flutter.dev/cookbook/design
----

[Display a Cupertino sheet](/cookbook/design/cupertino-sheets)

[How to implement a Cupertino sheet to display messages and content.](/cookbook/design/cupertino-sheets)

[Update the UI based on orientation](/cookbook/design/orientation)

[Respond to a change in the screen's orientation.](/cookbook/design/orientation)

[Use themes to share colors and font styles](/cookbook/design/themes)

[How to share colors and font styles throughout an app using Themes.](/cookbook/design/themes)

[Add a drawer to a screen](/cookbook/design/drawer)

[How to implement a Material Drawer.](/cookbook/design/drawer)

[Use a custom font](/cookbook/design/fonts)

[How to use custom fonts.](/cookbook/design/fonts)

[Work with tabs](/cookbook/design/tabs)

[How to implement tabs in a layout.](/cookbook/design/tabs)

[Display a snackbar](/cookbook/design/snackbars)

[How to implement a snackbar to display messages.](/cookbook/design/snackbars)

[Export fonts from a package](/cookbook/design/package-fonts)

[How to export fonts from a package.](/cookbook/design/package-fonts)

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/design/index.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/cookbook/design\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/design/index.md "Report an issue with this page").

----
url: https://docs.flutter.dev/learn/pathway/tutorial/widget-fundamentals
----

Learn to create custom widgets and use the most common SDK widgets like Container, Center, and Text.

[Watch on YouTube in a new tab: "Anatomy of a widget"](https://www.youtube.com/watch/gyBUnaojFDg)

widgets Create a custom StatelessWidget

tune Make widgets reusable with constructor parameters

palette Style widgets using Container and BoxDecoration

***

## Steps

1### Before you startkeyboard\_arrow\_up

This app relies on a bit of game logic that isn't UI-related, and thus is outside the scope of this tutorial. Before you move on, you need to add this logic to your app.

1. Download the following Dart file and save it as `lib/game.dart` in your project directory.

   game.dart

   dart

   ```
   /// Game logic and supporting types for Birdle,
   /// a five-letter word-guessing game similar to Wordle.
   ///
   /// Defines the [Game] state machine and the
   /// [Word], [Letter], and [HitType] data model used to
   /// represent guesses and their evaluation against a hidden word.
   library;
   ​
   import 'dart:collection';
   import 'dart:math';
   ​
   /// The result of evaluating a [Letter] of a guess against the hidden word.
   enum HitType {
     /// The letter hasn't yet been evaluated.
     none,
   ​
     /// The letter matches the hidden word's letter at the same position.
     hit,
   ​
     /// The letter is in the hidden word, but at a different position.
     partial,
   ​
     /// The letter doesn't appear in the hidden word.
     miss,
   }
   ​
   /// A single character paired with its [HitType] against the hidden word.
   typedef Letter = ({String char, HitType type});
   ​
   /// Every word that can be legally entered as a guess.
   const List<String> allLegalGuesses = [...legalWords, ...legalGuesses];
   ​
   /// Words that can be chosen as the hidden word.
   const List<String> legalWords = ['aback', 'abase', 'abate', 'abbey', 'abbot'];
   ​
   /// Additional words accepted as guesses beyond those in [legalWords].
   const List<String> legalGuesses = [
     'aback',
     'abase',
     'abate',
     'abbey',
     'abbot',
     'abhor',
     'abide',
     'abled',
     'abode',
     'abort',
   ];
   ​
   /// Game state of a single round of Birdle,
   /// a five-letter word-guessing game similar to Wordle.
   ///
   /// Exposes the state and methods a UI needs to
   /// evaluate guesses and track progress,
   /// but doesn't advance play on its own.
   ///
   /// Clients drive each round by calling [guess] to submit an attempt and
   /// [resetGame] to start over.
   class Game {
     /// The default maximum number of guesses allowed in a [Game].
     static const int defaultMaxGuesses = 5;
   ​
     /// Creates a new game with [maxGuesses] guesses allowed.
     ///
     /// If [seed] is provided, the hidden word is
     /// chosen deterministically from [legalWords],
     /// otherwise it is selected at random.
     Game({this.maxGuesses = defaultMaxGuesses, this.seed})
       : _wordToGuess = _generateInitialWord(seed),
         _guesses = List<Word>.filled(maxGuesses, Word.empty());
   ​
     /// The maximum number of guesses allowed in this game.
     final int maxGuesses;
   ​
     /// The seed used to choose the hidden word,
     /// or `null` if it was selected at random.
     final int? seed;
   ​
     /// The current hidden word, exposed publicly through [hiddenWord].
     Word _wordToGuess;
   ​
     /// Backing storage for [guesses].
     ///
     /// Holds every guess slot in order,
     /// with unfilled slots represented by empty [Word]s.
     List<Word> _guesses;
   ​
     /// The word the player is trying to guess.
     Word get hiddenWord => _wordToGuess;
   ​
     /// An unmodifiable view of every guess slot, including those still empty.
     UnmodifiableListView<Word> get guesses => UnmodifiableListView(_guesses);
   ​
     /// The most recently submitted guess,
     /// or an empty [Word] if no guesses have been made.
     Word get previousGuess {
       final index = _guesses.lastIndexWhere((word) => word.isNotEmpty);
       return index == -1 ? Word.empty() : _guesses[index];
     }
   ​
     /// The index of the next empty guess slot, or `-1` if every slot is full.
     int get activeIndex => _guesses.indexWhere((word) => word.isEmpty);
   ​
     /// The number of guesses still available to the player.
     int get guessesRemaining {
       if (activeIndex == -1) return 0;
       return maxGuesses - activeIndex;
     }
   ​
     /// Whether the most recent guess matches the hidden word.
     bool get didWin {
       if (_guesses.first.isEmpty) return false;
   ​
       for (final letter in previousGuess) {
         if (letter.type != HitType.hit) return false;
       }
   ​
       return true;
     }
   ​
     /// Whether all allowed guesses have been used without winning.
     bool get didLose => guessesRemaining == 0 && !didWin;
   ​
     /// Picks a new hidden word and clears every submitted guess.
     void resetGame() {
       _wordToGuess = _generateInitialWord(seed);
       _guesses = List<Word>.filled(maxGuesses, Word.empty());
     }
   ​
     /// Evaluates [guess] against the hidden word,
     /// records the result in [guesses], and returns it.
     ///
     /// For finer control, use [isLegalGuess] to validate input or
     /// [matchGuessOnly] to evaluate without recording the result.
     Word guess(String guess) {
       final result = matchGuessOnly(guess);
       addGuessToList(result);
       return result;
     }
   ​
     /// Whether [guess] is a legal word to guess.
     ///
     /// UIs can call this method before [guess] to
     /// show players a message when they enter an invalid word.
     bool isLegalGuess(String guess) => Word.fromString(guess).isLegalGuess;
   ​
     /// Evaluates [guess] against the hidden word without advancing the game.
     Word matchGuessOnly(String guess) =>
         Word.fromString(guess).evaluateGuess(_wordToGuess);
   ​
     /// Stores [guess] in the next empty slot of [guesses].
     void addGuessToList(Word guess) {
       final guessIndex = activeIndex;
       if (guessIndex == -1) {
         throw StateError('No guesses remaining.');
       }
   ​
       _guesses[guessIndex] = guess;
     }
   ​
     /// Returns the starting hidden word for a new round.
     ///
     /// Picks a deterministic word from [legalWords] when [seed] is provided,
     /// or one at random otherwise.
     static Word _generateInitialWord(int? seed) =>
         seed == null ? Word.random() : Word.fromSeed(seed);
   }
   ​
   /// A five-letter word made up of [Letter]s, each tracking its [HitType].
   class Word with IterableMixin<Letter> {
     /// Creates a word backed by the specified list of [Letter]s.
     Word(this._letters);
   ​
     /// Creates a word with five blank letters of [HitType.none].
     factory Word.empty() =>
         Word(List<Letter>.filled(5, (char: '', type: HitType.none)));
   ​
     /// Creates a [Word] from [guess].
     ///
     /// Each character is lowercased,
     /// every [Letter] starts as [HitType.none].
     factory Word.fromString(String guess) {
       if (guess.length != 5) {
         throw ArgumentError.value(
           guess,
           'guess',
           'Must be exactly 5 characters long.',
         );
       }
   ​
       final letters = guess
           .toLowerCase()
           .split('')
           .map((char) => (char: char, type: HitType.none))
           .toList();
       return Word(letters);
     }
   ​
     /// Creates a word chosen at random from [legalWords].
     factory Word.random() {
       final random = Random();
       final nextWord = legalWords[random.nextInt(legalWords.length)];
       return Word.fromString(nextWord);
     }
   ​
     /// Creates a word chosen from [legalWords] using [seed] as an index.
     factory Word.fromSeed(int seed) =>
         Word.fromString(legalWords[seed % legalWords.length]);
   ​
     /// An unmodifiable list of [Letter]s that make up this word.
     final List<Letter> _letters;
   ​
     @override
     Iterator<Letter> get iterator => _letters.iterator;
   ​
     /// Whether every [Letter] in this word has no character.
     @override
     bool get isEmpty => every((letter) => letter.char.isEmpty);
   ​
     @override
     int get length => _letters.length;
   ​
     /// The [Letter] at index [i] in word.
     Letter operator [](int i) => _letters[i];
   ​
     @override
     String toString() => _letters.map((letter) => letter.char).join().trim();
   ​
     /// Returns a multi-line string showing each [Letter] alongside its [HitType].
     ///
     /// Used to play the game from the command line.
     String toStringVerbose() => _letters
         .map((letter) => '${letter.char} - ${letter.type.name}')
         .join('\n');
   }
   ​
   /// Validation and guess-evaluation logic on [Word].
   extension WordUtils on Word {
     /// Whether this word appears in [allLegalGuesses].
     bool get isLegalGuess => allLegalGuesses.contains(toString());
   ​
     /// Compares this [Word] against the specified [hiddenWord]
     /// and returns a new [Word] with the same letters,
     /// but where each [Letter] has new a [HitType] of
     /// [HitType.hit], [HitType.partial], or [HitType.miss].
     Word evaluateGuess(Word hiddenWord) {
       assert(isLegalGuess);
   ​
       final result = List<Letter>.filled(length, (char: '', type: HitType.none));
       // Counts hidden-word letters that can still be claimed as partial matches.
       final unmatchedHiddenLetterCounts = <String, int>{};
   ​
       // Reserve exact matches before scoring partial matches.
       for (var i = 0; i < length; i++) {
         final guessChar = this[i].char;
         final hiddenChar = hiddenWord[i].char;
   ​
         if (guessChar == hiddenChar) {
           result[i] = (char: guessChar, type: HitType.hit);
         } else {
           // Track non-hit hidden letters for the partial-match pass.
           final unmatchedCount = unmatchedHiddenLetterCounts[hiddenChar] ?? 0;
           unmatchedHiddenLetterCounts[hiddenChar] = unmatchedCount + 1;
         }
       }
   ​
       // Spend each remaining hidden letter only once for partial matches.
       for (var i = 0; i < length; i++) {
         if (result[i].type == HitType.hit) continue;
   ​
         final guessChar = this[i].char;
         final unmatchedCount = unmatchedHiddenLetterCounts[guessChar] ?? 0;
         final isPartial = unmatchedCount > 0;
         if (isPartial) {
           // Use one available hidden letter for this partial match.
           unmatchedHiddenLetterCounts[guessChar] = unmatchedCount - 1;
         }
   ​
         result[i] = (
           char: guessChar,
           type: isPartial ? HitType.partial : HitType.miss,
         );
       }
   ​
       return Word(result);
     }
   }
   ```

2. To enable access to the types defined in the `game.dart` library, add an import to it from your `lib/main.dart` file:

   main.dart

   dart

   ```
   import 'package:material_ui/material_ui.dart';
   ​
   import 'game.dart';
   ```

2### Anatomy of a stateless widgetkeyboard\_arrow\_up

A `Widget` is a Dart class that extends one of the Flutter widget classes, in this case [`StatelessWidget`](https://api.flutter.dev/flutter/widgets/StatelessWidget-class.html).

Open your `main.dart` file and add this code below the `MainApp` class, which defines a new widget called `Tile`.

dart

```
class Tile extends StatelessWidget {
  const Tile(this.letter, this.hitType, {super.key});
​
  final String letter;
  final HitType hitType;
​
  @override
  Widget build(BuildContext context) {
    return Container();
  }
}
```

#### Constructor

[#](#constructor)

The `Tile` class has a [constructor](https://dart.dev/language/constructors) that defines what data needs to be passed into the widget to render the widget. In this case, the constructor accepts two parameters:

* A `String` representing the guessed letter of the tile.
* A `HitType` [enum value](https://dart.dev/language/enums) represent the guess result and used to determine the color of the tile. For example, `HitType.hit` results in a green tile.

Passing data into widget constructors is at the core of making widgets reusable.

#### Build method

[#](#build-method)

Finally, there's the all important `build` method, which must be defined on every widget, and will always return another widget.

dart

```
class Tile extends StatelessWidget {
  const Tile(this.letter, this.hitType, {super.key});
​
  final String letter;
  final HitType hitType;
​
  @override
  Widget build(BuildContext context) {
    // TODO: Replace Container with widgets.
    return Container();
  }
}
```

3### Use the custom widgetkeyboard\_arrow\_up

When the app is finished, there will be 25 instances of this widget on the screen. For now, display just a single tile in the center of the screen so you can see styling updates as you make them. In the `MainApp.build` method, replace the `Text` widget with your `Tile` widget:

dart

```
class MainApp extends StatelessWidget {
  const MainApp({super.key});
​
  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: Scaffold(
        body: Center(
          child: Tile('A', HitType.hit), // NEW
        ),
      ),
    );
  }
}
```

At the moment, your app appears blank because the `Tile` widget returns an empty `Container`, which doesn't render anything by default.

4### The `Container` widgetkeyboard\_arrow\_up

The `Tile` widget consists of three of the most common core widgets: `Container`, `Center`, and `Text`. [`Container`](https://api.flutter.dev/flutter/widgets/Container-class.html) is a convenience widget that wraps several core styling widgets, such as [`Padding`](https://api.flutter.dev/flutter/widgets/Padding-class.html), [`ColoredBox`](https://api.flutter.dev/flutter/widgets/ColoredBox-class.html), [`SizedBox`](https://api.flutter.dev/flutter/widgets/SizedBox-class.html), and [`DecoratedBox`](https://api.flutter.dev/flutter/widgets/DecoratedBox-class.html).

Because the finished UI contains 25 `Tile` widgets in neat columns and rows, it should have an explicit size. Set the width and height properties on the `Container`. (You could also do this with a `SizedBox` widget, but you'll use more properties of the `Container` next.)

dart

```
class Tile extends StatelessWidget {
  const Tile(this.letter, this.hitType, {super.key});
​
  final String letter;
  final HitType hitType;
​
  @override
  Widget build(BuildContext context) {
    // NEW
    return Container(
      width: 60,
      height: 60,
      // TODO: Add needed widgets
    );
  }
}
```

5### BoxDecorationkeyboard\_arrow\_up

Next, add a [`Border`](https://api.flutter.dev/flutter/widgets/Container-class.html) to the box with the following code:

dart

```
class Tile extends StatelessWidget {
  const Tile(this.letter, this.hitType, {super.key});
​
  final String letter;
  final HitType hitType;
​
  @override
  Widget build(BuildContext context) {
    // NEW
    return Container(
      width: 60,
      height: 60,
      decoration: BoxDecoration(
        border: Border.all(color: Colors.grey.shade300),
        // TODO: add background color
      ),
    );
  }
}
```

`BoxDecoration` is an object that knows how to add any number of decorations to a widget, from background color to borders to box shadows and more. In this case, you added a border. When you hot reload, a lightly colored border appears around the tile.

When this game is complete, the color of the tile will depend on the user's guess. The tile will be green when the user has guessed correctly, yellow when the letter is correct but the position is incorrect, and gray if the guess is wrong in both respects.

The following figure shows all three possibilities.

To achieve this in UI, use a [switch expression](https://dart.dev/language/branches#switch-expressions) to set the `color` of the `BoxDecoration`.

dart

```
class Tile extends StatelessWidget {
  const Tile(this.letter, this.hitType, {super.key});
​
  final String letter;
  final HitType hitType;
​
  @override
  Widget build(BuildContext context) {
    return Container(
      width: 60,
      height: 60,
      decoration: BoxDecoration(
        border: Border.all(color: Colors.grey.shade300),
        color: switch (hitType) {
          HitType.hit => Colors.green,
          HitType.partial => Colors.yellow,
          HitType.miss => Colors.grey,
          _ => Colors.white,
        },
        // TODO: add children
      ),
    );
  }
}
```

6### Child widgetskeyboard\_arrow\_up

Finally, add the `Center` and `Text` widgets to the `Container.child` property.

Most widgets in the Flutter SDK have a `child` or `children` property that's meant to be passed a widget or a list of widgets, respectively. It's the best practice to use the same naming convention in your own custom widgets.

dart

```
class Tile extends StatelessWidget {
  const Tile(this.letter, this.hitType, {super.key});
​
  final String letter;
  final HitType hitType;
​
  @override
  Widget build(BuildContext context) {
    return Container(
      width: 60,
      height: 60,
      decoration: BoxDecoration(
        border: Border.all(color: Colors.grey.shade300),
        color: switch (hitType) {
          HitType.hit => Colors.green,
          HitType.partial => Colors.yellow,
          HitType.miss => Colors.grey,
          _ => Colors.white,
        },
      ),
      child: Center(
        child: Text(
          letter.toUpperCase(),
          style: Theme.of(context).textTheme.titleLarge,
        ),
      ),
    );
  }
}
```

Hot reload, and a single green tile containing the letter **A** appears in the center of your app. To test different colors, try replacing the Tile widget in MainApp with one of the following examples, then hot reload:

dart

```
// main.dart line ~16
// green
Tile('A', HitType.hit);
// grey
Tile('A', HitType.miss);
// yellow
Tile('A', HitType.partial);
```

Soon, this small box will be one of many widgets on the screen. In the next lesson, you'll build the full 5x5 grid of tiles for the game board.

7### Reviewkeyboard\_arrow\_up

widgets Built a custom StatelessWidget keyboard\_arrow\_up

You created a new `Tile` widget by extending `StatelessWidget`. Every widget has a constructor to accept data and a `build` method that returns other widgets. This pattern is fundamental to building user interfaces with Flutter.

tune Made widgets reusable with constructor parameters keyboard\_arrow\_up

By accepting `letter` and `hitType` as constructor parameters, your `Tile` widget can display different content and colors. Passing data through constructors is how you can create flexible, reusable components.

palette Styled widgets using Container and BoxDecoration keyboard\_arrow\_up

You used `Container` to set the widget's size and `BoxDecoration` to add borders and background colors. Then to conditional style the tile's color, you used a switch expression on the `hitType` value.

8### Test yourselfkeyboard\_arrow\_up

### Widget Fundamentals Quiz

1 / 2

**What must every Flutter widget's `build` method return?**

1. A String describing the widget.

   Not quite.

   The `build` method returns a widget, not a String.

2. Another widget.

   That's right!

   The `build` method always returns another widget, which forms part of the widget tree.

3. A boolean indicating success or failure.

   Not quite.

   Widgets don't indicate success; they return other widgets to be rendered.

4. Null if there's nothing to display.

   Not quite.

   The `build` method cannot return null; it must return a valid widget.

**Which object is used to add decorations like borders, background colors, and shadows to a Container?**

1. ThemeData

   Not quite.

   ThemeData is for app-wide styling, not individual container decorations.

2. TextStyle

   Not quite.

   TextStyle is for text formatting, not container decorations.

3. BoxDecoration

   That's right!

   BoxDecoration can add borders, background colors, gradients, shadows, and more to a Container.

4. EdgeInsets

   Not quite.

   EdgeInsets is for specifying padding or margin, not visual decorations.

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-09-29. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/learn/pathway/tutorial/widget-fundamentals.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/learn/pathway/tutorial/widget-fundamentals\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/learn/pathway/tutorial/widget-fundamentals.md "Report an issue with this page").

----
url: https://docs.flutter.dev/perf/best-practices.md
----

\# Performance best practices > How to ensure that your Flutter app is performant. :::note To learn how to use the \*\*Performance View\*\* (part of Flutter DevTools) for debugging performance issues, see \[Using the Performance view]\[]. ::: \[Using the Performance view]: /tools/devtools/performance Generally, Flutter applications are performant by default, so you only need to avoid common pitfalls to get excellent performance. These best practice recommendations will help you write the most performant Flutter app possible. :::note If you are writing web apps in Flutter, you might be interested in a series of articles, written by the Flutter Material team, after they modified the \[Flutter Gallery]\[] app to make it more performant on the web: \* \[Optimizing performance in Flutter web apps with tree shaking and deferred loading]\[web-perf-1] \* \[Improving perceived performance with image placeholders, precaching, and disabled navigation transitions]\[web-perf-2] \* \[Building performant Flutter widgets]\[web-perf-3] ::: \[Flutter Gallery]: https\://flutter-gallery-archive.web.app \[web-perf-1]: https\://blog.flutter.dev/optimizing-performance-in-flutter-web-apps-with-tree-shaking-and-deferred-loading-535fbe3cd674 \[web-perf-2]: https\://blog.flutter.dev/improving-perceived-performance-with-image-placeholders-precaching-and-disabled-navigation-6b3601087a2b \[web-perf-3]: https\://blog.flutter.dev/building-performant-flutter-widgets-3b2558aa08fa How do you design a Flutter app to most efficiently render your scenes? In particular, how do you ensure that the painting code generated by the framework is as efficient as possible? Some rendering and layout operations are known to be slow, but can't always be avoided. They should be used thoughtfully, following the guidance below. ## Minimize expensive operations Some operations are more expensive than others, meaning that they consume more resources. Obviously, you want to only use these operations when necessary. How you design and implement your app's UI can have a big impact on how efficiently it runs. ### Control build() cost Here are some things to keep in mind when designing your UI: \* Avoid repetitive and costly work in \`build()\` methods since \`build()\` can be invoked frequently when ancestor widgets rebuild. \* Avoid overly large single widgets with a large \`build()\` function. Split them into different widgets based on encapsulation but also on how they change: \* When \`setState()\` is called on a \`State\` object, all descendent widgets rebuild. Therefore, localize the \`setState()\` call to the part of the subtree whose UI actually needs to change. Avoid calling \`setState()\` high up in the tree if the change is contained to a small part of the tree. \* The traversal to rebuild all descendents stops when the same instance of the child widget as the previous frame is re-encountered. This technique is heavily used inside the framework for optimizing animations where the animation doesn't affect the child subtree. See the \[\`TransitionBuilder\`]\[] pattern and the \[source code for \`SlideTransition\`]\[], which uses this principle to avoid rebuilding its descendents when animating. ("Same instance" is evaluated using \`operator ==\`, but see the pitfalls section at the end of this page for advice on when to avoid overriding \`operator ==\`.) \* To create reusable pieces of UIs, prefer using a \[\`StatelessWidget\`]\[] rather than a function. For more information, check out: \* \[Performance considerations]\[], part of the \[\`StatefulWidget\`]\[] API doc \* \[Widgets vs helper methods]\[], a video from the official Flutter YouTube channel that explains why widgets are more performant than functions. \[Performance considerations]: https\://api.flutter.dev/flutter/widgets/StatefulWidget-class.html#performance-considerations \[source code for \`SlideTransition\`]: https\://github.com/flutter/flutter/blob/main/packages/flutter/lib/src/widgets/transitions.dart#L168 \[\`StatefulWidget\`]: https\://api.flutter.dev/flutter/widgets/StatefulWidget-class.html \[\`StatelessWidget\`]: https\://api.flutter.dev/flutter/widgets/StatelessWidget-class.html \[\`TransitionBuilder\`]: https\://api.flutter.dev/flutter/widgets/TransitionBuilder.html \[Widgets vs helper methods]: https\://www\.youtube.com/watch?v=IOyq-eTRhvo --- ### Use StringBuffer for efficient string building When you need to build a string from multiple parts, especially inside a loop, using the \`+\` operator can be inefficient because it creates a new \`String\` object on each concatenation. A better approach is to use \`StringBuffer\`, which collects all the strings and concatenates them only once, when you call \`toString()\`. --- ### Use saveLayer() thoughtfully Some Flutter code uses \`saveLayer()\`, an expensive operation, to implement various visual effects in the UI. Even if your code doesn't explicitly call \`saveLayer()\`, other widgets or packages that you use might call it behind the scenes. Perhaps your app is calling \`saveLayer()\` more than necessary; excessive calls to \`saveLayer()\` can cause jank. #### Why is saveLayer expensive? Calling \`saveLayer()\` allocates an offscreen buffer and drawing content into the offscreen buffer might trigger a render target switch. The GPU wants to run like a firehose, and a render target switch forces the GPU to redirect that stream temporarily and then direct it back again. On mobile GPUs this is particularly disruptive to rendering throughput. #### When is saveLayer required? At runtime, if you need to dynamically display various shapes coming from a server (for example), each with some transparency, that might (or might not) overlap, then you pretty much have to use \`saveLayer()\`. #### Debugging calls to saveLayer How can you tell how often your app calls \`saveLayer()\`, either directly or indirectly? The \`saveLayer()\` method triggers an event on the \[DevTools timeline]\[]; learn when your scene uses \`saveLayer\` by checking the \`PerformanceOverlayLayer.checkerboardOffscreenLayers\` switch in the \[DevTools Performance view]\[]. \[DevTools timeline]: /tools/devtools/performance#timeline-events-tab #### Minimizing calls to saveLayer Can you avoid calls to \`saveLayer\`? It might require rethinking of how you create your visual effects: \* If the calls are coming from \_your\_ code, can you reduce or eliminate them? For example, perhaps your UI overlaps two shapes, each having non-zero transparency: \* If they always overlap in the same amount, in the same way, with the same transparency, you can precalculate what this overlapped, semi-transparent object looks like, cache it, and use that instead of calling \`saveLayer()\`. This works with any static shape you can precalculate. \* Can you refactor your painting logic to avoid overlaps altogether? \* If the calls are coming from a package that you don't own, contact the package owner and ask why these calls are necessary. Can they be reduced or eliminated? If not, you might need to find another package, or write your own. :::note Note to package owners As a best practice, consider providing documentation for when \`saveLayer\` might be necessary for your package, how it might be avoided, and when it can't be avoided. ::: Other widgets that might trigger \`saveLayer()\` and are potentially costly: \* \[\`ShaderMask\`]\[] \* \[\`ColorFilter\`]\[] \* \[\`Chip\`]\[]—might trigger a call to \`saveLayer()\` if \`disabledColorAlpha != 0xff\` \* \[\`Text\`]\[]—might trigger a call to \`saveLayer()\` if there's an \`overflowShader\` \[\`Chip\`]: https\://pub.dev/documentation/material\_ui/latest/material\_ui/Chip-class.html \[\`ColorFilter\`]: https\://api.flutter.dev/flutter/dart-ui/ColorFilter-class.html \[\`FadeInImage\`]: https\://api.flutter.dev/flutter/widgets/FadeInImage-class.html \[\`Opacity\`]: https\://api.flutter.dev/flutter/widgets/Opacity-class.html \[\`ShaderMask\`]: https\://api.flutter.dev/flutter/widgets/ShaderMask-class.html \[\`Text\`]: https\://api.flutter.dev/flutter/widgets/Text-class.html \[Transparent image]: https\://api.flutter.dev/flutter/widgets/Opacity-class.html#transparent-image --- ### Minimize use of opacity and clipping Opacity is another expensive operation, as is clipping. Here are some tips you might find to be useful: \* Use the \[\`Opacity\`]\[] widget only when necessary. See the \[Transparent image]\[] section in the \`Opacity\` API page for an example of applying opacity directly to an image, which is faster than using the \`Opacity\` widget. \* Instead of wrapping simple shapes or text in an \`Opacity\` widget, it's usually faster to just draw them with a semitransparent color. (Though this only works if there are no overlapping bits in the to-be-drawn shape.) \* To implement fading in an image, consider using the \[\`FadeInImage\`]\[] widget, which applies a gradual opacity using the GPU's fragment shader. For more information, check out the \[\`Opacity\`]\[] docs. \* \*\*Clipping\*\* doesn't call \`saveLayer()\` (unless explicitly requested with \`Clip.antiAliasWithSaveLayer\`), so these operations aren't as expensive as \`Opacity\`, but clipping is still costly, so use with caution. By default, clipping is disabled (\`Clip.none\`), so you must explicitly enable it when needed. \* To create a rectangle with rounded corners, instead of applying a clipping rectangle, consider using the \`borderRadius\` property offered by many of the widget classes. --- ### Implement grids and lists thoughtfully How your grids and lists are implemented might be causing performance problems for your app. This section describes an important best practice when creating grids and lists, and how to determine whether your app uses excessive layout passes. #### Be lazy! When building a large grid or list, use the lazy builder methods, with callbacks. That ensures that only the visible portion of the screen is built at startup time. For more information and examples, check out: \* \[Working with long lists]\[] \* \[Creating a \`ListView\` that loads one page at a time]\[] a community article by AbdulRahman AlHamali \* \[\`ListView\.builder\`]\[] API \[Creating a \`ListView\` that loads one page at a time]: https\://medium.com/saugo360/flutter-creating-a-listview-that-loads-one-page-at-a-time-c5c91b6fabd3 \[\`ListView\.builder\`]: https\://api.flutter.dev/flutter/widgets/ListView/ListView\.builder.html \[Working with long lists]: /cookbook/lists/long-lists #### Avoid intrinsics For information on how intrinsic passes might be causing problems with your grids and lists, see the next section. --- ### Minimize layout passes caused by intrinsic operations If you've done much Flutter programming, you are probably familiar with \[how layout and constraints work]\[] when creating your UI. You might even have memorized Flutter's basic layout rule: \*\*Constraints go down. Sizes go up. Parent sets position.\*\* For some widgets, particularly grids and lists, the layout process can be expensive. Flutter strives to perform just one layout pass over the widgets but, sometimes, a second pass (called an \_intrinsic pass\_) is needed, and that can slow performance. #### What is an intrinsic pass? An intrinsic pass happens when, for example, you want all cells to have the size of the biggest or smallest cell (or some similar calculation that requires polling all cells). For example, consider a large grid of \`Card\`s. A grid should have uniformly sized cells, so the layout code performs a pass, starting from the root of the grid (in the widget tree), asking \*\*each\*\* card in the grid (not just the visible cards) to return its \_intrinsic\_ size—the size that the widget prefers, assuming no constraints. With this information, the framework determines a uniform cell size, and re-visits all grid cells a second time, telling each card what size to use. #### Debugging intrinsic passes To determine whether you have excessive intrinsic passes, enable the \*\*\[Track layouts option]\[]\*\* in DevTools (disabled by default), and look at the app's \[stack trace]\[] to learn how many layout passes were performed. Once you enable tracking, intrinsic timeline events are labeled as '$runtimeType intrinsics'. #### Avoiding intrinsic passes You have a couple options for avoiding the intrinsic pass: \* Set the cells to a fixed size up front. \* Choose a particular cell to be the "anchor" cell—all cells will be sized relative to this cell. Write a custom \[\`RenderObject\`]\[] that positions the child anchor first and then lays out the other children around it. To dive even deeper into how layout works, check out the \[layout and rendering]\[] section in the \[Flutter architectural overview]\[]. \[Flutter architectural overview]: /resources/architectural-overview \[how layout and constraints work]: /ui/layout/constraints \[layout and rendering]: /resources/architectural-overview#layout-and-rendering \[stack trace]: /tools/devtools/cpu-profiler#flame-chart \[Track layouts option]: /tools/devtools/performance#track-layouts --- ### Build and display frames in 16ms Since there are two separate threads for building and rendering, you have 16ms for building, and 16ms for rendering on a 60Hz display. If latency is a concern, build and display a frame in 16ms \_or less\_. Note that means built in 8ms or less, and rendered in 8ms or less, for a total of 16ms or less. If your frames are rendering in well under 16ms total in \[profile mode]\[], you likely don't have to worry about performance even if some performance pitfalls apply, but you should still aim to build and render a frame as fast as possible. Why? \* Lowering the frame render time below 16ms might not make a visual difference, but it \*\*improves battery life\*\* and thermal issues. \* It might run fine on your device, but consider performance for the lowest device you are targeting. \* As 120fps devices become more widely available, you'll want to render frames in under 8ms (total) in order to provide the smoothest experience. If you are wondering why 60fps leads to a smooth visual experience, check out the video \[Why 60fps?]\[] \[profile mode]: /testing/build-modes#profile \[Why 60fps?]: https\://www\.youtube.com/watch?v=CaMTIgxCSqU ## Pitfalls If you need to tune your app's performance, or perhaps the UI isn't as smooth as you expect, the \[DevTools Performance view]\[] can help! Also, the Flutter plugin for your IDE might be useful. In the Flutter Performance window, enable the \*\*Show widget rebuild information\*\* check box. This feature helps you detect when frames are being rendered and displayed in more than 16ms. Where possible, the plugin provides a link to a relevant tip. The following behaviors might negatively impact your app's performance. \* Avoid using the \`Opacity\` widget, and particularly avoid it in an animation. Use \`AnimatedOpacity\` or \`FadeInImage\` instead. For more information, check out \[Performance considerations for opacity animation]\[]. \* When using an \`AnimatedBuilder\`, avoid putting a subtree in the builder function that builds widgets that don't depend on the animation. This subtree is rebuilt for every tick of the animation. Instead, build that part of the subtree once and pass it as a child to the \`AnimatedBuilder\`. For more information, check out \[Performance optimizations]\[]. \* Avoid clipping in an animation. If possible, pre-clip the image before animating it. \* Avoid using constructors with a concrete \`List\` of children (such as \`Column()\` or \`ListView()\`) if most of the children are not visible on screen to avoid the build cost. \* Avoid overriding \`operator ==\` on \`Widget\` objects. While it might seem like it would help by avoiding unnecessary rebuilds, in practice it hurts performance because it results in O(N²) behavior. The only exception to this rule is leaf widgets (widgets with no children), in the specific case where comparing the properties of the widget is likely to be significantly more efficient than rebuilding the widget and where the widget will rarely change configuration. Even in such cases, it is generally preferable to rely on caching the widgets, because even one override of \`operator ==\` can result in across-the-board performance degradation as the compiler can no longer assume that the call is always static. ## Resources For more performance info, check out the following resources: \* \[Performance optimizations]\[] in the AnimatedBuilder API page \* \[Performance considerations for opacity animation]\[] in the Opacity API page \* \[Child elements' lifecycle]\[] and how to load them efficiently, in the ListView API page \* \[Performance considerations]\[] of a \`StatefulWidget\` \* \[Best practices for optimizing Flutter web loading speed]\[best-practices-medium] \[Child elements' lifecycle]: https\://api.flutter.dev/flutter/widgets/ListView-class.html#child-elements-lifecycle \[\`CustomPainter\`]: https\://api.flutter.dev/flutter/rendering/CustomPainter-class.html \[DevTools Performance view]: /tools/devtools/performance \[Performance optimizations]: https\://api.flutter.dev/flutter/widgets/AnimatedBuilder-class.html#performance-optimizations \[Performance considerations for opacity animation]: https\://api.flutter.dev/flutter/widgets/Opacity-class.html#performance-considerations-for-opacity-animation \[\`RenderObject\`]: https\://api.flutter.dev/flutter/rendering/RenderObject-class.html \[best-practices-medium]: https\://blog.flutter.dev/best-practices-for-optimizing-flutter-web-loading-speed-7cc0df14ce5c

----
url: https://docs.flutter.dev/resources/inside-flutter.md
----

\# Inside Flutter > Learn about Flutter's inner workings from one of the founding engineers. This document describes the inner workings of the Flutter toolkit that make Flutter's API possible. Because Flutter widgets are built using aggressive composition, user interfaces built with Flutter have a large number of widgets. To support this workload, Flutter uses sublinear algorithms for layout and building widgets as well as data structures that make tree surgery efficient and that have a number of constant-factor optimizations. With some additional details, this design also makes it easy for developers to create infinite scrolling lists using callbacks that build exactly those widgets that are visible to the user. ## Aggressive composability One of the most distinctive aspects of Flutter is its \_aggressive composability\_. Widgets are built by composing other widgets, which are themselves built out of progressively more basic widgets. For example, \`Padding\` is a widget rather than a property of other widgets. As a result, user interfaces built with Flutter consist of many, many widgets. The widget building recursion bottoms out in \`RenderObjectWidgets\`, which are widgets that create nodes in the underlying \_render\_ tree. The render tree is a data structure that stores the geometry of the user interface, which is computed during \_layout\_ and used during \_painting\_ and \_hit testing\_. Most Flutter developers do not author render objects directly but instead manipulate the render tree using widgets. In order to support aggressive composability at the widget layer, Flutter uses a number of efficient algorithms and optimizations at both the widget and render tree layers, which are described in the following subsections. ### Sublinear layout With a large number of widgets and render objects, the key to good performance is efficient algorithms. Of paramount importance is the performance of \_layout\_, which is the algorithm that determines the geometry (for example, the size and position) of the render objects. Some other toolkits use layout algorithms that are O(N²) or worse (for example, fixed-point iteration in some constraint domain). Flutter aims for linear performance for initial layout, and \_sublinear layout performance\_ in the common case of subsequently updating an existing layout. Typically, the amount of time spent in layout should scale more slowly than the number of render objects. Flutter performs one layout per frame, and the layout algorithm works in a single pass. \_Constraints\_ are passed down the tree by parent objects calling the layout method on each of their children. The children recursively perform their own layout and then return \_geometry\_ up the tree by returning from their layout method. Importantly, once a render object has returned from its layout method, that render object will not be visited again\[^1] until the layout for the next frame. This approach combines what might otherwise be separate measure and layout passes into a single pass and, as a result, each render object is visited \_at most twice\_\[^2] during layout: once on the way down the tree, and once on the way up the tree. Flutter has several specializations of this general protocol. The most common specialization is \`RenderBox\`, which operates in two-dimensional, cartesian coordinates. In box layout, the constraints are a min and max width and a min and max height. During layout, the child determines its geometry by choosing a size within these bounds. After the child returns from layout, the parent decides the child's position in the parent's coordinate system\[^3]. Note that the child's layout cannot depend on its position, as the position is not determined until after the child returns from the layout. As a result, the parent is free to reposition the child without needing to recompute its layout. More generally, during layout, the \_only\_ information that flows from parent to child are the constraints and the \_only\_ information that flows from child to parent is the geometry. These invariants can reduce the amount of work required during layout: \* If the child has not marked its own layout as dirty, the child can return immediately from layout, cutting off the walk, as long as the parent gives the child the same constraints as the child received during the previous layout. \* Whenever a parent calls a child's layout method, the parent indicates whether it uses the size information returned from the child. If, as often happens, the parent does not use the size information, then the parent need not recompute its layout if the child selects a new size because the parent is guaranteed that the new size will conform to the existing constraints. \* \_Tight\_ constraints are those that can be satisfied by exactly one valid geometry. For example, if the min and max widths are equal to each other and the min and max heights are equal to each other, the only size that satisfies those constraints is one with that width and height. If the parent provides tight constraints, then the parent need not recompute its layout whenever the child recomputes its layout, even if the parent uses the child's size in its layout, because the child cannot change size without new constraints from its parent. \* A render object can declare that it uses the constraints provided by the parent only to determine its geometry. Such a declaration informs the framework that the parent of that render object does not need to recompute its layout when the child recomputes its layout \_even if the constraints are not tight\_ and \_even if the parent's layout depends on the child's size\_, because the child cannot change size without new constraints from its parent. As a result of these optimizations, when the render object tree contains dirty nodes, only those nodes and a limited part of the subtree around them are visited during layout. ### Sublinear widget building Similar to the layout algorithm, Flutter's widget building algorithm is sublinear. After being built, the widgets are held by the \_element tree\_, which retains the logical structure of the user interface. The element tree is necessary because the widgets themselves are \_immutable\_, which means (among other things), they cannot remember their parent or child relationships with other widgets. The element tree also holds the \_state\_ objects associated with stateful widgets. In response to user input (or other stimuli), an element can become dirty, for example if the developer calls \`setState()\` on the associated state object. The framework keeps a list of dirty elements and jumps directly to them during the \_build\_ phase, skipping over clean elements. During the build phase, information flows \_unidirectionally\_ down the element tree, which means each element is visited at most once during the build phase. Once cleaned, an element cannot become dirty again because, by induction, all its ancestor elements are also clean\[^4]. Because widgets are \_immutable\_, if an element has not marked itself as dirty, the element can return immediately from build, cutting off the walk, if the parent rebuilds the element with an identical widget. Moreover, the element need only compare the object identity of the two widget references in order to establish that the new widget is the same as the old widget. Developers exploit this optimization to implement the \_reprojection\_ pattern, in which a widget includes a prebuilt child widget stored as a member variable in its build. During build, Flutter also avoids walking the parent chain using \`InheritedWidgets\`. If widgets commonly walked their parent chain, for example to determine the current theme color, the build phase would become O(N²) in the depth of the tree, which can be quite large due to aggressive composition. To avoid these parent walks, the framework pushes information down the element tree by maintaining a hash table of \`InheritedWidget\`s at each element. Typically, many elements will reference the same hash table, which changes only at elements that introduce a new \`InheritedWidget\`. ### Linear reconciliation Contrary to popular belief, Flutter does not employ a tree-diffing algorithm. Instead, the framework decides whether to reuse elements by examining the child list for each element independently using an O(N) algorithm. The child list reconciliation algorithm optimizes for the following cases: \* The old child list is empty. \* The two lists are identical. \* There is an insertion or removal of one or more widgets in exactly one place in the list. \* If each list contains a widget with the same key\[^5], the two widgets are matched. The general approach is to match up the beginning and end of both child lists by comparing the runtime type and key of each widget, potentially finding a non-empty range in the middle of each list that contains all the unmatched children. The framework then places the children in the range in the old child list into a hash table based on their keys. Next, the framework walks the range in the new child list and queries the hash table by key for matches. Unmatched children are discarded and rebuilt from scratch whereas matched children are rebuilt with their new widgets. ### Tree surgery Reusing elements is important for performance because elements own two critical pieces of data: the state for stateful widgets and the underlying render objects. When the framework is able to reuse an element, the state for that logical part of the user interface is preserved and the layout information computed previously can be reused, often avoiding entire subtree walks. In fact, reusing elements is so valuable that Flutter supports \_non-local\_ tree mutations that preserve state and layout information. Developers can perform a non-local tree mutation by associating a \`GlobalKey\` with one of their widgets. Each global key is unique throughout the entire application and is registered with a thread-specific hash table. During the build phase, the developer can move a widget with a global key to an arbitrary location in the element tree. Rather than building a fresh element at that location, the framework will check the hash table and reparent the existing element from its previous location to its new location, preserving the entire subtree. The render objects in the reparented subtree are able to preserve their layout information because the layout constraints are the only information that flows from parent to child in the render tree. The new parent is marked dirty for layout because its child list has changed, but if the new parent passes the child the same layout constraints the child received from its old parent, the child can return immediately from layout, cutting off the walk. Global keys and non-local tree mutations are used extensively by developers to achieve effects such as hero transitions and navigation. ### Constant-factor optimizations In addition to these algorithmic optimizations, achieving aggressive composability also relies on several important constant-factor optimizations. These optimizations are most important at the leaves of the major algorithms discussed above. \* \*\*Child-model agnostic.\*\* Unlike most toolkits, which use child lists, Flutter's render tree does not commit to a specific child model. For example, the \`RenderBox\` class has an abstract \`visitChildren()\` method rather than a concrete \`firstChild\` and \`nextSibling\` interface. Many subclasses support only a single child, held directly as a member variable, rather than a list of children. For example, \`RenderPadding\` supports only a single child and, as a result, has a simpler layout method that takes less time to execute. \* \*\*Visual render tree, logical widget tree.\*\* In Flutter, the render tree operates in a device-independent, visual coordinate system, which means smaller values in the x coordinate are always towards the left, even if the current reading direction is right-to-left. The widget tree typically operates in logical coordinates, meaning with \_start\_ and \_end\_ values whose visual interpretation depends on the reading direction. The transformation from logical to visual coordinates is done in the handoff between the widget tree and the render tree. This approach is more efficient because layout and painting calculations in the render tree happen more often than the widget-to-render tree handoff and can avoid repeated coordinate conversions. \* \*\*Text handled by a specialized render object.\*\* The vast majority of render objects are ignorant of the complexities of text. Instead, text is handled by a specialized render object, \`RenderParagraph\`, which is a leaf in the render tree. Rather than subclassing a text-aware render object, developers incorporate text into their user interface using composition. This pattern means \`RenderParagraph\` can avoid recomputing its text layout as long as its parent supplies the same layout constraints, which is common, even during tree surgery. \* \*\*Observable objects.\*\* Flutter uses both the model-observation and the reactive paradigms. Obviously, the reactive paradigm is dominant, but Flutter uses observable model objects for some leaf data structures. For example, \`Animation\`s notify an observer list when their value changes. Flutter hands off these observable objects from the widget tree to the render tree, which observes them directly and invalidates only the appropriate stage of the pipeline when they change. For example, a change to an \`Animation\` might trigger only the paint phase rather than both the build and paint phases. Taken together and summed over the large trees created by aggressive composition, these optimizations have a substantial effect on performance. ### Separation of the Element and RenderObject trees The \`RenderObject\` and \`Element\` (Widget) trees in Flutter are isomorphic (strictly speaking, the \`RenderObject\` tree is a subset of the \`Element\` tree). An obvious simplification would be to combine these trees into one tree. However, in practice there are a number of benefits to having these trees be separate: \* \*\*Performance.\*\* When the layout changes, only the relevant parts of the layout tree need to be walked. Due to composition, the element tree frequently has many additional nodes that would have to be skipped. \* \*\*Clarity.\*\* The clearer separation of concerns allows the widget protocol and the render object protocol to each be specialized to their specific needs, simplifying the API surface and thus lowering the risk of bugs and the testing burden. \* \*\*Type safety.\*\* The render object tree can be more type safe since it can guarantee at runtime that children will be of the appropriate type (each coordinate system, e.g. has its own type of render object). Composition widgets can be agnostic about the coordinate system used during layout (for example, the same widget exposing a part of the app model could be used in both a box layout and a sliver layout), and thus in the element tree, verifying the type of render objects would require a tree walk. ## Infinite scrolling Infinite scrolling lists are notoriously difficult for toolkits. Flutter supports infinite scrolling lists with a simple interface based on the \_builder\_ pattern, in which a \`ListView\` uses a callback to build widgets on demand as they become visible to the user during scrolling. Supporting this feature requires \_viewport-aware layout\_ and \_building widgets on demand\_. ### Viewport-aware layout Like most things in Flutter, scrollable widgets are built using composition. The outside of a scrollable widget is a \`Viewport\`, which is a box that is "bigger on the inside," meaning its children can extend beyond the bounds of the viewport and can be scrolled into view. However, rather than having \`RenderBox\` children, a viewport has \`RenderSliver\` children, known as \_slivers\_, which have a viewport-aware layout protocol. The sliver layout protocol matches the structure of the box layout protocol in that parents pass constraints down to their children and receive geometry in return. However, the constraint and geometry data differs between the two protocols. In the sliver protocol, children are given information about the viewport, including the amount of visible space remaining. The geometry data they return enables a variety of scroll-linked effects, including collapsible headers and parallax. Different slivers fill the space available in the viewport in different ways. For example, a sliver that produces a linear list of children lays out each child in order until the sliver either runs out of children or runs out of space. Similarly, a sliver that produces a two-dimensional grid of children fills only the portion of its grid that is visible. Because they are aware of how much space is visible, slivers can produce a finite number of children even if they have the potential to produce an unbounded number of children. Slivers can be composed to create bespoke scrollable layouts and effects. For example, a single viewport can have a collapsible header followed by a linear list and then a grid. All three slivers will cooperate through the sliver layout protocol to produce only those children that are actually visible through the viewport, regardless of whether those children belong to the header, the list, or the grid\[^6]. ### Building widgets on demand If Flutter had a strict \_build-then-layout-then-paint\_ pipeline, the foregoing would be insufficient to implement an infinite scrolling list because the information about how much space is visible through the viewport is available only during the layout phase. Without additional machinery, the layout phase is too late to build the widgets necessary to fill the space. Flutter solves this problem by interleaving the build and layout phases of the pipeline. At any point in the layout phase, the framework can start building new widgets on demand \_as long as those widgets are descendants of the render object currently performing layout\_. Interleaving build and layout is possible only because of the strict controls on information propagation in the build and layout algorithms. Specifically, during the build phase, information can propagate only down the tree. When a render object is performing layout, the layout walk has not visited the subtree below that render object, which means writes generated by building in that subtree cannot invalidate any information that has entered the layout calculation thus far. Similarly, once layout has returned from a render object, that render object will never be visited again during this layout, which means any writes generated by subsequent layout calculations cannot invalidate the information used to build the render object's subtree. Additionally, linear reconciliation and tree surgery are essential for efficiently updating elements during scrolling and for modifying the render tree when elements are scrolled into and out of view at the edge of the viewport. ## API Ergonomics Being fast only matters if the framework can actually be used effectively. To guide Flutter's API design towards greater usability, Flutter has been repeatedly tested in extensive UX studies with developers. These studies sometimes confirmed pre-existing design decisions, sometimes helped guide the prioritization of features, and sometimes changed the direction of the API design. For instance, Flutter's APIs are heavily documented; UX studies confirmed the value of such documentation, but also highlighted the need specifically for sample code and illustrative diagrams. This section discusses some of the decisions made in Flutter's API design in aid of usability. ### Specializing APIs to match the developer's mindset The base class for nodes in Flutter's \`Widget\`, \`Element\`, and \`RenderObject\` trees does not define a child model. This allows each node to be specialized for the child model that is applicable to that node. Most \`Widget\` objects have a single child \`Widget\`, and therefore only expose a single \`child\` parameter. Some widgets support an arbitrary number of children, and expose a \`children\` parameter that takes a list. Some widgets don't have any children at all and reserve no memory, and have no parameters for them. Similarly, \`RenderObjects\` expose APIs specific to their child model. \`RenderImage\` is a leaf node, and has no concept of children. \`RenderPadding\` takes a single child, so it has storage for a single pointer to a single child. \`RenderFlex\` takes an arbitrary number of children and manages it as a linked list. In some rare cases, more complicated child models are used. The \`RenderTable\` render object's constructor takes an array of arrays of children, the class exposes getters and setters that control the number of rows and columns, and there are specific methods to replace individual children by x,y coordinate, to add a row, to provide a new array of arrays of children, and to replace the entire child list with a single array and a column count. In the implementation, the object does not use a linked list like most render objects but instead uses an indexable array. The \`Chip\` widgets and \`InputDecoration\` objects have fields that match the slots that exist on the relevant controls. Where a one-size-fits-all child model would force semantics to be layered on top of a list of children, for example, defining the first child to be the prefix value and the second to be the suffix, the dedicated child model allows for dedicated named properties to be used instead. This flexibility allows each node in these trees to be manipulated in the way most idiomatic for its role. It's rare to want to insert a cell in a table, causing all the other cells to wrap around; similarly, it's rare to want to remove a child from a flex row by index instead of by reference. The \`RenderParagraph\` object is the most extreme case: it has a child of an entirely different type, \`TextSpan\`. At the \`RenderParagraph\` boundary, the \`RenderObject\` tree transitions into being a \`TextSpan\` tree. The overall approach of specializing APIs to meet the developer's expectations is applied to more than just child models. Some rather trivial widgets exist specifically so that developers will find them when looking for a solution to a problem. Adding a space to a row or column is easily done once one knows how, using the \`Expanded\` widget and a zero-sized \`SizedBox\` child, but discovering that pattern is unnecessary because searching for \`space\` uncovers the \`Spacer\` widget, which uses \`Expanded\` and \`SizedBox\` directly to achieve the effect. Similarly, hiding a widget subtree is easily done by not including the widget subtree in the build at all. However, developers typically expect there to be a widget to do this, and so the \`Visibility\` widget exists to wrap this pattern in a trivial reusable widget. ### Explicit arguments UI frameworks tend to have many properties, such that a developer is rarely able to remember the semantic meaning of each constructor argument of each class. As Flutter uses the reactive paradigm, it is common for build methods in Flutter to have many calls to constructors. By leveraging Dart's support for named arguments, Flutter's API is able to keep such build methods clear and understandable. This pattern is extended to any method with multiple arguments, and in particular is extended to any boolean argument, so that isolated \`true\` or \`false\` literals in method calls are always self-documenting. Furthermore, to avoid confusion commonly caused by double negatives in APIs, boolean arguments and properties are always named in the positive form (for example, \`enabled: true\` rather than \`disabled: false\`). ### Paving over pitfalls A technique used in a number of places in the Flutter framework is to define the API such that error conditions don't exist. This removes entire classes of errors from consideration. For example, interpolation functions allow one or both ends of the interpolation to be null, instead of defining that as an error case: interpolating between two null values is always null, and interpolating from a null value or to a null value is the equivalent of interpolating to the zero analog for the given type. This means that developers who accidentally pass null to an interpolation function will not hit an error case, but will instead get a reasonable result. A more subtle example is in the \`Flex\` layout algorithm. The concept of this layout is that the space given to the flex render object is divided among its children, so the size of the flex should be the entirety of the available space. In the original design, providing infinite space would fail: it would imply that the flex should be infinitely sized, a useless layout configuration. Instead, the API was adjusted so that when infinite space is allocated to the flex render object, the render object sizes itself to fit the desired size of the children, reducing the possible number of error cases. The approach is also used to avoid having constructors that allow inconsistent data to be created. For instance, the \`PointerDownEvent\` constructor does not allow the \`down\` property of \`PointerEvent\` to be set to \`false\` (a situation that would be self-contradictory); instead, the constructor does not have a parameter for the \`down\` field and always sets it to \`true\`. In general, the approach is to define valid interpretations for all values in the input domain. The simplest example is the \`Color\` constructor. Instead of taking four integers, one for red, one for green, one for blue, and one for alpha, each of which could be out of range, the default constructor takes a single integer value, and defines the meaning of each bit (for example, the bottom eight bits define the red component), so that any input value is a valid color value. A more elaborate example is the \`paintImage()\` function. This function takes eleven arguments, some with quite wide input domains, but they have been carefully designed to be mostly orthogonal to each other, such that there are very few invalid combinations. ### Reporting error cases aggressively Not all error conditions can be designed out. For those that remain, in debug builds, Flutter generally attempts to catch the errors very early and immediately reports them. Asserts are widely used. Constructor arguments are spot checked in detail. Lifecycles are monitored and when inconsistencies are detected they immediately cause an exception to be thrown. In some cases, this is taken to extremes: for example, when running unit tests, regardless of what else the test is doing, every \`RenderBox\` subclass that is laid out aggressively inspects whether its intrinsic sizing methods fulfill the intrinsic sizing contract. This helps catch errors in APIs that might otherwise not be exercised. When exceptions are thrown, they include as much information as is available. Some of Flutter's error messages proactively probe the associated stack trace to determine the most likely location of the actual bug. Others walk the relevant trees to determine the source of bad data. The most common errors include detailed instructions including in some cases sample code for avoiding the error, or links to further documentation. ### Reactive paradigm Mutable tree-based APIs suffer from a dichotomous access pattern: creating the tree's original state typically uses a very different set of operations than subsequent updates. Flutter's rendering layer uses this paradigm, as it is an effective way to maintain a persistent tree, which is key for efficient layout and painting. However, it means that direct interaction with the rendering layer is awkward at best and bug-prone at worst. Flutter's widget layer introduces a composition mechanism using the reactive paradigm\[^7] to manipulate the underlying rendering tree. This API abstracts out the tree manipulation by combining the tree creation and tree mutation steps into a single tree description (build) step, where, after each change to the system state, the new configuration of the user interface is described by the developer and the framework computes the series of tree mutations necessary to reflect this new configuration. ### Interpolation Since Flutter's framework encourages developers to describe the interface configuration matching the current application state, a mechanism exists to implicitly animate between these configurations. For example, suppose that in state S1 the interface consists of a circle, but in state S2 it consists of a square. Without an animation mechanism, the state change would have a jarring interface change. An implicit animation allows the circle to be smoothly squared over several frames. Each feature that can be implicitly animated has a stateful widget that keeps a record of the current value of the input, and begins an animation sequence whenever the input value changes, transitioning from the current value to the new value over a specified duration. This is implemented using \`lerp\` (linear interpolation) functions using immutable objects. Each state (circle and square, in this case) is represented as an immutable object that is configured with appropriate settings (color, stroke width, etc) and knows how to paint itself. When it is time to draw the intermediate steps during the animation, the start and end values are passed to the appropriate \`lerp\` function along with a \_t\_ value representing the point along the animation, where 0.0 represents the \`start\` and 1.0 represents the \`end\`\[^8], and the function returns a third immutable object representing the intermediate stage. For the circle-to-square transition, the \`lerp\` function would return an object representing a "rounded square" with a radius described as a fraction derived from the \_t\_ value, a color interpolated using the \`lerp\` function for colors, and a stroke width interpolated using the \`lerp\` function for doubles. That object, which implements the same interface as circles and squares, would then be able to paint itself when requested to. This technique allows the state machinery, the mapping of states to configurations, the animation machinery, the interpolation machinery, and the specific logic relating to how to paint each frame to be entirely separated from each other. This approach is broadly applicable. In Flutter, basic types like \`Color\` and \`Shape\` can be interpolated, but so can much more elaborate types such as \`Decoration\`, \`TextStyle\`, or \`Theme\`. These are typically constructed from components that can themselves be interpolated, and interpolating the more complicated objects is often as simple as recursively interpolating all the values that describe the complicated objects. Some interpolatable objects are defined by class hierarchies. For example, shapes are represented by the \`ShapeBorder\` interface, and there exists a variety of shapes, including \`BeveledRectangleBorder\`, \`BoxBorder\`, \`CircleBorder\`, \`RoundedRectangleBorder\`, and \`StadiumBorder\`. A single \`lerp\` function can't anticipate all possible types, and therefore the interface instead defines \`lerpFrom\` and \`lerpTo\` methods, which the static \`lerp\` method defers to. When told to interpolate from a shape A to a shape B, first B is asked if it can \`lerpFrom\` A, then, if it cannot, A is instead asked if it can \`lerpTo\` B. (If neither is possible, then the function returns A from values of \`t\` less than 0.5, and returns B otherwise.) This allows the class hierarchy to be arbitrarily extended, with later additions being able to interpolate between previously-known values and themselves. In some cases, the interpolation itself cannot be described by any of the available classes, and a private class is defined to describe the intermediate stage. This is the case, for instance, when interpolating between a \`CircleBorder\` and a \`RoundedRectangleBorder\`. This mechanism has one further added advantage: it can handle interpolation from intermediate stages to new values. For example, half-way through a circle-to-square transition, the shape could be changed once more, causing the animation to need to interpolate to a triangle. So long as the triangle class can \`lerpFrom\` the rounded-square intermediate class, the transition can be seamlessly performed. ## Conclusion Flutter's slogan, "everything is a widget," revolves around building user interfaces by composing widgets that are, in turn, composed of progressively more basic widgets. The result of this aggressive composition is a large number of widgets that require carefully designed algorithms and data structures to process efficiently. With some additional design, these data structures also make it easy for developers to create infinite scrolling lists that build widgets on demand when they become visible. --- \*\*Footnotes:\*\* \[^1]: For layout, at least. It might be revisited for painting, for building the accessibility tree if necessary, and for hit testing if necessary. \[^2]: Reality, of course, is a bit more complicated. Some layouts involve intrinsic dimensions or baseline measurements, which do involve an additional walk of the relevant subtree (aggressive caching is used to mitigate the potential for quadratic performance in the worst case). These cases, however, are surprisingly rare. In particular, intrinsic dimensions are not required for the common case of shrink-wrapping. \[^3]: Technically, the child's position is not part of its RenderBox geometry and therefore need not actually be calculated during layout. Many render objects implicitly position their single child at 0,0 relative to their own origin, which requires no computation or storage at all. Some render objects avoid computing the position of their children until the last possible moment (for example, during the paint phase), to avoid the computation entirely if they are not subsequently painted. \[^4]: There exists one exception to this rule. As discussed in the \[Building widgets on demand]\(#building-widgets-on-demand) section, some widgets can be rebuilt as a result of a change in layout constraints. If a widget marked itself dirty for unrelated reasons in the same frame that it also is affected by a change in layout constraints, it will be updated twice. This redundant build is limited to the widget itself and does not impact its descendants. \[^5]: A key is an opaque object optionally associated with a widget whose equality operator is used to influence the reconciliation algorithm. \[^6]: For accessibility, and to give applications a few extra milliseconds between when a widget is built and when it appears on the screen, the viewport creates (but does not paint) widgets for a few hundred pixels before and after the visible widgets. \[^7]: This approach was first made popular by Facebook's React library. \[^8]: In practice, the \_t\_ value is allowed to extend past the 0.0-1.0 range, and does so for some curves. For example, the "elastic" curves overshoot briefly in order to represent a bouncing effect. The interpolation logic typically can extrapolate past the start or end as appropriate. For some types, for example, when interpolating colors, the \_t\_ value is effectively clamped to the 0.0-1.0 range.

----
url: https://docs.flutter.dev/ui/animations.md
----

\# Introduction to animations > How to perform animations in Flutter. Well-designed animations make 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. ## Choosing an approach There are different approaches you can take when creating animations in Flutter. Which approach is right for you? To help you decide, check out the video, \[How to choose which Flutter Animation Widget is right for you?]\[] (Also published as a \[\_companion article\_]\[article1].) (To dive deeper into the decision process, watch the \[Animations in Flutter done right]\[] video, presented at Flutter Europe.) As shown in the video, the following decision tree helps you decide what approach to use when implementing a Flutter animation: !\[The animation decision tree]\(/assets/images/docs/ui/animations/animation-decision-tree.png) ## Animation deep dive For a deeper understanding of how animations work in Flutter, watch \[Animation deep dive]\[]. (Also published as a \[\_companion article\_]\[article6].) ## Implicit and explicit animations ### Pre-packaged implicit animations If a pre-packaged implicit animation (the easiest animation to implement) suits your needs, watch \[Animation basics with implicit animations]\[]. (Also published as a \[\_companion article\_]\[article2].) ### Custom implicit animations To create a custom implicit animation, watch \[Creating your own custom implicit animations with TweenAnimationBuilder]\[]. (Also published as a \[\_companion article\_]\[article3].) ### Built-in explicit animations To create an explicit animation (where you control the animation, rather than letting the framework control it), consider using one of the built-in explicit animation classes. To learn more, watch \[Making your first directional animations with built-in explicit animations]\[]. (Also published as a \[\_companion article\_]\[article4].) ### Explicit animations If you need to build an explicit animation from scratch, watch \[Creating custom explicit animations with AnimatedBuilder and AnimatedWidget]\[]. (Also published as a \[\_companion article\_]\[article5].) ## Animation types Generally, animations are either tween- or physics-based. The following sections explain what these terms mean, and point you to resources where you can learn more. ### Tween animation Short for \_in-betweening\_. In a tween animation, the beginning and ending points are defined, as well as a timeline, and a curve that defines the timing and speed of the transition. The framework calculates how to transition from the beginning point to the end point. \* Refer to the \[Animations tutorial]\[], which uses tweens in the examples. \* Consult the API documentation for \[\`Tween\`]\[], \[\`CurveTween\`]\[], and \[\`TweenSequence\`]\[]. ### Physics-based animation In physics-based animation, motion is modeled to resemble real-world behavior. When you toss a ball, for example, where and when it lands depends on how fast it was tossed and how far it was from the ground. Similarly, dropping a ball attached to a spring falls (and bounces) differently than dropping a ball attached to a string. \* \[Animate a widget using a physics simulation]\[]\
A recipe in the animations section of the Flutter cookbook. \* Consult the API documentation for \[\`AnimationController.animateWith\`]\[] and \[\`SpringSimulation\`]\[]. ## Common animation patterns Most UX or motion designers find that certain animation patterns are used repeatedly when designing a UI. This section lists some of the commonly used animation patterns, and tells you where to learn more. ### Animated list or grid This pattern involves animating the addition or removal of elements from a list or grid. \* \[\`AnimatedList\` example]\[]\
This demo, from the \[Sample app catalog]\[], shows how to animate adding an element to a list, or removing a selected element. The internal Dart list is synced as the user modifies the list using the plus (+) and minus (-) buttons. ### Shared element transition In this pattern, the user selects an element—often an image—from the page, and the UI animates the selected element to a new page with more detail. In Flutter, you can implement shared element transitions between routes (pages) using the \`Hero\` widget. \* \[Hero animations]\[]\
How to create two styles of Hero animations: \* The hero flies from one page to another while changing position and size. \* The hero's boundary changes shape, from a circle to a square, as it flies from one page to another. \* Consult the API documentation for the \[\`Hero\`]\[], \[\`Navigator\`]\[], and \[\`PageRoute\`]\[] classes. ### Staggered animation 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]\[] []()## Essential animation concepts and classes The animation system in Flutter is based on typed \[\`Animation\`]\[] objects. Widgets can either incorporate these animations in their build functions directly by reading their current value and listening to their state changes or they can use the animations as the basis of more elaborate animations that they pass along to other widgets. []()### Animation​\ In Flutter, an \`Animation\` object is independent of what is rendered onscreen. An \`Animation\` is an abstract class that exposes its current value and its status: dismissed, forward, reverse, or completed. One of the more commonly used animation types is \`Animation\`. An \`Animation\` object sequentially generates interpolated numbers between two values over a certain duration. The output of an \`Animation\` object might be linear, a curve, a step function, or any other mapping you can create. Depending on how the \`Animation\` object is controlled, it could run in reverse, or even switch directions in the middle. Animations can also interpolate types other than double, such as \`Animation\` or \`Animation\`. An \`Animation\` object has state. Its current value is always available in the \`.value\` getter. An \`Animation\` object doesn't handle rendering or \`build()\` functions. ### CurvedAnimation A \[\`CurvedAnimation\`]\[] defines the animation's progress as a non-linear curve.

<!--?code-excerpt "animation/animate5/lib/main.dart (CurvedAnimation)"?-->

\`\`\`dart animation = CurvedAnimation(parent: controller, curve: Curves.easeIn); \`\`\` \`CurvedAnimation\` and \`AnimationController\` (described in the next sections) are both of type \`Animation\`, so you can pass them interchangeably. The \`CurvedAnimation\` wraps the object it's modifying—you don't subclass \`AnimationController\` to implement a curve. You can use \[\`Curves\`]\[] with \`CurvedAnimation\`. The \`Curves\` class defines many commonly used curves, or you can create your own by overriding \`transformInternal()\`. For example:

<!--?code-excerpt "animation/animate5/lib/main.dart (ShakeCurve)" plaster="none"?-->

\`\`\`dart import 'dart:math'; class ShakeCurve extends Curve { @override double transformInternal(double t) => t + sin(t \* pi \* 4) \* 0.2; } \`\`\` If you want to apply an animation curve to a \`Tween\`, consider using \[\`CurveTween\`]\[]. ### AnimationController \[\`AnimationController\`]\[] is a special \`Animation\` object that generates a new value whenever the hardware is ready for a new frame. By default, an \`AnimationController\` linearly produces the numbers from 0.0 to 1.0 during a given duration. For example, this code creates an \`Animation\` object, but does not start it running:

<!--?code-excerpt "animation/animate5/lib/main.dart (animation-controller)"?-->

\`\`\`dart controller = AnimationController( duration: const Duration(seconds: 2), vsync: this, ); \`\`\` \`AnimationController\` derives from \`Animation\`, so it can be used wherever an \`Animation\` object is needed. However, the \`AnimationController\` has additional methods to control the animation. For example, you start an animation with the \`.forward()\` method. The generation of numbers is tied to the screen refresh rate (typically 60 to 120 frames per second). After each number is generated, each \`Animation\` object calls the attached listener callbacks. To create a custom display list for each child, refer to \[\`RepaintBoundary\`]\[]. When creating an \`AnimationController\`, you pass it a \`vsync\` argument. The presence of \`vsync\` prevents offscreen animations from consuming unnecessary resources. You can use your stateful object as the vsync by adding \`SingleTickerProviderStateMixin\` to the class definition. You can find an example of this in \[animate1]\[] on GitHub. :::note A controller's values stay within its \`lowerBound\` and \`upperBound\` (0.0 and 1.0 by default), even when driven by \`fling()\`. A \`CurvedAnimation\`, however, can exceed the 0.0 to 1.0 range, even if its parent \`AnimationController\` doesn't. Depending on the curve selected, the output of the \`CurvedAnimation\` can have a wider range than the input. For example, elastic curves such as \`Curves.elasticIn\` significantly overshoot or undershoot the default range. ::: ### Tween By default, the \`AnimationController\` object ranges from 0.0 to 1.0. If you need a different range or a different data type, you can use a \[\`Tween\`]\[] to configure an animation to interpolate to a different range or data type. For example, the following \`Tween\` goes from -200.0 to 0.0:

<!--?code-excerpt "animation/animate5/lib/main.dart (tween)"?-->

\`\`\`dart tween = Tween(begin: -200, end: 0); \`\`\` A \`Tween\` defines a mapping from an input range to an output range. The input range is commonly 0.0 to 1.0, but that's not a requirement. A \`Tween\` inherits from \`Animatable\`, not from \`Animation\`. An \`Animatable\`, like \`Animation\`, doesn't have to output double. For example, \`ColorTween\` specifies a progression between two colors.

<!--?code-excerpt "animation/animate5/lib/main.dart (colorTween)"?-->

\`\`\`dart colorTween = ColorTween(begin: Colors.transparent, end: Colors.black54); \`\`\` A \`Tween\` stores mutable \`begin\` and \`end\` values, but doesn't track animation progress. Instead, it provides the \[\`evaluate(Animation animation)\`]\[] method that uses the \`transform\` function to map the current value of the animation (between 0.0 and 1.0) to the actual animation value. The current value of the \`Animation\` object is available from the \`.value\` getter. The tween returns \`begin\` and \`end\` when the animation values are 0.0 and 1.0, respectively. #### Tween.animate To use a \`Tween\` object, call \`animate()\` on the \`Tween\`, passing in the controller object. For example, the following code generates the integer values from 0 to 255 over the course of 500 ms.

<!--?code-excerpt "animation/animate5/lib/main.dart (IntTween)"?-->

\`\`\`dart final AnimationController controller = AnimationController( duration: const Duration(milliseconds: 500), vsync: this, ); final Animation alpha = IntTween( begin: 0, end: 255, ).animate(controller); \`\`\` :::note The \`animate()\` method returns an \[\`Animation\`]\[], not an \[\`Animatable\`]\[]. ::: The following example shows a controller, a curve, and a \`Tween\`:

<!--?code-excerpt "animation/animate5/lib/main.dart (IntTween-curve)"?-->

\`\`\`dart final AnimationController controller = AnimationController( duration: const Duration(milliseconds: 500), vsync: this, ); final Animation curve = CurvedAnimation( parent: controller, curve: Curves.easeOut, ); final Animation alpha = IntTween(begin: 0, end: 255).animate(curve); \`\`\` ### Animation notifications An \[\`Animation\`]\[] object can have listeners and status listeners, registered with \`addListener()\` and \`addStatusListener()\`. A listener is called whenever the value of the animation changes. The most common behavior of a listener is to call \`setState()\` to cause a rebuild. A status listener is called when an animation begins, ends, moves forward, or moves in reverse, as defined by \`AnimationStatus\`. ## Codelabs, tutorials, and articles The following resources are a good place to start learning the Flutter animation framework. Each of these documents shows how to write animation code. \* \[Animations in Flutter codelab]\[]\
Learn about implicit and explicit animations while building a multiple-choice quiz game. \* \[Animations tutorial]\[]\
Explains the fundamental classes in the Flutter animation package (controllers, \`Animatable\`, curves, listeners, builders), as it guides you through a progression of tween animations using different aspects of the animation APIs. This tutorial shows how to create your own custom explicit animations. \* \[Zero to One with Flutter, part 1]\[] and \[part 2]\[]\
Flutter blog posts showing how to create an animated chart using tweening. \* \[Casual games toolkit]\[]\
A toolkit with game templates that contain examples of how to use Flutter animations. ## Other resources Learn more about Flutter animations at the following links: \* There are several \[animations packages]\[] available on pub.dev that contain pre-built animations for commonly used patterns, including: \`Container\` transforms, shared axis transitions, fade through transitions, and fade transitions. \* \[Animation samples]\[] from the \[Sample app catalog]\[]. \* \[Animation recipes]\[] from the Flutter cookbook. \* \[Animation videos]\[] from the Flutter YouTube channel. \* \[Animations: overview]\[]\
A look at some of the major classes in the animations library, and Flutter's animation architecture. \* \[Animation and motion widgets]\[]\
A catalog of some of the animation widgets provided in the Flutter APIs. \* The \[animation library]\[] in the \[Flutter API documentation]\[]\
The animation API for the Flutter framework. This link takes you to a technical overview page for the library. \[animate1]: https\://github.com/flutter/website/tree/main/examples/animation/animate1 \[Animate a widget using a physics simulation]: /cookbook/animation/physics-simulation \[\`Animatable\`]: https\://api.flutter.dev/flutter/animation/Animatable-class.html \[\`AnimatedList\` example]: https\://github.com/flutter/samples/blob/main/animations \[\`Animation\`]: https\://api.flutter.dev/flutter/animation/Animation-class.html \[Animation and motion widgets]: /ui/widgets/animation \[Animation basics with implicit animations]: https\://www\.youtube.com/watch?v=IVTjpW3W33s\&list=PLjxrf2q8roU2v6UqYlt\_KPaXlnjbYySua\&index=1 \[Animation deep dive]: https\://www\.youtube.com/watch?v=PbcILiN8rbo\&list=PLjxrf2q8roU2v6UqYlt\_KPaXlnjbYySua\&index=5 \[animation library]: https\://api.flutter.dev/flutter/animation/animation-library.html \[Animation recipes]: /cookbook/animation \[Animation samples]: https\://github.com/flutter/samples/tree/main/animations#animation-samples \[Animation videos]: https\://www\.youtube.com/@flutterdev/search?query=animation \[Animations in Flutter done right]: https\://www\.youtube.com/watch?v=wnARLByOtKA\&t=3s \[Animations: overview]: /ui/animations/overview \[animations packages]: https\://pub.dev/packages?q=topic%3Aanimation \[Animations tutorial]: /ui/animations/tutorial \[\`AnimationController\`]: https\://api.flutter.dev/flutter/animation/AnimationController-class.html \[\`AnimationController.animateWith\`]: https\://api.flutter.dev/flutter/animation/AnimationController/animateWith.html \[article1]: https\://blog.flutter.dev/how-to-choose-which-flutter-animation-widget-is-right-for-you-79ecfb7e72b5 \[article2]: https\://blog.flutter.dev/flutter-animation-basics-with-implicit-animations-95db481c5916 \[article3]: https\://blog.flutter.dev/custom-implicit-animations-in-flutter-with-tweenanimationbuilder-c76540b47185 \[article4]: https\://blog.flutter.dev/directional-animations-with-built-in-explicit-animations-3e7c5e6fbbd7 \[article5]: https\://blog.flutter.dev/when-should-i-useanimatedbuilder-or-animatedwidget-57ecae0959e8 \[article6]: https\://blog.flutter.dev/animation-deep-dive-39d3ffea111f \[Casual games toolkit]: /resources/games-toolkit/ \[Creating your own custom implicit animations with TweenAnimationBuilder]: https\://www\.youtube.com/watch?v=6KiPEqzJIKQ\&feature=youtu.be \[Creating custom explicit animations with AnimatedBuilder and AnimatedWidget]: https\://www\.youtube.com/watch?v=fneC7t4R\_B0\&list=PLjxrf2q8roU2v6UqYlt\_KPaXlnjbYySua\&index=4 \[\`Curves\`]: https\://api.flutter.dev/flutter/animation/Curves-class.html \[\`CurvedAnimation\`]: https\://api.flutter.dev/flutter/animation/CurvedAnimation-class.html \[\`CurveTween\`]: https\://api.flutter.dev/flutter/animation/CurveTween-class.html \[\`evaluate(Animation animation)\`]: https\://api.flutter.dev/flutter/animation/Animatable/evaluate.html \[Flutter API documentation]: https\://api.flutter.dev \[\`Hero\`]: https\://api.flutter.dev/flutter/widgets/Hero-class.html \[Hero animations]: /ui/animations/hero-animations \[How to choose which Flutter Animation Widget is right for you?]: https\://www\.youtube.com/watch?v=GXIJJkq\_H8g \[Animations in Flutter codelab]: https\://codelabs.developers.google.com/advanced-flutter-animations \[Making your first directional animations with built-in explicit animations]: https\://www\.youtube.com/watch?v=CunyH6unILQ\&list=PLjxrf2q8roU2v6UqYlt\_KPaXlnjbYySua\&index=3 \[Material widgets]: /ui/widgets/material \[\`Navigator\`]: https\://api.flutter.dev/flutter/widgets/Navigator-class.html \[\`PageRoute\`]: https\://api.flutter.dev/flutter/widgets/PageRoute-class.html \[part 2]: https\://flutter.dev/blog/zero-to-one-with-flutter-part-two \[\`RepaintBoundary\`]: https\://api.flutter.dev/flutter/widgets/RepaintBoundary-class.html \[Sample app catalog]: https\://github.com/flutter/samples \[\`SpringSimulation\`]: https\://api.flutter.dev/flutter/physics/SpringSimulation-class.html \[Staggered Animations]: /ui/animations/staggered-animations \[\`Tween\`]: https\://api.flutter.dev/flutter/animation/Tween-class.html \[\`TweenSequence\`]: https\://api.flutter.dev/flutter/animation/TweenSequence-class.html \[Zero to One with Flutter, part 1]: https\://flutter.dev/blog/zero-to-one-with-flutter

----
url: https://docs.flutter.dev/deployment/macos
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Build and release a macOS app](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

This guide provides a step-by-step walkthrough of releasing a Flutter app to the [App Store](https://developer.apple.com/app-store/submissions/).

## Preliminaries

[#](#preliminaries)

Before beginning the process of releasing your app, ensure that it meets Apple's [App Review Guidelines](https://developer.apple.com/app-store/review/).

In order to publish your app to the App Store, you must first enroll in the [Apple Developer Program](https://developer.apple.com/programs/). You can read more about the various membership options in Apple's [Choosing a Membership](https://developer.apple.com/support/compare-memberships/) guide.

## Register your app on App Store Connect

[#](#register-your-app-on-app-store-connect)

Manage your app's life cycle on [App Store Connect](https://appstoreconnect.apple.com/) (formerly iTunes Connect). You define your app name and description, add screenshots, set pricing, and manage releases to the App Store and TestFlight.

Registering your app involves two steps: registering a unique Bundle ID, and creating an application record on App Store Connect.

For a detailed overview of App Store Connect, see the [App Store Connect](https://developer.apple.com/support/app-store-connect/) guide.

### Register a Bundle ID

[#](#register-a-bundle-id)

Every macOS application is associated with a Bundle ID, a unique identifier registered with Apple. To register a Bundle ID for your app, follow these steps:

1. Open the [App IDs](https://developer.apple.com/account/resources/identifiers/list) page of your developer account.

2. On the App Store Connect landing page, click **My Apps**.
3. Click **+** in the top-left corner of the My Apps page, then select **New App**.
4. Fill in your app details in the form that appears. In the Platforms section, ensure that macOS is checked. Since Flutter does not currently support tvOS, leave that checkbox unchecked. Click **Create**.

1. In Xcode, open `Runner.xcworkspace` in your app's `macos` folder.
2. To view your app's settings, select the **Runner** project in the Xcode project navigator. Then, in the main view sidebar, select the **Runner** target.
3. Select the **General** tab.

Verify the most important settings.

In the **Identity** section:

* `App Category`

  The app category under which your app will be listed on the Mac App Store. This cannot be none.

* `Bundle Identifier`

  The App ID you registered on App Store Connect.

In the **Deployment info** section:

* `Deployment Target`

  The minimum macOS version that your app supports. To check which versions of macOS that Flutter supports deploying to, check out Flutter's [Supported deployment platforms](/reference/supported-platforms).

In the **Signing & Capabilities** section:

* `Automatically manage signing`

  Whether Xcode should automatically manage app signing and provisioning. This is set `true` by default, which should be sufficient for most apps. For more complex scenarios, see the [Code Signing Guide](https://developer.apple.com/library/content/documentation/Security/Conceptual/CodeSigningGuide/Introduction/Introduction.html).

* `Team`

  Select the team associated with your registered Apple Developer account. If required, select **Add Account...**, then update this setting.

The **General** tab of your project settings should resemble the following:

For a detailed overview of app signing, see [Create, export, and delete signing certificates](https://help.apple.com/xcode/mac/current/#/dev154b28f09).

## Configuring the app's name, bundle identifier and copyright

[#](#configuring-the-apps-name-bundle-identifier-and-copyright)

The configuration for the product identifiers are centralized in `macos/Runner/Configs/AppInfo.xcconfig`. For the app's name, set `PRODUCT_NAME`, for the copyright set `PRODUCT_COPYRIGHT`, and finally set `PRODUCT_BUNDLE_IDENTIFIER` for the app's bundle identifier.

## Updating the app's version number

[#](#updating-the-apps-version-number)

The default version number of the app is `1.0.0`. To update it, navigate to the `pubspec.yaml` file and update the following line:

`version: 1.0.0+1`

The version number is three numbers separated by dots, such as `1.0.0` in the example above, followed by an optional build number such as `1` in the example above, separated by a `+`.

Both the version and the build number can be overridden in Flutter's build by specifying `--build-name` and `--build-number`, respectively.

In macOS, `build-name` uses `CFBundleShortVersionString` while `build-number` uses `CFBundleVersion`. Read more about iOS versioning at [Core Foundation Keys](https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html) on the Apple Developer's site.

## Add an app icon

[#](#add-an-app-icon)

When a new Flutter app is created, a placeholder icon set is created. This step covers replacing these placeholder icons with your app's icons:

1. Review the [macOS App Icon](https://developer.apple.com/design/human-interface-guidelines/macos/icons-and-images/app-icon/) guidelines.
2. In the Xcode project navigator, select `Assets.xcassets` in the `Runner` folder. Update the placeholder icons with your own app icons.
3. Verify the icon has been replaced by running your app using `flutter run -d macos`.

## Create a build archive with Xcode

[#](#create-a-build-archive-with-xcode)

This step covers creating a build archive and uploading your build to App Store Connect using Xcode.

During development, you've been building, debugging, and testing with *debug* builds. When you're ready to ship your app to users on the App Store or TestFlight, you need to prepare a *release* build. At this point, you might consider [obfuscating your Dart code](/deployment/obfuscate) to make it more difficult to reverse engineer. Obfuscating your code involves adding a couple flags to your build command.

In Xcode, configure the app version and build:

1. Open `Runner.xcworkspace` in your app's `macos` folder. To do this from the command line, run the following command from the base directory of your application project.
   ```
   open macos/Runner.xcworkspace
   ```
2. Select **Runner** in the Xcode project navigator, then select the **Runner** target in the settings view sidebar.
3. In the Identity section, update the **Version** to the user-facing version number you wish to publish.
4. In the Identity section, update the **Build** identifier to a unique build number used to track this build on App Store Connect. Each upload requires a unique build number.

Finally, create a build archive and upload it to App Store Connect:

1. Create a release Archive of your application. From the base directory of your application project, run the following.
   ```
   flutter build macos
   ```
2. Open Xcode and select **Product > Archive** to open the archive created in the previous step.
3. Click the **Validate App** button. If any issues are reported, address them and produce another build. You can reuse the same build ID until you upload an archive.
4. After the archive has been successfully validated, click **Distribute App**. You can follow the status of your build in the Activities tab of your app's details page on [App Store Connect](https://appstoreconnect.apple.com/).

You should receive an email within 30 minutes notifying you that your build has been validated and is available to release to testers on TestFlight. At this point you can choose whether to release on TestFlight, or go ahead and release your app to the App Store.

For more details, see [Upload an app to App Store Connect](https://help.apple.com/xcode/mac/current/#/dev442d7f2ca).

## Create a build archive with Codemagic CLI tools

[#](#create-a-build-archive-with-codemagic-cli-tools)

This step covers creating a build archive and uploading your build to App Store Connect using Flutter build commands and [Codemagic CLI Tools](https://github.com/codemagic-ci-cd/cli-tools) executed in a terminal in the Flutter project directory.

1. Install the Codemagic CLI tools:

   bash

   ```
   pip3 install codemagic-cli-tools
   ```

2. You'll need to generate an [App Store Connect API Key](https://appstoreconnect.apple.com/access/api) with App Manager access to automate operations with App Store Connect. To make subsequent commands more concise, set the following environment variables from the new key: issuer id, key id, and API key file.

   bash

   ```
   export APP_STORE_CONNECT_ISSUER_ID=aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee
   export APP_STORE_CONNECT_KEY_IDENTIFIER=ABC1234567
   export APP_STORE_CONNECT_PRIVATE_KEY=`cat /path/to/api/key/AuthKey_XXXYYYZZZ.p8`
   ```

3. You need to export or create a Mac App Distribution and a Mac Installer Distribution certificate to perform code signing and package a build archive.

   If you have existing [certificates](https://developer.apple.com/account/resources/certificates/list), you can export the private keys by executing the following command for each certificate:

   bash

   ```
   openssl pkcs12 -in <certificate_name>.p12 -nodes -nocerts | openssl rsa -out cert_key
   ```

   Or you can create a new private key by executing the following command:

   bash

   ```
   ssh-keygen -t rsa -b 2048 -m PEM -f cert_key -q -N ""
   ```

   Later, you can have CLI tools automatically create a new Mac App Distribution and Mac Installer Distribution certificate. You can use the same private key for each new certificate.

4. Fetch the code signing files from App Store Connect:

   bash

   ```
   app-store-connect fetch-signing-files YOUR.APP.BUNDLE_ID \
       --platform MAC_OS \
       --type MAC_APP_STORE \
       --certificate-key=@file:/path/to/cert_key \
       --create
   ```

   Where `cert_key` is either your exported Mac App Distribution certificate private key or a new private key which automatically generates a new certificate.

5. If you do not have a Mac Installer Distribution certificate, you can create a new certificate by executing the following:

   bash

   ```
   app-store-connect certificates create \
       --type MAC_INSTALLER_DISTRIBUTION \
       --certificate-key=@file:/path/to/cert_key \
       --save
   ```

   Use `cert_key` of the private key you created earlier.

6. Fetch the Mac Installer Distribution certificates:

   bash

   ```
   app-store-connect certificates list \
       --type MAC_INSTALLER_DISTRIBUTION \
       --certificate-key=@file:/path/to/cert_key \
       --save
   ```

7. Set up a new temporary keychain to be used for code signing:

   bash

   ```
   keychain initialize
   ```

8. Now add the fetched certificates to your keychain:

   bash

   ```
   keychain add-certificates
   ```

9. Update the Xcode project settings to use fetched code signing profiles:

   bash

   ```
   xcode-project use-profiles
   ```

10. Install Flutter dependencies:

    bash

    ```
    flutter packages pub get
    ```

11. Install CocoaPods dependencies:

    bash

    ```
    find . -name "Podfile" -execdir pod install \;
    ```

12. Build the Flutter macOS project:

    bash

    ```
    flutter build macos --release
    ```

13. Package the app:

    bash

    ```
    APP_NAME=$(find $(pwd) -name "*.app")
    PACKAGE_NAME=$(basename "$APP_NAME" .app).pkg
    xcrun productbuild --component "$APP_NAME" /Applications/ unsigned.pkg

    INSTALLER_CERT_NAME=$(keychain list-certificates \
              | jq '[.[]
                | select(.common_name
                | contains("Mac Developer Installer"))
                | .common_name][0]' \
              | xargs)
    xcrun productsign --sign "$INSTALLER_CERT_NAME" unsigned.pkg "$PACKAGE_NAME"
    rm -f unsigned.pkg
    ```

14. Publish the packaged app to App Store Connect:

    bash

    ```
    app-store-connect publish \
        --path "$PACKAGE_NAME"
    ```

15. As mentioned earlier, don't forget to set your login keychain as the default to avoid authentication issues with apps on your machine:

    bash

    ```
    keychain use-login
    ```

## Distribute to registered devices

[#](#distribute-to-registered-devices)

See [distribution guide](https://help.apple.com/xcode/mac/current/#/dev295cc0fae) to prepare an archive for distribution to designated Mac computers.

The [Distribute your app](https://help.apple.com/xcode/mac/current/#/dev8b4250b57) guide provides a detailed overview of the process of releasing an app to the App Store.

## Additional resources

[#](#additional-resources)

To learn how to package and distribute your Flutter desktop app for macOS the open source way, without using a paid Apple developer account, check out the step-by-step [macOS packaging guide](https://medium.com/@fluttergems/packaging-and-distributing-flutter-desktop-apps-the-missing-guide-part-1-macos-b36438269285).

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-07-31. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/deployment/macos.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/deployment/macos\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/deployment/macos.md "Report an issue with this page").

----
url: https://docs.flutter.dev/ui/layout/tutorial.md
----

\# Build a Flutter layout > Learn how to build a layout in Flutter. :::secondary What you'll learn \* How to lay out widgets next to each other. \* How to add space between widgets. \* How adding and nesting widgets results in a Flutter layout. ::: This tutorial explains how to design and build layouts in Flutter. If you use the example code provided, you can build the following app.

Photo by \[Dino Reichmuth]\[ch-photo] on \[Unsplash]\[]. Text by \[Switzerland Tourism]\[].

To get a better overview of the layout mechanism, start with \[Flutter's approach to layout]\[]. \[Switzerland Tourism]: https\://www\.myswitzerland.com/en-us/destinations/lake-oeschinen \[Flutter's approach to layout]: /ui/layout ## Diagram the layout In this section, consider what type of user experience you want for your app users. Consider how to position the components of your user interface. A layout consists of the total end result of these positionings. Consider planning your layout to speed up your coding. Using visual cues to know where something goes on screen can be a great help. Use whichever method you prefer, like an interface design tool or a pencil and a sheet of paper. Figure out where you want to place elements on your screen before writing code. It's the programming version of the adage: "Measure twice, cut once."

1. Ask these questions to break the layout down to its basic elements. \* Can you identify the rows and columns? \* Does the layout include a grid? \* Are there overlapping elements? \* Does the UI need tabs? \* What do you need to align, pad, or border?

2. Identify the larger elements. In this example, you arrange the image, title, buttons, and description into a column.

3. Diagram each row.

   1. Row 1, the \*\*Title\*\* section, has three children: a column of text, a star icon, and a number. Its first child, the column, contains two lines of text. That first column might need more space.
   2. Row 2, the \*\*Button\*\* section, has three children: each child contains a column which then contains an icon and text.

After diagramming the layout, consider how you would code it. Would you write all the code in one class? Or, would you create one class for each part of the layout? To follow Flutter best practices, create one class, or Widget, to contain each part of your layout. When Flutter needs to re-render part of a UI, it updates the smallest part that changes. This is why Flutter makes "everything a widget". If only the text changes in a \`Text\` widget, Flutter redraws only that text. Flutter changes the least amount of the UI possible in response to user input. For this tutorial, write each element you have identified as its own widget. ## Create the app base code In this section, shell out the basic Flutter app code to start your app.

<!--?code-excerpt path-base="layout/base"?-->

1\. \[Set up your Flutter environment]\[]. 1. \[Create a new Flutter app]\[new-flutter-app]. 1. Replace the contents of \`lib/main.dart\` with the following code. This app uses a parameter for the app title and the title shown on the app's \`appBar\`. This decision simplifies the code.

<!--?code-excerpt "lib/main.dart (all)"?-->

\`\`\`dart import 'package:material\_ui/material\_ui.dart'; void main() => runApp(const MyApp()); class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { const String appTitle = 'Flutter layout demo'; return MaterialApp( title: appTitle, home: Scaffold( appBar: AppBar(title: const Text(appTitle)), body: const Center( child: Text('Hello World'), ), ), ); } } \`\`\` \[Set up your Flutter environment]: /install \[new-flutter-app]: /reference/create-new-app ## Add the Title section In this section, create a \`TitleSection\` widget that resembles the following layout.

<!--?code-excerpt path-base="layout/lakes"?-->

\### Add the \`TitleSection\` Widget Add the following code after the \`MyApp\` class.

<!--?code-excerpt "step2/lib/main.dart (title-section)"?-->

\`\`\`dart class TitleSection extends StatelessWidget { const TitleSection({super.key, required this.name, required this.location}); final String name; final String location; @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.all(32), child: Row( children: \[ Expanded( /\*1\*/ child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: \[ /\*2\*/ Padding( padding: const EdgeInsets.only(bottom: 8), child: Text( name, style: const TextStyle(fontWeight: FontWeight.bold), ), ), Text(location, style: TextStyle(color: Colors.grey\[500])), ], ), ), /\*3\*/ Icon(Icons.star, color: Colors.red\[500]), const Text('41'), ], ), ); } } \`\`\` {:.numbered-code-notes} 1. To use all remaining free space in the row, use the \`Expanded\` widget to stretch the \`Column\` widget. To place the column at the start of the row, set the \`crossAxisAlignment\` property to \`CrossAxisAlignment.start\`. 2. To add space between the rows of text, put those rows in a \`Padding\` widget. 3. The title row ends with a red star icon and the text \`41\`. The entire row falls inside a \`Padding\` widget and pads each edge by 32 pixels. ### Change the app body to a scrolling view In the \`body\` property, replace the \`Center\` widget with a \`SingleChildScrollView\` widget. Within the \[\`SingleChildScrollView\`]\[] widget, replace the \`Text\` widget with a \`Column\` widget. \`\`\`dart diff - body: const Center( - child: Text('Hello World'), + body: const SingleChildScrollView( + child: Column( + children: \[ \`\`\` These code updates change the app in the following ways. \* A \`SingleChildScrollView\` widget can scroll. This allows elements that don't fit on the current screen to display. \* A \`Column\` widget displays any elements within its \`children\` property in the order listed. The first element listed in the \`children\` list displays at the top of the list. Elements in the \`children\` list display in array order on the screen from top to bottom. \[\`SingleChildScrollView\`]: https\://api.flutter.dev/flutter/widgets/SingleChildScrollView-class.html ### Update the app to display the title section Add the \`TitleSection\` widget as the first element in the \`children\` list. This places it at the top of the screen. Pass the provided name and location to the \`TitleSection\` constructor. \`\`\`dart diff + children: \[ + TitleSection( + name: 'Oeschinen Lake Campground', + location: 'Kandersteg, Switzerland', + ), + ], \`\`\` :::tip \* When pasting code into your app, indentation can become skewed. To fix this in your Flutter editor, use \[automatic reformatting support]\[]. \* To accelerate your development, try Flutter's \[hot reload]\[] feature. \* If you have problems, compare your code to \[\`lib/main.dart\`]\[]. ::: \[automatic reformatting support]: /tools/formatting \[hot reload]: /tools/hot-reload \[\`lib/main.dart\`]: https\://github.com/flutter/website/blob/main/examples/layout/lakes/step2/lib/main.dart ## Add the Button section In this section, add the buttons that will add functionality to your app.

<!--?code-excerpt path-base="layout/lakes/step3"?-->

The \*\*Button\*\* section contains three columns that use the same layout: an icon over a row of text. Plan to distribute these columns in one row so each takes the same amount of space. Paint all text and icons with the primary color. ### Add the \`ButtonSection\` widget Add the following code after the \`TitleSection\` widget to contain the code to build the row of buttons.

<!--?code-excerpt "lib/main.dart (button-start)"?-->

\`\`\`dart class ButtonSection extends StatelessWidget { const ButtonSection({super.key}); @override Widget build(BuildContext context) { final Color color = Theme.of(context).primaryColor; // ··· } } \`\`\` ### Create a widget to make buttons As the code for each column could use the same syntax, create a widget named \`ButtonWithText\`. The widget's constructor accepts a color, icon data, and a label for the button. Using these values, the widget builds a \`Column\` with an \`Icon\` and a stylized \`Text\` widget as its children. To help separate these children, a \`Padding\` widget the \`Text\` widget is wrapped with a \`Padding\` widget. Add the following code after the \`ButtonSection\` class.

<!--?code-excerpt "lib/main.dart (button-with-text)"?-->

\`\`\`dart class ButtonSection extends StatelessWidget { const ButtonSection({super.key}); // ··· } class ButtonWithText extends StatelessWidget { const ButtonWithText({ super.key, required this.color, required this.icon, required this.label, }); final Color color; final IconData icon; final String label; @override Widget build(BuildContext context) { return Column( mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.center, children: \[ Icon(icon, color: color), Padding( padding: const EdgeInsets.only(top: 8), child: Text( label, style: TextStyle( fontSize: 12, fontWeight: FontWeight.w400, color: color, ), ), ), ], ); } } \`\`\` ### Position the buttons with a \`Row\` widget Add the following code into the \`ButtonSection\` widget. 1. Add three instances of the \`ButtonWithText\` widget, once for each button. 1. Pass the color, \`Icon\`, and text for that specific button. 1. Align the columns along the main axis with the \`MainAxisAlignment.spaceEvenly\` value. The main axis for a \`Row\` widget is horizontal and the main axis for a \`Column\` widget is vertical. This value, then, tells Flutter to arrange the free space in equal amounts before, between, and after each column along the \`Row\`.

<!--?code-excerpt "lib/main.dart (button-section)"?-->

\`\`\`dart class ButtonSection extends StatelessWidget { const ButtonSection({super.key}); @override Widget build(BuildContext context) { final Color color = Theme.of(context).primaryColor; return SizedBox( child: Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: \[ ButtonWithText(color: color, icon: Icons.call, label: 'CALL'), ButtonWithText(color: color, icon: Icons.near\_me, label: 'ROUTE'), ButtonWithText(color: color, icon: Icons.share, label: 'SHARE'), ], ), ); } } class ButtonWithText extends StatelessWidget { const ButtonWithText({ super.key, required this.color, required this.icon, required this.label, }); final Color color; final IconData icon; final String label; @override Widget build(BuildContext context) { return Column( // ··· ); } } \`\`\` ### Update the app to display the button section Add the button section to the \`children\` list.

<!--?code-excerpt path-base="layout/lakes"?-->

\`\`\`dart diff TitleSection( name: 'Oeschinen Lake Campground', location: 'Kandersteg, Switzerland', ), + ButtonSection(), ], \`\`\` ## Add the Text section In this section, add the text description to this app.

<!--?code-excerpt path-base="layout/lakes"?-->

\### Add the \`TextSection\` widget Add the following code as a separate widget after the \`ButtonSection\` widget.

<!--?code-excerpt "step4/lib/main.dart (text-section)"?-->

\`\`\`dart class TextSection extends StatelessWidget { const TextSection({super.key, required this.description}); final String description; @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.all(32), child: Text(description, softWrap: true), ); } } \`\`\` By setting \[\`softWrap\`]\[] to \`true\`, text lines fill the column width before wrapping at a word boundary. \[\`softWrap\`]: https\://api.flutter.dev/flutter/widgets/Text/softWrap.html ### Update the app to display the text section Add a new \`TextSection\` widget as a child after the \`ButtonSection\`. When adding the \`TextSection\` widget, set its \`description\` property to the text of the location description. \`\`\`dart diff location: 'Kandersteg, Switzerland', ), ButtonSection(), + TextSection( + description: + 'Lake Oeschinen lies at the foot of the Blüemlisalp in the ' + 'Bernese Alps. Situated 1,578 meters above sea level, it ' + 'is one of the larger Alpine Lakes. A gondola ride from ' + 'Kandersteg, followed by a half-hour walk through pastures ' + 'and pine forest, leads you to the lake, which warms to 20 ' + 'degrees Celsius in the summer. Activities enjoyed here ' + 'include rowing, and riding the summer toboggan run.', + ), ], \`\`\` ## Add the Image section In this section, add the image file to complete your layout. ### Configure your app to use supplied images To configure your app to reference images, modify its \`pubspec.yaml\` file. 1. Create an \`images\` directory at the top of the project. 1. Download the \[\`lake.jpg\`]\[] image and add it to the new \`images\` directory. :::note You can't use \`wget\` to save this binary file. You can download the \[image]\[ch-photo] from \[Unsplash]\[] under the Unsplash License. The small size comes in at 94.4 kB. ::: 1. To include images, add an \`assets\` tag to the \`pubspec.yaml\` file at the root directory of your app. When you add \`assets\`, it serves as the set of pointers to the images available to your code. \`\`\`yaml title="pubspec.yaml" diff flutter: uses-material-design: true + assets: + - images/lake.jpg \`\`\` :::tip Text in the \`pubspec.yaml\` respects whitespace and text case. Write the changes to the file as given in the previous example. This change might require you to restart the running program to display the image. ::: \[\`lake.jpg\`]: https\://raw\.githubusercontent.com/flutter/website/main/examples/layout/lakes/step5/images/lake.jpg ### Create the \`ImageSection\` widget Define the following \`ImageSection\` widget after the other declarations.

<!--?code-excerpt "step5/lib/main.dart (image-section)"?-->

\`\`\`dart class ImageSection extends StatelessWidget { const ImageSection({super.key, required this.image}); final String image; @override Widget build(BuildContext context) { return Image.asset(image, width: 600, height: 240, fit: BoxFit.cover); } } \`\`\` The \`BoxFit.cover\` value tells Flutter to display the image with two constraints. First, display the image as small as possible. Second, cover all the space that the layout allotted, called the render box. ### Update the app to display the image section Add an \`ImageSection\` widget as the first child in the \`children\` list. Set the \`image\` property to the path of the image you added in \[Configure your app to use supplied images]\(#configure-your-app-to-use-supplied-images). \`\`\`dart diff children: \[ + ImageSection( + image: 'images/lake.jpg', + ), TitleSection( name: 'Oeschinen Lake Campground', location: 'Kandersteg, Switzerland', \`\`\` ## Congratulations That's it! When you hot reload the app, your app should look like this. ## Resources You can access the resources used in this tutorial from these locations: \*\*Dart code:\*\* \[\`main.dart\`]\[]\
\*\*Image:\*\* \[ch-photo]\[]\
\*\*Pubspec:\*\* \[\`pubspec.yaml\`]\[]\
\[\`main.dart\`]: https\://github.com/flutter/website/blob/main/examples/layout/lakes/step6/lib/main.dart \[ch-photo]: https\://unsplash.com/photos/red-and-gray-tents-in-grass-covered-mountain-5Rhl-kSRydQ \[\`pubspec.yaml\`]: https\://github.com/flutter/website/blob/main/examples/layout/lakes/step6/pubspec.yaml ## Next Steps To add interactivity to this layout, follow the \[interactivity tutorial]\[]. \[interactivity tutorial]: /ui/interactivity \[Unsplash]: https\://unsplash.com

----
url: https://docs.flutter.dev/ui/adaptive-responsive.md
----

\# Adaptive and responsive design in Flutter > It's important to create an app, whether for mobile or web, that responds to size and orientation changes and maximizes the use of each platform. !\[List of supported platforms]\(/assets/images/docs/ui/adaptive-responsive/platforms.png) One of Flutter's primary goals is to create a framework that allows you to develop apps from a single codebase that look and feel great on any platform. This means that your app might appear on screens of many different sizes, from a watch, to a foldable phone with two screens, to a high definition monitor. And your input device might be a physical or virtual keyboard, a mouse, a touchscreen, or any number of other devices. Two terms that describe these design concepts are \_adaptive\_ and \_responsive\_. Ideally, you'd want your app to be \_both\_ but what, exactly, does this mean? ## What is responsive vs adaptive? An easy way to think about it is that responsive design is about fitting the UI \_into\_ the space and adaptive design is about the UI being \_usable\_ in the space. So, a responsive app adjusts the placement of design elements to \_fit\_ the available space. And an adaptive app selects the appropriate layout and input devices to be usable \_in\_ the available space. For example, should a tablet UI use bottom navigation or side-panel navigation? :::note Often adaptive and responsive concepts are collapsed into a single term. Most often, \_adaptive design\_ is used to refer to both adaptive and responsive. ::: This section covers various aspects of adaptive and responsive design: \* \[General approach]\[] \* \[SafeArea & MediaQuery]\[] \* \[Large screens & foldables]\[] \* \[User input & accessibility]\[] \* \[Capabilities & policies]\[] \* \[Best practices for adaptive apps]\[] \* \[Additional resources]\[] \[Additional resources]: /ui/adaptive-responsive/more-info \[Best practices for adaptive apps]: /ui/adaptive-responsive/best-practices \[Capabilities & policies]: /ui/adaptive-responsive/capabilities \[General approach]: /ui/adaptive-responsive/general \[Large screens & foldables]: /ui/adaptive-responsive/large-screens \[SafeArea & MediaQuery]: /ui/adaptive-responsive/safearea-mediaquery \[User input & accessibility]: /ui/adaptive-responsive/input :::note You might also check out the Google I/O 2024 talk about this subject. :::

----
url: https://docs.flutter.dev/cookbook/persistence/key-value.md
----

\# Store key-value data on disk > Learn how to use the shared\_preferences package to store key-value data.

<!--?code-excerpt path-base="cookbook/persistence/key_value/"?-->

If you have a relatively small collection of key-values to save, you can use the \[\`shared\_preferences\`]\[] plugin. Normally, you would have to write native platform integrations for storing data on each platform. Fortunately, the \[\`shared\_preferences\`]\[] plugin can be used to persist key-value data to disk on each platform Flutter supports. To compare \`shared\_preferences\` with other local and cloud database options, check out \[Choose a database for your Flutter app]\[]. \[Choose a database for your Flutter app]: /data-and-backend/persistence/choose-a-database This recipe uses the following steps: 1. Add the dependency. 2. Save data. 3. Read data. 4. Remove data. :::note To learn more, watch this short Package of the Week video on the \`shared\_preferences\` package: ::: ## 1. Add the dependency Before starting, add the \[\`shared\_preferences\`]\[] package as a dependency. To add the \`shared\_preferences\` package as a dependency, run \`flutter pub add\`: \`\`\`console flutter pub add shared\_preferences \`\`\` ## 2. Save data To persist data, use the setter methods provided by the \`SharedPreferences\` class. Setter methods are available for various primitive types, such as \`setInt\`, \`setBool\`, and \`setString\`. Setter methods do two things: First, synchronously update the key-value pair in memory. Then, persist the data to disk.

<!--?code-excerpt "lib/partial_excerpts.dart (Step2)"?-->

\`\`\`dart // Load and obtain the shared preferences for this app. final prefs = await SharedPreferences.getInstance(); // Save the counter value to persistent storage under the 'counter' key. await prefs.setInt('counter', counter); \`\`\` ## 3. Read data To read data, use the appropriate getter method provided by the \`SharedPreferences\` class. For each setter there is a corresponding getter. For example, you can use the \`getInt\`, \`getBool\`, and \`getString\` methods.

<!--?code-excerpt "lib/partial_excerpts.dart (Step3)"?-->

\`\`\`dart final prefs = await SharedPreferences.getInstance(); // Try reading the counter value from persistent storage. // If not present, null is returned, so default to 0. final counter = prefs.getInt('counter') ?? 0; \`\`\` Note that the getter methods throw an exception if the persisted value has a different type than the getter method expects. ## 4. Remove data To delete data, use the \`remove()\` method.

<!--?code-excerpt "lib/partial_excerpts.dart (Step4)"?-->

\`\`\`dart final prefs = await SharedPreferences.getInstance(); // Remove the counter key-value pair from persistent storage. await prefs.remove('counter'); \`\`\` ## Supported types Although the key-value storage provided by \`shared\_preferences\` is easy and convenient to use, it has limitations: \* Only primitive types can be used: \`int\`, \`double\`, \`bool\`, \`String\`, and \`List\`. \* It's not designed to store large amounts of data. \* There is no guarantee that data will be persisted across app restarts. ## Testing support It's a good idea to test code that persists data using \`shared\_preferences\`. To enable this, the package provides an in-memory mock implementation of the preference store. To set up your tests to use the mock implementation, call the \`setMockInitialValues\` static method in a \`setUpAll()\` method in your test files. Pass in a map of key-value pairs to use as the initial values.

<!--?code-excerpt "test/prefs_test.dart (setup)"?-->

\`\`\`dart SharedPreferences.setMockInitialValues({'counter': 2}); \`\`\` ## Complete example

<!--?code-excerpt "lib/main.dart"?-->

\`\`\`dart import 'package:material\_ui/material\_ui.dart'; import 'package:shared\_preferences/shared\_preferences.dart'; void main() => runApp(const MyApp()); class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return const MaterialApp( title: 'Shared preferences demo', home: MyHomePage(title: 'Shared preferences demo'), ); } } class MyHomePage extends StatefulWidget { const MyHomePage({super.key, required this.title}); final String title; @override State createState() => \_MyHomePageState(); } class \_MyHomePageState extends State { int \_counter = 0; @override void initState() { super.initState(); \_loadCounter(); } /// Load the initial counter value from persistent storage on start, /// or fallback to 0 if it doesn't exist. Future \_loadCounter() async { final prefs = await SharedPreferences.getInstance(); setState(() { \_counter = prefs.getInt('counter') ?? 0; }); } /// After a click, increment the counter state and /// asynchronously save it to persistent storage. Future \_incrementCounter() async { final prefs = await SharedPreferences.getInstance(); setState(() { \_counter = (prefs.getInt('counter') ?? 0) + 1; prefs.setInt('counter', \_counter); }); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text(widget.title)), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: \[ const Text('You have pushed the button this many times: '), Text( '$\_counter', style: Theme.of(context).textTheme.headlineMedium, ), ], ), ), floatingActionButton: FloatingActionButton( onPressed: \_incrementCounter, tooltip: 'Increment', child: const Icon(Icons.add), ), ); } } \`\`\` \[\`shared\_preferences\`]: https\://pub.dev/packages/shared\_preferences

----
url: https://docs.flutter.dev/reference/widgets.md
----

\# Flutter widget index > An alphabetical list of Flutter widgets. This is an alphabetical list of many of the widgets that are bundled with Flutter. You can also \[browse widgets by category]\[catalog]. You might also want to check out our Widget of the Week video series on the \[Flutter YouTube channel]\(https\://www\.youtube.com/@flutterdev). Each short episode features a different Flutter widget. For more video series, see our \[videos]\(/resources/videos) page. \[Widget of the Week playlist]\(https\://www\.youtube.com/playlist?list=PLjxrf2q8roU23XGwz3Km7sQZFTdB996iG) \[catalog]: /ui/widgets

----
url: https://docs.flutter.dev/cookbook/networking/web-sockets.md
----

\# Communicate with WebSockets > How to connect to a web socket.

<!--?code-excerpt path-base="cookbook/networking/web_sockets/"?-->

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, connect to a \[test WebSocket server]\[]. The server sends back the same message you send to it. This recipe uses the following steps: 1. Connect to a WebSocket server. 2. Listen for messages from the server. 3. Send data to the server. 4. Close the WebSocket connection. ## 1. Connect to a WebSocket server The \[\`web\_socket\_channel\`]\[] 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. In Flutter, use the following line to create a \`WebSocketChannel\` that connects to a server:

<!--?code-excerpt "lib/main.dart (connect)" replace="/_channel/channel/g"?-->

\`\`\`dart final WebSocketChannel channel = WebSocketChannel.connect( Uri.parse('wss\://echo.websocket.org'), ); \`\`\` ## 2. Listen for messages from the server Now that you've established a connection, listen to messages from the server. After sending a message to the test server, it sends the same message back. In this example, use a \[\`StreamBuilder\`]\[] widget to listen for new messages, and a \[\`Text\`]\[] widget to display them.

<!--?code-excerpt "lib/main.dart (StreamBuilder)" replace="/_channel/channel/g"?-->

\`\`\`dart StreamBuilder( stream: channel.stream, builder: (context, snapshot) { return Text(snapshot.hasData ? '${snapshot.data}' : ''); }, ), \`\`\` ### How this works The \`WebSocketChannel\` provides a \[\`Stream\`]\[] of messages from the server. The \`Stream\` class is a fundamental part of the \`dart:async\` package. It provides a way to listen to async events from a data source. Unlike \`Future\`, which returns a single async response, the \`Stream\` class can deliver many events over time. The \[\`StreamBuilder\`]\[] 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 To send data to the server, \`add()\` messages to the \`sink\` provided by the \`WebSocketChannel\`.

<!--?code-excerpt "lib/main.dart (add)" replace="/_channel/channel/g;/_controller.text/'Hello!'/g"?-->

\`\`\`dart channel.sink.add('Hello!'); \`\`\` ### How this works The \`WebSocketChannel\` provides a \[\`StreamSink\`]\[] to push messages to the server. The \`StreamSink\` class provides a general way to add sync or async events to a data source. ## 4. Close the WebSocket connection After you're done using the WebSocket, close the connection:

<!--?code-excerpt "lib/main.dart (close)" replace="/_channel/channel/g"?-->

\`\`\`dart channel.sink.close(); \`\`\` ## Complete example

<!--?code-excerpt "lib/main.dart"?-->

\`\`\`dart import 'package:material\_ui/material\_ui.dart'; import 'package:web\_socket\_channel/web\_socket\_channel.dart'; void main() => runApp(const MyApp()); class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { const title = 'WebSocket Demo'; return const MaterialApp( title: title, home: MyHomePage(title: title), ); } } class MyHomePage extends StatefulWidget { const MyHomePage({super.key, required this.title}); final String title; @override State createState() => \_MyHomePageState(); } class \_MyHomePageState extends State { final TextEditingController \_controller = TextEditingController(); final WebSocketChannel \_channel = WebSocketChannel.connect( Uri.parse('wss\://echo.websocket.org'), ); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text(widget.title)), body: Padding( padding: const EdgeInsets.all(20), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: \[ Form( child: TextFormField( controller: \_controller, decoration: const InputDecoration(labelText: 'Send a message'), ), ), const SizedBox(height: 24), StreamBuilder( stream: \_channel.stream, builder: (context, snapshot) { return Text(snapshot.hasData ? '${snapshot.data}' : ''); }, ), ], ), ), floatingActionButton: FloatingActionButton( onPressed: \_sendMessage, tooltip: 'Send message', child: const Icon(Icons.send), ), // This trailing comma makes auto-formatting nicer for build methods. ); } void \_sendMessage() { if (\_controller.text.isNotEmpty) { \_channel.sink.add(\_controller.text); } } @override void dispose() { \_channel.sink.close(); \_controller.dispose(); super.dispose(); } } \`\`\` !\[Web sockets demo]\(/assets/images/docs/cookbook/web-sockets.webp){:.site-mobile-screenshot} \[\`Stream\`]: https\://api.flutter.dev/flutter/dart-async/Stream-class.html \[\`StreamBuilder\`]: https\://api.flutter.dev/flutter/widgets/StreamBuilder-class.html \[\`StreamSink\`]: https\://api.flutter.dev/flutter/dart-async/StreamSink-class.html \[test WebSocket server]: https\://websocket.org/tools/websocket-echo-server/ \[\`Text\`]: https\://api.flutter.dev/flutter/widgets/Text-class.html \[\`web\_socket\_channel\`]: https\://pub.dev/packages/web\_socket\_channel

----
url: https://docs.flutter.dev/cookbook/persistence/reading-writing-files.md
----

\# Read and write files > How to read from and write to files on disk.

<!--?code-excerpt path-base="cookbook/persistence/reading_writing_files/"?-->

In some cases, you need to read and write files to disk. For example, you might need to persist data across app launches, or download data from the internet and save it for later offline use. To save files to disk on mobile or desktop apps, combine the \[\`path\_provider\`]\[] plugin with the \[\`dart:io\`]\[] library. This recipe uses the following steps: 1. Find the correct local path. 2. Create a reference to the file location. 3. Write data to the file. 4. Read data from the file. To learn more, watch this Package of the Week video on the \`path\_provider\` package: :::note This recipe doesn't work with web apps at this time. To follow the discussion on this issue, check out \`flutter/flutter\` \[issue #45296]\(https\://github.com/flutter/flutter/issues/45296). ::: ## 1. Find the correct local path This example displays a counter. When the counter changes, write data on disk so you can read it again when the app loads. Where should you store this data? The \[\`path\_provider\`]\[] package provides a platform-agnostic way to access commonly used locations on the device's file system. The plugin currently supports access to two file system locations: \*Temporary directory\* : A temporary directory (cache) that the system can clear at any time. On iOS, this corresponds to the \[\`NSCachesDirectory\`]\[]. On Android, this is the value that \[\`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 the \`NSDocumentDirectory\`. On Android, this is the \`AppData\` directory. This example stores information in the documents directory. You can find the path to the documents directory as follows:

<!--?code-excerpt "lib/main.dart (localPath)"?-->

\`\`\`dart import 'package:path\_provider/path\_provider.dart'; // ··· Future get \_localPath async { final directory = await getApplicationDocumentsDirectory(); return directory.path; } \`\`\` ## 2. Create a reference to the file location Once you know where to store the file, create a reference to the file's full location. You can use the \[\`File\`]\[] class from the \[\`dart:io\`]\[] library to achieve this.

<!--?code-excerpt "lib/main.dart (localFile)"?-->

\`\`\`dart Future get \_localFile async { final path = await \_localPath; return File('$path/counter.txt'); } \`\`\` ## 3. Write data to the file Now that you have a \`File\` to work with, use it to read and write data. First, write some data to the file. The counter is an integer, but is written to the file as a string using the \`'$counter'\` syntax.

<!--?code-excerpt "lib/main.dart (writeCounter)"?-->

\`\`\`dart Future writeCounter(int counter) async { final file = await \_localFile; // Write the counter as a string to the file. return file.writeAsString('$counter'); } \`\`\` ## 4. Read data from the file Now that you have some data on disk, you can read it. Once again, use the \`File\` class.

<!--?code-excerpt "lib/main.dart (readCounter)"?-->

\`\`\`dart Future readCounter() async { try { final file = await \_localFile; // Read the contents of the local file. final contents = await file.readAsString(); return int.parse(contents); } catch (\_) { // If an error was encountered, return 0. return 0; } } \`\`\` ## Complete example

<!--?code-excerpt "lib/main.dart"?-->

\`\`\`dart import 'dart:async'; import 'dart:io'; import 'package:material\_ui/material\_ui.dart'; import 'package:path\_provider/path\_provider.dart'; void main() { runApp( MaterialApp( title: 'Reading and writing files', home: FlutterDemo(storage: CounterStorage()), ), ); } class CounterStorage { Future get \_localPath async { final directory = await getApplicationDocumentsDirectory(); return directory.path; } Future get \_localFile async { final path = await \_localPath; return File('$path/counter.txt'); } Future readCounter() async { try { final file = await \_localFile; // Read the contents of the local file. final contents = await file.readAsString(); return int.parse(contents); } catch (\_) { // If an error was encountered, return 0. return 0; } } Future writeCounter(int counter) async { final file = await \_localFile; // Write the counter as a string to the file. return file.writeAsString('$counter'); } } class FlutterDemo extends StatefulWidget { const FlutterDemo({super.key, required this.storage}); final CounterStorage storage; @override State createState() => \_FlutterDemoState(); } class \_FlutterDemoState extends State { int \_counter = 0; @override void initState() { super.initState(); widget.storage.readCounter().then((value) { setState(() { \_counter = value; }); }); } Future \_incrementCounter() { setState(() { \_counter++; }); // Write the variable as a string to the file. return widget.storage.writeCounter(\_counter); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('Reading and writing files')), body: Center( child: Text('Button tapped $\_counter time${\_counter == 1 ? '' : 's'}.'), ), floatingActionButton: FloatingActionButton( onPressed: \_incrementCounter, tooltip: 'Increment', child: const Icon(Icons.add), ), ); } } \`\`\` \[\`dart:io\`]: https\://api.flutter.dev/flutter/dart-io/dart-io-library.html \[\`File\`]: https\://api.flutter.dev/flutter/dart-io/File-class.html \[\`getCacheDir()\`]: https\://developer.android.com/reference/android/content/Context#getCacheDir() \[\`NSCachesDirectory\`]: https\://developer.apple.com/documentation/foundation/nssearchpathdirectory/nscachesdirectory \[\`path\_provider\`]: https\://pub.dev/packages/path\_provider

----
url: https://docs.flutter.dev/deployment/obfuscate
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Obfuscate Dart code](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

## What is code obfuscation?

[#](#what-is-code-obfuscation)

[Code obfuscation](https://en.wikipedia.org/wiki/Obfuscation_\(software\)) is the process of modifying an app's binary to make it harder for humans to understand. Obfuscation hides function and class names in your compiled Dart code, replacing each symbol with another symbol, making it difficult for an attacker to reverse engineer your proprietary app.

## Limitations and warnings

[#](#limitations)

**Flutter's code obfuscation works only on a [release build](/testing/build-modes#release).**

Obfuscating your code does *not* encrypt resources nor does it protect against reverse engineering. It only renames symbols with more obscure names.

Web apps don't support obfuscation. A web app can be [minified](https://en.wikipedia.org/wiki/Minification_\(programming\)), which provides a similar result. When you build a release version of a Flutter web app, the web compiler minifies the app. To learn more, see [Build and release a web app](/deployment/web).

## Supported targets

[#](#supported-targets)

The following build targets support the obfuscation process described on this page:

* `aar`
* `apk`
* `appbundle`
* `ios`
* `ios-framework`
* `ipa`
* `linux`
* `macos`
* `macos-framework`
* `windows`

For detailed information about the command line options available for a build target, run the following command. The `--obfuscate` and `--split-debug-info` options should be listed in the output. If they aren't, you'll need to install a newer version of Flutter to obfuscate your code.

```
$ flutter build <build-target> -h
```

* `<build-target>`: The build target. For example, `apk`.

## Obfuscate your app

[#](#obfuscate-your-app)

To obfuscate your app and create a symbol map, use the `flutter build` command in release mode with the `--obfuscate` and `--split-debug-info` options. If you want to debug your obfuscated app in the future, you will need the symbol map.

1. Run the following command to obfuscate your app and generate a SYMBOLS file:

   ```
   $ flutter build <build-target> \
      --obfuscate \
      --split-debug-info=/<symbols-directory>
   ```

   * `<build-target>`: The build target. For example, `apk`.
   * `<symbols-directory>`: The directory where the SYMBOLS file should be placed. For example, `out/android`.

2. Once you've obfuscated your binary, **backup the SYMBOLS file** (or the PDB file on Windows x64). You might need this if you lose your original SYMBOLS file (or PDB file) and you want to de-obfuscate a stack trace.

## Read an obfuscated stack trace

[#](#read-an-obfuscated-stack-trace)

To debug a stack trace created by an obfuscated app, use the following steps to make it human readable:

1. Find the matching SYMBOLS file. For example, a crash from an Android arm64 device would need `app.android-arm64.symbols`.

2. Provide both the stack trace (stored in a file) and the SYMBOLS file to the `flutter symbolize` command.

   ```
   $ flutter symbolize \
      -i <stack-trace-file> \
      -d <obfuscated-symbols-file>
   ```

   * `<stack-trace-file>`: The file path for the stacktrace. For example, `???`.
   * `<obfuscated-symbols-file>`: The file path for the symbols file that contains the obfuscated symbols. For example, `out/android/app.android-arm64.symbols`.

   For more information about the `symbolize` command, run `flutter symbolize -h`.

## Read an obfuscated name

[#](#read-an-obfuscated-name)

You can generate a JSON file that contains an obfuscation map. An obfuscation map is a JSON array with pairs of original names and obfuscated names. For example, `["MaterialApp", "ex", "Scaffold", "ey"]`, where `ex` is the obfuscated name of `MaterialApp`.

To generate an obfuscation map, use the following command:

```
$ flutter build <build-target> \
   --obfuscate \
   --split-debug-info=/<symbols-directory> \
   --extra-gen-snapshot-options=--save-obfuscation-map=/<obfuscation-map-file>
```

* `<build-target>`: The build target. For example, `apk`.
* `<symbols-directory>`: The directory where the symbols should be placed. For example, `out/android`
* `<obfuscation-map-file>`: The file path where the JSON obfuscation map should be placed. For example, `out/android/map.json`

## Caveat

[#](#caveat)

Be aware of the following when coding an app that will eventually be an obfuscated binary.

* Code that relies on matching specific class, function, or library names will fail. For example, the following call to `expect()` won't work in an obfuscated binary:

  dart

  ```
  expect(foo.runtimeType.toString(), equals('Foo'));
  ```

* Enum names are not obfuscated currently.

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-08-25. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/deployment/obfuscate.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/deployment/obfuscate\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/deployment/obfuscate.md "Report an issue with this page").

----
url: https://docs.flutter.dev/ui/animations/hero-animations.md
----

\# Hero animations > How to animate a widget to fly between two screens. :::secondary What you'll learn \* The \_hero\_ refers to the widget that flies between screens. \* Create a hero animation using Flutter's \`Hero\` widget. \* Fly the hero from one screen to another. \* Animate the transformation of a hero's shape from circular to rectangular while flying it from one screen to another. \* The \`Hero\` widget in Flutter implements a style of animation commonly known as \_shared element transitions\_ or \_shared element animations\_. ::: You've probably seen hero animations many times. For example, a screen displays a list of thumbnails representing items for sale. Selecting an item flies it to 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 might want to watch this one-minute video introducing the \`Hero\` widget: This guide demonstrates how to build standard hero animations, and hero animations that transform the image from a circular shape to a square shape during flight. :::secondary Examples This guide walks through examples of each hero animation style: \* \[Standard hero animations]\(#standard-hero-animations) \* \[Radial hero animations]\(#radial-hero-animations) If you want to run the examples yourself, follow the steps in \[Run the examples]\(#run-the-examples). ::: :::secondary New to Flutter? This page assumes you know how to create a layout using Flutter's widgets. To learn more, visit \[Building Layouts in Flutter]\[]. ::: :::tip Terminology A \[\_Route\_]\[] describes a page or screen in a Flutter app. ::: ## Hero animation styles You can create hero animations in Flutter with \`Hero\` widgets. As the hero animates from the source to the destination route, the destination route (minus the hero) fades into view. Typically, heroes are small parts of the UI, like images, that both routes have in common. From the user's perspective, the hero "flies" between the routes. This guide shows how to create the following hero animations: ### Standard animations {:#standard-hero-animation-preview} A \_standard hero animation\_ flies the hero from one route to a new route, usually landing at a different location and with a different size. The following video (recorded at slow speed) shows a typical example. Tapping the flippers in the center of the route flies them to the upper left corner of a new, blue route, at a smaller size. Tapping the flippers in the blue route (or using the device's back-to-previous-route gesture) flies the flippers back to the original route. ### Radial animations {:#radial-hero-animation-preview} In a \_radial hero animation\_, as the hero flies between routes its shape appears to change from circular to rectangular. The following video (recorded at slow speed), shows an example of a radial hero animation. At the start, a row of three circular images appears at the bottom of the route. Tapping any of the circular images flies that image to a new route that displays it with a square shape. Tapping the square image flies the hero back to the original route, displayed with a circular shape. Before moving to the sections specific to \[standard]\(#standard-hero-animations) or \[radial]\(#radial-hero-animations) hero animations, read \[basic structure of a hero animation]\(#basic-structure) to learn how to structure hero animation code, and \[behind the scenes]\(#behind-the-scenes) to understand how Flutter performs a hero animation. []()## Basic structure of a hero animation :::secondary What's the point? \* Use two hero widgets in different routes but with matching tags to implement the animation. \* The \`Navigator\` manages a stack containing the app's routes. \* Pushing a route on or popping a route from the \`Navigator\` stack triggers the animation. \* The Flutter framework calculates a rectangle tween, \[\`RectTween\`]\[], that defines the hero's boundary as it flies from the source to the destination route. During its flight, the hero is moved to an application overlay, so that it appears on top of both routes. ::: :::tip Terminology If the concept of tweens or tweening is new to you, visit the \[Animations in Flutter tutorial]\[]. ::: Hero animations are implemented using two \[\`Hero\`]\[] widgets: one describing the widget in the source route, and another describing the widget in the destination route. From the user's point of view, the hero appears to be shared, and only the programmer needs to understand this implementation detail. Hero animation code has the following structure: 1. Define a starting \`Hero\` widget, referred to as the \_source hero\_. The hero specifies its graphical representation (typically an image), and an identifying tag, and is in the currently displayed widget tree as defined by the source route. 1. Define an ending \`Hero\` widget, referred to as the \_destination hero\_. This hero also specifies its graphical representation, and the same tag as the source hero. It's \*\*essential that both hero widgets are created with the same tag\*\*, typically an object that represents the underlying data. For best results, the heroes should have virtually identical widget trees. 1. Create a route that contains the destination hero. The destination route defines the widget tree that exists at the end of the animation. 1. Trigger the animation by pushing the destination route on the \`Navigator\` stack. The \`Navigator\` push and pop operations trigger a hero animation for each pair of heroes with matching tags in the source and destination routes. Flutter calculates the tween that animates the \`Hero\` widget's bounds from the starting point to the endpoint (interpolating size and position), and performs the animation in an overlay. The next section describes Flutter's process in greater detail. ## Behind the scenes The following describes how Flutter performs the transition from one route to another. !\[Before the transition the source hero appears in the source route]\(/assets/images/docs/ui/animations/hero-transition-0.png) Before transition, the source hero waits in the source route's widget tree. The destination route does not yet exist, and the overlay is empty. --- !\[The transition begins]\(/assets/images/docs/ui/animations/hero-transition-1.png) Pushing a route to the \`Navigator\` triggers the animation. At \`t=0.0\`, Flutter does the following: \* Calculates the destination hero's path, offscreen, using the curved motion as described in the Material motion spec. Flutter now knows where the hero ends up. \* Places the destination hero in the overlay, at the same location and size as the \_source\_ hero. Adding a hero to the overlay changes its Z-order so that it appears on top of all routes. \* Moves the source hero offscreen. --- !\[The hero flies in the overlay to its final position and size]\(/assets/images/docs/ui/animations/hero-transition-2.png) As the hero flies, its rectangular bounds are animated using \[\`Tween\`]\[], specified by the \[\`createRectTween\`]\[] property of the \`Hero\` widget. By default, Flutter uses an instance of \[\`MaterialRectArcTween\`]\[], which animates the rectangle's opposing corners along a curved path. (Refer to \[Radial hero animations]\[] for an example that uses a different \`Tween\` animation.) --- !\[When the transition is complete, the hero is moved from the overlay to the destination route]\(/assets/images/docs/ui/animations/hero-transition-3.png) When the flight completes: \* Flutter moves the hero widget from the overlay to the destination route. The overlay is now empty. \* The destination hero appears in its final position in the destination route. \* The source hero is restored to its route. --- Popping the route performs the same process, animating the hero back to its size and location in the source route. ### Essential classes The examples in this guide use the following classes to implement hero animations: \[\`Hero\`]\[] : The widget that flies from the source to the destination route. Define one \`Hero\` for the source route and another for the destination route, and assign each the same tag. Flutter animates pairs of heroes with matching tags. \[\`InkWell\`]\[] : Specifies what happens when tapping the hero. The \`onTap()\` callback of the \`InkWell\` widget builds the new route and pushes it to the \`Navigator\` stack. \[\`Navigator\`]\[] : The \`Navigator\` manages a stack of routes. Pushing a route on or popping a route from the \`Navigator\` stack triggers the animation. \[\`Route\`]\[] : Specifies a screen or page. Most apps, beyond the most basic, have multiple routes. ## Standard hero animations :::secondary What's the point? \* Specify a route using \[\`MaterialPageRoute\`]\[], \[\`CupertinoPageRoute\`]\[], or build a custom route using \[\`PageRouteBuilder\`]\[]. The examples in this section use \`MaterialPageRoute\`. \* Change the size of the image at the end of the transition by wrapping the destination's image in a \`SizedBox\`. \* Change the location of the image by placing the destination's image in a layout widget. These examples use \`Container\`. ::: []()### What's going on? Flying an image from one route to another is straightforward to implement using Flutter's \`Hero\` widget. When using \`MaterialPageRoute\` to specify the new route, the image flies along a curved path, as described by the \[Material Design motion spec]\[]. The complete code and images for this section are in the \[standard hero animation example]\[]. To explore the animation yourself, \[set up and run the example]\(#run-the-examples), then try the following: \* Tap on the home route's photo to fly the image to a new route showing the same photo at a different location and scale. \* Return to the previous route by tapping the image, or by using the device's back-to-the-previous-route gesture. \* Slow the transition further by increasing the \[\`timeDilation\`]\[] value. ### PhotoHero class The custom \`PhotoHero\` class maintains the hero, and its size, image, and behavior when tapped. The \`PhotoHero\` builds the following widget tree: Here's the code:

<!--?code-excerpt "animation/hero_animation/lib/main.dart (photo-hero)"?-->

\`\`\`dart import 'package:flutter/scheduler.dart' show timeDilation; import 'package:material\_ui/material\_ui.dart'; class PhotoHero extends StatelessWidget { const PhotoHero({ super.key, required this.photo, this.onTap, required this.width, }); final String photo; final VoidCallback? onTap; final double width; @override Widget build(BuildContext context) { return SizedBox( width: width, child: Hero( tag: photo, child: Material( color: Colors.transparent, child: InkWell( onTap: onTap, child: Image.asset(photo, fit: BoxFit.contain), ), ), ), ); } } \`\`\` Key information: \* The starting route is implicitly pushed by \`MaterialApp\` when \`HeroAnimation\` is provided as the app's \`home\` property. \* An \`InkWell\` wraps the image, making it easy to add a tap gesture to both the source and destination heroes. \* Defining the \`Material\` widget with a transparent color enables the image to "pop out" of the background as it flies to its destination. \* The \`SizedBox\` specifies the hero's size at the start and end of the animation. \* Setting the \`Image\` widget's \`fit\` property to \`BoxFit.contain\` ensures that the image is as large as possible during the transition without changing its aspect ratio. ### HeroAnimation class The \`HeroAnimation\` class creates the source and destination \`PhotoHero\` widgets, and sets up the transition. Here's the code:

<!--?code-excerpt "animation/hero_animation/lib/main.dart (hero-animation)"?-->

\`\`\`dart highlightLines=9,12-15,23,26-27,43 class HeroAnimation extends StatelessWidget { const HeroAnimation({super.key}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('Basic Hero Animation')), body: Center( child: PhotoHero( photo: 'images/flippers.png', width: 300, onTap: () { Navigator.of(context).push( MaterialPageRoute( builder: (context) { return Scaffold( appBar: AppBar(title: const Text('Flippers Page')), body: Container( // Set background to blue to emphasize that it's a new route. color: Colors.lightBlueAccent, padding: const EdgeInsets.all(16), alignment: Alignment.topLeft, child: PhotoHero( photo: 'images/flippers.png', width: 100, onTap: () { Navigator.of(context).pop(); }, ), ), ); }, ), ); }, ), ), ); } } void main() { timeDilation = 5; // 1 means normal animation speed. runApp(const MaterialApp(home: HeroAnimation())); } \`\`\` Key information: \* When the user taps the \`InkWell\` containing the source hero, the code creates the destination route using \`MaterialPageRoute\`. Pushing the destination route to the \`Navigator\` stack triggers the animation. \* The \`Container\` positions the \`PhotoHero\` in the destination route's top-left corner, below the \`AppBar\`. \* The \`onTap()\` method for the destination \`PhotoHero\` pops the \`Navigator\` stack, triggering the animation that flies the \`Hero\` back to the original route. \* Use the \`timeDilation\` property to slow the transition while debugging. --- ## Radial hero animations :::secondary What's the point? \* A \_radial transformation\_ animates a circular shape into a square shape. \* A radial \_hero\_ animation performs a radial transformation while flying the hero from the source route to the destination route. \* \`MaterialRectCenterArcTween\` defines the tween animation. \* Build the destination route using \`PageRouteBuilder\`. ::: Flying a hero from one route to another as it transforms from a circular shape to a rectangular shape is a visual effect that you can implement using \`Hero\` widgets. To accomplish this, the code animates the intersection of two clip shapes: a circle and a square. Throughout the animation, the circle clip (and the image) scales from \`minRadius\` to \`maxRadius\`, while the square clip maintains constant size. At the same time, the image flies from its position in the source route to its position in the destination route. For visual examples of this transition, consult \[Radial transformation]\[] in the Material motion spec. []():::tip Pro tip The radial hero animation involves intersecting a round shape with a square shape. This can be hard to see, even when slowing the animation with \`timeDilation\`, so you might consider enabling the \[\`debugPaintSizeEnabled\`]\[] flag during development. ::: ### What's going on? The following diagram shows the clipped image at the beginning (\`t = 0.0\`), and the end (\`t = 1.0\`) of the animation. !\[Radial transformation from beginning to end]\(/assets/images/docs/ui/animations/radial-hero-animation.png) The blue gradient (representing the image), indicates where the clip shapes intersect. At the beginning of the transition, the result of the intersection is a circular clip (\[\`ClipOval\`]\[]). During the transformation, the \`ClipOval\` scales from \`minRadius\` to \`maxRadius\` while the \[\`ClipRect\`]\[] maintains a constant size. At the end of the transition the intersection of the circular and rectangular clips yield a rectangle that's the same size as the hero widget. In other words, at the end of the transition the image is no longer clipped. The complete code and images for this section, including the route setup that isn't shown on this page, are in the \[radial hero animation example]\[]. To explore the animation yourself, \[set up and run the example]\(#run-the-examples), then try the following: \* Tap on one of the three circular thumbnails to animate the image to a larger square positioned in the middle of a new route that obscures the original route. \* Return to the previous route by tapping the image, or by using the device's back-to-the-previous-route gesture. \* Slow the transition further by increasing the \[\`timeDilation\`]\[] value. ### Photo class The \`Photo\` class builds the widget tree that holds the image:

<!--?code-excerpt "animation/radial_hero_animation/lib/main.dart (photo)"?-->

\`\`\`dart highlightLines=9,11-14 class Photo extends StatelessWidget { const Photo({super.key, required this.photo, this.onTap}); final String photo; final VoidCallback? onTap; @override Widget build(BuildContext context) { return Material( // Slightly opaque color appears where the image has transparency. color: Theme.of(context).primaryColor.withValues(alpha: 0.25), child: InkWell( onTap: onTap, child: Image.asset(photo, fit: BoxFit.contain), ), ); } } \`\`\` Key information: \* The \`InkWell\` captures the tap gesture. The calling function passes the \`onTap()\` function to the \`Photo\` constructor. \* During flight, the \`InkWell\` draws its splash on its first \`Material\` ancestor. \* The \`Material\` widget has a slightly opaque color, so the transparent portions of the image are rendered with color. This ensures that the circle-to-square transition is easy to see, even for images with transparency. \* The \`Photo\` class does not include the \`Hero\` in its widget tree. For the animation to work, the hero wraps the \`RadialExpansion\` widget. ### RadialExpansion class The \`RadialExpansion\` widget, the core of the demo, builds the widget tree that clips the image during the transition. The clipped shape results from the intersection of a circular clip (that grows during flight), with a rectangular clip (that remains a constant size throughout). To do this, it builds the following widget tree: Here's the code:

<!--?code-excerpt "animation/radial_hero_animation/lib/main.dart (radial-expansion)"?-->

\`\`\`dart highlightLines=3,11-13,16-17 class RadialExpansion extends StatelessWidget { const RadialExpansion({super.key, required this.maxRadius, this.child}) : clipRectSize = 2 \* (maxRadius / math.sqrt2); final double maxRadius; final double clipRectSize; final Widget? child; @override Widget build(BuildContext context) { return ClipOval( child: Center( child: SizedBox( width: clipRectSize, height: clipRectSize, child: ClipRect( child: child, // Photo ), ), ), ); } } \`\`\` Key information: \* The hero wraps the \`RadialExpansion\` widget. \* As the hero flies, its size changes and, because it constrains its child's size, the \`RadialExpansion\` widget changes size to match. \* The \`RadialExpansion\` animation is created by two overlapping clips. \* The example defines the tweening interpolation using \[\`MaterialRectCenterArcTween\`]\[]. The default flight path for a hero animation interpolates the tweens using the corners of the heroes. This approach affects the hero's aspect ratio during the radial transformation, so the new flight path uses \`MaterialRectCenterArcTween\` to interpolate the tweens using the center point of each hero. Here's the code:

<!--?code-excerpt "animation/radial_hero_animation/lib/main.dart (create-rect-tween)"?-->

\`\`\`dart static RectTween \_createRectTween(Rect? begin, Rect? end) { return MaterialRectCenterArcTween(begin: begin, end: end); } \`\`\` The hero's flight path still follows an arc, but the image's aspect ratio remains constant. --- ## Run the examples Each example in this guide is a complete app: the \[standard hero animation example]\[] and the \[radial hero animation example]\[]. To run the examples yourself: 1. \[Create a new Flutter app]\[]. 1. Add the \[\`material\_ui\`]\[] package as a dependency by running \`flutter pub add material\_ui\`. 1. Replace \`lib/main.dart\` with the example's \`lib/main.dart\` file. 1. Download the following images into an \`images\` directory at the root of your project: \[\`beachball.png\`]\[], \[\`binoculars.png\`]\[], \[\`chair.png\`]\[], and \[\`flippers.png\`]\[]. 1. Declare the images as \[assets]\[asset declarations] by adding an \`assets\` entry to the existing \`flutter\` section of the project's \`pubspec.yaml\` file: \`\`\`yaml title="pubspec.yaml" highlightLines=3-4 flutter: uses-material-design: true assets: - images/ \`\`\` 1. Run the app with \`flutter run\`. \[Animations in Flutter tutorial]: /ui/animations/tutorial \[asset declarations]: /ui/assets/assets-and-images#specifying-assets \[\`beachball.png\`]: https\://raw\.githubusercontent.com/flutter/website/main/examples/animation/radial\_hero\_animation/images/beachball.png \[\`binoculars.png\`]: https\://raw\.githubusercontent.com/flutter/website/main/examples/animation/radial\_hero\_animation/images/binoculars.png \[Building Layouts in Flutter]: /ui/layout \[\`chair.png\`]: https\://raw\.githubusercontent.com/flutter/website/main/examples/animation/radial\_hero\_animation/images/chair.png \[\`ClipOval\`]: https\://api.flutter.dev/flutter/widgets/ClipOval-class.html \[\`ClipRect\`]: https\://api.flutter.dev/flutter/widgets/ClipRect-class.html \[Create a new Flutter app]: /reference/create-new-app \[\`createRectTween\`]: https\://api.flutter.dev/flutter/widgets/CreateRectTween.html \[\`CupertinoPageRoute\`]: https\://pub.dev/documentation/cupertino\_ui/latest/cupertino\_ui/CupertinoPageRoute-class.html \[\`debugPaintSizeEnabled\`]: /tools/devtools/inspector#debugging-layout-issues-visually \[\`flippers.png\`]: https\://raw\.githubusercontent.com/flutter/website/main/examples/animation/hero\_animation/images/flippers.png \[\`Hero\`]: https\://api.flutter.dev/flutter/widgets/Hero-class.html \[\`InkWell\`]: https\://pub.dev/documentation/material\_ui/latest/material\_ui/InkWell-class.html \[Material Design motion spec]: https\://m2.material.io/design/motion/understanding-motion.html#principles \[\`material\_ui\`]: https\://pub.dev/packages/material\_ui \[\`MaterialPageRoute\`]: https\://pub.dev/documentation/material\_ui/latest/material\_ui/MaterialPageRoute-class.html \[\`MaterialRectArcTween\`]: https\://pub.dev/documentation/material\_ui/latest/material\_ui/MaterialRectArcTween-class.html \[\`MaterialRectCenterArcTween\`]: https\://pub.dev/documentation/material\_ui/latest/material\_ui/MaterialRectCenterArcTween-class.html \[\`Navigator\`]: https\://api.flutter.dev/flutter/widgets/Navigator-class.html \[\`PageRouteBuilder\`]: https\://api.flutter.dev/flutter/widgets/PageRouteBuilder-class.html \[Radial hero animations]: #radial-hero-animations \[radial hero animation example]: https\://github.com/flutter/website/tree/main/examples/animation/radial\_hero\_animation \[Radial transformation]: https\://web.archive.org/web/20180223140424/https\://material.io/guidelines/motion/transforming-material.html \[\`RectTween\`]: https\://api.flutter.dev/flutter/animation/RectTween-class.html \[\_Route\_]: /cookbook/navigation/navigation-basics \[\`Route\`]: https\://api.flutter.dev/flutter/widgets/Route-class.html \[standard hero animation example]: https\://github.com/flutter/website/tree/main/examples/animation/hero\_animation \[\`timeDilation\`]: https\://api.flutter.dev/flutter/scheduler/timeDilation.html \[\`Tween\`]: https\://api.flutter.dev/flutter/animation/Tween-class.html

----
url: https://docs.flutter.dev/testing/errors
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Handling errors in Flutter](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

The Flutter framework catches errors that occur during callbacks triggered by the framework itself, including errors encountered during the build, layout, and paint phases. Errors that don't occur within Flutter's callbacks can't be caught by the framework, but you can handle them by setting up an error handler on the [`PlatformDispatcher`](https://api.flutter.dev/flutter/dart-ui/PlatformDispatcher-class.html).

All errors caught by Flutter are routed to the [`FlutterError.onError`](https://api.flutter.dev/flutter/foundation/FlutterError/onError.html) handler. By default, this calls [`FlutterError.presentError`](https://api.flutter.dev/flutter/foundation/FlutterError/presentError.html), which dumps the error to the device logs. When running from an IDE, the inspector overrides this behavior so that errors can also be routed to the IDE's console, allowing you to inspect the objects mentioned in the message.

When an error occurs during the build phase, the [`ErrorWidget.builder`](https://api.flutter.dev/flutter/widgets/ErrorWidget/builder.html) callback is invoked to build the widget that is used instead of the one that failed. By default, in debug mode this shows an error message in red, and in release mode this shows a gray background.

When errors occur without a Flutter callback on the call stack, they are handled by the `PlatformDispatcher`'s error callback. By default, this only prints errors and does nothing else.

You can customize these behaviors, typically by setting them to values in your `void main()` function.

Below each error type handling is explained. At the bottom there's a code snippet which handles all types of errors. Even though you can just copy-paste the snippet, we recommend you to first get acquainted with each of the error types.

## Errors caught by Flutter

[#](#errors-caught-by-flutter)

For example, to make your application quit immediately any time an error is caught by Flutter in release mode, you could use the following handler:

dart

```
import 'dart:io';
​
import 'package:flutter/foundation.dart';
import 'package:material_ui/material_ui.dart';
​
void main() {
  FlutterError.onError = (details) {
    FlutterError.presentError(details);
    if (kReleaseMode) exit(1);
  };
  runApp(const MyApp());
}
​
// The rest of the `flutter create` code...
```

This handler can also be used to report errors to a logging service. For more details, see our cookbook chapter for [reporting errors to a service](/cookbook/maintenance/error-reporting).

## Define a custom error widget for build phase errors

[#](#define-a-custom-error-widget-for-build-phase-errors)

To define a customized error widget that displays whenever the builder fails to build a widget, use [`MaterialApp.builder`](https://pub.dev/documentation/material_ui/latest/material_ui/MaterialApp/builder.html).

dart

```
class MyApp extends StatelessWidget {
  const MyApp({super.key});
​
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      builder: (context, widget) {
        Widget error = const Text('...rendering error...');
        if (widget is Scaffold || widget is Navigator) {
          error = Scaffold(body: Center(child: error));
        }
        ErrorWidget.builder = (errorDetails) => error;
        if (widget != null) return widget;
        throw StateError('widget is null');
      },
    );
  }
}
```

## Errors not caught by Flutter

[#](#errors-not-caught-by-flutter)

Consider an `onPressed` callback that invokes an asynchronous function, such as `MethodChannel.invokeMethod` (or pretty much any plugin). For example:

dart

```
OutlinedButton(
  child: const Text('Click me!'),
  onPressed: () async {
    const channel = MethodChannel('crashy-custom-channel');
    await channel.invokeMethod('blah');
  },
)
```

If `invokeMethod` throws an error, it won't be forwarded to `FlutterError.onError`. Instead, it's forwarded to the `PlatformDispatcher`.

To catch such an error, use [`PlatformDispatcher.instance.onError`](https://api.flutter.dev/flutter/dart-ui/PlatformDispatcher/onError.html).

dart

```
import 'dart:ui';
​
import 'package:material_ui/material_ui.dart';
​
void main() {
  MyBackend myBackend = MyBackend();
  PlatformDispatcher.instance.onError = (error, stack) {
    myBackend.sendError(error, stack);
    return true;
  };
  runApp(const MyApp());
}
```

## Handling all types of errors

[#](#handling-all-types-of-errors)

Say you want to exit application on any exception and to display a custom error widget whenever a widget building fails - you can base your errors handling on next code snippet:

dart

```
import 'dart:ui';
​
import 'package:material_ui/material_ui.dart';
​
Future<void> main() async {
  await myErrorsHandler.initialize();
  FlutterError.onError = (details) {
    FlutterError.presentError(details);
    myErrorsHandler.onErrorDetails(details);
  };
  PlatformDispatcher.instance.onError = (error, stack) {
    myErrorsHandler.onError(error, stack);
    return true;
  };
  runApp(const MyApp());
}
​
class MyApp extends StatelessWidget {
  const MyApp({super.key});
​
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      builder: (context, widget) {
        Widget error = const Text('...rendering error...');
        if (widget is Scaffold || widget is Navigator) {
          error = Scaffold(body: Center(child: error));
        }
        ErrorWidget.builder = (errorDetails) => error;
        if (widget != null) return widget;
        throw StateError('widget is null');
      },
    );
  }
}
```

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-09-29. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/testing/errors.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/testing/errors\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/testing/errors.md "Report an issue with this page").

----
url: https://docs.flutter.dev/deployment/windows
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Build and release a Windows desktop app](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

One convenient approach to distributing Windows apps is the [Microsoft Store](https://www.microsoft.com/store/apps/windows). This guide provides a step-by-step walkthrough of packaging and deploying a Flutter app in this way.

## Preliminaries

[#](#preliminaries)

Before beginning the process of releasing a Flutter Windows desktop app to the Microsoft Store, first confirm that it satisfies [Microsoft Store Policies](https://docs.microsoft.com/windows/uwp/publish/store-policies/).

Also, you must join the [Microsoft Partner Network](https://partner.microsoft.com/) to be able to submit apps.

## Set up your application in the Partner Center

[#](#set-up-your-application-in-the-partner-center)

Manage an application's life cycle in the [Microsoft Partner Center](https://partner.microsoft.com/).

First, reserve the application name and ensure that the required rights to the name exist. Once the name is reserved, the application will be provisioned for services (such as push notifications), and you can start adding add-ons.

Options such as pricing, availability, age ratings, and category have to be configured together with the first submission and are automatically retained for the subsequent submissions.

## Packaging and deployment

[#](#packaging-and-deployment)

In order to publish an application to Microsoft Store, you must first package it. The valid formats are **.msix**, **.msixbundle**, **.msixupload**, **.appx**, **.appxbundle**, **.appxupload**, and **.xap**.

### Manual packaging and deployment for the Microsoft Store

[#](#manual-packaging-and-deployment-for-the-microsoft-store)

Check out [MSIX packaging](/platform-integration/windows/building#msix-packaging) to learn about packaging Flutter Windows desktop applications.

Note that each product has a unique identity, which the Store assigns.

If the package is being built manually, you have to include its identity details manually during the packaging. The essential information can be retrieved from the Partner Center using the following instructions:

1. In the Partner Center, navigate to the application.
2. Select **Product management**.
3. Retrieve the package identity name, publisher, and publisher display name by clicking **Product identity**.

After manually packaging the application, manually submit it to the [Microsoft Partner Center](https://partner.microsoft.com/). You can do this by creating a new submission, navigating to **Packages**, and uploading the created application package.

### Continuous deployment

[#](#continuous-deployment)

In addition to manually creating and deploying the package, you can automate the build, package, versioning, and deployment process using CI/CD tooling after having submitted the application to the Microsoft Store for the first time.

#### Codemagic CI/CD

[#](#codemagic-cicd)

[Codemagic CI/CD](https://codemagic.io/start/) uses the [`msix` pub package](https://pub.dev/packages/msix) to package Flutter Windows desktop applications.

For Flutter applications, use either the [Codemagic Workflow Editor](https://docs.codemagic.io/flutter-publishing/publishing-to-microsoft-store/) or [codemagic.yaml](https://docs.codemagic.io/yaml-publishing/microsoft-store/) to package the application and deploy it to the Microsoft Partner Center. Additional options (such as the list of capabilities and language resources contained in the package) can be configured using this package.

For publishing, Codemagic uses the [Partner Center submission API](https://docs.microsoft.com/azure/marketplace/azure-app-apis); so, Codemagic requires [associating the Azure Active Directory and Partner Center accounts](https://docs.microsoft.com/windows/uwp/publish/associate-azure-ad-with-partner-center).

#### GitHub Actions CI/CD

[#](#github-actions-cicd)

GitHub Actions can use the [Microsoft Dev Store CLI](https://learn.microsoft.com/windows/apps/publish/msstore-dev-cli/overview) to package applications into an MSIX and publish them to the Microsoft Store. The [setup-msstore-cli](https://github.com/microsoft/setup-msstore-cli) GitHub Action installs the cli so that the Action can use it for packaging and publishing.

As packaging the MSIX uses the [`msix` pub package](https://pub.dev/packages/msix), the project's `pubspec.yaml` must contain an appropriate `msix_config` node.

You must create an Azure AD directory from the Dev Center with [global administrator permission](https://azure.microsoft.com/documentation/articles/active-directory-assign-admin-roles/).

The GitHub Action requires environment secrets from the partner center. `AZURE_AD_TENANT_ID`, `AZURE_AD_ClIENT_ID`, and `AZURE_AD_CLIENT_SECRET` are visible on the Dev Center following the instructions for the [Windows Store Publish Action](https://github.com/marketplace/actions/windows-store-publish#obtaining-your-credentials). You also need the `SELLER_ID` secret, which can be found in the Dev Center under **Account Settings** > **Organization Profile** > **Legal Info**.

The application must already be present in the Microsoft Dev Center with at least one complete submission, and `msstore init` must be run once within the repository before the Action can be performed. Once complete, running [`msstore package .`](https://learn.microsoft.com/windows/apps/publish/msstore-dev-cli/package-command) and [`msstore publish`](https://learn.microsoft.com/windows/apps/publish/msstore-dev-cli/publish-command) in a GitHub Action packages the application into an MSIX and uploads it to a new submission on the dev center.

The steps necessary for MSIX publishing resemble the following

yaml

```
- uses: microsoft/setup-msstore-cli@v1
​
- name: Configure the Microsoft Store CLI
  run: msstore reconfigure --tenantId $ --clientId $ --clientSecret $ --sellerId $
​
- name: Install Dart dependencies
  run: flutter pub get
​
- name: Create MSIX package
  run: msstore package .
​
- name: Publish MSIX to the Microsoft Store
  run: msstore publish -v
```

## Updating the app's version number

[#](#updating-the-apps-version-number)

For apps published to the Microsoft Store, the version number must be set during the packaging process.

The default version number of the app is `1.0.0.0`.

For apps not published to the Microsoft Store, you can set the app's executable's file and product versions. The executable's default file version is `1.0.0.1`, and its default product version is `1.0.0+1`. To update these, navigate to the `pubspec.yaml` file and update the following line:

yaml

```
version: 1.0.0+1
```

The build name is three numbers separated by dots, followed by an optional build number that is separated by a `+`. In the example above, the build name is `1.0.0` and the build number is `1`.

The build name becomes the first three numbers of the file and product versions, while the build number becomes the fourth number of the file and product versions.

Both the build name and number can be overridden in `flutter build windows` by specifying `--build-name` and `--build-number`, respectively.

## Add app icons

[#](#add-app-icons)

To update the icon of a Flutter Windows desktop application before packaging use the following instructions:

1. In the Flutter project, navigate to **windows\runner\resources**.
2. Replace the **app\_icon.ico** with the desired icon.
3. If the name of the icon is other than **app\_icon.ico**, proceed to change the **IDI\_APP\_ICON** value in the **windows\runner\Runner.rc** file to point to the new path.

When packaging with the [`msix` pub package](https://pub.dev/packages/msix), the logo path can also be configured inside the `pubspec.yaml` file.

To update the application image in the Store listing, navigate to the Store listing step of the submission and select Store logos. From there, you can upload the logo with the size of 300 x 300 pixels.

All uploaded images are retained for subsequent submissions.

## Validating the application package

[#](#validating-the-application-package)

Before publication to the Microsoft Store, first validate the application package locally.

[Windows App Certification Kit](https://docs.microsoft.com/windows/uwp/debug-test-perf/windows-app-certification-kit) is a tool included in the Windows Software Development Kit (SDK).

To validate the application:

1. Launch Windows App Cert Kit.
2. Select the Flutter Windows desktop package (**.msix**, **.msixbundle**, etc.).
3. Choose a destination for the test report.

The report might contain important warnings and information, even if the certification passes.

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/deployment/windows.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/deployment/windows\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/deployment/windows.md "Report an issue with this page").

----
url: https://docs.flutter.dev/ai/best-practices/tool-calls
----

While it's true that LLMs are trained essentially on the entire internet, they don't know everything. They know what was on the public internet the day they were trained, but they don't know anything more recent than that. They don't know anything that's private to you or your organization. And even things they do know can easily get tangled up with other things they know.

For those scenarios, and many others, we often provide an LLM with one or more tools.

### Tool defined

[#](#tool-defined)

A tool is a name, a description and a JSON schema for the format of the input data when the LLM "calls" the tool. For example, if we prompt the LLM to "Reduce the carbs in Grandma's All America Breakfast recipe", it won't know what grandma's recipe is unless we give it a "lookupRecipe" tool that takes a query string we can use to look up the recipe.

Conceptually, a tool is something we give the LLM to call when it needs that data or service. The way an LLM calls a tool is by responding to the app's request with a specially formatted message that means "tool call". A tool call message includes the name and JSON arguments for the tool. The app handles the tool call and bundles the result in another LLM request, to which the LLM then responds.

This can go on for a while. An app can configure a model instance with any number of tools (although the LLM does better with a smaller set of targeted tools that don't overlap in functionality). The LLM can bundle up any number of tool calls in its response and can take any number of tool results in a request. The LLM consolidates multiple round-trips for prompts and tool call results via a stack of messages that form a history of request/response pairs.

When it's done with the tool calls, the LLM returns its final response, for example "Here's a version of Grandma's All American Breakfast recipe that's high on protein and low on carbs…".

### Gemini functions

[#](#gemini-functions)

In the Firebase AI Logic SDK, a tool is called a "function", but it's the same thing. In the sample, the clue solver model is configured with a function to look up word details. If the LLM wants details about a word to help with the solving process, calling the function provides it with data from [the Free Dictionary API](https://dictionaryapi.dev/):

json

```
[
  {
    "word": "tool",
    "phonetic": "/tuːl/",
    "phonetics": [
      {
        "text": "/tuːl/",
        "audio": "https://api.dictionaryapi.dev/media/pronunciations/en/tool-uk.mp3",
        "sourceUrl": "https://commons.wikimedia.org/w/index.php?curid=94709459",
        "license": {
          "name": "BY-SA 4.0",
          "url": "https://creativecommons.org/licenses/by-sa/4.0"
        }
      }
    ],
    "meanings": [
      {
        "partOfSpeech": "noun",
        "definitions": [
          {
            "definition": "A mechanical device intended to make a task easier.",
            "synonyms": [],
            "antonyms": [],
            "example": "Hand me that tool, would you?   I don't have the right tools to start fiddling around with the engine."
          },
...
```

The app has a Dart function that does the look up:

dart

```
// Look up the metadata for a word in the dictionary API.
Future<Map<String, dynamic>> _getWordMetadataFromApi(String word) async {
  final url = Uri.parse(
    'https://api.dictionaryapi.dev/api/v2/entries/en/${Uri.encodeComponent(word)}',
  );
​
  final response = await http.get(url);
  return response.statusCode == 200
      ? {'result': jsonDecode(response.body)}
      : {'error': 'Could not find a definition for "$word".'};
}
```

The model is configured with the look up function as part of initialization:

dart

```
// The model for solving clues.
_clueSolverModel = FirebaseAI.googleAI().generativeModel(
  model: 'gemini-2.5-flash',
  systemInstruction: Content.text(clueSolverSystemInstruction),
  tools: [
    Tool.functionDeclarations([
      FunctionDeclaration(
        'getWordMetadata',
        'Gets grammatical metadata for a word, like its part of speech. '
        'Best used to verify a candidate answer against a clue that implies a '
        'grammatical constraint.',
        parameters: {
           'word': Schema(SchemaType.string, description: 'The word to look up.'),
         },
       ),
    ]),
  ],
);
```

For reliability, it's also a good idea to list the tools in the system instruction:

dart

````
static String get clueSolverSystemInstruction =>
    '''
You are an expert crossword puzzle solver.
​
...
​
### Tool: `getWordMetadata`
​
You have a tool to get grammatical information about a word.
​
**When to use:**
- This tool is most helpful as a verification step after you have a likely answer.
- Consider using this tool when a clue contains a grammatical hint that could be ambiguous.
- **Good candidates for verification:**
  - Clues that seem to be verbs (e.g., "To run," "Waving").
  - Clues that are adverbs (e.g., "Happily," "Quickly").
  - Clues that specify a plural form.
- **Try to avoid using the tool for:**
  - Simple definitions (e.g., "A small dog").
  - Fill-in-the-blank clues (e.g., "___ and flow").
  - Proper nouns (e.g., "Capital of France").
​
**Function signature:**
```json
${jsonEncode(_getWordMetadataFunction.toJson())}
```
''';
````

When the app makes a request, the model now has a tool to use when it decides that it will be helpful. To support tool calls, we need to implement an agentic loop.

## The Agentic Loop

[#](#the-agentic-loop)

An LLM is functionally stateless, which means that you have to give it all of the data it needs with every request. For a request that's just the prompt and any files you want to send along, the Firebase AI Logic SDK exposes the `generateContent` method on your model instance.

However, tool calling requires a history of messages that form the initial prompt, as well as the response/request pairs that make up tool calls and tool results. To support this Firebase Logic AI provides a "chat" object to gather the history. We use it to build the agentic loop:

* Start a chat to hold the message history across multiple request/response pairs
* Gather the tool results for any tool calls it provides
* Bundle the tool results into a new request
* Loop until the model provides a response without tool calls
* Return the text accumulated across all responses

Here's that algorithm expressed as an extension method on the `GenerativeModel` class so we can call it just like we call `generateContent`:

dart

```
extension on GenerativeModel {
  Future<String> generateContentWithFunctions({
    required String prompt,
    required Future<Map<String, dynamic>> Function(FunctionCall) onFunctionCall,
  }) async {
    // Use a chat session to support multiple request/response pairs, which is
    // needed to support function calls.
    final chat = startChat();
    final buffer = StringBuffer();
    var response = await chat.sendMessage(Content.text(prompt));
​
    while (true) {
      // Append the response text to the buffer.
      buffer.write(response.text ?? '');
​
      // If no function calls were collected, we're done
      if (response.functionCalls.isEmpty) break;
​
      // Append a newline to separate responses.
      buffer.write('\n');
​
      // Execute all function calls
      final functionResponses = <FunctionResponse>[];
      for (final functionCall in response.functionCalls) {
        try {
          functionResponses.add(
            FunctionResponse(
              functionCall.name,
              await onFunctionCall(functionCall),
            ),
          );
        } catch (ex) {
          functionResponses.add(
            FunctionResponse(functionCall.name, {'error': ex.toString()}),
          );
        }
      }
​
      // Get the next response stream with function results
      response = await chat.sendMessage(
        Content.functionResponses(functionResponses),
      );
    }
​
    return buffer.toString();
  }
}
```

This method takes a prompt and a callback for handling the specific tool calls, which the sample calls to handle the word lookup function:

dart

```
await _clueSolverModel.generateContentWithFunctions(
  prompt: getSolverPrompt(clue, length, pattern),
  onFunctionCall: (functionCall) async => switch (functionCall.name) {
    'getWordMetadata' => await _getWordMetadataFromApi(
      functionCall.args['word'] as String,
    ),
    _ => throw Exception('Unknown function call: ${functionCall.name}'),
  },
);
```

Structured output makes an LLM useful to program against but it's the tools that turn an LLM into an "agent" (more on this in the Mode of interaction section).

### Structured output and tool calls

[#](#structured-output-and-tool-calls)

Combining structured output and tool calls produce a powerful combination. In the sample, the clue solver has a tool to look up word details. It's also asked to return JSON that bundles the solution with a confidence score, both of which are shown in the app's task list:

Unfortunately, as of this writing, combining structured output and functions when using the Firebase AI Logic SDK produces an exception:

```
Function calling with a response mime type: 'application/json' is unsupported
```

As a (hopefully temporary) work-around to this issue, the sample removes the structured output configuration, instead using a tool called `returnResult` to simulate structured output:

dart

```
 // The model for solving clues.
_clueSolverModel = FirebaseAI.googleAI().generativeModel(
  model: 'gemini-2.5-flash',
  systemInstruction: Content.text(clueSolverSystemInstruction),
  tools: [
    Tool.functionDeclarations([
      ...,
      FunctionDeclaration(
        'returnResult',
        'Returns the final result of the clue solving process.',
        parameters: {
        'answer': Schema(
          SchemaType.string,
          description: 'The answer to the clue.',
        ),
        'confidence': Schema(
          SchemaType.number,
          description: 'The confidence score in the answer from 0.0 to 1.0.',
          ),
        },
      ),
    ]),
  ],
);
```

The `returnResult` method is also mentioned in the system instruction:

dart

````
static String get clueSolverSystemInstruction =>
    '''
You are an expert crossword puzzle solver.
​
...
​
### Tool: `returnResult`
​
You have a tool to return the final result of the clue solving process.
​
**When to use:**
- Use this tool when you have a final answer and confidence score to return. You
must use this tool exactly once, and only once, to return the final result.
​
**Function signature:**
```json
${jsonEncode(_returnResultFunction.toJson())}
```
''';
````

When the model calls `returnResult`, the sample caches the result, which the `solveClue` looks up after calling `generateContentWithFunctions`:

dart

```
// Buffer for the result of the clue solving process.
final _returnResult = <String, dynamic>{};
​
// Cache the return result of the clue solving process via a function call.
// This is how we get JSON responses from the model with functions, since the
// model cannot return JSON directly when tools are used.
Map<String, dynamic> _cacheReturnResult(Map<String, dynamic> returnResult) {
  assert(_returnResult.isEmpty);
  _returnResult.addAll(returnResult);
  return {'status': 'success'};
}
​
Future<ClueAnswer?> solveClue(Clue clue, int length, String pattern) async {
  // Clear the return result cache; this is where the result will be stored.
  _returnResult.clear();
​
  // Generate JSON response with functions and schema.
  await _clueSolverModel.generateContentWithFunctions(
    prompt: getSolverPrompt(clue, length, pattern),
    onFunctionCall: (functionCall) async => switch (functionCall.name) {
      'getWordMetadata' => ...,
      'returnResult' => _cacheReturnResult(functionCall.args),
      _ => throw Exception('Unknown function call: ${functionCall.name}'),
    },
  );
​
  // Use the structured output that the LLM has called function with
  assert(_returnResult.isNotEmpty);
  return ClueAnswer(
    answer: _returnResult['answer'] as String,
    confidence: (_returnResult['confidence'] as num).toDouble(),
  );
}
```

We have to work a little harder to get the combo of structured output and tool calls using Firebase AI Logic, but the results are worth it!

### Human in the loop

[#](#human-in-the-loop)

So far, we've seen tools used for gathering data and formatting output. We can also use them to get a human involved.

As an example, sometimes when the sample will pass in a pattern the solution should take – like "\_R\_Y" – the model wants to suggest an answer that doesn't fit this pattern – like "RENT". A conflict like this is a good time to ask for help from the user:\
\
This is called putting the "human in the loop" and it's yet another way for humans and LLMs to collaborate. Flutter and the Firebase AI Logic SDK make this easy to do. First, the sample defines a function and configures the model:

dart

````
​
// The new function to let the LLM resolve solution conflicts
static final _resolveConflictFunction = FunctionDeclaration(
  'resolveConflict',
  'Asks the user to resolve a conflict between the letter pattern and the '
  'proposed answer. Use this BEFORE calling returnResult if the answer you '
  'want to propose does not match the letter pattern.',
  parameters: {
    'proposedAnswer': Schema(
      SchemaType.string,
      description: 'The answer the LLM wants to suggest.',
    ),
    'pattern': Schema(
      SchemaType.string,
      description: 'The current letter pattern from the grid.',
    ),
    'clue': Schema(SchemaType.string, description: 'The clue text.'),
  },
);
​
// Pass the new tool to the model for solving clues.
final _clueSolverModel = FirebaseAI.googleAI().generativeModel(
  model: 'gemini-2.5-flash',
  systemInstruction: Content.text(clueSolverSystemInstruction),
  tools: [
    Tool.functionDeclarations([
      ...
      _resolveConflictFunction,
    ]),
  ],
);
// Let the LLM know that it has a new tool.
static String get clueSolverSystemInstruction =>
    '''
You are an expert crossword puzzle solver.
​
...
​
### Tool: `resolveConflict`
​
You have a tool to ask the user to resolve a conflict.
​
**When to use:**
- Use this tool **BEFORE** `returnResult` if your proposed answer conflicts with the provided letter pattern.
- For example, if the pattern is `_ R _ Y` and you want to suggest `RENT` (which fits the clue), there is a conflict at the second letter (`R` vs `E`). You should call `resolveConflict(proposedAnswer: "RENT", pattern: "_ R _ Y", clue: "...")`.
- The tool will return the user's decision (either your proposed answer or a new one). You should then use that result to call `returnResult`.
​
**Function signature:**
```json
${jsonEncode(_resolveConflictFunction.toJson())}
```
''';
````

Now when the model sees a conflict, it will call the tool:

dart

```
// handle the LLM's request to resolve the conflict
await _clueSolverModel.generateContentWithFunctions(
  prompt: getSolverPrompt(clue, length, pattern),
  onFunctionCall: (functionCall) async => switch (functionCall.name) {
    ...
    'resolveConflict' => await _handleResolveConflict(
      functionCall.args,
      onConflict,
    ),
  },
);
​
// Show the dialog to gather the user's input
Future<Map<String, dynamic>> _handleResolveConflict(
  Map<String, dynamic> args,
  Future<String> Function(String clue, String proposedAnswer, String pattern)?
  onConflict,
) async {
  final proposedAnswer = args['proposedAnswer'] as String;
  final pattern = args['pattern'] as String;
  final clue = args['clue'] as String;
​
  if (onConflict != null) {
    final result = await onConflict(clue, proposedAnswer, pattern);
    return {'result': result};
  }
​
  return {'result': proposedAnswer};
}
```

The sample handles the tool with an implementation of the `onConflict` method that calls `showDialog` to gather data from the user. This all happens in the middle of the agentic loop, but that's OK – the model is not waiting; it's already sent back its response to the app's initial request. The user can take their time with the UI while the sample waits on the `Future` returned `showDialog`. When they're done, the model continues where it left off using the message history and the most recent request, which in this case happens to be data gathered interactively from the user.

A modal dialog box is a simple way to put the human in the loop but is not the only way in Flutter to do so. If you'd prefer, an instance of [a `Completer`](https://api.flutter.dev/flutter/dart-async/Completer-class.html) lets you set some state in your app that puts it into "gathering data from the user" mode. When the app has the data, it can call `complete` on the `Completer` and resume the agentic loop.

Or, since you own the agentic loop, you can check for a call to a "special" function that indicates that you need to gather data from the user. This kind of special function is sometimes called an "interrupt" and you "resume" the conversation with the model when you have the data from the user.

Remember that the LLM is stateless. It's not waiting on you, so you can handle the agentic loop in whatever way makes the most sense for your app. You can come back to the LLM with an updated message history and a new prompt at any time, whether it's been a minute or in a month.

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-08-19. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/ai/best-practices/tool-calls.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/ai/best-practices/tool-calls\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/ai/best-practices/tool-calls.md "Report an issue with this page").

----
url: https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.42.3
----

The 2.42.3 release of the Dart and Flutter DevTools includes the following changes among other general improvements. To learn more about DevTools, check out the [DevTools overview](/tools/devtools/overview).

## General updates

[#](#general-updates)

* Added "View licenses" shortcut to the About dialog. - [#8610](https://github.com/flutter/devtools/pull/8610)

* Lower the wasm optimization level to resolve crashes on the dart2wasm build. - [#8814](https://github.com/flutter/devtools/pull/8814)

## Inspector updates

[#](#inspector-updates)

* Enabled the new inspector by default. This can be disabled in the inspector settings. - [#8650](https://github.com/flutter/devtools/pull/8650)
* Fixed an issue where selecting an implementation widget on the device while implementation widgets were hidden in the new inspector showed an error. - [#8625](https://github.com/flutter/devtools/pull/8625)
* Enabled auto-refreshes of the widget tree on hot-reloads and navigation events by default. This can be disabled in the inspector settings. - [#8646](https://github.com/flutter/devtools/pull/8646)

## Network profiler updates

[#](#network-profiler-updates)

* Fixed an issue where the HTTP requests would sometimes not be displayed properly, particularly when DevTools is communicating with an application over a slow network connection. - [#8860](https://github.com/flutter/devtools/pull/8860)

## Full commit history

[#](#full-commit-history)

To find a complete list of changes in this release, check out the [DevTools git log](https://github.com/flutter/devtools/tree/v2.42.3).

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/release-notes/release-notes-2.42.3.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.42.3\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/release-notes/release-notes-2.42.3.md "Report an issue with this page").

----
url: https://docs.flutter.dev/resources/glossary
----

The following are definitions of terms used across the Flutter documentation.

search

## Adaptive

[tag](#adaptive "Link to card")

A design concept where the UI adapts to be usable in the available space, often changing layout or input methods based on device capabilities.

Adaptive design is about the UI being *usable* in the space, as opposed to responsive design which is about fitting the UI *into* the space. An adaptive app selects the appropriate layout (such as having a bottom nav instead of a side panel) and input devices (for example, mouse versus touch) to feel natural on the current device.

### Related docs and resources

* [articleAdaptive vs responsive design](/ui/adaptive-responsive#what-is-responsive-vs-adaptive)
* [articleBuilding adaptive apps](/ui/adaptive-responsive)

## Agent skill

[tag](#agent-skill "Link to card")

A specialized capability or tool that an AI agent uses for a specific task.

An agent skill is a specialized set of instructions, scripts, and resources that extend an AI agent's capabilities for a specific domain or task. Skills enable agents to interact with the environment, execute multi-step workflows, and perform complex problem-solving autonomously.

## Cupertino

[tag](#cupertino "Link to card")

Flutter's implementation of the iOS design language.

Flutter's `cupertino_ui` library implements the iOS design language, comprising a set of widgets that implement Apple's Human Interface Guidelines.

The Cupertino library, originally part of the main Flutter repo, has been decoupled into a separate package, `cupertino_ui` on pub.dev.

### Related docs and resources

* [open\_in\_newHuman Interface Guidelines](https://developer.apple.com/design/human-interface-guidelines/)
* [articleCupertino widgets](/ui/widgets/cupertino)
* [open\_in\_newcupertino\_ui package](https://pub.dev//packages/cupertino_ui)
* [articleMaterial and Cupertino decoupling are here](https://blog.flutter.dev/decoupling-material-cupertino)
* [articleMaterial and Cupertino decoupling guide](/release/breaking-changes/material-ui-and-cupertino-ui)
* [open\_in\_newpub.dev](https://pub.dev)

## Dart

[tag](#dart "Link to card")

A programming language for fast apps on any platform.

Dart is an approachable, portable, and performant language designed for full-stack app development. It offers sound null safety, a strong type system, and compiles to native machine code for mobile, desktop, and backend, as well as JavaScript or WebAssembly for the web. While Dart is the foundation of Flutter, it is also used for building command-line tools, servers, and other applications.

### Related docs and resources

* [open\_in\_newDart language site](https://dart.dev)

## Declarative

[tag](#declarative "Link to card")

A programming style where the UI state is described, and the framework transitions the UI to match that state.

Declarative programming is a style where you describe the *current state* of your UI, and the framework takes care of transitioning the UI to match that state.

In Flutter, widgets are immutable "blueprints". To change the UI, a widget triggers a rebuild on itself (usually by calling `setState`) and constructs a new widget subtree. This contrasts with *imperative* programming, where you manually construct and mutate UI entities.

### Related docs and resources

* [articleIntroduction to declarative UI](/get-started/flutter-for/declarative)
* [articleStart thinking declaratively](/data-and-backend/state-mgmt/declarative)

## Embedder

[tag](#embedder "Link to card")

The platform-specific component that supports Flutter on a native platform.

Each native platform supported by Flutter has an *embedder* for platform-specific logic. The embedder is the bridge that coordinates with the underlying operating system. It provides access to services like input, accessibility, message event loops, and more. The embedder also launches and manages the Flutter engine.

Each embedder is written in the platform's native language: Java and Kotlin for Android, Swift and Objective-C for iOS and macOS, and C++ for Windows and Linux.

Each embedder enables plugin packages to add additional platform-specific functionality to the app.

The embedder is launched and managed by the runner app.

### Related docs and resources

* [articleArchitectural overview: The Embedder](/resources/architectural-overview#platform-embedding)
* [articleFlutter on embedded devices](/embedded)

## Engine

[tag](#engine "Link to card")

The portable runtime for Flutter apps.

The engine is Flutter's platform-agnostic logic that's written in native code, mostly C++.

The main responsibilities of the engine are as follows:

1. Exposes the `dart:ui` API, which are the low-level primitives that the Flutter [framework](/resources/architectural-overview#architectural-layers) builds upon.
2. Converts low-level drawing commands into pixels (also called *rasterization*, this includes [Impeller](/resources/glossary#impeller)ImpellerFlutter's modern graphics rendering engine, designed for smooth, predictable performance. [Learn more](/resources/glossary#impeller "Learn more about 'Impeller' and find related resources.") and Skia).
3. Responsible for launching and managing Dart's runtime.
4. Responsible for laying out text.
5. Responsible for asset resolution.

### Related docs and resources

* [articleArchitectural overview: The Engine](/resources/architectural-overview#architectural-layers)
* [code\_blocksEngine repository](https://github.com/flutter/flutter/tree/main/engine/src/flutter)

## Frame

[tag](#frame "Link to card")

A single image in a sequence of images that makes up an animation or UI update.

Flutter aims to produce 60 frames per second (fps), or 120 fps on capable devices. This means the framework has approximately 16ms (at 60 fps) or 8ms (at 120 fps) to render each frame. If the app takes longer than this to render a frame, the user might see [jank](/resources/glossary#jank)JankWhen an app appears to stutter or jerk visually instead of animating smoothly. [Learn more](/resources/glossary#jank "Learn more about 'Jank' and find related resources.").

### Related docs and resources

* [articleRendering performance](/perf/rendering-performance)

## Hot reload

[tag](#hot-reload "Link to card")

A Flutter feature that allows you to inject updated code into a running application in the Dart VM and see the changes immediately while maintaining application state.

This feature is also called "stateful hot reload". After the Dart runtime updates classes with the new versions of fields and functions, the Flutter framework automatically rebuilds the widget tree, allowing you to quickly view the effects of your changes. Hot reload greatly increases the speed of development.

Hot reload works on mobile, web, and desktop apps that are running in debug mode and is fully supported in VS Code, Android Studio, and IntelliJ IDEA. It does not re-run `main` or `initState`; for that, use [hot restart](/resources/glossary#hot-restart)Hot restartSimilar to hot reload, but it does not maintain app state. Use hot restart to re-run \`main\` or \`initState\`. [Learn more](/resources/glossary#hot-restart "Learn more about 'Hot restart' and find related resources.").

### Related docs and resources

* [articleHot reload documentation](/tools/hot-reload)
* [play\_arrowFast development cycles with Flutter's hot reload](https://youtu.be/YScJS8obxlo?si=QxJDIf_LGmle2Xs6)
* [play\_arrowStateful hot reload for web is here](https://youtu.be/7nT3BHm6Gyg?si=nLUM0n69PSQnm8CF)

## Hot restart

[tag](#hot-restart "Link to card")

Similar to hot reload, but it does not maintain app state. Use hot restart to re-run `main` or `initState` .

Hot restart is still faster than a full restart, which also recompiles the native, platform code (such as Swift). On the web, it also restarts the Dart Development Compiler (DDC).

### Related docs and resources

* [articleDifference between hot reload, hot restart, and full restart](/tools/hot-reload#hot-restart)

## Impeller

[tag](#impeller "Link to card")

Flutter's modern graphics rendering engine, designed for smooth, predictable performance.

*Impeller* is Flutter's high-performance rendering engine, built from the ground up for Flutter's needs and modern graphics APIs.

Its primary goal is to provide consistently smooth performance and eliminate stuttering while rendering, particularly that caused by shader compilation during animations and interactions.

Impeller achieves this by pre-compiling a specific, smaller set of shaders at application build time, rather than compiling at runtime.

### Related docs and resources

* [articleImpeller documentation](/perf/impeller)
* [open\_in\_newImpeller FAQ](https://github.com/flutter/flutter/blob/main/docs/engine/impeller/docs/faq.md)

## Jank

[tag](#jank "Link to card")

When an app appears to stutter or jerk visually instead of animating smoothly.

Jank occurs when a system can't keep up with the expected frame rate and drops frames. Jank is a performance problem. Flutter offers information and tooling, such as the Performance tool in DevTools, that can help you diagnose and fix jank in your application.

### Related docs and resources

* [articleUse the Performance view in DevTools](/tools/devtools/performance)
* [articleImproving rendering performance](/perf/rendering-performance)
* [articlePerformance best practices](/perf/best-practices)
* [articleMeasure performance with an integration test](/cookbook/testing/integration/profiling)

## Material

[tag](#material "Link to card")

An open-source design system built and supported by Google.

Material Design is an adaptable system of guidelines, components, and tools that support the best practices of user interface design. Flutter's `material_ui` library implements Material Design widgets.

The Material library, originally part of the main Flutter repo, has been decoupled into a separate package, `material_ui` on pub.dev.

### Related docs and resources

* [open\_in\_newMaterial Design site](https://m3.material.io)
* [articleMaterial widgets](/ui/widgets/material)
* [open\_in\_newmaterial\_ui package](https://pub.dev/packages/material_ui)
* [articleMaterial and Cupertino decoupling are here](https://blog.flutter.dev/decoupling-material-cupertino)
* [articleMaterial and Cupertino decoupling guide](/release/breaking-changes/material-ui-and-cupertino-ui)
* [open\_in\_newpub.dev](https://pub.dev)

## Null safety

[tag](#null-safety "Link to card")

A Dart feature that prevents errors that result from accessing variables set to `null`.

Dart's null safety prevents errors that result from unintentional access of variables set to `null`.

With [sound null safety](https://dart.dev/null-safety), variables are non-nullable by default: they can only be assigned a value of `null` if you explicitly declare them as nullable. This differs from other "mixed" null safety implementations, where a non-nullable variable could still contain `null` at runtime. With Dart's sound null safety, the compiler guarantees that a non-nullable variable can never be `null`.

### Related docs and resources

* [open\_in\_newSound null safety](https://dart.dev/null-safety)
* [schoolNull safety codelab](https://dart.dev/codelabs/null-safety)

## Package

[tag](#package "Link to card")

At a minimum, a Dart package is a directory containing a `pubspec.yaml` file.

Additionally, a package can contain dependencies (listed in the pubspec), Dart libraries, apps, resources, tests, images, fonts, and examples. The pub.dev site lists many packages—developed by Google engineers and generous members of the Flutter and Dart community—that you can use in your app.

### Related docs and resources

* [play\_arrowPackages v Plugins](https://youtu.be/Y9WifT8aN6o)
* [articlePackage dependency management](/packages-and-plugins/dependency-management)
* [articlePackage versioning guide](https://dart.dev/tools/pub/versioning)
* [open\_in\_newpub.dev](https://pub.dev)

## Plugin

[tag](#plugin "Link to card")

A plugin package (generally referred to as *a plugin* ), is a special kind of package that makes platform functionallity available to the app.

Plugin packages can be written for Android (using Kotlin or Java), iOS (using Swift or Objective-C), web, macOS, Windows, Linux, or any combination thereof. For example, a plugin might provide Flutter apps with the ability to use a device's camera.

### Related docs and resources

* [play\_arrowPackages v Plugins](https://youtu.be/Y9WifT8aN6o)

## Prop drilling

[tag](#prop-drilling "Link to card")

The process of passing data through multiple layers of widgets through constructor parameters.

The process of passing data through multiple layers of widgets through constructor parameters, usually to reach a deeper descendant. This pattern can become verbose, which is why other state management solutions (like `InheritedWidget` or `Provider`) are often used.

### Related docs and resources

* [articleState management introduction](/data-and-backend/state-mgmt/intro)

## pub

[tag](#pub "Link to card")

The package manager for the Dart programming language.

Pub is the tool used for managing Dart packages. It allows you to install, upgrade, and manage dependencies for your Dart app. Dependencies are defined in the [`pubspec.yaml`](https://dart.dev/tools/pub/pubspec) file. Packages are hosted on pub.dev, the official package repository.

### Related docs and resources

* [open\_in\_newpub.dev](https://pub.dev)
* [articleUsing packages](/packages-and-plugins/using-packages)

## RenderObject

[tag](#renderobject "Link to card")

A persistent object in the Flutter framework responsible for layout, painting, and hit testing.

While widgets are immutable blueprints, `RenderObject`s are mutable objects that persist between frames. They handle the heavy lifting of determining exactly where elements should be placed and how they should look.

They represent a node in the render tree, which is the most detailed tree in Flutter's multi-tree architecture.

### Related docs and resources

* [articleArchitectural Overview: The Rendering Layer](/resources/architectural-overview#architectural-layers)
* [descriptionRenderObject API documentation](https://api.flutter.dev/flutter/rendering/RenderObject-class.html)

## Sliver

[tag](#sliver "Link to card")

A customizable portion of a scrollable area.

A sliver is a portion of a scrollable area that you can define to behave in a special way. Think of slivers as building blocks that you can compose together inside a `CustomScrollView` to create custom scrolling experiences, like elastic scrolling or a collapsing header. Slivers are built lazily, which means that Flutter only renders the slivers that are visible on screen, making them very efficient for long lists of content.

### Related docs and resources

* [articleSliver documentation](/ui/layout/scrolling/slivers)
* [articleSlivers demystified](https://blog.flutter.dev/slivers-demystified-6ff68ab0296f)
* [play\_arrowSliverList and SliverGrid WotW](https://youtu.be/ORiTTaVY6mM)
* [play\_arrowSliverAppBar WotW](https://youtu.be/R9C5KMJKluE)
* [descriptionCustomScrollView class](https://api.flutter.dev/flutter/widgets/CustomScrollView-class.html)
* [descriptionSliverAppBar class](https://api.flutter.dev/flutter/material/SliverAppBar-class.html)
* [descriptionSliverGrid class](https://api.flutter.dev/flutter/widgets/SliverGrid-class.html)
* [descriptionSliverList class](https://api.flutter.dev/flutter/widgets/SliverList-class.html)

## Viewport

[tag](#viewport "Link to card")

A widget that displays a subset of its children based on the current scroll offset.

A viewport is the visual component of the scrolling machinery. It displays a subset of its children (usually slivers) based on the current scroll offset. It is often described as being "bigger on the inside" because it can contain more content than is visible on the screen.

### Related docs and resources

* [descriptionViewport class](https://api.flutter.dev/flutter/widgets/Viewport-class.html)
* [articleSlivers](/ui/layout/scrolling/slivers)

## Widget

[tag](#widget "Link to card")

The basic building block of a Flutter user interface.

An immutable description of part of a user interface.

In Flutter, almost everything is a *widget*. Widgets are the fundamental building blocks you use to create your application's UI with Flutter. Each widget is an immutable declaration of \_what the UI should look like based on its current configuration and state.

Widgets are composed together in a hierarchy to form the widget tree. When a widget's state changes, the Flutter framework rebuilds the necessary parts of the tree to update the UI.

The two primary types of widgets are [`StatelessWidget`](https://api.flutter.dev/flutter/widgets/StatelessWidget-class.html), which have no mutable state, and [`StatefulWidget`](https://api.flutter.dev/flutter/widgets/StatefulWidget-class.html), which have a persistent [state](https://api.flutter.dev/flutter/widgets/State-class.html) that can be updated.

### Related docs and resources

* [articleWidget fundamentals](/learn/pathway/tutorial/widget-fundamentals)
* [articleWidget catalog](/ui/widgets)
* [descriptionWidget class](https://api.flutter.dev/flutter/widgets/Widget-class.html)

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-09-29. [Report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/resources/glossary "Report an issue with this page").

----
url: https://docs.flutter.dev/platform-integration/android/predictive-back
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Add the predictive back gesture](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

The Android predictive back gesture lets users preview where a back gesture will navigate to, whether that's the previous screen or the home screen, before they commit to or cancel it.

## Overview

[#](#overview)

Starting with Android 14 (API level 34), predictive back animations are enabled by default for system gestures when supported by the application. Flutter provides built-in support for predictive back animations across default page route transitions and custom pop handling.

## Enable predictive back in Android

[#](#enable-predictive-back-in-android)

To support predictive back gestures in your Flutter app on Android 13 or later:

1. Open `android/app/src/main/AndroidManifest.xml`.
2. Add `android:enableOnBackInvokedCallback="true"` to the `<application>` tag:

xml

```
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    <application
        android:label="my_app"
        android:name="${applicationName}"
        android:icon="@mipmap/ic_launcher"
        android:enableOnBackInvokedCallback="true">
        <!-- ... -->
    </application>
</manifest>
```

## Handle back gestures with PopScope

[#](#handle-back-gestures-with-popscope)

To customize back navigation or prevent users from accidentally leaving a screen, use the [`PopScope`](https://api.flutter.dev/flutter/widgets/PopScope-class.html) widget. `PopScope` replaces the deprecated `WillPopScope` widget and supports predictive back gestures.

### Callback parameters

[#](#callback-parameters)

`PopScope` uses the `onPopInvokedWithResult` callback:

* `didPop`: A boolean indicating whether the pop operation succeeded. If `canPop` is `false`, `didPop` is `false`.
* `result`: An optional return payload passed when popping the route (for example, with `Navigator.pop(context, result)`).

### Example: Intercepting back navigation

[#](#example-intercepting-back-navigation)

dart

```
PopScope(
  canPop: false,
  onPopInvokedWithResult: (bool didPop, Object? result) async {
    if (didPop) {
      return;
    }
    final shouldLeave = await _showExitConfirmationDialog(context);
    if (shouldLeave && context.mounted) {
      Navigator.of(context).pop(result);
    }
  },
  child: Scaffold(
    appBar: AppBar(title: const Text('Form Screen')),
    body: const Center(child: Text('Complete the form before leaving.')),
  ),
)
```

## More information

[#](#more-information)

For more details on API migrations, see the [Android predictive back migration guide](/release/breaking-changes/android-predictive-back).

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-08-18. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/platform-integration/android/predictive-back.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/platform-integration/android/predictive-back\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/platform-integration/android/predictive-back.md "Report an issue with this page").

----
url: https://docs.flutter.dev/packages-and-plugins/background-processes
----

Have you ever wanted to execute Dart code in the background—even if your app wasn't the currently active app? Perhaps you wanted to implement a process that watches the time, or that catches camera movement. In Flutter, you can execute Dart code in the background.

The mechanism for this feature involves setting up an isolate. *Isolates* are Dart's model for multithreading, though an isolate differs from a conventional thread in that it doesn't share memory with the main program. You'll set up your isolate for background execution using callbacks and a callback dispatcher.

Additionally, the [WorkManager](https://pub.dev/packages/workmanager) plugin enables persistent background processing that keeps tasks scheduled through app restarts and system reboots.

For more information and a geofencing example that uses background execution of Dart code, see the Flutter blog post by Ben Konyi, [Executing Dart in the Background with Flutter Plugins and Geofencing](https://blog.flutter.dev/executing-dart-in-the-background-with-flutter-plugins-and-geofencing-2b3e40a1a124). At the end of this article, you'll find links to example code, and relevant documentation for Dart, iOS, and Android.

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-07-29. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/packages-and-plugins/background-processes.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/packages-and-plugins/background-processes\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/packages-and-plugins/background-processes.md "Report an issue with this page").

----
url: https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.18.0
----

The 2.18.0 release of the Dart and Flutter DevTools includes the following changes among other general improvements. To learn more about DevTools, check out the [DevTools overview](https://docs.flutter.dev/tools/devtools).

## Inspector updates

[#](#inspector-updates)

* Auto scrolling behavior improved when snapping a widget into focus - [#4283](https://github.com/flutter/devtools/pull/4283)
* Fix issue where widget inspector wouldn't load when connecting to a paused app - [#4527](https://github.com/flutter/devtools/pull/4527)
* Improve widget inspector hover cards to show progress while waiting for data - [#4488](https://github.com/flutter/devtools/pull/4488)

## Performance updates

[#](#performance-updates)

* Fix issue where scrollbar would go out of sync with the frame content - [#4503](https://github.com/flutter/devtools/pull/4503)

* Add offline support for raster stats - [#4491](https://github.com/flutter/devtools/pull/4491)

* Add 'Rendering time' column to Raster Metrics tab - [#4474](https://github.com/flutter/devtools/pull/4474)

## CPU profiler updates

[#](#cpu-profiler-updates)

* Fix crash when an empty frame is filtered - [#4502](https://github.com/flutter/devtools/pull/4502)
* Fix bugs in CPU profile trees - [#4413](https://github.com/flutter/devtools/pull/4413)
* UI Cleanup - [#4404](https://github.com/flutter/devtools/pull/4404)

## Memory updates

[#](#memory-updates)

* Add Profile and Allocation Tracing sub-tabs - [#4523](https://github.com/flutter/devtools/pull/4523)

* Implement snapshot visualization - [#4473](https://github.com/flutter/devtools/pull/4473)

## Debugger updates

[#](#debugger-updates)

* Fix bug for file opener and search - [#4525](https://github.com/flutter/devtools/pull/4525)
* Fix the code view's scrollable area - [#4448](https://github.com/flutter/devtools/pull/4448)
* Allow syntax highlighting on nested captures in parser - [#4427](https://github.com/flutter/devtools/pull/4427)

## Network profiler updates

[#](#network-profiler-updates)

* When on the Network tab, network recordings now continue working after the app hot restarts - [#4438](https://github.com/flutter/devtools/pull/4438)

## Logging updates

[#](#logging-updates)

* Log messages from non-stdout sources are now shown - [#4487](https://github.com/flutter/devtools/pull/4487)

## Full commit history

[#](#full-commit-history)

To find a complete list of changes since the previous release, check out [the diff on GitHub](https://github.com/flutter/devtools/compare/v2.17.0...v2.18.0).

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/release-notes/release-notes-2.18.0.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.18.0\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/release-notes/release-notes-2.18.0.md "Report an issue with this page").

----
url: https://docs.flutter.dev/cookbook/navigation/named-routes.md
----

\# Navigate with named routes > How to implement named routes for navigating between screens.

<!--?code-excerpt path-base="cookbook/navigation/named_routes"?-->

:::note Named routes are no longer recommended for most applications. For more information, see \[Limitations]\[] in the \[navigation overview]\[] page. ::: \[Limitations]: /ui/navigation#limitations \[navigation overview]: /ui/navigation In the \[Navigate to a new screen and back]\[] recipe, you learned how to navigate to a new screen by creating a new route and pushing it to the \[\`Navigator\`]\[]. However, if you need to navigate to the same screen in many parts of your app, this approach can result in code duplication. The solution is to define a \_named route\_, and use the named route for navigation. To work with named routes, use the \[\`Navigator.pushNamed()\`]\[] function. This example replicates the functionality from the original recipe, demonstrating how to use named routes using the following steps: 1. Create two screens. 2. Define the routes. 3. Navigate to the second screen using \`Navigator.pushNamed()\`. 4. Return to the first screen using \`Navigator.pop()\`. ## 1. Create two screens 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.

<!--?code-excerpt "lib/main_original.dart"?-->

\`\`\`dart import 'package:flutter/material.dart'; class FirstScreen extends StatelessWidget { const FirstScreen({super.key}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('First Screen')), body: Center( child: ElevatedButton( onPressed: () { // Navigate to the second screen when tapped. }, child: const Text('Launch screen'), ), ), ); } } class SecondScreen extends StatelessWidget { const SecondScreen({super.key}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('Second Screen')), body: Center( child: ElevatedButton( onPressed: () { // Navigate back to first screen when tapped. }, child: const Text('Go back!'), ), ), ); } } \`\`\` ## 2. Define the routes Next, define the routes by providing additional properties to the \[\`MaterialApp\`]\[] constructor: the \`initialRoute\` and the \`routes\` themselves. The \`initialRoute\` property defines which route the app should start with. The \`routes\` property defines the available named routes and the widgets to build when navigating to those routes.

<!--?code-excerpt "lib/main.dart (MaterialApp)" replace="/^\),$/)/g"?-->

\`\`\`dart MaterialApp( title: 'Named Routes Demo', // Start the app with the "/" named route. In this case, the app starts // on the FirstScreen widget. initialRoute: '/', routes: { // When navigating to the "/" route, build the FirstScreen widget. '/': (context) => const FirstScreen(), // When navigating to the "/second" route, build the SecondScreen widget. '/second': (context) => const SecondScreen(), }, ) \`\`\` :::warning When using \`initialRoute\`, \*\*don't\*\* define a \`home\` property. ::: ## 3. Navigate to the second screen With the widgets and routes in place, trigger navigation by using the \[\`Navigator.pushNamed()\`]\[] method. This tells Flutter to build the widget defined in the \`routes\` table and launch the screen. In the \`build()\` method of the \`FirstScreen\` widget, update the \`onPressed()\` callback:

<!--?code-excerpt "lib/main.dart (PushNamed)" replace="/,$//g"?-->

\`\`\`dart // Within the \`FirstScreen\` widget onPressed: () { // Navigate to the second screen using a named route. Navigator.pushNamed(context, '/second'); } \`\`\` ## 4. Return to the first screen To navigate back to the first screen, use the \[\`Navigator.pop()\`]\[] function.

<!--?code-excerpt "lib/main.dart (Pop)" replace="/,$//g"?-->

\`\`\`dart // Within the SecondScreen widget onPressed: () { // Navigate back to the first screen by popping the current route // off the stack. Navigator.pop(context); } \`\`\` ## Interactive example

<!--?code-excerpt "lib/main.dart"?-->

\`\`\`dartpad title="Flutter Named Routes hands-on example in DartPad" run="true" import 'package:flutter/material.dart'; void main() { runApp( MaterialApp( title: 'Named Routes Demo', // Start the app with the "/" named route. In this case, the app starts // on the FirstScreen widget. initialRoute: '/', routes: { // When navigating to the "/" route, build the FirstScreen widget. '/': (context) => const FirstScreen(), // When navigating to the "/second" route, build the SecondScreen widget. '/second': (context) => const SecondScreen(), }, ), ); } class FirstScreen extends StatelessWidget { const FirstScreen({super.key}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('First Screen')), body: Center( child: ElevatedButton( // Within the \`FirstScreen\` widget onPressed: () { // Navigate to the second screen using a named route. Navigator.pushNamed(context, '/second'); }, child: const Text('Launch screen'), ), ), ); } } class SecondScreen extends StatelessWidget { const SecondScreen({super.key}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('Second Screen')), body: Center( child: ElevatedButton( // Within the SecondScreen widget onPressed: () { // Navigate back to the first screen by popping the current route // off the stack. Navigator.pop(context); }, child: const Text('Go back!'), ), ), ); } } \`\`\` \[\`MaterialApp\`]: https\://pub.dev/documentation/material\_ui/latest/material\_ui/MaterialApp-class.html \[Navigate to a new screen and back]: /cookbook/navigation/navigation-basics \[\`Navigator\`]: https\://api.flutter.dev/flutter/widgets/Navigator-class.html \[\`Navigator.pop()\`]: https\://api.flutter.dev/flutter/widgets/Navigator/pop.html \[\`Navigator.pushNamed()\`]: https\://api.flutter.dev/flutter/widgets/Navigator/pushNamed.html

----
url: https://docs.flutter.dev/packages-and-plugins/developing-packages.md
----

\# Developing packages & plugins > How to write packages and plugins for Flutter. ## Package introduction Packages enable the creation of modular code that can be shared easily. A minimal package consists of the following: \*\*\`pubspec.yaml\`\*\* : A metadata file that declares the package name, version, author, and so on. \*\*\`lib\`\*\* : The \`lib\` directory contains the public code in the package, minimally a single \`.dart\` file. :::note For a list of dos and don'ts when writing an effective plugin, see the Flutter blog post by Mehmet Fidanboylu, \[Writing a good plugin]\[]. ::: ### Package types {:#types} Packages can contain more than one kind of content: \*\*Dart packages\*\* : General packages written in Dart, for example the \[\`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\`]\[] package. \*\*Plugin packages\*\* : A specialized Dart package that contains an API written in Dart code combined with one or more platform-specific implementations. Plugin packages can be written for Android (using Kotlin or Java), iOS (using Swift or Objective-C), web, macOS, Windows, or Linux, or any combination thereof. A concrete example is the \[\`url\_launcher\`]\[] plugin package. To see how to use the \`url\_launcher\` package, and how it was extended to implement support for web, see the Flutter blog post by Harry Terkelsen, \[How to Write a Flutter Web Plugin, Part 1]\[]. \*\*FFI packages\*\* : A specialized Dart package that enables calling native code using \`dart:ffi\`. These packages work in Dart standalone and don't require OS-specific build files. They are created with the \`flutter create --template=package\_ffi\` command (see \[Create an FFI package]\[bind-native]). This is the recommended approach to build and bundle native code since Flutter 3.38. ## Developing Dart packages {:#dart} The following instructions explain how to write a Flutter package. ### Step 1: Create the package To create a starter Flutter package, use the \`--template=package\` flag with \`flutter create\`: \`\`\`console $ flutter create --template=package hello \`\`\` This creates a package project in the \`hello\` folder with the following content: \*\*LICENSE\*\* : A (mostly) empty license text file. \*\*test/hello\_test.dart\*\* : The \[unit tests]\[] for the package. \*\*hello.iml\*\* : A configuration file used by the IntelliJ IDEs. \*\*.gitignore\*\* : A hidden file that tells Git which files or folders to ignore in a project. \*\*.metadata\*\* : A hidden file used by IDEs to track the properties of the Flutter project. \*\*pubspec.yaml\*\* : A yaml file containing metadata that specifies the package's dependencies. Used by the pub tool. \*\*README.md\*\* : A starter markdown file that briefly describes the package's purpose. \*\*lib/hello.dart\*\* : A starter app containing Dart code for the package. \*\*.idea/modules.xml\*\*, \*\*.idea/workspace.xml\*\* : A hidden folder containing configuration files for the IntelliJ IDEs. \*\*CHANGELOG.md\*\* : A (mostly) empty markdown file for tracking version changes to the package. ### Step 2: Implement the package For pure Dart packages, simply add the functionality inside the main \`lib/.dart\` file, or in several files in the \`lib\` directory. To test the package, add \[unit tests]\[] in a \`test\` directory. For additional details on how to organize the package contents, see the \[Dart library package]\[] documentation. ## Developing plugin packages {:#plugin} If you want to develop a package that calls into platform-specific APIs, you need to develop a plugin package. The API is connected to the platform-specific implementation(s) using a \[platform channel]\[]. ### Supporting multiple Flutter instances All Flutter platforms allow apps to create \[multiple Flutter engines]\[]. Each engine creates its own instance of every registered plugin, and those plugin instances can have independent lifetimes. Don't assume that a plugin class has only one instance. Keep engine-specific state on the plugin instance rather than in global or static variables, and clean it up when the plugin detaches from its engine. If plugin instances share a native singleton or other global resource, coordinate access across the instances, for example by using reference counting. Release the resource only after every plugin instance has stopped using it. ### Federated plugins \*\*Federated plugins\*\* are a way of splitting the API of a plugin into a platform interface, independent platform implementations of that interface, and an app-facing interface that uses the registered implementation of the running platform. \*\*Package-separated federated plugins\*\* are federated plugins where the platform interface, platform implementations, and the app-facing interface are all separated into their own Dart packages. So, a package-separated federated plugin can use one package for iOS, another for Android, another for web, and yet another for a car (as an example of an IoT device). Among other benefits, this approach allows a domain expert to extend an existing plugin to work for the platform they know best. A federated plugin requires the following: \*\*app-facing interface\*\* : The interface that plugin users interact with when using the plugin. This interface specifies the API used by the Flutter app. In a package-separated federated plugin, this is the package that plugin users depend on to use the plugin. \*\*platform implementation(s)\*\* : One or more implementations that contain the platform-specific implementation code. The app-facing interface calls into these implementations—they aren't directly used, or depended on when package-separated, in an app unless they contain platform-specific functionality accessible to the end user. \*\*platform interface\*\* : The interface that glues the app-facing interface to the platform implementations(s). This declares an interface that any platform implementation must implement to support the app-facing interface. Having a separate package that defines this interface ensures that all platform packages implement the same functionality in a uniform way. #### Endorsed federated plugin Ideally, when adding a platform implementation to a packaged-separated federated plugin, you will coordinate with the package author to include your implementation. In this way, the original author \_endorses\_ your implementation. For example, say you write a \`foobar\_windows\` implementation for the (imaginary) \`foobar\` plugin. In an endorsed plugin, the original \`foobar\` author adds your Windows implementation as a dependency in the pubspec for the app-facing package. Then, when a developer includes the \`foobar\` plugin in their Flutter app, the Windows implementation, as well as the other endorsed implementations, are automatically available to the app. #### Non-endorsed federated plugin If you can't, for whatever reason, get your implementation added by the original plugin author, then your plugin is \_not\_ endorsed. A developer can still use your implementation, but must manually add the plugin to the app's \`pubspec.yaml\` file: \`\`\`yaml dependencies: foobar: ^1.0.0 foobar\_windows: ^1.0.0 # Non-endorsed plugin implementation \`\`\` This approach also works for overriding an already endorsed plugin implementation of \`foobar\`. For more information on federated plugins, why they are useful, and how they are implemented, see the Flutter blog post by Harry Terkelsen, \[How To Write a Flutter Web Plugin, Part 2]\[]. ### Specifying a plugin's supported platforms {:#plugin-platforms} Plugins can specify the platforms they support by adding keys to the \`platforms\` map in the \`pubspec.yaml\` file. For example, the following pubspec file shows the \`flutter:\` map for the \`hello\` plugin, which supports only iOS and Android: \`\`\`yaml flutter: plugin: platforms: android: package: com.example.hello pluginClass: HelloPlugin ios: pluginClass: HelloPlugin \`\`\` When adding plugin implementations for more platforms, the \`platforms\` map should be updated accordingly. For example, here's the map in the pubspec file for the \`hello\` plugin, when updated to add support for macOS and web: \`\`\`yaml flutter: plugin: platforms: android: package: com.example.hello pluginClass: HelloPlugin ios: pluginClass: HelloPlugin macos: pluginClass: HelloPlugin web: pluginClass: HelloPlugin fileName: hello\_web.dart \`\`\` #### Federated platform packages A platform package uses the same format, but includes an \`implements\` entry indicating which app-facing package it implements. For example, a \`hello\_windows\` plugin containing the Windows implementation for \`hello\` would have the following \`flutter:\` map: \`\`\`yaml flutter: plugin: implements: hello platforms: windows: pluginClass: HelloPlugin \`\`\` #### Endorsed implementations An app facing package can endorse a platform package by adding a dependency on it, and including it as a \`default\_package\` in the \`platforms:\` map. If the \`hello\` plugin above endorsed \`hello\_windows\`, it would look as follows: \`\`\`yaml flutter: plugin: platforms: android: package: com.example.hello pluginClass: HelloPlugin ios: pluginClass: HelloPlugin windows: default\_package: hello\_windows dependencies: hello\_windows: ^1.0.0 \`\`\` Note that, as shown here, an app-facing package can have some platforms implemented within the package, and others in endorsed federated implementations. #### Shared iOS and macOS implementations Many frameworks support both iOS and macOS with identical or mostly identical APIs, making it possible to implement some plugins for both iOS and macOS with the same codebase. Normally each platform's implementation is in its own folder, but the \`sharedDarwinSource\` option allows iOS and macOS to use the same folder instead: \`\`\`yaml flutter: plugin: platforms: ios: pluginClass: HelloPlugin sharedDarwinSource: true macos: pluginClass: HelloPlugin sharedDarwinSource: true environment: sdk: ^3.0.0 # Flutter versions prior to 3.7 did not support the # sharedDarwinSource option. flutter: ">=3.7.0" \`\`\` When \`sharedDarwinSource\` is enabled, instead of an \`ios\` directory for iOS and a \`macos\` directory for macOS, both platforms use a shared \`darwin\` directory for all code and resources. When enabling this option, you need to move any existing files from \`ios\` and \`macos\` to the shared directory. You also need to update the podspec file to set the dependencies and deployment targets for both platforms, for example: \`\`\`ruby s.ios.dependency 'Flutter' s.osx.dependency 'FlutterMacOS' s.ios.deployment\_target = '13.0' s.osx.deployment\_target = '10.15' \`\`\` ### Step 1: Create the package To create a plugin package, use the \`--template=plugin\` flag with \`flutter create\`. Use the \`--platforms=\` option followed by a comma-separated list to specify the platforms that the plugin supports. Available platforms are: \`android\`, \`ios\`, \`web\`, \`linux\`, \`macos\`, and \`windows\`. If no platforms are specified, the resulting project doesn't support any platforms. Use the \`--org\` option to specify your organization, using reverse domain name notation. This value is used in various package and bundle identifiers in the generated plugin code. By default, the plugin project uses Swift for iOS code and Kotlin for Android code. If you prefer Objective-C or Java, you can specify the iOS language using \`-i\` and the Android language using \`-a\`. Please choose \*\*one\*\* of the following: \`\`\`console $ flutter create --org com.example --template=plugin --platforms=android,ios,linux,macos,windows -a kotlin hello \`\`\` \`\`\`console $ flutter create --org com.example --template=plugin --platforms=android,ios,linux,macos,windows -a java hello \`\`\` \`\`\`console $ flutter create --org com.example --template=plugin --platforms=android,ios,linux,macos,windows -i objc hello \`\`\` \`\`\`console $ flutter create --org com.example --template=plugin --platforms=android,ios,linux,macos,windows -i swift hello \`\`\` This creates a plugin project in the \`hello\` folder with the following specialized content: \*\*\`lib/hello.dart\`\*\* : The Dart API for the plugin. \*\*\`android/src/main/java/com/example/hello/HelloPlugin.kt\`\*\* : The Android platform-specific implementation of the plugin API in Kotlin. \*\*\`ios/Classes/HelloPlugin.m\`\*\* : The iOS-platform specific implementation of the plugin API in Objective-C. \*\*\`example/\`\*\* : A Flutter app that depends on the plugin, and illustrates how to use it. ### Step 2: Implement the package {:#edit-plugin-package} As a plugin package contains code for several platforms written in several programming languages, some specific steps are needed to ensure a smooth experience. #### Step 2a: Define the package API (.dart) The API of the plugin package is defined in Dart code. Open the main \`hello/\` folder in your favorite \[Flutter editor]\[]. Locate the file \`lib/hello.dart\`. #### Step 2b: Add Android platform code (.kt/.java) We recommend you edit the Android code using Android Studio. Before editing the Android platform code in Android Studio, first make sure that the code has been built at least once (in other words, run the example app from your IDE/editor, or in a terminal execute: \`\`\`console cd hello/example; flutter build apk --config-only \`\`\` Then use the following steps: 1. Launch Android Studio. 1. Select \*\*Open an existing Android Studio Project\*\* in the \*\*Welcome to Android Studio\*\* dialog, or select \*\*File > Open\*\* from the menu, and select either the \`hello/example/android/build.gradle\` or the \`hello/example/android/build.gradle.kts\` file. 1. In the \*\*Gradle Sync\*\* dialog, select \*\*OK\*\*. 1. In the \*\*Android Gradle Plugin Update\*\* dialog, select \*\*Don't remind me again for this project\*\*. The Android platform code of your plugin is located in \`hello/java/com.example.hello/HelloPlugin\`. You can run the example app from Android Studio by pressing the run (▶) button. #### Step 2c: Add iOS platform code (.swift/.h+.m) We recommend you edit the iOS code using Xcode. Before editing the iOS platform code in Xcode, first make sure that the code has been built at least once (in other words, run the example app from your IDE/editor, or in a terminal execute \`cd hello/example; flutter build ios --no-codesign --config-only\`). Then use the following steps: 1. Launch Xcode. 1. Select \*\*File > Open\*\*, and select the \`hello/example/ios/Runner.xcworkspace\` file. The iOS platform code for your plugin is located in \`Pods/Development Pods/hello/../../example/ios/.symlinks/plugins/hello/ios/Classes\` in the Project Navigator. (If you are using \`sharedDarwinSource\`, the path will end with \`Flutter/hello/Sources/hello\` instead.) You can run the example app by pressing the run (▶) button. ##### Add native Darwin dependencies (Swift Package Manager) Flutter uses Swift Package Manager as the primary strategy to manage native iOS and macOS dependencies. To add a dependency to your plugin using Swift Package Manager: 1. Create a \`Package.swift\` file in your plugin's \`ios/my\_plugin\_name\` or \`macos/my\_plugin\_name\` directory. 2. Declare your native dependency within the \`dependencies\` array of the \`Package.swift\` descriptor file: \`\`\`swift title="Package.swift" dependencies: \[ .package(url: "https\://github.com/path/to/HelloLibrary.git", from: "1.0.0") ] \`\`\` For complete details and instructions on structuring native folders, resource bundling, or handling hybrid setups, visit the \[Swift Package Manager for plugin authors]\[] guide. \[Swift Package Manager for plugin authors]: /packages-and-plugins/swift-package-manager/for-plugin-authors ##### Add CocoaPod dependencies (legacy) Flutter continues to support CocoaPods for backward compatibility. If your plugin needs to support developers who haven't migrated to Swift Package Manager yet, specify your CocoaPods dependency at the end of \`ios/hello.podspec\`: \`\`\`ruby s.dependency 'HelloPod', '0.0.1' \`\`\` For private pods, refer to \[Private CocoaPods]\[] to ensure repo access: \`\`\`ruby s.source = { # For pods hosted on GitHub :git => "https\://github.com/path/to/HelloPod.git", # Alternatively, for pods hosted locally # :path => "file:///path/to/private/repo", :tag => s.version.to\_s } \`\`\` \[Private CocoaPods]: https\://guides.cocoapods.org/making/private-cocoapods.html ##### Installing the plugin dependencies To fetch and link the plugin's dependencies, add the plugin to your app project's \`pubspec.yaml\` dependencies, and run \`flutter pub get\`. Flutter automatically resolves and wires up the Swift Package Manager descriptors or CocoaPods pod files during the native app build step. If your plugin requires a privacy manifest, for example, if it uses any \*\*required reason APIs\*\*, update the \`PrivacyInfo.xcprivacy\` file to describe your plugin's privacy impact, and add the following to the bottom of your podspec file: \`\`\`ruby s.resource\_bundles = {'your\_plugin\_privacy' => \['your\_plugin/Sources/your\_plugin/Resources/PrivacyInfo.xcprivacy']} \`\`\` For more information, check out \[Privacy manifest files]\[] on the Apple developer site. \[Privacy manifest files]: https\://developer.apple.com/documentation/bundleresources/privacy\_manifest\_files #### Step 2d: Add Linux platform code (.h+.cc) We recommend you edit the Linux code using an IDE with C++ integration. The instructions below are for Visual Studio Code with the "C/C++" and "CMake" extensions installed, but can be adjusted for other IDEs. Before editing the Linux platform code in an IDE, first make sure that the code has been built at least once (in other words, run the example app from your Flutter IDE/editor, or in a terminal execute \`cd hello/example; flutter build linux\`). Then use the following steps: 1. Launch Visual Studio Code. 1. Open the \`hello/example/linux/\` directory. 1. Choose \*\*Yes\*\* in the prompt asking: \`Would you like to configure project "linux"?\`. This will allow C++ autocomplete to work. The Linux platform code for your plugin is located in \`flutter/ephemeral/.plugin\_symlinks/hello/linux/\`. You can run the example app using \`flutter run\`. \*\*Note:\*\* Creating a runnable Flutter application on Linux requires steps that are part of the \`flutter\` tool, so even if your editor provides CMake integration building and running that way won't work correctly. #### Step 2e: Add macOS platform code (.swift) We recommend you edit the macOS code using Xcode. Before editing the macOS platform code in Xcode, first make sure that the code has been built at least once (in other words, run the example app from your IDE/editor, or in a terminal execute \`cd hello/example; flutter build macos --config-only\`). Then use the following steps: 1. Launch Xcode. 1. Select \*\*File > Open\*\*, and select the \`hello/example/macos/Runner.xcworkspace\` file. The macOS platform code for your plugin is located in \`Pods/Development Pods/hello/../../example/macos/Flutter/ephemeral/.symlinks/plugins/hello/macos/Classes\` in the Project Navigator. (If you are using \`sharedDarwinSource\`, the path will end with \`hello/darwin/Classes\` instead.) You can run the example app by pressing the run (▶) button. #### Step 2f: Add Windows platform code (.h+.cpp) We recommend you edit the Windows code using Visual Studio. Before editing the Windows platform code in Visual Studio, first make sure that the code has been built at least once (in other words, run the example app from your IDE/editor, or in a terminal execute \`cd hello/example; flutter build windows\`). Then use the following steps: 1. Launch Visual Studio. 1. Select \*\*Open a project or solution\*\*, and select the \`hello/example/build/windows/hello\_example.sln\` file. The Windows platform code for your plugin is located in \`hello\_plugin/Source Files\` and \`hello\_plugin/Header Files\` in the Solution Explorer. You can run the example app by right-clicking \`hello\_example\` in the Solution Explorer and selecting \*\*Set as Startup Project\*\*, then pressing the run (▶) button. \*\*Important:\*\* After making changes to plugin code, you must select \*\*Build > Build Solution\*\* before running again, otherwise an outdated copy of the built plugin will be run instead of the latest version containing your changes. #### Step 2g: Connect the API and the platform code Finally, you need to connect the API written in Dart code with the platform-specific implementations. This is done using a \[platform channel]\[], or through the interfaces defined in a platform interface package. ### Add support for platforms in an existing plugin project To add support for specific platforms to an existing plugin project, run \`flutter create\` with the \`--template=plugin\` flag again in the project directory. For example, to add web support in an existing plugin, run: \`\`\`console $ flutter create --template=plugin --platforms=web . \`\`\` If this command displays a message about updating the \`pubspec.yaml\` file, follow the provided instructions. ### Dart platform implementations In many cases, non-web platform implementations only use the platform-specific implementation language, as shown above. However, platform implementations can also use platform-specific Dart as well. :::note The examples below only apply to non-web platforms. Web plugin implementations are always written in Dart, and use \`pluginClass\` and \`fileName\` for their Dart implementations as shown above. ::: #### Dart-only platform implementations In some cases, some platforms can be implemented entirely in Dart (for example, using FFI). For a Dart-only platform implementation on a platform other than web, replace the \`pluginClass\` in pubspec.yaml with a \`dartPluginClass\`. Here is the \`hello\_windows\` example above modified for a Dart-only implementation: \`\`\`yaml flutter: plugin: implements: hello platforms: windows: dartPluginClass: HelloPluginWindows \`\`\` In this version you would have no C++ Windows code, and would instead subclass the \`hello\` plugin's Dart platform interface class with a \`HelloPluginWindows\` class that includes a static \`registerWith()\` method. This method is called during startup, and can be used to register the Dart implementation: \`\`\`dart class HelloPluginWindows extends HelloPluginPlatform { /// Registers this class as the default instance of \[HelloPluginPlatform]. static void registerWith() { HelloPluginPlatform.instance = HelloPluginWindows(); } \`\`\` #### Hybrid platform implementations Platform implementations can also use both Dart and a platform-specific language. For example, a plugin could use a different platform channel for each platform so that the channels can be customized per platform. A hybrid implementation uses both of the registration systems described above. Here is the \`hello\_windows\` example above modified for a hybrid implementation: \`\`\`yaml flutter: plugin: implements: hello platforms: windows: dartPluginClass: HelloPluginWindows pluginClass: HelloPlugin \`\`\` The Dart \`HelloPluginWindows\` class would use the \`registerWith()\` shown above for Dart-only implementations, while the C++ \`HelloPlugin\` class would be the same as in a C++-only implementation. ### Testing your plugin We encourage you test your plugin with automated tests to ensure that functionality doesn't regress as you make changes to your code. To learn more about testing your plugins, check out \[Testing plugins]\[]. If you are writing tests for your Flutter app and plugins are causing crashes, check out \[Flutter in plugin tests]\[]. \[Flutter in plugin tests]: /testing/plugins-in-tests \[Testing plugins]: /testing/testing-plugins ## Developing FFI packages {:#ffi} If you want to develop a package that calls into native APIs using Dart's FFI, you need to develop an FFI package. :::note If you need to access the Flutter Plugin API or configure a Google Play services runtime on Android, use the \`flutter create --template=plugin\` command instead. ::: ### Step 1: Create the package To create a starter FFI package, use the \`--template=package\_ffi\` flag with \`flutter create\`: \`\`\`console $ flutter create --template=package\_ffi hello \`\`\` This creates an FFI package project in the \`hello\` folder with the following specialized content: \*\*lib\*\*: The Dart code that defines the API of the package, and which calls into the native code using \`dart:ffi\`. \*\*src\*\*: The native source code. \*\*hook/build.dart\*\*: The build hook script that compiles the native code. ### Step 2: Binding to native code To use the native code, bindings in Dart are needed. To avoid writing these by hand, they are generated from the header file (\`src/hello.h\`) by \[\`package:ffigen\`]\[]. Reference the \[ffigen docs]\[] for information on how to install this package. To regenerate the bindings, run the following command: \`\`\`console $ dart run tool/ffigen.dart \`\`\` ### Step 3: Invoking native code Very short-running native functions can be directly invoked from any isolate. For an example, see \`sum\` in \`lib/hello.dart\`. Longer-running functions should be invoked on a \[helper isolate]\[] to avoid dropping frames in Flutter applications. For an example, see \`sumAsync\` in \`lib/hello.dart\`. ## Adding documentation It is recommended practice to add the following documentation to all packages: 1. A \`README.md\` file that introduces the package 1. A \`CHANGELOG.md\` file that documents changes in each version 1. A \[\`LICENSE\`] file containing the terms under which the package is licensed 1. API documentation for all public APIs (see below for details) ### API documentation When you publish a package, API documentation is automatically generated and published to pub.dev/documentation. For example, see the docs for \[\`device\_info\_plus\`]\[]. If you wish to generate API documentation locally on your development machine, use the following commands:

1. Change directory to the location of your package: \`\`\`console cd \~/dev/mypackage \`\`\`
2. Tell the documentation tool where the Flutter SDK is located (change the following commands to reflect where you placed it): \`\`\`console export FLUTTER\_ROOT=\~/dev/flutter # on macOS or Linux set FLUTTER\_ROOT=\~/dev/flutter # on Windows \`\`\`
3. Run the \`dart doc\` tool (included as part of the Flutter SDK), as follows: \`\`\`console $FLUTTER\_ROOT/bin/cache/dart-sdk/bin/dart doc # on macOS or Linux %FLUTTER\_ROOT%\bin\cache\dart-sdk\bin\dart doc # on Windows \`\`\`

For tips on how to write API documentation, see \[Effective Dart Documentation]\[]. ### Adding licenses to the LICENSE file Individual licenses inside each LICENSE file should be separated by 80 hyphens on their own on a line. If a LICENSE file contains more than one component license, then each component license must start with the names of the packages to which the component license applies, with each package name on its own line, and the list of package names separated from the actual license text by a blank line. (The packages need not match the names of the pub package. For example, a package might itself contain code from multiple third-party sources, and might need to include a license for each one.) The following example shows a well-organized license file: \`\`\`plaintext package\_1 -------------------------------------------------------------------------------- package\_2 \`\`\` Here is another example of a well-organized license file: \`\`\`plaintext package\_1 -------------------------------------------------------------------------------- package\_1 package\_2 \`\`\` Here is an example of a poorly-organized license file: \`\`\`plaintext -------------------------------------------------------------------------------- \`\`\` Another example of a poorly-organized license file: \`\`\`plaintext package\_1 -------------------------------------------------------------------------------- \`\`\` ## Publishing your package {:#publish} :::tip Have you noticed that some of the packages and plugins on pub.dev are designated as \[Flutter Favorites]\[]? These are the packages published by verified developers and are identified as the packages and plugins you should first consider using when writing your app. To learn more, see the \[Flutter Favorites program]\[]. ::: Once you have implemented a package, you can publish it on \[pub.dev]\[], so that other developers can easily use it. Prior to publishing, make sure to review the \`pubspec.yaml\`, \`README.md\`, and \`CHANGELOG.md\` files to make sure their content is complete and correct. Also, to improve the quality and usability of your package (and to make it more likely to achieve the status of a Flutter Favorite), consider including the following items: \* Diverse code usage examples \* Screenshots, animated gifs, or videos \* A link to the corresponding code repository Next, run the publish command in \`dry-run\` mode to see if everything passes analysis: \`\`\`console $ flutter pub publish --dry-run \`\`\` The next step is publishing to pub.dev, but be sure that you are ready because \[publishing is forever]\[]: \`\`\`console $ flutter pub publish \`\`\` For more details on publishing, see the \[publishing docs]\[] on dart.dev. ## Handling package interdependencies {:#dependencies} If you are developing a package \`hello\` that depends on the Dart API exposed by another package, you need to add that package to the \`dependencies\` section of your \`pubspec.yaml\` file. The code below makes the Dart API of the \`url\_launcher\` plugin available to \`hello\`: \`\`\`yaml dependencies: url\_launcher: ^6.3.2 \`\`\` You can now \`import 'package:url\_launcher/url\_launcher.dart'\` and \`launch(someUrl)\` in the Dart code of \`hello\`. This is no different from how you include packages in Flutter apps or any other Dart project. But if \`hello\` happens to be a \_plugin\_ package whose platform-specific code needs access to the platform-specific APIs exposed by \`url\_launcher\`, you also need to add suitable dependency declarations to your platform-specific build files, as shown below. ### Android The following example sets a dependency for \`url\_launcher\` in \`hello/android/build.gradle\`: \`\`\`groovy android { // lines skipped dependencies { compileOnly rootProject.findProject(":url\_launcher") } } \`\`\` You can now \`import io.flutter.plugins.urllauncher.UrlLauncherPlugin\` and access the \`UrlLauncherPlugin\` class in the source code at \`hello/android/src\`. For more information on \`build.gradle\` files, see the \[Gradle Documentation]\[] on build scripts. ### iOS The following example sets a dependency for \`url\_launcher\` in \`hello/ios/hello/Package.swift\`: \`\`\`swift let package = Package( // lines skipped dependencies: \[ .package(path: "../url\_launcher"), ], targets: \[ .target( name: "hello", dependencies: \[ .product(name: "url\_launcher", package: "url\_launcher"), ], ), ], ) \`\`\` You can now \`import url\_launcher\` and access the source code at \`hello/ios/hello/Sources\`. For additional details on Swift package manifests, consult \[Apple's PackageDescription documentation]\[]. #### CocoaPods (legacy) If your plugin supports CocoaPods for backward compatibility, set a dependency in \`hello/ios/hello.podspec\`: \`\`\`ruby Pod::Spec.new do |s| # lines skipped s.dependency 'url\_launcher' \`\`\` You can now \`#import "UrlLauncherPlugin.h"\` and access the \`UrlLauncherPlugin\` class in the source code at \`hello/ios/Classes\`. For additional details on \`.podspec\` files, consult the \[CocoaPods Documentation]\[]. ### Web All web dependencies are handled by the \`pubspec.yaml\` file, like any other Dart package. \[Apple's PackageDescription documentation]: https\://developer.apple.com/documentation/packagedescription \[bind-native]: /platform-integration/bind-native-code \[CocoaPods Documentation]: https\://guides.cocoapods.org/syntax/podspec.html \[Dart library package]: https\://dart.dev/guides/libraries/create-library-packages \[\`device\_info\_plus\`]: https\://pub.dev/documentation/device\_info\_plus \[Effective Dart Documentation]: https\://dart.dev/guides/language/effective-dart/documentation \[federated plugins]: #federated-plugins \[ffigen docs]: https\://pub.dev/packages/ffigen/install \[\`fluro\`]: https\://pub.dev/packages/fluro \[Flutter editor]: /tools/editors \[Flutter Favorites]: https\://pub.dev/flutter/favorites \[Flutter Favorites program]: /packages-and-plugins/favorites \[Gradle Documentation]: https\://docs.gradle.org/current/userguide/tutorial\_using\_tasks.html \[helper isolate]: https\://dart.dev/guides/language/concurrency#background-workers \[How to Write a Flutter Web Plugin, Part 1]: https\://blog.flutter.dev/how-to-write-a-flutter-web-plugin-5e26c689ea1 \[How To Write a Flutter Web Plugin, Part 2]: https\://blog.flutter.dev/how-to-write-a-flutter-web-plugin-part-2-afdddb69ece6 \[issue #33302]: https\://github.com/flutter/flutter/issues/33302 \[multiple Flutter engines]: /add-to-app/multiple-flutters \[\`LICENSE\`]: #adding-licenses-to-the-license-file \[\`path\`]: https\://pub.dev/packages/path \[\`package:ffigen\`]: https\://pub.dev/packages/ffigen \[platform channel]: /platform-integration/platform-channels \[pub.dev]: https\://pub.dev \[publishing docs]: https\://dart.dev/tools/pub/publishing \[publishing is forever]: https\://dart.dev/tools/pub/publishing#publishing-is-forever \[supported-platforms]: #plugin-platforms \[test your plugin]: #testing-your-plugin \[unit tests]: /testing/overview#unit-tests \[\`url\_launcher\`]: https\://pub.dev/packages/url\_launcher ## Bundle AI package skills You can help AI coding assistants use your package effectively by bundling custom instructions and recipes directly inside your repository. To distribute skills with your package: 1. Create a \`skills/\` directory in the root of your package repo. 2. Inside \`skills/\`, create a subdirectory named after your package (for example, \`skills//\`). 3. Add a \`SKILL.md\` file with guidelines and idiomatic code examples. 4. Publish your package to \`pub.dev\`. When developers add your package as a dependency, their assistants can automatically discover your instructions. To learn more about authoring guidelines and best practices, check out \[Ship skills with packages]\(https\://dart.dev/tools/pub/package-skills). \[Writing a good plugin]: https\://blog.flutter.dev/writing-a-good-flutter-plugin-1a561b986c9c

----
url: https://docs.flutter.dev/data-and-backend/state-mgmt/declarative
----

If you're coming to Flutter from an imperative framework (such as Android SDK or iOS UIKit), you need to start thinking about app development from a new perspective.

Many assumptions that you might have don't apply to Flutter. For example, in Flutter it's okay to rebuild parts of your UI from scratch instead of modifying it. Flutter is fast enough to do that, even on every frame if needed.

Flutter is *declarative*. This means that Flutter builds its user interface to reflect the current state of your app:

When the state of your app changes (for example, the user flips a switch in the settings screen), you change the state, and that triggers a redraw of the user interface. There is no imperative changing of the UI itself (like `widget.setText`)—you change the state, and the UI rebuilds from scratch.

Read more about the declarative approach to UI programming in the [Introduction to declarative UI](/flutter-for/declarative).

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 might not seem as intuitive as the imperative style. This is why this section is here. Read on.

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/data-and-backend/state-mgmt/declarative.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/data-and-backend/state-mgmt/declarative\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/data-and-backend/state-mgmt/declarative.md "Report an issue with this page").

----
url: https://docs.flutter.dev/platform-integration/ios/setup
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Set up iOS development](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

Learn how to set up your development environment to run, build, and deploy Flutter apps for iOS devices.

## Set up iOS tooling

[#](#set-up-tooling)

With Xcode, you can run Flutter apps on an iOS physical device or on the iOS Simulator.

1. ### Install Xcode

   If you haven't done so already, [install and set up the latest version of Xcode](https://developer.apple.com/xcode/).

   If you've already installed Xcode, update it to the latest version using the same installation method you used originally.

2. ### Set up Xcode command-line tools

   To configure the Xcode command-line tools to use the version of Xcode you installed, run the following command in your preferred terminal:

   ```
   $ sudo sh -c 'xcode-select -s /Applications/Xcode.app/Contents/Developer && xcodebuild -runFirstLaunch'
   ```

   If you downloaded Xcode elsewhere or need to use a different version, replace `/Applications/Xcode.app` with the path to your Xcode installation.

3. ### Agree to the Xcode licenses

   After you've set up Xcode and configured its command-line tools, agree to the Xcode licenses.

   1. Open your preferred terminal.

   2. Run the following command to review and sign the Xcode licenses.

      ```
      $ sudo xcodebuild -license
      ```

   3. Read and agree to all necessary licenses.

      Before agreeing to the terms of each license, read each with care.

4. ### Download prerequisite tooling

   To download iOS platform support and the latest iOS Simulator runtimes, run the following command in your preferred terminal.

   ```
   $ xcodebuild -downloadPlatform iOS
   ```

5. ### Install CocoaPods

   To support [Flutter plugins](/packages-and-plugins/developing-packages#types) that use native iOS or macOS code, install the latest version of [CocoaPods](https://guides.cocoapods.org/using/getting-started.html#installation).

   Install CocoaPods by following the [CocoaPods installation guide](https://guides.cocoapods.org/using/getting-started.html#installation).

   If you've already installed CocoaPods, update it by following the [CocoaPods update guide](https://guides.cocoapods.org/using/getting-started.html#updating-cocoapods).

## Set up an iOS device

[#](#set-up-devices)

It's easiest to first set up the simulator, but be sure to also test your app on a physical device.

* [Simulator](#207-tab-panel)
* [Physical device](#208-tab-panel)

Start the simulator (renamed to Device Hub on Xcode 27):

```
$ open -a DeviceHub
```

On Xcode 26 or earlier, start the Simulator app:

```
$ open -a Simulator
```

If you need to install a simulator for a different OS version, check out [Downloading and installing additional Xcode components](https://developer.apple.com/documentation/xcode/downloading-and-installing-additional-xcode-components) on the Apple Developer site.

Set up each iOS device on which you want to test.

1. ### Configure your physical iOS device

   1. Attach your iOS device to the USB port on your Mac.

   2. On first connecting an iOS device to your Mac, your device displays the **Trust this computer?** dialog.

   3. Click **Trust**.

2. ### Enable Developer Mode

   Apple requires enabling **[Developer Mode](https://developer.apple.com/documentation/xcode/enabling-developer-mode-on-a-device)** on the device to protect against malicious software.

   1. Tap on **Settings** > **Privacy & Security** > **Developer Mode**.

   2. Tap to toggle **Developer Mode** to **On**.

   3. Restart the device.

   4. When the **Turn on Developer Mode?** dialog appears, tap **Turn On**.

3. ### Create a developer code signing certificate

   To send your app to a physical iOS device, *even* for testing, you must establish trust between your Mac and the device. In addition to trusting the device when that popup appears, you must upload a signed developer certificate to your device.

   To create a signed development certificate, you need an Apple ID. If you don't have one, [create one](https://support.apple.com/en-us/108647). You must also enroll in the [Apple Developer program](https://developer.apple.com/programs/) and create an [Apple Developer account](https://developer.apple.com/account). If you're just *testing* your app on an iOS device, a personal Apple Developer account is free and works.

4. ### Prepare the device

   1. Find the **VPN & Device Management** menu under **Settings**.

      Toggle your certificate to **Enable**.

   2. Under the **Developer App** heading, you should find your certificate.

   3. Tap the certificate.

   4. Tap **Trust "\<certificate>"**.

   5. When the dialog displays, tap **Trust**.

      If the **codesign wants to access key...** dialog appears:

      1. Enter your macOS password.

      2. Tap **Always Allow**.

***

## Start developing for iOS

[#](#start-developing)

**Congratulations.** Now that you've set up iOS development for Flutter, you can continue your Flutter learning journey while testing on iOS or begin improving integration with iOS.

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-09-14. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/platform-integration/ios/setup.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/platform-integration/ios/setup\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/platform-integration/ios/setup.md "Report an issue with this page").

----
url: https://docs.flutter.dev/data-and-backend/state-mgmt/intro
----

*If you are already familiar with state management in reactive apps, you can skip this section, though you might want to review the [list of different approaches](/data-and-backend/state-mgmt/options).*

As you explore Flutter, there comes a time when you need to share application state between screens, across your app. There are many approaches you can take, and many questions to think about.

In the following pages, you will learn the basics of dealing with state in Flutter apps.

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/data-and-backend/state-mgmt/intro.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/data-and-backend/state-mgmt/intro\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/data-and-backend/state-mgmt/intro.md "Report an issue with this page").

----
url: https://docs.flutter.dev/add-to-app
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Add Flutter to an existing app](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

## Add-to-app

[#](#add-to-app)

If you are writing a new application from scratch, it is easy to [get started](/learn/pathway) using Flutter. But what if you already have an app that's not written in Flutter, and it's impractical to start from scratch?

For those situations, Flutter can be integrated into your existing application piecemeal, as a module. This feature is known as "add-to-app". The module can be imported into your existing app to render part of your app using Flutter, while the rest can be rendered using existing technology. This method can also be used to run shared non-UI logic by taking advantage of Dart's portability and interoperability with other languages.

Add-to-app is currently supported on Android, iOS, macOS, and web.

Flutter supports two flavors of add-to-app:

* **Multi-engine**: supported on Android, iOS, and macOS, allows running one or more instances of Flutter, each rendering a widget embedded into the host application. Each instance is a separate Dart program, running in isolation from other programs. Having multiple Flutter instances allows each instance to maintain independent application and UI state while using minimal memory resources. See more in the [multiple Flutters](/add-to-app/multiple-flutters) page.
* **Multi-view**: supported on the web, allows creating multiple [FlutterView](https://api.flutter.dev/flutter/dart-ui/FlutterView-class.html)s, each rendering a widget embedded into the host application. In this mode there's only one Dart program and all views and widgets can share objects.

Add-to-app supports integrating multiple Flutter views of any size, supporting various use-cases. Two of the most common use-cases are:

* **Hybrid navigation stacks**: an app is made of multiple screens, some of which are rendered by Flutter, and others by another framework. The user can navigate from one screen to another freely, no matter which framework is used to render the screen.
* **Partial-screen views**: a screen in the app renders multiple widgets, some of which are rendered by Flutter, and others by another framework. The user can scroll and interact with any widget freely, no matter which framework is used to render the widget.

## Supported features

[#](#supported-features)

### Add to Android applications

[#](#add-to-android-applications)

* Auto-build and import the Flutter module by adding a Flutter SDK hook to your Gradle script.
* Build your Flutter module into a generic [Android Archive (AAR)](https://developer.android.com/studio/projects/android-library) for integration into your own build system and for better Jetifier interoperability with AndroidX.
* [`FlutterEngine`](https://api.flutter.dev/javadoc/io/flutter/embedding/engine/FlutterEngine.html) API for starting and persisting your Flutter environment independently of attaching a [`FlutterActivity`](https://api.flutter.dev/javadoc/io/flutter/embedding/android/FlutterActivity.html)/[`FlutterFragment`](https://api.flutter.dev/javadoc/io/flutter/embedding/android/FlutterFragment.html) etc.
* Android Studio Android/Flutter co-editing and module creation/import wizard.
* Java and Kotlin host apps are supported.
* Flutter modules can use [Flutter plugins](https://pub.dev/flutter) to interact with the platform.
* Support for Flutter debugging and stateful hot reload by using `flutter attach` from IDEs or the command line to connect to an app that contains Flutter.

### Add to iOS applications

[#](#add-to-ios-applications)

* Build your Flutter module into a Swift package for integration into your own build system.
* Auto-build and import the Flutter module using Xcode build phases.
* [`FlutterEngine`](https://api.flutter.dev/ios-embedder/interface_flutter_engine.html) API for starting and persisting your Flutter environment independently of attaching a [`FlutterViewController`](https://api.flutter.dev/ios-embedder/interface_flutter_view_controller.html).
* Objective-C and Swift host apps supported.
* Flutter modules can use [Flutter plugins](https://pub.dev/flutter) to interact with the platform.
* Support for Flutter debugging and stateful hot reload by using `flutter attach` from IDEs or the command line to connect to an app that contains Flutter.

See our [add-to-app GitHub Samples repository](https://github.com/flutter/samples/tree/main/add_to_app) for sample projects in Android and iOS that import a Flutter module for UI.

### Add to macOS applications

[#](#add-to-macos-applications)

* Build your Flutter module into a Swift package for integration into your own build system.
* Auto-build and import the Flutter module using Xcode build phases.
* [`FlutterEngine`](https://api.flutter.dev/macos-embedder/interface_flutter_engine.html) API for starting and persisting your Flutter environment independently of attaching a [`FlutterViewController`](https://api.flutter.dev/macos-embedder/interface_flutter_view_controller.html).
* Swift host apps supported.
* Flutter modules can use [Flutter plugins](https://pub.dev/flutter) to interact with the platform.
* Support for Flutter debugging and stateful hot reload by using `flutter attach` from IDEs or the command line to connect to an app that contains Flutter.

### Add to web applications

[#](#add-to-web-applications)

Flutter can be added to any existing HTML DOM-based web app written in any client-side Dart web framework ([jaspr](https://pub.dev/packages/jaspr), [ngdart](https://pub.dev/packages/ngdart), [over\_react](https://pub.dev/packages/over_react), etc), any client-side JS framework ([React](https://react.dev/), [Angular](https://angular.dev/), [Vue.js](https://vuejs.org/), etc), any server-side rendered framework ([Django](https://www.djangoproject.com/), [Ruby on Rails](https://rubyonrails.org/), [Apache Struts](https://struts.apache.org/), etc), or even no framework (affectionately known as "[VanillaJS](http://vanilla-js.com/)"). The minimum requirement is only that your existing application and its framework support importing JavaScript libraries, and creating HTML elements for Flutter to render into.

To add Flutter to an existing app, build it normally, then follow the [embedding instructions](/platform-integration/web/embedding-flutter-web#embedded-mode) for putting Flutter views onto the page.

## Get started

[#](#get-started)

To get started, see our project integration guide for Android, web, iOS, and macOS:

[Android](/add-to-app/android/project-setup)

[Web](/platform-integration/web/embedding-flutter-web#embedded-mode)

[iOS](/add-to-app/ios/project-setup)

[macOS](/add-to-app/macos/project-setup)

## API usage

[#](#api-usage)

After Flutter is integrated into your project, see our API usage guides at the following links:

[Android](/add-to-app/android/add-flutter-screen)

[web](/platform-integration/web/embedding-flutter-web#manage-flutter-views-from-js)

[iOS](/add-to-app/ios/add-flutter-screen)

[macOS](/add-to-app/macos/add-flutter-screen)

## Limitations

[#](#limitations)

Mobile limitations:

* Multi-view mode is not supported (multi-engine only).
* Packing multiple Flutter libraries into an application isn't supported.
* Plugins that don't support `FlutterPlugin` might have unexpected behaviors if they make assumptions that are untenable in add-to-app (such as assuming that a Flutter `Activity` is always present).
* On Android, the Flutter module only supports AndroidX applications.

Web limitations:

* Multi-engine mode is not supported (multi-view only).
* There's no way to completely "shutdown" the Flutter engine. The app can remove all the [FlutterView](https://api.flutter.dev/flutter/dart-ui/FlutterView-class.html) objects and make sure all data is garbage collected using normal Dart concepts. However, the engine will remain warmed up, even if it's not rendering anything.

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-07-31. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/add-to-app/index.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/add-to-app\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/add-to-app/index.md "Report an issue with this page").

----
url: https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.38.0
----

The 2.38.0 release of the Dart and Flutter DevTools includes the following changes among other general improvements. To learn more about DevTools, check out the [DevTools overview](/tools/devtools/overview).

## Performance updates

[#](#performance-updates)

* Renamed the "Track" builds, paints, and layouts settings to "Trace" builds, paints, and layouts. - [#8084](https://github.com/flutter/devtools/pull/8084)
* Renamed the "Track widget build counts" setting to "Count widget builds". - [#8084](https://github.com/flutter/devtools/pull/8084)

## Debugger updates

[#](#debugger-updates)

* Added recommendation to debug code from an IDE, with links to IDE instructions. - [#8085](https://github.com/flutter/devtools/pull/8085)

## Network profiler updates

[#](#network-profiler-updates)

* Added support to export network requests as a HAR file (thanks to @hrajwade96!). - [#7970](https://github.com/flutter/devtools/pull/7970)

## DevTools Extension updates

[#](#devtools-extension-updates)

* Fixed an issue where extensions did not load with the proper theme when embedded in an IDE. - [#8034](https://github.com/flutter/devtools/pull/8034)
* Added an API for copying text to clipboard by proxy of the parent DevTools web app, which has workarounds for copy issues when embedded inside an IDE. - [#8130](https://github.com/flutter/devtools/pull/8130)

## Full commit history

[#](#full-commit-history)

To find a complete list of changes in this release, check out the [DevTools git log](https://github.com/flutter/devtools/tree/v2.38.0).

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/release-notes/release-notes-2.38.0.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.38.0\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/release-notes/release-notes-2.38.0.md "Report an issue with this page").

----
url: https://docs.flutter.dev/platform-integration/windows/setup.md
----

\# Set up Windows development > Configure your development environment to run, build, and deploy Flutter apps for Windows. Learn how to set up your development environment to run, build, and deploy Flutter apps for the Windows desktop platform. :::note If you haven't set up Flutter already, visit and follow \[Install Flutter]\[] first. If you've already installed Flutter, ensure that it's \[up to date]\[]. ::: \[Install Flutter]: /install \[up to date]: /install/upgrade ## Set up tooling {: #set-up-tooling} With \[Visual Studio]\[vs], you can run Flutter apps on Windows as well as compile and debug native C and C++ code. Note that \*\*Visual Studio\*\* is an IDE separate from \*\*Visual Studio \_Code\_\*\* and only supported on Windows. 1.

### Install Visual Studio

If you haven't done so already, follow the Microsoft guide to \[install and set up Visual Studio]\[vs]. If you've already installed Visual Studio, \[update it to the latest version]\[vs-update]. 1.

### Set up Visual Studio workloads

When the Visual Studio installer prompts you to choose workloads, select and install the \*\*Desktop development with C++\*\* workload. If you already installed Visual Studio, follow the Microsoft guide to \[Modify Visual Studio workloads]\[vs-modify]. :::tip If installing with the command line, the ID of the \*\*Desktop development with C++\*\* workload is \`Microsoft.VisualStudio.Workload.NativeDesktop\`. ::: {: .steps} \[vs]: https\://visualstudio.microsoft.com/ \[vs-install]: https\://learn.microsoft.com/en-us/visualstudio/install/install-visual-studio \[vs-update]: https\://learn.microsoft.com/en-us/visualstudio/install/update-visual-studio \[vs-modify]: https\://learn.microsoft.com/en-us/visualstudio/install/modify-visual-studio ## Validate your setup {: #validate-setup} 1.

### Check for toolchain issues

To check for any issues with your Windows development setup, run the \`flutter doctor\` command in your preferred terminal: \`\`\`console $ flutter doctor -v \`\`\` If you see any errors or tasks to complete under the \*\*Windows version\*\* and \*\*Visual Studio - develop Windows apps\*\* sections, complete and resolve them, then run \`flutter doctor -v\` again to verify any changes. 1.

### Check for Windows devices

To ensure Flutter can find and connect to your Windows device correctly, run \`flutter devices\` in your preferred terminal: \`\`\`console $ flutter devices \`\`\` If you've set everything up correctly, there should be at least one entry with the platform marked as \*\*windows\*\*. 1.

### Troubleshoot setup issues

If you need help resolving any setup issues, check out \[installation and setup troubleshooting]\[troubleshoot]. Depending on your issue, also check out Microsoft's guide on \[Visual Studio troubleshooting]\[vs-troubleshoot]. If you still have issues or questions, reach out on one of the Flutter \[community]\[] channels. {: .steps} \[troubleshoot]: /install/troubleshoot \[vs-troubleshoot]: https\://learn.microsoft.com/en-us/troubleshoot/developer/visualstudio/installation/troubleshoot-installation-issues \[community]: https\://flutter.dev/community ## Start developing for Windows {: #start-developing} Congratulations! Now that you've set up Windows desktop development for Flutter, you can continue your Flutter learning journey while testing on Windows or begin expanding integration with Windows.

----
url: https://docs.flutter.dev/packages-and-plugins/dependency-management.md
----

\# Package dependency management > Learn how Flutter and Dart resolve package dependencies, and how to handle version conflicts. When you build a Flutter app, you often use external packages to add functionality. Each package can depend on other packages, creating a tree of dependencies. Understanding how Dart and Flutter manage these dependencies helps you resolve version conflicts and keep your app stable. ## How dependency resolution works Your Flutter project specifies dependencies in the \`pubspec.yaml\` file. When you run \`flutter pub get\`, the package manager resolves the dependencies and records the exact versions in the \`pubspec.lock\` file. ### Direct and transitive dependencies A dependency can be direct or transitive: \*\*Direct dependencies\*\* : The packages you explicitly list under the \`dependencies\` or \`dev\_dependencies\` section in your \`pubspec.yaml\` file. \*\*Transitive dependencies\*\* : The packages that your direct dependencies depend on. You don't list these packages in your \`pubspec.yaml\` file, but your project requires them to compile. ### The pubspec.lock file The \`pubspec.lock\` file contains the exact version of every direct and transitive dependency used in your project. This file ensures that every developer on your team, and your build servers, use the exact same package versions. Commit the \`pubspec.lock\` file to version control for application projects, but not for package projects. :::note Use version ranges, not exact versions Since \`pubspec.lock\` always pins exact versions for predictable builds, you should use ranges in your \`pubspec.yaml\` file (possibly using caret syntax described below). This allows the version solver to find compatible versions of transitive dependencies, and enables you to safely update dependencies using \`flutter pub upgrade\`. ::: ## The role of the version solver Unlike some package managers, the Dart package manager (\`pub\`) allows only a single version of any package in your app's compilation tree. This constraint exists for several reasons: 1. \*\*Type safety:\*\* If your app uses two versions of the same package, you can get type mismatch errors. For example, if \`v1\` and \`v2\` of a package both define a \`User\` class, the Dart compiler treats them as two distinct types. You can't pass a \`v1.User\` to a method expecting a \`v2.User\`. 1. \*\*Binary size:\*\* Including multiple versions of the same package increases the size of your application. 1. \*\*Global state:\*\* Many packages use global variables or singletons. Having multiple copies of the package can lead to inconsistent state or bugs. The version solver's job is to look at all version constraints for your direct and transitive dependencies and find a single concrete version for each package that satisfies all constraints. ## Understand version constraints When you declare a dependency in \`pubspec.yaml\`, you specify a version constraint. Dart packages use \[Semantic Versioning]\[] (SemVer). A version number has three parts: \`major.minor.patch\` (for example, \`2.1.4\`). You can define version constraints in your \`pubspec.yaml\` file using different syntaxes: \[Semantic Versioning]: https\://semver.org/spec/v2.0.0-rc.1.html ### Caret syntax The caret syntax (\`^\`) is the most common way to define a version constraint. It tells the solver to use any version that is compatible with the specified version. \* For stable versions (1.0.0 and higher), the caret allows updates that don't change the major version: \* \`^1.2.3\` is equivalent to \`>=1.2.3 <2.0.0\` \* For pre-release/pre-1.0.0 versions, any change in the minor or patch version can introduce breaking changes. Therefore, the caret constraint is more restrictive: \* \`^0.8.0\` is equivalent to \`>=0.8.0 <0.9.0\` \* \`^0.0.3\` is equivalent to \`>=0.0.3 <0.0.4\` ### Traditional ranges You can define explicit version ranges using comparison operators: \`\`\`yaml dependencies: url\_launcher: '>=5.4.0 <6.0.0' \`\`\` ### Any version If you don't specify a version constraint or use \`any\`, the solver can choose any version: \`\`\`yaml dependencies: url\_launcher: any \`\`\` :::note Avoid using \`any\` We don't recommend using \`any\` because a future package update might introduce breaking changes that break your build. ::: ## Understand dependency conflicts A dependency conflict occurs when two packages in your dependency tree require incompatible versions of the same transitive dependency. Consider this example scenario: 1. Your app depends on two direct dependencies: \`package\_a\` and \`package\_b\`. 1. \`package\_a\` depends on \`foo: ^1.0.0\`. 1. \`package\_b\` depends on \`foo: ^2.0.0\`. !\[Version solver conflict]\(/assets/images/docs/development/packages-and-plugins/version-solver.png) Because \`foo\` can't be both \`^1.0.0\` (which is \`<2.0.0\`) and \`^2.0.0\` (which is \`>=2.0.0\`), the version solver fails. When you run \`flutter pub get\` or try to build the app, the solver prints an error message: \`\`\`console Because every version of package\_a depends on foo ^1.0.0 and every version of package\_b depends on foo ^2.0.0, package\_a is incompatible with package\_b. So, because my\_app depends on both package\_a ^1.0.0 and package\_b ^1.0.0, version solving failed. \`\`\` To read this error message, trace the conflict from the bottom up in the dependency graph: 1. Identify the conflicting package (\`foo\`). 1. Find which packages require conflicting versions. For example, \`package\_a\` requires \`foo: ^1.0.0\` and \`package\_b\` requires \`foo: ^2.0.0\`. 1. Check which direct dependencies pull in those conflicting packages. For example, \`my\_app\` depends on both \`package\_a: ^1.0.0\` and \`package\_b: ^1.0.0\`. ## Resolve dependency conflicts You can use the following steps to resolve dependency conflicts. ### Upgrade package versions Before making manual changes, check if newer, compatible versions of your dependencies exist. Run the following command to upgrade your packages to the latest versions allowed by your \`pubspec.yaml\` constraints: \`\`\`bash flutter pub upgrade \`\`\` To see which packages have newer versions available beyond your current constraints, run: \`\`\`bash flutter pub outdated \`\`\` To upgrade to newer major versions, run \`flutter pub upgrade --major-versions\`. This command upgrades the constraints in your \`pubspec.yaml\` file to allow the latest resolvable versions, and then runs \`flutter pub get\` to retrieve those upgraded dependencies. ### Use dependency overrides If no compatible versions exist because one of the packages hasn't been updated, you can force the version solver to use a specific version. Add a \`dependency\_overrides\` section to your \`pubspec.yaml\` file: \`\`\`yaml dependencies: package\_a: ^1.0.0 package\_b: ^1.0.0 dependency\_overrides: foo: ^2.0.0 \`\`\` This overrides all constraints for \`foo\` and forces the version solver to use \`^2.0.0\`. :::warning Use dependency overrides only as a temporary fix. If the packages aren't actually compatible, bypassing the version solver can cause compilation errors or runtime crashes (such as \`NoSuchMethodError\`). Thoroughly test your app after applying an override. ::: :::note Dependency overrides only apply to the root package. If you're developing a package to publish to pub.dev, don't include \`dependency\_overrides\` in your \`pubspec.yaml\` file because other projects that depend on your package will ignore them. ::: ### Support the ecosystem If a package is unmaintained or slow to update, you can help resolve the issue: 1. \*\*File an issue:\*\* Search the package's repository for existing issues or file a new issue to notify the maintainer. 1. \*\*Submit a pull request:\*\* If you can fix the conflict, fork the package repository, update the constraints, and submit a pull request. 1. \*\*Use a git or path dependency:\*\* While waiting for the maintainer to merge your pull request, you can point your \`pubspec.yaml\` to your fork or a local copy: \`\`\`yaml dependencies: package\_a: git: url: https\://github.com/your-username/package\_a.git ref: update-foo-dependency \`\`\` ## Learn more For more information on how Dart manages dependencies, see the following resources: \* \[Package versioning]\[] on dart.dev \* \[Pub dependencies]\[] on dart.dev \* \[Dependency overrides]\[] on dart.dev \[Dependency overrides]: https\://dart.dev/tools/pub/dependencies#dependency-overrides \[Package versioning]: https\://dart.dev/tools/pub/versioning \[Pub dependencies]: https\://dart.dev/tools/pub/dependencies

----
url: https://docs.flutter.dev/data-and-backend/persistence/choose-a-database
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Choose a database for your Flutter app](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

Flutter doesn't include a built-in database, so you can choose one based on the shape of your data and where it needs to live. For most Flutter apps, start with one of these options:

* **Cloud Firestore** for scalable NoSQL data that syncs across devices in realtime.
* **Firebase SQL Connect** for relational data that needs a structured SQL schema.
* **On-device SQLite** or **key-value storage** for local data that doesn't need a cloud backend by default.

## Compare database options

[#](#compare-database-options)

Here's a quick look at how each option stores and syncs data:

| Option                                                               | Data model                      | Data location             | Realtime updates             | Offline access              |
| -------------------------------------------------------------------- | ------------------------------- | ------------------------- | ---------------------------- | --------------------------- |
| [Cloud Firestore](https://firebase.google.com/docs/firestore)        | NoSQL documents and collections | Cloud, with a local cache | Yes                          | Built in (reads and writes) |
| [Firebase SQL Connect](https://firebase.google.com/docs/sql-connect) | Relational (PostgreSQL)         | Cloud                     | Yes                          | Optional query cache        |
| [On-device SQLite](/cookbook/persistence/sqlite)                     | Relational                      | Device by default         | Local streams (with `drift`) | Always local                |
| [Key-value storage](/cookbook/persistence/key-value)                 | Key-value pairs                 | Device only               | No                           | Always local                |

## Choose Cloud Firestore for NoSQL data

[#](#choose-cloud-firestore-for-nosql-data)

[Cloud Firestore](https://firebase.google.com/docs/firestore) is the recommended default when your app needs a scalable cloud database that keeps data in sync across users and devices—such as in chat apps, collaborative tools, live multiplayer games, or offline-first field service and inspection apps. It stores data as documents organized into collections, and the [`cloud_firestore`](https://pub.dev/packages/cloud_firestore) package lets your Flutter app read and write data directly from client code, secured with [Cloud Firestore Security Rules](https://firebase.google.com/docs/firestore/security/get-started).

Cloud Firestore is a good fit when your app needs:

* **Realtime sync:** listen to a document or query and rebuild your UI whenever the data changes, for example, with a `StreamBuilder` widget.
* **Offline support:** read and write cached data while the device is offline, and automatically sync changes when the connection returns, without building a custom offline queue. Offline persistence is enabled by default on Android and Apple platforms, and can be enabled on the web.
* **Flexible, evolving data:** store nested or semi-structured data without defining a schema up front.
* **Automatic scaling:** handle growing traffic without provisioning or managing servers.

To add Cloud Firestore to your project, run:

```
$ flutter pub add cloud_firestore
```

Then follow the [Cloud Firestore quickstart](https://firebase.google.com/docs/firestore/quickstart) to set up your database.

## Choose Firebase SQL Connect for SQL data

[#](#choose-firebase-sql-connect-for-sql-data)

For structured, relational data—such as in an ecommerce storefront, appointment booking system, personal finance tracker, or inventory system—[Firebase SQL Connect](https://firebase.google.com/docs/sql-connect) (formerly Firebase Data Connect) is the recommended default. SQL Connect is backed by a fully managed PostgreSQL database on Cloud SQL. You define your schema, queries, and mutations in GraphQL, deploy them to the server, and call them from your Flutter app using a generated, type-safe Dart SDK powered by the [`firebase_data_connect`](https://pub.dev/packages/firebase_data_connect) package.

Key capabilities include:

* **Relational data:** model entities with relationships, such as users, orders, and products, and query across them.
* **A strict schema:** enforce data types, relational constraints, and foreign keys in the database.
* **Type-safe generated SDKs:** call generated Dart methods for each predefined server query and mutation instead of writing raw queries or duplicating data models by hand. Because SQL Connect generates client SDKs directly from your backend schema, your Flutter app stays in sync with your database without cross-language schema drift.
* **Realtime updates:** subscribe to queries for automatic or configured refresh updates when data changes.
* **Optional query caching:** cache query responses on the client to reduce network requests and read cached results while offline.
* **Vector search:** build AI-powered features, such as semantic search, on top of your relational data.

To add the SQL Connect plugin to your project, run:

```
$ flutter pub add firebase_data_connect
```

Next, follow the guide to [use generated Flutter SDKs](https://firebase.google.com/docs/sql-connect/flutter-sdk) with SQL Connect.

## Use SQLite or key-value storage locally

[#](#use-sqlite-or-key-value-storage-locally)

If your data doesn't need to leave the device, you can store it locally without a cloud backend:

* **SQLite:** store and query larger amounts of structured relational data on the device. Use [`sqflite`](https://pub.dev/packages/sqflite) for direct SQL queries, or [`drift`](https://pub.dev/packages/drift) for type-safe Dart queries, reactive data streams, and schema migrations across mobile, desktop, and web. For a step-by-step example, check out [Persist data with SQLite](/cookbook/persistence/sqlite), or read [Persistent storage architecture: SQL](/app-architecture/design-patterns/sql) to learn how to structure a local database layer in a larger app.
* **Key-value storage:** save small amounts of simple data, such as user preferences and settings, with the [`shared_preferences`](https://pub.dev/packages/shared_preferences) package. Get started with the [Store key-value data on disk](/cookbook/persistence/key-value) recipe, or check out [Persistent storage architecture: Key-value data](/app-architecture/design-patterns/key-value-data). To store sensitive data like authentication tokens or encrypted records, use [`flutter_secure_storage`](https://pub.dev/packages/flutter_secure_storage) instead.

By default, on-device SQLite and key-value packages store data only on the local device, so data isn't synced across devices or preserved if a user uninstalls your app without a backup or sync service. If users expect automatic access to their data across devices or after switching phones, use a cloud database like Cloud Firestore or Firebase SQL Connect instead.

## Combine cloud and local storage

[#](#combine-cloud-and-local-storage)

You don't have to pick just one option. For example, your app might use Cloud Firestore or Firebase SQL Connect for shared, synced data, alongside `shared_preferences` for device-specific settings like a dark mode toggle. To learn how to combine local and remote data sources so your app keeps working without a network connection, check out [Offline-first support](/app-architecture/design-patterns/offline-first).

Both Cloud Firestore and Firebase SQL Connect also work alongside other Firebase services, including [Firebase Authentication](https://firebase.google.com/docs/auth/flutter/start) ([`firebase_auth`](https://pub.dev/packages/firebase_auth)), [Cloud Storage for Firebase](https://firebase.google.com/docs/storage/flutter/start) ([`firebase_storage`](https://pub.dev/packages/firebase_storage)), and [Firebase AI Logic](https://firebase.google.com/docs/ai-logic/get-started) ([`firebase_ai`](https://pub.dev/packages/firebase_ai)). For example, your app can capture receipt or inspection photos, upload them to Cloud Storage, extract structured fields with Firebase AI Logic, and sync the resulting records in Cloud Firestore or SQL Connect—all within a single project.

If your app needs custom backend logic, you can also write [Cloud Functions for Firebase or backend servers in Dart](https://dart.dev/server#sharing-code-between-flutter-apps-and-dart-backends) and share a single Dart package for your data models, serialization, and validation logic across both your Flutter client and backend.

## Next steps

[#](#next-steps)

To start building with a cloud database, check out the following resources:

* [Add Firebase to your Flutter app](https://firebase.google.com/docs/flutter/setup)
* [Firebase for Flutter](/data-and-backend/firebase)
* [Get started with Cloud Firestore](https://firebase.google.com/docs/firestore/quickstart)
* [Firebase SQL Connect for Flutter](https://firebase.google.com/docs/sql-connect/flutter-sdk)

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-10-13. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/data-and-backend/persistence/choose-a-database.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/data-and-backend/persistence/choose-a-database\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/data-and-backend/persistence/choose-a-database.md "Report an issue with this page").

----
url: https://docs.flutter.dev/flutter-for/dart-swift-concurrency.md
----

\# Flutter concurrency for Swift developers > Leverage your Swift concurrency knowledge while learning Flutter and Dart.

<!--?code-excerpt path-base="resources/dart_swift_concurrency"?-->

Both Dart and Swift support concurrent programming. This guide should help you understand how concurrency works in Dart and how it compares to Swift. With this understanding, you can create high-performing iOS apps. When developing in the Apple ecosystem, some tasks might take a long time to complete. These tasks include fetching or processing large amounts of data. iOS developers typically use Grand Central Dispatch (GCD) to schedule tasks using a shared thread pool. With GCD, developers add tasks to dispatch queues and GCD decides on which thread to execute them. But, GCD spins up threads to handle remaining work items. This means you can end up with a large number of threads and the system can become over committed. With Swift, the structured concurrency model reduced the number of threads and context switches. Now, each core has only one thread. Dart has a single-threaded execution model, with support for \`Isolates\`, an event loop, and asynchronous code. An \`Isolate\` is Dart's implementation of a lightweight thread. Unless you spawn an \`Isolate\`, your Dart code runs in the main UI thread driven by an event loop. Flutter's event loop is equivalent to the iOS main loop—in other words, the Looper attached to the main thread. Dart's single-threaded model doesn't mean you are required to run everything as a blocking operation that causes the UI to freeze. Instead, use the asynchronous features that the Dart language provides, such as \`async\`/\`await\`. ## Asynchronous Programming An asynchronous operation allows other operations to execute before it completes. Both Dart and Swift support asynchronous functions using the \`async\` and \`await\` keywords. In both cases, \`async\` marks that a function performs asynchronous work, and \`await\` tells the system to await a result from function. This means that the Dart VM \_could\_ suspend the function, if necessary. For more details on asynchronous programming, check out \[Concurrency in Dart]\(https\://dart.dev/guides/language/concurrency). ### Leveraging the main thread/isolate For Apple operating systems, the primary (also called the main) thread is where the application begins running. Rendering the user interface always happens on the main thread. One difference between Swift and Dart is that Swift might use different threads for different tasks, and Swift doesn't guarantee which thread is used. So, when dispatching UI updates in Swift, you might need to ensure that the work occurs on the main thread. Say you want to write a function that fetches the weather asynchronously and displays the results. In GCD, to manually dispatch a process to the main thread, you might do something like the following. First, define the \`Weather\` \`enum\`: \`\`\`swift enum Weather: String { case rainy, sunny } \`\`\` Next, define the view model and mark it as an \[\`@Observable\`]\[] that publishes the \`result\` of type \`Weather?\`. Use GCD to create a background \`DispatchQueue\` to send the work to the pool of threads, and then dispatch back to the main thread to update the \`result\`. \`\`\`swift @Observable class ContentViewModel { private(set) var result: Weather? private let queue = DispatchQueue(label: "weather\_io\_queue") func load() { // Mimic 1 second network delay. queue.asyncAfter(deadline: .now() + 1) { \[weak self] in DispatchQueue.main.async { self?.result = .sunny } } } } \`\`\` Finally, display the results: \`\`\`swift struct ContentView: View { @State var viewModel = ContentViewModel() var body: some View { Text(viewModel.result?.rawValue ?? "Loading...") .onAppear { viewModel.load() } } } \`\`\` More recently, Swift introduced \_actors\_ to support synchronization for shared, mutable state. To ensure that work is performed on the main thread, define a view model class that is marked as a \`@MainActor\`, with a \`load()\` function that internally calls an asynchronous function using \`Task\`. \`\`\`swift @MainActor @Observable class ContentViewModel { private(set) var result: Weather? func load() async { // Mimic 1 second network delay. try? await Task.sleep(nanoseconds: 1\_000\_000\_000) self.result = .sunny } } \`\`\` Next, define the view model as a state using \`@State\`, with a \`load()\` function that can be called by the view model: \`\`\`swift struct ContentView: View { @State var viewModel = ContentViewModel() var body: some View { Text(viewModel.result?.rawValue ?? "Loading...") .task { await viewModel.load() } } } \`\`\` In Dart, all work runs on the main isolate by default. To implement the same example in Dart, first, create the \`Weather\` \`enum\`:

<!--?code-excerpt "lib/async_weather.dart (weather)"?-->

\`\`\`dart enum Weather { rainy, windy, sunny } \`\`\` Then, define a simple view model (similar to what was created in SwiftUI), to fetch the weather. In Dart, a \`Future\` object represents a value to be provided in the future. A \`Future\` is similar to Swift's \`@Observable\`. In this example, a function within the view model returns a \`Future\` object:

<!--?code-excerpt "lib/async_weather.dart (home-page-view-model)"?-->

\`\`\`dart @immutable class HomePageViewModel { const HomePageViewModel(); Future load() async { await Future.delayed(const Duration(seconds: 1)); return Weather.sunny; } } \`\`\` The \`load()\` function in this example shares similarities with the Swift code. The Dart function is marked as \`async\` because it uses the \`await\` keyword. Additionally, a Dart function marked as \`async\` automatically returns a \`Future\`. In other words, you don't have to create a \`Future\` instance manually inside functions marked as \`async\`. For the last step, display the weather value. In Flutter, \[\`FutureBuilder\`]\(https\://api.flutter.dev/flutter/widgets/FutureBuilder-class.html) and \[\`StreamBuilder\`]\(https\://api.flutter.dev/flutter/widgets/StreamBuilder-class.html) widgets are used to display the results of a Future in the UI. The following example uses a \`FutureBuilder\`:

<!--?code-excerpt "lib/async_weather.dart (home-page-widget)"?-->

\`\`\`dart class HomePage extends StatelessWidget { const HomePage({super.key}); final HomePageViewModel viewModel = const HomePageViewModel(); @override Widget build(BuildContext context) { return CupertinoPageScaffold( // Feed a FutureBuilder to your widget tree. child: FutureBuilder( // Specify the Future that you want to track. future: viewModel.load(), builder: (context, snapshot) { // A snapshot is of type \`AsyncSnapshot\` and contains the // state of the Future. By looking if the snapshot contains // an error or if the data is null, you can decide what to // show to the user. if (snapshot.hasData) { return Center(child: Text(snapshot.data.toString())); } else { return const Center(child: CupertinoActivityIndicator()); } }, ), ); } } \`\`\` For the complete example, check out the \[async\_weather]\[] file on GitHub. \[async\_weather]: https\://github.com/flutter/website/blob/main/examples/resources/dart\_swift\_concurrency/lib/async\_weather.dart ### Leveraging a background thread/isolate Flutter apps can run on a variety of multi-core hardware, including devices running macOS and iOS. To improve the performance of these applications, you must sometimes run tasks on different cores concurrently. This is especially important to avoid blocking UI rendering with long-running operations. In Swift, you can leverage GCD to run tasks on global queues with different quality of service class (qos) properties. This indicates the task's priority. \`\`\`swift func parse(string: String, completion: @escaping (\[String:Any]) -> Void) { // Mimic 1 sec delay. DispatchQueue(label: "data\_processing\_queue", qos: .userInitiated) .asyncAfter(deadline: .now() + 1) { let result: \[String:Any] = \["foo": 123] completion(result) } } } \`\`\` In Dart, you can offload computation to a worker isolate, often called a background worker. A common scenario spawns a simple worker isolate and returns the results in a message when the worker exits. You can use \`Isolate.run()\` to spawn an isolate and run computations: \`\`\`dart void main() async { // Read some data. final jsonData = await Isolate.run(() => jsonDecode(jsonString) as Map);\` // Use that data. print('Number of JSON keys: ${jsonData.length}'); } \`\`\` In Flutter, you can also use the \`compute\` function to spin up an isolate to run a callback function: \`\`\`dart final jsonData = await compute(getNumberOfKeys, jsonString); \`\`\` In this case, the callback function is a top-level function as shown below: \`\`\`dart Map getNumberOfKeys(String jsonString) { return jsonDecode(jsonString); } \`\`\` You can find more information on Dart at \[Learning Dart as a Swift developer]\[], and more information on Flutter at \[Flutter for SwiftUI developers]\[] or \[Flutter for UIKit developers]\[]. \[Learning Dart as a Swift developer]: https\://dart.dev/guides/language/coming-from/swift-to-dart \[Flutter for SwiftUI developers]: /flutter-for/swiftui-devs \[Flutter for UIKit developers]: /flutter-for/uikit-devs \[\`@Observable\`]: https\://developer.apple.com/documentation/observation/observable()

----
url: https://docs.flutter.dev/app-architecture/design-patterns.md
----

\# Architecture design patterns > A collection of articles about useful design patterns for building Flutter applications. If you've already read through the \[architecture guide]\[] page, or if you're comfortable with Flutter and the MVVM pattern, the following articles are for you. These articles aren't about high-level app architecture, rather they're about solving specific design problems that improve your application's code base regardless of how you've architected your app. That said, the articles do assume the MVVM pattern laid out on the previous pages in the code examples. \[architecture guide]: /app-architecture/guide

----
url: https://docs.flutter.dev/ai/ai-toolkit/feature-integration
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Feature integration](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

In addition to the features that are provided automatically by the [`LlmChatView`](https://pub.dev/documentation/flutter_ai_toolkit/latest/flutter_ai_toolkit/LlmChatView-class.html), a number of integration points allow your app to blend seamlessly with other features to provide additional functionality:

* **Welcome messages**: Display an initial greeting to users.
* **Suggested prompts**: Offer users predefined prompts to guide interactions.
* **System instructions**: Provide the LLM with specific input to influence its responses.
* **Disable attachments and audio input**: Remove optional parts of the chat UI.
* **Manage cancel or error behavior**: Change the user cancellation or LLM error behavior.
* **Manage history**: Every LLM provider allows for managing chat history, which is useful for clearing it, changing it dynamically and storing it between sessions.
* **Chat serialization/deserialization**: Store and retrieve conversations between app sessions.
* **Custom response widgets**: Introduce specialized UI components to present LLM responses.
* **Custom styling**: Define unique visual styles to match the chat appearance to the overall app.
* **Chat without UI**: Interact directly with the LLM providers without affecting the user's current chat session.
* **Custom LLM providers**: Build your own LLM provider for integration of chat with your own model backend.
* **Rerouting prompts**: Debug, log, or reroute messages meant for the provider to track down issues or route prompts dynamically.

## Welcome messages

[#](#welcome-messages)

The chat view allows you to provide a custom welcome message to set context for the user:

You can initialize the `LlmChatView` with a welcome message by setting the `welcomeMessage` parameter:

dart

```
class ChatPage extends StatelessWidget {
 const ChatPage({super.key});
​
 @override
 Widget build(BuildContext context) => Scaffold(
       appBar: AppBar(title: const Text(App.title)),
       body: LlmChatView(
         welcomeMessage: 'Hello and welcome to the Flutter AI Toolkit!',
         provider: FirebaseProvider(
          model: FirebaseAI.geminiAI().generativeModel(
             model: 'gemini-2.5-flash',
           ),
         ),
       ),
     );
}
```

To see a complete example of setting the welcome message, check out the [welcome example](https://github.com/flutter/ai/blob/main/example/lib/welcome/welcome.dart).

## Suggested prompts

[#](#suggested-prompts)

You can provide a set of suggested prompts to give the user some idea of what the chat session has been optimized for:

The suggestions are only shown when there is no existing chat history. Clicking one sends it immediately as a request to the underlying LLM. To set the list of suggestions, construct the `LlmChatView` with the `suggestions` parameter:

dart

```
class ChatPage extends StatelessWidget {
 const ChatPage({super.key});
​
 @override
 Widget build(BuildContext context) => Scaffold(
       appBar: AppBar(title: const Text(App.title)),
       body: LlmChatView(
         suggestions: [
           'I\'m a Star Wars fan. What should I wear for Halloween?',
           'I\'m allergic to peanuts. What candy should I avoid at Halloween?',
           'What\'s the difference between a pumpkin and a squash?',
         ],
         provider: FirebaseProvider(
          model: FirebaseAI.geminiAI().generativeModel(
             model: 'gemini-2.5-flash',
           ),
         ),
       ),
     );
}
```

To see a complete example of setting up suggestions for the user, take a look at the [suggestions example](https://github.com/flutter/ai/blob/main/example/lib/suggestions/suggestions.dart).

## LLM instructions

[#](#llm-instructions)

To optimize an LLM's responses based on the needs of your app, you'll want to give it instructions. For example, the [recipes example app](https://github.com/flutter/ai/tree/main/example/lib/recipes) uses the `systemInstructions` parameter of the `GenerativeModel` class to tailor the LLM to focus on delivering recipes based on the user's instructions:

dart

```
class _HomePageState extends State<HomePage> {
  ...
  // create a new provider with the given history and the current settings
  LlmProvider _createProvider([List<ChatMessage>? history]) => FirebaseProvider(
      history: history,
        ...,
        model: FirebaseAI.geminiAI().generativeModel(
          model: 'gemini-2.5-flash',
          ...,
          systemInstruction: Content.system('''
You are a helpful assistant that generates recipes based on the ingredients and
instructions provided as well as my food preferences, which are as follows:
${Settings.foodPreferences.isEmpty ? 'I don\'t have any food preferences' : Settings.foodPreferences}
​
You should keep things casual and friendly. You may generate multiple recipes in a single response, but only if asked. ...
''',
          ),
        ),
      );
  ...
}
```

Setting system instructions is unique to each provider; the `FirebaseProvider` allows you to provide them through the `systemInstruction` parameter.

Notice that, in this case, we're bringing in user preferences as part of the creation of the LLM provider passed to the `LlmChatView` constructor. We set the instructions as part of the creation process each time the user changes their preferences. The recipes app allows the user to change their food preferences using a drawer on the scaffold:

Whenever the user changes their food preferences, the recipes app creates a new model to use the new preferences:

dart

```
class _HomePageState extends State<HomePage> {
  ...
  void _onSettingsSave() => setState(() {
        // move the history over from the old provider to the new one
        final history = _provider.history.toList();
        _provider = _createProvider(history);
      });
}
```

## Function calling

[#](#function-calling)

To enable the LLM to perform actions on behalf of the user, you can provide a set of tools (functions) that the LLM can call. The `FirebaseProvider` supports function calling out of the box. It handles the loop of sending the user's prompt, receiving a function call request from the LLM, executing the function, and sending the result back to the LLM until a final text response is generated.

To use function calling, you need to define your tools and pass them to the `FirebaseProvider`. Check out the [function calling example](https://github.com/flutter/ai/blob/main/example/lib/function_calls/function_calls.dart) for details.

## Disable attachments and audio input

[#](#disable-attachments-and-audio-input)

If you'd like to disable attachments (the **+** button) or audio input (the mic button), you can do so with the `enableAttachments` and `enableVoiceNotes` parameters to the `LlmChatView` constructor:

dart

```
class ChatPage extends StatelessWidget {
  const ChatPage({super.key});
​
  @override
  Widget build(BuildContext context) {
    // ...
​
    return Scaffold(
      appBar: AppBar(title: const Text('Restricted Chat')),
      body: LlmChatView(
        // ...
        enableAttachments: false,
        enableVoiceNotes: false,
      ),
    );
  }
}
```

Both of these flags default to `true`.

## Custom speech-to-text

[#](#custom-speech-to-text)

By default, the AI Toolkit uses the `LlmProvider` to pass to the `LlmChatView` to provide the speech-to-text implementation. If you'd like to provide your own implementation, for example to use a device-specific service, you can do so by implementing the `SpeechToText` interface and passing it to the `LlmChatView` constructor:

dart

```
LlmChatView(
  // ...
  speechToText: MyCustomSpeechToText(),
)
```

Check out the [custom STT example](https://github.com/flutter/ai/tree/main/example/lib/custom_stt) for details.

## Manage cancel or error behavior

[#](#manage-cancel-or-error-behavior)

By default, when the user cancels an LLM request, the LLM's response will be appended with the string "CANCEL" and a message will pop up that the user has canceled the request. Likewise, in the event of an LLM error, like a dropped network connection, the LLM's response will be appended with the string "ERROR" and an alert dialog will pop up with the details of the error.

You can override the cancel and error behavior with the `cancelMessage`, `errorMessage`, `onCancelCallback` and `onErrorCallback` parameters of the `LlmChatView`. For example, the following code replaces the default cancellation handling behavior:

dart

```
class ChatPage extends StatelessWidget {
  // ...
​
  void _onCancel(BuildContext context) {
    ScaffoldMessenger.of(
      context,
    ).showSnackBar(const SnackBar(content: Text('Chat cancelled')));
  }
​
  @override
  Widget build(BuildContext context) => Scaffold(
    appBar: AppBar(title: const Text(App.title)),
    body: LlmChatView(
      // ...
      onCancelCallback: _onCancel,
      cancelMessage: 'Request cancelled',
    ),
  );
}
```

You can override any or all of these parameters and the `LlmChatView` will use its defaults for anything you don't override.

## Manage history

[#](#manage-history)

The [standard interface that defines all LLM providers](https://pub.dev/documentation/flutter_ai_toolkit/latest/flutter_ai_toolkit/LlmProvider-class.html) that can plug into the chat view includes the ability to get and set history for the provider:

dart

```
abstract class LlmProvider implements Listenable {
  Stream<String> generateStream(
    String prompt, {
    Iterable<Attachment> attachments,
  });
​
  Stream<String> sendMessageStream(
    String prompt, {
    Iterable<Attachment> attachments,
  });
​
  Iterable<ChatMessage> get history;
  set history(Iterable<ChatMessage> history);
}
```

When the history for a provider changes, it calls the `notifyListener` method exposed by the `Listenable` base class. This means that you manually subscribe/unsubscribe with the `add` and `remove` methods or use it to construct an instance of the `ListenableBuilder` class.

The `generateStream` method calls into the underlying LLM without affecting the history. Calling the `sendMessageStream` method changes the history by adding two new messages to the provider's history—one for the user message and one for the LLM response—when the response is completed. The chat view uses `sendMessageStream` when it processes a user's chat prompt and `generateStream` when it's processing the user's voice input.

To see or set the history, you can access the `history` property:

dart

```
void _clearHistory() => _provider.history = [];
```

The ability to access a provider's history is also useful when it comes to recreating a provider while maintaining the history:

dart

```
class _HomePageState extends State<HomePage> {
  ...
  void _onSettingsSave() => setState(() {
        // move the history over from the old provider to the new one
        final history = _provider.history.toList();
        _provider = _createProvider(history);
      });
}
```

The `_createProvider` method creates a new provider with the history from the previous provider *and* the new user preferences. It's seamless for the user; they can keep chatting away but now the LLM gives them responses taking their new food preferences into account. For example:

dart

```
class _HomePageState extends State<HomePage> {
  ...
  // create a new provider with the given history and the current settings
  LlmProvider _createProvider([List<ChatMessage>? history]) =>
    FirebaseProvider(
      history: history,
      ...
    );
  ...
}
```

To see history in action, check out the [recipes example app](https://github.com/flutter/ai/tree/main/example/lib/recipes) and the [history example app](https://github.com/flutter/ai/blob/main/example/lib/history/history.dart).

## Chat serialization/deserialization

[#](#chat-serializationdeserialization)

To save and restore chat history between sessions of an app requires the ability to serialize and deserialize each user prompt, including the attachments, and each LLM response. Both kinds of messages (the user prompts and LLM responses), are exposed in the `ChatMessage` class. Serialization can be accomplished by using the `toJson` method of each `ChatMessage` instance.

dart

```
Future<void> _saveHistory() async {
  // get the latest history
  final history = _provider.history.toList();
​
  // write the new messages
  for (var i = 0; i != history.length; ++i) {
    // skip if the file already exists
    final file = await _messageFile(i);
    if (file.existsSync()) continue;
​
    // write the new message to disk
    final map = history[i].toJson();
    final json = JsonEncoder.withIndent('  ').convert(map);
    await file.writeAsString(json);
  }
}
```

Likewise, to deserialize, use the static `fromJson` method of the `ChatMessage` class:

dart

```
Future<void> _loadHistory() async {
  // read the history from disk
  final history = <ChatMessage>[];
  for (var i = 0;; ++i) {
    final file = await _messageFile(i);
    if (!file.existsSync()) break;
​
    final map = jsonDecode(await file.readAsString());
    history.add(ChatMessage.fromJson(map));
  }
​
  // set the history on the controller
  _provider.history = history;
}
```

To ensure fast turnaround when serializing, we recommend only writing each user message once. Otherwise, the user must wait for your app to write every message every time and, in the face of binary attachments, that could take a while.

To see this in action, check out the [history example app](https://github.com/flutter/ai/blob/main/example/lib/history/history.dart).

## Custom response widgets

[#](#custom-response-widgets)

By default, the LLM response shown by the chat view is formatted Markdown. However, in some cases, you want to create a custom widget to show the LLM response that's specific to and integrated with your app. For example, when the user requests a recipe in the [recipes example app](https://github.com/flutter/ai/tree/main/example/lib/recipes), the LLM response is used to create a widget that's specific to showing recipes just like the rest of the app does and to provide for an **Add** button in case the user would like to add the recipe to their database:

This is accomplished by setting the `responseBuilder` parameter of the `LlmChatView` constructor:

dart

```
LlmChatView(
  provider: _provider,
  welcomeMessage: _welcomeMessage,
  responseBuilder: (context, response) => RecipeResponseView(
    response,
  ),
),
```

In this particular example, the `RecipeResponseView` widget is constructed with the LLM provider's response text and uses that to implement its `build` method:

dart

```
class RecipeResponseView extends StatelessWidget {
  const RecipeResponseView(this.response, {super.key});
  final String response;
​
  @override
  Widget build(BuildContext context) {
    final children = <Widget>[];
    String? finalText;
​
    // created with the response from the LLM as the response streams in, so
    // many not be a complete response yet
    try {
      final map = jsonDecode(response);
      final recipesWithText = map['recipes'] as List<dynamic>;
      finalText = map['text'] as String?;
​
      for (final recipeWithText in recipesWithText) {
        // extract the text before the recipe
        final text = recipeWithText['text'] as String?;
        if (text != null && text.isNotEmpty) {
          children.add(MarkdownBody(data: text));
        }
​
        // extract the recipe
        final json = recipeWithText['recipe'] as Map<String, dynamic>;
        final recipe = Recipe.fromJson(json);
        children.add(const Gap(16));
        children.add(Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text(recipe.title, style: Theme.of(context).textTheme.titleLarge),
            Text(recipe.description),
            RecipeContentView(recipe: recipe),
          ],
        ));
​
        // add a button to add the recipe to the list
        children.add(const Gap(16));
        children.add(OutlinedButton(
          onPressed: () => RecipeRepository.addNewRecipe(recipe),
          child: const Text('Add Recipe'),
        ));
        children.add(const Gap(16));
      }
    } catch (e) {
      debugPrint('Error parsing response: $e');
    }
​
    ...
​
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: children,
    );
  }
}
```

This code parses the text to extract introductory text and the recipe from the LLM, bundling them together with an **Add Recipe** button to show in place of the Markdown.

Notice that we're parsing the LLM response as JSON. It's common to set the provider into JSON mode and to provide a schema to restrict the format of its responses to ensure that we've got something we can parse. Each provider exposes this functionality in its own way, but the `FirebaseProvider` class enables this with a `GenerationConfig` object that the recipes example uses as follows:

dart

```
class _HomePageState extends State<HomePage> {
  ...
​
  // create a new provider with the given history and the current settings
  LlmProvider _createProvider([List<ChatMessage>? history]) => FirebaseProvider(
        ...
        model: FirebaseAI.geminiAI().generativeModel(
          ...
          generationConfig: GenerationConfig(
            responseMimeType: 'application/json',
            responseSchema: Schema(...),
          systemInstruction: Content.system('''
...
Generate each response in JSON format
with the following schema, including one or more "text" and "recipe" pairs as
well as any trailing text commentary you care to provide:
​
{
  "recipes": [
    {
      "text": "Any commentary you care to provide about the recipe.",
      "recipe":
      {
        "title": "Recipe Title",
        "description": "Recipe Description",
        "ingredients": ["Ingredient 1", "Ingredient 2", "Ingredient 3"],
        "instructions": ["Instruction 1", "Instruction 2", "Instruction 3"]
      }
    }
  ],
  "text": "any final commentary you care to provide",
}
''',
          ),
        ),
      );
  ...
}
```

This code initializes the `GenerationConfig` object by setting the `responseMimeType` parameter to `'application/json'` and the `responseSchema` parameter to an instance of the `Schema` class that defines the structure of the JSON that you're prepared to parse. In addition, it's good practice to also ask for JSON and to provide a description of that JSON schema in the system instructions, which we've done here.

To see this in action, check out the [recipes example app](https://github.com/flutter/ai/tree/main/example/lib/recipes).

## Custom styling

[#](#custom-styling)

The chat view comes out of the box with a set of default styles for the background, the text field, the buttons, the icons, the suggestions, and so on. You can fully customize those styles by setting your own by using the `style` parameter to the `LlmChatView` constructor:

dart

```
LlmChatView(
  provider: FirebaseProvider(...),
  style: LlmChatViewStyle(...),
),
```

For example, the [custom styles example app](https://github.com/flutter/ai/blob/main/example/lib/custom_styles/custom_styles.dart) uses this feature to implement an app with a Halloween theme:

For a complete list of the styles available in the `LlmChatViewStyle` class, check out the [reference documentation](https://pub.dev/documentation/flutter_ai_toolkit/latest/flutter_ai_toolkit/LlmChatViewStyle-class.html). You can also customize the appearance of the voice recorder using the `voiceNoteRecorderStyle` parameter of the `LlmChatViewStyle` class, which is demonstrated in the [styles example](https://github.com/flutter/ai/blob/main/example/lib/styles/styles.dart).

To see custom styles in action, in addition to the [custom styles example](https://github.com/flutter/ai/blob/main/example/lib/custom_styles/custom_styles.dart) and the [styles example](https://github.com/flutter/ai/blob/main/example/lib/styles/styles.dart), check out the [dark mode example](https://github.com/flutter/ai/blob/main/example/lib/dark_mode/dark_mode.dart) and the [demo app](https://github.com/flutter/ai#online-demo).

## Chat without UI

[#](#chat-without-ui)

You don't have to use the chat view to access the functionality of the underlying provider. In addition to being able to simply call it with whatever proprietary interface it provides, you can also use it with the [LlmProvider interface](https://pub.dev/documentation/flutter_ai_toolkit/latest/flutter_ai_toolkit/LlmProvider-class.html).

As an example, the recipes example app provides a Magic button on the page for editing recipes. The purpose of that button is to update an existing recipe in your database with your current food preferences. Pressing the button allows you to preview the recommended changes and decide whether you'd like to apply them or not:

Instead of using the same provider that the chat portion of the app uses, which would insert spurious user messages and LLM responses into the user's chat history, the Edit Recipe page instead creates its own provider and uses it directly:

dart

```
class _EditRecipePageState extends State<EditRecipePage> {
  ...
  final _provider = FirebaseProvider(...);
  ...
  Future<void> _onMagic() async {
    final stream = _provider.sendMessageStream(
      'Generate a modified version of this recipe based on my food preferences: '
      '${_ingredientsController.text}\n\n${_instructionsController.text}',
    );
    var response = await stream.join();
    final json = jsonDecode(response);
​
    try {
      final modifications = json['modifications'];
      final recipe = Recipe.fromJson(json['recipe']);
​
      if (!context.mounted) return;
      final accept = await showDialog<bool>(
        context: context,
        builder: (context) => AlertDialog(
          title: Text(recipe.title),
          content: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              const Text('Modifications:'),
              const Gap(16),
              Text(_wrapText(modifications)),
            ],
          ),
          actions: [
            TextButton(
              onPressed: () => context.pop(true),
              child: const Text('Accept'),
            ),
            TextButton(
              onPressed: () => context.pop(false),
              child: const Text('Reject'),
            ),
          ],
        ),
      );
      ...
    } catch (ex) {
      ...
      }
    }
  }
}
```

The call to `sendMessageStream` creates entries in the provider's history, but since it's not associated with a chat view, they won't be shown. If it's convenient, you can also accomplish the same thing by calling `generateStream`, which allows you to reuse an existing provider without affecting the chat history.

To see this in action, check out the [Edit Recipe page](https://github.com/flutter/ai/blob/main/example/lib/recipes/pages/edit_recipe_page.dart) of the recipes example.

## Rerouting prompts

[#](#rerouting-prompts)

If you'd like to debug, log, or manipulate the connection between the chat view and the underlying provider, you can do so with an implementation of an [`LlmStreamGenerator`](https://pub.dev/documentation/flutter_ai_toolkit/latest/flutter_ai_toolkit/LlmStreamGenerator.html) function. You then pass that function to the `LlmChatView` in the `messageSender` parameter:

dart

```
class ChatPage extends StatelessWidget {
  final _provider = FirebaseProvider(...);
​
  @override
  Widget build(BuildContext context) => Scaffold(
      appBar: AppBar(title: const Text(App.title)),
      body: LlmChatView(
        provider: _provider,
        messageSender: _logMessage,
      ),
    );
​
  Stream<String> _logMessage(
    String prompt, {
    required Iterable<Attachment> attachments,
  }) async* {
    // log the message and attachments
    debugPrint('# Sending Message');
    debugPrint('## Prompt\n$prompt');
    debugPrint('## Attachments\n${attachments.map((a) => a.toString())}');
​
    // forward the message on to the provider
    final response = _provider.sendMessageStream(
      prompt,
      attachments: attachments,
    );
​
    // log the response
    final text = await response.join();
    debugPrint('## Response\n$text');
​
    // return it
    yield text;
  }
}
```

This example logs the user prompts and LLM responses as they go back and forth. When providing a function as a `messageSender`, it's your responsibility to call the underlying provider. If you don't, it won't get the message. This capability allows you to do advanced things like routing to a provider dynamically or Retrieval Augmented Generation (RAG).

To see this in action, check out the [logging example app](https://github.com/flutter/ai/blob/main/example/lib/logging/logging.dart).

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-08-19. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/ai/ai-toolkit/feature-integration.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/ai/ai-toolkit/feature-integration\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/ai/ai-toolkit/feature-integration.md "Report an issue with this page").

----
url: https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.9.1
----

The 2.9.1 release of the Dart and Flutter DevTools includes the following changes among other general improvements. To learn more about DevTools, check out the [DevTools overview](https://docs.flutter.dev/tools/devtools).

## Debugger updates

[#](#debugger-updates)

* Improve support for inspecting large lists and maps in the Debugger variables pane - [#3497](https://github.com/flutter/devtools/pull/3497)

* Added support for selecting objects in the program explorer outline view. Selecting an object will automatically scroll the source code in the debugger to the selected object - [#3480](https://github.com/flutter/devtools/pull/3480)

## Performance updates

[#](#performance-updates)

* Fix bugs with performance page search and improve performance - [#3515](https://github.com/flutter/devtools/pull/3515)

* Added an enhanced tooltip for flutter frames - [#3493](https://github.com/flutter/devtools/pull/3493)

## Full commit history

[#](#full-commit-history)

To find a complete list of changes since the previous release, check out [the diff on GitHub](https://github.com/flutter/devtools/compare/v2.8.0...v2.9.1).

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/release-notes/release-notes-2.9.1.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.9.1\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/release-notes/release-notes-2.9.1.md "Report an issue with this page").

----
url: https://docs.flutter.dev/perf/faq.md
----

\# Performance FAQ > Frequently asked questions about Flutter performance This page collects some frequently asked questions about evaluating and debugging Flutter's performance. \* Which performance dashboards have metrics that are related to Flutter? \* \[Flutter dashboard on appspot]\[] \* \[Flutter Skia dashboard]\[] \* \[Flutter Engine Skia dashboard]\[] \[Flutter dashboard on appspot]: https\://flutter-dashboard.appspot.com/ \[Flutter engine Skia dashboard]: https\://flutter-engine-perf.skia.org/t/?subset=regressions \[Flutter Skia dashboard]: https\://flutter-flutter-perf.skia.org/t/?subset=regressions \* How do I add a benchmark to Flutter? \* \[How to write a render speed test for Flutter]\[speed-test] \* \[How to write a memory test for Flutter]\[memory-test] \[memory-test]: https\://github.com/flutter/flutter/blob/main/docs/contributing/testing/How-to-write-a-memory-test-for-Flutter.md \[speed-test]: https\://github.com/flutter/flutter/blob/main/docs/contributing/testing/How-to-write-a-render-speed-test-for-Flutter.md \* What are some tools for capturing and analyzing performance metrics? \* \[Dart/Flutter DevTools]\(/tools/devtools) \* \[Apple instruments]\(https\://en.wikipedia.org/wiki/Instruments\_(software)) \* \[Linux perf]\(https\://en.wikipedia.org/wiki/Perf\_(Linux)) \* \[Chrome tracing (enter \`about:tracing\` in your Chrome URL field)]\[tracing] \* \[Android systrace (\`adb systrace\`)]\[systrace] \* \[Fuchsia \`fx traceutil\`]\[traceutil] \* \[Perfetto]\(https\://ui.perfetto.dev/) \* \[speedscope]\(https\://www\.speedscope.app/) \[systrace]: https\://developer.android.com/studio/profile/systrace \[tracing]: https\://www\.chromium.org/developers/how-tos/trace-event-profiling-tool \[traceutil]: https\://fuchsia.dev/fuchsia-src/development/tracing/usage-guide \* My Flutter app looks janky or stutters. How do I fix it? \* \[Improving rendering performance]\[] \[Improving rendering performance]: /perf/rendering-performance \* What are some costly performance operations that I need to be careful with? \* \[\`Opacity\`]\[], \[\`Clip.antiAliasWithSaveLayer\`]\[], or anything that triggers \[\`saveLayer\`]\[] \* \[\`ImageFilter\`]\[] \* Also see \[Performance best practices]\[] \[\`Clip.antiAliasWithSaveLayer\`]: https\://api.flutter.dev/flutter/dart-ui/Clip.html#antiAliasWithSaveLayer \[\`ImageFilter\`]: https\://api.flutter.dev/flutter/dart-ui/ImageFilter-class.html \[\`Opacity\`]: https\://api.flutter.dev/flutter/widgets/Opacity-class.html \[Performance best practices]: /perf/best-practices \[\`savelayer\`]: https\://api.flutter.dev/flutter/dart-ui/Canvas/saveLayer.html \* How do I tell which widgets in my Flutter app are rebuilt in each frame? \* Set \[\`debugProfileBuildsEnabled\`]\[] true in \[widgets/debug.dart]\[debug.dart]. \* Alternatively, change the \`performRebuild\` function in \[widgets/framework.dart]\[framework.dart] to ignore \`debugProfileBuildsEnabled\` and always call \`Timeline.startSync(...)/finish\`. \* If you use IntelliJ, a GUI view of this data is available. Select \*\*Track widget rebuilds\*\*, and your IDE displays which the widgets rebuild. \[\`debugProfileBuildsEnabled\`]: https\://api.flutter.dev/flutter/widgets/debugProfileBuildsEnabled.html \[debug.dart]: https\://github.com/flutter/flutter/blob/main/packages/flutter/lib/src/widgets/debug.dart \[framework.dart]: https\://github.com/flutter/flutter/blob/main/packages/flutter/lib/src/widgets/framework.dart \* How do I query the target frames per second (of the display)? \* \[Get the display refresh rate]\[] \[Get the display refresh rate]: https\://github.com/flutter/flutter/blob/main/docs/engine/Engine-specific-Service-Protocol-extensions.md#get-the-display-refresh-rate-\_fluttergetdisplayrefreshrate \* How to solve my app's poor animations caused by an expensive Dart async function call that is blocking the UI thread? \* Spawn another isolate using the \[\`compute()\`]\[] method, as demonstrated in \[Parse JSON in the background]\[] cookbook. \[\`compute()\`]: https\://api.flutter.dev/flutter/foundation/compute-constant.html \[Parse JSON in the background]: /cookbook/networking/background-parsing \* How do I determine my Flutter app's package size that a user will download? \* See \[Measuring your app's size]\[] \[Measuring your app's size]: /perf/app-size \* How do I see the breakdown of the Flutter engine size? \* Visit the \[binary size dashboard]\[], and replace the git hash in the URL with a recent commit hash from \[Flutter's GitHub commits]\[]. \[binary size dashboard]: https\://storage.googleapis.com/flutter\_infra\_release/flutter/241c87ad800beeab545ab867354d4683d5bfb6ce/android-arm-release/sizes/index.html \[Flutter's GitHub commits]: https\://github.com/flutter/flutter/commits/main \* How can I take a screenshot of an app that is running and export it as a SKP file? \* Run \`flutter screenshot --type=skia --observatory-uri=...\` \* Note a known issue viewing screenshots: \* \[Issue 21237]\[]: Doesn't record images in real devices. \* To analyze and visualize the SKP file, check out the \[Skia WASM debugger]\[]. \[Issue 21237]: https\://github.com/flutter/flutter/issues/21237 \[Skia WASM debugger]: https\://debugger.skia.org/ \* How do I retrieve the shader persistent cache from a device? \* On Android, you can do the following: \`\`\`console adb shell run-as cp -r adb pull \`\`\` \* How do I perform a trace in Fuchsia? \* See \[Fuchsia tracing guidelines]\[traceutil]

----
url: https://docs.flutter.dev/ui/interactivity/actions-and-shortcuts
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Using Actions and Shortcuts](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

This page describes how to bind physical keyboard events to actions in the user interface. For instance, to define keyboard shortcuts in your application, this page is for you.

## Overview

[#](#overview)

For a GUI application to do anything, it has to have actions: users want to tell the application to *do* something. Actions are often simple functions that directly perform the action (such as set a value or save a file). In a larger application, however, things are more complex: the code for invoking the action, and the code for the action itself might need to be in different places. Shortcuts (key bindings) might need definition at a level that knows nothing about the actions they invoke.

That's where Flutter's actions and shortcuts system comes in. It allows developers to define actions that fulfill intents bound to them. In this context, an intent is a generic action that the user wishes to perform, and an [`Intent`](https://api.flutter.dev/flutter/widgets/Intent-class.html) class instance represents these user intents in Flutter. An `Intent` can be general purpose, fulfilled by different actions in different contexts. An [`Action`](https://api.flutter.dev/flutter/widgets/Action-class.html) can be a simple callback (as in the case of the [`CallbackAction`](https://api.flutter.dev/flutter/widgets/CallbackAction-class.html)) or something more complex that integrates with entire undo/redo architectures (for example) or other logic.

[`Shortcuts`](https://api.flutter.dev/flutter/widgets/Shortcuts-class.html) are key bindings that activate by pressing a key or combination of keys. The key combinations reside in a table with their bound intent. When the `Shortcuts` widget invokes them, it sends their matching intent to the actions subsystem for fulfillment.

To illustrate the concepts in actions and shortcuts, this article creates a simple app that allows a user to select and copy text in a text field using both buttons and shortcuts.

### Why separate Actions from Intents?

[#](#why-separate-actions-from-intents)

You might wonder: why not just map a key combination directly to an action? Why have intents at all? This is because it is useful to have a separation of concerns between where the key mapping definitions are (often at a high level), and where the action definitions are (often at a low level), and because it is important to be able to have a single key combination map to an intended operation in an app, and have it adapt automatically to whichever action fulfills that intended operation for the focused context.

For instance, Flutter has an `ActivateIntent` widget that maps each type of control to its corresponding version of an `ActivateAction` (and that executes the code that activates the control). This code often needs fairly private access to do its work. If the extra layer of indirection that `Intent`s provide didn't exist, it would be necessary to elevate the definition of the actions to where the defining instance of the `Shortcuts` widget could see them, causing the shortcuts to have more knowledge than necessary about which action to invoke, and to have access to or provide state that it wouldn't necessarily have or need otherwise. This allows your code to separate the two concerns to be more independent.

Intents configure an action so that the same action can serve multiple uses. An example of this is `DirectionalFocusIntent`, which takes a direction to move the focus in, allowing the `DirectionalFocusAction` to know which direction to move the focus. Just be careful: don't pass state in the `Intent` that applies to all invocations of an `Action`: that kind of state should be passed to the constructor of the `Action` itself, to keep the `Intent` from needing to know too much.

### Why not use callbacks?

[#](#why-not-use-callbacks)

You also might wonder: why not just use a callback instead of an `Action` object? The main reason is that it's useful for actions to decide whether they are enabled by implementing `isEnabled`. Also, it is often helpful if the key bindings, and the implementation of those bindings, are in different places.

If all you need are callbacks without the flexibility of `Actions` and `Shortcuts`, you can use the [`CallbackShortcuts`](https://api.flutter.dev/flutter/widgets/CallbackShortcuts-class.html) widget:

dart

```
@override
Widget build(BuildContext context) {
  return CallbackShortcuts(
    bindings: <ShortcutActivator, VoidCallback>{
      const SingleActivator(LogicalKeyboardKey.arrowUp): () {
        setState(() => count = count + 1);
      },
      const SingleActivator(LogicalKeyboardKey.arrowDown): () {
        setState(() => count = count - 1);
      },
    },
    child: Focus(
      autofocus: true,
      child: Column(
        children: <Widget>[
          const Text('Press the up arrow key to add to the counter'),
          const Text('Press the down arrow key to subtract from the counter'),
          Text('count: $count'),
        ],
      ),
    ),
  );
}
```

## Shortcuts

[#](#shortcuts)

As you'll see below, actions are useful on their own, but the most common use case involves binding them to a keyboard shortcut. This is what the `Shortcuts` widget is for.

It is inserted into the widget hierarchy to define key combinations that represent the user's intent when that key combination is pressed. To convert that intended purpose for the key combination into a concrete action, the `Actions` widget used to map the `Intent` to an `Action`. For instance, you can define a `SelectAllIntent`, and bind it to your own `SelectAllAction` or to your `CanvasSelectAllAction`, and from that one key binding, the system invokes either one, depending on which part of your application has focus. Let's see how the key binding part works:

dart

```
@override
Widget build(BuildContext context) {
  return Shortcuts(
    shortcuts: <LogicalKeySet, Intent>{
      LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.keyA):
          const SelectAllIntent(),
    },
    child: Actions(
      dispatcher: LoggingActionDispatcher(),
      actions: <Type, Action<Intent>>{
        SelectAllIntent: SelectAllAction(model),
      },
      child: Builder(
        builder: (context) => TextButton(
          onPressed: Actions.handler<SelectAllIntent>(
            context,
            const SelectAllIntent(),
          ),
          child: const Text('SELECT ALL'),
        ),
      ),
    ),
  );
}
```

The map given to a `Shortcuts` widget maps a `LogicalKeySet` (or a `ShortcutActivator`, see note below) to an `Intent` instance. The logical key set defines a set of one or more keys, and the intent indicates the intended purpose of the keypress. The `Shortcuts` widget looks up key presses in the map, to find an `Intent` instance, which it gives to the action's `invoke()` method.

### The ShortcutManager

[#](#the-shortcutmanager)

The shortcut manager, a longer-lived object than the `Shortcuts` widget, passes on key events when it receives them. It contains the logic for deciding how to handle the keys, the logic for walking up the tree to find other shortcut mappings, and maintains a map of key combinations to intents.

While the default behavior of the `ShortcutManager` is usually desirable, the `Shortcuts` widget takes a `ShortcutManager` that you can subclass to customize its functionality.

For example, if you wanted to log each key that a `Shortcuts` widget handled, you could make a `LoggingShortcutManager`:

dart

```
class LoggingShortcutManager extends ShortcutManager {
  @override
  KeyEventResult handleKeypress(BuildContext context, KeyEvent event) {
    final KeyEventResult result = super.handleKeypress(context, event);
    if (result == KeyEventResult.handled) {
      print('Handled shortcut $event in $context');
    }
    return result;
  }
}
```

Now, every time the `Shortcuts` widget handles a shortcut, it prints out the key event and relevant context.

## Actions

[#](#actions)

`Actions` allow for the definition of operations that the application can perform by invoking them with an `Intent`. Actions can be enabled or disabled, and receive the intent instance that invoked them as an argument to allow configuration by the intent.

### Defining actions

[#](#defining-actions)

Actions, in their simplest form, are just subclasses of `Action<Intent>` with an `invoke()` method. Here's a simple action that simply invokes a function on the provided model:

dart

```
class SelectAllAction extends Action<SelectAllIntent> {
  SelectAllAction(this.model);
​
  final Model model;
​
  @override
  void invoke(covariant SelectAllIntent intent) => model.selectAll();
}
```

Or, if it's too much of a bother to create a new class, use a `CallbackAction`:

dart

```
CallbackAction(onInvoke: (intent) => model.selectAll());
```

Once you have an action, you add it to your application using the [`Actions`](https://api.flutter.dev/flutter/widgets/Actions-class.html) widget, which takes a map of `Intent` types to `Action`s:

dart

```
@override
Widget build(BuildContext context) {
  return Actions(
    actions: <Type, Action<Intent>>{SelectAllIntent: SelectAllAction(model)},
    child: child,
  );
}
```

The `Shortcuts` widget uses the `Focus` widget's context and `Actions.invoke` to find which action to invoke. If the `Shortcuts` widget doesn't find a matching intent type in the first `Actions` widget encountered, it considers the next ancestor `Actions` widget, and so on, until it reaches the root of the widget tree, or finds a matching intent type and invokes the corresponding action.

### Invoking Actions

[#](#invoking-actions)

The actions system has several ways to invoke actions. By far the most common way is through the use of a `Shortcuts` widget covered in the previous section, but there are other ways to interrogate the actions subsystem and invoke an action. It's possible to invoke actions that are not bound to keys.

For instance, to find an action associated with an intent, you can use:

dart

```
Action<SelectAllIntent>? selectAll = Actions.maybeFind<SelectAllIntent>(
  context,
);
```

This returns an `Action` associated with the `SelectAllIntent` type if one is available in the given `context`. If one isn't available, it returns null. If an associated `Action` should always be available, then use `find` instead of `maybeFind`, which throws an exception when it doesn't find a matching `Intent` type.

To invoke the action (if it exists), call:

dart

```
Object? result;
if (selectAll != null) {
  result = Actions.of(context)
      .invokeAction(selectAll, const SelectAllIntent());
}
```

Combine that into one call with the following:

dart

```
Object? result = Actions.maybeInvoke<SelectAllIntent>(
  context,
  const SelectAllIntent(),
);
```

Sometimes you want to invoke an action as a result of pressing a button or another control. You can do this with the `Actions.handler` function. If the intent has a mapping to an enabled action, the `Actions.handler` function creates a handler closure. However, if it doesn't have a mapping, it returns `null`. This allows the button to be disabled if there is no enabled action that matches in the context.

dart

```
@override
Widget build(BuildContext context) {
  return Actions(
    actions: <Type, Action<Intent>>{SelectAllIntent: SelectAllAction(model)},
    child: Builder(
      builder: (context) => TextButton(
        onPressed: Actions.handler<SelectAllIntent>(
          context,
          SelectAllIntent(controller: controller),
        ),
        child: const Text('SELECT ALL'),
      ),
    ),
  );
}
```

The `Actions` widget only invokes actions when `isEnabled(Intent intent)` returns true, allowing the action to decide if the dispatcher should consider it for invocation. If the action isn't enabled, the action is not invoked and the framework stops searching for matching actions at that point.

The previous example uses a `Builder` because `Actions.handler` and `Actions.invoke` (for example) only finds actions in the provided `context`, and if the example passes the `context` given to the `build` function, the framework starts looking *above* the current widget. Using a `Builder` allows the framework to find the actions defined in the same `build` function.

You can invoke an action without needing a `BuildContext`, but since the `Actions` widget requires a context to find an enabled action to invoke, you need to provide one, either by creating your own `Action` instance, or by finding one in an appropriate context with `Actions.find`.

To invoke the action, pass the action to the `invoke` method on an `ActionDispatcher`, either one you created yourself, or one retrieved from an existing `Actions` widget using the `Actions.of(context)` method. Check whether the action is enabled before calling `invoke`. Of course, you can also just call `invoke` on the action itself, passing an `Intent`, but then you opt out of any services that an action dispatcher might provide (like logging, undo/redo, and so on).

### Action dispatchers

[#](#action-dispatchers)

Most of the time, you just want to invoke an action, have it do its thing, and forget about it. Sometimes, however, you might want to log the executed actions.

This is where replacing the default `ActionDispatcher` with a custom dispatcher comes in. You pass your `ActionDispatcher` to the `Actions` widget, and it invokes actions from any `Actions` widgets below that one that doesn't set a dispatcher of its own.

The first thing `Actions` does when invoking an action is look up the `ActionDispatcher` and pass the action to it for invocation. If there is none, it creates a default `ActionDispatcher` that simply invokes the action.

If you want a log of all the actions invoked, however, you can create your own `LoggingActionDispatcher` to do the job:

dart

```
class LoggingActionDispatcher extends ActionDispatcher {
  @override
  Object? invokeAction(
    covariant Action<Intent> action,
    covariant Intent intent, [
    BuildContext? context,
  ]) {
    print('Action invoked: $action($intent) from $context');
    super.invokeAction(action, intent, context);
​
    return null;
  }
​
  @override
  (bool, Object?) invokeActionIfEnabled(
    covariant Action<Intent> action,
    covariant Intent intent, [
    BuildContext? context,
  ]) {
    print('Action invoked: $action($intent) from $context');
    return super.invokeActionIfEnabled(action, intent, context);
  }
}
```

Then you pass that to your top-level `Actions` widget:

dart

```
@override
Widget build(BuildContext context) {
  return Actions(
    dispatcher: LoggingActionDispatcher(),
    actions: <Type, Action<Intent>>{SelectAllIntent: SelectAllAction(model)},
    child: Builder(
      builder: (context) => TextButton(
        onPressed: Actions.handler<SelectAllIntent>(
          context,
          const SelectAllIntent(),
        ),
        child: const Text('SELECT ALL'),
      ),
    ),
  );
}
```

This logs every action as it executes, like so:

```
flutter: Action invoked: SelectAllAction#906fc(SelectAllIntent#a98e3) from Builder(dependencies: _[ActionsMarker])
```

## Putting it together

[#](#putting-it-together)

The combination of `Actions` and `Shortcuts` is powerful: you can define generic intents that map to specific actions at the widget level. Here's a simple app that illustrates the concepts described above. The app creates a text field that also has "select all" and "copy to clipboard" buttons next to it. The buttons invoke actions to accomplish their work. All the invoked actions and shortcuts are logged.

```
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

/// A text field that also has buttons to select all the text and copy the
/// selected text to the clipboard.
class CopyableTextField extends StatefulWidget {
  const CopyableTextField({super.key, required this.title});

  final String title;

  @override
  State<CopyableTextField> createState() => _CopyableTextFieldState();
}

class _CopyableTextFieldState extends State<CopyableTextField> {
  late final TextEditingController controller = TextEditingController();
  late final FocusNode focusNode = FocusNode();

  @override
  void dispose() {
    controller.dispose();
    focusNode.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Actions(
      dispatcher: LoggingActionDispatcher(),
      actions: <Type, Action<Intent>>{
        ClearIntent: ClearAction(controller),
        CopyIntent: CopyAction(controller),
        SelectAllIntent: SelectAllAction(controller, focusNode),
      },
      child: Builder(
        builder: (context) {
          return Scaffold(
            body: Center(
              child: Row(
                children: <Widget>[
                  const Spacer(),
                  Expanded(
                    child: TextField(
                      controller: controller,
                      focusNode: focusNode,
                    ),
                  ),
                  IconButton(
                    icon: const Icon(Icons.copy),
                    onPressed: Actions.handler<CopyIntent>(
                      context,
                      const CopyIntent(),
                    ),
                  ),
                  IconButton(
                    icon: const Icon(Icons.select_all),
                    onPressed: Actions.handler<SelectAllIntent>(
                      context,
                      const SelectAllIntent(),
                    ),
                  ),
                  const Spacer(),
                ],
              ),
            ),
          );
        },
      ),
    );
  }
}

/// A ShortcutManager that logs all keys that it handles.
class LoggingShortcutManager extends ShortcutManager {
  @override
  KeyEventResult handleKeypress(BuildContext context, KeyEvent event) {
    final KeyEventResult result = super.handleKeypress(context, event);
    if (result == KeyEventResult.handled) {
      print('Handled shortcut $event in $context');
    }
    return result;
  }
}

/// An ActionDispatcher that logs all the actions that it invokes.
class LoggingActionDispatcher extends ActionDispatcher {
  @override
  Object? invokeAction(
    covariant Action<Intent> action,
    covariant Intent intent, [
    BuildContext? context,
  ]) {
    print('Action invoked: $action($intent) from $context');
    super.invokeAction(action, intent, context);

    return null;
  }
}

/// An intent that is bound to ClearAction in order to clear its
/// TextEditingController.
class ClearIntent extends Intent {
  const ClearIntent();
}

/// An action that is bound to ClearIntent that clears its
/// TextEditingController.
class ClearAction extends Action<ClearIntent> {
  ClearAction(this.controller);

  final TextEditingController controller;

  @override
  Object? invoke(covariant ClearIntent intent) {
    controller.clear();

    return null;
  }
}

/// An intent that is bound to CopyAction to copy from its
/// TextEditingController.
class CopyIntent extends Intent {
  const CopyIntent();
}

/// An action that is bound to CopyIntent that copies the text in its
/// TextEditingController to the clipboard.
class CopyAction extends Action<CopyIntent> {
  CopyAction(this.controller);

  final TextEditingController controller;

  @override
  Object? invoke(covariant CopyIntent intent) {
    final String selectedString = controller.text.substring(
      controller.selection.baseOffset,
      controller.selection.extentOffset,
    );
    Clipboard.setData(ClipboardData(text: selectedString));

    return null;
  }
}

/// An intent that is bound to SelectAllAction to select all the text in its
/// controller.
class SelectAllIntent extends Intent {
  const SelectAllIntent();
}

/// An action that is bound to SelectAllAction that selects all text in its
/// TextEditingController.
class SelectAllAction extends Action<SelectAllIntent> {
  SelectAllAction(this.controller, this.focusNode);

  final TextEditingController controller;
  final FocusNode focusNode;

  @override
  Object? invoke(covariant SelectAllIntent intent) {
    controller.selection = controller.selection.copyWith(
      baseOffset: 0,
      extentOffset: controller.text.length,
      affinity: controller.selection.affinity,
    );

    focusNode.requestFocus();

    return null;
  }
}

/// The top level application class.
///
/// Shortcuts defined here are in effect for the whole app,
/// although different widgets may fulfill them differently.
class MyApp extends StatelessWidget {
  const MyApp({super.key});

  static const String title = 'Shortcuts and Actions Demo';

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: title,
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
      ),
      home: Shortcuts(
        shortcuts: <LogicalKeySet, Intent>{
          LogicalKeySet(LogicalKeyboardKey.escape): const ClearIntent(),
          LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.keyC):
              const CopyIntent(),
          LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.keyA):
              const SelectAllIntent(),
        },
        child: const CopyableTextField(title: title),
      ),
    );
  }
}

void main() => runApp(const MyApp());
```

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-08-24. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/ui/interactivity/actions-and-shortcuts.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/ui/interactivity/actions-and-shortcuts\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/ui/interactivity/actions-and-shortcuts.md "Report an issue with this page").

----
url: https://docs.flutter.dev/ai/ai-toolkit/chat-client-sample
----

The AI Chat sample is meant to be a full-fledged chat app built using the Flutter AI Toolkit and the Firebase AI Logic SDK. In addition to all of the multi-shot, multi-media, streaming features that it gets from the AI Toolkit, the AI Chat sample shows how to store and manage multiple chats at once in your own apps. On desktop form-factors, the AI Chat sample looks like the following:

On mobile form-factors, it looks like this:

The chats are stored in an authenticated Cloud Firestore database; any authenticated user can have as many chats as they like.

In addition, for each new chat, while the user can manually title it whatever they like, the initial prompt and response is used to ask the LLM what an appropriate title should be. In fact, the titles of the chats in the screenshots in this page were set automatically.

To build and run the sample, follow the instructions in the [AI Chat README](https://github.com/csells/flutter_ai_chat).

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-08-19. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/ai/ai-toolkit/chat-client-sample.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/ai/ai-toolkit/chat-client-sample\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/ai/ai-toolkit/chat-client-sample.md "Report an issue with this page").

----
url: https://docs.flutter.dev/ui/interactivity.md
----

\# Add interactivity to your Flutter app > How to implement a stateful widget that responds to taps. :::secondary What you'll learn \* How to respond to taps. \* How to create a custom widget. \* The difference between stateless and stateful widgets. ::: How do you modify your app to make it react to user input? In this tutorial, you'll add interactivity to an app that contains only non-interactive widgets. Specifically, you'll modify an icon to make it tappable by creating a custom stateful widget that manages two stateless widgets. The \[building layouts tutorial]\[] showed you how to create the layout for the following screenshot. When the app first launches, the star is solid red, indicating that this lake has previously been favorited. The number next to the star indicates that 41 people have favorited this lake. After completing this tutorial, tapping the star removes its favorited status, replacing the solid star with an outline and decreasing the count. Tapping again favorites the lake, drawing a solid star and increasing the count. To accomplish this, you'll create a single custom widget that includes both the star and the count, which are themselves widgets. Tapping the star changes state for both widgets, so the same widget should manage both. You can get right to touching the code in \[Step 2: Subclass StatefulWidget]\(#step-2). If you want to try different ways of managing state, skip to \[Managing state]\[]. ## Stateful and stateless widgets A widget is either stateful or stateless. If a widget can change—when a user interacts with it, for example—it's stateful. A \_stateless\_ widget never changes. \[\`Icon\`]\[], \[\`IconButton\`]\[], and \[\`Text\`]\[] are examples of stateless widgets. Stateless widgets subclass \[\`StatelessWidget\`]\[]. A \_stateful\_ widget is dynamic: for example, it can change its appearance in response to events triggered by user interactions or when it receives data. \[\`Checkbox\`]\[], \[\`Radio\`]\[], \[\`Slider\`]\[], \[\`InkWell\`]\[], \[\`Form\`]\[], and \[\`TextField\`]\[] are examples of stateful widgets. Stateful widgets subclass \[\`StatefulWidget\`]\[]. A widget's state is stored in a \[\`State\`]\[] object, separating the widget's state from its appearance. The state consists of values that can change, like a slider's current value or whether a checkbox is checked. When the widget's state changes, the state object calls \`setState()\`, telling the framework to redraw the widget. ## Creating a stateful widget :::secondary What's the point? \* A stateful widget is implemented by two classes: a subclass of \`StatefulWidget\` and a subclass of \`State\`. \* The state class contains the widget's mutable state and the widget's \`build()\` method. \* When the widget's state changes, the state object calls \`setState()\`, telling the framework to redraw the widget. ::: In this section, you'll create a custom stateful widget. You'll replace two stateless widgets—the solid red star and the numeric count next to it—with a single custom stateful widget that manages a row with two children widgets: an \`IconButton\` and \`Text\`. Implementing a custom stateful widget requires creating two classes: \* A subclass of \`StatefulWidget\` that defines the widget. \* A subclass of \`State\` that contains the state for that widget and defines the widget's \`build()\` method. This section shows you how to build a stateful widget, called \`FavoriteWidget\`, for the lakes app. After setting up, your first step is choosing how state is managed for \`FavoriteWidget\`. ### Step 0: Get ready If you've already built the app in the \[building layouts tutorial]\[], skip to the next section. 1. Make sure you've \[set up]\[] your environment. 1. \[Create a new Flutter app]\[new-flutter-app]. 1. Replace the \`lib/main.dart\` file with \[\`main.dart\`]\[]. 1. Replace the \`pubspec.yaml\` file with \[\`pubspec.yaml\`]\[]. 1. Create an \`images\` directory in your project, and add \[\`lake.jpg\`]\[]. Once you have a connected and enabled device, or you've launched the \[iOS simulator]\[] (part of the Flutter install) or the \[Android emulator]\[] (part of the Android Studio install), you are good to go! []()### Step 1: Decide which object manages the widget's state A widget's state can be managed in several ways, but in our example the widget itself, \`FavoriteWidget\`, will manage its own state. In this example, toggling the star is an isolated action that doesn't affect the parent widget or the rest of the UI, so the widget can handle its state internally. Learn more about the separation of widget and state, and how state might be managed, in \[Managing state]\[]. []()### Step 2: Subclass StatefulWidget The \`FavoriteWidget\` class manages its own state, so it overrides \`createState()\` to create a \`State\` object. The framework calls \`createState()\` when it wants to build the widget. In this example, \`createState()\` returns an instance of \`\_FavoriteWidgetState\`, which you'll implement in the next step.

<!--?code-excerpt path-base="layout/lakes/interactive"?-->

<!--?code-excerpt "lib/main.dart (favorite-widget)"?-->

\`\`\`dart class FavoriteWidget extends StatefulWidget { const FavoriteWidget({super.key}); @override State createState() => \_FavoriteWidgetState(); } \`\`\` :::note Members or classes that start with an underscore (\`\_\`) are private. For more information, see \[Libraries and imports]\[], a section in the \[Dart language documentation]\[]. ::: []()### Step 3: Subclass State The \`\_FavoriteWidgetState\` class stores the mutable data that can change over the lifetime of the widget. When the app first launches, the UI displays a solid red star, indicating that the lake has "favorite" status, along with 41 likes. These values are stored in the \`\_isFavorited\` and \`\_favoriteCount\` fields:

<!--?code-excerpt "lib/main.dart (favorite-state-fields)" replace="/(bool|int) .*/[!$&!]/g"?-->

\`\`\`dart class \_FavoriteWidgetState extends State { \[!bool \_isFavorited = true;!] \[!int \_favoriteCount = 41;!] \`\`\` The class also defines a \`build()\` method, which creates a row containing a red \`IconButton\`, and \`Text\`. You use \[\`IconButton\`]\[] (instead of \`Icon\`) because it has an \`onPressed\` property that defines the callback function (\`\_toggleFavorite\`) for handling a tap. You'll define the callback function next.

<!--?code-excerpt "lib/main.dart (favorite-state-build)" replace="/build|icon.*|onPressed.*|child: Text.*/[!$&!]/g"?-->

\`\`\`dart class \_FavoriteWidgetState extends State { // ··· @override Widget \[!build!]\(BuildContext context) { return Row( mainAxisSize: MainAxisSize.min, children: \[ Container( padding: const EdgeInsets.all(0), child: IconButton( padding: const EdgeInsets.all(0), alignment: Alignment.center, \[!icon: (\_isFavorited!] ? const Icon(Icons.star) : const Icon(Icons.star\_border)), color: Colors.red\[500], \[!onPressed: \_toggleFavorite,!] ), ), SizedBox(width: 18, child: SizedBox(\[!child: Text('$\_favoriteCount'))),!] ], ); } // ··· } \`\`\` :::tip Placing the \`Text\` in a \[\`SizedBox\`]\[] and setting its width prevents a discernible "jump" when the text changes between the values of 40 and 41 — a jump would otherwise occur because those values have different widths. ::: The \`\_toggleFavorite()\` method, which is called when the \`IconButton\` is pressed, calls \`setState()\`. Calling \`setState()\` is critical, because this tells the framework that the widget's state has changed and that the widget should be redrawn. The function argument to \`setState()\` toggles the UI between these two states: \* A \`star\` icon and the number 41 \* A \`star\_border\` icon and the number 40

<!--?code-excerpt "lib/main.dart (toggle-favorite)"?-->

\`\`\`dart void \_toggleFavorite() { setState(() { if (\_isFavorited) { \_favoriteCount -= 1; \_isFavorited = false; } else { \_favoriteCount += 1; \_isFavorited = true; } }); } \`\`\` []()### Step 4: Plug the stateful widget into the widget tree Add your custom stateful widget to the widget tree in the app's \`build()\` method. First, locate the code that creates the \`Icon\` and \`Text\`, and delete it. In the same location, create the stateful widget:

<!--?code-excerpt path-base=""?-->

\`\`\`dart diff child: Row( children: \[ // ... - Icon( - Icons.star, - color: Colors.red\[500], - ), - const Text('41'), + const FavoriteWidget(), ], ), \`\`\` That's it! When you hot reload the app, the star icon should now respond to taps. ### Problems? If you can't get your code to run, look in your IDE for possible errors. \[Debugging Flutter apps]\[] might help. If you still can't find the problem, check your code against the interactive lakes example on GitHub. \* \[\`lib/main.dart\`]\(https\://github.com/flutter/website/tree/main/examples/layout/lakes/interactive/lib/main.dart) \* \[\`pubspec.yaml\`]\(https\://github.com/flutter/website/tree/main/examples/layout/lakes/interactive/pubspec.yaml) \* \[\`lakes.jpg\`]\(https\://github.com/flutter/website/tree/main/examples/layout/lakes/interactive/images/lake.jpg) If you still have questions, refer to any one of the developer \[community]\[] channels. --- The rest of this page covers several ways a widget's state can be managed, and lists other available interactive widgets. ## Managing state :::secondary What's the point? \* There are different approaches for managing state. \* You, as the widget designer, choose which approach to use. \* If in doubt, start by managing state in the parent widget. ::: Who manages the stateful widget's state? The widget itself? The parent widget? Both? Another object? The answer is... it depends. There are several valid ways to make your widget interactive. You, as the widget designer, make the decision based on how you expect your widget to be used. Here are the most common ways to manage state: \* \[The widget manages its own state]\(#self-managed) \* \[The parent manages the widget's state]\(#parent-managed) \* \[A mix-and-match approach]\(#mix-and-match) How do you decide which approach to use? The following principles should help you decide: \* If the state in question is user data, for example the checked or unchecked mode of a checkbox, or the position of a slider, then the state is best managed by the parent widget. \* If the state in question is aesthetic, for example an animation, then the state is best managed by the widget itself. If in doubt, start by managing state in the parent widget. We'll give examples of the different ways of managing state by creating three simple examples: TapboxA, TapboxB, and TapboxC. The examples all work similarly—each creates a container that, when tapped, toggles between a green or grey box. The \`\_active\` boolean determines the color: green for active or grey for inactive.These examples use \[\`GestureDetector\`]\[] to capture activity on the \`Container\`. []()### The widget manages its own state Sometimes it makes the most sense for the widget to manage its state internally. For example, \[\`ListView\`]\[] automatically scrolls when its content exceeds the render box. Most developers using \`ListView\` don't want to manage \`ListView\`'s scrolling behavior, so \`ListView\` itself manages its scroll offset. The \`\_TapboxAState\` class: \* Manages state for \`TapboxA\`. \* Defines the \`\_active\` boolean which determines the box's current color. \* Defines the \`\_handleTap()\` function, which updates \`\_active\` when the box is tapped and calls the \`setState()\` function to update the UI. \* Implements all interactive behavior for the widget.

<!--?code-excerpt path-base="ui/interactive/"?-->

<!--?code-excerpt "lib/self_managed.dart"?-->

\`\`\`dart import 'package:material\_ui/material\_ui.dart'; // TapboxA manages its own state. //------------------------- TapboxA ---------------------------------- class TapboxA extends StatefulWidget { const TapboxA({super.key}); @override State createState() => \_TapboxAState(); } class \_TapboxAState extends State { bool \_active = false; void \_handleTap() { setState(() { \_active = !\_active; }); } @override Widget build(BuildContext context) { return GestureDetector( onTap: \_handleTap, child: Container( width: 200, height: 200, decoration: BoxDecoration( color: \_active ? Colors.lightGreen\[700] : Colors.grey\[600], ), child: Center( child: Text( \_active ? 'Active' : 'Inactive', style: const TextStyle(fontSize: 32, color: Colors.white), ), ), ), ); } } //------------------------- MyApp ---------------------------------- class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', home: Scaffold( appBar: AppBar(title: const Text('Flutter Demo')), body: const Center(child: TapboxA()), ), ); } } \`\`\`

***

[]()### The parent widget manages the widget's state Often it makes the most sense for the parent widget to manage the state and tell its child widget when to update. For example, \[\`IconButton\`]\[] allows you to treat an icon as a tappable button. \`IconButton\` is a stateless widget because we decided that the parent widget needs to know whether the button has been tapped, so it can take appropriate action. In the following example, TapboxB exports its state to its parent through a callback. Because TapboxB doesn't manage any state, it subclasses StatelessWidget. The ParentWidgetState class: \* Manages the \`\_active\` state for TapboxB. \* Implements \`\_handleTapboxChanged()\`, the method called when the box is tapped. \* When the state changes, calls \`setState()\` to update the UI. The TapboxB class: \* Extends StatelessWidget because all state is handled by its parent. \* When a tap is detected, it notifies the parent.

<!--?code-excerpt "lib/parent_managed.dart"?-->

\`\`\`dart import 'package:material\_ui/material\_ui.dart'; // ParentWidget manages the state for TapboxB. //------------------------ ParentWidget -------------------------------- class ParentWidget extends StatefulWidget { const ParentWidget({super.key}); @override State createState() => \_ParentWidgetState(); } class \_ParentWidgetState extends State { bool \_active = false; void \_handleTapboxChanged(bool newValue) { setState(() { \_active = newValue; }); } @override Widget build(BuildContext context) { return SizedBox( child: TapboxB(active: \_active, onChanged: \_handleTapboxChanged), ); } } //------------------------- TapboxB ---------------------------------- class TapboxB extends StatelessWidget { const TapboxB({super.key, this.active = false, required this.onChanged}); final bool active; final ValueChanged onChanged; void \_handleTap() { onChanged(!active); } @override Widget build(BuildContext context) { return GestureDetector( onTap: \_handleTap, child: Container( width: 200, height: 200, decoration: BoxDecoration( color: active ? Colors.lightGreen\[700] : Colors.grey\[600], ), child: Center( child: Text( active ? 'Active' : 'Inactive', style: const TextStyle(fontSize: 32, color: Colors.white), ), ), ), ); } } \`\`\`

***

[]()### A mix-and-match approach For some widgets, a mix-and-match approach makes the most sense. In this scenario, the stateful widget manages some of the state, and the parent widget manages other aspects of the state. In the \`TapboxC\` example, on tap down, a dark green border appears around the box. On tap up, the border disappears and the box's color changes. \`TapboxC\` exports its \`\_active\` state to its parent but manages its \`\_highlight\` state internally. This example has two \`State\` objects, \`\_ParentWidgetState\` and \`\_TapboxCState\`. The \`\_ParentWidgetState\` object: \* Manages the \`\_active\` state. \* Implements \`\_handleTapboxChanged()\`, the method called when the box is tapped. \* Calls \`setState()\` to update the UI when a tap occurs and the \`\_active\` state changes. The \`\_TapboxCState\` object: \* Manages the \`\_highlight\` state. \* The \`GestureDetector\` listens to all tap events. As the user taps down, it adds the highlight (implemented as a dark green border). As the user releases the tap, it removes the highlight. \* Calls \`setState()\` to update the UI on tap down, tap up, or tap cancel, and the \`\_highlight\` state changes. \* On a tap event, passes that state change to the parent widget to take appropriate action using the \[\`widget\`]\[] property.

<!--?code-excerpt "lib/mixed.dart"?-->

\`\`\`dart import 'package:material\_ui/material\_ui.dart'; //---------------------------- ParentWidget ---------------------------- class ParentWidget extends StatefulWidget { const ParentWidget({super.key}); @override State createState() => \_ParentWidgetState(); } class \_ParentWidgetState extends State { bool \_active = false; void \_handleTapboxChanged(bool newValue) { setState(() { \_active = newValue; }); } @override Widget build(BuildContext context) { return SizedBox( child: TapboxC(active: \_active, onChanged: \_handleTapboxChanged), ); } } //----------------------------- TapboxC ------------------------------ class TapboxC extends StatefulWidget { const TapboxC({super.key, this.active = false, required this.onChanged}); final bool active; final ValueChanged onChanged; @override State createState() => \_TapboxCState(); } class \_TapboxCState extends State { bool \_highlight = false; void \_handleTapDown(TapDownDetails details) { setState(() { \_highlight = true; }); } void \_handleTapUp(TapUpDetails details) { setState(() { \_highlight = false; }); } void \_handleTapCancel() { setState(() { \_highlight = false; }); } void \_handleTap() { widget.onChanged(!widget.active); } @override Widget build(BuildContext context) { // This example adds a green border on tap down. // On tap up, the square changes to the opposite state. return GestureDetector( onTapDown: \_handleTapDown, // Handle the tap events in the order that onTapUp: \_handleTapUp, // they occur: down, up, tap, cancel onTap: \_handleTap, onTapCancel: \_handleTapCancel, child: Container( width: 200, height: 200, decoration: BoxDecoration( color: widget.active ? Colors.lightGreen\[700] : Colors.grey\[600], border: \_highlight ? Border.all(color: Colors.teal\[700]!, width: 10) : null, ), child: Center( child: Text( widget.active ? 'Active' : 'Inactive', style: const TextStyle(fontSize: 32, color: Colors.white), ), ), ), ); } } \`\`\` An alternate implementation might have exported the highlight state to the parent while keeping the active state internal, but if you asked someone to use that tap box, they'd probably complain that it doesn't make much sense. The developer cares whether the box is active. The developer probably doesn't care how the highlighting is managed, and prefers that the tap box handles those details.

***

\## Other interactive widgets Flutter offers a variety of buttons and similar interactive widgets. Most of these widgets implement the \[Material Design guidelines]\[], which define a set of components with an opinionated UI. If you prefer, you can use \[\`GestureDetector\`]\[] to build interactivity into any custom widget. You can find examples of \`GestureDetector\` in \[Managing state]\[]. Learn more about the \`GestureDetector\` in \[Handle taps]\[], a recipe in the Flutter cookbook. :::tip Flutter also provides a set of iOS-style widgets called \[\`Cupertino\`]\[]. ::: When you need interactivity, it's easiest to use one of the prefabricated widgets. Here's a partial list: ### Standard widgets \* \[\`Form\`]\[] \* \[\`FormField\`]\[] ### Material Components \* \[\`Checkbox\`]\[] \* \[\`DropdownButton\`]\[] \* \[\`TextButton\`]\[] \* \[\`FloatingActionButton\`]\[] \* \[\`IconButton\`]\[] \* \[\`Radio\`]\[] \* \[\`ElevatedButton\`]\[] \* \[\`Slider\`]\[] \* \[\`Switch\`]\[] \* \[\`TextField\`]\[] ## Resources The following resources might help when adding interactivity to your app. \[Gestures]\[], a section in the Flutter cookbook. \[Handling gestures]\[] : How to create a button and make it respond to input. \[Gestures in Flutter]\[] : A description of Flutter's gesture mechanism. \[Flutter API documentation]\[] : Reference documentation for all of the Flutter libraries. Wonderous app \[running app]\[wonderous-app], \[repo]\[wonderous-repo] : Flutter showcase app with a custom design and engaging interactions. \[Flutter's Layered Design]\[] (video) : This video includes information about state and stateless widgets. Presented by Google engineer, Ian Hickson. \[Android emulator]: /platform-integration/android/setup#set-up-devices \[\`Checkbox\`]: https\://pub.dev/documentation/material\_ui/latest/material\_ui/Checkbox-class.html \[\`Cupertino\`]: https\://pub.dev/documentation/cupertino\_ui/latest/cupertino\_ui \[Dart language documentation]: https\://dart.dev/language \[Debugging Flutter apps]: /testing/debugging \[\`DropdownButton\`]: https\://pub.dev/documentation/material\_ui/latest/material\_ui/DropdownButton-class.html \[\`TextButton\`]: https\://pub.dev/documentation/material\_ui/latest/material\_ui/TextButton-class.html \[\`FloatingActionButton\`]: https\://pub.dev/documentation/material\_ui/latest/material\_ui/FloatingActionButton-class.html \[Flutter API documentation]: https\://api.flutter.dev \[Flutter's Layered Design]: https\://www\.youtube.com/watch?v=dkyY9WCGMi0 \[\`FormField\`]: https\://api.flutter.dev/flutter/widgets/FormField-class.html \[\`Form\`]: https\://api.flutter.dev/flutter/widgets/Form-class.html \[\`GestureDetector\`]: https\://api.flutter.dev/flutter/widgets/GestureDetector-class.html \[Gestures]: /cookbook/gestures \[Gestures in Flutter]: /ui/interactivity/gestures \[Handling gestures]: /ui#handling-gestures \[new-flutter-app]: /reference/create-new-app \[\`IconButton\`]: https\://pub.dev/documentation/material\_ui/latest/material\_ui/IconButton-class.html \[\`Icon\`]: https\://api.flutter.dev/flutter/widgets/Icon-class.html \[\`InkWell\`]: https\://pub.dev/documentation/material\_ui/latest/material\_ui/InkWell-class.html \[iOS simulator]: /platform-integration/ios/setup#set-up-devices \[building layouts tutorial]: /ui/layout/tutorial \[community]: https\://flutter.dev/community \[Handle taps]: /cookbook/gestures/handling-taps \[\`lake.jpg\`]: https\://github.com/flutter/website/blob/main/examples/layout/lakes/step6/images/lake.jpg \[Libraries and imports]: https\://dart.dev/language/libraries \[\`ListView\`]: https\://api.flutter.dev/flutter/widgets/ListView-class.html \[\`main.dart\`]: https\://github.com/flutter/website/blob/main/examples/layout/lakes/step6/lib/main.dart \[Managing state]: #managing-state \[Material Design guidelines]: https\://m3.material.io/styles \[\`pubspec.yaml\`]: https\://github.com/flutter/website/blob/main/examples/layout/lakes/step6/pubspec.yaml \[\`Radio\`]: https\://pub.dev/documentation/material\_ui/latest/material\_ui/Radio-class.html \[\`ElevatedButton\`]: https\://pub.dev/documentation/material\_ui/latest/material\_ui/ElevatedButton-class.html \[wonderous-app]: https\://wonderous.app//web \[wonderous-repo]: https\://github.com/gskinnerTeam/flutter-wonderous-app \[set up]: /install \[\`SizedBox\`]: https\://api.flutter.dev/flutter/widgets/SizedBox-class.html \[\`Slider\`]: https\://pub.dev/documentation/material\_ui/latest/material\_ui/Slider-class.html \[\`State\`]: https\://api.flutter.dev/flutter/widgets/State-class.html \[\`StatefulWidget\`]: https\://api.flutter.dev/flutter/widgets/StatefulWidget-class.html \[\`StatelessWidget\`]: https\://api.flutter.dev/flutter/widgets/StatelessWidget-class.html \[\`Switch\`]: https\://pub.dev/documentation/material\_ui/latest/material\_ui/Switch-class.html \[\`TextField\`]: https\://pub.dev/documentation/material\_ui/latest/material\_ui/TextField-class.html \[\`Text\`]: https\://api.flutter.dev/flutter/widgets/Text-class.html \[\`widget\`]: https\://api.flutter.dev/flutter/widgets/State/widget.html

----
url: https://docs.flutter.dev/tools/devtools/android-studio.md
----

\# Run DevTools from Android Studio > Learn how to launch and use DevTools from Android Studio. ## Install the Flutter plugin Add the Flutter plugin if you don't already have it installed. This can be done using the normal \*\*Plugins\*\* page in the IntelliJ and Android Studio settings. Once that page is open, you can search the marketplace for the Flutter plugin. ## Start an app to debug {: #run-and-debug} To open DevTools, you first need to run a Flutter app. This can be accomplished by opening a Flutter project, ensuring that you have a device connected, and clicking the \*\*Run\*\* or \*\*Debug\*\* toolbar buttons. ## Launch DevTools from the toolbar/menu Once an app is running, you can start DevTools using one of the following techniques: \* Select the \*\*Open DevTools\*\* toolbar action in the Run view. \* Select the \*\*Open DevTools\*\* toolbar action in the Debug view. (if debugging) \* Select the \*\*Open DevTools\*\* action from the \*\*More Actions\*\* menu in the Flutter Inspector view. !\[screenshot of Open DevTools button]\(/assets/images/docs/tools/devtools/android\_studio\_open\_devtools.png){:width="100%"} ## Launch DevTools from an action You can also open DevTools from an IntelliJ action. Open the \*\*Find Action...\*\* dialog (on macOS, press `Cmd` + `Shift` + `A`), and search for the \*\*Open DevTools\*\* action. When you select that action, the DevTools server launches and a browser instance opens pointing to the DevTools app. When opened with an IntelliJ action, DevTools is not connected to a Flutter app. You'll need to provide a service protocol port for a currently running app. You can do this using the inline \*\*Connect to a running app\*\* dialog.

----
url: https://docs.flutter.dev/ui/adaptive-responsive/idioms.md
----

\# Platform idioms > Learn how to create a responsive app that responds to changes in the screen size.

<!--?code-excerpt path-base="ui/adaptive_app_demos"?-->

The final area to consider for adaptive apps is platform standards. Each platform has its own idioms and norms; these nominal or de facto standards inform user expectations of how an application should behave. Thanks, in part to the web, users are accustomed to more customized experiences, but reflecting these platform standards can still provide significant benefits: \* \*\*Reduce cognitive load\*\* : By matching the user's existing mental model, accomplishing tasks becomes intuitive, which requires less thinking, boosts productivity, and reduces frustrations. \* \*\*Build trust\*\* : Users can become wary or suspicious when applications don't adhere to their expectations. Conversely, a UI that feels familiar can build user trust and can help improve the perception of quality. This often has the added benefit of better app store ratings—something we can all appreciate! ## Consider expected behavior on each platform The first step is to spend some time considering what the expected appearance, presentation, or behavior is on this platform. Try to forget any limitations of your current implementation, and just envision the ideal user experience. Work backwards from there. Another way to think about this is to ask, "How would a user of this platform expect to achieve this goal?" Then, try to envision how that would work in your app without any compromises. This can be difficult if you aren't a regular user of the platform. You might be unaware of the specific idioms and can easily miss them completely. For example, a lifetime Android user is likely unaware of platform conventions on iOS, and the same holds true for macOS, Linux, and Windows. These differences might be subtle to you, but be painfully obvious to an experienced user. ### Find a platform advocate If possible, assign someone as an advocate for each platform. Ideally, your advocate uses the platform as their primary device, and can offer the perspective of a highly opinionated user. To reduce the number of people, combine roles. Have one advocate for Windows and Android, one for Linux and the web, and one for Mac and iOS. The goal is to have constant, informed feedback so the app feels great on each platform. Advocates should be encouraged to be quite picky, calling out anything they feel differs from typical applications on their device. A simple example is how the default button in a dialog is typically on the left on Mac and Linux, but is on the right on Windows. Details like that are easy to miss if you aren't using a platform on a regular basis. :::secondary Important Advocates don't need to be developers or even full-time team members. They can be designers, stakeholders, or external testers that are provided with regular builds. ::: ### Stay unique Conforming to expected behaviors doesn't mean that your app needs to use default components or styling. Many of the most popular multiplatform apps have very distinct and opinionated UIs including custom buttons, context menus, and title bars. The more you can consolidate styling and behavior across platforms, the easier development and testing will be. The trick is to balance creating a unique experience with a strong identity, while respecting the norms of each platform. ## Common idioms and norms to consider Take a quick look at a few specific norms and idioms you might want to consider, and how you could approach them in Flutter. ### Scrollbar appearance and behavior Desktop and mobile users expect scrollbars, but they expect them to behave differently on different platforms. Mobile users expect smaller scrollbars that only appear while scrolling, whereas desktop users generally expect omnipresent, larger scrollbars that they can click or drag. Flutter comes with a built-in \`Scrollbar\` widget that already has support for adaptive colors and sizes according to the current platform. The one tweak you might want to make is to toggle \`alwaysShown\` when on a desktop platform:

<!--?code-excerpt "lib/pages/adaptive_grid_page.dart (scrollbar-always-shown)"?-->

\`\`\`dart return Scrollbar( thumbVisibility: DeviceType.isDesktop, controller: \_scrollController, child: GridView\.count( controller: \_scrollController, padding: const EdgeInsets.all(Insets.extraLarge), childAspectRatio: 1, crossAxisCount: colCount, children: listChildren, ), ); \`\`\` This subtle attention to detail can make your app feel more comfortable on a given platform. ### Multi-select Dealing with multi-select within a list is another area with subtle differences across platforms:

<!--?code-excerpt "lib/widgets/extra_widget_excerpts.dart (multi-select-shift)"?-->

\`\`\`dart static bool get isSpanSelectModifierDown => isKeyDown({LogicalKeyboardKey.shiftLeft, LogicalKeyboardKey.shiftRight}); \`\`\` To perform a platform-aware check for control or command, you can write something like this:

<!--?code-excerpt "lib/widgets/extra_widget_excerpts.dart (multi-select-modifier-down)"?-->

\`\`\`dart static bool get isMultiSelectModifierDown { bool isDown = false; if (Platform.isMacOS) { isDown = isKeyDown({ LogicalKeyboardKey.metaLeft, LogicalKeyboardKey.metaRight, }); } else { isDown = isKeyDown({ LogicalKeyboardKey.controlLeft, LogicalKeyboardKey.controlRight, }); } return isDown; } \`\`\` A final consideration for keyboard users is the \*\*Select All\*\* action. If you have a large list of items of selectable items, many of your keyboard users will expect that they can use \`Control+A\` to select all the items. #### Touch devices On touch devices, multi-selection is typically simplified, with the expected behavior being similar to having the \`isMultiSelectModifier\` down on the desktop. You can select or deselect items using a single tap, and will usually have a button to \*\*Select All\*\* or \*\*Clear\*\* the current selection. How you handle multi-selection on different devices depends on your specific use cases, but the important thing is to make sure that you're offering each platform the best interaction model possible. ### Selectable text A common expectation on the web (and to a lesser extent desktop) is that most visible text can be selected with the mouse cursor. When text is not selectable, users on the web tend to have an adverse reaction. Luckily, this is easy to support with the \[\`SelectableText\`]\[] widget:

<!--?code-excerpt "lib/widgets/extra_widget_excerpts.dart (selectable-text)"?-->

\`\`\`dart return const SelectableText('Select me!'); \`\`\` To support rich text, then use \`TextSpan\`:

<!--?code-excerpt "lib/widgets/extra_widget_excerpts.dart (rich-text-span)"?-->

\`\`\`dart return const SelectableText.rich( TextSpan( children: \[ TextSpan(text: 'Hello'), TextSpan( text: 'Bold', style: TextStyle(fontWeight: FontWeight.bold), ), ], ), ); \`\`\` \[\`SelectableText\`]: https\://pub.dev/documentation/material\_ui/latest/material\_ui/SelectableText-class.html ### Title bars On modern desktop applications, it's common to customize the title bar of your app window, adding a logo for stronger branding or contextual controls to help save vertical space in your main UI. !\[Samples of title bars]\(/assets/images/docs/ui/adaptive-responsive/titlebar.png){:width="100%"} This isn't supported directly in Flutter, but you can use the \[\`bits\_dojo\`]\[] package to disable the native title bars, and replace them with your own. This package lets you add whatever widgets you want to the \`TitleBar\` because it uses pure Flutter widgets under the hood. This makes it easy to adapt the title bar as you navigate to different sections of the app. \[\`bits\_dojo\`]: https\://github.com/bitsdojo/bitsdojo\_window ### Context menus and tooltips On desktop, there are several interactions that manifest as a widget shown in an overlay, but with differences in how they're triggered, dismissed, and positioned: \* \*\*Context menu\*\* : Typically triggered by a right-click, a context menu is positioned close to the mouse, and is dismissed by clicking anywhere, selecting an option from the menu, or clicking outside it. \* \*\*Tooltip\*\* : Typically triggered by hovering for 200-400ms over an interactive element, a tooltip is usually anchored to a widget (as opposed to the mouse position) and is dismissed when the mouse cursor leaves that widget. \* \*\*Popup panel (also known as flyout)\*\* : Similar to a tooltip, a popup panel is usually anchored to a widget. The main difference is that panels are most often shown on a tap event, and they usually don't hide themselves when the cursor leaves. Instead, panels are typically dismissed by clicking outside the panel or by pressing a \*\*Close\*\* or \*\*Submit\*\* button. To show basic tooltips in Flutter, use the built-in \[\`Tooltip\`]\[] widget:

<!--?code-excerpt "lib/widgets/extra_widget_excerpts.dart (tooltip)"?-->

\`\`\`dart return const Tooltip( message: 'I am a Tooltip', child: Text('Hover over the text to show a tooltip.'), ); \`\`\` Flutter also provides built-in context menus when editing or selecting text. To show more advanced tooltips, popup panels, or create custom context menus, you either use one of the available packages, or build it yourself using a \`Stack\` or \`Overlay\`. Some available packages include: \* \[\`context\_menus\`]\[] \* \[\`anchored\_popups\`]\[] \* \[\`flutter\_portal\`]\[] \* \[\`super\_tooltip\`]\[] \* \[\`custom\_pop\_up\_menu\`]\[] While these controls can be valuable for touch users as accelerators, they are essential for mouse users. These users expect to right-click things, edit content in place, and hover for more information. Failing to meet those expectations can lead to disappointed users, or at least, a feeling that something isn't quite right. \[\`anchored\_popups\`]: https\://pub.dev/packages/anchored\_popups \[\`context\_menus\`]: https\://pub.dev/packages/context\_menus \[\`custom\_pop\_up\_menu\`]: https\://pub.dev/packages/custom\_pop\_up\_menu \[\`flutter\_portal\`]: https\://pub.dev/packages/flutter\_portal \[\`super\_tooltip\`]: https\://pub.dev/packages/super\_tooltip \[\`Tooltip\`]: https\://pub.dev/documentation/material\_ui/latest/material\_ui/Tooltip-class.html ### Horizontal button order On Windows, when presenting a row of buttons, the confirmation button is placed at the start of the row (left side). On all other platforms, it's the opposite. The confirmation button is placed at the end of the row (right side). This can be easily handled in Flutter using the \`TextDirection\` property on \`Row\`:

<!--?code-excerpt "lib/widgets/ok_cancel_dialog.dart (row-text-direction)"?-->

\`\`\`dart TextDirection btnDirection = DeviceType.isWindows ? TextDirection.rtl : TextDirection.ltr; return Row( children: \[ const Spacer(), Row( textDirection: btnDirection, children: \[ DialogButton( label: 'Cancel', onPressed: () => Navigator.pop(context, false), ), DialogButton( label: 'Ok', onPressed: () => Navigator.pop(context, true), ), ], ), ], ); \`\`\` !\[Sample of embedded image]\(/assets/images/docs/ui/adaptive-responsive/embed\_image1.png){:width="75%"} !\[Sample of embedded image]\(/assets/images/docs/ui/adaptive-responsive/embed\_image2.png){:width="90%"} ### Menu bar Another common pattern on desktop apps is the menu bar. On Windows and Linux, this menu lives as part of the Chrome title bar, whereas on macOS, it's located along the top of the primary screen. Currently, you can specify custom menu bar entries using a prototype plugin, but it's expected that this functionality will eventually be integrated into the main SDK. It's worth mentioning that on Windows and Linux, you can't combine a custom title bar with a menu bar. When you create a custom title bar, you're replacing the native one completely, which means you also lose the integrated native menu bar. If you need both a custom title bar and a menu bar, you can achieve that by implementing it in Flutter, similar to a custom context menu. ### Drag and drop One of the core interactions for both touch-based and pointer-based inputs is drag and drop. Although this interaction is expected for both types of input, there are important differences to think about when it comes to scrolling lists of draggable items. Generally speaking, touch users expect to see drag handles to differentiate draggable areas from scrollable ones, or alternatively, to initiate a drag by using a long press gesture. This is because scrolling and dragging are both sharing a single finger for input. Mouse users have more input options. They can use a wheel or scrollbar to scroll, which generally eliminates the need for dedicated drag handles. If you look at the macOS Finder or Windows Explorer, you'll see that they work this way: you just select an item and start dragging. In Flutter, you can implement drag and drop in many ways. Discussing specific implementations is outside the scope of this article, but some high level options include the following: \* Use the \[\`Draggable\`]\[] and \[\`DragTarget\`]\[] APIs directly for a custom look and feel. \* Hook into \`onPan\` gesture events, and move an object yourself within a parent \`Stack\`. \* Use one of the \[pre-made list packages]\[] on pub.dev. \[\`Draggable\`]: https\://api.flutter.dev/flutter/widgets/Draggable-class.html \[\`DragTarget\`]: https\://api.flutter.dev/flutter/widgets/DragTarget-class.html \[pre-made list packages]: https\://pub.dev/packages?q=reorderable+list

----
url: https://docs.flutter.dev/resources/ads-overview.md
----

\# Ads overview > Learn about the resources available for adding ads to your Flutter app. !\[adding ads]\(/assets/images/docs/add-ads.png) The Google Mobile Ads SDK for Flutter works with both AdMob and Ad Manager. It supports a variety of ad formats such as app open, banner, interstitial, native, rewarded, and rewarded interstitial formats, and features like mediation. Easily integrate Google AdMob and Ad Manager ads directly in your Flutter App The following resources can help get you started: \* \[Add ads to your mobile Flutter app or game]\[] (cookbook recipe) \* \[Adding AdMob ads to a Flutter app]\[] (codelab) \* \[Adding an AdMob banner and native inline ads to a Flutter app]\[] (codelab) \* \[How to support Google Mobile Ads (AdMob) mediation]\[mediation] (guide) Listed in alphabetical order, the following packages support AdMob mediation: \* AppLovin (\[gma\_mediation\_applovin]\[]) \* DT Exchange (\[gma\_mediation\_dtexchange]\[]) \* InMobi (\[gma\_mediation\_inmobi]\[]) \* IronSource (\[gma\_mediation\_ironsource]\[]) \* Liftoff (\[gma\_mediation\_liftoffmonetize]\[]) \* Meta (\[gma\_mediation\_meta]\[]) \* Mintegral (\[gma\_mediation\_mintegral]\[]) \* Pangle (\[gma\_mediation\_pangle]\[]) \* Unity (\[gma\_mediation\_unity]\[]) \[Add ads to your mobile Flutter app or game]: /cookbook/plugins/google-mobile-ads \[Adding AdMob ads to a Flutter app]: https\://codelabs.developers.google.com/codelabs/admob-ads-in-flutter#0 \[Adding an AdMob banner and native inline ads to a Flutter app]: https\://codelabs.developers.google.com/codelabs/admob-inline-ads-in-flutter#0 \[gma\_mediation\_applovin]: https\://pub.dev/packages/gma\_mediation\_applovin \[gma\_mediation\_dtexchange]: https\://pub.dev/packages/gma\_mediation\_dtexchange \[gma\_mediation\_inmobi]: https\://pub.dev/packages/gma\_mediation\_inmobi \[gma\_mediation\_ironsource]: https\://pub.dev/packages/gma\_mediation\_ironsource \[gma\_mediation\_liftoffmonetize]: https\://pub.dev/packages/gma\_mediation\_liftoffmonetize \[gma\_mediation\_meta]: https\://pub.dev/packages/gma\_mediation\_meta \[gma\_mediation\_mintegral]: https\://pub.dev/packages/gma\_mediation\_mintegral \[gma\_mediation\_pangle]: https\://pub.dev/packages/gma\_mediation\_pangle \[gma\_mediation\_unity]: https\://pub.dev/packages/gma\_mediation\_unity \[mediation]: https\://developers.google.com/admob/flutter/mediation

----
url: https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.21.1.md
----

\# DevTools 2.21.1 release notes > Release notes for Dart and Flutter DevTools version 2.21.1. The 2.21.1 release of the Dart and Flutter DevTools includes the following changes among other general improvements. To learn more about DevTools, check out the \[DevTools overview]\(https\://docs.flutter.dev/tools/devtools). ## Performance updates \* Replace the DevTools timeline trace viewer with the \[Perfetto]\(https\://perfetto.dev/) trace viewer - \[#5142]\(https\://github.com/flutter/devtools/pull/5142) !\[perfetto trace viewer]\(/assets/images/docs/tools/devtools/release-notes/images-2.21.1/image1.png "perfetto\_trace\_viewer") \* Fix several issues with loading a Performance snapshot into DevTools - \[#5048]\(https\://github.com/flutter/devtools/pull/5048), \[#4929]\(https\://github.com/flutter/devtools/pull/4929) \* UI polish and cleanup - \[#4889]\(https\://github.com/flutter/devtools/pull/4889) ## Memory updates \* Improve usability of snapshot diffing - \[#5015]\(https\://github.com/flutter/devtools/pull/5015) \* UI polish and cleanup - \[#4855]\(https\://github.com/flutter/devtools/pull/4855) \* Color code classes based on where they are defined (SDK, your package, dependencies, etc.) - \[#5030]\(https\://github.com/flutter/devtools/pull/5030) \* Fix state management issue for tracing - \[#5062]\(https\://github.com/flutter/devtools/pull/5062) \* Improve the performance of taking a heap snapshot - \[#5134]\(https\://github.com/flutter/devtools/pull/5134) \* Retire broken import/export feature - \[#5135]\(https\://github.com/flutter/devtools/pull/5135) ## Debugger updates \* Added support for viewing profiler hits in the debugger script viewer - \[#4831]\(https\://github.com/flutter/devtools/pull/4831) \* Added support for inspecting records - \[#5084]\(https\://github.com/flutter/devtools/pull/5084) ## General updates \* Fix several issues in syntax highlighting that would color variable names that contain reserved words incorrectly and leave \`extends\`/\`implements\` clauses uncolored for some classes - \[#4948]\(https\://github.com/flutter/devtools/pull/4948) \* Fix an issue in Safari, and other browsers that do not support RegExp negative lookbehind, that prevented DevTools from loading - \[#4938]\(https\://github.com/flutter/devtools/pull/4938) \* Fix an issue that would prevent DevTools connecting to the backend server that would disable some functionality - \[#5016]\(https\://github.com/flutter/devtools/pull/5016) \* Add a link to the DevTools \[contribution guide]\(https\://github.com/flutter/devtools/blob/master/CONTRIBUTING.md) to the About menu, and fixed the Discord link - \[#4926]\(https\://github.com/flutter/devtools/pull/4926) \* Fix conflicting colors in light theme - \[#5067]\(https\://github.com/flutter/devtools/pull/5067) ## Full commit history To find a complete list of changes since the previous release, check out \[the diff on GitHub]\(https\://github.com/flutter/devtools/compare/v2.20.0...v2.21.1).

----
url: https://docs.flutter.dev/tools/flutter-fix.md
----

\# Flutter fix > Keep your code up to date with the help of the Flutter Fix feature. As Flutter continues to evolve, we provide a tool to help you clean up deprecated APIs from your codebase. The tool ships as part of Flutter, and suggests changes that you might want to make to your code. The tool is available from the command line, and is also integrated into the IDE plugins for Android Studio and Visual Studio Code. :::tip These automated updates are called \_quick-fixes\_ in IntelliJ and Android Studio, and \_code actions\_ in VS Code. ::: ## Applying individual fixes You can use any supported IDE to apply a single fix at a time. ### IntelliJ and Android Studio When the analyzer detects a deprecated API, a light bulb appears on that line of code. Clicking the light bulb displays the suggested fix that updates that code to the new API. Clicking the suggested fix performs the update. !\[Screenshot showing suggested change in IntelliJ]\(/assets/images/docs/development/tools/flutter-fix-suggestion-intellij.png)\
A sample quick-fix in IntelliJ ### VS Code When the analyzer detects a deprecated API, it presents an error. You can do any of the following: \* Hover over the error and then click the \*\*Quick Fix\*\* link. This presents a filtered list showing \_only\_ fixes. \* Put the caret in the code with the error and click the light bulb icon that appears. This shows a list of all actions, including refactors. \* Put the caret in the code with the error and press the shortcut (\*\*Command+.\*\* on macOS, \*\*Control+.\*\* elsewhere) This shows a list of all actions, including refactors. !\[Screenshot showing suggested change in VS Code]\(/assets/images/docs/development/tools/flutter-fix-suggestion-vscode.png)\
A sample code action in VS Code ## Applying project-wide fixes \[dart fix Decoding Flutter]\[] To see or apply changes to an entire project, you can use the command-line tool, \[\`dart fix\`]\[]. This tool has two options: \* To see a full list of available changes, run the following command: \`\`\`console $ dart fix --dry-run \`\`\` \* To apply all changes in bulk, run the following command: \`\`\`console $ dart fix --apply \`\`\` For more information on Flutter deprecations, read \[Deprecation lifetime in Flutter]\[] on the Flutter blog. \[Deprecation lifetime in Flutter]: https\://blog.flutter.dev/deprecation-lifetime-in-flutter-e4d76ee738ad \[\`dart fix\`]: https\://dart.dev/tools/dart-fix \[dart fix Decoding Flutter]: https\://www\.youtube.com/watch?v=OBIuSrg\_Quo

----
url: https://docs.flutter.dev/add-to-app/debugging.md
----

\# Debug your add-to-app module > How to run, debug, and hot reload your add-to-app Flutter module. Once you've integrated the Flutter module to your project and used Flutter's platform APIs to run the Flutter engine and/or UI, you can then build and run your Android or iOS app the same way you run normal Android or iOS apps. Flutter now powers the UI wherever your code includes \`FlutterActivity\` or \`FlutterViewController\`. ## Overview You might be used to having your suite of favorite Flutter debugging tools available when running \`flutter run\` or an equivalent command from an IDE. But you can also use all your Flutter \[debugging functionalities]\[] such as hot reload, performance overlays, DevTools, and setting breakpoints in add-to-app scenarios. The \`flutter attach\` command provides these functionalities. To run this command, you can use the SDK's CLI tools, VS Code or IntelliJ IDEA or Android Studio. The \`flutter attach\` command connects once you run your \`FlutterEngine\`. It remains attached until you dispose your \`FlutterEngine\`. You can invoke \`flutter attach\` before starting your engine. The \`flutter attach\` command waits for the next available Dart VM that your engine hosts. ## Debug from the Terminal To attach from the terminal, run \`flutter attach\`. To select a specific target device, add \`-d \`. \`\`\`console $ flutter attach \`\`\` The command should print output resembling the following: \`\`\`console Syncing files to device iPhone 15 Pro... 7,738ms (!) To hot reload the changes while running, press "r". To hot restart (and rebuild state). press "R". \`\`\` ## Debug iOS extension in Xcode and VS Code #### Build the iOS version of the Flutter app in the Terminal To generate the needed iOS platform dependencies, run the \`flutter build\` command. \`\`\`console $ flutter build ios --config-only --no-codesign --debug \`\`\` \`\`\`console Warning: Building for device with codesigning disabled. You will have to manually codesign before deploying to device. Building com.example.myApp for device (ios)... \`\`\` #### Start debugging with VS Code first {:#vscode-ios} If you use VS Code to debug most of your code, start with this section. ##### Start the Dart debugger in VS Code 1. To open the Flutter app directory, go to \*\*File\*\* > \*\*Open Folder...\*\* and choose the \`my\_app\` directory. 1. Open the \`lib/main.dart\` file. 1. If you can build an app for more than one device, you must select the device first. Go to \*\*View\*\* > \*\*Command Palette...\*\* You can also press `Ctrl` / `Cmd` + `Shift` + `P`. 1. Type \`flutter select\`. 1. Click the \*\*Flutter: Select Device\*\* command. 1. Choose your target device. 1. Click the debug icon (!\[VS Code's bug icon to trigger the debugging mode of a Flutter app]\(/assets/images/docs/testing/debugging/vscode-ui/icons/debug.png)). This opens the \*\*Debug\*\* pane and launches the app. Wait for the app to launch on the device and for the debug pane to indicate \*\*Connected\*\*. The debugger takes longer to launch the first time. Subsequent launches start faster. This Flutter app contains two buttons: - \*\*Launch in browser\*\*: This button opens this page in the default browser of your device. - \*\*Launch in app\*\*: This button opens this page within your app. This button only works for iOS or Android. Desktop apps launch a browser. ##### Enable automatic attachment You can configure VS Code to attach to your Flutter module project whenever you start debugging. To enable this feature, create a \`.vscode/launch.json\` file in your Flutter module project. 1. Go to \*\*View\*\* > \*\*Run\*\*. You can also press `Ctrl` / `Cmd` + `Shift` + `D`. VS Code displays the \*\*Run and Debug\*\* sidebar. 1. In this sidebar, click \*\*create a launch.json file\*\*. VS Code displays the \*\*Select debugger\*\* menu at the top. 1. Select \*\*Dart & Flutter\*\*. VS Code creates then opens the \`.vscode/launch.json\` file.

Expand to see an example launch.json file

\`\`\`json { // Use IntelliSense to learn about possible attributes. // Hover to view descriptions of existing attributes. // For more information, visit: https\://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": \[ { "name": "my\_app", "request": "launch", "type": "dart" }, { "name": "my\_app (profile mode)", "request": "launch", "type": "dart", "flutterMode": "profile" }, { "name": "my\_app (release mode)", "request": "launch", "type": "dart", "flutterMode": "release" } ] } \`\`\` 1. To attach, go to \*\*Run\*\* > \*\*Start Debugging\*\*. You can also press `F5`. ##### Attach to the Flutter process in Xcode To attach to the Flutter app in Xcode: 1. Go to \*\*Debug\*\* > \*\*Attach to Process\*\*. 1. Select \*\*Runner\*\*. It should be at the top of the \*\*Attach to Process\*\* menu under the \*\*Likely Targets\*\* heading. #### Start debugging with Xcode first {:#xcode-ios} If you use Xcode to debug most of your code, start with this section. ##### Start the Xcode debugger 1. Open \`ios/Runner.xcworkspace\` from your Flutter app directory. 1. Select the correct device using the \*\*Scheme\*\* menu in the toolbar. If you have no preference, choose \*\*iPhone Pro 14\*\*. 1. Run this Runner as a normal app in Xcode. When the run completes, the \*\*Debug\*\* area at the bottom of Xcode displays a message with the Dart VM service URI. It resembles the following response: \`\`\`console 2023-07-12 14:55:39.966191-0500 Runner\[58361:53017145] flutter: The Dart VM service is listening on http\://127.0.0.1:50642/00wEOvfyff8=/ \`\`\` 1. Copy the Dart VM service URI. ##### Attach to the Dart VM in VS Code 1. To open the command palette, go to \*\*View\*\* > \*\*Command Palette...\*\* You can also press `Cmd` + `Shift` + `P`. 1. Type \`debug\`. 1. Click the \*\*Debug: Attach to Flutter on Device\*\* command. 1. In the \*\*Paste an VM Service URI\*\* box, paste the URI you copied from Xcode and press `Enter`. ## Debug Android extension in Android Studio 1. To open the Flutter app directory, go to \*\*File\*\* > \*\*Open...\*\* and choose the \`my\_app\` directory. 1. Open the \`lib/main.dart\` file. 1. Choose a virtual Android device. Go to the toolbar, open the leftmost dropdown menu, and click on \*\*Open Android Emulator: \\\*\*. You can choose any installed emulator that's doesn't include \`arm64\`. 1. From that same menu, select the virtual Android device. 1. From the toolbar, click \*\*Run 'main.dart'\*\*. You can also press `Ctrl` + `Shift` + `R`. After the app displays in the emulator, continue to the next step. \[debugging functionalities]: /testing/debugging ## Debug without USB connection {:#wireless-debugging} To debug your app over Wi-Fi on an iOS or Android device, use \`flutter attach\`. ### Debug over Wi-Fi on iOS devices For an iOS target, complete the follow steps: 1. Verify your device connects to Xcode over Wi-Fi as described in the \[iOS setup guide]\[]. 1. On your macOS development machine, open \*\*Xcode\*\* > \*\*Product\*\* > \*\*Scheme\*\* > \*\*Edit Scheme...\*\*. You can also press `Cmd` + `<`. 1. Click \*\*Run\*\*. 1. Click \*\*Arguments\*\*. 1. In \*\*Arguments Passed On Launch\*\*, Click \*\*+\*\*. {:type="a"} 1. If your dev machine uses IPv4, add \`--vm-service-host=0.0.0.0\`. 1. If your dev machine uses IPv6, add \`--vm-service-host=::0\`. #### To determine if you're on an IPv6 network 1. Open \*\*Settings\*\* > \*\*Wi-Fi\*\*. 1. Click on your connected network. 1. Click \*\*Details...\*\* 1. Click \*\*TCP/IP\*\*. 1. Check for an \*\*IPv6 address\*\* section. ### Debug over Wi-Fi on Android devices Verify your device connects to Android Studio over Wi-Fi as described in the \[Android setup guide]\[]. \[iOS setup guide]: /platform-integration/ios/setup \[Android setup guide]: /platform-integration/android/setup#set-up-devices

----
url: https://docs.flutter.dev/platform-integration/legacy-ffi-plugin.md
----

\# Bind to native code using the legacy FFI plugin template > Use the legacy plugin\_ffi template and dart:ffi to bind to native C code in your Flutter plugin or app. :::warning This page documents the legacy \`plugin\_ffi\` approach to C interop. Since Flutter 3.38, we recommend using the \`package\_ffi\` template with \[build hooks]\[] for C interop. However, the legacy FFI plugin template (\`plugin\_ffi\`) documented here is still useful if you need to: - Access the Flutter Plugin API. - Use static linking (on iOS and macOS). - Configure a Google Play services runtime on Android. ::: Flutter mobile and desktop apps can use the \[\`dart:ffi\`]\[] library to call native C APIs. \_FFI\_ stands for \[\_foreign function interface.\_]\[FFI] Other terms for similar functionality include \_native interface\_ and \_language bindings.\_ \[build hooks]: /platform-integration/bind-native-code \[\`dart:ffi\`]: https\://api.dart.dev/dart-ffi/dart-ffi-library.html \[FFI]: https\://en.wikipedia.org/wiki/Foreign\_function\_interface Before your library or program can use the FFI library to bind to native code, you must ensure that the native code is loaded and its symbols are visible to Dart. This page focuses on compiling, packaging, and loading native code within a Flutter plugin or app. This tutorial demonstrates how to bundle C/C++ sources in a Flutter plugin and bind to them using the Dart FFI library. In this walkthrough, you'll create a C function that implements 32-bit addition and then exposes it through a Dart plugin named \`native\_add\`. ## Dynamic versus static linking A native library can be linked into an app either dynamically or statically. A statically linked library is embedded into the app's executable image, and is loaded when the app starts. Symbols from a statically linked library can be loaded using \[\`DynamicLibrary.executable\`]\[] or \[\`DynamicLibrary.process\`]\[]. A dynamically linked library, by contrast, is distributed in a separate file or folder within the app, and loaded on-demand. The distribution format depends on the platform: - On Android, a dynamically linked library is distributed as a set of \`.so\` (ELF) files, one for each architecture. Only dynamic libraries are supported, because the main executable is the JVM, which Flutter doesn't link to statically. - On iOS and macOS, the dynamically linked library is distributed as a \`.framework\` folder. A dynamically linked library can be loaded into Dart using \[\`DynamicLibrary.open\`]\[]. \[\`DynamicLibrary.executable\`]: https\://api.dart.dev/dart-ffi/DynamicLibrary/DynamicLibrary.executable.html \[\`DynamicLibrary.open\`]: https\://api.dart.dev/dart-ffi/DynamicLibrary/DynamicLibrary.open.html \[\`DynamicLibrary.process\`]: https\://api.dart.dev/dart-ffi/DynamicLibrary/DynamicLibrary.process.html ## Create an FFI plugin To create an FFI plugin called \`native\_add\`, use \`flutter create\` with the \`plugin\_ffi\` template: \`\`\`console $ flutter create --platforms=android,ios,macos,windows,linux --template=plugin\_ffi native\_add \`\`\` :::note You can exclude platforms from \`--platforms\` that you don't want to build to. However, you need to include the platform of the device you are testing on. ::: This creates a plugin with C/C++ sources in \`native\_add/src\`. These sources are built by the native build files in the various OS build folders. The FFI library can only bind against C symbols, so in C++ these symbols are marked \`extern "C"\`. You should also add attributes to indicate that the symbols are referenced from Dart, to prevent the linker from discarding the symbols during link-time optimization: \`\_\_attribute\_\_((visibility("default"))) \_\_attribute\_\_((used))\`. The platform-specific build file links the code: - On Android, \`native\_add/android/build.gradle\`. - On iOS, \`native\_add/ios/native\_add.podspec\`. - On macOS, \`native\_add/macos/native\_add.podspec\`. - On Linux, \`native\_add/linux/CMakeLists.txt\`. - On Windows, \`native\_add/windows/CMakeLists.txt\`. The native code is invoked from Dart in \`lib/native\_add\_bindings\_generated.dart\`. The bindings are generated with \[\`package:ffigen\`]\[]. \[\`package:ffigen\`]: https\://pub.dev/packages/ffigen ## Other use cases ### iOS The dynamic linker automatically loads dynamically linked libraries when the app starts. Their constituent symbols can be resolved using \[\`DynamicLibrary.process\`]\[]. You can also get a handle to the library with \[\`DynamicLibrary.open\`]\[] to restrict the scope of symbol resolution, but it's unclear how Apple's review process handles this. Symbols statically linked into the application binary can be resolved using \[\`DynamicLibrary.executable\`]\[] or \[\`DynamicLibrary.process\`]\[]. #### Platform library {:#ios-platform-library} To link against a platform library, use the following instructions: 1. In Xcode, open \`Runner.xcworkspace\`. 1. Select the target platform. 1. Click \*\*+\*\* in the \*\*Linked Frameworks and Libraries\*\* section. 1. Select the system library to link against. #### First-party library {:#ios-first-party-library} A first-party native library can be included either as source or as a (signed) \`.framework\` file. It's probably possible to include statically linked archives as well, but it requires testing. #### Source code {:#ios-source-code} To link directly to source code, use the following instructions: 1. In Xcode, open \`Runner.xcworkspace\`. 1. Add the C/C++/Objective-C/Swift source files to the Xcode project. 1. Add the following prefix to the exported symbol declarations to ensure they are visible to Dart: \*\*C/C++/Objective-C:\*\* \`\`\`objc extern "C" /\* <= C++ only \*/ \_\_attribute\_\_((visibility("default"))) \_\_attribute\_\_((used)) \`\`\` \*\*Swift:\*\* \`\`\`swift @\_cdecl("myFunctionName") \`\`\` #### Compiled (dynamic) library {:#ios-compiled-dynamic-library} To link to a compiled dynamic library, use the following instructions: 1. If a properly signed \`Framework\` file is present, open \`Runner.xcworkspace\`. 1. Add the framework file to the \*\*Frameworks, Libraries, and Embedded Content\*\* section of the target in Xcode. 1. Under the \*\*Embed\*\* column, select \*\*Embed & Sign\*\*. #### Open-source third-party library {:#ios-open-source-third-party-library} To create a Flutter plugin that includes both C/C++/Objective-C \_and\_ Dart code, use the following instructions: 1. In your plugin project, open \`ios/.podspec\`. 1. Add the native code to the \`source\_files\` field. The native code is then statically linked into the application binary of any app that uses this plugin. #### Closed-source third-party library {:#ios-closed-source-third-party-library} To create a Flutter plugin that includes Dart source code, but distribute the C/C++ library in binary form, use the following instructions: 1. In your plugin project, open \`ios/.podspec\`. 1. Add a \`vendored\_frameworks\` field. See the \[CocoaPods example]\[]. :::warning \*\*Do not\*\* upload this plugin (or any plugin containing binary code) to pub.dev. Instead, this plugin should be downloaded from a trusted third-party, as shown in the CocoaPods example. ::: \[CocoaPods example]: https\://github.com/CocoaPods/CocoaPods/blob/master/examples/Vendored%20Framework%20Example/Example%20Pods/VendoredFrameworkExample.podspec #### Stripping symbols {:#ios-stripping-symbols} When creating a release build, Xcode strips the symbols. 1. In Xcode, select the \*\*Runner\*\* target, then go to \*\*Build Settings > Strip Style\*\*. 1. Change from \*\*All Symbols\*\* to \*\*Non-Global Symbols\*\*. ### macOS The dynamic linker automatically loads dynamically linked libraries when the app starts. Their constituent symbols can be resolved using \[\`DynamicLibrary.process\`]\[]. You can also get a handle to the library with \[\`DynamicLibrary.open\`]\[] to restrict the scope of symbol resolution, but it's unclear how Apple's review process handles this. Symbols statically linked into the application binary can be resolved using \[\`DynamicLibrary.executable\`]\[] or \[\`DynamicLibrary.process\`]\[]. #### Platform library {:#macos-platform-library} To link against a platform library, use the following instructions: 1. In Xcode, open \`Runner.xcworkspace\`. 1. Select the target platform. 1. Click \*\*+\*\* in the \*\*Linked Frameworks and Libraries\*\* section. 1. Select the system library to link against. #### First-party library {:#macos-first-party-library} A first-party native library can be included either as source or as a (signed) \`.framework\` file. It's probably possible to include statically linked archives as well, but it requires testing. #### Source code {:#macos-source-code} To link directly to source code, use the following instructions: 1. In Xcode, open \`Runner.xcworkspace\`. 1. Add the C/C++/Objective-C/Swift source files to the Xcode project. 1. Add the following prefix to the exported symbol declarations to ensure they are visible to Dart: \*\*C/C++/Objective-C:\*\* \`\`\`objc extern "C" /\* <= C++ only \*/ \_\_attribute\_\_((visibility("default"))) \_\_attribute\_\_((used)) \`\`\` \*\*Swift:\*\* \`\`\`swift @\_cdecl("myFunctionName") \`\`\` #### Compiled (dynamic) library {:#macos-compiled-dynamic-library} To link to a compiled dynamic library, use the following instructions: 1. If a properly signed \`Framework\` file is present, open \`Runner.xcworkspace\`. 1. Add the framework file to the \*\*Frameworks, Libraries, and Embedded Content\*\* section of the target in Xcode. 1. Under the \*\*Embed\*\* column, select \*\*Embed & Sign\*\*. #### Compiled (dynamic) library, closed source {:#macos-compiled-dynamic-library-closed-source} To add a closed source library to a \[Flutter macOS Desktop]\[] app, use the following instructions: 1. Follow the instructions for Flutter desktop to create a Flutter desktop app. 1. Open the \`yourapp/macos/Runner.xcworkspace\` in Xcode. 1. Drag your precompiled library (\`libyourlibrary.dylib\`) into \`Runner/Frameworks\`. 1. Click \`Runner\` and go to the \`Build Phases\` tab. 1. Drag \`libyourlibrary.dylib\` into the \`Copy Bundle Resources\` list. 1. Under \`Embed Libraries\`, check \`Code Sign on Copy\`. 1. Under \`Link Binary With Libraries\`, set status to \`Optional\`. (We use dynamic linking, no need to statically link.) 1. Click \`Runner\` and go to the \`General\` tab. 1. Drag \`libyourlibrary.dylib\` into the \*\*Frameworks, Libraries, and Embedded Content\*\* list. 1. Select \*\*Embed & Sign\*\*. 1. Click \*\*Runner\*\* and go to the \*\*Build Settings\*\* tab. 1. In the \*\*Search Paths\*\* section configure the \*\*Library Search Paths\*\* to include the path where \`libyourlibrary.dylib\` is located. 1. Edit \`lib/main.dart\`. 1. Use \`DynamicLibrary.open('libyourlibrary.dylib')\` to dynamically link to the symbols. 1. Call your native function somewhere in a widget. 1. Run \`flutter run\` and check that your native function gets called. 1. Run \`flutter build macos\` to build a self-contained release version of your app. \[Flutter macOS Desktop]: /platform-integration/macos/building #### Stripping symbols {:#macos-stripping-symbols} When creating a release build, Xcode strips the symbols. 1. In Xcode, select the \*\*Runner\*\* target, then go to \*\*Build Settings > Strip Style\*\*. 1. Change from \*\*All Symbols\*\* to \*\*Non-Global Symbols\*\*. ### Android #### Platform library {:#android-platform-library} To link against a platform library, use the following instructions: 1. Find the desired library in the \[Android NDK Native APIs]\[] list in the Android docs. This lists stable native APIs. 1. Load the library using \[\`DynamicLibrary.open\`]\[]. For example, to load OpenGL ES (v3): \`\`\`dart DynamicLibrary.open('libGLES\_v3.so'); \`\`\` You might need to update the Android manifest file of the app or plugin if indicated by the documentation. \[Android NDK Native APIs]: https\://developer.android.com/ndk/guides/stable\_apis #### First-party library {:#android-first-party-library} The process for including native code in source code or binary form is the same for an app or plugin. #### Open-source third-party library {:#android-open-source-third-party-library} Follow the \[Add C and C++ code to your project]\[] instructions in the Android docs to add native code and support for the native code toolchain (either CMake or \`ndk-build\`). \[Add C and C++ code to your project]: https\://developer.android.com/studio/projects/add-native-code #### Closed-source third-party library {:#android-closed-source-third-party-library} To create a Flutter plugin that includes Dart source code, but distribute the C/C++ library in binary form, use the following instructions: 1. Open the \`android/build.gradle\` file for your project. 1. Add the AAR artifact as a dependency. \*\*Don't\*\* include the artifact in your Flutter package. Instead, it should be downloaded from a repository, such as Maven Central. #### Android APK size (shared object compression) \[Android guidelines]\[] in general recommend distributing native shared objects uncompressed because that actually saves on device space. Shared objects can be directly loaded from the APK instead of unpacking them on the device into a temporary location and then loading. APKs are additionally packed in transit—that's why you should be looking at download size. By default, Flutter APKs compress \`libflutter.so\` and \`libapp.so\`, which leads to a smaller APK size but a larger on-device size. To control whether native libraries are stored compressed and extracted at install time, set the Android Gradle plugin's \`useLegacyPackaging\` option. For current recommendations, see the \[Android guidelines]\[]. \[Android guidelines]: https\://developer.android.com/topic/performance/reduce-apk-size#extract-false ## Other resources To learn more about C interoperability, check out these videos: - \[C interoperability with Dart FFI]\[] - \[How to Use Dart FFI to Build a Retro Audio Player]\[] \[C interoperability with Dart FFI]: https\://www\.youtube.com/watch?v=2MMK7YoFgaA \[How to Use Dart FFI to Build a Retro Audio Player]: https\://www\.youtube.com/watch?v=05Wn2oM\_nWw

----
url: https://docs.flutter.dev/install/uninstall.md
----

\# Uninstall Flutter > How to remove the Flutter SDK and clean up its configuration files. To remove the Flutter SDK from your development machine, delete the directories that store Flutter and its configuration files. ## Choose your development platform {: #dev-platform } The instructions on this page are configured to cover uninstall Flutter on a \*\*Windows\*\*{:.selected-os-text} device. If you'd like to follow the instructions for a different OS, please select one of the following. ## Uninstall the Flutter SDK {: #uninstall } 1.

### Determine your Flutter SDK installation location

Copy the absolute path to the directory that you downloaded and extracted the Flutter SDK into. 1.

### Remove the installation directory

To uninstall the Flutter SDK, delete the \`flutter\` directory you installed Flutter to. For example, if you downloaded Flutter into a \`develop\flutter\` folder inside your user directory, run the following command to delete the SDK: \`\`\`ps $ Remove-Item -Recurse -Force -Path (Join-Path $env:USERPROFILE "develop\flutter") \`\`\` {: .steps .windows-only} 1.

### Determine your Flutter SDK installation location

Copy the absolute path to the directory that you downloaded and extracted the Flutter SDK into. 1.

### Remove the installation directory

To uninstall the Flutter SDK, delete the \`flutter\` directory you installed Flutter to. For example, if you downloaded Flutter into a \`develop/flutter\` folder inside your user directory, run the following command to delete the SDK: \`\`\`console $ rm -rf \~/develop/flutter \`\`\` {: .steps .macos-only .linux-only .chromeos-only } ## Clean up installation and configuration files {: #cleanup } Flutter and Dart add to additional directories in your home directory. These contain configuration files and package downloads. The following cleanup is optional. 1.

### Remove Flutter configuration directories

If you don't want to preserve your Flutter tooling configuration, remove the following directories from your device.

\- \`%APPDATA%\\.flutter-devtools\` To remove these directories, run the following command: \`\`\`ps $ Remove-Item -Recurse -Force -Path (Join-Path $env:APPDATA ".flutter-devtools") \`\`\`

\- \`\~/.flutter\` - \`\~/.flutter-devtools\` - \`\~/.flutter\_settings\` To remove these directories, run the following command: \`\`\`ps $ rm -rf \~/.flutter \~/.flutter-devtools \~/.flutter\_settings \`\`\`

1\.

### Remove Dart configuration directories

If you don't want to preserve your Dart tooling configuration, remove the following directories from your device.

\- \`%APPDATA%\\.dart\` - \`%APPDATA%\\.dart-tool\` - \`%LOCALAPPDATA%\\.dartServer\` To remove these directories, run the following command: \`\`\`console $ Remove-Item -Recurse -Force -Path (Join-Path $env:APPDATA ".dart"), (Join-Path $env:APPDATA ".dart-tool"), (Join-Path $env:LOCALAPPDATA ".dartServer") \`\`\`

\- \`\~/.dart\` - \`\~/.dart-tool\` - \`\~/.dartServer\` To remove these directories, run the following command: \`\`\`console $ rm -rf \~/.dart \~/.dart-tool \~/.dartServer \`\`\`

1\.

### Remove pub package directories

If you don't want to preserve your locally installed pub packages, remove the \[pub system cache]\[] directory from your device.

If you didn't change the location of the pub system cache, run the following command to delete the \`%LOCALAPPDATA%\Pub\Cache\` directory: \`\`\`ps $ Remove-Item -Recurse -Force -Path (Join-Path $env:LOCALAPPDATA "Pub\Cache") \`\`\`

If you didn't change the location of the pub system cache, run the following command to delete the \`\~/.pub-cache\` directory: \`\`\`console $ rm -rf \~/.pub-cache \`\`\`

{: .steps } \[pub system cache]: https\://dart.dev/tools/pub/glossary#system-cache ## Reinstall Flutter {: #reinstall } You can \[reinstall Flutter]\[flutter-install] or \[just Dart]\[dart-install] at any time. If you removed any configuration directories, reinstalling Flutter restores them to default settings. \[flutter-install]: /install \[dart-install]: https\://dart.dev/get-dart

----
url: https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.16.0.md
----

\# DevTools 2.16.0 release notes > Release notes for Dart and Flutter DevTools version 2.16.0. The 2.16.0 release of the Dart and Flutter DevTools includes the following changes among other general improvements. To learn more about DevTools, check out the \[DevTools overview]\(https\://docs.flutter.dev/tools/devtools). ## General updates This release includes several bug fixes and improvements, as well as work towards some new features that are coming soon! ## Full commit history To find a complete list of changes since the previous release, check out \[the diff on GitHub]\(https\://github.com/flutter/devtools/compare/v2.15.0...v2.16.0).

----
url: https://docs.flutter.dev/cookbook/networking/send-data
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Send data to the internet](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

Sending data to the internet is necessary for most apps. The `http` package has got that covered, too.

This recipe uses the following steps:

1. Add the `http` package.
2. Send data to a server using the `http` package.
3. Convert the response into a custom Dart object.
4. Get a `title` from user input.
5. Display the response on screen.

## 1. Add the `http` package

[#](#1-add-the-http-package)

To add the `http` package as a dependency, run `flutter pub add`:

```
$ flutter pub add http
```

Import the `http` package.

dart

```
import 'package:http/http.dart' as http;
import 'package:material_ui/material_ui.dart';
```

If you are deploying to Android, edit your `AndroidManifest.xml` file to add the Internet permission.

xml

```
<!-- Required to fetch data from the internet. -->
<uses-permission android:name="android.permission.INTERNET" />
```

Likewise, if you are deploying to macOS, edit your `macos/Runner/DebugProfile.entitlements` and `macos/Runner/Release.entitlements` files to include the network client entitlement.

xml

```
<!-- Required to fetch data from the internet. -->
<key>com.apple.security.network.client</key>
<true/>
```

## 2. Sending data to server

[#](#2-sending-data-to-server)

This recipe covers how to create an `Album` by sending an album title to the [JSONPlaceholder](https://jsonplaceholder.typicode.com/) using the [`http.post()`](https://pub.dev/documentation/http/latest/http/post.html) method.

Import `dart:convert` for access to `jsonEncode` to encode the data:

dart

```
import 'dart:convert';
```

Use the `http.post()` method to send the encoded data:

dart

```
Future<http.Response> createAlbum(String title) {
  return http.post(
    Uri.parse('https://jsonplaceholder.typicode.com/albums'),
    headers: <String, String>{
      'Content-Type': 'application/json; charset=UTF-8',
    },
    body: jsonEncode(<String, String>{'title': title}),
  );
}
```

The `http.post()` method returns a `Future` that contains a `Response`.

* [`Future`](https://api.flutter.dev/flutter/dart-async/Future-class.html) is a core Dart class for working with asynchronous operations. A Future object represents a potential value or error that will be available at some time in the future.
* The `http.Response` class contains the data received from a successful http call.
* The `createAlbum()` method takes an argument `title` that is sent to the server to create an `Album`.

```
class Album {
  final int id;
  final String title;
​
  const Album({required this.id, required this.title});
​
  factory Album.fromJson(Map<String, dynamic> json) {
    return switch (json) {
      {'id': int id, 'title': String title} => Album(id: id, title: title),
      _ => throw const FormatException('Failed to load album.'),
    };
  }
}
```

### Convert the `http.Response` to an `Album`

[#](#convert-the-http-response-to-an-album)

Use the following steps to update the `createAlbum()` function to return a `Future<Album>`:

1. Convert the response body into a JSON `Map` with the `dart:convert` package.
2. If the server returns a `CREATED` response with a status code of 201, then convert the JSON `Map` into an `Album` using the `fromJson()` factory method.
3. If the server doesn't return a `CREATED` response with a status code of 201, then throw an exception. (Even in the case of a "404 Not Found" server response, throw an exception. Do not return `null`. This is important when examining the data in `snapshot`, as shown below.)

dart

```
Future<Album> createAlbum(String title) async {
  final response = await http.post(
    Uri.parse('https://jsonplaceholder.typicode.com/albums'),
    headers: <String, String>{
      'Content-Type': 'application/json; charset=UTF-8',
    },
    body: jsonEncode(<String, String>{'title': title}),
  );
​
  if (response.statusCode == 201) {
    // If the server did return a 201 CREATED response,
    // then parse the JSON.
    return Album.fromJson(jsonDecode(response.body) as Map<String, dynamic>);
  } else {
    // If the server did not return a 201 CREATED response,
    // then throw an exception.
    throw Exception('Failed to create album.');
  }
}
```

Hooray! Now you've got a function that sends the title to a server to create an album.

## 4. Get a title from user input

[#](#4-get-a-title-from-user-input)

Next, create a `TextField` to enter a title and a `ElevatedButton` to send data to server. Also define a `TextEditingController` to read the user input from a `TextField`.

When the `ElevatedButton` is pressed, the `_futureAlbum` is set to the value returned by `createAlbum()` method.

dart

```
Column(
  mainAxisAlignment: MainAxisAlignment.center,
  children: <Widget>[
    TextField(
      controller: _controller,
      decoration: const InputDecoration(hintText: 'Enter Title'),
    ),
    ElevatedButton(
      onPressed: () {
        setState(() {
          _futureAlbum = createAlbum(_controller.text);
        });
      },
      child: const Text('Create Data'),
    ),
  ],
)
```

On pressing the **Create Data** button, make the network request, which sends the data in the `TextField` to the server as a `POST` request. The Future, `_futureAlbum`, is used in the next step.

## 5. Display the response on screen

[#](#5-display-the-response-on-screen)

To display the data on screen, use the [`FutureBuilder`](https://api.flutter.dev/flutter/widgets/FutureBuilder-class.html) widget. The `FutureBuilder` widget comes with Flutter and makes it easy to work with asynchronous data sources. You must provide two parameters:

1. The `Future` you want to work with. In this case, the future returned from the `createAlbum()` function.
2. A `builder` function that tells Flutter what to render, depending on the state of the `Future`: loading, success, or error.

Note that `snapshot.hasData` only returns `true` when the snapshot contains a non-null data value. This is why the `createAlbum()` function should throw an exception even in the case of a "404 Not Found" server response. If `createAlbum()` returns `null`, then `CircularProgressIndicator` displays indefinitely.

dart

```
FutureBuilder<Album>(
  future: _futureAlbum,
  builder: (context, snapshot) {
    if (snapshot.hasData) {
      return Text(snapshot.data!.title);
    } else if (snapshot.hasError) {
      return Text('${snapshot.error}');
    }
​
    return const CircularProgressIndicator();
  },
)
```

## Complete example

[#](#complete-example)

dart

```
import 'dart:async';
import 'dart:convert';
​
import 'package:http/http.dart' as http;
import 'package:material_ui/material_ui.dart';
​
Future<Album> createAlbum(String title) async {
  final response = await http.post(
    Uri.parse('https://jsonplaceholder.typicode.com/albums'),
    headers: <String, String>{
      'Content-Type': 'application/json; charset=UTF-8',
    },
    body: jsonEncode(<String, String>{'title': title}),
  );
​
  if (response.statusCode == 201) {
    // If the server did return a 201 CREATED response,
    // then parse the JSON.
    return Album.fromJson(jsonDecode(response.body) as Map<String, dynamic>);
  } else {
    // If the server did not return a 201 CREATED response,
    // then throw an exception.
    throw Exception('Failed to create album.');
  }
}
​
class Album {
  final int id;
  final String title;
​
  const Album({required this.id, required this.title});
​
  factory Album.fromJson(Map<String, dynamic> json) {
    return switch (json) {
      {'id': int id, 'title': String title} => Album(id: id, title: title),
      _ => throw const FormatException('Failed to load album.'),
    };
  }
}
​
void main() {
  runApp(const MyApp());
}
​
class MyApp extends StatefulWidget {
  const MyApp({super.key});
​
  @override
  State<MyApp> createState() {
    return _MyAppState();
  }
}
​
class _MyAppState extends State<MyApp> {
  final TextEditingController _controller = TextEditingController();
  Future<Album>? _futureAlbum;
​
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Create Data Example',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
      ),
      home: Scaffold(
        appBar: AppBar(title: const Text('Create Data Example')),
        body: Container(
          alignment: Alignment.center,
          padding: const EdgeInsets.all(8),
          child: (_futureAlbum == null) ? buildColumn() : buildFutureBuilder(),
        ),
      ),
    );
  }
​
  Column buildColumn() {
    return Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: <Widget>[
        TextField(
          controller: _controller,
          decoration: const InputDecoration(hintText: 'Enter Title'),
        ),
        ElevatedButton(
          onPressed: () {
            setState(() {
              _futureAlbum = createAlbum(_controller.text);
            });
          },
          child: const Text('Create Data'),
        ),
      ],
    );
  }
​
  FutureBuilder<Album> buildFutureBuilder() {
    return FutureBuilder<Album>(
      future: _futureAlbum,
      builder: (context, snapshot) {
        if (snapshot.hasData) {
          return Text(snapshot.data!.title);
        } else if (snapshot.hasError) {
          return Text('${snapshot.error}');
        }
​
        return const CircularProgressIndicator();
      },
    );
  }
}
```

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-09-29. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/networking/send-data.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/cookbook/networking/send-data\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/networking/send-data.md "Report an issue with this page").

----
url: https://docs.flutter.dev/cookbook/animation/physics-simulation
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Animate a widget using a physics simulation](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

Physics simulations can make app interactions feel realistic and interactive. For example, you might want to animate a widget to act as if it were attached to a spring or falling with gravity.

This recipe demonstrates how to move a widget from a dragged point back to the center using a spring simulation.

This recipe uses these steps:

1. Set up an animation controller.
2. Move the widget using gestures.
3. Animate the widget.
4. Calculate the velocity to simulate a springing motion.

## Step 1: Set up an animation controller

[#](#step-1-set-up-an-animation-controller)

Start with a stateful widget called `DraggableCard`:

dart

```
import 'package:flutter/material.dart';
​
void main() {
  runApp(const MaterialApp(home: PhysicsCardDragDemo()));
}
​
class PhysicsCardDragDemo extends StatelessWidget {
  const PhysicsCardDragDemo({super.key});
​
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(),
      body: const DraggableCard(child: FlutterLogo(size: 128)),
    );
  }
}
​
class DraggableCard extends StatefulWidget {
  const DraggableCard({required this.child, super.key});
​
  final Widget child;
​
  @override
  State<DraggableCard> createState() => _DraggableCardState();
}
​
class _DraggableCardState extends State<DraggableCard> {
  @override
  void initState() {
    super.initState();
  }
​
  @override
  void dispose() {
    super.dispose();
  }
​
  @override
  Widget build(BuildContext context) {
    return Align(child: Card(child: widget.child));
  }
}
```

Make the `_DraggableCardState` class extend from [`SingleTickerProviderStateMixin`](https://api.flutter.dev/flutter/widgets/SingleTickerProviderStateMixin-mixin.html). Then construct an [`AnimationController`](https://api.flutter.dev/flutter/animation/AnimationController-class.html) in `initState` and set `vsync` to `this`.

dart

```
class _DraggableCardState extends State<DraggableCard> {
class _DraggableCardState extends State<DraggableCard>
    with SingleTickerProviderStateMixin {
  late AnimationController _controller;
​
  @override
  void initState() {
    super.initState();
    _controller =
        AnimationController(vsync: this, duration: const Duration(seconds: 1));
  }
​
  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }
```

## Step 2: Move the widget using gestures

[#](#step-2-move-the-widget-using-gestures)

Make the widget move when it's dragged, and add an [`Alignment`](https://api.flutter.dev/flutter/painting/Alignment-class.html) field to the `_DraggableCardState` class:

dart

```
class _DraggableCardState extends State<DraggableCard>
    with SingleTickerProviderStateMixin {
  late AnimationController _controller;
  Alignment _dragAlignment = Alignment.center;
```

Add a [`GestureDetector`](https://api.flutter.dev/flutter/widgets/GestureDetector-class.html) that handles the `onPanDown`, `onPanUpdate`, and `onPanEnd` callbacks. To adjust the alignment, use [`MediaQuery.sizeOf()`](https://api.flutter.dev/flutter/widgets/MediaQuery/sizeOf.html) to get the size of the widget, and divide by 2. (This converts units of "pixels dragged" to coordinates that [`Align`](https://api.flutter.dev/flutter/widgets/Align-class.html) uses.) Then, set the `Align` widget's `alignment` to `_dragAlignment`:

dart

```
@override
Widget build(BuildContext context) {
  return Align(
    child: Card(
      child: widget.child,
  final size = MediaQuery.sizeOf(context);
  return GestureDetector(
    onPanDown: (details) {},
    onPanUpdate: (details) {
      setState(() {
        _dragAlignment += Alignment(
          details.delta.dx / (size.width / 2),
          details.delta.dy / (size.height / 2),
        );
      });
    },
    onPanEnd: (details) {},
    child: Align(
      alignment: _dragAlignment,
      child: Card(
        child: widget.child,
      ),
    ),
  );
}
```

## Step 3: Animate the widget

[#](#step-3-animate-the-widget)

When the widget is released, it should spring back to the center.

Add an `Animation<Alignment>` field and a `_runAnimation()` method. This method defines a `Tween` that interpolates between the point the widget was dragged to and the point in the center.

dart

```
class _DraggableCardState extends State<DraggableCard>
    with SingleTickerProviderStateMixin {
  late AnimationController _controller;
  late Animation<Alignment> _animation;
  Alignment _dragAlignment = Alignment.center;
```

dart

```
void _runAnimation() {
  _animation = _controller.drive(
    AlignmentTween(begin: _dragAlignment, end: Alignment.center),
  );
  _controller.reset();
  _controller.forward();
}
```

Next, update `_dragAlignment` when the `AnimationController` produces a value:

dart

```
@override
void initState() {
  super.initState();
  _controller =
      AnimationController(vsync: this, duration: const Duration(seconds: 1));
  _controller.addListener(() {
    setState(() {
      _dragAlignment = _animation.value;
    });
  });
}
```

Next, make the `Align` widget use the `_dragAlignment` field:

dart

```
child: Align(
  alignment: _dragAlignment,
  child: Card(child: widget.child),
),
```

Finally, update the `GestureDetector` to manage the animation controller:

dart

```
return GestureDetector(
  onPanDown: (details) {},
  onPanDown: (details) {
    _controller.stop();
  },
  onPanUpdate: (details) {
    // ...
  },
  onPanEnd: (details) {},
  onPanEnd: (details) {
    _runAnimation();
  },
  child: Align(
```

## Step 4: Calculate the velocity to simulate a springing motion

[#](#step-4-calculate-the-velocity-to-simulate-a-springing-motion)

The last step is to do a little math, to calculate the velocity of the widget after it's finished being dragged. This is so that the widget realistically continues at that speed before being snapped back. (The `_runAnimation()` method already sets the direction by setting the animation's start and end alignment.)

First, import the `physics` package:

dart

```
import 'package:flutter/physics.dart';
```

The `onPanEnd` callback provides a [`DragEndDetails`](https://api.flutter.dev/flutter/gestures/DragEndDetails-class.html) object. This object provides the velocity of the pointer when it stopped contacting the screen. The velocity is in pixels per second, but the `Align` widget doesn't use pixels. It uses coordinate values between \[-1.0, -1.0] and \[1.0, 1.0], where \[0.0, 0.0] represents the center. The `size` calculated in step 2 is used to convert pixels to coordinate values in this range.

Finally, `AnimationController` has an `animateWith()` method that can be given a [`SpringSimulation`](https://api.flutter.dev/flutter/physics/SpringSimulation-class.html):

dart

```
/// Calculates and runs a [SpringSimulation].
void _runAnimation(Offset pixelsPerSecond, Size size) {
  _animation = _controller.drive(
    AlignmentTween(begin: _dragAlignment, end: Alignment.center),
  );
  // Calculate the velocity relative to the unit interval, [0,1],
  // used by the animation controller.
  final unitsPerSecondX = pixelsPerSecond.dx / size.width;
  final unitsPerSecondY = pixelsPerSecond.dy / size.height;
  final unitsPerSecond = Offset(unitsPerSecondX, unitsPerSecondY);
  final unitVelocity = unitsPerSecond.distance;
​
  const spring = SpringDescription(mass: 1, stiffness: 1, damping: 1);
​
  final simulation = SpringSimulation(spring, 0, 1, -unitVelocity);
​
  _controller.animateWith(simulation);
}
```

Don't forget to call `_runAnimation()` with the velocity and size:

dart

```
onPanEnd: (details) {
  _runAnimation(details.velocity.pixelsPerSecond, size);
},
```

## Interactive example

[#](#interactive-example)

```
import 'package:flutter/material.dart';
import 'package:flutter/physics.dart';

void main() {
  runApp(const MaterialApp(home: PhysicsCardDragDemo()));
}

class PhysicsCardDragDemo extends StatelessWidget {
  const PhysicsCardDragDemo({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(),
      body: const DraggableCard(child: FlutterLogo(size: 128)),
    );
  }
}

/// A draggable card that moves back to [Alignment.center] when it's
/// released.
class DraggableCard extends StatefulWidget {
  const DraggableCard({required this.child, super.key});

  final Widget child;

  @override
  State<DraggableCard> createState() => _DraggableCardState();
}

class _DraggableCardState extends State<DraggableCard>
    with SingleTickerProviderStateMixin {
  late AnimationController _controller;

  /// The alignment of the card as it is dragged or being animated.
  ///
  /// While the card is being dragged, this value is set to the values computed
  /// in the GestureDetector onPanUpdate callback. If the animation is running,
  /// this value is set to the value of the [_animation].
  Alignment _dragAlignment = Alignment.center;

  late Animation<Alignment> _animation;

  /// Calculates and runs a [SpringSimulation].
  void _runAnimation(Offset pixelsPerSecond, Size size) {
    _animation = _controller.drive(
      AlignmentTween(begin: _dragAlignment, end: Alignment.center),
    );
    // Calculate the velocity relative to the unit interval, [0,1],
    // used by the animation controller.
    final unitsPerSecondX = pixelsPerSecond.dx / size.width;
    final unitsPerSecondY = pixelsPerSecond.dy / size.height;
    final unitsPerSecond = Offset(unitsPerSecondX, unitsPerSecondY);
    final unitVelocity = unitsPerSecond.distance;

    const spring = SpringDescription(mass: 1, stiffness: 1, damping: 1);

    final simulation = SpringSimulation(spring, 0, 1, -unitVelocity);

    _controller.animateWith(simulation);
  }

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(vsync: this);

    _controller.addListener(() {
      setState(() {
        _dragAlignment = _animation.value;
      });
    });
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    final size = MediaQuery.sizeOf(context);
    return GestureDetector(
      onPanDown: (details) {
        _controller.stop();
      },
      onPanUpdate: (details) {
        setState(() {
          _dragAlignment += Alignment(
            details.delta.dx / (size.width / 2),
            details.delta.dy / (size.height / 2),
          );
        });
      },
      onPanEnd: (details) {
        _runAnimation(details.velocity.pixelsPerSecond, size);
      },
      child: Align(
        alignment: _dragAlignment,
        child: Card(child: widget.child),
      ),
    );
  }
}
```

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-10-07. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/animation/physics-simulation.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/cookbook/animation/physics-simulation\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/animation/physics-simulation.md "Report an issue with this page").

----
url: https://docs.flutter.dev/cookbook/lists/basic-list.md
----

\# Use lists > How to implement a list.

<!--?code-excerpt path-base="cookbook/lists/basic_list"?-->

Displaying lists of data is a fundamental pattern for mobile apps. Flutter includes the \[\`ListView\`]\[] 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. The built-in \[\`ListTile\`]\[] widget is a way to give items a visual structure.

<!--?code-excerpt "lib/main.dart (ListView)" replace="/^body\: //g"?-->

\`\`\`dart ListView( children: const \[ ListTile(leading: Icon(Icons.map), title: Text('Map')), ListTile(leading: Icon(Icons.photo\_album), title: Text('Album')), ListTile(leading: Icon(Icons.phone), title: Text('Phone')), ], ), \`\`\` ## Interactive example

<!--?code-excerpt "lib/main.dart"?-->

\`\`\`dartpad title="Flutter lists hands-on example in DartPad" run="true" import 'package:flutter/material.dart'; void main() => runApp(const MyApp()); class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { const title = 'Basic List'; return MaterialApp( title: title, home: Scaffold( appBar: AppBar(title: const Text(title)), body: ListView( children: const \[ ListTile(leading: Icon(Icons.map), title: Text('Map')), ListTile(leading: Icon(Icons.photo\_album), title: Text('Album')), ListTile(leading: Icon(Icons.phone), title: Text('Phone')), ], ), ), ); } } \`\`\` \[\`ListTile\`]: https\://pub.dev/documentation/material\_ui/latest/material\_ui/ListTile-class.html \[\`ListView\`]: https\://pub.dev/documentation/material\_ui/latest/material\_ui/ListView-class.html

----
url: https://docs.flutter.dev/cookbook/lists
----

[Use lists](/cookbook/lists/basic-list)

[How to implement a list.](/cookbook/lists/basic-list)

[List with spaced items](/cookbook/lists/spaced-items)

[How to create a list with spaced or expanded items](/cookbook/lists/spaced-items)

[Create lists with different types of items](/cookbook/lists/mixed-list)

[How to implement a list that contains different types of assets.](/cookbook/lists/mixed-list)

[Create a horizontal list](/cookbook/lists/horizontal-list)

[How to implement a horizontal list.](/cookbook/lists/horizontal-list)

[Create a grid list](/cookbook/lists/grid-lists)

[How to implement a grid list.](/cookbook/lists/grid-lists)

[Place a floating app bar above a list](/cookbook/lists/floating-app-bar)

[How to place a floating app bar or navigation bar above a list.](/cookbook/lists/floating-app-bar)

[Work with long lists](/cookbook/lists/long-lists)

[Use ListView.builder to implement a long or infinite list.](/cookbook/lists/long-lists)

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/lists/index.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/cookbook/lists\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/lists/index.md "Report an issue with this page").

----
url: https://docs.flutter.dev/data-and-backend/google-apis
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Google APIs](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

The [Google APIs package](https://pub.dev/packages/googleapis) exposes dozens of Google services that you can use from Dart projects.

This page describes how to use APIs that interact with end-user data by using Google authentication.

Examples of user-data APIs include [Calendar](https://pub.dev/documentation/googleapis/latest/calendar_v3/calendar_v3-library.html), [Gmail](https://pub.dev/documentation/googleapis/latest/gmail_v1/gmail_v1-library.html), [YouTube](https://pub.dev/documentation/googleapis/latest/youtube_v3/youtube_v3-library.html), and Firebase.

To add authentication to Firebase explicitly, check out the [Add a user authentication flow to a Flutter app using FirebaseUI](https://firebase.google.com/codelabs/firebase-auth-in-flutter-apps) codelab and the [Get Started with Firebase Authentication on Flutter](https://firebase.google.com/docs/auth/flutter/start) docs.

## Overview

[#](#overview)

To use Google APIs, follow these steps:

1. Pick the desired API
2. Enable the API
3. Authenticate and determine the current user
4. Obtain an authenticated HTTP client
5. Create and use the desired API class

## 1. Pick the desired API

[#](#1-pick-the-desired-api)

The documentation for [`package:googleapis`](https://pub.dev/documentation/googleapis) lists each API as a separate Dart library\&emdash;in a `name_version` format. Check out [`youtube_v3`](https://pub.dev/documentation/googleapis/latest/youtube_v3/youtube_v3-library.html) as an example.

Each library might provide many types, but there is one *root* class that ends in `Api`. For YouTube, it's [`YouTubeApi`](https://pub.dev/documentation/googleapis/latest/youtube_v3/YouTubeApi-class.html).

Not only is the `Api` class the one you need to instantiate (see step 3), but it also exposes the scopes that represent the permissions needed to use the API. For example, the [Constants section](https://pub.dev/documentation/googleapis/latest/youtube_v3/YouTubeApi-class.html#constants) of the `YouTubeApi` class lists the available scopes. To request access to read (but not write) an end-user's YouTube data, authenticate the user with [`youtubeReadonlyScope`](https://pub.dev/documentation/googleapis/latest/youtube_v3/YouTubeApi/youtubeReadonlyScope-constant.html).

dart

```
// Provides the `YouTubeApi` class.
import 'package:googleapis/youtube/v3.dart';
```

## 2. Enable the API

[#](#2-enable-the-api)

To use Google APIs you must have a Google account and a Google project. You also need to enable your desired API.

This example enables [YouTube Data API v3](https://console.cloud.google.com/apis/library/youtube.googleapis.com). For details, see the [getting started instructions](https://cloud.google.com/apis/docs/getting-started).

## 3. Authenticate and determine the current user

[#](#3-authenticate-and-determine-the-current-user)

Use the [google\_sign\_in](https://pub.dev/packages/google_sign_in) package to authenticate users with their Google identity. Configure sign in for each platform you want to support.

dart

```
/// Provides the `GoogleSignIn` class.
import 'package:google_sign_in/google_sign_in.dart';
```

The package's functionality is accessed through a static instance of the [`GoogleSignIn`](https://pub.dev/documentation/google_sign_in/latest/google_sign_in/GoogleSignIn-class.html) class. Before interacting with the instance, the `initialize` method must be called and allowed to complete.

dart

```
final GoogleSignIn _googleSignIn = GoogleSignIn.instance;
​
@override
void initState() {
  super.initState();
  _googleSignIn.initialize();
  // ···
}
```

Once initialization is complete but before user authentication, listen to authentication events to determine if a user signed in.

dart

```
GoogleSignInAccount? _currentUser;
@override
void initState() {
  super.initState();
  _googleSignIn.initialize().then((_) {
    _googleSignIn.authenticationEvents.listen((event) {
      setState(() {
        _currentUser = switch (event) {
          GoogleSignInAuthenticationEventSignIn() => event.user,
          _ => null,
        };
      });
    });
  });
}
```

Once you're listening to any relevant authentication events, you can attempt to authenticate a previously signed-in user.

dart

```
void initState() {
  super.initState();
  _googleSignIn.initialize().then((_) {
    // ...
    // Attempt to authenticate a previously signed in user.
    _googleSignIn.attemptLightweightAuthentication();
  });
}
```

To also allow for new users to authenticate, follow the instructions provided by [`package:google_sign_in`](https://pub.dev/packages/google_sign_in).

Once a user has been authenticated, you must obtain an authenticated HTTP client.

## 4. Obtain an authenticated HTTP client

[#](#4-obtain-an-authenticated-http-client)

Once you have a signed-in user, request the relevant client authorization tokens using [`authorizationForScopes`](https://pub.dev/documentation/google_sign_in/latest/google_sign_in/GoogleSignInAuthorizationClient/authorizationForScopes.html) for the API scopes that your app requires.

dart

```
const relevantScopes = [YouTubeApi.youtubeReadonlyScope];
final authorization = await currentUser.authorizationClient
    .authorizationForScopes(relevantScopes);
```

Once you have the relevant authorization tokens, use the [`authClient`](https://pub.dev/documentation/extension_google_sign_in_as_googleapis_auth/latest/extension_google_sign_in_as_googleapis_auth/GoogleApisGoogleSignInAuth/authClient.html) extension from [`package:extension_google_sign_in_as_googleapis_auth`](https://pub.dev/packages/extension_google_sign_in_as_googleapis_auth) to set up an authenticated HTTP client with the relevant credentials applied.

dart

```
import 'package:extension_google_sign_in_as_googleapis_auth/extension_google_sign_in_as_googleapis_auth.dart';
```

dart

```
final authenticatedClient = authorization!.authClient(
  scopes: relevantScopes,
);
```

## 5. Create and use the desired API class

[#](#5-create-and-use-the-desired-api-class)

Use the API to create the desired API type and call methods. For instance:

dart

```
final youTubeApi = YouTubeApi(authenticatedClient);
​
final favorites = await youTubeApi.playlistItems.list(
  ['snippet'],
  playlistId: 'LL', // Liked List
);
```

## More information

[#](#more-information)

You might want to check out the following:

* The [`extension_google_sign_in_as_googleapis_auth` example](https://pub.dev/packages/extension_google_sign_in_as_googleapis_auth/example) is a working implementation of the concepts described on this page.

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-06-25. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/data-and-backend/google-apis.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/data-and-backend/google-apis\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/data-and-backend/google-apis.md "Report an issue with this page").

----
url: https://docs.flutter.dev/data-and-backend/state-mgmt
----

[Differentiate between ephemeral state and app state](/data-and-backend/state-mgmt/ephemeral-vs-app)

[How to tell the difference between ephemeral and app state.](/data-and-backend/state-mgmt/ephemeral-vs-app)

[Start thinking declaratively](/data-and-backend/state-mgmt/declarative)

[How to think about declarative programming.](/data-and-backend/state-mgmt/declarative)

[Simple app state management](/data-and-backend/state-mgmt/simple)

[A simple form of state management.](/data-and-backend/state-mgmt/simple)

[Approaches to state management](/data-and-backend/state-mgmt/options)

[An introduction to different approaches to managing state in Flutter apps.](/data-and-backend/state-mgmt/options)

[State management](/data-and-backend/state-mgmt/intro)

[How to structure an app to manage the state of the data flowing through it.](/data-and-backend/state-mgmt/intro)

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/data-and-backend/state-mgmt/index.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/data-and-backend/state-mgmt\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/data-and-backend/state-mgmt/index.md "Report an issue with this page").

----
url: https://docs.flutter.dev/app-architecture/design-patterns/optimistic-state.md
----

\# Optimistic state > Improve the perception of responsiveness of an application by implementing optimistic state.

<!--?code-excerpt path-base="app-architecture/optimistic_state"?-->

When building user experiences, the perception of performance is sometimes just as important as the actual performance of the code. In general, users don’t like waiting for an action to finish to see the result, and anything that takes more than a few milliseconds could be considered “slow” or “unresponsive” from the user’s perspective. Developers can help mitigate this negative perception by presenting a successful UI state before the background task is fully completed. An example of this would be tapping a “Subscribe” button, and seeing it change to “Subscribed” instantly, even if the background call to the subscription API is still running. This technique is known as Optimistic State, Optimistic UI or Optimistic User Experience. In this recipe, you will implement an application feature using Optimistic State and following the \[Flutter architecture guidelines]\[]. ## Example feature: a subscribe button This example implements a subscribe button similar to the one you could find in a video streaming application or a newsletter. When the button is tapped, the application then calls an external API, performing a subscription action, for example recording in a database that the user is now in the subscription list. For demo purposes, you will not implement the actual backend code, instead you will replace this call with a fake action that will simulate a network request. In the case that the call is successful, the button text will change from “Subscribe” to “Subscribed”. The button background color will change as well. On the contrary, if the call fails, the button text should revert back to “Subscribe”, and the UI should show an error message to the user, for example using a Snackbar. Following the Optimistic State idea, the button should instantly change to “Subscribed” once it is tapped, and only change back to “Subscribe” if the request failed. ## Feature architecture Start by defining the feature architecture. Following the architecture guidelines, create these Dart classes in a Flutter project: - A \`StatefulWidget\` named \`SubscribeButton\` - A class named \`SubscribeButtonViewModel\` extending \`ChangeNotifier\` - A class named \`SubscriptionRepository\`

<!--?code-excerpt "lib/starter.dart (Starter)"?-->

\`\`\`dart class SubscribeButton extends StatefulWidget { const SubscribeButton({super.key}); @override State createState() => \_SubscribeButtonState(); } class \_SubscribeButtonState extends State { @override Widget build(BuildContext context) { return const Placeholder(); } } class SubscribeButtonViewModel extends ChangeNotifier {} class SubscriptionRepository {} \`\`\` The \`SubscribeButton\` widget and the \`SubscribeButtonViewModel\` represent the presentation layer of this solution. The widget is going to display a button that will show the text “Subscribe” or “Subscribed” depending on the subscription state. The view model will contain the subscription state. When the button is tapped, the widget will call the view model to perform the action. The \`SubscriptionRepository\` will implement a subscribe method that will throw an exception when the action fails. The view model will call this method when performing the subscription action. Next, connect them together by adding the \`SubscriptionRepository\` to the \`SubscribeButtonViewModel\`:

<!--?code-excerpt "lib/main.dart (ViewModelStart)" replace="/y;$/y;\n}/g"?-->

\`\`\`dart class SubscribeButtonViewModel extends ChangeNotifier { SubscribeButtonViewModel({required this.subscriptionRepository}); final SubscriptionRepository subscriptionRepository; } \`\`\` And add the \`SubscribeButtonViewModel\` to the \`SubscribeButton\` widget:

<!--?code-excerpt "lib/main.dart (Widget)"?-->

\`\`\`dart class SubscribeButton extends StatefulWidget { const SubscribeButton({super.key, required this.viewModel}); /// Subscribe button view model. final SubscribeButtonViewModel viewModel; @override State createState() => \_SubscribeButtonState(); } \`\`\` Now that you have created the basic solution architecture, you can create the \`SubscribeButton\` widget the following way:

<!--?code-excerpt "lib/main.dart (SubscribeButton)" replace="/^child: //g;/^\),$/)/g"?-->

\`\`\`dart SubscribeButton( viewModel: SubscribeButtonViewModel( subscriptionRepository: SubscriptionRepository(), ), ) \`\`\` ### Implement the \`SubscriptionRepository\` Add a new asynchronous method named \`subscribe()\` to the \`SubscriptionRepository\` with the following code:

<!--?code-excerpt "lib/main.dart (SubscriptionRepository)"?-->

\`\`\`dart class SubscriptionRepository { /// Simulates a network request and then fails. Future subscribe() async { // Simulate a network request. await Future.delayed(const Duration(seconds: 1)); // Fail after one second. throw Exception('Failed to subscribe'); } } \`\`\` The call to \`await Future.delayed()\` with a duration of one second has been added to simulate a long running request. The method execution will pause for a second, and then it will continue running. In order to simulate a request failing, the subscribe method throws an exception at the end. This will be used later on to show how to recover from a failed request when implementing Optimistic State. ### Implement the \`SubscribeButtonViewModel\` To represented the subscription state, as well a possible error state, add the following public members to the \`SubscribeButtonViewModel\`:

<!--?code-excerpt "lib/main.dart (States)"?-->

\`\`\`dart /// Whether the user is subscribed. bool subscribed = false; /// Whether the subscription action has failed. bool error = false; \`\`\` Both are set to \`false\` on start. Following the ideas of Optimistic State, the \`subscribed\` state will change to \`true\` as soon as the user taps the subscribe button. And will only change back to \`false\` if the action fails. The \`error\` state will change to \`true\` when the action fails, indicating the \`SubscribeButton\` widget to show an error message to the user. The variable should go back to \`false\` once the error has been displayed. Next, implement an asynchronous \`subscribe()\` method:

<!--?code-excerpt "lib/main.dart (subscribe)"?-->

\`\`\`dart // Subscription action Future subscribe() async { // Ignore taps when subscribed. if (subscribed) { return; } // Optimistic state. // It will be reverted if the subscription fails. subscribed = true; // Notify listeners to update the UI. notifyListeners(); try { await subscriptionRepository.subscribe(); } catch (e) { print('Failed to subscribe: $e'); // Revert to the previous state. subscribed = false; // Set the error state. error = true; } finally { notifyListeners(); } } \`\`\` As described previously, first the method sets the \`subscribed\` state to \`true\` and then calls to \`notifyListeners()\`. This forces the UI to update and the button changes its appearance, showing the text “Subscribed” to the user. Then the method performs the actual call to the repository. This call is wrapped by a \`try-catch\` in order to catch any exceptions it may throw. In case an exception is caught, the \`subscribed\` state is set back to \`false\`, and the \`error\` state is set to \`true\`. A final call to \`notifyListeners()\` is done to change the UI back to ‘Subscribe’. If there is no exception, the process is complete because the UI is already reflecting the success state. The complete \`SubscribeButtonViewModel\` should look like this:

<!--?code-excerpt "lib/main.dart (ViewModelFull)"?-->

\`\`\`dart /// Subscribe button view model. /// Handles the subscribe action and exposes the state to the subscription. class SubscribeButtonViewModel extends ChangeNotifier { SubscribeButtonViewModel({required this.subscriptionRepository}); final SubscriptionRepository subscriptionRepository; /// Whether the user is subscribed. bool subscribed = false; /// Whether the subscription action has failed. bool error = false; // Subscription action Future subscribe() async { // Ignore taps when subscribed. if (subscribed) { return; } // Optimistic state. // It will be reverted if the subscription fails. subscribed = true; // Notify listeners to update the UI. notifyListeners(); try { await subscriptionRepository.subscribe(); } catch (e) { print('Failed to subscribe: $e'); // Revert to the previous state. subscribed = false; // Set the error state. error = true; } finally { notifyListeners(); } } } \`\`\` ### Implement the \`SubscribeButton\` In this step, you will first implement the build method of the \`SubscribeButton\`, and then implement the feature’s error handling. Add the following code to the build method:

<!--?code-excerpt "lib/main.dart (build)"?-->

\`\`\`dart @override Widget build(BuildContext context) { return ListenableBuilder( listenable: widget.viewModel, builder: (context, \_) { return FilledButton( onPressed: widget.viewModel.subscribe, style: widget.viewModel.subscribed ? SubscribeButtonStyle.subscribed : SubscribeButtonStyle.unsubscribed, child: widget.viewModel.subscribed ? const Text('Subscribed') : const Text('Subscribe'), ); }, ); } \`\`\` This build method contains a \`ListenableBuilder\` that listens to changes from the view model. The builder then creates a \`FilledButton\` that will display the text "Subscribed" or "Subscribe" depending on the view model state. The button style will also change depending on this state. As well, when the button is tapped, it runs the \`subscribe()\` method from the view model. The \`SubscribeButtonStyle\` can be found here. Add this class next to the \`SubscribeButton\`. Feel free to modify the \`ButtonStyle\`.

<!--?code-excerpt "lib/main.dart (style)"?-->

\`\`\`dart class SubscribeButtonStyle { static const unsubscribed = ButtonStyle( backgroundColor: WidgetStatePropertyAll(Colors.red), ); static const subscribed = ButtonStyle( backgroundColor: WidgetStatePropertyAll(Colors.green), ); } \`\`\` If you run the application now, you will see how the button changes when you press it, however it will change back to the original state without showing an error. ### Handling errors To handle errors, add the \`initState()\` and \`dispose()\` methods to the \`SubscribeButtonState\`, and then add the \`\_onViewModelChange()\` method.

<!--?code-excerpt "lib/main.dart (listener1)"?-->

\`\`\`dart @override void initState() { super.initState(); widget.viewModel.addListener(\_onViewModelChange); } @override void dispose() { widget.viewModel.removeListener(\_onViewModelChange); super.dispose(); } \`\`\`

<!--?code-excerpt "lib/main.dart (listener2)"?-->

\`\`\`dart /// Listen to ViewModel changes. void \_onViewModelChange() { // If the subscription action has failed. if (widget.viewModel.error) { // Reset the error state. widget.viewModel.error = false; // Show an error message. ScaffoldMessenger.of(context) .showSnackBar(const SnackBar(content: Text('Failed to subscribe'))); } } \`\`\` The \`addListener()\` call registers the \`\_onViewModelChange()\` method to be called when the view model notifies listeners. It’s important to call \`removeListener()\` when the widget is disposed of, in order to avoid errors. The \`\_onViewModelChange()\` method checks the \`error\` state, and if it is \`true\`, displays a \`Snackbar\` to the user showing an error message. As well, the \`error\` state is set back to \`false\`, to avoid displaying the error message multiple times if \`notifyListeners()\` is called again in the view model. ## Advanced Optimistic State In this tutorial, you’ve learned how to implement an Optimistic State with a single binary state, but you can use this technique to create a more advanced solution by incorporating a third temporal state that indicates that the action is still running. For example, in a chat application when the user sends a new message, the application will display the new chat message in the chat window, but with an icon indicating that the message is still pending to be delivered. When the message is delivered, that icon would be removed. In the subscribe button example, you could add another flag in the view model indicating that the \`subscribe()\` method is still running, or use the Command pattern running state, then modify the button style slightly to show that the operation is running. ## Interactive example This example shows the \`SubscribeButton\` widget together with the \`SubscribeButtonViewModel\` and \`SubscriptionRepository\`, which implement a subscribe tap action with Optimistic State. When you tap the button, the button text changes from “Subscribe” to “Subscribed”. After a second, the repository throws an exception, which gets captured by the view model, and the button reverts back to showing “Subscribe”, while also displaying a Snackbar with an error message.

<!--?code-excerpt "lib/main.dart"?-->

\`\`\`dartpad title="Flutter Optimistic State example in DartPad" run="true" // ignore\_for\_file: avoid\_print import 'package:flutter/material.dart'; void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({super.key}); // This widget is the root of your application. @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( body: Center( child: SubscribeButton( viewModel: SubscribeButtonViewModel( subscriptionRepository: SubscriptionRepository(), ), ), ), ), ); } } /// A button that simulates a subscription action. /// For example, subscribing to a newsletter or a streaming channel. class SubscribeButton extends StatefulWidget { const SubscribeButton({super.key, required this.viewModel}); /// Subscribe button view model. final SubscribeButtonViewModel viewModel; @override State createState() => \_SubscribeButtonState(); } class \_SubscribeButtonState extends State { @override void initState() { super.initState(); widget.viewModel.addListener(\_onViewModelChange); } @override void dispose() { widget.viewModel.removeListener(\_onViewModelChange); super.dispose(); } @override Widget build(BuildContext context) { return ListenableBuilder( listenable: widget.viewModel, builder: (context, \_) { return FilledButton( onPressed: widget.viewModel.subscribe, style: widget.viewModel.subscribed ? SubscribeButtonStyle.subscribed : SubscribeButtonStyle.unsubscribed, child: widget.viewModel.subscribed ? const Text('Subscribed') : const Text('Subscribe'), ); }, ); } /// Listen to ViewModel changes. void \_onViewModelChange() { // If the subscription action has failed. if (widget.viewModel.error) { // Reset the error state. widget.viewModel.error = false; // Show an error message. ScaffoldMessenger.of(context) .showSnackBar(const SnackBar(content: Text('Failed to subscribe'))); } } } class SubscribeButtonStyle { static const unsubscribed = ButtonStyle( backgroundColor: WidgetStatePropertyAll(Colors.red), ); static const subscribed = ButtonStyle( backgroundColor: WidgetStatePropertyAll(Colors.green), ); } /// Subscribe button view model. /// Handles the subscribe action and exposes the state to the subscription. class SubscribeButtonViewModel extends ChangeNotifier { SubscribeButtonViewModel({required this.subscriptionRepository}); final SubscriptionRepository subscriptionRepository; /// Whether the user is subscribed. bool subscribed = false; /// Whether the subscription action has failed. bool error = false; // Subscription action Future subscribe() async { // Ignore taps when subscribed. if (subscribed) { return; } // Optimistic state. // It will be reverted if the subscription fails. subscribed = true; // Notify listeners to update the UI. notifyListeners(); try { await subscriptionRepository.subscribe(); } catch (e) { print('Failed to subscribe: $e'); // Revert to the previous state. subscribed = false; // Set the error state. error = true; } finally { notifyListeners(); } } } /// Repository of subscriptions. class SubscriptionRepository { /// Simulates a network request and then fails. Future subscribe() async { // Simulate a network request. await Future.delayed(const Duration(seconds: 1)); // Fail after one second. throw Exception('Failed to subscribe'); } } \`\`\` \[Flutter Architecture guidelines]:/app-architecture

----
url: https://docs.flutter.dev/testing
----

[Debug Flutter apps from code](/testing/code-debugging)

[How to enable various debugging tools from your code and at the command line.](/testing/code-debugging)

[Testing plugins](/testing/testing-plugins)

[Learn how to test your plugin package.](/testing/testing-plugins)

[Debug Flutter apps](/testing/debugging)

[How to debug your Flutter app.](/testing/debugging)

[Handling errors in Flutter](/testing/errors)

[How to control error messages and logging of errors](/testing/errors)

[Plugins in Flutter tests](/testing/plugins-in-tests)

[Adding plugin as part of your Flutter tests.](/testing/plugins-in-tests)

[Flutter's build modes](/testing/build-modes)

[Describes Flutter's build modes and when you should use debug, release, or profile mode.](/testing/build-modes)

[Common Flutter errors](/testing/common-errors)

[How to recognize and resolve common Flutter framework errors.](/testing/common-errors)

[Check app functionality with an integration test](/testing/integration-tests)

[Learn how to write integration tests](/testing/integration-tests)

[Use a native language debugger](/testing/native-debugging)

[How to connect a native debugger to your running Flutter app.](/testing/native-debugging)

[Testing Flutter apps](/testing/overview)

[Learn more about the different types of testing and how to write them.](/testing/overview)

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/testing/index.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/testing\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/testing/index.md "Report an issue with this page").

----
url: https://docs.flutter.dev/platform-integration/web/web-images.md
----

\# Display images on the web > Learn how to load and display images on the web. The web supports the standard \[\`Image\`]\[] widget and the more advanced \[\`dart:ui/Image\`]\[] class (where more fine-grained control is needed to display images). However, because web browsers are built to run untrusted code safely, there are certain limitations in what you can do with images compared to mobile and desktop platforms. This page explains these limitations and offers ways to work around them. \[\`Image\`]: https\://api.flutter.dev/flutter/widgets/Image-class.html \[\`dart:ui/Image\`]: https\://api.flutter.dev/flutter/dart-ui/Image-class.html :::note For information on how to optimize web loading speed, read the Flutter blog post \[Best practices for optimizing Flutter web loading speed]\[article]. \[article]: https\://blog.flutter.dev/best-practices-for-optimizing-flutter-web-loading-speed-7cc0df14ce5c ::: ## Background The web offers several methods for displaying images: - The built-in \[\`\`]\[] and \[\`

----
url: https://docs.flutter.dev/app-architecture/design-patterns/optimistic-state
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Optimistic state](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

When building user experiences, the perception of performance is sometimes just as important as the actual performance of the code. In general, users don’t like waiting for an action to finish to see the result, and anything that takes more than a few milliseconds could be considered “slow” or “unresponsive” from the user’s perspective.

Developers can help mitigate this negative perception by presenting a successful UI state before the background task is fully completed. An example of this would be tapping a “Subscribe” button, and seeing it change to “Subscribed” instantly, even if the background call to the subscription API is still running.

This technique is known as Optimistic State, Optimistic UI or Optimistic User Experience. In this recipe, you will implement an application feature using Optimistic State and following the [Flutter architecture guidelines](/app-architecture).

## Example feature: a subscribe button

[#](#example-feature-a-subscribe-button)

This example implements a subscribe button similar to the one you could find in a video streaming application or a newsletter.

When the button is tapped, the application then calls an external API, performing a subscription action, for example recording in a database that the user is now in the subscription list. For demo purposes, you will not implement the actual backend code, instead you will replace this call with a fake action that will simulate a network request.

In the case that the call is successful, the button text will change from “Subscribe” to “Subscribed”. The button background color will change as well.

On the contrary, if the call fails, the button text should revert back to “Subscribe”, and the UI should show an error message to the user, for example using a Snackbar.

Following the Optimistic State idea, the button should instantly change to “Subscribed” once it is tapped, and only change back to “Subscribe” if the request failed.

## Feature architecture

[#](#feature-architecture)

Start by defining the feature architecture. Following the architecture guidelines, create these Dart classes in a Flutter project:

* A `StatefulWidget` named `SubscribeButton`
* A class named `SubscribeButtonViewModel` extending `ChangeNotifier`
* A class named `SubscriptionRepository`

dart

```
class SubscribeButton extends StatefulWidget {
  const SubscribeButton({super.key});
​
  @override
  State<SubscribeButton> createState() => _SubscribeButtonState();
}
​
class _SubscribeButtonState extends State<SubscribeButton> {
  @override
  Widget build(BuildContext context) {
    return const Placeholder();
  }
}
​
class SubscribeButtonViewModel extends ChangeNotifier {}
​
class SubscriptionRepository {}
```

The `SubscribeButton` widget and the `SubscribeButtonViewModel` represent the presentation layer of this solution. The widget is going to display a button that will show the text “Subscribe” or “Subscribed” depending on the subscription state. The view model will contain the subscription state. When the button is tapped, the widget will call the view model to perform the action.

The `SubscriptionRepository` will implement a subscribe method that will throw an exception when the action fails. The view model will call this method when performing the subscription action.

Next, connect them together by adding the `SubscriptionRepository` to the `SubscribeButtonViewModel`:

dart

```
class SubscribeButtonViewModel extends ChangeNotifier {
  SubscribeButtonViewModel({required this.subscriptionRepository});
​
  final SubscriptionRepository subscriptionRepository;
}
```

And add the `SubscribeButtonViewModel` to the `SubscribeButton` widget:

dart

```
class SubscribeButton extends StatefulWidget {
  const SubscribeButton({super.key, required this.viewModel});
​
  /// Subscribe button view model.
  final SubscribeButtonViewModel viewModel;
​
  @override
  State<SubscribeButton> createState() => _SubscribeButtonState();
}
```

Now that you have created the basic solution architecture, you can create the `SubscribeButton` widget the following way:

dart

```
SubscribeButton(
  viewModel: SubscribeButtonViewModel(
    subscriptionRepository: SubscriptionRepository(),
  ),
)
```

### Implement the `SubscriptionRepository`

[#](#implement-the-subscriptionrepository)

Add a new asynchronous method named `subscribe()` to the `SubscriptionRepository` with the following code:

dart

```
class SubscriptionRepository {
  /// Simulates a network request and then fails.
  Future<void> subscribe() async {
    // Simulate a network request.
    await Future<void>.delayed(const Duration(seconds: 1));
    // Fail after one second.
    throw Exception('Failed to subscribe');
  }
}
```

The call to `await Future.delayed()` with a duration of one second has been added to simulate a long running request. The method execution will pause for a second, and then it will continue running.

In order to simulate a request failing, the subscribe method throws an exception at the end. This will be used later on to show how to recover from a failed request when implementing Optimistic State.

### Implement the `SubscribeButtonViewModel`

[#](#implement-the-subscribebuttonviewmodel)

To represented the subscription state, as well a possible error state, add the following public members to the `SubscribeButtonViewModel`:

dart

```
/// Whether the user is subscribed.
bool subscribed = false;
​
/// Whether the subscription action has failed.
bool error = false;
```

Both are set to `false` on start.

Following the ideas of Optimistic State, the `subscribed` state will change to `true` as soon as the user taps the subscribe button. And will only change back to `false` if the action fails.

The `error` state will change to `true` when the action fails, indicating the `SubscribeButton` widget to show an error message to the user. The variable should go back to `false` once the error has been displayed.

Next, implement an asynchronous `subscribe()` method:

dart

```
// Subscription action
Future<void> subscribe() async {
  // Ignore taps when subscribed.
  if (subscribed) {
    return;
  }
​
  // Optimistic state.
  // It will be reverted if the subscription fails.
  subscribed = true;
  // Notify listeners to update the UI.
  notifyListeners();
​
  try {
    await subscriptionRepository.subscribe();
  } catch (e) {
    print('Failed to subscribe: $e');
    // Revert to the previous state.
    subscribed = false;
    // Set the error state.
    error = true;
  } finally {
    notifyListeners();
  }
}
```

As described previously, first the method sets the `subscribed` state to `true` and then calls to `notifyListeners()`. This forces the UI to update and the button changes its appearance, showing the text “Subscribed” to the user.

Then the method performs the actual call to the repository. This call is wrapped by a `try-catch` in order to catch any exceptions it may throw. In case an exception is caught, the `subscribed` state is set back to `false`, and the `error` state is set to `true`. A final call to `notifyListeners()` is done to change the UI back to ‘Subscribe’.

If there is no exception, the process is complete because the UI is already reflecting the success state.

The complete `SubscribeButtonViewModel` should look like this:

dart

```
/// Subscribe button view model.
/// Handles the subscribe action and exposes the state to the subscription.
class SubscribeButtonViewModel extends ChangeNotifier {
  SubscribeButtonViewModel({required this.subscriptionRepository});
​
  final SubscriptionRepository subscriptionRepository;
​
  /// Whether the user is subscribed.
  bool subscribed = false;
​
  /// Whether the subscription action has failed.
  bool error = false;
​
  // Subscription action
  Future<void> subscribe() async {
    // Ignore taps when subscribed.
    if (subscribed) {
      return;
    }
​
    // Optimistic state.
    // It will be reverted if the subscription fails.
    subscribed = true;
    // Notify listeners to update the UI.
    notifyListeners();
​
    try {
      await subscriptionRepository.subscribe();
    } catch (e) {
      print('Failed to subscribe: $e');
      // Revert to the previous state.
      subscribed = false;
      // Set the error state.
      error = true;
    } finally {
      notifyListeners();
    }
  }
}
```

### Implement the `SubscribeButton`

[#](#implement-the-subscribebutton)

In this step, you will first implement the build method of the `SubscribeButton`, and then implement the feature’s error handling.

Add the following code to the build method:

dart

```
@override
Widget build(BuildContext context) {
  return ListenableBuilder(
    listenable: widget.viewModel,
    builder: (context, _) {
      return FilledButton(
        onPressed: widget.viewModel.subscribe,
        style: widget.viewModel.subscribed
            ? SubscribeButtonStyle.subscribed
            : SubscribeButtonStyle.unsubscribed,
        child: widget.viewModel.subscribed
            ? const Text('Subscribed')
            : const Text('Subscribe'),
      );
    },
  );
}
```

This build method contains a `ListenableBuilder` that listens to changes from the view model. The builder then creates a `FilledButton` that will display the text "Subscribed" or "Subscribe" depending on the view model state. The button style will also change depending on this state. As well, when the button is tapped, it runs the `subscribe()` method from the view model.

The `SubscribeButtonStyle` can be found here. Add this class next to the `SubscribeButton`. Feel free to modify the `ButtonStyle`.

dart

```
class SubscribeButtonStyle {
  static const unsubscribed = ButtonStyle(
    backgroundColor: WidgetStatePropertyAll(Colors.red),
  );
​
  static const subscribed = ButtonStyle(
    backgroundColor: WidgetStatePropertyAll(Colors.green),
  );
}
```

If you run the application now, you will see how the button changes when you press it, however it will change back to the original state without showing an error.

### Handling errors

[#](#handling-errors)

To handle errors, add the `initState()` and `dispose()` methods to the `SubscribeButtonState`, and then add the `_onViewModelChange()` method.

dart

```
@override
void initState() {
  super.initState();
  widget.viewModel.addListener(_onViewModelChange);
}
​
@override
void dispose() {
  widget.viewModel.removeListener(_onViewModelChange);
  super.dispose();
}
```

dart

```
/// Listen to ViewModel changes.
void _onViewModelChange() {
  // If the subscription action has failed.
  if (widget.viewModel.error) {
    // Reset the error state.
    widget.viewModel.error = false;
    // Show an error message.
    ScaffoldMessenger.of(context)
        .showSnackBar(const SnackBar(content: Text('Failed to subscribe')));
  }
}
```

The `addListener()` call registers the `_onViewModelChange()` method to be called when the view model notifies listeners. It’s important to call `removeListener()` when the widget is disposed of, in order to avoid errors.

The `_onViewModelChange()` method checks the `error` state, and if it is `true`, displays a `Snackbar` to the user showing an error message. As well, the `error` state is set back to `false`, to avoid displaying the error message multiple times if `notifyListeners()` is called again in the view model.

## Advanced Optimistic State

[#](#advanced-optimistic-state)

In this tutorial, you’ve learned how to implement an Optimistic State with a single binary state, but you can use this technique to create a more advanced solution by incorporating a third temporal state that indicates that the action is still running.

For example, in a chat application when the user sends a new message, the application will display the new chat message in the chat window, but with an icon indicating that the message is still pending to be delivered. When the message is delivered, that icon would be removed.

In the subscribe button example, you could add another flag in the view model indicating that the `subscribe()` method is still running, or use the Command pattern running state, then modify the button style slightly to show that the operation is running.

## Interactive example

[#](#interactive-example)

This example shows the `SubscribeButton` widget together with the `SubscribeButtonViewModel` and `SubscriptionRepository`, which implement a subscribe tap action with Optimistic State.

When you tap the button, the button text changes from “Subscribe” to “Subscribed”. After a second, the repository throws an exception, which gets captured by the view model, and the button reverts back to showing “Subscribe”, while also displaying a Snackbar with an error message.

```
// ignore_for_file: avoid_print

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: Center(
          child: SubscribeButton(
            viewModel: SubscribeButtonViewModel(
              subscriptionRepository: SubscriptionRepository(),
            ),
          ),
        ),
      ),
    );
  }
}

/// A button that simulates a subscription action.
/// For example, subscribing to a newsletter or a streaming channel.
class SubscribeButton extends StatefulWidget {
  const SubscribeButton({super.key, required this.viewModel});

  /// Subscribe button view model.
  final SubscribeButtonViewModel viewModel;

  @override
  State<SubscribeButton> createState() => _SubscribeButtonState();
}

class _SubscribeButtonState extends State<SubscribeButton> {
  @override
  void initState() {
    super.initState();
    widget.viewModel.addListener(_onViewModelChange);
  }

  @override
  void dispose() {
    widget.viewModel.removeListener(_onViewModelChange);
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return ListenableBuilder(
      listenable: widget.viewModel,
      builder: (context, _) {
        return FilledButton(
          onPressed: widget.viewModel.subscribe,
          style: widget.viewModel.subscribed
              ? SubscribeButtonStyle.subscribed
              : SubscribeButtonStyle.unsubscribed,
          child: widget.viewModel.subscribed
              ? const Text('Subscribed')
              : const Text('Subscribe'),
        );
      },
    );
  }

  /// Listen to ViewModel changes.
  void _onViewModelChange() {
    // If the subscription action has failed.
    if (widget.viewModel.error) {
      // Reset the error state.
      widget.viewModel.error = false;
      // Show an error message.
      ScaffoldMessenger.of(context)
          .showSnackBar(const SnackBar(content: Text('Failed to subscribe')));
    }
  }
}

class SubscribeButtonStyle {
  static const unsubscribed = ButtonStyle(
    backgroundColor: WidgetStatePropertyAll(Colors.red),
  );

  static const subscribed = ButtonStyle(
    backgroundColor: WidgetStatePropertyAll(Colors.green),
  );
}

/// Subscribe button view model.
/// Handles the subscribe action and exposes the state to the subscription.
class SubscribeButtonViewModel extends ChangeNotifier {
  SubscribeButtonViewModel({required this.subscriptionRepository});

  final SubscriptionRepository subscriptionRepository;

  /// Whether the user is subscribed.
  bool subscribed = false;

  /// Whether the subscription action has failed.
  bool error = false;

  // Subscription action
  Future<void> subscribe() async {
    // Ignore taps when subscribed.
    if (subscribed) {
      return;
    }

    // Optimistic state.
    // It will be reverted if the subscription fails.
    subscribed = true;
    // Notify listeners to update the UI.
    notifyListeners();

    try {
      await subscriptionRepository.subscribe();
    } catch (e) {
      print('Failed to subscribe: $e');
      // Revert to the previous state.
      subscribed = false;
      // Set the error state.
      error = true;
    } finally {
      notifyListeners();
    }
  }
}

/// Repository of subscriptions.
class SubscriptionRepository {
  /// Simulates a network request and then fails.
  Future<void> subscribe() async {
    // Simulate a network request.
    await Future<void>.delayed(const Duration(seconds: 1));
    // Fail after one second.
    throw Exception('Failed to subscribe');
  }
}
```

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-09-02. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/app-architecture/design-patterns/optimistic-state.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/app-architecture/design-patterns/optimistic-state\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/app-architecture/design-patterns/optimistic-state.md "Report an issue with this page").

----
url: https://docs.flutter.dev/data-and-backend/persistence
----

[Choose a database for your Flutter app](/data-and-backend/persistence/choose-a-database)

[Compare Cloud Firestore, Firebase SQL Connect, SQLite, and key-value storage to pick the right database for your Flutter app.](/data-and-backend/persistence/choose-a-database)

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/data-and-backend/persistence/index.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/data-and-backend/persistence\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/data-and-backend/persistence/index.md "Report an issue with this page").

----
url: https://docs.flutter.dev/reference/user-surveys.md
----

\# User surveys > Why users see a survey announcement, how the data is used, and how to disable. ## Why do I see a survey announcement? If you have not opted-out of Flutter's \[analytics and crash reporting]\(/reference/crash-reporting), you may receive a survey announcement in your IDE. We run two types of surveys: 1. \*\*Each quarter.\*\* We give all active Flutter and Dart users the option to take this survey. 2. \*\*Ad-hoc.\*\* We design and deploy experimental surveys when we want to learn more about specific topics. If your telemetry data meets the survey criteria, you might see a survey announcement. ## How will my responses be used? We use the responses you submit via survey to improve Flutter and Dart. We store this information independent of the information sent to Google via analytics. To see how we used prior surveys to improve Flutter and Dart, check out the survey posts on the \[Flutter blog]\[]. ## How can I disable it? To mute survey announcements, you might do one of the following: 1. Click the button on the message. 2. Opt-out of analytics and crash reporting per the steps given in \[Disabling analytics reporting]\(/reference/crash-reporting#disabling-analytics-reporting). \[Flutter blog]: https\://blog.flutter.dev/search?q=survey

----
url: https://docs.flutter.dev/install/troubleshoot.md
----

\# Troubleshooting installation > Get help with common installation issues that new Flutter developers might have run into. This page describes some common installation issues that new Flutter users have encountered and offers suggestions on how to resolve them. If you are still experiencing problems after using this page, consider reaching out to any of the resources listed under \[community support channels]\[]. To add a topic to this page or make a correction, you can \[file an issue]\[] or submit a \[pull request]\[] on GitHub. \[community support channels]: #community-support \[file an issue]: https\://github.com/flutter/website/issues/new \[pull request]: https\://github.com/flutter/website/pulls ## Get the Flutter SDK ### Unable to find the \`flutter\` command \_\_What does this issue look like?\_\_ When you try to run the \`flutter\` command, the console fails to find it. The error usually looks as follows: \`\`\`plaintext 'flutter' is not recognized as an internal or external command operable program or batch file \`\`\` Error messages on macOS and Linux could look slightly different from the one on Windows. \_\_Explanation and suggestions\_\_ Did you add Flutter to the \`PATH\` environment variable for your platform? On Windows, follow these \[instructions for adding a command to your path]\[windows path]. If you've already \[set up VS Code]\[] for Flutter development, you can use the Flutter extension's \*\*Locate SDK\*\* prompt to identify the location of your \`flutter\` folder. See also: \[Configuring PATH and Environment Variables - Dart Code]\[config path] \[windows path]: https\://www\.wikihow\.com/Change-the-PATH-Environment-Variable-on-Windows \[set up VS Code]: /tools/vs-code#setup \[config path]: https\://dartcode.org/docs/configuring-path-and-environment-variables/ ### Flutter in special folders \_\_What does this issue look like?\_\_ Running your Flutter project produces an error like the following: \`\`\`plaintext The Flutter SDK is installed in a protected folder and may not function correctly. Please move the SDK to a location that is user-writable without Administration permissions and restart. \`\`\` \_\_Explanation and suggestions\_\_ On Windows, this usually happens when Flutter is installed in a directory like \`C:\Program Files\\\` that requires elevated privileges. Try relocating Flutter to a different folder, such as \`C:\src\flutter\`. ### Invoke-Expression: You cannot call a method on a null-valued expression \_\_What does this issue look like?\_\_ When running \`flutter doctor\` on Windows, you might see an error like: \`\`\`plaintext Invoke-Expression : You cannot call a method on a null-valued expression. At ...\update\_engine\_version.ps1:60 char:20 \`\`\` \_\_Explanation and suggestions\_\_ This error typically occurs when the \`SystemRoot\` environment variable is missing or when the PowerShell execution policy prevents the script from running correctly. To resolve this: 1. \*\*Run as Administrator\*\*: Open your PowerShell terminal as an Administrator. 2. \*\*Check Environment Variables\*\*: Ensure the \`SystemRoot\` environment variable is set (usually to \`C:\Windows\`). You can check its value by running \`echo $env:SystemRoot\` in your PowerShell terminal. 3. \*\*Check Execution Policy\*\*: If the issue persists, you might need to adjust your execution policy. Run the following command in an Administrator PowerShell window: \`\`\`powershell Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser \`\`\` ## Android setup ### Having multiple versions of Java installed \_\_What does this issue look like?\_\_ The command \`flutter doctor --android-licenses\` fails. Running \`flutter doctor --verbose\` gives an error message like the following: \`\`\`plaintext java.lang.UnsupportedClassVersionError: com/android/prefs/AndroidLocationsProvider has been compiled by a more recent version of the Java Runtime (class file version 55.0), this version of the Java Runtime only recognizes class file versions up to 52.0 \`\`\` \_\_Explanation and suggestions\_\_ The error occurs when an older version of the Java Development Kit (JDK) is installed on your computer. If you don't need multiple versions of Java, uninstall existing JDKs from your computer. Flutter automatically uses the JDK included in Android Studio. If you do need another version of Java, try the workaround described in \[this GitHub issue]\[java binary path] until a long-term solution is implemented. For more information, check out the \[Android Java Gradle migration guide]\[] or \[flutter doctor --android-licenses not working due to java.lang.UnsupportedClassVersionError - Stack Overflow]\[so java version]. \[java binary path]: https\://github.com/flutter/flutter/issues/106416#issuecomment-1522198064 \[Android Java Gradle migration guide]: /release/breaking-changes/android-java-gradle-migration-guide \[so java version]: https\://stackoverflow\.com/questions/75328050/ ### \`cmdline-tools\` component is missing \_\_What does this issue look like?\_\_ The \`flutter doctor\` command complains that the \`cmdline-tools\` are missing from the Android toolchain. For example: \`\`\`plaintext noHighlight \[!] Android toolchain - develop for Android devices (Android SDK version 33.0.2) • Android SDK at C:\Users\My PC\AppData\Local\Android\sdk X cmdline-tools component is missing \`\`\` \_\_Explanation and suggestions\_\_ The easiest way to get the cmdline-tools is through the SDK Manager in Android Studio. To do this, use the following instructions: 1. Open the SDK Manager from Android Studio by selecting \*\*Tools > SDK Manager\*\* from the menu bar. 2. Select the latest Android SDK (or a specific version that your app requires), Android SDK Command-line Tools, and Android SDK Build-Tools. 3. Click \*\*Apply\*\* to install the selected artifacts. !\[Android Studio SDK Manager]\(/assets/images/docs/get-started/install\_android\_tools.png) If you're not using Android Studio, you can download the tools using the \[sdkmanager]\[] command-line tool. \[sdkmanager]: https\://developer.android.com/studio/command-line/sdkmanager ### Android license status unknown \_\_What does this issue look like?\_\_ When running \`flutter doctor\`, you see an error indicating that the Android license status is unknown: \`\`\`plaintext noHighlight \[!] Android toolchain - develop for Android devices ! Android license status unknown. Run \`flutter doctor --android-licenses\` to accept the SDK licenses. See https\://flutter.dev/to/macos-android-setup for more details. \`\`\` \_\_Explanation and suggestions\_\_ This issue occurs when you haven't accepted the Android SDK licenses yet, or when \`flutter doctor\` can't locate the necessary Android SDK command-line tools to check license status. To resolve this issue: 1. Ensure that the \*\*Android SDK Command-line Tools\*\* component is installed: 1. Open Android Studio. 2. Open the SDK Manager (\*\*Tools > SDK Manager\*\* or \*\*More Actions > SDK Manager\*\*). 3. Switch to the \*\*SDK Tools\*\* tab. 4. Select \*\*Android SDK Command-line Tools (latest)\*\*. 5. Click \*\*Apply\*\*, then \*\*OK\*\* to install the component. 1. Run the license agreement command in your terminal: \`\`\`console $ flutter doctor --android-licenses \`\`\` 1. Review and accept each license by entering \`y\` when prompted. 1. Run \`flutter doctor\` again to verify that all licenses are accepted: \`\`\`console $ flutter doctor \`\`\` ## macOS setup ### SocketException: Send failed, OS Error: No route to host, errno = 65 \_\_What does this issue look like?\_\_ On macOS, the \`flutter run\` command produces an error like: \`\`\`plaintext $ flutter run Launching lib/main.dart in debug mode... ... Installing and launching... Oops; flutter has exited unexpectedly: "SocketException: Send failed (OS Error: No route to host, errno = 65), address = 0.0.0.0, port = 5353". \`\`\` \_\_Explanation and suggestions\_\_ This issue is related to macOS permissions. To fix this: 1. Upgrade your Flutter SDK to the latest version. 2. Open \*\*System Settings\*\* > \*\*Privacy & Security\*\* > \*\*Local Network\*\*. Toggle on the permission for all the code editors and terminals you use to launch Flutter apps. You might need to restart your code editor, terminal, and physical device. ## Other problems ### Exit code 69 \_\_What does this issue look like?\_\_ Running a \`flutter\` command produces an "exit code: 69" error, as shown in the following example: \`\`\`plaintext Running "flutter pub get" in flutter\_tools... Resolving dependencies in .../flutter/packages/flutter\_tools... (28.0s) Got TLS error trying to find package test at https\://pub.dev/. pub get failed command: ".../flutter/bin/cache/dart-sdk/bin/ dart \_\_deprecated\_pub --color --directory .../flutter/packages/flutter\_tools get --example" pub env: { "FLUTTER\_ROOT": ".../flutter", "PUB\_ENVIRONMENT": "flutter\_cli:get", "PUB\_CACHE": ".../.pub-cache", } exit code: 69 \`\`\` \_\_Explanation and suggestions\_\_ This issue is related to networking. Try the following instructions to troubleshoot: \* Check your internet connection. Make sure that you're connected to the internet and that your connection is stable. \* Restart your devices, including your computer and networking equipment. \* Use a VPN to help to bypass any restrictions that might prevent you from connecting to the network. \* If you have tried all of these steps and are still getting the error, print out verbose logs with the \`flutter doctor -v\` command and ask for help in one of the \[community support channels]\[]. \[community support channels]: #community-support ## Community support The Flutter community is helpful and welcoming. If none of the above suggestions solves your installation issue, consider asking for support from one of the following channels: \* \[/r/flutterhelp]\(https\://www\.reddit.com/r/flutterhelp/) on Reddit \* \[/r/flutterdev]\(https\://discord.gg/rflutterdev) on Discord, particularly the \`install-and-setup\` channel on this server. \* \[StackOverflow]\[], in particular, questions tagged with \[#flutter]\[] or \[#dart]\[]. To be respectful of everyone's time, search the archive for a similar issue before posting a new one. \[StackOverflow]: https\://stackoverflow\.com \[#dart]: https\://stackoverflow\.com/questions/tagged/dart \[#flutter]: https\://stackoverflow\.com/questions/tagged/flutter

----
url: https://docs.flutter.dev/perf/isolates
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Concurrency and isolates](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

All Dart code runs in [isolates](https://dart.dev/language/concurrency), which are similar to threads, but differ in that isolates have their own isolated memory. They do not share state in any way, and can only communicate by messaging. By default, Flutter apps do all of their work on a single isolate – the main isolate. In most cases, this model allows for simpler programming and is fast enough that the application's UI doesn't become unresponsive.

Sometimes though, applications need to perform exceptionally large computations that can cause "UI jank" (jerky motion). If your app is experiencing jank for this reason, you can move these computations to a helper isolate. This allows the underlying runtime environment to run the computation concurrently with the main UI isolate's work and takes advantage of multi-core devices.

Each isolate has its own memory and its own event loop. The event loop processes events in the order that they're added to an event queue. On the main isolate, these events can be anything from handling a user tapping in the UI, to executing a function, to painting a frame on the screen. The following figure shows an example event queue with 3 events waiting to be processed.

For smooth rendering, Flutter adds a "paint frame" event to the event queue 60 times per second(for a 60Hz device). If these events aren't processed on time, the application experiences UI jank, or worse, become unresponsive altogether.

Whenever a process can't be completed in a frame gap, the time between two frames, it's a good idea to offload the work to another isolate to ensure that the main isolate can produce 60 frames per second. When you spawn an isolate in Dart, it can process the work concurrently with the main isolate, without blocking it.

You can read more about how isolates and the event loop work in Dart on the [concurrency page](https://dart.dev/language/concurrency) of the Dart documentation.

[Watch on YouTube in a new tab: "Isolates and the event loop | Flutter in Focus"](https://www.youtube.com/watch/vl_AaCgudcY)

## Common use cases for isolates

[#](#common-use-cases-for-isolates)

There is only one hard rule for when you should use isolates, and that's when large computations are causing your Flutter application to experience UI jank. This jank happens when there is any computation that takes longer than Flutter's frame gap.

Any process *could* take longer to complete, depending on the implementation and the input data, making it impossible to create an exhaustive list of when you need to consider using isolates.

That said, isolates are commonly used for the following:

* Reading data from a local database
* Sending push notifications
* Parsing and decoding large data files
* Processing or compressing photos, audio files, and video files
* Converting audio and video files
* When you need asynchronous support while using FFI
* Applying filtering to complex lists or filesystems

## Message passing between isolates

[#](#message-passing-between-isolates)

Dart's isolates are an implementation of the [Actor model](https://en.wikipedia.org/wiki/Actor_model). They can only communicate with each other by message passing, which is done with [`Port` objects](https://api.dart.dev/dart-isolate/ReceivePort-class.html). When messages are "passed" between each other, they are generally copied from the sending isolate to the receiving isolate. This means that any value passed to an isolate, even if mutated on that isolate, doesn't change the value on the original isolate.

The only [objects that aren't copied when passed](https://api.dart.dev/dart-isolate/SendPort/send.html) to an isolate are immutable objects that can't be changed anyway, such a String or an unmodifiable byte. When you pass an immutable object between isolates, a reference to that object is sent across the port, rather than the object being copied, for better performance. Because immutable objects can't be updated, this effectively retains the actor model behavior.

An exception to this rule is when an isolate exits when it sends a message using the `Isolate.exit` method. Because the sending isolate won't exist after sending the message, it can pass ownership of the message from one isolate to the other, ensuring that only one isolate can access the message.

The two lowest-level primitives that send messages are `SendPort.send`, which makes a copy of a mutable message as it sends, and `Isolate.exit`, which sends the reference to the message. Both `Isolate.run` and `compute` use `Isolate.exit` under the hood.

## Short-lived isolates

[#](#short-lived-isolates)

The easiest way to move a process to an isolate in Flutter is with the `Isolate.run` method. This method spawns an isolate, passes a callback to the spawned isolate to start some computation, returns a value from the computation, and then shuts the isolate down when the computation is complete. This all happens concurrently with the main isolate, and doesn't block it.

The `Isolate.run` method requires a single argument, a callback function, that is run on the new isolate. This callback's function signature must have exactly one required, unnamed argument. When the computation completes, it returns the callback's value back to the main isolate, and exits the spawned isolate.

For example, consider this code that loads a large JSON blob from a file, and converts that JSON into custom Dart objects. If the json decoding process wasn't off loaded to a new isolate, this method would cause the UI to become unresponsive for several seconds.

dart

```
// Produces a list of 211,640 photo objects.
// (The JSON file is ~20MB.)
Future<List<Photo>> getPhotos() async {
  final String jsonString = await rootBundle.loadString('assets/photos.json');
  final List<Photo> photos = await Isolate.run<List<Photo>>(() {
    final List<Object?> photoData = jsonDecode(jsonString) as List<Object?>;
    return photoData.cast<Map<String, Object?>>().map(Photo.fromJson).toList();
  });
  return photos;
}
```

For a complete walkthrough of using Isolates to parse JSON in the background, see [this cookbook recipe](/cookbook/networking/background-parsing).

## Stateful, longer-lived isolates

[#](#stateful-longer-lived-isolates)

Short-live isolates are convenient to use, but there is performance overhead required to spawn new isolates, and to copy objects from one isolate to another. If you're doing the same computation using `Isolate.run` repeatedly, you might have better performance by creating isolates that don't exit immediately.

To do this, you can use a handful of lower-level isolate-related APIs that `Isolate.run` abstracts:

* [`Isolate.spawn()`](https://api.dart.dev/dart-isolate/Isolate/spawn.html) and [`Isolate.exit()`](https://api.dart.dev/dart-isolate/Isolate/exit.html)
* [`ReceivePort`](https://api.dart.dev/dart-isolate/ReceivePort-class.html) and [`SendPort`](https://api.dart.dev/dart-isolate/SendPort-class.html)
* [`send()`](https://api.dart.dev/dart-isolate/SendPort/send.html) method

When you use the `Isolate.run` method, the new isolate immediately shuts down after it returns a single message to the main isolate. Sometimes, you'll need isolates that are long lived, and can pass multiple messages to each other over time. In Dart, you can accomplish this with the Isolate API and Ports. These long-lived isolates are colloquially known as *background workers*.

Long-lived isolates are useful when you have a specific process that either needs to be run repeatedly throughout the lifetime of your application, or if you have a process that runs over a period of time and needs to yield multiple return values to the main isolate.

Or, you might use [worker\_manager](https://pub.dev/packages/worker_manager) to manage long-lived isolates.

### ReceivePorts and SendPorts

[#](#receiveports-and-sendports)

Set up long-lived communication between isolates with two classes (in addition to Isolate): [`ReceivePort`](https://api.dart.dev/dart-isolate/ReceivePort-class.html) and [`SendPort`](https://api.dart.dev/dart-isolate/SendPort-class.html). These ports are the only way isolates can communicate with each other.

`Ports` behave similarly to `Streams`, in which the `StreamController` or `Sink` is created in one isolate, and the listener is set up in the other isolate. In this analogy, the `StreamController` is called a `SendPort`, and you can "add" messages with the `send()` method. `ReceivePort`s are the listeners, and when these listeners receive a new message, they call a provided callback with the message as an argument.

For an in-depth explanation on setting up two-way communication between the main isolate and a worker isolate, follow the examples in the [Dart documentation](https://dart.dev/language/concurrency).

## Using platform plugins in isolates

[#](#using-platform-plugins-in-isolates)

You can use platform plugins in background isolates. This enables plugins to offload heavy, platform-dependent computations to an isolate that won't block your UI. For example, imagine you're encrypting data using a native host API (such as an Android API on Android, an iOS API on iOS, and so on). Previously, [marshaling data](https://en.wikipedia.org/wiki/Marshalling_\(computer_science\)) to the host platform could waste UI thread time, and can now be done in a background isolate.

Platform channel isolates use the [`BackgroundIsolateBinaryMessenger`](https://api.flutter.dev/flutter/services/BackgroundIsolateBinaryMessenger-class.html) API. The following snippet shows an example of using the `shared_preferences` package in a background isolate.

dart

```
import 'dart:isolate';
​
import 'package:flutter/services.dart';
import 'package:shared_preferences/shared_preferences.dart';
​
void main() {
  // Identify the root isolate to pass to the background isolate.
  RootIsolateToken rootIsolateToken = RootIsolateToken.instance!;
  Isolate.spawn(_isolateMain, rootIsolateToken);
}
​
Future<void> _isolateMain(RootIsolateToken rootIsolateToken) async {
  // Register the background isolate with the root isolate.
  BackgroundIsolateBinaryMessenger.ensureInitialized(rootIsolateToken);
​
  // You can now use the shared_preferences plugin.
  SharedPreferences sharedPreferences = await SharedPreferences.getInstance();
​
  print(sharedPreferences.getBool('isDebug'));
}
```

## Limitations of Isolates

[#](#limitations-of-isolates)

If you're coming to Dart from a language with multithreading, it's reasonable to expect isolates to behave like threads, but that isn't the case. Isolates have their own global fields, and can only communicate with message passing, ensuring that mutable objects in an isolate are only ever accessible in a single isolate. Therefore, isolates are limited by their access to their own memory. For example, if you have an application with a global mutable variable called `configuration`, it is copied as a new global field in a spawned isolate. If you mutate that variable in the spawned isolate, it remains untouched in the main isolate. This is true even if you pass the `configuration` object as a message to the new isolate. This is how isolates are meant to function, and it's important to keep in mind when you consider using isolates.

### Web platforms and compute

[#](#web-platforms-and-compute)

Dart web platforms, including Flutter web, don't support isolates. If you're targeting the web with your Flutter app, you can use the `compute` method to ensure your code compiles. The [`compute()`](https://api.flutter.dev/flutter/foundation/compute.html) method runs the computation on the main thread on the web, but spawns a new thread on mobile devices. On mobile and desktop platforms `await compute(fun, message)` is equivalent to `await Isolate.run(() => fun(message))`.

For more information on concurrency on the web, check out the [concurrency documentation](https://dart.dev/language/concurrency) on dart.dev.

### No `rootBundle` access or `dart:ui` methods

[#](#no-rootbundle-access-or-dart-ui-methods)

All UI tasks and Flutter itself are coupled to the main isolate. Therefore, you can't access assets using `rootBundle` in spawned isolates, nor can you perform any widget or UI work in spawned isolates.

### Limited plugin messages from host platform to Flutter

[#](#limited-plugin-messages-from-host-platform-to-flutter)

With background isolate platform channels, you can use platform channels in isolates to send messages to the host platform (for example Android or iOS), and receive responses to those messages. However, you can't receive unsolicited messages from the host platform.

As an example, you can't set up a long-lived Firestore listener in a background isolate, because Firestore uses platform channels to push updates to Flutter, which are unsolicited. You can, however, query Firestore for a response in the background.

## More information

[#](#more-information)

For more information on isolates, check out the following resources:

* If you're using many isolates, consider the [IsolateNameServer](https://api.flutter.dev/flutter/dart-ui/IsolateNameServer-class.html) class in Flutter, or the pub package that clones the functionality for Dart applications not using Flutter.
* Dart's Isolates are an implementation of the [Actor model](https://en.wikipedia.org/wiki/Actor_model).
* [isolate\_agents](https://medium.com/@gaaclarke/isolate-agents-easy-isolates-for-flutter-6d75bf69a2e7) is a package that abstracts Ports and make it easier to create long-lived isolates.
* Read more about the `BackgroundIsolateBinaryMessenger` API [announcement](https://blog.flutter.dev/introducing-background-isolate-channels-7a299609cad8).

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-07-31. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/perf/isolates.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/perf/isolates\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/perf/isolates.md "Report an issue with this page").

----
url: https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.23.1
----

The 2.23.1 release of the Dart and Flutter DevTools includes the following changes among other general improvements. To learn more about DevTools, check out the [DevTools overview](https://docs.flutter.dev/tools/devtools).

## General updates

[#](#general-updates)

* Update DevTools to the new Material 3 design - [#5429](https://github.com/flutter/devtools/pull/5429)

* Use the default Flutter service worker - [#5331](https://github.com/flutter/devtools/pull/5331)

* Added the new verbose logging feature for helping us debug user issues - [#5404](https://github.com/flutter/devtools/pull/5404)

* Fix a bug where some asynchronous errors were not being reported - [#5456](https://github.com/flutter/devtools/pull/5456)

* Added support for viewing data after an app disconnects for screens that support offline viewing (currently only the Performance and CPU profiler pages) - [#5509](https://github.com/flutter/devtools/pull/5509)

* Include settings button in the footer of the embedded view - [#5528](https://github.com/flutter/devtools/pull/5528)

## Performance updates

[#](#performance-updates)

* Fix a performance regression in timeline event processing - [#5460](https://github.com/flutter/devtools/pull/5460)
* Persist a user's preference for whether the Flutter Frames chart should be shown by default - [#5339](https://github.com/flutter/devtools/pull/5339)
* Point users to [Impeller](https://docs.flutter.dev/perf/impeller) when shader compilation jank is detected on an iOS device - [#5455](https://github.com/flutter/devtools/pull/5455)
* Remove the CPU profiler from the legacy trace viewer - [#5539](https://github.com/flutter/devtools/pull/5539)

## CPU profiler updates

[#](#cpu-profiler-updates)

* Add a Method Table to the CPU profiler - [#5366](https://github.com/flutter/devtools/pull/5366)

* Improve the performance of data processing in the CPU profiler - [#5468](https://github.com/flutter/devtools/pull/5468), [#5533](https://github.com/flutter/devtools/pull/5533), [#5535](https://github.com/flutter/devtools/pull/5535)

* Polish and performance improvements for the CPU profile flame chart - [#5529](https://github.com/flutter/devtools/pull/5529)

* Add ability to inspect statistics for a CPU profile - [#5340](https://github.com/flutter/devtools/pull/5340)

* Fix a bug where Native stack frames were missing their name - [#5344](https://github.com/flutter/devtools/pull/5344)

* Fix an error in total and self time calculations for the bottom up tree - [#5348](https://github.com/flutter/devtools/pull/5348)

* Add support for zooming and navigating the flame chart with ,AOE keys (helpful for Dvorak users) - [#5545](https://github.com/flutter/devtools/pull/5545)

## Memory updates

[#](#memory-updates)

* Fix filtering bug in the "Trace Instances" view - [#5406](https://github.com/flutter/devtools/pull/5406)
* Enabled evaluation and browsing for instances in heap snapshot - [#5542](https://github.com/flutter/devtools/pull/5542)
* Fix heap snapshot failure - [#5520](https://github.com/flutter/devtools/pull/5520)
* Stop displaying external sizes in the allocation profile - [#5555](https://github.com/flutter/devtools/pull/5555)
* Expose totals for memory in heap snapshot - [#5593](https://github.com/flutter/devtools/pull/5593)

## Debugger updates

[#](#debugger-updates)

* Fix a bug where variable inspection for instances sometimes showed no children - [#5356](https://github.com/flutter/devtools/pull/5356)
* Hide "search in file" dialog if the "file search" dialog is open - [#5393](https://github.com/flutter/devtools/pull/5393)
* Fix file search bug where last letter disappeared when searching at end of file name - [#5397](https://github.com/flutter/devtools/pull/5397)
* Add search icon in file bar to make file search more discoverable - [#5351](https://github.com/flutter/devtools/issues/5351)
* Allow expression evaluation when pausing in JS for web apps - [#5427](https://github.com/flutter/devtools/pull/5427)
* Update syntax highlighting to [dart-lang/dart-syntax-highlight v1.2.0](https://github.com/dart-lang/dart-syntax-highlight/blob/master/CHANGELOG.md#120-2023-01-30) - [#5477](https://github.com/flutter/devtools/pull/5477)
* Debugger panel respects "dense mode" - [#5517](https://github.com/flutter/devtools/pull/5517)

## Network profiler updates

[#](#network-profiler-updates)

* Fix a bug viewing JSON responses with null values - [#5424](https://github.com/flutter/devtools/pull/5424)
* Fix a bug where JSON requests were shown in plain text, instead of the formatted JSON viewer - [#5463](https://github.com/flutter/devtools/pull/5463)
* Fix a UI issue where the copy button on the response or request tab would let you copy while still loading the data - [#5476](https://github.com/flutter/devtools/pull/5476)

## Full commit history

[#](#full-commit-history)

To find a complete list of changes since the previous release, check out [the diff on GitHub](https://github.com/flutter/devtools/compare/v2.22.2...v2.23.1).

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/release-notes/release-notes-2.23.1.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.23.1\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/release-notes/release-notes-2.23.1.md "Report an issue with this page").

----
url: https://docs.flutter.dev/cookbook/navigation/returning-data
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Return data from a screen](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

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 that it can act on that information.

You can do this with the [`Navigator.pop()`](https://api.flutter.dev/flutter/widgets/Navigator/pop.html) method using the following steps:

1. Define the home screen
2. Add a button that launches the selection screen
3. Show the selection screen with two buttons
4. When a button is tapped, close the selection screen
5. Show a snackbar on the home screen with the selection

## 1. Define the home screen

[#](#1-define-the-home-screen)

The home screen displays a button. When tapped, it launches the selection screen.

dart

```
class HomeScreen extends StatelessWidget {
  const HomeScreen({super.key});
​
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Returning Data Demo')),
      // Create the SelectionButton widget in the next step.
      body: const Center(child: SelectionButton()),
    );
  }
}
```

## 2. Add a button that launches the selection screen

[#](#2-add-a-button-that-launches-the-selection-screen)

Now, create the SelectionButton, which does the following:

* Launches the SelectionScreen when it's tapped.
* Waits for the SelectionScreen to return a result.

dart

```
class SelectionButton extends StatefulWidget {
  const SelectionButton({super.key});
​
  @override
  State<SelectionButton> createState() => _SelectionButtonState();
}
​
class _SelectionButtonState extends State<SelectionButton> {
  @override
  Widget build(BuildContext context) {
    return ElevatedButton(
      onPressed: () {
        _navigateAndDisplaySelection(context);
      },
      child: const Text('Pick an option, any option!'),
    );
  }
​
  Future<void> _navigateAndDisplaySelection(BuildContext context) async {
    // Navigator.push returns a Future that completes after calling
    // Navigator.pop on the Selection Screen.
    final result = await Navigator.push(
      context,
      // Create the SelectionScreen in the next step.
      MaterialPageRoute<String>(builder: (context) => const SelectionScreen()),
    );
  }
}
```

## 3. Show the selection screen with two buttons

[#](#3-show-the-selection-screen-with-two-buttons)

Now, build a selection screen that contains two buttons. When a user taps a button, that app closes the selection screen and lets the home screen know which button was tapped.

This step defines the UI. The next step adds code to return data.

dart

```
class SelectionScreen extends StatelessWidget {
  const SelectionScreen({super.key});
​
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Pick an option')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Padding(
              padding: const EdgeInsets.all(8),
              child: ElevatedButton(
                onPressed: () {
                  // Pop here with "Yep"...
                },
                child: const Text('Yep!'),
              ),
            ),
            Padding(
              padding: const EdgeInsets.all(8),
              child: ElevatedButton(
                onPressed: () {
                  // Pop here with "Nope"...
                },
                child: const Text('Nope.'),
              ),
            ),
          ],
        ),
      ),
    );
  }
}
```

## 4. When a button is tapped, close the selection screen

[#](#4-when-a-button-is-tapped-close-the-selection-screen)

Now, update the `onPressed()` callback for both of the buttons. To return data to the first screen, use the [`Navigator.pop()`](https://api.flutter.dev/flutter/widgets/Navigator/pop.html) method, which accepts an optional second argument called `result`. Any result is returned to the `Future` in the SelectionButton.

### Yep button

[#](#yep-button)

dart

```
ElevatedButton(
  onPressed: () {
    // Close the screen and return "Yep!" as the result.
    Navigator.pop(context, 'Yep!');
  },
  child: const Text('Yep!'),
)
```

### Nope button

[#](#nope-button)

dart

```
ElevatedButton(
  onPressed: () {
    // Close the screen and return "Nope." as the result.
    Navigator.pop(context, 'Nope.');
  },
  child: const Text('Nope.'),
)
```

## 5. Show a snackbar on the home screen with the selection

[#](#5-show-a-snackbar-on-the-home-screen-with-the-selection)

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, show a snackbar displaying the result by using the `_navigateAndDisplaySelection()` method in `SelectionButton`:

dart

```
// A method that launches the SelectionScreen and awaits the result from
// Navigator.pop.
Future<void> _navigateAndDisplaySelection(BuildContext context) async {
  // Navigator.push returns a Future that completes after calling
  // Navigator.pop on the Selection Screen.
  final result = await Navigator.push(
    context,
    MaterialPageRoute<String>(builder: (context) => const SelectionScreen()),
  );
​
  // When a BuildContext is used from a StatefulWidget, the mounted property
  // must be checked after an asynchronous gap.
  if (!context.mounted) return;
​
  // After the Selection Screen returns a result, hide any previous snackbars
  // and show the new result.
  ScaffoldMessenger.of(context)
    ..removeCurrentSnackBar()
    ..showSnackBar(SnackBar(content: Text('$result')));
}
```

## Interactive example

[#](#interactive-example)

```
import 'package:flutter/material.dart';

void main() {
  runApp(const MaterialApp(title: 'Returning Data', home: HomeScreen()));
}

class HomeScreen extends StatelessWidget {
  const HomeScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Returning Data Demo')),
      body: const Center(child: SelectionButton()),
    );
  }
}

class SelectionButton extends StatefulWidget {
  const SelectionButton({super.key});

  @override
  State<SelectionButton> createState() => _SelectionButtonState();
}

class _SelectionButtonState extends State<SelectionButton> {
  @override
  Widget build(BuildContext context) {
    return ElevatedButton(
      onPressed: () {
        _navigateAndDisplaySelection(context);
      },
      child: const Text('Pick an option, any option!'),
    );
  }

  // A method that launches the SelectionScreen and awaits the result from
  // Navigator.pop.
  Future<void> _navigateAndDisplaySelection(BuildContext context) async {
    // Navigator.push returns a Future that completes after calling
    // Navigator.pop on the Selection Screen.
    final result = await Navigator.push(
      context,
      MaterialPageRoute<String>(builder: (context) => const SelectionScreen()),
    );

    // When a BuildContext is used from a StatefulWidget, the mounted property
    // must be checked after an asynchronous gap.
    if (!context.mounted) return;

    // After the Selection Screen returns a result, hide any previous snackbars
    // and show the new result.
    ScaffoldMessenger.of(context)
      ..removeCurrentSnackBar()
      ..showSnackBar(SnackBar(content: Text('$result')));
  }
}

class SelectionScreen extends StatelessWidget {
  const SelectionScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Pick an option')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Padding(
              padding: const EdgeInsets.all(8),
              child: ElevatedButton(
                onPressed: () {
                  // Close the screen and return "Yep!" as the result.
                  Navigator.pop(context, 'Yep!');
                },
                child: const Text('Yep!'),
              ),
            ),
            Padding(
              padding: const EdgeInsets.all(8),
              child: ElevatedButton(
                onPressed: () {
                  // Close the screen and return "Nope." as the result.
                  Navigator.pop(context, 'Nope.');
                },
                child: const Text('Nope.'),
              ),
            ),
          ],
        ),
      ),
    );
  }
}
```

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-06-25. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/navigation/returning-data.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/cookbook/navigation/returning-data\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/navigation/returning-data.md "Report an issue with this page").

----
url: https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.22.2
----

The 2.22.2 release of the Dart and Flutter DevTools includes the following changes among other general improvements. To learn more about DevTools, check out the [DevTools overview](https://docs.flutter.dev/tools/devtools).

## General updates

[#](#general-updates)

* Prevent crashes if there is no main isolate - [#5232](https://github.com/flutter/devtools/pull/5232)

## CPU profiler updates

[#](#cpu-profiler-updates)

* Display stack frame URI inline with method name to ensure the URI is always visible in deeply nested trees - [#5181](https://github.com/flutter/devtools/pull/5181)

* Add the ability to filter by method name or source URI - [#5204](https://github.com/flutter/devtools/pull/5204)

## Memory updates

[#](#memory-updates)

* Change filter default to show only project and 3rd party dependencies - [#5201](https://github.com/flutter/devtools/pull/5201).

* Support expression evaluation in console for running application - [#5248](https://github.com/flutter/devtools/pull/5248).

* Add column `Persisted` for memory diffing - [#5290](https://github.com/flutter/devtools/pull/5290)

## Debugger updates

[#](#debugger-updates)

* Add support for browser navigation history when navigating using the File Explorer - [#4906](https://github.com/flutter/devtools/pull/4906)

* Designate positional fields for `Record` types with the getter syntax beginning at `$1` - [#5272](https://github.com/flutter/devtools/pull/5272)

* Fix variable inspection for `Map` and `List` instances - [#5320](https://github.com/flutter/devtools/pull/5320)

* Fix variable inspection for `Set` instances - [#5323](https://github.com/flutter/devtools/pull/5323)

## Network profiler updates

[#](#network-profiler-updates)

* Improve reliability and performance of the Network tab - [#5056](https://github.com/flutter/devtools/pull/5056)

## Full commit history

[#](#full-commit-history)

To find a complete list of changes since the previous release, check out [the diff on GitHub](https://github.com/flutter/devtools/compare/v2.21.1...v2.22.2).

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/release-notes/release-notes-2.22.2.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.22.2\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/release-notes/release-notes-2.22.2.md "Report an issue with this page").

----
url: https://docs.flutter.dev/cookbook/lists/long-lists.md
----

\# Work with long lists > Use ListView\.builder to implement a long or infinite list.

<!--?code-excerpt path-base="cookbook/lists/long_lists/"?-->

The standard \[\`ListView\`]\[] 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\`]\[] constructor. In contrast to the default \`ListView\` constructor, which requires creating all items at once, the \`ListView\.builder()\` constructor creates items as they're scrolled onto the screen. ## 1. Create a data source 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, generate a list of 10,000 Strings using the \[\`List.generate\`]\[] constructor.

<!--?code-excerpt "lib/main.dart (Items)" replace="/^items: //g"?-->

\`\`\`dart List.generate(10000, (i) => 'Item $i'), \`\`\` ## 2. Convert the data source into widgets 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.

<!--?code-excerpt "lib/main.dart (ListView)" replace="/^body: //g;/^\),$/)/g"?-->

\`\`\`dart ListView\.builder( itemCount: items.length, prototypeItem: ListTile(title: Text(items.first)), itemBuilder: (context, index) { return ListTile(title: Text(items\[index])); }, ) \`\`\` ## Interactive example

<!--?code-excerpt "lib/main.dart"?-->

\`\`\`dartpad title="Flutter create long list hands-on example in DartPad" run="true" import 'package:flutter/material.dart'; void main() { runApp( MyApp( items: List.generate(10000, (i) => 'Item $i'), ), ); } class MyApp extends StatelessWidget { final List items; const MyApp({super.key, required this.items}); @override Widget build(BuildContext context) { const title = 'Long List'; return MaterialApp( title: title, home: Scaffold( appBar: AppBar(title: const Text(title)), body: ListView\.builder( itemCount: items.length, prototypeItem: ListTile(title: Text(items.first)), itemBuilder: (context, index) { return ListTile(title: Text(items\[index])); }, ), ), ); } } \`\`\` ## Children's extent To specify each item's extent, you can use either \[\`prototypeItem\`]\[], \[\`itemExtent\`]\[], or \[\`itemExtentBuilder\`]\[]. Specifying either is more efficient than letting the children determine their own extent because the scrolling machinery can make use of the foreknowledge of the children's extent to save work, for example when the scroll position changes drastically. Use \[\`prototypeItem\`]\[] or \[\`itemExtent\`]\[] if your list has items of fixed size. Use \[\`itemExtentBuilder\`]\[] if your list has items of different sizes. \[\`List.generate\`]: https\://api.flutter.dev/flutter/dart-core/List/List.generate.html \[\`ListView\`]: https\://api.flutter.dev/flutter/widgets/ListView-class.html \[\`ListView\.builder\`]: https\://api.flutter.dev/flutter/widgets/ListView/ListView\.builder.html \[\`prototypeItem\`]: https\://api.flutter.dev/flutter/widgets/ListView/prototypeItem.html \[\`itemExtent\`]: https\://api.flutter.dev/flutter/widgets/ListView/itemExtent.html \[\`itemExtentBuilder\`]: https\://api.flutter.dev/flutter/widgets/ListView/itemExtentBuilder.html

----
url: https://docs.flutter.dev/ui/navigation.md
----

\# Navigation and routing > Overview of Flutter's navigation and routing features Flutter provides a complete system for navigating between screens and handling deep links. Small applications without complex deep linking can use \[\`Navigator\`]\[], while apps with specific deep linking and navigation requirements should also use the \[\`Router\`]\[] to correctly handle deep links on Android and iOS, and to stay in sync with the address bar when the app is running on the web. To configure your Android or iOS application to handle deep links, see \[Deep linking]\[]. ## Using the Navigator The \`Navigator\` widget displays screens as a stack using the correct transition animations for the target platform. To navigate to a new screen, access the \`Navigator\` through the route's \`BuildContext\` and call imperative methods such as \`push()\` \`or pop()\`:

<!--?code-excerpt "ui/navigation/lib/navigator_basic.dart (push-route)"?-->

\`\`\`dart child: const Text('Open second screen'), onPressed: () { Navigator.of(context).push( MaterialPageRoute( builder: (context) => const SecondScreen(), ), ); }, \`\`\` Because \`Navigator\` keeps a stack of \`Route\` objects (representing the history stack), The \`push()\` method also takes a \`Route\` object. The \`MaterialPageRoute\` object is a subclass of \`Route\` that specifies the transition animations for Material Design. For more examples of how to use the \`Navigator\`, follow the \[navigation recipes]\[] from the Flutter Cookbook or visit the \[Navigator API documentation]\[\`Navigator\`]. ## Using named routes :::note We don't recommend using named routes for most applications. Instead, use \[go\_router]\[] (or another routing package) or use \`Navigator\` with \[\`MaterialPageRoute\`]\[]. For more information, see the \[Limitations]\(#limitations) section. ::: Applications with simple navigation and deep linking requirements can use the \`Navigator\` for navigation and the \[\`MaterialApp.routes\`]\[] parameter for deep links:

<!--?code-excerpt "ui/navigation/lib/navigator_named_routes.dart (push-route)"?-->

\`\`\`dart child: const Text('Open second screen'), onPressed: () { Navigator.pushNamed(context, '/second'); }, \`\`\` \`/second\` represents a \_named route\_ that was declared in the \`MaterialApp.routes\` list. For a complete example, follow the \[Navigate with named routes]\[] recipe from the Flutter Cookbook. ### Limitations {: #limitations} Although named routes can handle deep links, the behavior is always the same and can't be customized. When a new deep link is received by the platform, Flutter pushes a new \`Route\` onto the Navigator regardless of where the user currently is. Flutter also doesn't support the browser forward button for applications using named routes. For these reasons, we don't recommend using named routes in most applications. Instead, use a routing package like \[go\_router]\[] or use \`Navigator\` with \[\`MaterialPageRoute\`]\[]. ## Using the Router Flutter applications with advanced navigation and routing requirements (such as a web app that uses direct links to each screen, or an app with multiple \`Navigator\` widgets) should use a routing package such as \[go\_router]\[] that can parse the route path and configure the \`Navigator\` whenever the app receives a new deep link. To use the Router, switch to the \`router\` constructor on \`MaterialApp\` or \`CupertinoApp\` and provide it with a \`Router\` configuration. Routing packages, such as \[go\_router]\[], typically provide route configuration and routes can be used as follows:

<!--?code-excerpt "ui/navigation/lib/navigator_router.dart (push-route)"?-->

\`\`\`dart child: const Text('Open second screen'), onPressed: () => context.go('/second'), \`\`\` Because packages like go\_router are \_declarative\_, they will always display the same screen(s) when a deep link is received. :::note Note for advanced developers If you prefer not to use a routing package and would like full control over navigation and routing in your app, override \`RouteInformationParser\` and \`RouterDelegate\`. When the state in your app changes, you can precisely control the stack of screens by providing a list of \`Page\` objects using the \`Navigator.pages\` parameter. For more details, see the \`Router\` API documentation. ::: ## Using Router and Navigator together The \`Router\` and \`Navigator\` are designed to work together. You can navigate using the \`Router\` API through a declarative routing package, such as \`go\_router\`, or by calling imperative methods such as \`push()\` and \`pop()\` on the \`Navigator\`. When you navigate using the \`Router\` or a declarative routing package, each route on the Navigator is \_page-backed\_, meaning it was created from a \[\`Page\`]\[] using the \[\`pages\`]\[] argument on the \`Navigator\` constructor. Conversely, any \`Route\` created by calling \`Navigator.push\` or \`showDialog\` will add a \_pageless\_ route to the Navigator. If you are using a routing package, Routes that are \_page-backed\_ are always deep-linkable, whereas \_pageless\_ routes are not. When a \_page-backed\_ \`Route\` is removed from the \`Navigator\`, all of the \_pageless\_ routes after it are also removed. For example, if a deep link navigates by removing a \_page-backed\_ route from the Navigator, all \_pageless\_ routes after (up until the next \_page-backed\_ route) are removed too. :::note You can't prevent navigation from page-backed screens using \`PopScope\` or the deprecated \`WillPopScope\`. Instead, you should consult your routing package's API documentation. For guidelines on migrating to \`PopScope\`, check out the \[Android predictive back migration guide]\[]. ::: \[Android predictive back migration guide]: /release/breaking-changes/android-predictive-back ## Web support Apps using the \`Router\` class integrate with the browser History API to provide a consistent experience when using the browser's back and forward buttons. Whenever you navigate using the \`Router\`, a History API entry is added to the browser's history stack. Pressing the \*\*back\*\* button uses \_\[reverse chronological navigation]\[]\_, meaning that the user is taken to the previously visited location that was shown using the \`Router\`. This means that if the user pops a page from the \`Navigator\` and then presses the browser \*\*back\*\* button the previous page is pushed back onto the stack. ## More information For more information on navigation and routing, check out the following resources: \* The Flutter cookbook includes multiple \[navigation recipes]\[] that show how to use the \`Navigator\`. \* The \[\`Navigator\`]\[] and \[\`Router\`]\[] API documentation contain details on how to set up declarative navigation without a routing package. \* \[Understanding navigation]\[], a page from the Material Design documentation, outlines concepts for designing the navigation in your app, including explanations for forward, upward, and chronological navigation. \* \[Learning Flutter's new navigation and routing system]\[], a Flutter blog post, describes how to use the \`Router\` widget directly, without a routing package. \* The \[Router design document]\[] contains the motivation and design of the \`Router\` API. \[\`Navigator\`]: https\://api.flutter.dev/flutter/widgets/Navigator-class.html \[\`Router\`]: https\://api.flutter.dev/flutter/widgets/Router-class.html \[Deep linking]: /ui/navigation/deep-linking \[navigation recipes]: /cookbook/navigation \[\`MaterialApp.routes\`]: https\://pub.dev/documentation/material\_ui/latest/material\_ui/MaterialApp/routes.html \[Navigate with named routes]: /cookbook/navigation/named-routes \[go\_router]: https\://pub.dev/packages/go\_router \[\`Page\`]: https\://api.flutter.dev/flutter/widgets/Page-class.html \[\`pages\`]: https\://api.flutter.dev/flutter/widgets/Navigator/pages.html \[reverse chronological navigation]: https\://material.io/design/navigation/understanding-navigation.html#reverse-navigation \[Understanding navigation]: https\://material.io/design/navigation/understanding-navigation.html \[Learning Flutter's new navigation and routing system]: https\://flutter.dev/blog/learning-flutters-new-navigation-and-routing-system \[Router design document]: https\://flutter.dev/go/navigator-with-router \[\`MaterialPageRoute\`]: https\://pub.dev/documentation/material\_ui/latest/material\_ui/MaterialPageRoute-class.html

----
url: https://docs.flutter.dev/resources/faq
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top FAQ](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

## Introduction

[#](#introduction)

This page collects common questions about Flutter. You can also check out the following specialized FAQs:

* [Web FAQ](/platform-integration/web/faq)
* [Performance FAQ](/perf/faq)

### What is Flutter?

[#](#what-is-flutter)

Flutter is Google's open source framework for crafting beautiful, natively compiled applications for mobile (iOS, Android), web, desktop (macOS, Windows, Linux), and embedded devices from a single codebase. Flutter works with existing code, is used by developers and organizations worldwide, and is free and open source.

### Who is Flutter for?

[#](#who-is-flutter-for)

For developers, Flutter accelerates app development and reduces the cost and complexity of building across platforms.

For designers, Flutter provides a flexible canvas for high-end user experiences. It turns concepts into production code without the compromises imposed by traditional cross-platform frameworks.

For engineering managers and businesses, Flutter enables the unification of app developers into a single team, building branded apps for multiple platforms from a single codebase. Flutter speeds feature development and synchronizes release schedules across your customer base.

### How much development experience do I need to use Flutter?

[#](#how-much-development-experience-do-i-need-to-use-flutter)

Flutter is approachable to programmers familiar with object-oriented and imperative or declarative programming concepts. Experience with languages such as Java, Kotlin, Swift, TypeScript, or C# helps you learn Dart and Flutter quickly.

### What kinds of apps can I build with Flutter?

[#](#what-kinds-of-apps-can-i-build-with-flutter)

Flutter supports building mobile apps on Android and iOS, web applications, desktop apps on macOS, Windows, and Linux, and embedded device interfaces.

Apps that require custom, branded designs are well suited for Flutter. You can also create pixel-perfect experiences that match Android's Material Design and iOS's Cupertino design languages.

Flutter's [package ecosystem](https://pub.dev/flutter) on pub.dev supports a wide variety of hardware (such as camera, GPS, Bluetooth, network, and local storage) and cloud services (such as authentication, notifications, payments, and storage).

### Who makes Flutter?

[#](#who-makes-flutter)

Flutter is an open source project managed by Google, with contributions from a global community of developers, companies, and individuals.

### Who uses Flutter?

[#](#who-uses-flutter)

Developers and organizations worldwide use Flutter to build natively compiled applications. To explore apps built with Flutter, visit the [showcase](https://flutter.dev/showcase).

### What makes Flutter unique?

[#](#what-makes-flutter-unique)

Flutter differs from most cross-platform alternatives because it doesn't rely on web browser views or system-provided platform widgets. Instead, Flutter renders its UI using its own high-performance rendering engine ([Impeller](/perf/impeller)).

In addition, Flutter implements most of its framework (including compositing, gestures, animation, and widgets) in [Dart](https://dart.dev/). This architecture gives you deep control over the UI, high predictability, and the ability to inspect or modify the framework at any level.

### Should I build my next production app with Flutter?

[#](#should-i-build-my-next-production-app-with-flutter)

Yes. Thousands of organizations, from early-stage startups to large enterprises, deploy production Flutter apps to hundreds of millions of users. Explore real-world examples in the [showcase](https://flutter.dev/showcase).

Flutter releases updates on a regular cadence, bringing performance improvements, new features, and platform updates to the stable channel.

## What does Flutter provide?

[#](#what-does-flutter-provide)

### What is inside the Flutter SDK?

[#](#what-is-inside-the-flutter-sdk)

The Flutter SDK includes:

* High-performance 2D rendering engine ([Impeller](/perf/impeller))
* Modern, reactive framework written in Dart
* Rich set of widgets implementing Material Design 3 and Cupertino (iOS-style)
* APIs for unit, widget, and integration tests
* Interop and plugin APIs to connect to system features and native SDKs
* Headless test runner for running tests on Windows, Linux, and macOS
* [Flutter DevTools](/tools/devtools) for profiling, debugging, and inspecting your app
* Command-line tools for creating, building, testing, and compiling apps

### Does Flutter work with any editors or IDEs?

[#](#does-flutter-work-with-any-editors-or-ides)

Flutter provides plugins and extensions for [VS Code](https://code.visualstudio.com/), [Antigravity](/ai/get-started) (an AI-assisted IDE), [Android Studio](https://developer.android.com/studio), and [IntelliJ IDEA](https://www.jetbrains.com/idea/). See [editor configuration](/tools/editors) for setup details, and [VS Code](https://code.visualstudio.com/) and [Android Studio/IntelliJ](/tools/android-studio) for usage guidance.

You can also run the `flutter` command from a terminal along with any editor that supports [editing Dart](https://dart.dev/tools).

### Does Flutter come with a framework?

[#](#does-flutter-come-with-a-framework)

Yes. Flutter includes a modern, reactive framework. The framework is layered and customizable, allowing you to use high-level widgets or interact directly with lower layers of the rendering pipeline.

### Does Flutter come with widgets?

[#](#does-flutter-come-with-widgets)

Yes. Flutter ships with a collection of high-quality [Material Design and Cupertino (iOS-style) widgets](/ui/widgets), layouts, and themes. You can use these widgets as provided, customize them, or create custom widgets.

### Does Flutter support Material Design?

[#](#does-flutter-support-material-design)

Yes. Flutter provides full support for [Material Design 3](/ui/widgets/material), Google's design system.

### Does Flutter come with a testing framework?

[#](#does-flutter-come-with-a-testing-framework)

Yes. Flutter provides built-in support for unit, widget, and integration testing. To learn more, see [Testing overview](/testing/overview).

### Does Flutter come with debugging tools?

[#](#does-flutter-come-with-debugging-tools)

Yes. Flutter includes [Flutter DevTools](/tools/devtools). For more information, see [Debugging Flutter apps](/testing/debugging) and the [Flutter DevTools](/tools/devtools) documentation.

### Does Flutter come with a dependency injection framework?

[#](#does-flutter-come-with-a-dependency-injection-framework)

Flutter doesn't require an opinionated dependency injection framework. The Flutter ecosystem offers several packages for dependency injection and service location, such as [Riverpod](https://pub.dev/packages/riverpod), [get\_it](https://pub.dev/packages/get_it), [Provider](https://pub.dev/packages/provider), and [injectable](https://pub.dev/packages/injectable).

## Technology

[#](#technology)

### What technology is Flutter built with?

[#](#what-technology-is-flutter-built-with)

Flutter is built with C, C++, and Dart. It uses [Impeller](/perf/impeller) as its default rendering engine, taking advantage of modern graphics APIs such as Metal on iOS and macOS, and Vulkan on Android. For a detailed description of Flutter's layered architecture, read the [architectural overview](/resources/architectural-overview).

### How does Flutter run my code on Android?

[#](#run-android)

The engine's C and C++ code is compiled using Android's NDK. The Dart code (both the framework and your application code) is ahead-of-time (AOT) compiled into native machine code. These libraries are packaged into a runner Android project and built into an APK or Android App Bundle (AAB). When launched, the app loads the Flutter engine, which executes the compiled code and renders the UI.

During debug mode, Flutter runs inside the Dart VM to enable stateful [hot reload](/tools/hot-reload), allowing you to make changes to your running code without recompiling. A "debug" banner in the top right corner indicates that the app is running in debug mode.

### How does Flutter run my code on iOS?

[#](#run-ios)

The engine's C and C++ code is compiled with LLVM. The Dart code (both the framework and your application code) is ahead-of-time (AOT) compiled into native ARM machine code. The compiled code is packaged into a runner iOS project and built into an `.ipa` bundle. When launched, the app loads the Flutter engine, which executes the compiled code and renders the UI using Impeller.

During debug mode, Flutter runs inside the Dart VM to enable stateful [hot reload](/tools/hot-reload). A "debug" banner in the top right corner indicates that the app is running in debug mode.

### Does Flutter use my operating system's built-in platform widgets?

[#](#does-flutter-use-my-operating-systems-built-in-platform-widgets)

No. Instead, Flutter renders widgets directly using its own engine, providing implementations of Material Design and Cupertino (iOS-style) widgets. You can explore the available widgets in the [widget catalog](/ui/widgets).

By rendering pixels directly, Flutter avoids the performance constraints and behavioral differences of platform widget abstractions. This approach delivers consistent performance and appearance across platforms.

For cases where you need a platform-native component (such as Google Maps or a web view), Flutter supports embedding [platform views](/platform-integration/platform-views) directly into the widget tree.

### What happens when my mobile OS updates and introduces new widgets?

[#](#what-happens-when-my-mobile-os-updates-and-introduces-new-widgets)

The Flutter team and community track new design guidelines and platform widgets from Apple and Google, updating Flutter's widget libraries accordingly. Because Flutter widgets are written in Dart, you can also build or adapt widgets immediately without waiting for an OS or SDK update.

### What happens when my mobile OS updates and introduces new platform capabilities?

[#](#what-happens-when-my-mobile-os-updates-and-introduces-new-platform-capabilities)

Flutter's plugin architecture, [platform channels](/platform-integration/platform-channels), and native interop tools (such as Dart FFI, `ffigen`, and `jnigen`) allow you to access new OS APIs immediately. You don't need to wait for a Flutter SDK release to integrate new platform capabilities.

### Does Flutter support code push?

[#](#does-flutter-support-code-push)

Flutter doesn't provide built-in code push functionality. Third-party solutions, such as [Shorebird](https://shorebird.dev/), provide code push support for Flutter applications.

### What operating systems can I use to build a Flutter app?

[#](#what-operating-systems-can-i-use-to-build-a-flutter-app)

Flutter supports development on macOS, Windows, Linux, and ChromeOS. For details on system requirements, see the [install guide](/install).

### What language is Flutter written in?

[#](#what-language-is-flutter-written-in)

The Flutter framework is written in [Dart](https://dart.dev/). The underlying engine and graphics pipeline are implemented in C and C++.

### Why did Flutter choose to use Dart?

[#](#why-did-flutter-choose-to-use-dart)

During initial development, the Flutter team evaluated multiple languages and runtimes before selecting Dart for the framework and widgets. Dart scored highest across several key evaluation criteria:

* *Developer productivity*

  Dart supports both JIT (Just-in-Time) compilation for fast development with stateful hot reload, and AOT (Ahead-of-Time) compilation for efficient native machine code in production. Because the framework is written in Dart, developers can inspect, debug, and modify the entire stack.

* *Object orientation and UI composition*

  Dart's object-oriented structure matches the requirements of building composable user interfaces without requiring a separate markup language.

* *Predictable, high performance*

  Dart compiles directly to native machine code without an interpreter or bridge, delivering consistent frame rates without unpredictable runtime pauses.

* *Fast memory allocation*

  Flutter's reactive framework creates many short-lived objects. Dart's generational garbage collector is optimized for fast allocation and cleanup of ephemeral objects.

### Can Flutter run any Dart code?

[#](#can-flutter-run-any-dart-code)

Flutter can run any Dart code that doesn't depend on libraries unavailable on the target platform. For example, mobile and desktop apps can't import web-specific libraries, and web apps can't use `dart:io`.

### How does Flutter compile for the web?

[#](#how-does-flutter-compile-for-the-web)

Flutter compiles Dart for the web to both JavaScript and WebAssembly (Wasm). WebAssembly compilation (WasmGC) delivers high-performance rendering comparable to native desktop and mobile apps. For details, see the [Flutter web documentation](/platform-integration/web).

### How big is the Flutter engine?

[#](#how-big-is-the-flutter-engine)

A minimal release Flutter application includes the Flutter engine, the framework, and your application code. The engine adds a small baseline footprint (a few megabytes compressed), which varies by target platform and architecture.

App size is largely determined by bundled assets, fonts, and third-party packages. To analyze and minimize the size of your application, see [Measuring your app's size](/perf/app-size).

### How does Flutter define a pixel?

[#](#how-does-flutter-define-a-pixel)

Flutter uses logical pixels, which correspond roughly to physical pixels on a standard-density display. Flutter automatically maps logical pixels to physical device pixels based on the view's [`devicePixelRatio`](https://api.flutter.dev/flutter/dart-ui/FlutterView/devicePixelRatio.html).

## Capabilities

[#](#capabilities)

### What kind of app performance can I expect?

[#](#what-kind-of-app-performance-can-i-expect)

Flutter is designed to deliver smooth 60fps and 120fps animations. Flutter apps compile directly to native machine code or WebAssembly, eliminating interpreter overhead. To learn more about performance optimization, see the [Performance FAQ](/perf/faq).

### What kind of developer cycles can I expect? How long between edit and refresh?

[#](#hot-reload)

Flutter implements a stateful *hot reload* developer cycle. You can expect sub-second reload times on physical devices, emulators, and simulators.

Hot reload preserves your application state, allowing you to iterate rapidly without navigating back to the current screen after every code change.

### How is hot reload different from hot restart?

[#](#how-is-hot-reload-different-from-hot-restart)

Hot reload injects updated source code files into the running Dart VM without clearing the application state. Hot restart resets the application state and re-executes `main()`, which takes slightly longer than hot reload but remains faster than a full recompile.

For more information, see [Hot reload](/tools/hot-reload).

### Where can I deploy my Flutter app?

[#](#where-can-i-deploy-my-flutter-app)

You can compile and deploy Flutter apps to iOS, Android, [web](/platform-integration/web), [desktop](/platform-integration/desktop) (macOS, Windows, Linux), and embedded devices.

### What devices and OS versions does Flutter run on?

[#](#what-devices-and-os-versions-does-flutter-run-on)

Flutter supports a broad range of operating systems and hardware platforms:

* Mobile: Android (ARM32, ARM64, x86\_64) and iOS (ARM64).
* Desktop: macOS (x86\_64, ARM64), Windows (x86\_64, ARM64), and Linux (x86\_64, ARM64).
* Web: Modern browsers supporting WebAssembly or JavaScript.

For the complete list of supported OS versions, see [Supported platforms](/reference/supported-platforms).

### Does Flutter run on the web?

[#](#does-flutter-run-on-the-web)

Yes. Flutter web support is available on the stable channel and supports compilation to both WebAssembly (Wasm) and JavaScript. For more details, see [Web support for Flutter](/platform-integration/web).

### Can I use Flutter to build desktop apps?

[#](#can-i-use-flutter-to-build-desktop-apps)

Yes. Desktop support is available on the stable channel for macOS, Windows, and Linux. For details, see [Desktop support for Flutter](/platform-integration/desktop).

### Can I use Flutter inside of my existing native app?

[#](#can-i-use-flutter-inside-of-my-existing-native-app)

Yes. You can integrate Flutter into existing Android and iOS applications using [Add-to-App](/add-to-app).

### Can I access platform services and APIs like sensors and local storage?

[#](#can-i-access-platform-services-and-apis-like-sensors-and-local-storage)

Yes. Flutter provides access to platform-specific APIs and hardware through plugins available on [pub.dev](https://pub.dev/flutter/). You can also create custom integrations using [platform channels](/platform-integration/platform-channels) or native interop (Dart FFI).

### Can I extend and customize the bundled widgets?

[#](#can-i-extend-and-customize-the-bundled-widgets)

Yes. Flutter's widget system is built around composition. Instead of subclassing complex widgets, you compose smaller, single-purpose widgets to create custom designs. This architecture provides control over layout, painting, and gesture handling.

### Why would I want to share layout code across iOS and Android?

[#](#why-would-i-want-to-share-layout-code-across-ios-and-android)

Sharing layout and UI code reduces engineering effort, keeps feature releases synchronized, and ensures a consistent brand experience across platforms. When you want platform-specific behavior or styling, you can adapt the UI using platform checks or responsive layouts.

### Can I interop with my mobile platform's default programming language?

[#](#can-i-interop-with-my-mobile-platforms-default-programming-language)

Yes. Flutter supports calling native code on all supported platforms:

* On Android: Kotlin and Java via [platform channels](/platform-integration/platform-channels) and [JNI interop](https://dart.dev/interop/java-interop) (`jnigen`).
* On iOS and macOS: Swift and Objective-C via [platform channels](/platform-integration/platform-channels) and [Objective-C/Swift interop](https://dart.dev/interop/objective-c-interop) (`ffigen`).
* On desktop and mobile: C and C++ via Dart FFI ([`dart:ffi`](https://dart.dev/interop/c-interop)).

### Does Flutter come with a reflection / mirrors system?

[#](#does-flutter-come-with-a-reflection-mirrors-system)

No. Dart disables `dart:mirrors` in Flutter to enable aggressive ahead-of-time (AOT) compilation and dead-code elimination (tree shaking). This keeps binary sizes small and startup times fast. For metadata inspection and serialization, Flutter applications use compile-time code generation.

### Are internationalization and localization supported?

[#](#are-internationalization-and-localization-supported)

Yes. Flutter provides built-in internationalization (i18n) and localization (l10n) support, including pluralization, bidirectional text, and date formatting. To learn more, see [Internationalizing Flutter apps](/ui/internationalization).

### What accessibility is supported?

[#](#what-accessibility-is-supported)

Flutter supports standard accessibility features (a11y), including screen readers (TalkBack, VoiceOver), large text scaling, high-contrast themes, and hardware switch access. For details, see [Accessibility](/ui/accessibility).

### How do I write parallel and/or concurrent apps for Flutter?

[#](#how-do-i-write-parallel-andor-concurrent-apps-for-flutter)

Dart uses an event loop for asynchronous operations (`Future` and `Stream`). For CPU-intensive tasks, Flutter supports isolates—isolated execution threads with independent memory heaps that communicate through message passing. You can use [`Isolate.run()`](https://api.flutter.dev/flutter/dart-isolate/Isolate/run.html) or the [`compute()`](https://api.flutter.dev/flutter/foundation/compute.html) function to run heavy computations in the background.

### Can I run Dart code in the background of a Flutter app?

[#](#can-i-run-dart-code-in-the-background-of-a-flutter-app)

Yes. You can execute Dart code in background processes on Android and iOS using background execution packages and platform service integrations.

### Can I use JSON, XML, or Protocol Buffers with Flutter?

[#](#can-i-use-json-xml-or-protocol-buffers-with-flutter)

Yes. The Dart ecosystem provides packages on [pub.dev](https://pub.dev) for JSON serialization, XML parsing, Protocol Buffers, and other data formats. For details on working with JSON, see [JSON and serialization](/data-and-backend/serialization/json).

### Can I build 3D apps with Flutter?

[#](#can-i-build-3d-apps-with-flutter)

Yes. Flutter supports 3D graphics through Flutter GPU and packages such as `flutter_scene`, which use the Impeller rendering backend to draw 3D scenes. You can also embed third-party 3D views (such as Unity or native 3D engines) using platform views.

### Why is my APK or IPA so big?

[#](#why-is-my-apk-or-ipa-so-big)

App bundle size includes the Flutter engine, framework code, application logic, and bundled assets (such as images, fonts, and audio). Always create a release build (`flutter build apk` or `flutter build appbundle` for Android, and `flutter build ipa` for iOS), which removes debug symbols and enables compiler optimizations. To analyze and reduce app size, see [Measuring your app's size](/perf/app-size).

### Is Flutter ABI compatible?

[#](#is-flutter-abi-compatible)

Flutter and Dart don't provide application binary interface (ABI) compatibility across different compiler releases. Always compile your application using matching SDK versions.

### How does Flutter handle scrolling?

[#](#how-does-flutter-handle-scrolling)

Flutter provides platform-specific scroll physics that match the native scrolling behavior of each platform (such as momentum scrolling on iOS and overscroll stretch on Android). For more details, see the [scrolling](/ui/layout/scrolling) documentation.

## Framework

[#](#framework)

### Why is the build() method on State, rather than StatefulWidget?

[#](#why-is-the-build-method-on-state-rather-than-statefulwidget)

Placing the `Widget build(BuildContext context)` method on `State` rather than `StatefulWidget` provides greater flexibility when subclassing `StatefulWidget` and managing lifecycle state. For a detailed explanation, see the [API documentation for `State.build`](https://api.flutter.dev/flutter/widgets/State/build.html).

### Where is Flutter's markup language? Why doesn't Flutter have a markup syntax?

[#](#where-is-flutters-markup-language-why-doesnt-flutter-have-a-markup-syntax)

Flutter UIs are defined in Dart code rather than a declarative markup language. Defining UI in code enables:

* Fast development with stateful hot reload.
* Full access to programming language features (loops, conditionals, functions).
* Strong type safety and compiler checks.
* Easy refactoring and code reuse across widgets.

Because build methods are standard code, you can also create widgets dynamically from data or external configurations.

### My app has a Debug banner in the upper right. Why am I seeing that?

[#](#my-app-has-a-debug-banner-in-the-upper-right-why-am-i-seeing-that)

By default, `flutter run` launches the app in debug mode. The debug build configuration runs code inside the Dart VM to enable stateful [hot reload](/tools/hot-reload), and enables runtime assertions to catch errors early. The "Debug" banner indicates that these checks are active.

To run without assertions and with optimized performance, run the app in release mode using `flutter run --release` or build a release binary.

### What programming paradigm does Flutter's framework use?

[#](#what-programming-paradigm-does-flutters-framework-use)

Flutter uses a combination of programming paradigms, applying each where it is most effective:

* **Composition**

  The primary paradigm in Flutter is composing small, single-purpose widgets into complex user interfaces. For example, the Material [`TextButton`](https://pub.dev/documentation/material_ui/latest/material_ui/TextButton-class.html) widget is composed of an [`IconTheme`](https://api.flutter.dev/flutter/widgets/IconTheme-class.html), an [`InkWell`](https://pub.dev/documentation/material_ui/latest/material_ui/InkWell-class.html), a [`Padding`](https://api.flutter.dev/flutter/widgets/Padding-class.html), a [`Center`](https://api.flutter.dev/flutter/widgets/Center-class.html), a [`Material`](https://pub.dev/documentation/material_ui/latest/material_ui/Material-class.html), an [`AnimatedDefaultTextStyle`](https://api.flutter.dev/flutter/widgets/AnimatedDefaultTextStyle-class.html), and a [`ConstrainedBox`](https://api.flutter.dev/flutter/widgets/ConstrainedBox-class.html).

* **Functional programming**

  Applications can define UI using [`StatelessWidget`](https://api.flutter.dev/flutter/widgets/StatelessWidget-class.html) instances, which act as functions mapping configuration inputs to child widgets. Flutter uses immutable data structures across its widget hierarchy, including [`Widget`](https://api.flutter.dev/flutter/widgets/Widget-class.html), [`TextStyle`](https://api.flutter.dev/flutter/painting/TextStyle-class.html), and [`Rect`](https://api.flutter.dev/flutter/dart-ui/Rect-class.html).

* **Event-driven programming**

  User interactions are represented by event objects dispatched to callbacks registered on widgets. Classes like [`Listenable`](https://api.flutter.dev/flutter/foundation/Listenable-class.html) formalize event subscription models.

* **Class-based object-oriented programming**

  The framework APIs use inheritance to specialize behavior. For example, [`RenderBox`](https://api.flutter.dev/flutter/rendering/RenderBox-class.html) extends [`RenderObject`](https://api.flutter.dev/flutter/rendering/RenderObject-class.html) to establish a Cartesian coordinate system for 2D layout.

* **Prototype-based object-oriented programming**

  The [`ScrollPhysics`](https://api.flutter.dev/flutter/widgets/ScrollPhysics-class.html) class chains instances to compose scrolling physics dynamically at runtime.

* **Imperative programming**

  Imperative code manages encapsulated state and test execution, where step-by-step logic is clearest.

* **Reactive programming**

  Constructor parameters propagate changes down the widget tree, and user interactions trigger updates back up the tree through callbacks. State changes schedule rebuilds of affected subtrees.

* **Declarative programming**

  Widget `build` methods describe the desired UI state declaratively, leaving layout and rendering updates to the framework.

* **Generic programming**

  Generic types enforce type safety across the framework, such as parameterizing [`State<T>`](https://api.flutter.dev/flutter/widgets/State-class.html) with its widget type and [`GlobalKey<T>`](https://api.flutter.dev/flutter/widgets/GlobalKey-class.html) with its state type.

* **Concurrent programming**

  Flutter uses [`Future`](https://api.flutter.dev/flutter/dart-async/Future-class.html) and [`Stream`](https://api.flutter.dev/flutter/dart-async/Stream-class.html) for asynchronous operations, and isolates for parallel background computation.

* **Constraint programming**

  The layout system uses box constraints (minimum and maximum width and height) passed down from parent to child to compute geometry in a single pass.

## Project

[#](#project)

### Where can I get support?

[#](#where-can-i-get-support)

To report bugs or request features, file an issue in the [Flutter issue tracker](https://github.com/flutter/flutter/issues).

For community discussion, troubleshooting, and advice:

* Join the [Flutter Discord](https://discord.com/invite/rflutterdev).
* Ask questions on [Stack Overflow](https://stackoverflow.com/tags/flutter) using the `flutter` tag.
* Participate in discussions on [Reddit r/FlutterDev](https://www.reddit.com/r/FlutterDev/).
* Visit the [Community](https://flutter.dev/community) page for additional resources.

### How do I get involved?

[#](#how-do-i-get-involved)

Flutter is open source, and contributions are welcome. You can help by:

* Reporting bugs and feature requests on the [issue tracker](https://github.com/flutter/flutter/issues).
* Contributing code and documentation by following the [Contributing guide](https://github.com/flutter/flutter/blob/main/CONTRIBUTING.md).
* Triaging issues or helping other developers on [Discord](https://discord.com/invite/rflutterdev) and [Stack Overflow](https://stackoverflow.com/tags/flutter).

### Is Flutter open source?

[#](#is-flutter-open-source)

Yes. Flutter is open source and available under the BSD 3-Clause License on [GitHub](https://github.com/flutter/flutter).

### Which software license(s) apply to Flutter and its dependencies?

[#](#which-software-licenses-apply-to-flutter-and-its-dependencies)

Flutter includes two main components: the engine (compiled binary with dependencies) and the framework (Dart source code).

* The framework is licensed under the [BSD 3-Clause License](https://github.com/flutter/flutter/blob/main/LICENSE).
* The engine uses open source dependencies listed in its [license file](https://github.com/flutter/flutter/blob/main/engine/src/flutter/sky/packages/sky_engine/LICENSE).
* Dart packages on pub.dev specify their own individual licenses.

### How can I determine the licenses my Flutter application needs to show?

[#](#how-can-i-determine-the-licenses-my-flutter-application-needs-to-show)

Flutter provides built-in APIs to display open source licenses:

* If your application uses a `Drawer`, add an [`AboutListTile`](https://pub.dev/documentation/material_ui/latest/material_ui/AboutListTile-class.html).
* If your application uses Material Design, call [`showAboutDialog`](https://pub.dev/documentation/material_ui/latest/material_ui/showAboutDialog.html) or [`showLicensePage`](https://pub.dev/documentation/material_ui/latest/material_ui/showLicensePage.html).
* For custom presentations, access the raw licenses from the [`LicenseRegistry`](https://api.flutter.dev/flutter/foundation/LicenseRegistry-class.html).

### Who works on Flutter?

[#](#who-works-on-flutter)

Flutter is developed by engineers at Google along with hundreds of open source contributors worldwide. To join the community, see [contribute to Flutter](https://github.com/flutter/flutter/blob/main/CONTRIBUTING.md).

### What are Flutter's guiding principles?

[#](#what-are-flutters-guiding-principles)

Flutter is built around three core principles:

* *Control*

  Developers have full access to and control over every layer of the system.

* *Performance*

  Applications deliver smooth, responsive, jank-free experiences.

* *Fidelity*

  Applications achieve pixel-perfect, beautiful user experiences on every platform.

### Will Apple or Google reject my Flutter app?

[#](#will-apple-or-google-reject-my-flutter-app)

No. Thousands of Flutter apps are published, approved, and featured in the Apple App Store and Google Play Store. As with any application, ensure your app complies with Apple's [App Store Review Guidelines](https://developer.apple.com/app-store/review/guidelines/) and Google Play's Developer Program Policies.

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-09-29. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/resources/faq.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/resources/faq\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/resources/faq.md "Report an issue with this page").

----
url: https://docs.flutter.dev/tools/devtools/release-notes
----

This page summarizes the changes in official stable releases of DevTools. To view a complete list of changes, check out the [DevTools git log](https://github.com/flutter/devtools/commits/master).

The Dart and Flutter SDKs include DevTools. To check your current version of DevTools, run the following on your command line:

```
$ dart devtools --version
```

### Release notes

[#](#release-notes)

* [2.61.0 release notes](/tools/devtools/release-notes/release-notes-2.61.0)
* [2.60.0 release notes](/tools/devtools/release-notes/release-notes-2.60.0)
* [2.59.0 release notes](/tools/devtools/release-notes/release-notes-2.59.0)
* [2.58.0 release notes](/tools/devtools/release-notes/release-notes-2.58.0)
* [2.57.0 release notes](/tools/devtools/release-notes/release-notes-2.57.0)
* [2.55.0 release notes](/tools/devtools/release-notes/release-notes-2.55.0)
* [2.54.0 release notes](/tools/devtools/release-notes/release-notes-2.54.0)
* [2.53.0 release notes](/tools/devtools/release-notes/release-notes-2.53.0)
* [2.52.0 release notes](/tools/devtools/release-notes/release-notes-2.52.0)
* [2.51.1 release notes](/tools/devtools/release-notes/release-notes-2.51.1)
* [2.51.0 release notes](/tools/devtools/release-notes/release-notes-2.51.0)
* [2.50.0 release notes](/tools/devtools/release-notes/release-notes-2.50.0)
* [2.49.0 release notes](/tools/devtools/release-notes/release-notes-2.49.0)
* [2.48.0 release notes](/tools/devtools/release-notes/release-notes-2.48.0)
* [2.47.0 release notes](/tools/devtools/release-notes/release-notes-2.47.0)
* [2.46.0 release notes](/tools/devtools/release-notes/release-notes-2.46.0)
* [2.45.0 release notes](/tools/devtools/release-notes/release-notes-2.45.0)
* [2.44.0 release notes](/tools/devtools/release-notes/release-notes-2.44.0)
* [2.42.3 release notes](/tools/devtools/release-notes/release-notes-2.42.3)
* [2.41.0 release notes](/tools/devtools/release-notes/release-notes-2.41.0)
* [2.40.2 release notes](/tools/devtools/release-notes/release-notes-2.40.2)
* [2.39.0 release notes](/tools/devtools/release-notes/release-notes-2.39.0)
* [2.38.0 release notes](/tools/devtools/release-notes/release-notes-2.38.0)
* [2.37.2 release notes](/tools/devtools/release-notes/release-notes-2.37.2)
* [2.36.0 release notes](/tools/devtools/release-notes/release-notes-2.36.0)
* [2.35.0 release notes](/tools/devtools/release-notes/release-notes-2.35.0)
* [2.34.1 release notes](/tools/devtools/release-notes/release-notes-2.34.1)
* [2.33.0 release notes](/tools/devtools/release-notes/release-notes-2.33.0)
* [2.32.0 release notes](/tools/devtools/release-notes/release-notes-2.32.0)
* [2.31.0 release notes](/tools/devtools/release-notes/release-notes-2.31.0)
* [2.30.0 release notes](/tools/devtools/release-notes/release-notes-2.30.0)
* [2.29.0 release notes](/tools/devtools/release-notes/release-notes-2.29.0)
* [2.28.5 release notes](/tools/devtools/release-notes/release-notes-2.28.5)
* [2.28.4 release notes](/tools/devtools/release-notes/release-notes-2.28.4)
* [2.28.3 release notes](/tools/devtools/release-notes/release-notes-2.28.3)
* [2.28.2 release notes](/tools/devtools/release-notes/release-notes-2.28.2)
* [2.28.1 release notes](/tools/devtools/release-notes/release-notes-2.28.1)
* [2.27.0 release notes](/tools/devtools/release-notes/release-notes-2.27.0)
* [2.26.1 release notes](/tools/devtools/release-notes/release-notes-2.26.1)
* [2.25.0 release notes](/tools/devtools/release-notes/release-notes-2.25.0)
* [2.24.0 release notes](/tools/devtools/release-notes/release-notes-2.24.0)
* [2.23.1 release notes](/tools/devtools/release-notes/release-notes-2.23.1)
* [2.22.2 release notes](/tools/devtools/release-notes/release-notes-2.22.2)
* [2.21.1 release notes](/tools/devtools/release-notes/release-notes-2.21.1)
* [2.20.0 release notes](/tools/devtools/release-notes/release-notes-2.20.0)
* [2.19.0 release notes](/tools/devtools/release-notes/release-notes-2.19.0)
* [2.18.0 release notes](/tools/devtools/release-notes/release-notes-2.18.0)
* [2.17.0 release notes](/tools/devtools/release-notes/release-notes-2.17.0)
* [2.16.0 release notes](/tools/devtools/release-notes/release-notes-2.16.0)
* [2.15.0 release notes](/tools/devtools/release-notes/release-notes-2.15.0)
* [2.14.0 release notes](/tools/devtools/release-notes/release-notes-2.14.0)
* [2.13.1 release notes](/tools/devtools/release-notes/release-notes-2.13.1)
* [2.12.2 release notes](/tools/devtools/release-notes/release-notes-2.12.2)
* [2.12.1 release notes](/tools/devtools/release-notes/release-notes-2.12.1)
* [2.11.2 release notes](/tools/devtools/release-notes/release-notes-2.11.2)
* [2.10.0 release notes](/tools/devtools/release-notes/release-notes-2.10.0)
* [2.9.2 release notes](/tools/devtools/release-notes/release-notes-2.9.2)
* [2.9.1 release notes](/tools/devtools/release-notes/release-notes-2.9.1)
* [2.8.0 release notes](/tools/devtools/release-notes/release-notes-2.8.0)
* [2.7.0 release notes](/tools/devtools/release-notes/release-notes-2.7.0)

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/release-notes/index.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/tools/devtools/release-notes\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/release-notes/index.md "Report an issue with this page").

----
url: https://docs.flutter.dev/platform-integration/android/local-network-permission.md
----

\# Request local network permissions on Android > Learn how to declare and request Android local area network permissions before opening Dart sockets in Flutter. Starting in Android 17 (API level 37), Android blocks local network access by default. Apps targeting Android 17 or higher that discover, scan, or connect to devices on the local area network must declare and request the \`ACCESS\_LOCAL\_NETWORK\` runtime permission. :::note Although enforced for apps targeting Android 17 (API level 37) and higher, you can opt in to test this behavior on Android 16 (API level 36). ::: ## Understand the impact on Dart sockets The standard \`dart:io\` library provides low-level socket networking, including \[\`Socket\`]\[socket-class], \[\`RawSocket\`]\[raw-socket-class], and \[\`ServerSocket\`]\[server-socket-class]. These socket classes operate at the system level and do not interact with Android's application framework UI. Because Dart sockets cannot display an Android permission prompt, any attempt to connect to a local IP address without the required permission fails and throws a \`SocketException\`. To prevent connection failures, request the runtime permission in Flutter before you initialize or connect your Dart socket. ## Declare the permission in Android Before you can request the permission at runtime, declare it in your Android manifest. 1. Open \`android/app/src/main/AndroidManifest.xml\`. 1. Add the \`ACCESS\_LOCAL\_NETWORK\` permission inside the \`\` tag: \`\`\`xml \`\`\` ## Add the permission\_handler package To request Android runtime permissions from Dart, use the \[\`permission\_handler\`]\[] plugin from \[pub.dev]\[]. Add \`permission\_handler\` to your Flutter project dependencies: \`\`\`bash flutter pub add permission\_handler \`\`\` ## Request permission before connecting a socket Call \`Permission.accessLocalNetwork.request()\` before opening a Dart socket connection. The following example demonstrates how to check the platform, request local network permission, and open a socket connection: \`\`\`dart import 'dart:io'; import 'package:material\_ui/material\_ui.dart'; import 'package:permission\_handler/permission\_handler.dart'; Future connectToLocalDevice({ required String host, required int port, }) async { // Check and request runtime permission on Android. if (Platform.isAndroid) { final status = await Permission.accessLocalNetwork.request(); if (status.isPermanentlyDenied) { // The user opted not to grant permission and checked "Don't ask again". // In a real app, show an explanation dialog before calling openAppSettings(). debugPrint('Permission permanently denied. Please enable it in settings.'); return; } if (!status.isGranted) { // Handle the case where the user denied permission. debugPrint('Local network permission was denied.'); return; } } // Connect the Dart socket after the permission is granted. try { final socket = await Socket.connect( host, port, timeout: const Duration(seconds: 5), ); debugPrint( 'Connected to ${socket.remoteAddress.address}:${socket.remotePort}', ); // Use the socket... await socket.close(); } on SocketException catch (e) { debugPrint('Failed to connect: $e'); } } \`\`\` ## Handle permission status The \`Permission.accessLocalNetwork.request()\` method returns a \`PermissionStatus\` enum that indicates the outcome: \* \`PermissionStatus.granted\`: The user granted local network access. You can proceed to connect your Dart sockets. \* \`PermissionStatus.denied\`: The user denied permission for this request. Prompt the user with an explanation before requesting permission again. \* \`PermissionStatus.permanentlyDenied\`: The user denied permission permanently. Call \`openAppSettings()\` to help the user enable permission in the system settings. :::tip You only need to obtain permission once during an app session, but checking the status before establishing a socket connection ensures reliable network calls. ::: ## More information To learn more about local network permissions and socket programming, consult the following resources: \* Android developer guide on \[Local network permission]\[android-local-network] \* \[\`permission\_handler\`]\[] package on pub.dev \* Dart \[\`Socket\` class documentation]\[socket-class] \* \[Issue #184859 on GitHub]\[flutter-issue-184859] \[android-local-network]: https\://developer.android.com/privacy-and-security/local-network-permission#android-17-enforcement \[\`permission\_handler\`]: https\://pub.dev/packages/permission\_handler \[pub.dev]: https\://pub.dev \[socket-class]: https\://api.dart.dev/dart-io/Socket-class.html \[raw-socket-class]: https\://api.dart.dev/dart-io/RawSocket-class.html \[server-socket-class]: https\://api.dart.dev/dart-io/ServerSocket-class.html \[flutter-issue-184859]: https\://github.com/flutter/flutter/issues/184859

----
url: https://docs.flutter.dev/flutter-for/swiftui-devs.md
----

\# Flutter for SwiftUI Developers > Learn how to apply SwiftUI developer knowledge when building Flutter apps.

<!--?code-excerpt path-base="get-started/flutter-for/ios_devs"?-->

SwiftUI developers who want to write mobile apps using Flutter should review this guide. It explains how to apply existing SwiftUI knowledge to Flutter. :::note If you instead have experience building apps for iOS with UIKit, see \[Flutter for UIKit developers]\[]. ::: Flutter is a framework for building cross-platform applications that uses the Dart programming language. To understand some differences between programming with Dart and programming with Swift, see \[Learning Dart as a Swift Developer]\[] and \[Flutter concurrency for Swift developers]\[]. Your SwiftUI knowledge and experience are highly valuable when building with Flutter. Flutter also makes a number of adaptations to app behavior when running on iOS and macOS. To learn how, see \[Platform adaptations]\[]. :::tip To integrate Flutter code into an \*\*existing\*\* iOS app, check out \[Add Flutter to existing app]\[]. ::: This document can be used as a cookbook by jumping around and finding questions that are most relevant to your needs. This guide embeds sample code. ## Overview As an introduction, watch the following video. It outlines how Flutter works on iOS and how to use Flutter to build iOS apps. Flutter and SwiftUI code describes how the UI looks and works. Developers call this type of code a \_declarative framework\_. ### Views vs. Widgets \*\*SwiftUI\*\* represents UI components as \_views\_. You configure views using \_modifiers\_. \`\`\`swift Text("Hello, World!") // <-- This is a View .padding(10) // <-- This is a modifier of that View \`\`\` \*\*Flutter\*\* represents UI components as \_widgets\_. Both views and widgets only exist until they need to be changed. These languages call this property \_immutability\_. SwiftUI represents a UI component property as a View modifier. By contrast, Flutter uses widgets for both UI components and their properties. \`\`\`dart Padding( // <-- This is a Widget padding: EdgeInsets.all(10.0), // <-- So is this child: Text("Hello, World!"), // <-- This, too ) \`\`\` To compose layouts, both SwiftUI and Flutter nest UI components within one another. SwiftUI nests Views while Flutter nests Widgets. ### Layout process \*\*SwiftUI\*\* lays out views using the following process: 1. The parent view proposes a size to its child view. 1. All subsequent child views: - propose a size to \_their\_ child's view - ask that child what size it wants 1. Each parent view renders its child view at the returned size. \*\*Flutter\*\* differs somewhat with its process: 1. The parent widget passes constraints down to its children. Constraints include minimum and maximum values for height and width. 1. The child tries to decide its size. It repeats the same process with its own list of children: - It informs its child of the child's constraints. - It asks its child what size it wishes to be. 1. The parent lays out the child. - If the requested size fits in the constraints, the parent uses that size. - If the requested size doesn't fit in the constraints, the parent limits the height, width, or both to fit in its constraints. Flutter differs from SwiftUI because the parent component can override the child's desired size. The widget cannot have any size it wants. It also cannot know or decide its position on screen as its parent makes that decision. To force a child widget to render at a specific size, the parent must set tight constraints. A constraint becomes tight when its constraint's minimum size value equals its maximum size value. In \*\*SwiftUI\*\*, views might expand to the available space or limit their size to that of its content. \*\*Flutter\*\* widgets behave in similar manner. However, in Flutter parent widgets can offer unbounded constraints. Unbounded constraints set their maximum values to infinity. \`\`\`dart UnboundedBox( child: Container( width: double.infinity, height: double.infinity, color: red), ) \`\`\` If the child expands and it has unbounded constraints, Flutter returns an overflow warning: \`\`\`dart UnconstrainedBox( child: Container(color: red, width: 4000, height: 50), ) \`\`\` To learn how constraints work in Flutter, see \[Understanding constraints]\[]. ### Design system Because Flutter targets multiple platforms, your app doesn't need to conform to any design system. Though this guide features \[Material]\[] widgets, your Flutter app can use many different design systems: - Custom Material widgets - Community built widgets - Your own custom widgets - \[Cupertino widgets]\[] that follow Apple's Human Interface Guidelines If you're looking for a great reference app that features a custom design system, check out \[Wonderous]\[]. ## UI Basics This section covers the basics of UI development in Flutter and how it compares to SwiftUI. This includes how to start developing your app, display static text, create buttons, react to on-press events, display lists, grids, and more. ### Getting started In \*\*SwiftUI\*\*, you use \`App\` to start your app. \`\`\`swift @main struct MyApp: App { var body: some Scene { WindowGroup { HomePage() } } } \`\`\` Another common SwiftUI practice places the app body within a \`struct\` that conforms to the \`View\` protocol as follows: \`\`\`swift struct HomePage: View { var body: some View { Text("Hello, World!") } } \`\`\` To start your \*\*Flutter\*\* app, pass in an instance of your app to the \`runApp\` function.

<!--?code-excerpt "lib/get_started.dart (main)"?-->

\`\`\`dart dartpad="42cf3026e1460ef618257684ee5af6a2" void main() { runApp(const MyApp()); } \`\`\` \`App\` is a widget. The build method describes the part of the user interface it represents. It's common to begin your app with a \[\`WidgetApp\`]\[] class, like \[\`CupertinoApp\`]\[].

<!--?code-excerpt "lib/get_started.dart (myapp)"?-->

\`\`\`dart dartpad="42cf3026e1460ef618257684ee5af6a2" class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { // Returns a CupertinoApp that, by default, // has the look and feel of an iOS app. return const CupertinoApp(home: HomePage()); } } \`\`\` The widget used in \`HomePage\` might begin with the \`Scaffold\` class. \`Scaffold\` implements a basic layout structure for an app.

<!--?code-excerpt "lib/get_started.dart (homepage)"?-->

\`\`\`dart dartpad="42cf3026e1460ef618257684ee5af6a2" class HomePage extends StatelessWidget { const HomePage({super.key}); @override Widget build(BuildContext context) { return const Scaffold(body: Center(child: Text('Hello, World!'))); } } \`\`\` Note how Flutter uses the \[\`Center\`]\[] widget. SwiftUI renders a view's contents in its center by default. That's not always the case with Flutter. \`Scaffold\` doesn't render its \`body\` widget at the center of the screen. To center the text, wrap it in a \`Center\` widget. To learn about different widgets and their default behaviors, check out the \[Widget catalog]\[]. ### Adding Buttons In \*\*SwiftUI\*\*, you use the \`Button\` struct to create a button. \`\`\`swift Button("Do something") { // this closure gets called when your // button is tapped } \`\`\` To achieve the same result in \*\*Flutter\*\*, use the \`CupertinoButton\` class:

<!--?code-excerpt "lib/text_button.dart (text-button)" replace="/child: //g;"?-->

\`\`\`dart dartpad="3c9b9a4de431b86725197a7fc2c84158" CupertinoButton( onPressed: () { // This closure is called when your button is tapped. }, const Text('Do something'), ), \`\`\` \*\*Flutter\*\* gives you access to a variety of buttons with predefined styles. The \[\`CupertinoButton\`]\[] class comes from the Cupertino library. Widgets in the Cupertino library use Apple's design system. ### Aligning components horizontally In \*\*SwiftUI\*\*, stack views play a big part in designing your layouts. Two separate structures allow you to create stacks: 1. \`HStack\` for horizontal stack views 2. \`VStack\` for vertical stack views The following SwiftUI view adds a globe image and text to a horizontal stack view: \`\`\`swift HStack { Image(systemName: "globe") Text("Hello, world!") } \`\`\` \*\*Flutter\*\* uses \[\`Row\`]\[] rather than \`HStack\`:

<!--?code-excerpt "lib/row.dart (row)" replace="/child: //g;"?-->

\`\`\`dart dartpad="0365338f938427b01d72e37cea554f75" Row( mainAxisAlignment: MainAxisAlignment.center, children: \[Icon(CupertinoIcons.globe), Text('Hello, world!')], ), \`\`\` The \`Row\` widget requires a \`List\` in the \`children\` parameter. The \`mainAxisAlignment\` property tells Flutter how to position children with extra space. \`MainAxisAlignment.center\` positions children in the center of the main axis. For \`Row\`, the main axis is the horizontal axis. ### Aligning components vertically The following examples build on those in the previous section. In \*\*SwiftUI\*\*, you use \`VStack\` to arrange the components into a vertical pillar. \`\`\`swift VStack { Image(systemName: "globe") Text("Hello, world!") } \`\`\` \*\*Flutter\*\* uses the same Dart code from the previous example, except it swaps \[\`Column\`]\[] for \`Row\`:

<!--?code-excerpt "lib/column.dart (column)" replace="/child: //g;"?-->

\`\`\`dart dartpad="d9a288be0c2a353296fc8825680b84b8" Column( mainAxisAlignment: MainAxisAlignment.center, children: \[Icon(CupertinoIcons.globe), Text('Hello, world!')], ), \`\`\` ### Displaying a list view In \*\*SwiftUI\*\*, you use the \`List\` base component to display sequences of items. To display a sequence of model objects, make sure that the user can identify your model objects. To make an object identifiable, use the \`Identifiable\` protocol. \`\`\`swift struct Person: Identifiable { var name: String } var persons = \[ Person(name: "Person 1"), Person(name: "Person 2"), Person(name: "Person 3"), ] struct ListWithPersons: View { let persons: \[Person] var body: some View { List { ForEach(persons) { person in Text(person.name) } } } } \`\`\` This resembles how \*\*Flutter\*\* prefers to build its list widgets. Flutter doesn't need the list items to be identifiable. You set the number of items to display then build a widget for each item.

<!--?code-excerpt "lib/list.dart (simple-list)"?-->

\`\`\`dart dartpad="67426fd4f9c38c0c1db96b1af65598f2" class Person { String name; Person(this.name); } final List items = \[ Person('Person 1'), Person('Person 2'), Person('Person 3'), ]; class HomePage extends StatelessWidget { const HomePage({super.key}); @override Widget build(BuildContext context) { return Scaffold( body: ListView\.builder( itemCount: items.length, itemBuilder: (context, index) { return ListTile(title: Text(items\[index].name)); }, ), ); } } \`\`\` Flutter has some caveats for lists: - The \[\`ListView\`] widget has a builder method. This works like the \`ForEach\` within SwiftUI's \`List\` struct. - The \`itemCount\` parameter of the \`ListView\` sets how many items the \`ListView\` displays. - The \`itemBuilder\` has an index parameter that will be between zero and one less than itemCount. The previous example returned a \[\`ListTile\`]\[] widget for each item. The \`ListTile\` widget includes properties like \`height\` and \`font-size\`. These properties help build a list. However, Flutter allows you to return almost any widget that represents your data. ### Displaying a grid When constructing non-conditional grids in \*\*SwiftUI\*\*, you use \`Grid\` with \`GridRow\`. \`\`\`swift Grid { GridRow { Text("Row 1") Image(systemName: "square.and.arrow\.down") Image(systemName: "square.and.arrow\.up") } GridRow { Text("Row 2") Image(systemName: "square.and.arrow\.down") Image(systemName: "square.and.arrow\.up") } } \`\`\` To display grids in \*\*Flutter\*\*, use the \[\`GridView\`] widget. This widget has various constructors. Each constructor has a similar goal, but uses different input parameters. The following example uses the \`.builder()\` initializer:

<!--?code-excerpt "lib/grid.dart (grid-example)"?-->

\`\`\`dart dartpad="d6b9174f33db94164e457b3da80da933" const widgets = \[ Text('Row 1'), Icon(CupertinoIcons.arrow\_down\_square), Icon(CupertinoIcons.arrow\_up\_square), Text('Row 2'), Icon(CupertinoIcons.arrow\_down\_square), Icon(CupertinoIcons.arrow\_up\_square), ]; class HomePage extends StatelessWidget { const HomePage({super.key}); @override Widget build(BuildContext context) { return Scaffold( body: GridView\.builder( gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 3, mainAxisExtent: 40, ), itemCount: widgets.length, itemBuilder: (context, index) => widgets\[index], ), ); } } \`\`\` The \`SliverGridDelegateWithFixedCrossAxisCount\` delegate determines various parameters that the grid uses to lay out its components. This includes \`crossAxisCount\` that dictates the number of items displayed on each row. How SwiftUI's \`Grid\` and Flutter's \`GridView\` differ in that \`Grid\` requires \`GridRow\`. \`GridView\` uses the delegate to decide how the grid should lay out its components. ### Creating a scroll view In \*\*SwiftUI\*\*, you use \`ScrollView\` to create custom scrolling components. The following example displays a series of \`PersonView\` instances in a scrollable fashion. \`\`\`swift ScrollView { VStack(alignment: .leading) { ForEach(persons) { person in PersonView(person: person) } } } \`\`\` To create a scrolling view, \*\*Flutter\*\* uses \[\`SingleChildScrollView\`]\[]. In the following example, the function \`mockPerson\` mocks instances of the \`Person\` class to create the custom \`PersonView\` widget.

<!--?code-excerpt "lib/scroll.dart (scroll-example)" replace="/body: //g;"?-->

\`\`\`dart dartpad="a75740320989ed04020d95502a0de34e" SingleChildScrollView( child: Column( children: mockPersons .map((person) => PersonView(person: person)) .toList(), ), ), \`\`\` ### Responsive and adaptive design In \*\*SwiftUI\*\*, you use \`GeometryReader\` to create relative view sizes. For example, you could: - Multiply \`geometry.size.width\` by some factor to set the \_width\_. - Use \`GeometryReader\` as a breakpoint to change the design of your app. You can also see if the size class has \`.regular\` or \`.compact\` using \`horizontalSizeClass\`. To create relative views in \*\*Flutter\*\*, you can use one of two options: - Get the \`BoxConstraints\` object in the \[\`LayoutBuilder\`]\[] class. - Use the \[\`MediaQuery.of()\`]\[] in your build functions to get the size and orientation of your current app. To learn more, check out \[Creating responsive and adaptive apps]\[]. ### Managing state In \*\*SwiftUI\*\*, you use the \`@State\` property wrapper to represent the internal state of a SwiftUI view. \`\`\`swift struct ContentView: View { @State private var counter = 0; var body: some View { VStack{ Button("+") { counter+=1 } Text(String(counter)) } }} \`\`\` \*\*SwiftUI\*\* also includes several options for more complex state management such as the \`ObservableObject\` protocol. \*\*Flutter\*\* manages local state using a \[\`StatefulWidget\`]\[]. Implement a stateful widget with the following two classes: - a subclass of \`StatefulWidget\` - a subclass of \`State\` The \`State\` object stores the widget's state. To change a widget's state, call \`setState()\` from the \`State\` subclass to tell the framework to redraw the widget. The following example shows a part of a counter app:

<!--?code-excerpt "lib/state.dart (state)"?-->

\`\`\`dart dartpad="34815ab7d6ee0c5a45c82597df444450" class MyHomePage extends StatefulWidget { const MyHomePage({super.key}); @override State createState() => \_MyHomePageState(); } class \_MyHomePageState extends State { int \_counter = 0; @override Widget build(BuildContext context) { return Scaffold( body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: \[ Text('$\_counter'), TextButton( onPressed: () => setState(() { \_counter++; }), child: const Text('+'), ), ], ), ), ); } } \`\`\` To learn more ways to manage state, check out \[State management]\[]. ### Animations Two main types of UI animations exist. - Implicit that animated from a current value to a new target. - Explicit that animates when asked. #### Implicit Animation SwiftUI and Flutter take a similar approach to animation. In both frameworks, you specify parameters like \`duration\`, and \`curve\`. In \*\*SwiftUI\*\*, you use the \`animate()\` modifier to handle implicit animation. \`\`\`swift Button("Tap me!"){ angle += 45 } .rotationEffect(.degrees(angle)) .animation(.easeIn(duration: 1)) \`\`\` \*\*Flutter\*\* includes widgets for implicit animation. This simplifies animating common widgets. Flutter names these widgets with the following format: \`AnimatedFoo\`. For example: To rotate a button, use the \[\`AnimatedRotation\`]\[] class. This animates the \`Transform.rotate\` widget.

<!--?code-excerpt "lib/simple_animation.dart (animated-button)" replace="/child: //g;"?-->

\`\`\`dart dartpad="0ad0572cbf98ead2e5d31a2a94430f19" AnimatedRotation( duration: const Duration(seconds: 1), turns: turns, curve: Curves.easeIn, TextButton( onPressed: () { setState(() { turns += .125; }); }, const Text('Tap me!'), ), ), \`\`\` Flutter allows you to create custom implicit animations. To compose a new animated widget, use the \[\`TweenAnimationBuilder\`]\[]. #### Explicit Animation For explicit animations, \*\*SwiftUI\*\* uses the \`withAnimation()\` function. \*\*Flutter\*\* includes explicitly animated widgets with names formatted like \`FooTransition\`. One example would be the \[\`RotationTransition\`]\[] class. Flutter also allows you to create a custom explicit animation using \`AnimatedWidget\` or \`AnimatedBuilder\`. To learn more about animations in Flutter, see \[Animations overview]\[]. ### Drawing on the Screen In \*\*SwiftUI\*\*, you use \`CoreGraphics\` to draw lines and shapes to the screen. \*\*Flutter\*\* has an API based on the \`Canvas\` class, with two classes that help you draw: 1. \[\`CustomPaint\`]\[] that requires a painter:

<!--?code-excerpt "lib/canvas.dart (custom-paint)" replace="/child: //g;"?-->

\`\`\`dart dartpad="978d64ee66d54177fb639f8a9f801039" CustomPaint( painter: SignaturePainter(\_points), size: Size.infinite, ), \`\`\` 2. \[\`CustomPainter\`]\[] that implements your algorithm to draw to the canvas.

<!--?code-excerpt "lib/canvas.dart (custom-painter)"?-->

\`\`\`dart dartpad="978d64ee66d54177fb639f8a9f801039" class SignaturePainter extends CustomPainter { SignaturePainter(this.points); final List points; @override void paint(Canvas canvas, Size size) { final Paint paint = Paint() ..color = Colors.black ..strokeCap = StrokeCap.round ..strokeWidth = 5; for (int i = 0; i < points.length - 1; i++) { if (points\[i] != null && points\[i + 1] != null) { canvas.drawLine(points\[i]!, points\[i + 1]!, paint); } } } @override bool shouldRepaint(SignaturePainter oldDelegate) => oldDelegate.points != points; } \`\`\` ## Navigation This section explains how to navigate between pages of an app, the push and pop mechanism, and more. ### Navigating between pages Developers build iOS and macOS apps with different pages called \_navigation routes\_. In \*\*SwiftUI\*\*, the \`NavigationStack\` represents this stack of pages. The following example creates an app that displays a list of persons. To display a person's details in a new navigation link, tap on that person. \`\`\`swift NavigationStack(path: $path) { List { ForEach(persons) { person in NavigationLink( person.name, value: person ) } } .navigationDestination(for: Person.self) { person in PersonView(person: person) } } \`\`\` If you have a small \*\*Flutter\*\* app without complex linking, use \[\`Navigator\`]\[] with named routes. After defining your navigation routes, call your navigation routes using their names. 1. Name each route in the class passed to the \`runApp()\` function. The following example uses \`App\`:

<!--?code-excerpt "lib/navigation.dart (routes)"?-->

\`\`\`dart dartpad="d8b22d4dcbefdc8a2e21f1382cf7dc2a" // Defines the route name as a constant // so that it's reusable. const detailsPageRouteName = '/details'; class App extends StatelessWidget { const App({super.key}); @override Widget build(BuildContext context) { return CupertinoApp( home: const HomePage(), // The \[routes] property defines the available named routes // and the widgets to build when navigating to those routes. routes: {detailsPageRouteName: (context) => const DetailsPage()}, ); } } \`\`\` The following sample generates a list of persons using \`mockPersons()\`. Tapping a person pushes the person's detail page to the \`Navigator\` using \`pushNamed()\`.

<!--?code-excerpt "lib/navigation.dart (list-view)" replace="/child: //g;"?-->

\`\`\`dart dartpad="d8b22d4dcbefdc8a2e21f1382cf7dc2a" ListView\.builder( itemCount: mockPersons.length, itemBuilder: (context, index) { final person = mockPersons.elementAt(index); final age = '${person.age} years old'; return ListTile( title: Text(person.name), subtitle: Text(age), trailing: const Icon(Icons.arrow\_forward\_ios), onTap: () { // When a \[ListTile] that represents a person is // tapped, push the detailsPageRouteName route // to the Navigator and pass the person's instance // to the route. Navigator.of(context) .pushNamed(detailsPageRouteName, arguments: person); }, ); }, ), \`\`\` 1. Define the \`DetailsPage\` widget that displays the details of each person. In Flutter, you can pass arguments into the widget when navigating to the new route. Extract the arguments using \`ModalRoute.of()\`:

<!--?code-excerpt "lib/navigation.dart (details-page)"?-->

\`\`\`dart dartpad="d8b22d4dcbefdc8a2e21f1382cf7dc2a" class DetailsPage extends StatelessWidget { const DetailsPage({super.key}); @override Widget build(BuildContext context) { // Read the person instance from the arguments. final Person person = ModalRoute.of(context)?.settings.arguments as Person; // Extract the age. final age = '${person.age} years old'; return Scaffold( // Display name and age. body: Column(children: \[Text(person.name), Text(age)]), ); } } \`\`\` To create more advanced navigation and routing requirements, use a routing package such as \[go\_router]\[]. To learn more, check out \[Navigation and routing]\[]. ### Manually pop back In \*\*SwiftUI\*\*, you use the \`dismiss\` environment value to pop-back to the previous screen. \`\`\`swift Button("Pop back") { dismiss() } \`\`\` In \*\*Flutter\*\*, use the \`pop()\` function of the \`Navigator\` class:

<!--?code-excerpt "lib/popback.dart (pop-back)"?-->

\`\`\`dart dartpad="3c125ab2dfba9f4178aeaeb8619c5bea" TextButton( onPressed: () { // This code allows the // view to pop back to its presenter. Navigator.of(context).pop(); }, child: const Text('Pop back'), ), \`\`\` ### Navigating to another app In \*\*SwiftUI\*\*, you use the \`openURL\` environment variable to open a URL to another application. \`\`\`swift @Environment(\\.openURL) private var openUrl // View code goes here Button("Open website") { openUrl( URL( string: "https\://google.com" )! ) } \`\`\` In \*\*Flutter\*\*, use the \[\`url\_launcher\`]\[] plugin.

<!--?code-excerpt "lib/openapp.dart (open-app-example)" replace="/child: //g;"?-->

\`\`\`dart dartpad="695beba25fa8120d89c9960cb222e276" CupertinoButton( onPressed: () async { await launchUrl(Uri.parse('https\://google.com')); }, const Text('Open website'), ), \`\`\` ## Themes, styles, and media You can style Flutter apps with little effort. Styling includes switching between light and dark themes, changing the design of your text and UI components, and more. This section covers how to style your apps. ### Using dark mode In \*\*SwiftUI\*\*, you call the \`preferredColorScheme()\` function on a \`View\` to use dark mode. In \*\*Flutter\*\*, you can control light and dark mode at the app-level. To control the brightness mode, use the \`theme\` property of the \`App\` class:

<!--?code-excerpt "lib/cupertino_themes.dart (theme)" replace="/return //g;"?-->

\`\`\`dart dartpad="18790cfaa8441085994373a4bc4f46b0" const CupertinoApp( theme: CupertinoThemeData(brightness: Brightness.dark), home: HomePage(), ); \`\`\` ### Styling text In \*\*SwiftUI\*\*, you use modifier functions to style text. For example, to change the font of a \`Text\` string, use the \`font()\` modifier: \`\`\`swift Text("Hello, world!") .font(.system(size: 30, weight: .heavy)) .foregroundColor(.yellow) \`\`\` To style text in \*\*Flutter\*\*, add a \`TextStyle\` widget as the value of the \`style\` parameter of the \`Text\` widget.

<!--?code-excerpt "lib/cupertino_themes.dart (styling-text)" replace="/child: //g;"?-->

\`\`\`dart dartpad="18790cfaa8441085994373a4bc4f46b0" Text( 'Hello, world!', style: TextStyle( fontSize: 30, fontWeight: FontWeight.bold, color: CupertinoColors.systemYellow, ), ), \`\`\` ### Styling buttons In \*\*SwiftUI\*\*, you use modifier functions to style buttons. \`\`\`swift Button("Do something") { // Do something when the button is tapped. } .font(.system(size: 30, weight: .bold)) .background(Color.yellow) .foregroundColor(Color.blue) \`\`\` To style button widgets in \*\*Flutter\*\*, set the style of its child, or modify properties on the button itself. In the following example: - The \`color\` property of \`CupertinoButton\` sets its \`color\`. - The \`color\` property of the child \`Text\` widget sets the button text color.

<!--?code-excerpt "lib/stylingbutton.dart (styling-button)"?-->

\`\`\`dart dartpad="f8b6622f526fc5c7d5adadf1e071c28f" child: CupertinoButton( color: CupertinoColors.systemYellow, onPressed: () {}, child: const Text( 'Do something', style: TextStyle( color: CupertinoColors.systemBlue, fontSize: 30, fontWeight: FontWeight.bold, ), ), ), \`\`\` ### Using custom fonts In \*\*SwiftUI\*\*, you can use a custom font in your app in two steps. First, add the font file to your SwiftUI project. After adding the file, use the \`.font()\` modifier to apply it to your UI components. \`\`\`swift Text("Hello") .font( Font.custom( "BungeeSpice-Regular", size: 40 ) ) \`\`\` In \*\*Flutter\*\*, you control your resources with a file named \`pubspec.yaml\`. This file is platform agnostic. To add a custom font to your project, follow these steps: 1. Create a folder called \`fonts\` in the project's root directory. This optional step helps to organize your fonts. 1. Add your \`.ttf\`, \`.otf\`, or \`.ttc\` font file into the \`fonts\` folder. 1. Open the \`pubspec.yaml\` file within the project. 1. Find the \`flutter\` section. 1. Add your custom font(s) under the \`fonts\` section. \`\`\`yaml flutter: fonts: - family: BungeeSpice fonts: - asset: fonts/BungeeSpice-Regular.ttf \`\`\` After you add the font to your project, you can use it as in the following example:

<!--?code-excerpt "lib/stylingbutton.dart (custom-font)" replace="/middle: //g;"?-->

\`\`\`dart Text( 'Cupertino', style: TextStyle(fontSize: 40, fontFamily: 'BungeeSpice'), ), \`\`\` :::note To download custom fonts to use in your apps, check out \[Google Fonts]\(https\://fonts.google.com). ::: ### Bundling images in apps In \*\*SwiftUI\*\*, you first add the image files to \`Assets.xcassets\`, then use the \`Image\` view to display the images. To add images in \*\*Flutter\*\*, follow a method similar to how you added custom fonts. 1. Add an \`images\` folder to the root directory. 1. Add this asset to the \`pubspec.yaml\` file. \`\`\`yaml flutter: assets: - images/Blueberries.jpg \`\`\` After adding your image, display it using the \`Image\` widget's \`.asset()\` constructor. This constructor: 1. Instantiates the given image using the provided path. 1. Reads the image from the assets bundled with your app. 1. Displays the image on the screen. To review a complete example, check out the \[\`Image\`]\[] docs. ### Bundling videos in apps In \*\*SwiftUI\*\*, you bundle a local video file with your app in two steps. First, you import the \`AVKit\` framework, then you instantiate a \`VideoPlayer\` view. In \*\*Flutter\*\*, add the \[video\_player]\[] plugin to your project. This plugin allows you to create a video player that works on Android, iOS, and on the web from the same codebase. 1. Add the plugin to your app and add the video file to your project. 1. Add the asset to your \`pubspec.yaml\` file. 1. Use the \`VideoPlayerController\` class to load and play your video file. To review a complete walkthrough, check out the \[video\_player example]\[]. \[Flutter for UIKit developers]: /flutter-for/uikit-devs \[Add Flutter to existing app]: /add-to-app \[Animations overview]: /ui/animations \[Cupertino widgets]: /ui/widgets/cupertino \[Flutter concurrency for Swift developers]: /flutter-for/dart-swift-concurrency \[Navigation and routing]: /ui/navigation \[Material]: https\://m3.material.io/develop/flutter/ \[Platform adaptations]: /platform-integration/platform-adaptations \[\`url\_launcher\`]: https\://pub.dev/packages/url\_launcher \[widget catalog]: /ui/widgets/layout \[Understanding constraints]: /ui/layout/constraints \[\`WidgetApp\`]: https\://api.flutter.dev/flutter/widgets/WidgetsApp-class.html \[\`CupertinoApp\`]: https\://pub.dev/documentation/cupertino\_ui/latest/cupertino\_ui/CupertinoApp-class.html \[\`Center\`]: https\://api.flutter.dev/flutter/widgets/Center-class.html \[\`CupertinoButton\`]: https\://pub.dev/documentation/cupertino\_ui/latest/cupertino\_ui/CupertinoButton-class.html \[\`Row\`]: https\://api.flutter.dev/flutter/widgets/Row-class.html \[\`Column\`]: https\://api.flutter.dev/flutter/widgets/Column-class.html \[Learning Dart as a Swift Developer]: https\://dart.dev/guides/language/coming-from/swift-to-dart \[\`ListView\`]: https\://api.flutter.dev/flutter/widgets/ListView-class.html \[\`ListTile\`]: https\://api.flutter.dev/flutter/widgets/ListTitle-class.html \[\`GridView\`]: https\://api.flutter.dev/flutter/widgets/GridView-class.html \[\`SingleChildScrollView\`]: https\://api.flutter.dev/flutter/widgets/SingleChildScrollView-class.html \[\`LayoutBuilder\`]: https\://api.flutter.dev/flutter/widgets/LayoutBuilder-class.html \[\`AnimatedRotation\`]: https\://api.flutter.dev/flutter/widgets/AnimatedRotation-class.html \[\`TweenAnimationBuilder\`]: https\://api.flutter.dev/flutter/widgets/TweenAnimationBuilder-class.html \[\`RotationTransition\`]: https\://api.flutter.dev/flutter/widgets/RotationTransition-class.html \[\`Navigator\`]: https\://api.flutter.dev/flutter/widgets/Navigator-class.html \[\`StatefulWidget\`]: https\://api.flutter.dev/flutter/widgets/StatefulWidget-class.html \[State management]: /data-and-backend/state-mgmt \[Wonderous]: https\://flutter.gskinner.com/wonderous/?utm\_source=flutterdocs\&utm\_medium=docs\&utm\_campaign=iosdevs \[video\_player]: https\://pub.dev/packages/video\_player \[video\_player example]: https\://pub.dev/packages/video\_player/example \[Creating responsive and adaptive apps]: /ui/adaptive-responsive \[\`MediaQuery.of()\`]: https\://api.flutter.dev/flutter/widgets/MediaQuery-class.html \[\`CustomPaint\`]: https\://api.flutter.dev/flutter/widgets/CustomPaint-class.html \[\`CustomPainter\`]: https\://api.flutter.dev/flutter/rendering/CustomPainter-class.html \[\`Image\`]: https\://api.flutter.dev/flutter/widgets/Image-class.html \[go\_router]: https\://pub.dev/packages/go\_router

----
url: https://docs.flutter.dev/packages-and-plugins/swift-package-manager
----

[Swift Package Manager for app developers](/packages-and-plugins/swift-package-manager/for-app-developers)

[How to use Swift Package Manager for native iOS or macOS dependencies](/packages-and-plugins/swift-package-manager/for-app-developers)

[Swift Package Manager for plugin authors](/packages-and-plugins/swift-package-manager/for-plugin-authors)

[How to add Swift Package Manager compatibility to iOS and macOS plugins](/packages-and-plugins/swift-package-manager/for-plugin-authors)

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/packages-and-plugins/swift-package-manager/index.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/packages-and-plugins/swift-package-manager\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/packages-and-plugins/swift-package-manager/index.md "Report an issue with this page").

----
url: https://docs.flutter.dev/learn/pathway/tutorial/change-notifier.md
----

\# State management in Flutter > Instructions on how to manage state with ChangeNotifiers. Learn to create a ViewModel with ChangeNotifier and manage loading, success, and error states. title: What you'll accomplish items: - title: Create a ViewModel with ChangeNotifier icon: layers - title: Manage loading, success, and error states icon: toggle\_on - title: Signal UI updates with notifyListeners icon: notifications\_active --- ### Introduction When developers talk about state-management in Flutter, they're essentially referring to the pattern by which your app updates the data it needs to render correctly and then tells Flutter to re-render the UI with that new data. In MVVM, this responsibility falls to the ViewModel layer, which sits between and connects your UI to your Model layer. In Flutter, ViewModels use Flutter's \`ChangeNotifier\` class to notify the UI when data changes. To use \[\`ChangeNotifier\`]\[], extend it in your state management class to gain access to the \`notifyListeners()\` method, which triggers UI rebuilds when called. \[\`ChangeNotifier\`]: https\://api.flutter.dev/flutter/foundation/ChangeNotifier-class.html ### Create the basic view model structure Create the \`ArticleViewModel\` class with its basic structure and state properties:

<!--?code-excerpt "fwe/wikipedia_reader/lib/step3a_main.dart (ArticleViewModel)"?-->

\`\`\`dart class ArticleViewModel extends ChangeNotifier { final ArticleModel model; Summary? summary; Exception? error; bool isLoading = false; ArticleViewModel(this.model); } \`\`\` The \`ArticleViewModel\` holds three pieces of state: - \`summary\`: The current Wikipedia article data. - \`error\`: Any error that occurred during data fetching. - \`isLoading\`: A flag to show progress indicators. ### Add constructor initialization Update the constructor to automatically fetch content when the \`ArticleViewModel\` is created:

<!--?code-excerpt "fwe/wikipedia_reader/lib/step3b_main.dart (ArticleViewModel)"?-->

\`\`\`dart class ArticleViewModel extends ChangeNotifier { final ArticleModel model; Summary? summary; Exception? error; bool isLoading = false; ArticleViewModel(this.model) { fetchArticle(); } // Methods will be added next. Future fetchArticle() async {} } \`\`\` This constructor initialization provides immediate content when a \`ArticleViewModel\` object is created. Because constructors can't be asynchronous, it delegates initial content fetching to a separate method. ### Set up the \`fetchArticle\` method Add the \`fetchArticle\` method that fetches data and manages state updates:

<!--?code-excerpt "fwe/wikipedia_reader/lib/step3c_main.dart (ArticleViewModel)"?-->

\`\`\`dart class ArticleViewModel extends ChangeNotifier { final ArticleModel model; Summary? summary; Exception? error; bool isLoading = false; ArticleViewModel(this.model) { fetchArticle(); } Future fetchArticle() async { isLoading = true; notifyListeners(); // TODO: Add data fetching logic isLoading = false; notifyListeners(); } } \`\`\` The ViewModel updates the \`isLoading\` property and calls \`notifyListeners()\` to inform the UI of the update. When the operation completes, it toggles the property back. When you build the UI, you'll use this \`isLoading\` property to show a loading indicator while fetching a new article. ### Retrieve an article from the \`ArticleModel\` Complete the \`fetchArticle\` method to fetch an article summary. Use a \[try-catch block]\[] to gracefully handle network errors and store error messages that the UI can display to users. The method clears previous errors on success and clears the previous article summary on error to maintain a consistent state.

<!--?code-excerpt "fwe/wikipedia_reader/lib/step3d_main.dart (ArticleViewModel)"?-->

\`\`\`dart class ArticleViewModel extends ChangeNotifier { final ArticleModel model; Summary? summary; Exception? error; bool isLoading = false; ArticleViewModel(this.model) { fetchArticle(); } Future fetchArticle() async { isLoading = true; notifyListeners(); try { summary = await model.getRandomArticleSummary(); error = null; // Clear any previous errors. } on HttpException catch (e) { error = e; summary = null; } isLoading = false; notifyListeners(); } } \`\`\` \[try-catch block]: https\://dart.dev/language/error-handling#catch ### Test the ViewModel Before building the full UI, test that your HTTP requests work by printing results to the console. First, update the \`fetchArticle\` method to print the results:

<!--?code-excerpt "fwe/wikipedia_reader/lib/step3e_main.dart (fetchArticle)"?-->

\`\`\`dart Future fetchArticle() async { isLoading = true; notifyListeners(); try { summary = await model.getRandomArticleSummary(); print('Article loaded: ${summary!.titles.normalized}'); // Temporary error = null; // Clear any previous errors. } on HttpException catch (e) { print('Error loading article: ${e.message}'); // Temporary error = e; summary = null; } isLoading = false; notifyListeners(); } \`\`\` Then, update the \`MainApp\` widget to create the \`ArticleViewModel\`, which calls the \`fetchArticle\` method on creation:

<!--?code-excerpt "fwe/wikipedia_reader/lib/step3f_main.dart (MainApp)"?-->

\`\`\`dart class MainApp extends StatelessWidget { const MainApp({super.key}); @override Widget build(BuildContext context) { // Instantiate your \`ArticleViewModel\` to test its HTTP requests. final viewModel = ArticleViewModel(ArticleModel()); return MaterialApp( home: Scaffold( appBar: AppBar(title: const Text('Wikipedia Flutter')), body: const Center(child: Text('Check console for article data')), ), ); } } \`\`\` Hot reload your app and check your console output. You should see either an article title or an error message, which confirms that your Model and ViewModel are wired up correctly. ### Review title: What you accomplished subtitle: Here's a summary of what you built and learned in this lesson. completed: true items: - title: Created the ArticleViewModel with ChangeNotifier icon: layers details: >- The ViewModel sits between your UI and Model, managing state and connecting the two layers. By extending \`ChangeNotifier\`, your ViewModel gains the ability to notify listeners when data changes. - title: Managed loading, success, and error states icon: toggle\_on details: >- Your ViewModel tracks three pieces of state: \`isLoading\`, \`summary\`, and \`error\`. Using \`try\` and \`catch\`, you handle network errors gracefully and maintain consistent state for each possible outcome. - title: Used notifyListeners to signal UI updates icon: notifications\_active details: >- Calling \`notifyListeners()\` tells any listening widgets to rebuild. You call it after setting \`loading = true\` and again after the operation completes. This is how you can implement reactive UI updates in Flutter. ### Test yourself - question: What is a ChangeNotifier? options: - text: A widget that displays notifications to the user. correct: false explanation: ChangeNotifier is not a widget; it's a class for managing state. - text: A class that can notify listeners when its data changes, enabling reactive UI updates. correct: true explanation: ChangeNotifier provides the notifyListeners method to signal widgets to rebuild when state changes. - text: A built-in Dart class for sending push notifications. correct: false explanation: ChangeNotifier is for in-app state management, not push notifications. - text: A type of animation controller in Flutter. correct: false explanation: Animation controllers are separate; ChangeNotifier is for state management. - question: "What does calling \`notifyListeners()\` do in a ChangeNotifier?" options: - text: Saves the current state to local storage. correct: false explanation: "\`notifyListeners()\` signals UI updates; persistence requires separate implementation." - text: Tells any listening widgets to rebuild and reflect the new state. correct: true explanation: "Calling \`notifyListeners()\` triggers a rebuild of all widgets listening to this ChangeNotifier." - text: Logs the state change to the console for debugging. correct: false explanation: It doesn't log anything; it signals listeners to rebuild. - text: Resets all state properties to their default values. correct: false explanation: "\`notifyListeners()\` doesn't modify state; it just signals that state has changed."

----
url: https://docs.flutter.dev/learn/pathway/tutorial/http-requests
----

Learn the MVVM architecture pattern and how to build HTTP requests with async/await.

layers Understand the MVVM architecture pattern

cloud\_download Build HTTP requests with async/await

data\_object Handle errors and parse JSON responses

***

## Steps

1### Introductionkeyboard\_arrow\_up

The overarching pattern that this tutorial implements is called *Model-View-ViewModel* or *MVVM*. MVVM is an [architectural pattern](/app-architecture/guide) used in client apps that separates your app into three layers:

* **Model**: Handles data operations.
* **View**: Displays the UI.
* **ViewModel**: Manages state and connects the two.

The core tenet of MVVM (and many other patterns) is *separation of concerns*. Managing state in separate classes (outside your UI widgets) makes your code more testable, reusable, and easier to maintain.

A single feature in your app contains each one of the MVVM components. In this tutorial, in addition to Flutter widgets, you'll create `ArticleModel`, `ArticleViewModel`, and `ArticleView`.

2### Define the Modelkeyboard\_arrow\_up

The Model is the source-of-truth for your app's data and is responsible for low-level tasks such as making HTTP requests, caching data, or managing system resources such as used by a Flutter plugin. A model doesn't usually need to import Flutter libraries.

Create an empty `ArticleModel` class in your `main.dart` file:

dart

```
class ArticleModel {
  // Properties and methods will be added here.
}
```

3### Build the HTTP requestkeyboard\_arrow\_up

Wikipedia provides a REST API that returns JSON data about articles. For this app, you'll use the endpoint that returns a random article summary.

```
https://en.wikipedia.org/api/rest_v1/page/random/summary
```

Add a method to fetch a random Wikipedia article summary:

dart

```
class ArticleModel {
  Future<Summary> getRandomArticleSummary() async {
    final uri = Uri.https(
      'en.wikipedia.org',
      '/api/rest_v1/page/random/summary',
    );
    final response = await get(uri);
​
    // TODO: Add error handling and JSON parsing.
    throw UnimplementedError();
  }
}
```

Use the [`async` and `await`](https://dart.dev/language/async) keywords to handle asynchronous operations. The `async` keyword marks a method as asynchronous, and `await` waits for expressions that return a [`Future`](https://api.flutter.dev/flutter/dart-async/Future-class.html).

The `Uri.https` constructor safely builds URLs by handling encoding and formatting. This approach is more reliable than string concatenation, especially when dealing with special characters or query parameters.

4### Handle network errorskeyboard\_arrow\_up

Always handle errors when making HTTP requests. A status code of **200** indicates success, while other codes indicate errors. If the status code isn't **200**, the model throws an error for the UI to display to users.

dart

```
class ArticleModel {
  Future<Summary> getRandomArticleSummary() async {
    final uri = Uri.https(
      'en.wikipedia.org',
      '/api/rest_v1/page/random/summary',
    );
    final response = await get(uri);
​
    if (response.statusCode != 200) {
      throw const HttpException('Failed to update resource');
    }
​
    // TODO: Parse JSON and return Summary.
    throw UnimplementedError();
  }
}
```

5### Parse JSON from Wikipediakeyboard\_arrow\_up

The [Wikipedia API](https://en.wikipedia.org/api/rest_v1/) returns [JSON](https://dart.dev/tutorial/json) data that you decode into a `Summary` class Complete the `getRandomArticleSummary` method:

dart

```
class ArticleModel {
  Future<Summary> getRandomArticleSummary() async {
    final uri = Uri.https(
      'en.wikipedia.org',
      '/api/rest_v1/page/random/summary',
    );
    final response = await get(uri);
​
    if (response.statusCode != 200) {
      throw const HttpException('Failed to update resource');
    }
​
    return Summary.fromJson(jsonDecode(response.body) as Map<String, Object?>);
  }
}
```

The `Summary` class is defined in `summary.dart`. If you're unfamiliar with JSON parsing, check out the [Getting started with Dart](https://dart.dev/tutorial) tutorial.

6### Reviewkeyboard\_arrow\_up

layers Understood the MVVM architecture pattern keyboard\_arrow\_up

MVVM separates your app into Model (data operations), View (user interface), and ViewModel (state management). This separation of concerns makes your code more testable, reusable, and easier to maintain.

cloud\_download Built an HTTP request to fetch Wikipedia data keyboard\_arrow\_up

You created an `ArticleModel` class with a method that uses `async` and `await` to fetch data from the Wikipedia API. To safely build the URLs for the requests, you used the `Uri.https` constructor which handles encoding and special characters for you.

data\_object Handled errors and parsed JSON responses keyboard\_arrow\_up

You checked the HTTP status code to detect errors and used `jsonDecode` to parse the response body. Then to convert the raw JSON into a typed Dart object, you used the `Summary.fromJson` named constructor.

7### Test yourselfkeyboard\_arrow\_up

### HTTP Requests Quiz

1 / 2

**What do the `async` and `await` keywords do in Dart?**

1. They make code run on a separate thread.

   Not quite.

   Dart is single-threaded; async/await handles asynchronous operations without threads.

2. They mark a function as asynchronous and pause execution until a Future completes.

   That's right!

   The `async` keyword marks a function as asynchronous, and `await` pauses execution until the Future resolves.

3. They automatically cache the results of function calls.

   Not quite.

   Caching requires separate implementation; async/await is for handling asynchronous operations.

4. They convert synchronous code to run in the background.

   Not quite.

   They don't move code to the background; they manage asynchronous execution flow.

**Why is `Uri.https` preferred over string concatenation when building URLs in Dart?**

1. It makes the code shorter.

   Not quite.

   Code length isn't the main benefit; proper encoding is.

2. It safely handles encoding and formatting, especially for special characters and query parameters.

   That's right!

   Uri.https properly encodes special characters and formats URLs, preventing common errors.

3. It's required by the http package.

   Not quite.

   You can use strings, but Uri.https is safer and more reliable.

4. It automatically validates that the URL exists.

   Not quite.

   Uri.https builds the URL; it doesn't check if the endpoint exists.

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/learn/pathway/tutorial/http-requests.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/learn/pathway/tutorial/http-requests\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/learn/pathway/tutorial/http-requests.md "Report an issue with this page").

----
url: https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.32.0.md
----

\# DevTools 2.32.0 release notes > Release notes for Dart and Flutter DevTools version 2.32.0. The 2.32.0 release of the Dart and Flutter DevTools includes the following changes among other general improvements. To learn more about DevTools, check out the \[DevTools overview]\(https\://docs.flutter.dev/tools/devtools). ## General updates \* Improved overall usability by making the DevTools UI more dense. This significantly improves the user experience when using DevTools embedded in an IDE. - \[#7030]\(https\://github.com/flutter/devtools/pull/7030) \* Removed the "Dense mode" setting. - \[#7086]\(https\://github.com/flutter/devtools/pull/7086) \* Added support for filtering with regular expressions in the Logging, Network, and CPU profiler pages - \[#7027]\(https\://github.com/flutter/devtools/pull/7027) \* Add a DevTools server interaction for getting the DTD uri. - \[#7054]\(https\://github.com/flutter/devtools/pull/7054) ## Memory updates \* Supported allocation tracing for Flutter profile builds and Dart AOT compiled applications. - \[#7058]\(https\://github.com/flutter/devtools/pull/7058) \* Supported import of memory snapshots. - \[#6974]\(https\://github.com/flutter/devtools/pull/6974) ## Debugger updates \* Highlighted \`extension type\` as a declaration keyword, highlight the \`$\` in identifier interpolation as part of the interpolation, and properly highlight comments within type arguments. - \[#6837]\(https\://github.com/flutter/devtools/pull/6837) ## Logging updates \* Added toggle filters to filter out noisy Flutter and Dart logs - \[#7026]\(https\://github.com/flutter/devtools/pull/7026) !\[Logging view filters]\(/assets/images/docs/tools/devtools/release-notes/images-2.32.0/logging\_toggle\_filters.png "Toggle filters for logging screen") \* Added a scrollbar to the details pane. - \[#6917]\(https\://github.com/flutter/devtools/pull/6917) ## DevTools extension updates \* Added a description and documentation link to the \`devtools\_options.yaml\` file that is created in a user's project. - \[#7052]\(https\://github.com/flutter/devtools/pull/7052) \* Updated the Simulated DevTools Environment Panel to be collapsible (thanks to @victoreronmosele!) - \[#7062]\(https\://github.com/flutter/devtools/pull/7062) \* Integrated DevTools extensions with the new Dart Tooling Daemon. This will allow DevTools extensions to access public methods registered by other DTD clients, such as an IDE, as well as access a minimal file system API for interacting with the development project. - \[#7108]\(https\://github.com/flutter/devtools/pull/7108) ## VS Code sidebar updates \* Fixed an issue that prevented the VS code sidebar from loading in recent \`beta\` and \`main\` builds. - \[#6984]\(https\://github.com/flutter/devtools/pull/6984) \* Showed DevTools extensions as an option from the debug sessions DevTools dropdown, when available. \[#6709]\(https\://github.com/flutter/devtools/pull/6709) ## Full commit history To find a complete list of changes in this release, check out the \[DevTools git log]\(https\://github.com/flutter/devtools/tree/v2.32.0).

----
url: https://docs.flutter.dev/platform-integration/android/splash-screen
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Adding a splash screen to your Android app](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

## Overview

[#](#overview)

A splash screens (also known as a launch screen) provides a simple initial experience while your Android app loads. It sets the stage for your application, while allowing time for the app engine to load and your app to initialize.

You have a couple options for implementing a splash screen:

1. You can use one of the packages available on [pub.dev](https://pub.dev/packages?q=splash+screen).

2. You can implement it manually, as shown in the [splash screen sample app](https://github.com/flutter/samples/tree/main/android_splash_screen). The rest of this page assumes the manual approach.

## Initializing the app

[#](#initializing-the-app)

Every Android app requires initialization time while the operating system sets up the app's process. Android provides the concept of a [launch screen](https://developer.android.com/topic/performance/vitals/launch-time#themed) to display a `Drawable` while the app is initializing.

A `Drawable` is an Android graphic. To learn how to add a `Drawable` to your Flutter project in Android Studio, check out [Import drawables into your project](https://developer.android.com/studio/write/resource-manager#import)\[] in the Android developer documentation.

The default Flutter project template includes a definition of a launch theme and a launch background. You can customize this by editing `styles.xml`, where you can define a theme whose `windowBackground` is set to the `Drawable` that should be displayed as the launch screen.

xml

```
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
    <item name="android:windowBackground">@drawable/launch_background</item>
</style>
```

In addition, `styles.xml` defines a *normal theme* to be applied to `FlutterActivity` after the launch screen is gone. The normal theme background only shows for a very brief moment after the splash screen disappears, and during orientation change and `Activity` restoration. Therefore, it's recommended that the normal theme use a solid background color that looks similar to the primary background color of the Flutter UI.

xml

```
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
    <item name="android:windowBackground">@drawable/normal_background</item>
</style>
```

## Set up the FlutterActivity in AndroidManifest.xml

[#](#set-up-the-flutteractivity-in-androidmanifest-xml)

In `AndroidManifest.xml`, set the `theme` of `FlutterActivity` to the launch theme. Then, add a metadata element to the desired `FlutterActivity` to instruct Flutter to switch from the launch theme to the normal theme at the appropriate time.

xml

```
<activity
    android:name=".MyActivity"
    android:theme="@style/LaunchTheme"
    // ...
    >
    <meta-data
        android:name="io.flutter.embedding.android.NormalTheme"
        android:resource="@style/NormalTheme"
        />
    <intent-filter>
        <action android:name="android.intent.action.MAIN"/>
        <category android:name="android.intent.category.LAUNCHER"/>
    </intent-filter>
</activity>
```

The Android app now displays the desired launch screen while the app initializes.

## SplashScreen API

[#](#splashscreen-api)

Android 12 introduced the [`SplashScreen`](https://developer.android.com/reference/android/window/SplashScreen) API. Use the `SplashScreen` API in your `styles.xml`file. For example:

xml

```
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
    <item name="android:windowSplashScreenBackground">@color/bgColor</item>
    <item name="android:windowSplashScreenAnimatedIcon">@drawable/launch_background</item>
</style>
```

Some apps might want to continue showing the last frame of the Android launch screen in Flutter. For example, this preserves the illusion of a single frame while additional loading continues in Dart. To achieve this, the following Android APIs might be helpful:

* [Kotlin](#205-tab-panel)
* [Java](#206-tab-panel)

MainActivity.kt

kotlin

```
import android.os.Build
import android.os.Bundle
import androidx.core.view.WindowCompat
import io.flutter.embedding.android.FlutterActivity
​
class MainActivity : FlutterActivity() {
  override fun onCreate(savedInstanceState: Bundle?) {
    // Aligns the Flutter view vertically with the window.
    WindowCompat.setDecorFitsSystemWindows(getWindow(), false)
​
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
      // Disable the Android splash screen fade out animation to avoid
      // a flicker before the similar frame is drawn in Flutter.
      splashScreen.setOnExitAnimationListener { splashScreenView -> splashScreenView.remove() }
    }
​
    super.onCreate(savedInstanceState)
  }
}
```

MainActivity.java

java

```
import android.os.Build;
import android.os.Bundle;
import android.window.SplashScreenView;
import androidx.core.view.WindowCompat;
import io.flutter.embedding.android.FlutterActivity;
​
public class MainActivity extends FlutterActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        // Aligns the Flutter view vertically with the window.
        WindowCompat.setDecorFitsSystemWindows(getWindow(), false);
​
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
            // Disable the Android splash screen fade out animation to avoid
            // a flicker before the similar frame is drawn in Flutter.
            getSplashScreen()
                .setOnExitAnimationListener(
                    (SplashScreenView splashScreenView) -> {
                        splashScreenView.remove();
                    });
        }
​
        super.onCreate(savedInstanceState);
    }
}
```

Then, you can reimplement the first frame in Flutter that shows elements of your Android launch screen in the same positions on screen. For an example of this, check out the [splash screen sample app](https://github.com/flutter/samples/tree/main/android_splash_screen).

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-07-31. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/platform-integration/android/splash-screen.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/platform-integration/android/splash-screen\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/platform-integration/android/splash-screen.md "Report an issue with this page").

----
url: https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.40.2
----

The 2.40.2 release of the Dart and Flutter DevTools includes the following changes among other general improvements. To learn more about DevTools, check out the [DevTools overview](/tools/devtools/overview).

## General updates

[#](#general-updates)

* Add a setting that allows users to opt in to loading DevTools with WebAssembly. - [#8270](https://github.com/flutter/devtools/pull/8270)

* Removed the legacy Provider screen from DevTools. The `package:provider` tool is now distributed as a DevTools extension from `package:provider`. Upgrade your `package:provider` dependency to use the extension. - [#8364](https://github.com/flutter/devtools/pull/8364)

* Fixed a bug that was causing the DevTools release notes to always show. - [#8277](https://github.com/flutter/devtools/pull/8277)

* Added support for loading extensions in pub workspaces [8347](https://github.com/flutter/devtools/pull/8347).

* Mapped error stack traces to use the Dart source code locations so that they are human-readable. - [#8385](https://github.com/flutter/devtools/pull/8385)

* Added handling for IDE theme change events to update embedded DevTools UI. - [#8336](https://github.com/flutter/devtools/pull/8336)

* Fixed a bug that was causing data filters to be cleared when clearing data on the Network and Logging screens. - [#8407](https://github.com/flutter/devtools/pull/8407)

* Fixed a bug that was causing the navigator to lose state when opening the VM Flags dialog. - [#8413](https://github.com/flutter/devtools/pull/8413)

* Tables match IDE theme when embedded in an IDE. - [#8498](https://github.com/flutter/devtools/pull/8498)

## Inspector updates

[#](#inspector-updates)

* Added a setting to the Flutter Inspector controls that allows users to opt in to the newly redesigned Flutter Inspector. - [#8342](https://github.com/flutter/devtools/pull/8342)

## Performance updates

[#](#performance-updates)

* Fixed an issue with the "Refreshing timeline" overlay that was getting shown when it should not have been. - [#8318](https://github.com/flutter/devtools/pull/8318)

## Network profiler updates

[#](#network-profiler-updates)

* Resolved an issue in `.har` export where response content was sometimes missing in the data. - [#8333](https://github.com/flutter/devtools/pull/8333)

## Deep links tool updates

[#](#deep-links-tool-updates)

* Added support for validating iOS deep link settings. - [#8394](https://github.com/flutter/devtools/pull/8394)

## Full commit history

[#](#full-commit-history)

To find a complete list of changes in this release, check out the [DevTools git log](https://github.com/flutter/devtools/tree/v2.40.2).

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/release-notes/release-notes-2.40.2.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.40.2\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/release-notes/release-notes-2.40.2.md "Report an issue with this page").

----
url: https://docs.flutter.dev/learn/pathway/tutorial/devtools.md
----

\# DevTools > Learn to use the Dart DevTools when developing Flutter apps. Learn to use the widget inspector and property editor to debug layout issues and experiment with properties in real-time. title: What you'll accomplish items: - title: Explore your app's widget tree with the widget inspector icon: account\_tree - title: Learn to debug layout issues like unbounded constraints icon: bug\_report - title: Experiment with properties in real-time icon: tune --- ### Introduction As your Flutter app grows in complexity, it becomes more important to understand how each of the widget properties affects the UI. The \[Dart and Flutter DevTools]\[] provide you with two particularly useful features: the \*\*widget inspector\*\* and the \*\*property editor\*\*. First, launch DevTools by running the following commands while your app is running in debug mode. Run this command in a \*\*separate terminal window\*\* from where your app is running: \`\`\`console $ dart devtools \`\`\` Running this command starts the DevTools server and opens the interface in a browser. To connect DevTools to your running app: 1. Find the DevTools URL printed in the terminal where your app is running (for example: \`Serving DevTools at http\://127.0.0.1:9101\`). 2. Copy this URL. 3. Paste it into the connect bar in the DevTools browser page. :::note Run in your IDE Provided you have the appropriate Flutter plugin installed, you can also run DevTools directly inside Code OSS-based editors such as \[VS Code]\[] as well as \[IntelliJ and Android Studio]\[]. The screenshots in this lesson are from VS Code. ::: \[Dart and Flutter DevTools]: /tools/devtools \[VS Code]: /tools/vs-code \[IntelliJ and Android Studio]: /tools/android-studio ### The widget inspector The widget inspector allows you to visualize and explore your widget tree. It helps you understand the layout of your UI and identifies which widgets are responsible for different parts of the screen. Running against the app you've built so far, the inspector looks like this: Consider the \`GamePage\` widget you created in this section: \`\`\`dart class GamePage extends StatelessWidget { GamePage({super.key}); final Game \_game = Game(); @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.all(8.0), child: Column( spacing: 5.0, children: \[ for (var guess in \_game.guesses) Row( spacing: 5.0, children: \[ for (var letter in guess) Tile(letter.char, letter.type) ] ), ], ), ); } } \`\`\` And how it's used in \`MainApp\`: \`\`\`dart class MainApp extends StatelessWidget { const MainApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( body: Center(child: GamePage()), ), ); } } \`\`\` In the widget inspector, you should see a tree of exactly the same widgets that are in your code: \`MaterialApp\` as the root, with \`Scaffold\` as its \`home\`, an \`AppBar\` as its \`appBar\`, and so on down the entire tree to the \`Row\` widgets with \`Tile\` children. You can select any widget in the tree to see its properties and even jump to its source code in your IDE. ### Debugging layout issues The widget inspector is perhaps most useful for debugging layout issues. In certain situations, a widget's \[constraints]\[] are unbounded, or infinite. This means that either the maximum width or the maximum height is set to \[\`double.infinity\`]\[]. A widget that tries to be as big as possible won't function usefully when given an unbounded constraint and, in debug mode, throws an exception. The most common case where a render box ends up with an unbounded constraint is within a flex box widget (\[\`Row\`]\[] or \[\`Column\`]\[]), and within a scrollable region, such as a \[\`ListView\`]\[] or \[\`ScrollView\`]\[] subclasses. \`ListView\`, for example, tries to expand to fit the space available in its cross-direction. Such as if it's a vertically scrolling block that tries to be as wide as its parent. If you nest a vertically scrolling \`ListView\` inside a horizontally scrolling \`ListView\`, the inner list tries to be as wide as possible, which is infinitely wide, since the outer one is scrollable in that direction. Perhaps the most common error you'll run into while building a Flutter application is due to incorrectly using layout widgets. This error is referred to as the "unbounded constraints" error. Watch the following video to get an understanding of how to spot and resolve this issue. \[constraints]: /ui/layout/constraints \[\`double.infinity\`]: https\://api.flutter.dev/flutter/dart-core/double/infinity-constant.html \[\`Column\`]: https\://api.flutter.dev/flutter/widgets/Column-class.html \[\`Row\`]: https\://api.flutter.dev/flutter/widgets/Row-class.html \[\`ListView\`]: https\://api.flutter.dev/flutter/widgets/ListView-class.html \[\`ScrollView\`]: https\://api.flutter.dev/flutter/widgets/ScrollView-class.html ### The property editor When you select a widget in the widget inspector, the property editor displays all the properties of that selected widget. This is a powerful tool for understanding why a widget looks the way it does and for experimenting with property value changes in real-time. Look at the \`Tile\` widget's \`build\` method from earlier: \`\`\`dart class Tile extends StatelessWidget { const Tile(this.letter, this.hitType, {super.key}); final String letter; final HitType hitType; @override Widget build(BuildContext context) { return Container( width: 60, height: 60, decoration: BoxDecoration( border: Border.all(color: Colors.grey.shade300), color: switch (hitType) { HitType.hit => Colors.green, HitType.partial => Colors.yellow, HitType.miss => Colors.grey, \_ => Colors.white, }, ), ); } } \`\`\` If you select the \`Container\` widget inside the \`Tile\` in the widget inspector, the property editor shows its \`width\` (60), \`height\` (60), and the \`decoration\` property. You could then expand the \`BoxDecoration\` to see the \`border\` and \`color\` properties. For many properties, you can even modify their values directly within the property editor. For example, to quickly test how a different \`width\` or \`height\` would look for your \`Container\` in the \`Tile\` widget, change the numerical value in the property editor. The tool writes this update directly back to your \`.dart\` source file on disk, allowing you to instantly view the visual update on your running app upon saving or triggering a hot reload. This allows for rapid iteration on UI design. ### Review title: What you accomplished subtitle: Here's a summary of what you built and learned in this lesson. completed: true items: - title: Explored your app's widget tree with the widget inspector icon: account\_tree details: >- The widget inspector lets you visualize your entire widget tree, select any widget to view its properties, and jump directly to its source code. It's an essential tool for understanding your app's structure. - title: Learned about common layout issues icon: bug\_report details: >- You learned about \*\*unbounded constraints\*\*, one of the most common errors hit in Flutter development. This happens when widgets like \`Row\`, \`Column\`, or \`ListView\` receive infinite constraints. Now you can recognize and fix these issues when they occur. - title: Experimented with properties in real-time icon: tune details: >- The property editor shows all properties of a selected widget and lets you modify values directly on disk, allowing you to instantly view updates upon saving or triggering a hot reload. This enables rapid iteration when fine-tuning your UI. ### Test yourself - question: What is a common cause of "unbounded constraints" errors in Flutter? options: - text: Using too many StatefulWidgets in the widget tree. correct: false explanation: StatefulWidget usage doesn't cause unbounded constraints. - text: Placing a widget that tries to expand infinitely inside a scrollable or flex container without proper constraints. correct: true explanation: Widgets like ListView inside a Row, or nested scrollables, can receive infinite constraints and fail. - text: Forgetting to call setState after changing data. correct: false explanation: Missing setState causes UI not to update, not constraint errors. - text: Using Container without specifying a color. correct: false explanation: Color is optional and unrelated to layout constraints. - question: What can you do with the Widget Inspector in Flutter DevTools? options: - text: Automatically generate unit tests for your widgets. correct: false explanation: The Widget Inspector is for visualization and debugging, not test generation. - text: Visualize your widget tree, select widgets to view their properties, and jump to source code. correct: true explanation: The Widget Inspector lets you explore your app's structure, inspect widget properties, and navigate to the corresponding source code. - text: Deploy your app directly to the app store. correct: false explanation: Deployment is handled separately; the Widget Inspector is for debugging. - text: Edit your app's theme colors and typography. correct: false explanation: Theme editing requires code changes; the Widget Inspector is for inspecting the current state.

----
url: https://docs.flutter.dev/embedded.md
----

\# Embedded support for Flutter > Details of how Flutter supports the creation of embedded experiences. If you would like to embed Flutter engine into a car, a refrigerator, a thermostat... you CAN! For example, you might embed Flutter in the following situations: \* Using Flutter on an "embedded device", typically a low-powered hardware device such as a smart-display, a thermostat, or similar. \* Embedding Flutter into a new operating system or environment, for example a new mobile platform or a new operating system. The ability to embed Flutter, while stable, uses low-level API and is \_not\_ for beginners. In addition to the resources listed below, you might consider joining \[Discord]\[], where Flutter developers (including Google engineers) discuss various aspects of Flutter. The Flutter \[community]\[] page has info on more community resources. \* \[Custom Flutter Engine Embedders]\[], on the Flutter wiki. \* The doc comments in the \[Flutter engine \`embedder.h\` file]\[] on GitHub. \* The \[Flutter architectural overview]\[] on docs.flutter.dev. \* A small, self-contained \[Flutter Embedder Engine GLFW example]\[] in the Flutter engine GitHub repo. \* An exploration into \[embedding Flutter in a terminal]\[] by implementing Flutter's custom embedder API. \* \[Issue 31043]\[]: \_Questions for porting flutter engine to a new os\_ might also be helpful. \[community]: https\://flutter.dev/community \[Discord]: https\://discord.com/invite/N7Yshp4 \[Custom Flutter Engine Embedders]: https\://github.com/flutter/flutter/blob/main/docs/engine/Custom-Flutter-Engine-Embedders.md \[Flutter architectural overview]: /resources/architectural-overview \[Flutter engine \`embedder.h\` file]: https\://github.com/flutter/flutter/blob/main/engine/src/flutter/shell/platform/embedder/embedder.h \[Flutter Embedder Engine GLFW example]: https\://github.com/flutter/flutter/tree/main/engine/src/flutter/examples/glfw#flutter-embedder-engine-glfw-example \[embedding Flutter in a terminal]: https\://github.com/jiahaog/flt \[Issue 31043]: https\://github.com/flutter/flutter/issues/31043

----
url: https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.53.0
----

# DevTools 2.53.0 release notes

The 2.53.0 release of the Dart and Flutter DevTools includes the following changes among other general improvements. To learn more about DevTools, check out the [DevTools overview](/tools/devtools/overview).

## General updates

[#](#general-updates)

* Switched default compiler for DevTools to `dart2wasm`. - [#9530](https://github.com/flutter/devtools/pull/9530)

## Performance updates

[#](#performance-updates)

* Increased profile data limit from 64MB to 2GB, fixing issue where panel wouldn't load for large profiles. - [#9540](https://github.com/flutter/devtools/pull/9540)

## Advanced developer mode updates

[#](#advanced-developer-mode-updates)

* Fixed issue preventing CPU profiles from loading when "advanced developer mode" was enabled. - [#9528](https://github.com/flutter/devtools/pull/9528)

## Full commit history

[#](#full-commit-history)

To find a complete list of changes in this release, check out the [DevTools git log](https://github.com/flutter/devtools/tree/v2.53.0).

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/release-notes/release-notes-2.53.0.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.53.0\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/release-notes/release-notes-2.53.0.md "Report an issue with this page").

----
url: https://docs.flutter.dev/cookbook/navigation/navigate-with-arguments.md
----

\# Pass arguments to a named route > How to pass arguments to a named route.

<!--?code-excerpt path-base="cookbook/navigation/navigate_with_arguments"?-->

The \[\`Navigator\`]\[] provides the ability to navigate to a named route from any part of an app using 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. :::note Named routes are no longer recommended for most applications. For more information, see \[Limitations]\[] in the \[navigation overview]\[] page. ::: \[Limitations]: /ui/navigation#limitations \[navigation overview]: /ui/navigation You can accomplish this task using the \`arguments\` parameter of the \[\`Navigator.pushNamed()\`]\[] method. Extract the arguments using the \[\`ModalRoute.of()\`]\[] method or inside an \[\`onGenerateRoute()\`]\[] function provided to the \[\`MaterialApp\`]\[] or \[\`CupertinoApp\`]\[] constructor. This recipe demonstrates how to pass arguments to a named route and read the arguments using \`ModalRoute.of()\` and \`onGenerateRoute()\` using the following steps: 1. Define the arguments you need to pass. 2. Create a widget that extracts the arguments. 3. Register the widget in the \`routes\` table. 4. Navigate to the widget. ## 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\`. To pass both pieces of data, create a class that stores this information.

<!--?code-excerpt "lib/main.dart (ScreenArguments)"?-->

\`\`\`dart // 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; ScreenArguments(this.title, this.message); } \`\`\` ## 2. Create a widget that extracts the arguments Next, create a widget that extracts and displays the \`title\` and \`message\` from the \`ScreenArguments\`. To access the \`ScreenArguments\`, use the \[\`ModalRoute.of()\`]\[] method. This method returns the current route with the arguments.

<!--?code-excerpt "lib/main.dart (ExtractArgumentsScreen)"?-->

\`\`\`dart // A Widget that extracts the necessary arguments from // the ModalRoute. class ExtractArgumentsScreen extends StatelessWidget { const ExtractArgumentsScreen({super.key}); static const routeName = '/extractArguments'; @override Widget build(BuildContext context) { // Extract the arguments from the current ModalRoute // settings and cast them as ScreenArguments. final args = ModalRoute.of(context)!.settings.arguments as ScreenArguments; return Scaffold( appBar: AppBar(title: Text(args.title)), body: Center(child: Text(args.message)), ); } } \`\`\` ## 3. Register the widget in the \`routes\` table 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.

<!--?code-excerpt "lib/main.dart (routes)" plaster="none" replace="/return //g;/^\);$/)/g"?-->

\`\`\`dart MaterialApp( routes: { ExtractArgumentsScreen.routeName: (context) => const ExtractArgumentsScreen(), }, ) \`\`\` ## 4. Navigate to the widget Finally, navigate to the \`ExtractArgumentsScreen\` when a user taps a button using \[\`Navigator.pushNamed()\`]\[]. Provide the arguments to the route via the \`arguments\` property. The \`ExtractArgumentsScreen\` extracts the \`title\` and \`message\` from these arguments.

<!--?code-excerpt "lib/main.dart (PushNamed)"?-->

\`\`\`dart // A button that navigates to a named route. // The named route extracts the arguments // by itself. ElevatedButton( onPressed: () { // When the user taps the button, // navigate to a named route and // provide the arguments as an optional // parameter. Navigator.pushNamed( context, ExtractArgumentsScreen.routeName, arguments: ScreenArguments( 'Extract Arguments Screen', 'This message is extracted in the build method.', ), ); }, child: const Text('Navigate to screen that extracts arguments'), ), \`\`\` ## Alternatively, extract the arguments using \`onGenerateRoute\` Instead of extracting the arguments directly inside the widget, you can also extract the arguments inside an \[\`onGenerateRoute()\`]\[] function and pass them to a widget. 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 // Screen. onGenerateRoute: (settings) { // If you push the PassArguments route if (settings.name == PassArgumentsScreen.routeName) { // Cast the arguments to the correct // type: ScreenArguments. final args = settings.arguments as ScreenArguments; // Then, extract the required data from // the arguments and pass the data to the // correct screen. return MaterialPageRoute( builder: (context) { return PassArgumentsScreen( title: args.title, message: args.message, ); }, ); } // The code only supports // PassArgumentsScreen.routeName right now. // Other values need to be implemented if we // add them. The assertion here will help remind // us of that higher up in the call stack, since // this assertion would otherwise fire somewhere // in the framework. assert(false, 'Need to implement ${settings.name}'); return null; }, ) \`\`\` ## Interactive example

<!--?code-excerpt "lib/main.dart"?-->

\`\`\`dartpad title="Flutter complete navigation hands-on example in DartPad" run="true" import 'package:flutter/material.dart'; void main() => runApp(const MyApp()); class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( routes: { ExtractArgumentsScreen.routeName: (context) => const ExtractArgumentsScreen(), }, // Provide a function to handle named routes. // Use this function to identify the named // route being pushed, and create the correct // Screen. onGenerateRoute: (settings) { // If you push the PassArguments route if (settings.name == PassArgumentsScreen.routeName) { // Cast the arguments to the correct // type: ScreenArguments. final args = settings.arguments as ScreenArguments; // Then, extract the required data from // the arguments and pass the data to the // correct screen. return MaterialPageRoute( builder: (context) { return PassArgumentsScreen( title: args.title, message: args.message, ); }, ); } // The code only supports // PassArgumentsScreen.routeName right now. // Other values need to be implemented if we // add them. The assertion here will help remind // us of that higher up in the call stack, since // this assertion would otherwise fire somewhere // in the framework. assert(false, 'Need to implement ${settings.name}'); return null; }, title: 'Navigation with Arguments', home: const HomeScreen(), ); } } class HomeScreen extends StatelessWidget { const HomeScreen({super.key}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('Home Screen')), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: \[ // A button that navigates to a named route. // The named route extracts the arguments // by itself. ElevatedButton( onPressed: () { // When the user taps the button, // navigate to a named route and // provide the arguments as an optional // parameter. Navigator.pushNamed( context, ExtractArgumentsScreen.routeName, arguments: ScreenArguments( 'Extract Arguments Screen', 'This message is extracted in the build method.', ), ); }, child: const Text('Navigate to screen that extracts arguments'), ), // A button that navigates to a named route. // For this route, extract the arguments in // the onGenerateRoute function and pass them // to the screen. ElevatedButton( onPressed: () { // When the user taps the button, navigate // to a named route and provide the arguments // as an optional parameter. Navigator.pushNamed( context, PassArgumentsScreen.routeName, arguments: ScreenArguments( 'Accept Arguments Screen', 'This message is extracted in the onGenerateRoute ' 'function.', ), ); }, child: const Text('Navigate to a named that accepts arguments'), ), ], ), ), ); } } // A Widget that extracts the necessary arguments from // the ModalRoute. class ExtractArgumentsScreen extends StatelessWidget { const ExtractArgumentsScreen({super.key}); static const routeName = '/extractArguments'; @override Widget build(BuildContext context) { // Extract the arguments from the current ModalRoute // settings and cast them as ScreenArguments. final args = ModalRoute.of(context)!.settings.arguments as ScreenArguments; return Scaffold( appBar: AppBar(title: Text(args.title)), body: Center(child: Text(args.message)), ); } } // A Widget that accepts the necessary arguments via the // constructor. class PassArgumentsScreen extends StatelessWidget { static const routeName = '/passArguments'; final String title; final String message; // This Widget accepts the arguments as constructor // parameters. It does not extract the arguments from // the ModalRoute. // // The arguments are extracted by the onGenerateRoute // function provided to the MaterialApp widget. const PassArgumentsScreen({ super.key, required this.title, required this.message, }); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text(title)), body: Center(child: Text(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; ScreenArguments(this.title, this.message); } \`\`\` \[\`CupertinoApp\`]: https\://pub.dev/documentation/cupertino\_ui/latest/cupertino\_ui/CupertinoApp-class.html \[\`MaterialApp\`]: https\://pub.dev/documentation/material\_ui/latest/material\_ui/MaterialApp-class.html \[\`ModalRoute.of()\`]: https\://api.flutter.dev/flutter/widgets/ModalRoute/of.html \[\`Navigator\`]: https\://api.flutter.dev/flutter/widgets/Navigator-class.html \[\`Navigator.pushNamed()\`]: https\://api.flutter.dev/flutter/widgets/Navigator/pushNamed.html \[\`onGenerateRoute()\`]: https\://api.flutter.dev/flutter/widgets/WidgetsApp/onGenerateRoute.html \[\`RouteSettings\`]: https\://api.flutter.dev/flutter/widgets/RouteSettings-class.html

----
url: https://docs.flutter.dev/flutter-for/compose-devs.md
----

\# Flutter for Jetpack Compose developers > Learn how to apply Jetpack Compose developer knowledge when building Flutter apps.

<!--?code-excerpt path-base="get-started/flutter-for/compose_devs"?-->

:::note If you have experience building Android apps with Views (XML), check out \[Flutter for Android developers]\[]. ::: Flutter is a framework for building cross-platform applications that uses the Dart programming language. Your Jetpack Compose knowledge and experience are highly valuable when building with Flutter. :::tip To integrate Flutter code into an \*\*existing\*\* Android app, check out \[Add Flutter to existing app]\[]. ::: This document can be used as a reference by jumping around and finding questions that are most relevant to your needs. This guide embeds sample code. ## Overview Flutter and Jetpack Compose code describe how the UI looks and works. Developers call this type of code a \_declarative framework\_. While there are key differences especially when it comes to interacting with legacy Android code, there are many commonalities between the two frameworks. ### Composables vs. Widgets \*\*Jetpack Compose\*\* represents UI components as \_composable functions\_, later noted in this document as \_composables\_. Composables can be altered or decorated through the use of \_Modifier\_ objects. \`\`\` kotlin Text("Hello, World!", modifier: Modifier.padding(10.dp) ) Text("Hello, World!", modifier = Modifier.padding(10.dp)) \`\`\` \*\*Flutter\*\* represents UI components as \_widgets\_. Both composables and widgets only exist until they need to change. These languages call this property \_immutability\_. Jetpack Compose modifies UI component properties using an optional \_modifier\_ property backed by a \`Modifier\` object. By contrast, Flutter widgets configure their properties directly through constructor parameters. \`\`\`dart Padding( // <-- This is a Widget padding: EdgeInsets.all(10.0), // <-- a parameter to Padding child: Text("Hello, World!"), // <-- This is also a Widget ); \`\`\` To compose layouts, both Jetpack Compose and Flutter nest UI components within one another. Jetpack Compose nests \`Composables\` while Flutter nests \`Widgets\`. ### Layout process Jetpack Compose and Flutter handle layout in similar ways. Both of them lay out the UI in a single pass and parent elements provide layout constraints down to their children. More specifically, 1. The parent measures itself and its children recursively providing any constraints from the parent to the child. 2. The children try to size themselves using the above methods and provide their own children both their constraints and any that might apply from their ancestor nodes. 3. Upon encountering a leaf node (a node with no children), the size and properties are determined based on the provided constraints and the element is placed in the UI. 4. With all the children sized and placed, the root nodes can determine their measurement, size, and placement. In both Jetpack Compose and Flutter, the parent component can override or constrain the child's desired size. The widget cannot have any size it wants. It also cannot \_usually\_ know or decide its position on screen as its parent makes that decision. To force a child widget to render at a specific size, the parent must set tight constraints. A constraint becomes tight when its constraint's minimum size value equals its maximum size value. To learn how constraints work in Flutter, visit \[Understanding constraints]\[]. ### Design system Because Flutter targets multiple platforms, your app doesn't need to conform to any design system. While this guide features \[Material]\[] widgets, your Flutter app can use many different design systems: - Custom Material widgets - Community built widgets - Your own custom widgets If you're looking for a great reference app that features a custom design system, check out \[Wonderous]\[]. ## UI basics This section covers the basics of UI development in Flutter and how it compares to Jetpack Compose. This includes how to start developing your app, display static text, create buttons, react to on-press events, display lists, grids, and more. ### Getting started For \*\*Compose\*\* apps, your main entry point will be \_Activity\_ or one of its descendants, generally \_ComponentActivity\_. \`\`\`kotlin class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) enableEdgeToEdge() setContent { SampleTheme { Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding -> Greeting( name = "Android", modifier = Modifier.padding(innerPadding) ) } } } } } @Composable fun Greeting(name: String, modifier: Modifier = Modifier) { Text( text = "Hello $name!", modifier = modifier ) } \`\`\` To start your \*\*Flutter\*\* app, pass an instance of your app to the \`runApp\` function. \`\`\`dart void main() { runApp(const MyApp()); } \`\`\` \`App\` is a widget. It's \`build\` method describes the part of the user interface it represents. It's common to begin your app with a \[\`WidgetApp\`]\[] class, like \[\`MaterialApp\`]\[]. \`\`\`dart class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return const MaterialApp( home: HomePage(), ); } } \`\`\` The widget used in the \`HomePage\` might begin with the \`Scaffold\` class. \`Scaffold\` implements a basic layout structure for an app. \`\`\`dart class HomePage extends StatelessWidget { const HomePage({super.key}); @override Widget build(BuildContext context) { return const Scaffold( body: Center( child: Text( 'Hello, World!', ), ), ); } } \`\`\` Note how Flutter uses the \[\`Center\`]\[] widget. Compose has a number of defaults from its ancestor Android Views. Unless otherwise specified, most components "wrap" their size to content meaning they only take up as much space as needed when rendered. That's not always the case with Flutter. To center the text, wrap it in a \`Center\` widget. To learn about different widgets and their default behaviors, check out the \[Widget catalog]\[]. ### Adding Buttons In \*\*Compose\*\*, you use the \`Button\` composable or one of its variants to create a button. \`Button\` is an alias for \`FilledTonalButton\` when using a Material theme. \`\`\`kotlin Button(onClick = {}) { Text("Do something") } \`\`\` To achieve the same result in \*\*Flutter\*\*, use the \`FilledButton\` class: \`\`\`dart FilledButton( onPressed: () { // This closure is called when your button is tapped. }, const Text('Do something'), ), \`\`\` \*\*Flutter\*\* gives you access to a variety of buttons with pre-defined styles. ### Aligning components horizontally or vertically Jetpack Compose and Flutter handle horizontal and vertical collections of items similarly. The following Compose snippet adds a globe image and text in both \`Row\` and \`Column\` containers with centering of the items: \`\`\`kotlin Row(horizontalArrangement = Arrangement.Center) { Image(Icons.Default.Public, contentDescription = "") Text("Hello, world!") } Column(verticalArrangement = Arrangement.Center) { Image(Icons.Default.Public, contentDescription = "") Text("Hello, world!") } \`\`\` \*\*Flutter\*\* uses \[\`Row\`]\[] and \[\`Column\`]\[] as well but there are some slight differences for specifying child widgets and alignment. The following is equivalent to the Compose example. \`\`\`dart Row( mainAxisAlignment: MainAxisAlignment.center, children: \[ Icon(Icons.public), Text('Hello, world!'), ], ), Column( mainAxisAlignment: MainAxisAlignment.center, children: \[ Icon(MaterialIcons.globe), Text('Hello, world!'), ], ) \`\`\` \`Row\` and \`Column\` require a \`List\` in the \`children\` parameter. The \`mainAxisAlignment\` property tells Flutter how to position children with extra space. \`MainAxisAlignment.center\` positions children in the center of the main axis. For \`Row\`, the main axis is the horizontal axis, inversely for \`Column\`, the main axis is the vertical axis. ::: note Whereas Flutter's \`Row\` and \`Column\` have \`MainAxisAlignment\` and \`CrossAxisAlignment\` to control how items are placed, the properties that control placement in Jetpack Compose are one vertical and horizontal property from the following: \`verticalArrangement\`, \`verticalAlignment\`, \`horizontalAlignment\`, and \`horizontalArrangement\`. The trick to determine which is the \`MainAxis\` is to look for the property that ends in \`arrangement\`. The \`CrossAxis\` will be the property that ends in \`alignment\`. ::: ### Displaying a list view In \*\*Compose\*\*, you have a couple ways to create a list based on the size of the list you need to display. For a small number of items that can all be displayed at once, you can iterate over a collection inside a \`Column\` or \`Row\`. For a list with a large number of items, \`LazyList\` has better performance. It only lays out the components that will be visible versus all of them. \`\`\`kotlin data class Person(val name: String) val people = arrayOf( Person(name = "Person 1"), Person(name = "Person 2"), Person(name = "Person 3") ) @Composable fun ListDemo(people: List) { Column { people.forEach { Text(it.name) } } } @Composable fun ListDemo2(people: List) { LazyColumn { items(people) { person -> Text(person.name) } } } \`\`\` To lazily build a list in Flutter, .... \`\`\`dart class Person { String name; Person(this.name); } var items = \[ Person('Person 1'), Person('Person 2'), Person('Person 3'), ]; class HomePage extends StatelessWidget { const HomePage({super.key}); @override Widget build(BuildContext context) { return Scaffold( body: ListView\.builder( itemCount: items.length, itemBuilder: (context, index) { return ListTile( title: Text(items\[index].name), ); }, ), ); } } \`\`\` Flutter has some conventions for lists: - The \[\`ListView\`] widget has a builder method. This works like the \`item\` closure inside a Compose \`LazyList\`. - The \`itemCount\` parameter of the \`ListView\` sets how many items the \`ListView\` displays. - The \`itemBuilder\` has an index parameter that will be between zero and one less than itemCount. The previous example returned a \[\`ListTile\`]\[] widget for each item. The \`ListTile\` widget includes properties like \`height\` and \`font-size\`. These properties help build a list. However, Flutter allows you to return almost any widget that represents your data. ### Displaying a grid Constructing a grid in \*\*Compose\*\* is similar to a LazyList (\`LazyColumn\` or \`LazyRow\`). You can use the same \`items\` closure. There are properties on each grid type to specify how to arrange the items, whether or not to use adaptive or fixed layout, amongst others. \`\`\`kotlin val widgets = arrayOf( "Row 1", Icons.Filled.ArrowDownward, Icons.Filled.ArrowUpward, "Row 2", Icons.Filled.ArrowDownward, Icons.Filled.ArrowUpward ) LazyVerticalGrid ( columns = GridCells.Fixed(3), contentPadding = PaddingValues(8.dp) ) { items(widgets) { i -> if (i is String) { Text(i) } else { Image(i as ImageVector, "") } } } \`\`\` To display grids in \*\*Flutter\*\*, use the \[\`GridView\`] widget. This widget has various constructors. Each constructor has a similar goal, but uses different input parameters. The following example uses the \`.builder()\` initializer: \`\`\`dart const widgets = \[ Text('Row 1'), Icon(Icons.arrow\_downward), Icon(Icons.arrow\_upward), Text('Row 2'), Icon(Icons.arrow\_downward), Icon(Icons.arrow\_upward), ]; class HomePage extends StatelessWidget { const HomePage({super.key}); @override Widget build(BuildContext context) { return Scaffold( body: GridView\.builder( gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 3, mainAxisExtent: 40, ), itemCount: widgets.length, itemBuilder: (context, index) => widgets\[index], ), ); } } \`\`\` The \`SliverGridDelegateWithFixedCrossAxisCount\` delegate determines various parameters that the grid uses to lay out its components. This includes \`crossAxisCount\` that dictates the number of items displayed on each row. Jetpack Compose's \`LazyHorizontalGrid\`, \`LazyVerticalGrid\`, and Flutter's \`GridView\` are somewhat similar. \`GridView\` uses a delegate to decide how the grid should lay out its components. The \`rows\`, \`columns\`, and other associated properties on \`LazyHorizontalGrid\` \ \`LazyVerticalGrid\` serve the same purpose. ### Creating a scroll view \`LazyColumn\` and \`LazyRow\` in \*\*Jetpack Compose\*\* have built-in support for scrolling. To create a scrolling view, \*\*Flutter\*\* uses \[\`SingleChildScrollView\`]\[]. In the following example, the function \`mockPerson\` mocks instances of the \`Person\` class to create the custom \`PersonView\` widget. \`\`\`dart SingleChildScrollView( child: Column( children: mockPersons .map( (person) => PersonView( person: person, ), ) .toList(), ), ), \`\`\` ### Responsive and adaptive design Adaptive Design in \*\*Compose\*\* is a complex topic with many viable solutions: \* Using a custom layout \* Using \`WindowSizeClass\` alone \* Using \`BoxWithConstraints\` to control what is shown based on available space \* Using the Material 3 adaptive library that uses \`WindowSizeClass\` along with specialized composable layouts for common layouts For that reason, you are encouraged to look into the \*\*Flutter\*\* options directly and see what fits your requirements versus attempting to find something that is a one to one translation. To create relative views in \*\*Flutter\*\*, you can use one of two options: - Get the \`BoxConstraints\` object in the \[\`LayoutBuilder\`]\[] class. - Use the \[\`MediaQuery.of()\`]\[] in your build functions to get the size and orientation of your current app. To learn more, check out \[Creating responsive and adaptive apps]\[]. ### Managing state \*\*Compose\*\* stores state with the \`remember\` API and descendants of the \`MutableState\` interface. \`\`\`kotlin Scaffold( content = { padding -> var \_counter = remember { mutableIntStateOf(0) } Column(horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center, modifier = Modifier.fillMaxSize().padding(padding)) { Text(\_counter.value.toString()) Spacer(modifier = Modifier.height(16.dp)) FilledIconButton (onClick = { -> \_counter.intValue += 1 }) { Text("+") } } } ) \`\`\` \*\*Flutter\*\* manages local state using a \[\`StatefulWidget\`]\[]. Implement a stateful widget with the following two classes: - a subclass of \`StatefulWidget\` - a subclass of \`State\` The \`State\` object stores the widget's state. To change a widget's state, call \`setState()\` from the \`State\` subclass to tell the framework to redraw the widget. The following example shows a part of a counter app: \`\`\`dart class MyHomePage extends StatefulWidget { const MyHomePage({super.key}); @override State createState() => \_MyHomePageState(); } class \_MyHomePageState extends State { int \_counter = 0; @override Widget build(BuildContext context) { return Scaffold( body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: \[ Text('$\_counter'), TextButton( onPressed: () => setState(() { \_counter++; }), child: const Text('+'), ), ], ), ), ); } } \`\`\` To learn more ways to manage state, check out \[State management]\[]. ### Drawing on the Screen In \*\*Compose\*\*, you use the \`Canvas\` composable to draw shapes, images, and text to the screen. \*\*Flutter\*\* has an API based on the \`Canvas\` class, with two classes that help you draw: 1. \[\`CustomPaint\`]\[] that requires a painter: \`\`\`dart CustomPaint( painter: SignaturePainter(\_points), size: Size.infinite, ), \`\`\` 2. \[\`CustomPainter\`]\[] that implements your algorithm to draw to the canvas. \`\`\`dart class SignaturePainter extends CustomPainter { SignaturePainter(this.points); final List points; @override void paint(Canvas canvas, Size size) { final Paint paint = Paint() ..color = Colors.black ..strokeCap = StrokeCap.round ..strokeWidth = 5; for (int i = 0; i < points.length - 1; i++) { if (points\[i] != null && points\[i + 1] != null) { canvas.drawLine(points\[i]!, points\[i + 1]!, paint); } } } @override bool shouldRepaint(SignaturePainter oldDelegate) => oldDelegate.points != points; } \`\`\` ## Themes, styles, and media You can style Flutter apps with little effort. Styling includes switching between light and dark themes, changing the design of your text and UI components, and more. This section covers how to style your apps. ### Using dark mode In \*\*Compose\*\*, you can control light and dark at any arbitrary level by wrapping a component with a \`Theme\` composable. In \*\*Flutter\*\*, you can control light and dark mode at the app-level. To control the brightness mode, use the \`theme\` property of the \`App\` class: \`\`\`dart const MaterialApp( theme: ThemeData( brightness: Brightness.dark, ), home: HomePage(), ); \`\`\` ### Styling text In \*\*Compose\*\*, you use the properties on \`Text\` for one or two attributes or construct a \`TextStyle\` object to set many at once. \`\`\`kotlin Text("Hello, world!", color = Color.Green, fontWeight = FontWeight.Bold, fontSize = 30.sp) \`\`\` \`\`\`kotlin Text("Hello, world!", style = TextStyle( color = Color.Green, fontSize = 30.sp, fontWeight = FontWeight.Bold ), ) \`\`\` To style text in \*\*Flutter\*\*, add a \`TextStyle\` widget as the value of the \`style\` parameter of the \`Text\` widget. \`\`\`dart Text( 'Hello, world!', style: TextStyle( fontSize: 30, fontWeight: FontWeight.bold, color: Colors.blue, ), ), \`\`\` ### Styling buttons In \*\*Compose\*\*, you modify the colors of a button using the \`colors\` property. If left unmodified, they use the defaults from the current theme. \`\`\`kotlin Button(onClick = {}, colors = ButtonDefaults.buttonColors().copy( containerColor = Color.Yellow, contentColor = Color.Blue, )) { Text("Do something", fontSize = 30.sp, fontWeight = FontWeight.Bold) } \`\`\` To style button widgets in \*\*Flutter\*\*, you similarly set the style of its child, or modify properties on the button itself. \`\`\`dart FilledButton( onPressed: (){}, style: FilledButton.styleFrom(backgroundColor: Colors.amberAccent), child: const Text( 'Do something', style: TextStyle( color: Colors.blue, fontSize: 30, fontWeight: FontWeight.bold, ) ) ) \`\`\` ## Bundling assets for use in Flutter There is commonly a need to bundle resources for use in your application. They can be animations, vector graphics, images, fonts, or other general files. Unlike native Android apps that expect a set directory structure under \`/res//\` where the qualifier could be indicating the type of file, a specific orientation, or android version, Flutter doesn't require a specific location as long as the referenced files are listed in the \`pubspec.yaml\` file. Below is an excerpt from a \`pubspec.yaml\` referencing several images and a font file. \`\`\`yaml flutter: assets: - assets/my\_icon.png - assets/background.png fonts: - family: FiraSans fonts: - asset: fonts/FiraSans-Regular.ttf \`\`\` ### Using fonts In \*\*Compose\*\*, you have two options for using fonts in your app. You can use a runtime service I to retrieve them \[Google Fonts]\[]. Alternatively, they may be bundled in resource files. \*\*Flutter\*\* has similar methods to use fonts, let's discuss them both inline. ### Using bundled fonts The following are roughly equivalent Compose and Flutter code for using a font file in the \`/res/\` or \`fonts\` directory as listed above. \`\`\`kotlin // Font files bundled with app val firaSansFamily = FontFamily( Font(R.font.firasans\_regular, FontWeight.Normal), // ... ) // Usage Text(text = "Compose", fontFamily = firaSansFamily, fontWeight = FontWeight.Normal) \`\`\` \`\`\`dart Text( 'Flutter', style: TextStyle( fontSize: 40, fontFamily: 'FiraSans', ), ), \`\`\` ### Using a font provider (Google Fonts) One point of difference is using fonts from a font provider like Google Fonts. In \*\*Compose\*\*, the instantiation is done inline with the same approximate code to reference a local file. After instantiating a provider that references the special strings for the font service, you would use the same \`FontFamily\` declaration. \`\`\`kotlin // Font files bundled with app val provider = GoogleFont.Provider( providerAuthority = "com.google.android.gms.fonts", providerPackage = "com.google.android.gms", certificates = R.array.com\_google\_android\_gms\_fonts\_certs ) val firaSansFamily = FontFamily( Font( googleFont = GoogleFont("FiraSans"), fontProvider = provider, ) ) // Usage Text(text = "Compose", fontFamily = firaSansFamily, fontWeight = FontWeight.Light) \`\`\` For Flutter, this is provided by the \[google\_fonts]\[] plugin using the name of the font. \`\`\`dart import 'package:google\_fonts/google\_fonts.dart'; //... Text( 'Flutter', style: GoogleFonts.firaSans(), // or //style: GoogleFonts.getFont('FiraSans') ), \`\`\` ### Using images In \*\*Compose\*\*, typically image files to the drawable directory in resources \`/res/drawable\` and one uses \`Image\` composable to display the images. Assets are referenced by using the resource locator in the style of \`R.drawable.\` without the file extension. In \*\*Flutter\*\*, the resource location is a listed in \`pubspec.yaml\` as shown in the snippet below. \`\`\`yaml flutter: assets: - images/Blueberries.jpg \`\`\` After adding your image, you can display it using the \`Image\` widget's \`.asset()\` constructor. This constructor: To review a complete example, check out the \[\`Image\`]\[] docs. \[Flutter for Android developers]: /flutter-for/android-devs \[Add Flutter to existing app]: /add-to-app \[Material]: https\://m3.material.io/develop/flutter/ \[Platform adaptations]: /platform-integration/platform-adaptations \[widget catalog]: /ui/widgets/layout \[Understanding constraints]: /ui/layout/constraints \[\`WidgetApp\`]: https\://api.flutter.dev/flutter/widgets/WidgetsApp-class.html \[\`Center\`]: https\://api.flutter.dev/flutter/widgets/Center-class.html \[\`Row\`]: https\://api.flutter.dev/flutter/widgets/Row-class.html \[\`Column\`]: https\://api.flutter.dev/flutter/widgets/Column-class.html \[\`ListView\`]: https\://api.flutter.dev/flutter/widgets/ListView-class.html \[\`ListTile\`]: https\://api.flutter.dev/flutter/widgets/ListTitle-class.html \[\`GridView\`]: https\://api.flutter.dev/flutter/widgets/GridView-class.html \[\`SingleChildScrollView\`]: https\://api.flutter.dev/flutter/widgets/SingleChildScrollView-class.html \[\`LayoutBuilder\`]: https\://api.flutter.dev/flutter/widgets/LayoutBuilder-class.html \[\`AnimatedRotation\`]: https\://api.flutter.dev/flutter/widgets/AnimatedRotation-class.html \[\`TweenAnimationBuilder\`]: https\://api.flutter.dev/flutter/widgets/TweenAnimationBuilder-class.html \[\`RotationTransition\`]: https\://api.flutter.dev/flutter/widgets/RotationTransition-class.html \[\`Navigator\`]: https\://api.flutter.dev/flutter/widgets/Navigator-class.html \[\`StatefulWidget\`]: https\://api.flutter.dev/flutter/widgets/StatefulWidget-class.html \[State management]: /data-and-backend/state-mgmt \[Wonderous]: https\://flutter.gskinner.com/wonderous/?utm\_source=flutterdocs\&utm\_medium=docs \[video\_player]: https\://pub.dev/packages/video\_player \[video\_player example]: https\://pub.dev/packages/video\_player/example \[Creating responsive and adaptive apps]: /ui/adaptive-responsive \[\`MediaQuery.of()\`]: https\://api.flutter.dev/flutter/widgets/MediaQuery-class.html \[\`CustomPaint\`]: https\://api.flutter.dev/flutter/widgets/CustomPaint-class.html \[\`CustomPainter\`]: https\://api.flutter.dev/flutter/rendering/CustomPainter-class.html \[\`Image\`]: https\://api.flutter.dev/flutter/widgets/Image-class.html \[go\_router]: https\://pub.dev/packages/go\_router \[Google Fonts]: https\://fonts.google.com/ \[google\_fonts]: https\://pub.dev/packages/google\_fonts \[\`MaterialApp\`]: https\://pub.dev/documentation/material\_ui/latest/material\_ui/MaterialApp-class.html

----
url: https://docs.flutter.dev/flutter-for/declarative
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Introduction to declarative UI](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

*This introduction describes the conceptual difference between the declarative style used by Flutter, and the imperative style used by many other UI frameworks.*

## Why a declarative UI?

[#](#why-a-declarative-ui)

Frameworks from Win32 to web to Android and iOS typically use an imperative style of UI programming. This might be the style you're most familiar with—where you manually construct a full-functioned UI entity, such as a UIView or equivalent, and later mutate it using methods and setters when the UI changes.

In order to lighten the burden on developers from having to program how to transition between various UI states, Flutter, by contrast, lets the developer describe the current UI state and leaves the transitioning to the framework.

This, however, requires a slight shift in thinking for how to manipulate UI.

## How to change UI in a declarative framework

[#](#how-to-change-ui-in-a-declarative-framework)

Consider a simplified example below:

In the imperative style, you would typically go to ViewB's owner and retrieve the instance `b` using selectors or with `findViewById` or similar, and invoke mutations on it (and implicitly invalidate it). For example:

java

```
// Imperative style
b.setColor(red)
b.clearChildren()
ViewC c3 = new ViewC(...)
b.add(c3)
```

You might also need to replicate this configuration in the constructor of ViewB since the source of truth for the UI might outlive instance `b` itself.

In the declarative style, view configurations (such as Flutter's `Widget`s) are immutable and are only lightweight "blueprints". To change the UI, a widget triggers a rebuild on itself (most commonly by calling `setState` on `StatefulWidgets` in Flutter) and constructs a new `Widget` subtree.

dart

```
// Declarative style
return ViewB(color: red, child: const ViewC());
```

Here, rather than mutating an old instance `b` when the UI changes, Flutter constructs new `Widget` instances. The framework manages many of the responsibilities of a traditional UI object (such as maintaining the state of the layout) behind the scenes with [`RenderObjects`](/resources/glossary#renderobject)RenderObjectA persistent object in the Flutter framework responsible for layout, painting, and hit testing. [Learn more](/resources/glossary#renderobject "Learn more about 'RenderObject' and find related resources."). A `RenderObject` is a persistent, mutable object that handles the heavy lifting of layout, painting, and hit testing. `RenderObject`s persist between frames and Flutter's lightweight, immutable `Widget`s serve as blueprints that tell the framework to mutate the `RenderObject`s between states. The Flutter framework handles the rest.

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/flutter-for/declarative.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/flutter-for/declarative\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/flutter-for/declarative.md "Report an issue with this page").

----
url: https://docs.flutter.dev/add-to-app/android/plugin-setup.md
----

\# Manage plugins and dependencies in add-to-app > Learn how to use plugins and share your plugin's library dependencies with your existing app. This guide describes how to set up your project to consume plugins and how to manage your Gradle library dependencies between your existing Android app and your Flutter module's plugins. ## A. Simple scenario In the simple cases: \* Your Flutter module uses a plugin that has no additional Android Gradle dependency because it only uses Android OS APIs, such as the camera plugin. \* Your Flutter module uses a plugin that has an Android Gradle dependency, such as \[ExoPlayer from the video\_player plugin]\[], but your existing Android app didn't depend on ExoPlayer. There are no additional steps needed. Your add-to-app module will work the same way as a full-Flutter app. Whether you integrate using Android Studio, Gradle subproject or AARs, transitive Android Gradle libraries are automatically bundled as needed into your outer existing app. ## B. Plugins needing project edits Some plugins require you to make some edits to the Android side of your project. For example, the integration instructions for the \[firebase\_crashlytics]\[] plugin require manual edits to your Android wrapper project's \`build.gradle\` file. For full-Flutter apps, these edits are done in your Flutter project's \`/android/\` directory. In the case of a Flutter module, there are only Dart files in your module project. Perform those Android Gradle file edits on your outer, existing Android app rather than in your Flutter module. :::note Astute readers might notice that the Flutter module directory also contains an \`.android\` and an \`.ios\` directory. Those directories are Flutter-tool-generated and are only meant to bootstrap Flutter into generic Android or iOS libraries. They should not be edited or checked-in. This allows Flutter to improve the integration point should there be bugs or updates needed with new versions of Gradle, Android, Android Gradle Plugin, etc. For advanced users, if more modularity is needed and you must not leak knowledge of your Flutter module's dependencies into your outer host app, you can rewrap and repackage your Flutter module's Gradle library inside another native Android Gradle library that depends on the Flutter module's Gradle library. You can make your Android specific changes such as editing the AndroidManifest.xml, Gradle files or adding more Java files in that wrapper library. ::: ## C. Merging libraries The scenario that requires slightly more attention is if your existing Android application already depends on the same Android library that your Flutter module does (transitively via a plugin). For instance, your existing app's Gradle might already have: \`\`\`kotlin title="ExistingApp/app/build.gradle.kts" … dependencies { … implementation("com.crashlytics.sdk.android:crashlytics:2.10.1") … } … \`\`\` \`\`\`groovy title="ExistingApp/app/build.gradle" … dependencies { … implementation "com.crashlytics.sdk.android:crashlytics:2.10.1" … } … \`\`\` And your Flutter module also depends on \[firebase\_crashlytics]\[] via \`pubspec.yaml\`: \`\`\`yaml title="flutter\_module/pubspec.yaml" … dependencies: … firebase\_crashlytics: ^0.1.3 … … \`\`\` This plugin usage transitively adds a Gradle dependency again via firebase\_crashlytics v0.1.3's own \[Gradle file]\[]: \`\`\`groovy title="firebase\_crashlytics\_via\_pub/android/build.gradle … dependencies { … implementation "com.crashlytics.sdk.android:crashlytics:2.9.9" … } … \`\`\` The two \`com.crashlytics.sdk.android:crashlytics\` dependencies might not be the same version. In this example, the host app requested v2.10.1 and the Flutter module plugin requested v2.9.9. By default, Gradle v5 \[resolves dependency version conflicts]\[] by using the newest version of the library. This is generally ok as long as there are no API or implementation breaking changes between the versions. For example, you might use the new Crashlytics library in your existing app as follows: \`\`\`kotlin title="ExistingApp/app/build.gradle.kts" … dependencies { … implementation("com.crashlytics.sdk.android:crashlytics:2.10.1") … } … \`\`\` \`\`\`groovy title="ExistingApp/app/build.gradle" … dependencies { … implementation "com.google.firebase:firebase-crashlytics:17.0.0-beta03" … } … \`\`\` This approach won't work since there are major API differences between the Crashlytics' Gradle library version v17.0.0-beta03 and v2.9.9. For Gradle libraries that follow semantic versioning, you can generally avoid compilation and runtime errors by using the same major semantic version in your existing app and Flutter module plugin. \[ExoPlayer from the video\_player plugin]: https\://github.com/flutter/packages/blob/main/packages/video\_player/video\_player\_android/android/build.gradle \[firebase\_crashlytics]: https\://pub.dev/packages/firebase\_crashlytics \[Gradle file]: https\://github.com/firebase/flutterfire/blob/bdb95fcacf7cf077d162d2f267eee54a8b0be3bc/packages/firebase\_crashlytics/android/build.gradle#L40 \[resolves dependency version conflicts]: https\://docs.gradle.org/current/userguide/dependency\_resolution.html#sub:resolution-strategy

----
url: https://docs.flutter.dev/tos.md
----

\# Terms of Service > The terms of service for the Flutter website. The Flutter website (the "Website") is hosted by Google. By using and / or visiting the Website, you consent to be bound by Google's general \[Terms of Service]\[] and \[Privacy Policy]\[]. The "Flutter" name and the Flutter logo (the "Flutter Marks") are trademarks owned by Google and are not included within the assets licensed under the Creative Commons Attribution 4.0 International License. Google grants you a non-transferable, non-exclusive, royalty-free limited license to use the Flutter Marks subject to your compliance with the \[Flutter Brand Guidelines]\(/brand). Except as set forth above, nothing herein grants or should be deemed to grant to you any right, title or interest in or to the Flutter Marks. As noted above, Google owns the Flutter trademarks, but we license them so they can be used by the developer community to support the Flutter SDK, including through training materials and other community content. At the same time, it's important to make sure that people don't use the marks in ways that could cause confusion or otherwise misuse the marks, so we have prepared \[brand guidelines]\(/brand) that describe the allowed uses of the marks. Our goal in protecting the Flutter trademarks is to benefit the entire community by ensuring that the marks are only used in ways that are consistent with Google's mission to provide a free and open source SDK. \[Terms of Service]: https\://policies.google.com/terms \[Privacy Policy]: https\://policies.google.com/privacy

----
url: https://docs.flutter.dev/ui/layout/constraints.md
----

\# Understanding constraints > Flutter's model for widget constraints, sizing, positioning, and how they interact.

<!--?code-excerpt path-base="layout/constraints/"?-->

:::note If you are experiencing specific layout errors, you might check out \[Common Flutter errors]\[]. ::: \[Common Flutter errors]: /testing/common-errors When someone learning Flutter asks you why some widget with \`width: 100\` isn't 100 pixels wide, the default answer is to tell them to put that widget inside of a \`Center\`, right? \*\*Don't do that.\*\* If you do, they'll come back again and again, asking why some \`FittedBox\` isn't working, why that \`Column\` is overflowing, or what \`IntrinsicWidth\` is supposed to be doing. Instead, first tell them that Flutter layout is very different from HTML layout (which is probably where they're coming from), and then make them memorize the following rule:

**Constraints go down. Sizes go up. Parent sets position.**

Flutter layout can't really be understood without knowing this rule, so Flutter developers should learn it early on. In more detail: \* A widget gets its own \*\*constraints\*\* from its \*\*parent\*\*. A \_constraint\_ is just a set of 4 doubles: a minimum and maximum width, and a minimum and maximum height. \* Then the widget goes through its own list of \*\*children\*\*. One by one, the widget tells its children what their \*\*constraints\*\* are (which can be different for each child), and then asks each child what size it wants to be. \* Then, the widget positions its \*\*children\*\* (horizontally in the \`x\` axis, and vertically in the \`y\` axis), one by one. \* And, finally, the widget tells its parent about its own \*\*size\*\* (within the original constraints, of course). For example, if a composed widget contains a column with some padding, and wants to lay out its two children as follows: The negotiation goes something like this: \*\*Widget\*\*: "Hey parent, what are my constraints?" \*\*Parent\*\*: "You must be from \`0\` to \`300\` pixels wide, and \`0\` to \`85\` tall." \*\*Widget\*\*: "Hmmm, since I want to have \`5\` pixels of padding, then my children can have at most \`290\` pixels of width and \`75\` pixels of height." \*\*Widget\*\*: "Hey first child, You must be from \`0\` to \`290\` pixels wide, and \`0\` to \`75\` tall." \*\*First child\*\*: "OK, then I wish to be \`290\` pixels wide, and \`20\` pixels tall." \*\*Widget\*\*: "Hmmm, since I want to put my second child below the first one, this leaves only \`55\` pixels of height for my second child." \*\*Widget\*\*: "Hey second child, You must be from \`0\` to \`290\` wide, and \`0\` to \`55\` tall." \*\*Second child\*\*: "OK, I wish to be \`140\` pixels wide, and \`30\` pixels tall." \*\*Widget\*\*: "Very well. My first child has position \`x: 5\` and \`y: 5\`, and my second child has \`x: 80\` and \`y: 25\`." \*\*Widget\*\*: "Hey parent, I've decided that my size is going to be \`300\` pixels wide, and \`60\` pixels tall." ## Limitations Flutter's layout engine is designed to be a one-pass process. This means that Flutter lays out its widgets very efficiently, but does result in a few limitations: \* A widget can decide its own size only within the constraints given to it by its parent. This means a widget usually \*\*can't have any size it wants\*\*. \* A widget \*\*can't know and doesn't decide its own position in the screen\*\*, since it's the widget's parent who decides the position of the widget. \* Since the parent's size and position, in its turn, also depends on its own parent, it's impossible to precisely define the size and position of any widget without taking into consideration the tree as a whole. \* If a child wants a different size from its parent and the parent doesn't have enough information to align it, then the child's size might be ignored. \*\*Be specific when defining alignment.\*\* In Flutter, widgets are rendered by their underlying \[\`RenderBox\`]\[] objects. Many boxes in Flutter, especially those that just take a single child, pass their constraint on to their children. Generally, there are three kinds of boxes, in terms of how they handle their constraints: \* Those that try to be as big as possible. For example, the boxes used by \[\`Center\`]\[] and \[\`ListView\`]\[]. \* Those that try to be the same size as their children. For example, the boxes used by \[\`Transform\`]\[] and \[\`Opacity\`]\[]. \* Those that try to be a particular size. For example, the boxes used by \[\`Image\`]\[] and \[\`Text\`]\[]. Some widgets, for example \[\`Container\`]\[], vary from type to type based on their constructor arguments. The \[\`Container\`]\[] constructor defaults to trying to be as big as possible, but if you give it a \`width\`, for instance, it tries to honor that and be that particular size. Others, for example \[\`Row\`]\[] and \[\`Column\`]\[] (flex boxes) vary based on the constraints they are given, as described in the \[Flex]\(#flex) section. \[\`Center\`]: https\://api.flutter.dev/flutter/widgets/Center-class.html \[\`Column\`]: https\://api.flutter.dev/flutter/widgets/Column-class.html \[\`Container\`]: https\://api.flutter.dev/flutter/widgets/Container-class.html \[\`Image\`]: https\://api.flutter.dev/flutter/dart-ui/Image-class.html \[\`ListView\`]: https\://api.flutter.dev/flutter/widgets/ListView-class.html \[\`Opacity\`]: https\://api.flutter.dev/flutter/widgets/Opacity-class.html \[\`Row\`]: https\://api.flutter.dev/flutter/widgets/Row-class.html \[\`Text\`]: https\://api.flutter.dev/flutter/widgets/Text-class.html \[\`Transform\`]: https\://api.flutter.dev/flutter/widgets/Transform-class.html ## Examples For an interactive experience, use the following DartPad. Use the numbered horizontal scrolling bar to switch between 29 different examples.

<!--?code-excerpt "lib/main.dart"?-->

\`\`\`dartpad title="Constraints DartPad hands-on example" run="true" import 'package:flutter/material.dart'; void main() => runApp(const HomePage()); const Color red = Colors.red; const Color green = Colors.green; const Color blue = Colors.blue; const TextStyle big = TextStyle(fontSize: 30); ////////////////////////////////////////////////// class HomePage extends StatelessWidget { const HomePage({super.key}); @override Widget build(BuildContext context) { return const FlutterLayoutArticle(\[ Example1(), Example2(), Example3(), Example4(), Example5(), Example6(), Example7(), Example8(), Example9(), Example10(), Example11(), Example12(), Example13(), Example14(), Example15(), Example16(), Example17(), Example18(), Example19(), Example20(), Example21(), Example22(), Example23(), Example24(), Example25(), Example26(), Example27(), Example28(), Example29(), ]); } } ////////////////////////////////////////////////// abstract class Example extends StatelessWidget { const Example({super.key}); String get code; String get explanation; } ////////////////////////////////////////////////// class FlutterLayoutArticle extends StatefulWidget { const FlutterLayoutArticle(this.examples, {super.key}); final List examples; @override State createState() => \_FlutterLayoutArticleState(); } ////////////////////////////////////////////////// class \_FlutterLayoutArticleState extends State { late int count; late Widget example; late String code; late String explanation; @override void initState() { count = 1; code = const Example1().code; explanation = const Example1().explanation; super.initState(); } @override void didUpdateWidget(FlutterLayoutArticle oldWidget) { super.didUpdateWidget(oldWidget); var example = widget.examples\[count - 1]; code = example.code; explanation = example.explanation; } @override Widget build(BuildContext context) { return MaterialApp( debugShowCheckedModeBanner: false, title: 'Flutter Layout Article', home: SafeArea( child: Material( color: Colors.black, child: FittedBox( child: Container( width: 400, height: 670, color: const Color(0xFFCCCCCC), child: Column( crossAxisAlignment: CrossAxisAlignment.center, children: \[ Expanded( child: ConstrainedBox( constraints: const BoxConstraints.tightFor( width: double.infinity, height: double.infinity, ), child: widget.examples\[count - 1], ), ), Container( height: 50, width: double.infinity, color: Colors.black, child: SingleChildScrollView( scrollDirection: Axis.horizontal, child: Row( mainAxisSize: MainAxisSize.min, children: \[ for (int i = 0; i < widget.examples.length; i++) Container( width: 58, padding: const EdgeInsets.only(left: 4, right: 4), child: button(i + 1), ), ], ), ), ), Container( height: 273, color: Colors.grey\[50], child: Scrollbar( child: SingleChildScrollView( key: ValueKey(count), child: Padding( padding: const EdgeInsets.all(10), child: Column( children: \[ Center(child: Text(code)), const SizedBox(height: 15), Text( explanation, style: TextStyle( color: Colors.blue\[900], fontStyle: FontStyle.italic, ), ), ], ), ), ), ), ), ], ), ), ), ), ), ); } Widget button(int exampleNumber) { return Button( key: ValueKey('button$exampleNumber'), isSelected: count == exampleNumber, exampleNumber: exampleNumber, onPressed: () { showExample( exampleNumber, widget.examples\[exampleNumber - 1].code, widget.examples\[exampleNumber - 1].explanation, ); }, ); } void showExample(int exampleNumber, String code, String explanation) { setState(() { count = exampleNumber; this.code = code; this.explanation = explanation; }); } } ////////////////////////////////////////////////// class Button extends StatelessWidget { final bool isSelected; final int exampleNumber; final VoidCallback onPressed; const Button({ super.key, required this.isSelected, required this.exampleNumber, required this.onPressed, }); @override Widget build(BuildContext context) { return TextButton( style: TextButton.styleFrom( foregroundColor: Colors.white, backgroundColor: isSelected ? Colors.grey : Colors.grey\[800], ), child: Text(exampleNumber.toString()), onPressed: () { Scrollable.ensureVisible( context, duration: const Duration(milliseconds: 350), curve: Curves.easeOut, alignment: 0.5, ); onPressed(); }, ); } } ////////////////////////////////////////////////// class Example1 extends Example { const Example1({super.key}); @override final code = 'Container(color: red)'; @override final explanation = 'The screen is the parent of the Container, ' 'and it forces the Container to be exactly the same size as the screen.' '\n\n' 'So the Container fills the screen and paints it red.'; @override Widget build(BuildContext context) { return Container(color: red); } } ////////////////////////////////////////////////// class Example2 extends Example { const Example2({super.key}); @override final code = 'Container(width: 100, height: 100, color: red)'; @override final String explanation = 'The red Container wants to be 100x100, but it can\\'t, ' 'because the screen forces it to be exactly the same size as the screen.' '\n\n' 'So the Container fills the screen.'; @override Widget build(BuildContext context) { return Container(width: 100, height: 100, color: red); } } ////////////////////////////////////////////////// class Example3 extends Example { const Example3({super.key}); @override final code = 'Center(\n' ' child: Container(width: 100, height: 100, color: red))'; @override final String explanation = 'The screen forces the Center to be exactly the same size as the screen, ' 'so the Center fills the screen.' '\n\n' 'The Center tells the Container that it can be any size it wants, but not bigger than the screen.' 'Now the Container can indeed be 100x100.'; @override Widget build(BuildContext context) { return Center(child: Container(width: 100, height: 100, color: red)); } } ////////////////////////////////////////////////// class Example4 extends Example { const Example4({super.key}); @override final code = 'Align(\n' ' alignment: Alignment.bottomRight,\n' ' child: Container(width: 100, height: 100, color: red))'; @override final String explanation = 'This is different from the previous example in that it uses Align instead of Center.' '\n\n' 'Align also tells the Container that it can be any size it wants, but if there is empty space it won\\'t center the Container. ' 'Instead, it aligns the Container to the bottom-right of the available space.'; @override Widget build(BuildContext context) { return Align( alignment: Alignment.bottomRight, child: Container(width: 100, height: 100, color: red), ); } } ////////////////////////////////////////////////// class Example5 extends Example { const Example5({super.key}); @override final code = 'Center(\n' ' child: Container(\n' ' color: red,\n' ' width: double.infinity,\n' ' height: double.infinity))'; @override final String explanation = 'The screen forces the Center to be exactly the same size as the screen, ' 'so the Center fills the screen.' '\n\n' 'The Center tells the Container that it can be any size it wants, but not bigger than the screen.' 'The Container wants to be of infinite size, but since it can\\'t be bigger than the screen, it just fills the screen.'; @override Widget build(BuildContext context) { return Center( child: Container( width: double.infinity, height: double.infinity, color: red, ), ); } } ////////////////////////////////////////////////// class Example6 extends Example { const Example6({super.key}); @override final code = 'Center(child: Container(color: red))'; @override final String explanation = 'The screen forces the Center to be exactly the same size as the screen, ' 'so the Center fills the screen.' '\n\n' 'The Center tells the Container that it can be any size it wants, but not bigger than the screen.' '\n\n' 'Since the Container has no child and no fixed size, it decides it wants to be as big as possible, so it fills the whole screen.' '\n\n' 'But why does the Container decide that? ' 'Simply because that\\'s a design decision by those who created the Container widget. ' 'It could have been created differently, and you have to read the Container documentation to understand how it behaves, depending on the circumstances. '; @override Widget build(BuildContext context) { return Center(child: Container(color: red)); } } ////////////////////////////////////////////////// class Example7 extends Example { const Example7({super.key}); @override final code = 'Center(\n' ' child: Container(color: red\n' ' child: Container(color: green, width: 30, height: 30)))'; @override final String explanation = 'The screen forces the Center to be exactly the same size as the screen, ' 'so the Center fills the screen.' '\n\n' 'The Center tells the red Container that it can be any size it wants, but not bigger than the screen.' 'Since the red Container has no size but has a child, it decides it wants to be the same size as its child.' '\n\n' 'The red Container tells its child that it can be any size it wants, but not bigger than the screen.' '\n\n' 'The child is a green Container that wants to be 30x30.' '\n\n' 'Since the red \`Container\` has no size but has a child, it decides it wants to be the same size as its child. ' 'The red color isn\\'t visible, since the green Container entirely covers all of the red Container.'; @override Widget build(BuildContext context) { return Center( child: Container( color: red, child: Container(color: green, width: 30, height: 30), ), ); } } ////////////////////////////////////////////////// class Example8 extends Example { const Example8({super.key}); @override final code = 'Center(\n' ' child: Container(color: red\n' ' padding: const EdgeInsets.all(20),\n' ' child: Container(color: green, width: 30, height: 30)))'; @override final String explanation = 'The red Container sizes itself to its children size, but it takes its own padding into consideration. ' 'So it is also 30x30 plus padding. ' 'The red color is visible because of the padding, and the green Container has the same size as in the previous example.'; @override Widget build(BuildContext context) { return Center( child: Container( padding: const EdgeInsets.all(20), color: red, child: Container(color: green, width: 30, height: 30), ), ); } } ////////////////////////////////////////////////// class Example9 extends Example { const Example9({super.key}); @override final code = 'ConstrainedBox(\n' ' constraints: BoxConstraints(\n' ' minWidth: 70, minHeight: 70,\n' ' maxWidth: 150, maxHeight: 150),\n' ' child: Container(color: red, width: 10, height: 10)))'; @override final String explanation = 'You might guess that the Container has to be between 70 and 150 pixels, but you would be wrong. ' 'The ConstrainedBox only imposes ADDITIONAL constraints from those it receives from its parent.' '\n\n' 'Here, the screen forces the ConstrainedBox to be exactly the same size as the screen, ' 'so it tells its child Container to also assume the size of the screen, ' 'thus ignoring its \\'constraints\\' parameter.'; @override Widget build(BuildContext context) { return ConstrainedBox( constraints: const BoxConstraints( minWidth: 70, minHeight: 70, maxWidth: 150, maxHeight: 150, ), child: Container(color: red, width: 10, height: 10), ); } } ////////////////////////////////////////////////// class Example10 extends Example { const Example10({super.key}); @override final code = 'Center(\n' ' child: ConstrainedBox(\n' ' constraints: BoxConstraints(\n' ' minWidth: 70, minHeight: 70,\n' ' maxWidth: 150, maxHeight: 150),\n' ' child: Container(color: red, width: 10, height: 10))))'; @override final String explanation = 'Now, Center allows ConstrainedBox to be any size up to the screen size.' '\n\n' 'The ConstrainedBox imposes ADDITIONAL constraints from its \\'constraints\\' parameter onto its child.' '\n\n' 'The Container must be between 70 and 150 pixels. It wants to have 10 pixels, so it will end up having 70 (the MINIMUM).'; @override Widget build(BuildContext context) { return Center( child: ConstrainedBox( constraints: const BoxConstraints( minWidth: 70, minHeight: 70, maxWidth: 150, maxHeight: 150, ), child: Container(color: red, width: 10, height: 10), ), ); } } ////////////////////////////////////////////////// class Example11 extends Example { const Example11({super.key}); @override final code = 'Center(\n' ' child: ConstrainedBox(\n' ' constraints: BoxConstraints(\n' ' minWidth: 70, minHeight: 70,\n' ' maxWidth: 150, maxHeight: 150),\n' ' child: Container(color: red, width: 1000, height: 1000))))'; @override final String explanation = 'Center allows ConstrainedBox to be any size up to the screen size.' 'The ConstrainedBox imposes ADDITIONAL constraints from its \\'constraints\\' parameter onto its child' '\n\n' 'The Container must be between 70 and 150 pixels. It wants to have 1000 pixels, so it ends up having 150 (the MAXIMUM).'; @override Widget build(BuildContext context) { return Center( child: ConstrainedBox( constraints: const BoxConstraints( minWidth: 70, minHeight: 70, maxWidth: 150, maxHeight: 150, ), child: Container(color: red, width: 1000, height: 1000), ), ); } } ////////////////////////////////////////////////// class Example12 extends Example { const Example12({super.key}); @override final code = 'Center(\n' ' child: ConstrainedBox(\n' ' constraints: BoxConstraints(\n' ' minWidth: 70, minHeight: 70,\n' ' maxWidth: 150, maxHeight: 150),\n' ' child: Container(color: red, width: 100, height: 100))))'; @override final String explanation = 'Center allows ConstrainedBox to be any size up to the screen size.' 'ConstrainedBox imposes ADDITIONAL constraints from its \\'constraints\\' parameter onto its child.' '\n\n' 'The Container must be between 70 and 150 pixels. It wants to have 100 pixels, and that\\'s the size it has, since that\\'s between 70 and 150.'; @override Widget build(BuildContext context) { return Center( child: ConstrainedBox( constraints: const BoxConstraints( minWidth: 70, minHeight: 70, maxWidth: 150, maxHeight: 150, ), child: Container(color: red, width: 100, height: 100), ), ); } } ////////////////////////////////////////////////// class Example13 extends Example { const Example13({super.key}); @override final code = 'UnconstrainedBox(\n' ' child: Container(color: red, width: 20, height: 50));'; @override final String explanation = 'The screen forces the UnconstrainedBox to be exactly the same size as the screen.' 'However, the UnconstrainedBox lets its child Container be any size it wants.'; @override Widget build(BuildContext context) { return UnconstrainedBox( child: Container(color: red, width: 20, height: 50), ); } } ////////////////////////////////////////////////// class Example14 extends Example { const Example14({super.key}); @override final code = 'UnconstrainedBox(\n' ' child: Container(color: red, width: 4000, height: 50));'; @override final String explanation = 'The screen forces the UnconstrainedBox to be exactly the same size as the screen, ' 'and UnconstrainedBox lets its child Container be any size it wants.' '\n\n' 'Unfortunately, in this case the Container has 4000 pixels of width and is too big to fit in the UnconstrainedBox, ' 'so the UnconstrainedBox displays the much dreaded "overflow warning".'; @override Widget build(BuildContext context) { return UnconstrainedBox( child: Container(color: red, width: 4000, height: 50), ); } } ////////////////////////////////////////////////// class Example15 extends Example { const Example15({super.key}); @override final code = 'OverflowBox(\n' ' minWidth: 0,' ' minHeight: 0,' ' maxWidth: double.infinity,' ' maxHeight: double.infinity,' ' child: Container(color: red, width: 4000, height: 50));'; @override final String explanation = 'The screen forces the OverflowBox to be exactly the same size as the screen, ' 'and OverflowBox lets its child Container be any size it wants.' '\n\n' 'OverflowBox is similar to UnconstrainedBox, and the difference is that it won\\'t display any warnings if the child doesn\\'t fit the space.' '\n\n' 'In this case the Container is 4000 pixels wide, and is too big to fit in the OverflowBox, ' 'but the OverflowBox simply shows as much as it can, with no warnings given.'; @override Widget build(BuildContext context) { return OverflowBox( minWidth: 0, minHeight: 0, maxWidth: double.infinity, maxHeight: double.infinity, child: Container(color: red, width: 4000, height: 50), ); } } ////////////////////////////////////////////////// class Example16 extends Example { const Example16({super.key}); @override final code = 'UnconstrainedBox(\n' ' child: Container(color: Colors.red, width: double.infinity, height: 100));'; @override final String explanation = 'This won\\'t render anything, and you\\'ll see an error in the console.' '\n\n' 'The UnconstrainedBox lets its child be any size it wants, ' 'however its child is a Container with infinite size.' '\n\n' 'Flutter can\\'t render infinite sizes, so it throws an error with the following message: ' '"BoxConstraints forces an infinite width."'; @override Widget build(BuildContext context) { return UnconstrainedBox( child: Container(color: Colors.red, width: double.infinity, height: 100), ); } } ////////////////////////////////////////////////// class Example17 extends Example { const Example17({super.key}); @override final code = 'UnconstrainedBox(\n' ' child: LimitedBox(maxWidth: 100,\n' ' child: Container(color: Colors.red,\n' ' width: double.infinity, height: 100));'; @override final String explanation = 'Here you won\\'t get an error anymore, ' 'because when the LimitedBox is given an infinite size by the UnconstrainedBox, ' 'it passes a maximum width of 100 down to its child.' '\n\n' 'If you swap the UnconstrainedBox for a Center widget, ' 'the LimitedBox won\\'t apply its limit anymore (since its limit is only applied when it gets infinite constraints), ' 'and the width of the Container is allowed to grow past 100.' '\n\n' 'This explains the difference between a LimitedBox and a ConstrainedBox.'; @override Widget build(BuildContext context) { return UnconstrainedBox( child: LimitedBox( maxWidth: 100, child: Container( color: Colors.red, width: double.infinity, height: 100, ), ), ); } } ////////////////////////////////////////////////// class Example18 extends Example { const Example18({super.key}); @override final code = 'FittedBox(\n' ' child: Text(\\'Some Example Text.\\'));'; @override final String explanation = 'The screen forces the FittedBox to be exactly the same size as the screen.' 'The Text has some natural width (also called its intrinsic width) that depends on the amount of text, its font size, and so on.' '\n\n' 'The FittedBox lets the Text be any size it wants, ' 'but after the Text tells its size to the FittedBox, ' 'the FittedBox scales the Text until it fills all of the available width.'; @override Widget build(BuildContext context) { return const FittedBox(child: Text('Some Example Text.')); } } ////////////////////////////////////////////////// class Example19 extends Example { const Example19({super.key}); @override final code = 'Center(\n' ' child: FittedBox(\n' ' child: Text(\\'Some Example Text.\\')));'; @override final String explanation = 'But what happens if you put the FittedBox inside of a Center widget? ' 'The Center lets the FittedBox be any size it wants, up to the screen size.' '\n\n' 'The FittedBox then sizes itself to the Text, and lets the Text be any size it wants.' '\n\n' 'Since both FittedBox and the Text have the same size, no scaling happens.'; @override Widget build(BuildContext context) { return const Center(child: FittedBox(child: Text('Some Example Text.'))); } } //////////////////////////////////////////////////// class Example20 extends Example { const Example20({super.key}); @override final code = 'Center(\n' ' child: FittedBox(\n' ' child: Text(\\'…\\')));'; @override final String explanation = 'However, what happens if FittedBox is inside of a Center widget, but the Text is too large to fit the screen?' '\n\n' 'FittedBox tries to size itself to the Text, but it can\\'t be bigger than the screen. ' 'It then assumes the screen size, and resizes Text so that it fits the screen, too.'; @override Widget build(BuildContext context) { return const Center( child: FittedBox( child: Text( 'This is some very very very large text that is too big to fit a regular screen in a single line.', ), ), ); } } ////////////////////////////////////////////////// class Example21 extends Example { const Example21({super.key}); @override final code = 'Center(\n' ' child: Text(\\'…\\'));'; @override final String explanation = 'If, however, you remove the FittedBox, ' 'the Text gets its maximum width from the screen, ' 'and breaks the line so that it fits the screen.'; @override Widget build(BuildContext context) { return const Center( child: Text( 'This is some very very very large text that is too big to fit a regular screen in a single line.', ), ); } } ////////////////////////////////////////////////// class Example22 extends Example { const Example22({super.key}); @override final code = 'FittedBox(\n' ' child: Container(\n' ' height: 20, width: double.infinity));'; @override final String explanation = 'FittedBox can only scale a widget that is BOUNDED (has non-infinite width and height).' 'Otherwise, it won\\'t render anything, and you\\'ll see an error in the console.'; @override Widget build(BuildContext context) { return FittedBox( child: Container(height: 20, width: double.infinity, color: Colors.red), ); } } ////////////////////////////////////////////////// class Example23 extends Example { const Example23({super.key}); @override final code = 'Row(children:\[\n' ' Container(color: red, child: Text(\\'Hello!\\'))\n' ' Container(color: green, child: Text(\\'Goodbye!\\'))]'; @override final String explanation = 'The screen forces the Row to be exactly the same size as the screen.' '\n\n' 'Just like an UnconstrainedBox, the Row won\\'t impose any constraints onto its children, ' 'and instead lets them be any size they want.' '\n\n' 'The Row then puts them side-by-side, and any extra space remains empty.'; @override Widget build(BuildContext context) { return Row( children: \[ Container( color: red, child: const Text('Hello!', style: big), ), Container( color: green, child: const Text('Goodbye!', style: big), ), ], ); } } ////////////////////////////////////////////////// class Example24 extends Example { const Example24({super.key}); @override final code = 'Row(children:\[\n' ' Container(color: red, child: Text(\\'…\\'))\n' ' Container(color: green, child: Text(\\'Goodbye!\\'))]'; @override final String explanation = 'Since the Row won\\'t impose any constraints onto its children, ' 'it\\'s quite possible that the children might be too big to fit the available width of the Row.' 'In this case, just like an UnconstrainedBox, the Row displays the "overflow warning".'; @override Widget build(BuildContext context) { return Row( children: \[ Container( color: red, child: const Text( 'This is a very long text that ' 'won\\'t fit the line.', style: big, ), ), Container( color: green, child: const Text('Goodbye!', style: big), ), ], ); } } ////////////////////////////////////////////////// class Example25 extends Example { const Example25({super.key}); @override final code = 'Row(children:\[\n' ' Expanded(\n' ' child: Container(color: red, child: Text(\\'…\\')))\n' ' Container(color: green, child: Text(\\'Goodbye!\\'))]'; @override final String explanation = 'When a Row\\'s child is wrapped in an Expanded widget, the Row won\\'t let this child define its own width anymore.' '\n\n' 'Instead, it defines the Expanded width according to the other children, and only then the Expanded widget forces the original child to have the Expanded\\'s width.' '\n\n' 'In other words, once you use Expanded, the original child\\'s width becomes irrelevant, and is ignored.'; @override Widget build(BuildContext context) { return Row( children: \[ Expanded( child: Center( child: Container( color: red, child: const Text( 'This is a very long text that won\\'t fit the line.', style: big, ), ), ), ), Container( color: green, child: const Text('Goodbye!', style: big), ), ], ); } } ////////////////////////////////////////////////// class Example26 extends Example { const Example26({super.key}); @override final code = 'Row(children:\[\n' ' Expanded(\n' ' child: Container(color: red, child: Text(\\'…\\')))\n' ' Expanded(\n' ' child: Container(color: green, child: Text(\\'Goodbye!\\'))]'; @override final String explanation = 'If all of Row\\'s children are wrapped in Expanded widgets, each Expanded has a size proportional to its flex parameter, ' 'and only then each Expanded widget forces its child to have the Expanded\\'s width.' '\n\n' 'In other words, Expanded ignores the preferred width of its children.'; @override Widget build(BuildContext context) { return Row( children: \[ Expanded( child: Container( color: red, child: const Text( 'This is a very long text that won\\'t fit the line.', style: big, ), ), ), Expanded( child: Container( color: green, child: const Text('Goodbye!', style: big), ), ), ], ); } } ////////////////////////////////////////////////// class Example27 extends Example { const Example27({super.key}); @override final code = 'Row(children:\[\n' ' Flexible(\n' ' child: Container(color: red, child: Text(\\'…\\')))\n' ' Flexible(\n' ' child: Container(color: green, child: Text(\\'Goodbye!\\'))]'; @override final String explanation = 'The only difference if you use Flexible instead of Expanded, ' 'is that Flexible lets its child be SMALLER than the Flexible width, ' 'while Expanded forces its child to have the same width of the Expanded.' '\n\n' 'But both Expanded and Flexible ignore their children\\'s width when sizing themselves.' '\n\n' 'This means that it\\'s IMPOSSIBLE to expand Row children proportionally to their sizes. ' 'The Row either uses the exact child\\'s width, or ignores it completely when you use Expanded or Flexible.'; @override Widget build(BuildContext context) { return Row( children: \[ Flexible( child: Container( color: red, child: const Text( 'This is a very long text that won\\'t fit the line.', style: big, ), ), ), Flexible( child: Container( color: green, child: const Text('Goodbye!', style: big), ), ), ], ); } } ////////////////////////////////////////////////// class Example28 extends Example { const Example28({super.key}); @override final code = 'Scaffold(\n' ' body: Container(color: blue,\n' ' child: Column(\n' ' children: \[\n' ' Text(\\'Hello!\\'),\n' ' Text(\\'Goodbye!\\')])))'; @override final String explanation = 'The screen forces the Scaffold to be exactly the same size as the screen, ' 'so the Scaffold fills the screen.' '\n\n' 'The Scaffold tells the Container that it can be any size it wants, but not bigger than the screen.' '\n\n' 'When a widget tells its child that it can be smaller than a certain size, ' 'we say the widget supplies "loose" constraints to its child. More on that later.'; @override Widget build(BuildContext context) { return Scaffold( body: Container( color: blue, child: const Column(children: \[Text('Hello!'), Text('Goodbye!')]), ), ); } } ////////////////////////////////////////////////// class Example29 extends Example { const Example29({super.key}); @override final code = 'Scaffold(\n' ' body: Container(color: blue,\n' ' child: SizedBox.expand(\n' ' child: Column(\n' ' children: \[\n' ' Text(\\'Hello!\\'),\n' ' Text(\\'Goodbye!\\')]))))'; @override final String explanation = 'If you want the Scaffold\\'s child to be exactly the same size as the Scaffold itself, ' 'you can wrap its child with SizedBox.expand.' '\n\n' 'When a widget tells its child that it must be of a certain size, ' 'we say the widget supplies "tight" constraints to its child. More on that later.'; @override Widget build(BuildContext context) { return Scaffold( body: SizedBox.expand( child: Container( color: blue, child: const Column(children: \[Text('Hello!'), Text('Goodbye!')]), ), ), ); } } ////////////////////////////////////////////////// \`\`\` If you prefer, you can grab the code from \[this GitHub repo]\[]. The examples are explained in the following sections. \[this GitHub repo]: https\://github.com/marcglasberg/flutter\_layout\_article ### Example 1

<!--?code-excerpt "lib/main.dart (Example1)" replace="/(return |;)//g"?-->

\`\`\`dart Container(color: red) \`\`\` The screen is the parent of the \`Container\`, and it forces the \`Container\` to be exactly the same size as the screen. So the \`Container\` fills the screen and paints it red. ### Example 2

<!--?code-excerpt "lib/main.dart (Example2)" replace="/(return |;)//g"?-->

\`\`\`dart Container(width: 100, height: 100, color: red) \`\`\` The red \`Container\` wants to be 100 × 100, but it can't, because the screen forces it to be exactly the same size as the screen. So the \`Container\` fills the screen. ### Example 3

<!--?code-excerpt "lib/main.dart (Example3)" replace="/(return |;)//g"?-->

\`\`\`dart Center(child: Container(width: 100, height: 100, color: red)) \`\`\` The screen forces the \`Center\` to be exactly the same size as the screen, so the \`Center\` fills the screen. The \`Center\` tells the \`Container\` that it can be any size it wants, but not bigger than the screen. Now the \`Container\` can indeed be 100 × 100. ### Example 4

<!--?code-excerpt "lib/main.dart (Example4)" replace="/(return |;)//g"?-->

\`\`\`dart Align( alignment: Alignment.bottomRight, child: Container(width: 100, height: 100, color: red), ) \`\`\` This is different from the previous example in that it uses \`Align\` instead of \`Center\`. \`Align\` also tells the \`Container\` that it can be any size it wants, but if there is empty space it won't center the \`Container\`. Instead, it aligns the container to the bottom-right of the available space. ### Example 5

<!--?code-excerpt "lib/main.dart (Example5)" replace="/(return |;)//g"?-->

\`\`\`dart Center( child: Container( width: double.infinity, height: double.infinity, color: red, ), ) \`\`\` The screen forces the \`Center\` to be exactly the same size as the screen, so the \`Center\` fills the screen. The \`Center\` tells the \`Container\` that it can be any size it wants, but not bigger than the screen. The \`Container\` wants to be of infinite size, but since it can't be bigger than the screen, it just fills the screen. ### Example 6

<!--?code-excerpt "lib/main.dart (Example6)" replace="/(return |;)//g"?-->

\`\`\`dart Center(child: Container(color: red)) \`\`\` The screen forces the \`Center\` to be exactly the same size as the screen, so the \`Center\` fills the screen. The \`Center\` tells the \`Container\` that it can be any size it wants, but not bigger than the screen. Since the \`Container\` has no child and no fixed size, it decides it wants to be as big as possible, so it fills the whole screen. But why does the \`Container\` decide that? Simply because that's a design decision by those who created the \`Container\` widget. It could have been created differently, and you have to read the \[\`Container\`]\[] API documentation to understand how it behaves, depending on the circumstances. ### Example 7

<!--?code-excerpt "lib/main.dart (Example7)" replace="/(return |;)//g"?-->

\`\`\`dart Center( child: Container( color: red, child: Container(color: green, width: 30, height: 30), ), ) \`\`\` The screen forces the \`Center\` to be exactly the same size as the screen, so the \`Center\` fills the screen. The \`Center\` tells the red \`Container\` that it can be any size it wants, but not bigger than the screen. Since the red \`Container\` has no size but has a child, it decides it wants to be the same size as its child. The red \`Container\` tells its child that it can be any size it wants, but not bigger than the screen. The child is a green \`Container\` that wants to be 30 × 30. Given that the red \`Container\` sizes itself to the size of its child, it is also 30 × 30. The red color isn't visible because the green \`Container\` entirely covers the red \`Container\`. ### Example 8

<!--?code-excerpt "lib/main.dart (Example8)" replace="/(return |;)//g"?-->

\`\`\`dart Center( child: Container( padding: const EdgeInsets.all(20), color: red, child: Container(color: green, width: 30, height: 30), ), ) \`\`\` The red \`Container\` sizes itself to its children's size, but it takes its own padding into consideration. So it is also 30 × 30 plus padding. The red color is visible because of the padding, and the green \`Container\` has the same size as in the previous example. ### Example 9

<!--?code-excerpt "lib/main.dart (Example9)" replace="/(return |;)//g"?-->

\`\`\`dart ConstrainedBox( constraints: const BoxConstraints( minWidth: 70, minHeight: 70, maxWidth: 150, maxHeight: 150, ), child: Container(color: red, width: 10, height: 10), ) \`\`\` You might guess that the \`Container\` has to be between 70 and 150 pixels, but you would be wrong. The \`ConstrainedBox\` only imposes \*\*additional\*\* constraints from those it receives from its parent. Here, the screen forces the \`ConstrainedBox\` to be exactly the same size as the screen, so it tells its child \`Container\` to also assume the size of the screen, thus ignoring its \`constraints\` parameter. ### Example 10

<!--?code-excerpt "lib/main.dart (Example10)" replace="/(return |;)//g"?-->

\`\`\`dart Center( child: ConstrainedBox( constraints: const BoxConstraints( minWidth: 70, minHeight: 70, maxWidth: 150, maxHeight: 150, ), child: Container(color: red, width: 10, height: 10), ), ) \`\`\` Now, \`Center\` allows \`ConstrainedBox\` to be any size up to the screen size. The \`ConstrainedBox\` imposes \*\*additional\*\* constraints from its \`constraints\` parameter onto its child. The Container must be between 70 and 150 pixels. It wants to have 10 pixels, so it ends up having 70 (the minimum). ### Example 11

<!--?code-excerpt "lib/main.dart (Example11)" replace="/(return |;)//g"?-->

\`\`\`dart Center( child: ConstrainedBox( constraints: const BoxConstraints( minWidth: 70, minHeight: 70, maxWidth: 150, maxHeight: 150, ), child: Container(color: red, width: 1000, height: 1000), ), ) \`\`\` \`Center\` allows \`ConstrainedBox\` to be any size up to the screen size. The \`ConstrainedBox\` imposes \*\*additional\*\* constraints from its \`constraints\` parameter onto its child. The \`Container\` must be between 70 and 150 pixels. It wants to have 1000 pixels, so it ends up having 150 (the maximum). ### Example 12

<!--?code-excerpt "lib/main.dart (Example12)" replace="/(return |;)//g"?-->

\`\`\`dart Center( child: ConstrainedBox( constraints: const BoxConstraints( minWidth: 70, minHeight: 70, maxWidth: 150, maxHeight: 150, ), child: Container(color: red, width: 100, height: 100), ), ) \`\`\` \`Center\` allows \`ConstrainedBox\` to be any size up to the screen size. The \`ConstrainedBox\` imposes \*\*additional\*\* constraints from its \`constraints\` parameter onto its child. The \`Container\` must be between 70 and 150 pixels. It wants to have 100 pixels, and that's the size it has, since that's between 70 and 150. ### Example 13

<!--?code-excerpt "lib/main.dart (Example13)" replace="/(return |;)//g"?-->

\`\`\`dart UnconstrainedBox( child: Container(color: red, width: 20, height: 50), ) \`\`\` The screen forces the \`UnconstrainedBox\` to be exactly the same size as the screen. However, the \`UnconstrainedBox\` lets its child \`Container\` be any size it wants. ### Example 14

<!--?code-excerpt "lib/main.dart (Example14)" replace="/(return |;)//g"?-->

\`\`\`dart UnconstrainedBox( child: Container(color: red, width: 4000, height: 50), ) \`\`\` The screen forces the \`UnconstrainedBox\` to be exactly the same size as the screen, and \`UnconstrainedBox\` lets its child \`Container\` be any size it wants. Unfortunately, in this case the \`Container\` is 4000 pixels wide and is too big to fit in the \`UnconstrainedBox\`, so the \`UnconstrainedBox\` displays the much dreaded "overflow warning". ### Example 15

<!--?code-excerpt "lib/main.dart (Example15)" replace="/(return |;)//g"?-->

\`\`\`dart OverflowBox( minWidth: 0, minHeight: 0, maxWidth: double.infinity, maxHeight: double.infinity, child: Container(color: red, width: 4000, height: 50), ) \`\`\` The screen forces the \`OverflowBox\` to be exactly the same size as the screen, and \`OverflowBox\` lets its child \`Container\` be any size it wants. \`OverflowBox\` is similar to \`UnconstrainedBox\`; the difference is that it won't display any warnings if the child doesn't fit the space. In this case, the \`Container\` has 4000 pixels of width, and is too big to fit in the \`OverflowBox\`, but the \`OverflowBox\` simply shows as much as it can, with no warnings given. ### Example 16

<!--?code-excerpt "lib/main.dart (Example16)" replace="/(return |;)//g"?-->

\`\`\`dart UnconstrainedBox( child: Container(color: Colors.red, width: double.infinity, height: 100), ) \`\`\` This won't render anything, and you'll see an error in the console. The \`UnconstrainedBox\` lets its child be any size it wants, however its child is a \`Container\` with infinite size. Flutter can't render infinite sizes, so it throws an error with the following message: \`BoxConstraints forces an infinite width.\` ### Example 17

<!--?code-excerpt "lib/main.dart (Example17)" replace="/(return |;)//g"?-->

\`\`\`dart UnconstrainedBox( child: LimitedBox( maxWidth: 100, child: Container( color: Colors.red, width: double.infinity, height: 100, ), ), ) \`\`\` Here you won't get an error anymore, because when the \`LimitedBox\` is given an infinite size by the \`UnconstrainedBox\`; it passes a maximum width of 100 down to its child. If you swap the \`UnconstrainedBox\` for a \`Center\` widget, the \`LimitedBox\` won't apply its limit anymore (since its limit is only applied when it gets infinite constraints), and the width of the \`Container\` is allowed to grow past 100. This explains the difference between a \`LimitedBox\` and a \`ConstrainedBox\`. ### Example 18

<!--?code-excerpt "lib/main.dart (Example18)" replace="/(return |;)//g"?-->

\`\`\`dart const FittedBox(child: Text('Some Example Text.')) \`\`\` The screen forces the \`FittedBox\` to be exactly the same size as the screen. The \`Text\` has some natural width (also called its intrinsic width) that depends on the amount of text, its font size, and so on. The \`FittedBox\` lets the \`Text\` be any size it wants, but after the \`Text\` tells its size to the \`FittedBox\`, the \`FittedBox\` scales the Text until it fills all of the available width. ### Example 19

<!--?code-excerpt "lib/main.dart (Example19)" replace="/(return |;)//g"?-->

\`\`\`dart const Center(child: FittedBox(child: Text('Some Example Text.'))) \`\`\` But what happens if you put the \`FittedBox\` inside of a \`Center\` widget? The \`Center\` lets the \`FittedBox\` be any size it wants, up to the screen size. The \`FittedBox\` then sizes itself to the \`Text\`, and lets the \`Text\` be any size it wants. Since both \`FittedBox\` and the \`Text\` have the same size, no scaling happens. ### Example 20

<!--?code-excerpt "lib/main.dart (Example20)" replace="/(return |;)//g"?-->

\`\`\`dart const Center( child: FittedBox( child: Text( 'This is some very very very large text that is too big to fit a regular screen in a single line.', ), ), ) \`\`\` However, what happens if \`FittedBox\` is inside of a \`Center\` widget, but the \`Text\` is too large to fit the screen? \`FittedBox\` tries to size itself to the \`Text\`, but it can't be bigger than the screen. It then assumes the screen size, and resizes \`Text\` so that it fits the screen, too. ### Example 21

<!--?code-excerpt "lib/main.dart (Example21)" replace="/(return |;)//g"?-->

\`\`\`dart const Center( child: Text( 'This is some very very very large text that is too big to fit a regular screen in a single line.', ), ) \`\`\` If, however, you remove the \`FittedBox\`, the \`Text\` gets its maximum width from the screen, and breaks the line so that it fits the screen. ### Example 22

<!--?code-excerpt "lib/main.dart (Example22)" replace="/(return |;)//g"?-->

\`\`\`dart FittedBox( child: Container(height: 20, width: double.infinity, color: Colors.red), ) \`\`\` \`FittedBox\` can only scale a widget that is bounded (has non-infinite width and height). Otherwise, it won't render anything, and you'll see an error in the console. ### Example 23

<!--?code-excerpt "lib/main.dart (Example23)" replace="/(return |;)//g"?-->

\`\`\`dart Row( children: \[ Container( color: red, child: const Text('Hello!', style: big), ), Container( color: green, child: const Text('Goodbye!', style: big), ), ], ) \`\`\` The screen forces the \`Row\` to be exactly the same size as the screen. Just like an \`UnconstrainedBox\`, the \`Row\` won't impose any constraints onto its children, and instead lets them be any size they want. The \`Row\` then puts them side-by-side, and any extra space remains empty. ### Example 24

<!--?code-excerpt "lib/main.dart (Example24)" replace="/(return |;)//g"?-->

\`\`\`dart Row( children: \[ Container( color: red, child: const Text( 'This is a very long text that ' 'won\\'t fit the line.', style: big, ), ), Container( color: green, child: const Text('Goodbye!', style: big), ), ], ) \`\`\` Since \`Row\` won't impose any constraints onto its children, it's quite possible that the children might be too big to fit the available width of the \`Row\`. In this case, just like an \`UnconstrainedBox\`, the \`Row\` displays the "overflow warning". ### Example 25

<!--?code-excerpt "lib/main.dart (Example25)" replace="/(return |;)//g"?-->

\`\`\`dart Row( children: \[ Expanded( child: Center( child: Container( color: red, child: const Text( 'This is a very long text that won\\'t fit the line.', style: big, ), ), ), ), Container( color: green, child: const Text('Goodbye!', style: big), ), ], ) \`\`\` When a \`Row\`'s child is wrapped in an \`Expanded\` widget, the \`Row\` won't let this child define its own width anymore. Instead, it defines the \`Expanded\` width according to the other children, and only then the \`Expanded\` widget forces the original child to have the \`Expanded\`'s width. In other words, once you use \`Expanded\`, the original child's width becomes irrelevant, and is ignored. ### Example 26

<!--?code-excerpt "lib/main.dart (Example26)" replace="/(return |;)//g"?-->

\`\`\`dart Row( children: \[ Expanded( child: Container( color: red, child: const Text( 'This is a very long text that won\\'t fit the line.', style: big, ), ), ), Expanded( child: Container( color: green, child: const Text('Goodbye!', style: big), ), ), ], ) \`\`\` If all of \`Row\`'s children are wrapped in \`Expanded\` widgets, each \`Expanded\` has a size proportional to its flex parameter, and only then each \`Expanded\` widget forces its child to have the \`Expanded\`'s width. In other words, \`Expanded\` ignores the preferred width of its children. ### Example 27

<!--?code-excerpt "lib/main.dart (Example27)" replace="/(return |;)//g"?-->

\`\`\`dart Row( children: \[ Flexible( child: Container( color: red, child: const Text( 'This is a very long text that won\\'t fit the line.', style: big, ), ), ), Flexible( child: Container( color: green, child: const Text('Goodbye!', style: big), ), ), ], ) \`\`\` The only difference if you use \`Flexible\` instead of \`Expanded\`, is that \`Flexible\` lets its child have the same or smaller width than the \`Flexible\` itself, while \`Expanded\` forces its child to have the exact same width of the \`Expanded\`. But both \`Expanded\` and \`Flexible\` ignore their children's width when sizing themselves. :::note This means that it's impossible to expand \`Row\` children proportionally to their sizes. The \`Row\` either uses the exact child's width, or ignores it completely when you use \`Expanded\` or \`Flexible\`. ::: ### Example 28

<!--?code-excerpt "lib/main.dart (Example28)" replace="/(return |;)//g"?-->

\`\`\`dart Scaffold( body: Container( color: blue, child: const Column(children: \[Text('Hello!'), Text('Goodbye!')]), ), ) \`\`\` The screen forces the \`Scaffold\` to be exactly the same size as the screen, so the \`Scaffold\` fills the screen. The \`Scaffold\` tells the \`Container\` that it can be any size it wants, but not bigger than the screen. :::note When a widget tells its child that it can be smaller than a certain size, we say the widget supplies \_loose\_ constraints to its child. More on that later. ::: ### Example 29

<!--?code-excerpt "lib/main.dart (Example29)" replace="/(return |;)//g"?-->

\`\`\`dart Scaffold( body: SizedBox.expand( child: Container( color: blue, child: const Column(children: \[Text('Hello!'), Text('Goodbye!')]), ), ), ) \`\`\` If you want the \`Scaffold\`'s child to be exactly the same size as the \`Scaffold\` itself, you can wrap its child with \`SizedBox.expand\`. ## Tight vs loose constraints It's very common to hear that some constraint is "tight" or "loose", so what does that mean? ### Tight constraints A \_tight\_ constraint offers a single possibility, an exact size. In other words, a tight constraint has its maximum width equal to its minimum width; and has its maximum height equal to its minimum height. An example of this is the \`App\` widget, which is contained by the \[\`RenderView\`]\[] class: the box used by the child returned by the application's \[\`build\`]\[] function is given a constraint that forces it to exactly fill the application's content area (typically, the entire screen). Another example: if you nest a bunch of boxes inside each other at the root of your application's render tree, they'll all exactly fit in each other, forced by the box's tight constraints. If you go to Flutter's \`box.dart\` file and search for the \`BoxConstraints\` constructors, you'll find the following: \`\`\`dart BoxConstraints.tight(Size size) : minWidth = size.width, maxWidth = size.width, minHeight = size.height, maxHeight = size.height; \`\`\` If you revisit \[Example 2]\(#example-2), the screen forces the red \`Container\` to be exactly the same size as the screen. The screen achieves that, of course, by passing tight constraints to the \`Container\`. ### Loose constraints A \_loose\_ constraint is one that has a minimum of zero and a maximum non-zero. Some boxes \_loosen\_ the incoming constraints, meaning the maximum is maintained but the minimum is removed, so the widget can have a \*\*minimum\*\* width and height both equal to \*\*zero\*\*. Ultimately, \`Center\`'s purpose is to transform the tight constraints it received from its parent (the screen) to loose constraints for its child (the \`Container\`). If you revisit \[Example 3]\(#example-3), the \`Center\` allows the red \`Container\` to be smaller, but not bigger than the screen. \[\`build\`]: https\://api.flutter.dev/flutter/widgets/State/build.html \[\`RenderView\`]: https\://api.flutter.dev/flutter/rendering/RenderView-class.html []()## Unbounded constraints :::note You might be directed here if the framework detects a problem involving box constraints. The \`Flex\` section below might also apply. ::: In certain situations, a box's constraint is \_unbounded\_, or infinite. This means that either the maximum width or the maximum height is set to \[\`double.infinity\`]\[]. A box that tries to be as big as possible won't function usefully when given an unbounded constraint and, in debug mode, throws an exception. The most common case where a render box ends up with an unbounded constraint is within a flex box (\[\`Row\`]\[] or \[\`Column\`]\[]), and \*\*within a scrollable region\*\* (such as \[\`ListView\`]\[] and other \[\`ScrollView\`]\[] subclasses). \[\`ListView\`]\[], for example, tries to expand to fit the space available in its cross-direction (perhaps it's a vertically-scrolling block and tries to be as wide as its parent). If you nest a vertically scrolling \[\`ListView\`]\[] inside a horizontally scrolling \`ListView\`, the inner list tries to be as wide as possible, which is infinitely wide, since the outer one is scrollable in that direction. The next section describes the error you might encounter with unbounded constraints in a \`Flex\` widget. ## Flex A flex box (\[\`Row\`]\[] and \[\`Column\`]\[]) behaves differently depending on whether its constraint is bounded or unbounded in its primary direction. A flex box with a bounded constraint in its primary direction tries to be as big as possible. A flex box with an unbounded constraint in its primary direction tries to fit its children in that space. Each child's \`flex\` value must be set to zero, meaning that you can't use \[\`Expanded\`]\[] when the flex box is inside another flex box or a scrollable; otherwise it throws an exception. The \_cross\_ direction (width for \[\`Column\`]\[] or height for \[\`Row\`]\[]), must \_never\_ be unbounded, or it can't reasonably align its children. \[\`double.infinity\`]: https\://api.flutter.dev/flutter/dart-core/double/infinity-constant.html \[\`Expanded\`]: https\://api.flutter.dev/flutter/widgets/Expanded-class.html \[\`RenderBox\`]: https\://api.flutter.dev/flutter/rendering/RenderBox-class.html \[\`ScrollView\`]: https\://api.flutter.dev/flutter/widgets/ScrollView-class.html ## Learning the layout rules for specific widgets Knowing the general layout rule is necessary, but it's not enough. Each widget has a lot of freedom when applying the general rule, so there is no way of knowing how it behaves by just reading the widget's name. If you try to guess, you'll probably guess wrong. You can't know exactly how a widget behaves unless you've read its documentation, or studied its source-code. The layout source-code is usually complex, so it's probably better to just read the documentation. However, if you decide to study the layout source-code, you can easily find it by using the navigating capabilities of your IDE. Here's an example: \* Find a \`Column\` in your code and navigate to its source code. To do this, use \`command+B\` (macOS) or \`control+B\` (Windows/Linux) in Android Studio or IntelliJ. You'll be taken to the \`basic.dart\` file. Since \`Column\` extends \`Flex\`, navigate to the \`Flex\` source code (also in \`basic.dart\`). \* Scroll down until you find a method called \`createRenderObject()\`. As you can see, this method returns a \`RenderFlex\`. This is the render-object for the \`Column\`. Now navigate to the source-code of \`RenderFlex\`, which takes you to the \`flex.dart\` file. \* Scroll down until you find a method called \`performLayout()\`. This is the method that does the layout for the \`Column\`. --- Original article by Marcelo Glasberg Marcelo originally published this content as \[Flutter: The Advanced Layout Rule Even Beginners Must Know]\[article] on Medium. We loved it and asked that he allow us to publish in on docs.flutter.dev, to which he graciously agreed. Thanks, Marcelo! You can find Marcelo on \[GitHub]\[] and \[pub.dev]\[]. Also, thanks to \[Simon Lightfoot]\[] for creating the header image at the top of the article. \[article]: https\://medium.com/flutter-community/flutter-the-advanced-layout-rule-even-beginners-must-know-edc9516d1a2 \[GitHub]: https\://github.com/marcglasberg \[pub.dev]: https\://pub.dev/publishers/glasberg.dev/packages \[Simon Lightfoot]: https\://github.com/slightfoot :::note To better understand how Flutter implements layout constraints, check out the following 5-minute video: :::

----
url: https://docs.flutter.dev/cookbook/animation/animated-container
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Animate the properties of a container](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

The [`Container`](https://api.flutter.dev/flutter/widgets/Container-class.html) 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 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`](https://api.flutter.dev/flutter/widgets/AnimatedContainer-class.html) 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 animations are known as "implicit animations."

This recipe describes how to use an `AnimatedContainer` to animate the size, background color, and border radius when the user taps a button using the following steps:

1. Create a `StatefulWidget` with default properties.
2. Build an `AnimatedContainer` using the properties.
3. Start the animation by rebuilding with new properties.

## 1. Create a `StatefulWidget` with default properties

[#](#1-create-a-statefulwidget-with-default-properties)

To start, create [`StatefulWidget`](https://api.flutter.dev/flutter/widgets/StatefulWidget-class.html) and [`State`](https://api.flutter.dev/flutter/widgets/State-class.html) classes. 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. You can also define the default value of each property.

These properties belong to a custom `State` class so they can be updated when the user taps a button.

dart

```
class AnimatedContainerApp extends StatefulWidget {
  const AnimatedContainerApp({super.key});
​
  @override
  State<AnimatedContainerApp> createState() => _AnimatedContainerAppState();
}
​
class _AnimatedContainerAppState extends State<AnimatedContainerApp> {
  // Define the various properties with default values. Update these properties
  // when the user taps a FloatingActionButton.
  double _width = 50;
  double _height = 50;
  Color _color = Colors.green;
  BorderRadiusGeometry _borderRadius = BorderRadius.circular(8);
​
  @override
  Widget build(BuildContext context) {
    // Fill this out in the next steps.
  }
}
```

## 2. Build an `AnimatedContainer` using the properties

[#](#2-build-an-animatedcontainer-using-the-properties)

Next, build the `AnimatedContainer` using the properties defined in the previous step. Furthermore, provide a `duration` that defines how long the animation should run.

dart

```
AnimatedContainer(
  // Use the properties stored in the State class.
  width: _width,
  height: _height,
  decoration: BoxDecoration(
    color: _color,
    borderRadius: _borderRadius,
  ),
  // Define how long the animation should take.
  duration: const Duration(seconds: 1),
  // Provide an optional curve to make the animation feel smoother.
  curve: Curves.fastOutSlowIn,
)
```

## 3. Start the animation by rebuilding with new properties

[#](#3-start-the-animation-by-rebuilding-with-new-properties)

Finally, start the animation by rebuilding the `AnimatedContainer` with the new properties. How to trigger a rebuild? Use the [`setState()`](https://api.flutter.dev/flutter/widgets/State/setState.html) method.

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()`.

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

```
FloatingActionButton(
  // When the user taps the button
  onPressed: () {
    // Use setState to rebuild the widget with new values.
    setState(() {
      // Create a random number generator.
      final random = Random();
​
      // Generate a random width and height.
      _width = random.nextInt(300).toDouble();
      _height = random.nextInt(300).toDouble();
​
      // Generate a random color.
      _color = Color.fromRGBO(
        random.nextInt(256),
        random.nextInt(256),
        random.nextInt(256),
        1,
      );
​
      // Generate a random border radius.
      _borderRadius = BorderRadius.circular(
        random.nextInt(100).toDouble(),
      );
    });
  },
  tooltip: 'Animate',
  child: const Icon(Icons.play_arrow),
)
```

## Interactive example

[#](#interactive-example)

```
import 'dart:math';

import 'package:flutter/material.dart';

void main() => runApp(const AnimatedContainerApp());

class AnimatedContainerApp extends StatefulWidget {
  const AnimatedContainerApp({super.key});

  @override
  State<AnimatedContainerApp> createState() => _AnimatedContainerAppState();
}

class _AnimatedContainerAppState extends State<AnimatedContainerApp> {
  // Define the various properties with default values. Update these properties
  // when the user taps a FloatingActionButton.
  double _width = 50;
  double _height = 50;
  Color _color = Colors.green;
  BorderRadiusGeometry _borderRadius = BorderRadius.circular(8);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: const Text('AnimatedContainer Demo')),
        body: Center(
          child: AnimatedContainer(
            // Use the properties stored in the State class.
            width: _width,
            height: _height,
            decoration: BoxDecoration(
              color: _color,
              borderRadius: _borderRadius,
            ),
            // Define how long the animation should take.
            duration: const Duration(seconds: 1),
            // Provide an optional curve to make the animation feel smoother.
            curve: Curves.fastOutSlowIn,
          ),
        ),
        floatingActionButton: FloatingActionButton(
          // When the user taps the button
          onPressed: () {
            // Use setState to rebuild the widget with new values.
            setState(() {
              // Create a random number generator.
              final random = Random();

              // Generate a random width and height.
              _width = random.nextInt(300).toDouble();
              _height = random.nextInt(300).toDouble();

              // Generate a random color.
              _color = Color.fromRGBO(
                random.nextInt(256),
                random.nextInt(256),
                random.nextInt(256),
                1,
              );

              // Generate a random border radius.
              _borderRadius = BorderRadius.circular(
                random.nextInt(100).toDouble(),
              );
            });
          },
          tooltip: 'Animate',
          child: const Icon(Icons.play_arrow),
        ),
      ),
    );
  }
}
```

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-10-07. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/animation/animated-container.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/cookbook/animation/animated-container\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/animation/animated-container.md "Report an issue with this page").

----
url: https://docs.flutter.dev/ui/adaptive-responsive/capabilities
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Capabilities & policies](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

Most real-world apps have the need to adapt to the capabilities and policies of different devices and platforms. This page contains advice for how to handle these scenarios in your code.

## Design to the strengths of each device type

[#](#design-to-the-strengths-of-each-device-type)

Consider the unique strengths and weaknesses of different devices. Beyond their screen size and inputs, such as touch, mouse, keyboard, what other unique capabilities can you leverage? Flutter enables your code to *run* on different devices, but strong design is more than just running code. Think about what each platform does best and see if there are unique capabilities to leverage.

For example: Apple's App Store and Google's Play Store have different rules that apps need to abide by. Different host operating systems have differing capabilities across time as well as each other.

Another example is leveraging the web's extremely low barrier for sharing. If you're deploying a web app, decide what deep links to support, and design the navigation routes with those in mind.

Flutter's recommended pattern for handling different behavior based on these unique capabilities is to create a set of `Capability` and `Policy` classes for your app.

### Capabilities

[#](#capabilities)

A *capability* defines what the code or device *can* do. Examples of capabilities include:

* The existence of an API
* OS-enforced restrictions
* Physical hardware requirements (like a camera)

### Policies

[#](#policies)

A *policy* defines what the code *should* do.

Examples of policies include:

* App store guidelines
* Design preferences
* Assets or copy that refers to the host device
* Features enabled on the server side

### How to structure policy code

[#](#how-to-structure-policy-code)

The simplest mechanical way is `Platform.isAndroid`, `Platform.isIOS`, and `kIsWeb`. These APIs mechanically let you know where the code is running but have some problems as the app expands where it can run, and as host platforms add functionality.

The following guidelines explain best practices when developing the capabilities and policies for your app:

**Avoid using `Platform.isAndroid` and similar functions to make layout decisions or assumptions about what a device can do.**

Instead, describe what you want to branch on in a method.

Example: Your app has a link to buy something in a website, but you don't want to show that link on iOS devices for policy reasons.

dart

```
bool shouldAllowPurchaseClick() {
  // Banned by Apple App Store guidelines.
  return !Platform.isIOS;
}
​
...
TextSpan(
  text: 'Buy in browser',
  style: new TextStyle(color: Colors.blue),
  recognizer: shouldAllowPurchaseClick ? TapGestureRecognizer()
    ..onTap = () { launch('<some url>') : null;
  } : null,
```

What did you get by adding an additional layer of indirection? The code makes it more clear why the branched path exists. This method can exist directly in the class but it's likely that other parts of the code might need this same check. If so, put the code in a class.

policy.dart

dart

```
​
class Policy {
​
  bool shouldAllowPurchaseClick() {
    // Banned by Apple App Store guidelines.
    return !Platform.isIOS;
  }
}
```

With this code in a class, any widget test can mock `Policy().shouldAllowPurchaseClick` and verify the behavior independently of where the device runs. It also means that later, if you decide that buying on the web isn't the right flow for Android users, you can change the implementation and the tests for clickable text won't need to change.

## Capabilities

[#](#capabilities-1)

Sometimes you want your code to do something but the API doesn't exist, or maybe you depend on a plugin feature that isn't yet implemented on all of the platforms you support. This is a limitation of what the device *can* do.

Those situations are similar to the policy decisions described above, but these are referred to as *capabilities*. Why separate policy classes from capabilities when the structure of the classes is similar? The Flutter team has found with productionized apps that making a logical distinction between what apps *can* do and what they *should* do helps larger products respond to changes in what platforms can do or require in addition to your own preferences after the initial code is written.

For example, consider the case where one platform adds a new permission that requires users to interact with a system dialog before your code calls a sensitive API. Your team does the work for platform 1 and creates a capability named `requirePermissionDialogFlow`. Then, if and when platform 2 adds a similar requirement but only for new API versions, then the implementation of `requirePermissionDialogFlow` can now check the API level and return true for platform 2. You've leveraged the work you already did.

## Policies

[#](#policies-1)

We encourage starting with a `Policy` class initially even if it seems like you won't make many policy based decisions. As the complexity of the class grows or the number of inputs expands, you might decide to break up the policy class by feature or some other criteria.

For policy implementation, you can use compile time, run time, or Remote Procedure Call (RPC) backed implementations.

Compile-time policy checks are good for platforms where the preference is unlikely to change and where accidentally changing the value might have large consequences. For example, if a platform requires that you not link to the Play store, or requires that you use a specific payment provider given the content of your app.

Runtime checks can be good for determining if there is a touch screen the user can use. Android has a feature you can check and your web implementation could check for max touch points.

RPC-backed policy changes are good for incremental feature rollout or for decisions that might change later.

## Summary

[#](#summary)

Use a `Capability` class to define what the code *can* do. You might check against the existence of an API, OS-enforced restrictions, and physical hardware requirements (like a camera). A capability usually involves compile or runtime checks.

Use a `Policy` class (or classes depending on complexity) to define what the code *should* do to comply with App store guidelines, design preferences, and assets or copy that need to refer to the host device. Policies can be a mix of compile, runtime, or RPC checks.

Test the branching code by mocking capabilities and policies so the widget tests don't need to change when capabilities or policies change.

Name the methods in your capabilities and policies classes based on what they are trying to branch, rather than on device type.

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/ui/adaptive-responsive/capabilities.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/ui/adaptive-responsive/capabilities\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/ui/adaptive-responsive/capabilities.md "Report an issue with this page").

----
url: https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.20.0
----

The 2.20.0 release of the Dart and Flutter DevTools includes the following changes among other general improvements. To learn more about DevTools, check out the [DevTools overview](https://docs.flutter.dev/tools/devtools).

## CPU profiler updates

[#](#cpu-profiler-updates)

* Add support for grouping samples by tag - [#4693](https://github.com/flutter/devtools/pull/4693)

* Enable guidelines for tree view - [#4722](https://github.com/flutter/devtools/pull/4722)

* Rename "Profile granularity" to "CPU sampling rate" and move down to the area it relates to - [#4803](https://github.com/flutter/devtools/pull/4722)

## Memory updates

[#](#memory-updates)

* Retire the **Analysis** tab - [#4714](https://github.com/flutter/devtools/pull/4714)

* Add a new tab, **Diff**, to enable memory leak detection and troubleshooting by comparing heap snapshots, providing insights about the number of instances, shallow size, retained size, and retaining paths - [#4714](https://github.com/flutter/devtools/pull/4714)

## Debugger updates

[#](#debugger-updates)

* Support for inspecting more types of instances in the variables viewer (Expandos, Types, TypeArguments, Parameters, Closures + closure Contexts, WeakProperty, Function, FunctionType, ReceivePort, Closure, RegExp) - [#4760](https://github.com/flutter/devtools/pull/4760)

* Add support for displaying coverage in CodeView - [#4700](https://github.com/flutter/devtools/pull/4700)

## Network updates

[#](#network-updates)

* Display request data if content type is not json (thanks to @leungpuikuen!) - [#4602](https://github.com/flutter/devtools/pull/4602)

## Full commit history

[#](#full-commit-history)

To find a complete list of changes since the previous release, check out [the diff on GitHub](https://github.com/flutter/devtools/compare/v2.19.0...v2.20.0).

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/release-notes/release-notes-2.20.0.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.20.0\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/release-notes/release-notes-2.20.0.md "Report an issue with this page").

----
url: https://docs.flutter.dev/flutter-for/uikit-devs.md
----

\# Flutter for UIKit developers > Learn how to apply iOS and UIKit developer knowledge when building Flutter apps.

<!--?code-excerpt path-base="get-started/flutter-for/ios_devs"?-->

iOS developers with experience using UIKit who want to write mobile apps using Flutter should review this guide. It explains how to apply existing UIKit knowledge to Flutter. :::note If you have experience building apps with SwiftUI, check out \[Flutter for SwiftUI developers]\[] instead. ::: Flutter is a framework for building cross-platform applications that uses the Dart programming language. To understand some differences between programming with Dart and programming with Swift, check out \[Learning Dart as a Swift Developer]\[] and \[Flutter concurrency for Swift developers]\[]. Your iOS and UIKit knowledge and experience are highly valuable when building with Flutter. Flutter also makes a number of adaptations to app behavior when running on iOS. To learn how, see \[Platform adaptations]\[]. :::tip To integrate Flutter code into an \*\*existing\*\* iOS app, check out \[Add Flutter to existing app]\[]. ::: Use this guide as a cookbook. Jump around and find questions that address your most relevant needs. ## Overview As an introduction, watch the following video. It outlines how Flutter works on iOS and how to use Flutter to build iOS apps. ### Views vs. Widgets :::secondary How is react-style, or \_declarative\_, programming different from the traditional imperative style? For a comparison, see \[Introduction to declarative UI]\[]. ::: In UIKit, most of what you create in the UI is done using view objects, which are instances of the \`UIView\` class. These can act as containers for other \`UIView\` classes, which form your layout. In Flutter, the rough equivalent to a \`UIView\` is a \`Widget\`. Widgets don't map exactly to iOS views, but while you're getting acquainted with how Flutter works you can think of them as "the way you declare and construct UI". However, these have a few differences to a \`UIView\`. To start, widgets have a different lifespan: they are immutable and only exist until they need to be changed. Whenever widgets or their state change, Flutter's framework creates a new tree of widget instances. In comparison, a UIKit view is not recreated when it changes, but rather it's a mutable entity that is drawn once and doesn't redraw until it is invalidated using \`setNeedsDisplay()\`. Furthermore, unlike \`UIView\`, Flutter's widgets are lightweight, in part due to their immutability. Because they aren't views themselves, and aren't directly drawing anything, but rather are a description of the UI and its semantics that get "inflated" into actual view objects under the hood. Flutter includes the \[Material Components]\[] library. These are widgets that implement the \[Material Design guidelines]\[]. Material Design is a flexible design system \[optimized for all platforms]\[], including iOS. But Flutter is flexible and expressive enough to implement any design language. On iOS, you can use the \[Cupertino widgets]\[] library to produce an interface that looks like \[Apple's iOS design language]\[]. ### Updating widgets To update your views in UIKit, you directly mutate them. In Flutter, widgets are immutable and not updated directly. Instead, you have to manipulate the widget's state. This is where the concept of Stateful vs Stateless widgets comes in. A \`StatelessWidget\` is just what it sounds like—a widget with no state attached. \`StatelessWidgets\` are useful when the part of the user interface you are describing does not depend on anything other than the initial configuration information in the widget. For example, with UIKit, this is similar to placing a \`UIImageView\` with your logo as the \`image\`. If the logo is not changing during runtime, use a \`StatelessWidget\` in Flutter. If you want to dynamically change the UI based on data received after making an HTTP call, use a \`StatefulWidget\`. After the HTTP call has completed, tell the Flutter framework that the widget's \`State\` is updated, so it can update the UI. The important difference between stateless and stateful widgets is that \`StatefulWidget\`s have a \`State\` object that stores state data and carries it over across tree rebuilds, so it's not lost. If you are in doubt, remember this rule: if a widget changes outside of the \`build\` method (because of runtime user interactions, for example), it's stateful. If the widget never changes, once built, it's stateless. However, even if a widget is stateful, the containing parent widget can still be stateless if it isn't itself reacting to those changes (or other inputs). The following example shows how to use a \`StatelessWidget\`. A common\`StatelessWidget\` is the \`Text\` widget. If you look at the implementation of the \`Text\` widget, you'll find it subclasses \`StatelessWidget\`.

<!--?code-excerpt "lib/text_widget.dart (text-widget)" replace="/return const //g"?-->

\`\`\`dart Text( 'I like Flutter!', style: TextStyle(fontWeight: FontWeight.bold), ); \`\`\` If you look at the code above, you might notice that the \`Text\` widget carries no explicit state with it. It renders what is passed in its constructors and nothing more. But, what if you want to make "I Like Flutter" change dynamically, for example when clicking a \`FloatingActionButton\`? To achieve this, wrap the \`Text\` widget in a \`StatefulWidget\` and update it when the user clicks the button. For example:

<!--?code-excerpt "lib/text_widget.dart (stateful-widget)"?-->

\`\`\`dart class SampleApp extends StatelessWidget { // This widget is the root of your application. const SampleApp({super.key}); @override Widget build(BuildContext context) { return const MaterialApp(title: 'Sample App', home: SampleAppPage()); } } class SampleAppPage extends StatefulWidget { const SampleAppPage({super.key}); @override State createState() => \_SampleAppPageState(); } class \_SampleAppPageState extends State { // Default placeholder text String textToShow = 'I Like Flutter'; void \_updateText() { setState(() { // Update the text textToShow = 'Flutter is Awesome!'; }); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('Sample App')), body: Center(child: Text(textToShow)), floatingActionButton: FloatingActionButton( onPressed: \_updateText, tooltip: 'Update Text', child: const Icon(Icons.update), ), ); } } \`\`\` ### Widget layout In UIKit, you might use a Storyboard file to organize your views and set constraints, or you might set your constraints programmatically in your view controllers. In Flutter, declare your layout in code by composing a widget tree. The following example shows how to display a simple widget with padding:

<!--?code-excerpt "lib/layout.dart (simple-widget)"?-->

\`\`\`dart @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('Sample App')), body: Center( child: CupertinoButton( onPressed: () {}, padding: const EdgeInsets.only(left: 10, right: 10), child: const Text('Hello'), ), ), ); } \`\`\` You can add padding to any widget, which mimics the functionality of constraints in iOS. You can view the layouts that Flutter has to offer in the \[widget catalog]\[]. ### Removing Widgets In UIKit, you call \`addSubview()\` on the parent, or \`removeFromSuperview()\` on a child view to dynamically add or remove child views. In Flutter, because widgets are immutable, there is no direct equivalent to \`addSubview()\`. Instead, you can pass a function to the parent that returns a widget, and control that child's creation with a boolean flag. The following example shows how to toggle between two widgets when the user clicks the \`FloatingActionButton\`:

<!--?code-excerpt "lib/layout.dart (toggle-widget)"?-->

\`\`\`dart class SampleApp extends StatelessWidget { // This widget is the root of your application. const SampleApp({super.key}); @override Widget build(BuildContext context) { return const MaterialApp(title: 'Sample App', home: SampleAppPage()); } } class SampleAppPage extends StatefulWidget { const SampleAppPage({super.key}); @override State createState() => \_SampleAppPageState(); } class \_SampleAppPageState extends State { // Default value for toggle. bool toggle = true; void \_toggle() { setState(() { toggle = !toggle; }); } Widget \_getToggleChild() { if (toggle) { return const Text('Toggle One'); } return CupertinoButton(onPressed: () {}, child: const Text('Toggle Two')); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('Sample App')), body: Center(child: \_getToggleChild()), floatingActionButton: FloatingActionButton( onPressed: \_toggle, tooltip: 'Update Text', child: const Icon(Icons.update), ), ); } } \`\`\` ### Animations In UIKit, you create an animation by calling the \`animate(withDuration:animations:)\` method on a view. In Flutter, use the animation library to wrap widgets inside an animated widget. In Flutter, use an \`AnimationController\`, which is an \`Animation\` that can pause, seek, stop, and reverse the animation. It requires a \`Ticker\` that signals when vsync happens and produces a linear interpolation between 0 and 1 on each frame while it's running. You then create one or more \`Animation\`s and attach them to the controller. For example, you might use \`CurvedAnimation\` to implement an animation along an interpolated curve. In this sense, the controller is the "master" source of the animation progress and the \`CurvedAnimation\` computes the curve that replaces the controller's default linear motion. Like widgets, animations in Flutter work with composition. When building the widget tree you assign the \`Animation\` to an animated property of a widget, such as the opacity of a \`FadeTransition\`, and tell the controller to start the animation. The following example shows how to write a \`FadeTransition\` that fades the widget into a logo when you press the \`FloatingActionButton\`:

<!--?code-excerpt "lib/animation.dart"?-->

\`\`\`dart import 'package:material\_ui/material\_ui.dart'; class SampleApp extends StatelessWidget { // This widget is the root of your application. const SampleApp({super.key}); @override Widget build(BuildContext context) { return const MaterialApp( title: 'Fade Demo', home: MyFadeTest(title: 'Fade Demo'), ); } } class MyFadeTest extends StatefulWidget { const MyFadeTest({super.key, required this.title}); final String title; @override State createState() => \_MyFadeTest(); } class \_MyFadeTest extends State with SingleTickerProviderStateMixin { late AnimationController controller; late CurvedAnimation curve; @override void initState() { super.initState(); controller = AnimationController( duration: const Duration(milliseconds: 2000), vsync: this, ); curve = CurvedAnimation(parent: controller, curve: Curves.easeIn); } @override void dispose() { controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text(widget.title)), body: Center( child: FadeTransition( opacity: curve, child: const FlutterLogo(size: 100), ), ), floatingActionButton: FloatingActionButton( onPressed: () { controller.forward(); }, tooltip: 'Fade', child: const Icon(Icons.brush), ), ); } } \`\`\` For more information, see \[Animation & Motion widgets]\[], the \[Animations tutorial]\[], and the \[Animations overview]\[]. ### Drawing on the screen In UIKit, you use \`CoreGraphics\` to draw lines and shapes to the screen. Flutter has a different API based on the \`Canvas\` class, with two other classes that help you draw: \`CustomPaint\` and \`CustomPainter\`, the latter of which implements your algorithm to draw to the canvas. To learn how to implement a signature painter in Flutter, see Collin's answer on \[StackOverflow]\[]. \[StackOverflow]: https\://stackoverflow\.com/questions/46241071/create-signature-area-for-mobile-app-in-dart-flutter

<!--?code-excerpt "lib/canvas.dart"?-->

\`\`\`dart import 'package:material\_ui/material\_ui.dart'; void main() => runApp(const MaterialApp(home: DemoApp())); class DemoApp extends StatelessWidget { const DemoApp({super.key}); @override Widget build(BuildContext context) => const Scaffold(body: Signature()); } class Signature extends StatefulWidget { const Signature({super.key}); @override State createState() => SignatureState(); } class SignatureState extends State { List \_points = \[]; @override Widget build(BuildContext context) { return GestureDetector( onPanUpdate: (details) { setState(() { RenderBox? referenceBox = context.findRenderObject() as RenderBox; Offset localPosition = referenceBox.globalToLocal( details.globalPosition, ); \_points = List.from(\_points)..add(localPosition); }); }, onPanEnd: (details) => \_points.add(null), child: CustomPaint( painter: SignaturePainter(\_points), size: Size.infinite, ), ); } } class SignaturePainter extends CustomPainter { SignaturePainter(this.points); final List points; @override void paint(Canvas canvas, Size size) { final Paint paint = Paint() ..color = Colors.black ..strokeCap = StrokeCap.round ..strokeWidth = 5; for (int i = 0; i < points.length - 1; i++) { if (points\[i] != null && points\[i + 1] != null) { canvas.drawLine(points\[i]!, points\[i + 1]!, paint); } } } @override bool shouldRepaint(SignaturePainter oldDelegate) => oldDelegate.points != points; } \`\`\` ### Widget opacity In UIKit, everything has \`.opacity\` or \`.alpha\`. In Flutter, most of the time you need to wrap a widget in an \`Opacity\` widget to accomplish this. ### Custom Widgets In UIKit, you typically subclass \`UIView\`, or use a pre-existing view, to override and implement methods that achieve the desired behavior. In Flutter, build a custom widget by \[composing]\[] smaller widgets (instead of extending them). For example, how do you build a \`CustomButton\` that takes a label in the constructor? Create a CustomButton that composes a \`ElevatedButton\` with a label, rather than by extending \`ElevatedButton\`:

<!--?code-excerpt "lib/custom.dart (custom-button)"?-->

\`\`\`dart class CustomButton extends StatelessWidget { const CustomButton(this.label, {super.key}); final String label; @override Widget build(BuildContext context) { return ElevatedButton(onPressed: () {}, child: Text(label)); } } \`\`\` Then use \`CustomButton\`, just as you'd use any other Flutter widget:

<!--?code-excerpt "lib/custom.dart (use-custom-button)"?-->

\`\`\`dart @override Widget build(BuildContext context) { return const Center(child: CustomButton('Hello')); } \`\`\` ### Managing dependencies In iOS, you add dependencies with CocoaPods by adding to your \`Podfile\`. Flutter uses Dart's build system and the Pub package manager to handle dependencies. The tools delegate the building of the native Android and iOS wrapper apps to the respective build systems. While there is a Podfile in the iOS folder in your Flutter project, only use this if you are adding native dependencies needed for per-platform integration. In general, use \`pubspec.yaml\` to declare external dependencies in Flutter. A good place to find great packages for Flutter is on \[pub.dev]\[]. ## Navigation This section of the document discusses navigation between pages of an app, the push and pop mechanism, and more. ### Navigating between pages In UIKit, to travel between view controllers, you can use a \`UINavigationController\` that manages the stack of view controllers to display. Flutter has a similar implementation, using a \`Navigator\` and \`Routes\`. A \`Route\` is an abstraction for a "screen" or "page" of an app, and a \`Navigator\` is a \[widget]\[] that manages routes. A route roughly maps to a \`UIViewController\`. The navigator works in a similar way to the iOS \`UINavigationController\`, in that it can \`push()\` and \`pop()\` routes depending on whether you want to navigate to, or back from, a view. To navigate between pages, you have a couple options: \* Specify a \`Map\` of route names. \* Directly navigate to a route. The following example builds a \`Map.\`

<!--?code-excerpt "lib/intent.dart (map)"?-->

\`\`\`dart void main() { runApp( CupertinoApp( home: const MyAppHome(), // becomes the route named '/' routes: { '/a': (context) => const MyPage(title: 'page A'), '/b': (context) => const MyPage(title: 'page B'), '/c': (context) => const MyPage(title: 'page C'), }, ), ); } \`\`\` Navigate to a route by \`push\`ing its name to the \`Navigator\`.

<!--?code-excerpt "lib/intent.dart (push)"?-->

\`\`\`dart Navigator.of(context).pushNamed('/b'); \`\`\` The \`Navigator\` class handles routing in Flutter and is used to get a result back from a route that you have pushed on the stack. This is done by \`await\`ing on the \`Future\` returned by \`push()\`. For example, to start a \`location\` route that lets the user select their location, you might do the following:

<!--?code-excerpt "lib/intent.dart (push-await)"?-->

\`\`\`dart Object? coordinates = await Navigator.of(context).pushNamed('/location'); \`\`\` And then, inside your \`location\` route, once the user has selected their location, \`pop()\` the stack with the result:

<!--?code-excerpt "lib/intent.dart (pop)"?-->

\`\`\`dart Navigator.of(context).pop({'lat': 43.821757, 'long': -79.226392}); \`\`\` ### Navigating to another app In UIKit, to send the user to another application, you use a specific URL scheme. For the system level apps, the scheme depends on the app. To implement this functionality in Flutter, create a native platform integration, or use an \[existing plugin]\[], such as \[\`url\_launcher\`]\[]. ### Manually pop back Calling \`SystemNavigator.pop()\` from your Dart code invokes the following iOS code: \`\`\`objc UIViewController\* viewController = \[UIApplication sharedApplication].keyWindow\.rootViewController; if (\[viewController isKindOfClass:\[UINavigationController class]]) { \[((UINavigationController\*)viewController) popViewControllerAnimated:NO]; } \`\`\` If that doesn't do what you want, you can create your own \[platform channel]\[] to invoke arbitrary iOS code. ### Handling localization Unlike iOS, which has the \`Localizable.strings\` file, Flutter doesn't currently have a dedicated system for handling strings. At the moment, the best practice is to declare your copy text in a class as static fields and access them from there. For example:

<!--?code-excerpt "lib/string_examples.dart (strings)"?-->

\`\`\`dart class Strings { static const String welcomeMessage = 'Welcome To Flutter'; } \`\`\` You can access your strings as such:

<!--?code-excerpt "lib/string_examples.dart (access-string)" replace="/const //g; /return //g;"?-->

\`\`\`dart Text(Strings.welcomeMessage); \`\`\` By default, Flutter only supports US English for its strings. If you need to add support for other languages, include the \`flutter\_localizations\` package. You might also need to add Dart's \[\`intl\`]\[] package to use i10n machinery, such as date/time formatting. \`\`\`yaml dependencies: flutter\_localizations: sdk: flutter intl: any # Use version of intl from flutter\_localizations. \`\`\` To use the \`flutter\_localizations\` package, specify the \`localizationsDelegates\` and \`supportedLocales\` on the app widget:

<!--?code-excerpt "lib/localizations_example.dart"?-->

\`\`\`dart import 'package:flutter\_localizations/flutter\_localizations.dart' hide GlobalMaterialLocalizations; import 'package:material\_ui/material\_ui.dart'; class MyWidget extends StatelessWidget { const MyWidget({super.key}); @override Widget build(BuildContext context) { return const MaterialApp( localizationsDelegates: >\[ // Add app-specific localization delegate\[s] here GlobalMaterialLocalizations.delegate, GlobalWidgetsLocalizations.delegate, ], supportedLocales: \[ Locale('en', 'US'), // English Locale('he', 'IL'), // Hebrew // ... other locales the app supports ], ); } } \`\`\` The delegates contain the actual localized values, while the \`supportedLocales\` defines which locales the app supports. The above example uses a \`MaterialApp\`, so it has both a \`GlobalWidgetsLocalizations\` for the base widgets localized values, and a \`MaterialWidgetsLocalizations\` for the Material widgets localizations. If you use \`WidgetsApp\` for your app, you don't need the latter. Note that these two delegates contain "default" values, but you'll need to provide one or more delegates for your own app's localizable copy, if you want those to be localized too. When initialized, the \`WidgetsApp\` (or \`MaterialApp\`) creates a \[\`Localizations\`]\[] widget for you, with the delegates you specify. The current locale for the device is always accessible from the \`Localizations\` widget from the current context (in the form of a \`Locale\` object), or using the \[\`Window\.locale\`]\[]. To access localized resources, use the \`Localizations.of()\` method to access a specific localizations class that is provided by a given delegate. Use the \[\`intl\_translation\`]\[] package to extract translatable copy to \[arb]\[] files for translating, and importing them back into the app for using them with \`intl\`. For further details on internationalization and localization in Flutter, see the \[internationalization guide]\[], which has sample code with and without the \`intl\` package. ## ViewControllers This section of the document discusses the equivalent of ViewController in Flutter and how to listen to lifecycle events. ### Equivalent of ViewController in Flutter In UIKit, a \`ViewController\` represents a portion of user interface, most commonly used for a screen or section. These are composed together to build complex user interfaces, and help scale your application's UI. In Flutter, this job falls to Widgets. As mentioned in the Navigation section, screens in Flutter are represented by Widgets since "everything is a widget!" Use a \`Navigator\` to move between different \`Route\`s that represent different screens or pages, or maybe different states or renderings of the same data. ### Listening to lifecycle events In UIKit, you can override methods to the \`ViewController\` to capture lifecycle methods for the view itself, or register lifecycle callbacks in the \`AppDelegate\`. In Flutter, you have neither concept, but you can instead listen to lifecycle events by hooking into the \`WidgetsBinding\` observer and listening to the \`didChangeAppLifecycleState()\` change event. The observable lifecycle events are: \*\*\`inactive\`\*\* : The application is in an inactive state and is not receiving user input. This event only works on iOS, as there is no equivalent event on Android. \*\*\`paused\`\*\* : The application is not currently visible to the user, is not responding to user input, but is running in the background. \*\*\`resumed\`\*\* : The application is visible and responding to user input. \*\*\`suspending\`\*\* : The application is suspended momentarily. The iOS platform has no equivalent event. For more details on the meaning of these states, see \[\`AppLifecycleState\` documentation]\[]. ## Layouts This section discusses different layouts in Flutter and how they compare with UIKit. ### Displaying a list view In UIKit, you might show a list in either a \`UITableView\` or a \`UICollectionView\`. In Flutter, you have a similar implementation using a \`ListView\`. In UIKit, these views have delegate methods for deciding the number of rows, the cell for each index path, and the size of the cells. Due to Flutter's immutable widget pattern, you pass a list of widgets to your \`ListView\`, and Flutter takes care of making sure that scrolling is fast and smooth.

<!--?code-excerpt "lib/listview.dart"?-->

\`\`\`dart import 'package:material\_ui/material\_ui.dart'; void main() { runApp(const SampleApp()); } class SampleApp extends StatelessWidget { const SampleApp({super.key}); // This widget is the root of your application. @override Widget build(BuildContext context) { return const MaterialApp(title: 'Sample App', home: SampleAppPage()); } } class SampleAppPage extends StatefulWidget { const SampleAppPage({super.key}); @override State createState() => \_SampleAppPageState(); } class \_SampleAppPageState extends State { List \_getListData() { final List widgets = \[]; for (int i = 0; i < 100; i++) { widgets.add( Padding(padding: const EdgeInsets.all(10), child: Text('Row $i')), ); } return widgets; } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('Sample App')), body: ListView(children: \_getListData()), ); } } \`\`\` ### Detecting what was clicked In UIKit, you implement the delegate method, \`tableView:didSelectRowAtIndexPath:\`. In Flutter, use the touch handling provided by the passed-in widgets.

<!--?code-excerpt "lib/list_item_tapped.dart"?-->

\`\`\`dart import 'dart:developer' as developer; import 'package:material\_ui/material\_ui.dart'; void main() { runApp(const SampleApp()); } class SampleApp extends StatelessWidget { const SampleApp({super.key}); // This widget is the root of your application. @override Widget build(BuildContext context) { return const MaterialApp(title: 'Sample App', home: SampleAppPage()); } } class SampleAppPage extends StatefulWidget { const SampleAppPage({super.key}); @override State createState() => \_SampleAppPageState(); } class \_SampleAppPageState extends State { List \_getListData() { List widgets = \[]; for (int i = 0; i < 100; i++) { widgets.add( GestureDetector( onTap: () { developer.log('row tapped'); }, child: Padding( padding: const EdgeInsets.all(10), child: Text('Row $i'), ), ), ); } return widgets; } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('Sample App')), body: ListView(children: \_getListData()), ); } } \`\`\` ### Dynamically updating ListView In UIKit, you update the data for the list view, and notify the table or collection view using the \`reloadData\` method. In Flutter, if you update the list of widgets inside a \`setState()\`, you quickly see that your data doesn't change visually. This is because when \`setState()\` is called, the Flutter rendering engine looks at the widget tree to see if anything has changed. When it gets to your \`ListView\`, it performs an \`==\` check, and determines that the two \`ListView\`s are the same. Nothing has changed, so no update is required. For a simple way to update your \`ListView\`, create a new \`List\` inside of \`setState()\`, and copy the data from the old list to the new list. While this approach is simple, it is not recommended for large data sets, as shown in the next example.

<!--?code-excerpt "lib/listview_dynamic.dart"?-->

\`\`\`dart import 'dart:developer' as developer; import 'package:material\_ui/material\_ui.dart'; void main() { runApp(const SampleApp()); } class SampleApp extends StatelessWidget { const SampleApp({super.key}); // This widget is the root of your application. @override Widget build(BuildContext context) { return const MaterialApp(title: 'Sample App', home: SampleAppPage()); } } class SampleAppPage extends StatefulWidget { const SampleAppPage({super.key}); @override State createState() => \_SampleAppPageState(); } class \_SampleAppPageState extends State { List widgets = \[]; @override void initState() { super.initState(); for (int i = 0; i < 100; i++) { widgets.add(getRow(i)); } } Widget getRow(int i) { return GestureDetector( onTap: () { setState(() { widgets = List.from(widgets); widgets.add(getRow(widgets.length)); developer.log('row $i'); }); }, child: Padding(padding: const EdgeInsets.all(10), child: Text('Row $i')), ); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('Sample App')), body: ListView(children: widgets), ); } } \`\`\` The recommended, efficient, and effective way to build a list uses a \`ListView\.Builder\`. This method is great when you have a dynamic list or a list with very large amounts of data.

<!--?code-excerpt "lib/listview_builder.dart"?-->

\`\`\`dart import 'dart:developer' as developer; import 'package:material\_ui/material\_ui.dart'; void main() { runApp(const SampleApp()); } class SampleApp extends StatelessWidget { const SampleApp({super.key}); // This widget is the root of your application. @override Widget build(BuildContext context) { return const MaterialApp(title: 'Sample App', home: SampleAppPage()); } } class SampleAppPage extends StatefulWidget { const SampleAppPage({super.key}); @override State createState() => \_SampleAppPageState(); } class \_SampleAppPageState extends State { List widgets = \[]; @override void initState() { super.initState(); for (int i = 0; i < 100; i++) { widgets.add(getRow(i)); } } Widget getRow(int i) { return GestureDetector( onTap: () { setState(() { widgets.add(getRow(widgets.length)); developer.log('row $i'); }); }, child: Padding(padding: const EdgeInsets.all(10), child: Text('Row $i')), ); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('Sample App')), body: ListView\.builder( itemCount: widgets.length, itemBuilder: (context, position) { return getRow(position); }, ), ); } } \`\`\` Instead of creating a \`ListView\`, create a \`ListView\.builder\` that takes two key parameters: the initial length of the list, and an \`ItemBuilder\` function. The \`ItemBuilder\` function is similar to the \`cellForItemAt\` delegate method in an iOS table or collection view, as it takes a position, and returns the cell you want rendered at that position. Finally, but most importantly, notice that the \`onTap()\` function doesn't recreate the list anymore, but instead \`.add\`s to it. ### Creating a scroll view In UIKit, you wrap your views in a \`ScrollView\` that allows a user to scroll your content if needed. In Flutter the easiest way to do this is using the \`ListView\` widget. This acts as both a \`ScrollView\` and an iOS \`TableView\`, as you can lay out widgets in a vertical format.

<!--?code-excerpt "lib/layout.dart (list-view)"?-->

\`\`\`dart @override Widget build(BuildContext context) { return ListView( children: const \[ Text('Row One'), Text('Row Two'), Text('Row Three'), Text('Row Four'), ], ); } \`\`\` For more detailed docs on how to lay out widgets in Flutter, see the \[layout tutorial]\[]. ## Gesture detection and touch event handling This section discusses how to detect gestures and handle different events in Flutter, and how they compare with UIKit. ### Adding a click listener In UIKit, you attach a \`GestureRecognizer\` to a view to handle click events. In Flutter, there are two ways of adding touch listeners: 1. If the widget supports event detection, pass a function to it, and handle the event in the function. For example, the \`ElevatedButton\` widget has an \`onPressed\` parameter:

<!--?code-excerpt "lib/events.dart (on-pressed)"?-->

\`\`\`dart @override Widget build(BuildContext context) { return ElevatedButton( onPressed: () { developer.log('click'); }, child: const Text('Button'), ); } \`\`\` 2. If the Widget doesn't support event detection, wrap the widget in a GestureDetector and pass a function to the \`onTap\` parameter.

<!--?code-excerpt "lib/events.dart (on-tap)"?-->

\`\`\`dart class SampleTapApp extends StatelessWidget { const SampleTapApp({super.key}); @override Widget build(BuildContext context) { return Scaffold( body: Center( child: GestureDetector( onTap: () { developer.log('tap'); }, child: const FlutterLogo(size: 200), ), ), ); } } \`\`\` ### Handling other gestures Using \`GestureDetector\` you can listen to a wide range of gestures such as: \* \*\*Tapping\*\* \*\*\`onTapDown\`\*\* : A pointer that might cause a tap has contacted the screen at a particular location. \*\*\`onTapUp\`\*\* : A pointer that triggers a tap has stopped contacting the screen at a particular location. \*\*\`onTap\`\*\* : A tap has occurred. \*\*\`onTapCancel\`\*\* : The pointer that previously triggered the \`onTapDown\` won't cause a tap. \* \*\*Double tapping\*\* \*\*\`onDoubleTap\`\*\* : The user tapped the screen at the same location twice in quick succession. \* \*\*Long pressing\*\* \*\*\`onLongPress\`\*\* : A pointer has remained in contact with the screen at the same location for a long period of time. \* \*\*Vertical dragging\*\* \*\*\`onVerticalDragStart\`\*\* : A pointer has contacted the screen and might begin to move vertically. \*\*\`onVerticalDragUpdate\`\*\* : A pointer in contact with the screen has moved further in the vertical direction. \*\*\`onVerticalDragEnd\`\*\* : A pointer that was previously in contact with the screen and moving vertically is no longer in contact with the screen and was moving at a specific velocity when it stopped contacting the screen. \* \*\*Horizontal dragging\*\* \*\*\`onHorizontalDragStart\`\*\* : A pointer has contacted the screen and might begin to move horizontally. \*\*\`onHorizontalDragUpdate\`\*\* : A pointer in contact with the screen has moved further in the horizontal direction. \*\*\`onHorizontalDragEnd\`\*\* : A pointer that was previously in contact with the screen and moving horizontally is no longer in contact with the screen. The following example shows a \`GestureDetector\` that rotates the Flutter logo on a double tap:

<!--?code-excerpt "lib/events.dart (sample-app)"?-->

\`\`\`dart class SampleApp extends StatefulWidget { const SampleApp({super.key}); @override State createState() => \_SampleAppState(); } class \_SampleAppState extends State with SingleTickerProviderStateMixin { late AnimationController controller; late CurvedAnimation curve; @override void initState() { super.initState(); controller = AnimationController( vsync: this, duration: const Duration(milliseconds: 2000), ); curve = CurvedAnimation(parent: controller, curve: Curves.easeIn); } @override Widget build(BuildContext context) { return Scaffold( body: Center( child: GestureDetector( onDoubleTap: () { if (controller.isCompleted) { controller.reverse(); } else { controller.forward(); } }, child: RotationTransition( turns: curve, child: const FlutterLogo(size: 200), ), ), ), ); } } \`\`\` ## Themes, styles, and media Flutter applications are easy to style; you can switch between light and dark themes, change the style of your text and UI components, and more. This section covers aspects of styling your Flutter apps and compares how you might do the same in UIKit. ### Using a theme Out of the box, Flutter comes with a beautiful implementation of Material Design, which takes care of a lot of styling and theming needs that you would typically do. To take full advantage of Material Components in your app, declare a top-level widget, \`MaterialApp\`, as the entry point to your application. \`MaterialApp\` is a convenience widget that wraps a number of widgets that are commonly required for applications implementing Material Design. It builds upon a \`WidgetsApp\` by adding Material specific functionality. But Flutter is flexible and expressive enough to implement any design language. On iOS, you can use the \[Cupertino library]\[] to produce an interface that adheres to the \[Human Interface Guidelines]\[]. For the full set of these widgets, see the \[Cupertino widgets]\[] gallery. You can also use a \`WidgetsApp\` as your app widget, which provides some of the same functionality, but is not as rich as \`MaterialApp\`. To customize the colors and styles of any child components, pass a \`ThemeData\` object to the \`MaterialApp\` widget. For example, in the code below, the color scheme from seed is set to deepPurple and divider color is grey.

<!--?code-excerpt "lib/theme.dart (theme)"?-->

\`\`\`dart import 'package:material\_ui/material\_ui.dart'; class SampleApp extends StatelessWidget { const SampleApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( title: 'Sample App', theme: ThemeData( colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), dividerColor: Colors.grey, ), home: const SampleAppPage(), ); } } \`\`\` ### Using custom fonts In UIKit, you import any \`ttf\` font files into your project and create a reference in the \`info.plist\` file. In Flutter, place the font file in a folder and reference it in the \`pubspec.yaml\` file, similar to how you import images. \`\`\`yaml fonts: - family: MyCustomFont fonts: - asset: fonts/MyCustomFont.ttf - style: italic \`\`\` Then assign the font to your \`Text\` widget:

<!--?code-excerpt "lib/text.dart (custom-font)"?-->

\`\`\`dart @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('Sample App')), body: const Center( child: Text( 'This is a custom font text', style: TextStyle(fontFamily: 'MyCustomFont'), ), ), ); } \`\`\` ### Styling text Along with fonts, you can customize other styling elements on a \`Text\` widget. The style parameter of a \`Text\` widget takes a \`TextStyle\` object, where you can customize many parameters, such as: \* \`color\` \* \`decoration\` \* \`decorationColor\` \* \`decorationStyle\` \* \`fontFamily\` \* \`fontSize\` \* \`fontStyle\` \* \`fontWeight\` \* \`hashCode\` \* \`height\` \* \`inherit\` \* \`letterSpacing\` \* \`textBaseline\` \* \`wordSpacing\` ### Bundling images in apps While iOS treats images and assets as distinct items, Flutter apps have only assets. Resources that are placed in the \`Images.xcasset\` folder on iOS, are placed in an assets' folder for Flutter. As with iOS, assets are any type of file, not just images. For example, you might have a JSON file located in the \`my-assets\` folder: \`\`\`plaintext my-assets/data.json \`\`\` Declare the asset in the \`pubspec.yaml\` file: \`\`\`yaml assets: - my-assets/data.json \`\`\` And then access it from code using an \[\`AssetBundle\`]\[]:

<!--?code-excerpt "lib/asset_bundle.dart"?-->

\`\`\`dart import 'dart:async' show Future; import 'package:flutter/services.dart' show rootBundle; Future loadAsset() async { return await rootBundle.loadString('my-assets/data.json'); } \`\`\` For images, Flutter follows a simple density-based format like iOS. Image assets might be \`1.0x\`, \`2.0x\`, \`3.0x\`, or any other multiplier. Flutter's \[\`devicePixelRatio\`]\[] expresses the ratio of physical pixels in a single logical pixel. Assets are located in any arbitrary folder— Flutter has no predefined folder structure. You declare the assets (with location) in the \`pubspec.yaml\` file, and Flutter picks them up. For example, to add an image called \`my\_icon.png\` to your Flutter project, you might decide to store it in a folder arbitrarily called \`images\`. Place the base image (1.0x) in the \`images\` folder, and the other variants in sub-folders named after the appropriate ratio multiplier: \`\`\`plaintext images/my\_icon.png // Base: 1.0x image images/2.0x/my\_icon.png // 2.0x image images/3.0x/my\_icon.png // 3.0x image \`\`\` Next, declare these images in the \`pubspec.yaml\` file: \`\`\`yaml assets: - images/my\_icon.png \`\`\` You can now access your images using \`AssetImage\`:

<!--?code-excerpt "lib/images.dart (asset-image)"?-->

\`\`\`dart image: AssetImage('images/a\_dot\_burr.png'), \`\`\` or directly in an \`Image\` widget:

<!--?code-excerpt "lib/images.dart (image-asset)"?-->

\`\`\`dart @override Widget build(BuildContext context) { return Image.asset('images/my\_image.png'); } \`\`\` For more details, see \[Adding Assets and Images in Flutter]\[]. ## Form input This section discusses how to use forms in Flutter and how they compare with UIKit. ### Retrieving user input Given how Flutter uses immutable widgets with a separate state, you might be wondering how user input fits into the picture. In UIKit, you usually query the widgets for their current values when it's time to submit the user input, or action on it. How does that work in Flutter? In practice forms are handled, like everything in Flutter, by specialized widgets. If you have a \`TextField\` or a \`TextFormField\`, you can supply a \[\`TextEditingController\`]\[] to retrieve user input:

<!--?code-excerpt "lib/form.dart (my-form-state)"?-->

\`\`\`dart class \_MyFormState extends State { // 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 disposing of the Widget. myController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('Retrieve Text Input')), body: Padding( padding: const EdgeInsets.all(16), child: TextField(controller: myController), ), floatingActionButton: FloatingActionButton( // When the user presses the button, show an alert dialog with the // text the user has typed into our text field. onPressed: () { showDialog( context: context, builder: (context) { return AlertDialog( // Retrieve the text the user has typed in using our // TextEditingController. content: Text(myController.text), ); }, ); }, tooltip: 'Show me the value!', child: const Icon(Icons.text\_fields), ), ); } } \`\`\` You can find more information and the full code listing in \[Retrieve the value of a text field]\[]. ### Placeholder in a text field In Flutter, you can easily show a "hint" or a placeholder text for your field by adding an \`InputDecoration\` object to the decoration constructor parameter for the \`Text\` widget:

<!--?code-excerpt "lib/form.dart (input-hint)" replace="/return const //g;/;//g"?-->

\`\`\`dart Center( child: TextField(decoration: InputDecoration(hintText: 'This is a hint')), ) \`\`\` ### Showing validation errors Just as you would with a "hint", pass an \`InputDecoration\` object to the decoration constructor for the \`Text\` widget. However, you don't want to start off by showing an error. Instead, when the user has entered invalid data, update the state, and pass a new \`InputDecoration\` object.

<!--?code-excerpt "lib/validation_errors.dart"?-->

\`\`\`dart import 'package:material\_ui/material\_ui.dart'; void main() { runApp(const SampleApp()); } class SampleApp extends StatelessWidget { const SampleApp({super.key}); // This widget is the root of your application. @override Widget build(BuildContext context) { return const MaterialApp(title: 'Sample App', home: SampleAppPage()); } } class SampleAppPage extends StatefulWidget { const SampleAppPage({super.key}); @override State createState() => \_SampleAppPageState(); } class \_SampleAppPageState extends State { String? \_errorText; bool isEmail(String em) { String emailRegexp = r'^((\[^<>()\[\\]\\\\.,;:\s@\\"]+(\\.\[^<>()\[\\]\\\\.,;:\s@\\"]+)\*)|' r'(\\".+\\"))@((\\\[\[0-9]{1,3}\\.\[0-9]{1,3}\\.\[0-9]{1,3}\\.\[0-9]{1,3}\\])|' r'((\[a-zA-Z\\-0-9]+\\.)+\[a-zA-Z]{2,}))$'; RegExp regExp = RegExp(emailRegexp); return regExp.hasMatch(em); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('Sample App')), body: Center( child: TextField( onSubmitted: (text) { setState(() { if (!isEmail(text)) { \_errorText = 'Error: This is not an email'; } else { \_errorText = null; } }); }, decoration: InputDecoration( hintText: 'This is a hint', errorText: \_errorText, ), ), ), ); } } \`\`\` ## Threading & asynchronicity This section discusses concurrency in Flutter and how it compares with UIKit. ### Writing asynchronous code Dart has a single-threaded execution model, with support for \`Isolate\`s (a way to run Dart code on another thread), an event loop, and asynchronous programming. Unless you spawn an \`Isolate\`, your Dart code runs in the main UI thread and is driven by an event loop. Flutter's event loop is equivalent to the iOS main loop—that is, the \`Looper\` that is attached to the main thread. Dart's single-threaded model doesn't mean you are required to run everything as a blocking operation that causes the UI to freeze. Instead, use the asynchronous facilities that the Dart language provides, such as \`async\`/\`await\`, to perform asynchronous work. For example, you can run network code without causing the UI to hang by using \`async\`/\`await\` and letting Dart do the heavy lifting:

<!--?code-excerpt "lib/async.dart (load-data)"?-->

\`\`\`dart Future loadData() async { final Uri dataURL = Uri.parse('https\://jsonplaceholder.typicode.com/posts'); final http.Response response = await http.get(dataURL); setState(() { data = (jsonDecode(response.body) as List).cast>(); }); } \`\`\` Once the \`await\`ed network call is done, update the UI by calling \`setState()\`, which triggers a rebuild of the widget subtree and updates the data. The following example loads data asynchronously and displays it in a \`ListView\`:

<!--?code-excerpt "lib/async.dart"?-->

\`\`\`dart import 'dart:convert'; import 'package:http/http.dart' as http; import 'package:material\_ui/material\_ui.dart'; void main() { runApp(const SampleApp()); } class SampleApp extends StatelessWidget { const SampleApp({super.key}); @override Widget build(BuildContext context) { return const MaterialApp(title: 'Sample App', home: SampleAppPage()); } } class SampleAppPage extends StatefulWidget { const SampleAppPage({super.key}); @override State createState() => \_SampleAppPageState(); } class \_SampleAppPageState extends State { List> data = \[]; @override void initState() { super.initState(); loadData(); } Future loadData() async { final Uri dataURL = Uri.parse('https\://jsonplaceholder.typicode.com/posts'); final http.Response response = await http.get(dataURL); setState(() { data = (jsonDecode(response.body) as List).cast>(); }); } Widget getRow(int index) { return Padding( padding: const EdgeInsets.all(10), child: Text('Row ${data\[index]\['title']}'), ); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('Sample App')), body: ListView\.builder( itemCount: data.length, itemBuilder: (context, index) { return getRow(index); }, ), ); } } \`\`\` Refer to the next section for more information on doing work in the background, and how Flutter differs from iOS. ### Moving to the background thread Since Flutter is single threaded and runs an event loop (like Node.js), you don't have to worry about thread management or spawning background threads. If you're doing I/O-bound work, such as disk access or a network call, then you can safely use \`async\`/\`await\` and you're done. If, on the other hand, you need to do computationally intensive work that keeps the CPU busy, you want to move it to an \`Isolate\` to avoid blocking the event loop. For I/O-bound work, declare the function as an \`async\` function, and \`await\` on long-running tasks inside the function:

<!--?code-excerpt "lib/async.dart (load-data)"?-->

\`\`\`dart Future loadData() async { final Uri dataURL = Uri.parse('https\://jsonplaceholder.typicode.com/posts'); final http.Response response = await http.get(dataURL); setState(() { data = (jsonDecode(response.body) as List).cast>(); }); } \`\`\` This is how you typically do network or database calls, which are both I/O operations. However, there are times when you might be processing a large amount of data and your UI hangs. In Flutter, use \`Isolate\`s to take advantage of multiple CPU cores to do long-running or computationally intensive tasks. Isolates are separate execution threads that do not share any memory with the main execution memory heap. This means you can't access variables from the main thread, or update your UI by calling \`setState()\`. Isolates are true to their name, and cannot share memory (in the form of static fields, for example). The following example shows, in a simple isolate, how to share data back to the main thread to update the UI.

<!--?code-excerpt "lib/isolates.dart (load-data)"?-->

\`\`\`dart Future loadData() async { final ReceivePort receivePort = ReceivePort(); await Isolate.spawn(dataLoader, receivePort.sendPort); // The 'echo' isolate sends its SendPort as the first message. final SendPort sendPort = await receivePort.first as SendPort; final List> msg = await sendReceive( sendPort, 'https\://jsonplaceholder.typicode.com/posts', ); setState(() { data = msg; }); } // The entry point for the isolate. static Future dataLoader(SendPort sendPort) async { // Open the ReceivePort for incoming messages. final ReceivePort port = ReceivePort(); // Notify any other isolates what port this isolate listens to. sendPort.send(port.sendPort); await for (final dynamic msg in port) { final String url = msg\[0] as String; final SendPort replyTo = msg\[1] as SendPort; final Uri dataURL = Uri.parse(url); final http.Response response = await http.get(dataURL); // Lots of JSON to parse replyTo.send(jsonDecode(response.body) as List>); } } Future>> sendReceive(SendPort port, String msg) { final ReceivePort response = ReceivePort(); port.send(\[msg, response.sendPort]); return response.first as Future>>; } \`\`\` Here, \`dataLoader()\` is the \`Isolate\` that runs in its own separate execution thread. In the isolate, you can perform more CPU intensive processing (parsing a big JSON, for example), or perform computationally intensive math, such as encryption or signal processing. You can run the full example below:

<!--?code-excerpt "lib/isolates.dart"?-->

\`\`\`dart import 'dart:async'; import 'dart:convert'; import 'dart:isolate'; import 'package:http/http.dart' as http; import 'package:material\_ui/material\_ui.dart'; void main() { runApp(const SampleApp()); } class SampleApp extends StatelessWidget { const SampleApp({super.key}); @override Widget build(BuildContext context) { return const MaterialApp(title: 'Sample App', home: SampleAppPage()); } } class SampleAppPage extends StatefulWidget { const SampleAppPage({super.key}); @override State createState() => \_SampleAppPageState(); } class \_SampleAppPageState extends State { List> data = \[]; @override void initState() { super.initState(); loadData(); } bool get showLoadingDialog => data.isEmpty; Future loadData() async { final ReceivePort receivePort = ReceivePort(); await Isolate.spawn(dataLoader, receivePort.sendPort); // The 'echo' isolate sends its SendPort as the first message. final SendPort sendPort = await receivePort.first as SendPort; final List> msg = await sendReceive( sendPort, 'https\://jsonplaceholder.typicode.com/posts', ); setState(() { data = msg; }); } // The entry point for the isolate. static Future dataLoader(SendPort sendPort) async { // Open the ReceivePort for incoming messages. final ReceivePort port = ReceivePort(); // Notify any other isolates what port this isolate listens to. sendPort.send(port.sendPort); await for (final dynamic msg in port) { final String url = msg\[0] as String; final SendPort replyTo = msg\[1] as SendPort; final Uri dataURL = Uri.parse(url); final http.Response response = await http.get(dataURL); // Lots of JSON to parse replyTo.send(jsonDecode(response.body) as List>); } } Future>> sendReceive(SendPort port, String msg) { final ReceivePort response = ReceivePort(); port.send(\[msg, response.sendPort]); return response.first as Future>>; } Widget getBody() { bool showLoadingDialog = data.isEmpty; if (showLoadingDialog) { return getProgressDialog(); } else { return getListView(); } } Widget getProgressDialog() { return const Center(child: CircularProgressIndicator()); } ListView getListView() { return ListView\.builder( itemCount: data.length, itemBuilder: (context, position) { return getRow(position); }, ); } Widget getRow(int i) { return Padding( padding: const EdgeInsets.all(10), child: Text("Row ${data\[i]\["title"]}"), ); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('Sample App')), body: getBody(), ); } } \`\`\` ### Making network requests Making a network call in Flutter is easy when you use the popular \[\`http\` package]\[]. This abstracts away a lot of the networking that you might normally implement yourself, making it simple to make network calls. To add the \`http\` package as a dependency, run \`flutter pub add\`: \`\`\`console flutter pub add http \`\`\` To make a network call, call \`await\` on the \`async\` function \`http.get()\`:

<!--?code-excerpt "lib/progress.dart (load-data)"?-->

\`\`\`dart Future loadData() async { final Uri dataURL = Uri.parse('https\://jsonplaceholder.typicode.com/posts'); final http.Response response = await http.get(dataURL); setState(() { data = (jsonDecode(response.body) as List).cast>(); }); } \`\`\` ### Showing the progress on long-running tasks In UIKit, you typically use a \`UIProgressView\` while executing a long-running task in the background. In Flutter, use a \`ProgressIndicator\` widget. Show the progress programmatically by controlling when it's rendered through a boolean flag. Tell Flutter to update its state before your long-running task starts, and hide it after it ends. In the example below, the build function is separated into three different functions. If \`showLoadingDialog\` is \`true\` (when \`widgets.length == 0\`), then render the \`ProgressIndicator\`. Otherwise, render the \`ListView\` with the data returned from a network call.

<!--?code-excerpt "lib/progress.dart"?-->

\`\`\`dart import 'dart:convert'; import 'package:http/http.dart' as http; import 'package:material\_ui/material\_ui.dart'; void main() { runApp(const SampleApp()); } class SampleApp extends StatelessWidget { const SampleApp({super.key}); @override Widget build(BuildContext context) { return const MaterialApp(title: 'Sample App', home: SampleAppPage()); } } class SampleAppPage extends StatefulWidget { const SampleAppPage({super.key}); @override State createState() => \_SampleAppPageState(); } class \_SampleAppPageState extends State { List> data = \[]; @override void initState() { super.initState(); loadData(); } bool get showLoadingDialog => data.isEmpty; Future loadData() async { final Uri dataURL = Uri.parse('https\://jsonplaceholder.typicode.com/posts'); final http.Response response = await http.get(dataURL); setState(() { data = (jsonDecode(response.body) as List).cast>(); }); } Widget getBody() { if (showLoadingDialog) { return getProgressDialog(); } return getListView(); } Widget getProgressDialog() { return const Center(child: CircularProgressIndicator()); } ListView getListView() { return ListView\.builder( itemCount: data.length, itemBuilder: (context, index) { return getRow(index); }, ); } Widget getRow(int i) { return Padding( padding: const EdgeInsets.all(10), child: Text("Row ${data\[i]\["title"]}"), ); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('Sample App')), body: getBody(), ); } } \`\`\` \[Flutter for SwiftUI developers]: /flutter-for/swiftui-devs \[Add Flutter to existing app]: /add-to-app \[Adding Assets and Images in Flutter]: /ui/assets/assets-and-images \[Animation & Motion widgets]: /ui/widgets/animation \[Animations overview]: /ui/animations \[Animations tutorial]: /ui/animations/tutorial \[Apple's iOS design language]: https\://developer.apple.com/design/resources \[\`AppLifecycleState\` documentation]: https\://api.flutter.dev/flutter/dart-ui/AppLifecycleState.html \[arb]: https\://github.com/googlei18n/app-resource-bundle \[\`AssetBundle\`]: https\://api.flutter.dev/flutter/services/AssetBundle-class.html \[composing]: /resources/architectural-overview#composition \[Cupertino library]: https\://pub.dev/documentation/cupertino\_ui/latest/cupertino\_ui \[Cupertino widgets]: /ui/widgets/cupertino \[\`devicePixelRatio\`]: https\://api.flutter.dev/flutter/dart-ui/FlutterView/devicePixelRatio.html \[existing plugin]: https\://pub.dev/flutter \[Flutter concurrency for Swift developers]: /flutter-for/dart-swift-concurrency \[\`http\` package]: https\://pub.dev/packages/http \[Human Interface Guidelines]: https\://developer.apple.com/ios/human-interface-guidelines/overview/themes/ \[internationalization guide]: /ui/internationalization \[\`intl\`]: https\://pub.dev/packages/intl \[\`intl\_translation\`]: https\://pub.dev/packages/intl\_translation \[Introduction to declarative UI]: /flutter-for/declarative \[layout tutorial]: /ui/widgets/layout \[\`Localizations\`]: https\://api.flutter.dev/flutter/widgets/Localizations-class.html \[Material Components]: https\://m3.material.io/develop/flutter/ \[Material Design guidelines]: https\://m3.material.io/styles/ \[optimized for all platforms]: https\://m2.material.io/design/platform-guidance/cross-platform-adaptation.html#cross-platform-guidelines \[Platform adaptations]: /platform-integration/platform-adaptations \[platform channel]: /platform-integration/platform-channels \[pub.dev]: https\://pub.dev/flutter/packages \[Retrieve the value of a text field]: /cookbook/forms/retrieve-input \[\`TextEditingController\`]: https\://api.flutter.dev/flutter/widgets/TextEditingController-class.html \[\`url\_launcher\`]: https\://pub.dev/packages/url\_launcher \[widget]: /resources/architectural-overview#widgets \[widget catalog]: /ui/widgets/layout \[\`Window\.locale\`]: https\://api.flutter.dev/flutter/dart-ui/Window/locale.html \[Learning Dart as a Swift Developer]: https\://dart.dev/guides/language/coming-from/swift-to-dart

----
url: https://docs.flutter.dev/platform-integration/ios/restore-state-ios
----

When a user runs a mobile app and then selects another app to run, the first app is moved to the background, or *backgrounded*. The operating system (both iOS and Android) often kills the backgrounded app to release memory or improve performance for the app running in the foreground.

You can use the [`RestorationManager`](https://api.flutter.dev/flutter/services/RestorationManager-class.html) (and related) classes to handle state restoration. An iOS app requires [a bit of extra setup](https://api.flutter.dev/flutter/services/RestorationManager-class.html#state-restoration-on-ios) in Xcode, but the restoration classes otherwise work the same on both iOS and Android.

For more information, check out [State restoration on Android](/platform-integration/android/restore-state-android).

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/platform-integration/ios/restore-state-ios.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/platform-integration/ios/restore-state-ios\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/platform-integration/ios/restore-state-ios.md "Report an issue with this page").

----
url: https://docs.flutter.dev/packages-and-plugins/favorites.md
----

\# Flutter Favorite program > Guidelines for identifying a plugin or package as a Flutter Favorite. !\[The Flutter Favorite program logo]\(/assets/images/docs/development/packages-and-plugins/FlutterFavoriteLogo.png){:width="20%"} The aim of the \*\*Flutter Favorite\*\* program is to identify packages and plugins that you should first consider when building your app. This is not a guarantee of quality or suitability to your particular situation—you should always perform your own evaluation of packages and plugins for your project. You can see the complete list of \[Flutter Favorite packages]\[] on pub.dev. :::note If you came here looking for the Happy Paths recommendations, we have discontinued that project in favor of Flutter Favorites. ::: ## Metrics Flutter Favorite packages have passed high quality standards using the following metrics: \* \[Overall package score]\[] \* \*\*Permissive license\*\*, including (but not limited to) Apache, Artistic, BSD, CC BY, MIT, MS-PL and W3C \* GitHub \*\*version tag\*\* matches the current version from pub.dev, so you can see exactly what source is in the package \* Feature \*\*completeness\*\*—and not marked as incomplete (for example, with labels like "beta" or "under construction") \* \[Verified publisher]\[] \* General \*\*usability\*\* when it comes to the overview, docs, sample/example code, and API quality \* Good \*\*runtime behavior\*\* in terms of CPU and memory usage \* High quality \*\*dependencies\*\* ## Flutter Ecosystem Committee The Flutter Ecosystem Committee is comprised of Flutter team members and Flutter community members spread across its ecosystem. One of their jobs is to decide when a package has met the quality bar to become a Flutter Favorite. The current committee members (ordered alphabetically by last name) are as follows: \* Abdallah Shaban \* Pooja Bhaumik \* Hillel Coren \* Majid Hajian \* Simon Lightfoot \* John Ryan \* Diego Velasquez If you'd like to nominate a package or plugin as a potential future Flutter Favorite, or would like to bring any other issues to the attention of the committee, \[send the committee]\[] an email. ## Flutter Favorite usage guidelines Flutter Favorite packages are labeled as such on pub.dev by the Flutter team. If you own a package that has been designated as a Flutter Favorite, you must adhere to the following guidelines: \* Flutter Favorite package authors can place the Flutter Favorite logo in the package's GitHub README, on the package's pub.dev \*\*Overview\*\* tab, and on social media as related to posts about that package. \* We encourage you to use the \*\*#FlutterFavorite\*\* hashtag in social media. \* When using the Flutter Favorite logo, the author must link to (this) Flutter Favorite landing page, to provide context for the designation. \* If a Flutter Favorite package loses its Flutter Favorite status, the author will be notified, at which point the author must immediately remove all uses of "Flutter Favorite" and the Flutter Favorite logo from the affected package. \* Don't alter, distort, or modify the Flutter Favorite logo in any way, including displaying the logo with color variations or unapproved visual elements. \* Don't display the Flutter Favorite logo in a manner that is misleading, unfair, defamatory, infringing, libelous, disparaging, obscene, or otherwise objectionable to Google. ## What's next You should expect the list of Flutter Favorite packages to grow and change as the ecosystem continues to thrive. The committee will continue working with package authors to increase quality, as well as consider other areas of the ecosystem that could benefit from the Flutter Favorite program, such as tools, consulting firms, and prolific Flutter contributors. As the Flutter ecosystem grows, we'll be looking at expanding the set of metrics, which might include the following: \* Use of the \[pubspec.yaml format]\[] that clearly indicates which platforms a plugin supports. \* Support for the latest stable version of Flutter. \* Support for AndroidX. \* Support for multiple platforms, such as web, macOS, Windows, Linux, etc. \* Integration as well as unit test coverage. ## Flutter Favorites You can see the complete list of \[Flutter Favorite packages]\[] on pub.dev. \[send the committee]: mailto:flutter-committee\@googlegroups.com \[Flutter Favorite packages]: https\://pub.dev/flutter/favorites \[Overall package score]: https\://pub.dev/help \[pubspec.yaml format]: /packages-and-plugins/developing-packages#plugin-platforms \[Verified publisher]: https\://dart.dev/tools/pub/verified-publishers

----
url: https://docs.flutter.dev/cookbook/forms/text-input
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Create and style a text field](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

Text fields allow users to type text into an app. They 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 Material Design text fields: [`TextField`](https://pub.dev/documentation/material_ui/latest/material_ui/TextField-class.html) and [`TextFormField`](https://pub.dev/documentation/material_ui/latest/material_ui/TextFormField-class.html).

## `TextField`

[#](#textfield)

[`TextField`](https://pub.dev/documentation/material_ui/latest/material_ui/TextField-class.html) is the most commonly used text input widget.

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`](https://pub.dev/documentation/material_ui/latest/material_ui/InputDecoration-class.html) as the [`decoration`](https://pub.dev/documentation/material_ui/latest/material_ui/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`.

dart

```
TextField(
  decoration: InputDecoration(
    border: OutlineInputBorder(),
    hintText: 'Enter a search term',
  ),
),
```

To retrieve the value when it changes, consult the [Handle changes to a text field](/cookbook/forms/text-field-changes) recipe.

## `TextFormField`

[#](#textformfield)

[`TextFormField`](https://pub.dev/documentation/material_ui/latest/material_ui/TextFormField-class.html) wraps a `TextField` and integrates it with the enclosing [`Form`](https://api.flutter.dev/flutter/widgets/Form-class.html). This provides additional functionality, such as validation and integration with other [`FormField`](https://api.flutter.dev/flutter/widgets/FormField-class.html) widgets.

dart

```
TextFormField(
  decoration: const InputDecoration(
    border: UnderlineInputBorder(),
    labelText: 'Enter your username',
  ),
),
```

## Interactive example

[#](#interactive-example)

```
import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    const appTitle = 'Form Styling Demo';
    return MaterialApp(
      title: appTitle,
      home: Scaffold(
        appBar: AppBar(title: const Text(appTitle)),
        body: const MyCustomForm(),
      ),
    );
  }
}

class MyCustomForm extends StatelessWidget {
  const MyCustomForm({super.key});

  @override
  Widget build(BuildContext context) {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        const Padding(
          padding: EdgeInsets.symmetric(horizontal: 8, vertical: 16),
          child: TextField(
            decoration: InputDecoration(
              border: OutlineInputBorder(),
              hintText: 'Enter a search term',
            ),
          ),
        ),
        Padding(
          padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 16),
          child: TextFormField(
            decoration: const InputDecoration(
              border: UnderlineInputBorder(),
              labelText: 'Enter your username',
            ),
          ),
        ),
      ],
    );
  }
}
```

To learn more about input validation, consult the [Build a form with validation](/cookbook/forms/validation) recipe.

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-10-06. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/forms/text-input.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/cookbook/forms/text-input\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/forms/text-input.md "Report an issue with this page").

----
url: https://docs.flutter.dev/install/with-vs-code
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Install Flutter using VS Code](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

Learn how to install and set up Flutter in a Code OSS-based editor. This includes (but is not limited to), [VS Code](https://code.visualstudio.com), [Antigravity](https://antigravity.google/), [Cursor](https://cursor.com/), and [Windsurf](https://windsurf.com/).

## Choose your development platform

[#](#dev-platform)

The instructions on this page are configured to cover installing Flutter on a **Windows** device.

If you'd like to follow the instructions for a different OS, please select one of the following.

## Download prerequisite software

[#](#download-prerequisites)

For the smoothest Flutter setup, first install the following tools.

1. ### Set up Linux support

   If you haven't set up Linux support on your Chromebook before, [Turn on Linux support](https://support.google.com/chromebook/answer/9145439).

   If you've already turned on Linux support, ensure it's up to date following the [Fix problems with Linux](https://support.google.com/chromebook/answer/9145439?hl=en#:~:text=Fix%20problems%20with%20Linux) instructions.

2. ### Download and install prerequisite packages

   ```
   $ sudo apt-get update -y && sudo apt-get upgrade -y
   $ sudo apt-get install -y curl git unzip xz-utils zip libglu1-mesa
   ```

3. ### Download and install Visual Studio Code

   To quickly install Flutter, then edit and debug your apps, [install and set up Visual Studio Code](https://code.visualstudio.com/docs/setup/setup-overview).

1) ### Install the Xcode command-line tools

   Download the Xcode command-line tools to get access to the command-line tools that Flutter relies on, including Git.

   To download the tools, run the following command in your preferred terminal:

   ```
   $ xcode-select --install
   ```

   If you haven't installed the tools already, a dialog should open that confirms you'd like to install them. Click **Install**, then once the installation is complete, click **Done**.

2) ### Download and install Visual Studio Code

   To quickly install Flutter, then edit and debug your apps, [install and set up Visual Studio Code](https://code.visualstudio.com/docs/setup/setup-overview).

1. ### Install Git for Windows

   Download and install the latest version of [Git for Windows](https://git-scm.com/downloads/win).

   For help installing or troubleshooting Git, reference the [Git documentation](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git).

2. ### Download and install Visual Studio Code

   To quickly install Flutter, then edit and debug your apps, [install and set up Visual Studio Code](https://code.visualstudio.com/docs/setup/setup-overview).

1) ### Download and install prerequisite packages

   ```
   $ sudo apt-get update -y && sudo apt-get upgrade -y
   $ sudo apt-get install -y curl git unzip xz-utils zip libglu1-mesa
   ```

2) ### Download and install Visual Studio Code

   To quickly install Flutter, then edit and debug your apps, [install and set up Visual Studio Code](https://code.visualstudio.com/docs/setup/setup-overview).

## Install and set up Flutter

[#](#install-flutter)

Now that you've installed Git and VS Code, follow these steps to use VS Code to install and set up Flutter.

1. ### Launch VS Code

   If not already open, open VS Code by searching for it with Spotlight or opening it manually from the directory where it's installed.

2. ### Add the Flutter extension to VS Code

   To add the Dart and Flutter extensions to VS Code, visit the [Flutter extension's marketplace page](https://marketplace.visualstudio.com/items?itemName=Dart-Code.flutter), and click **Install**. If prompted by your browser, allow it to open VS Code.

3. ### Install Flutter with VS Code

   1. Open the command palette in VS Code.

      Go to **View** > **Command Palette** or press `Cmd/Ctrl` + `Shift` + `P`.

   2. In the command palette, type `flutter`.

   3. Select **Flutter: New Project**.

   4. VS Code prompts you to locate the Flutter SDK on your computer. Select **Download SDK**.

   5. When the **Select Folder for Flutter SDK** dialog displays, choose where you want to install Flutter.

   6. Click **Clone Flutter**.

      While downloading Flutter, VS Code displays this pop-up notification:

      ```
      Downloading the Flutter SDK. This may take a few minutes.
      ```

      This download takes a few minutes. If you suspect that the download has hung, click **Cancel** then start the installation again.

   7. Click **Add SDK to PATH**.

      When successful, a notification displays:

      ```
      The Flutter SDK was added to your PATH
      ```

   8. VS Code might display a Google Analytics notice.

      If you agree, click **OK**.

   9. To ensure that Flutter is available in all terminals:

      1. Close, then reopen all terminal windows.
      2. Restart VS Code.

4. ### Validate your setup

   To ensure you installed Flutter correctly, run `flutter doctor -v` in your preferred terminal.

   If the command isn't found or there's an error, check out [Flutter installation troubleshooting](/install/troubleshoot).

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-07-31. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/install/with-vs-code.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/install/with-vs-code\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/install/with-vs-code.md "Report an issue with this page").

----
url: https://docs.flutter.dev/ai/genui/components
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top GenUI SDK main components and concepts](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

## Main components

[#](#main-components)

The [`genui`](https://pub.dev/packages/genui) package is built around the following main components:

* `Conversation`

  The primary facade and entry point for the package. It includes the `SurfaceController` class, manages the conversation history, and orchestrates the entire generative UI process.

* `Catalog`

  A collection of `CatalogItem` objects that defines the set of widgets that the AI is allowed to use. The `SurfaceController` supports multiple catalogs, allowing you to organize your widgets into logical groups. Each `CatalogItem` specifies a widget's name (for the AI to reference), a data schema for its properties, and a builder function to render the Flutter widget.

* `DataModel`

  A centralized, observable store for all dynamic UI state. Widgets are *bound* to data in this model. When data changes, only the widgets that depend on that specific piece of data are rebuilt.

* `A2uiTransportAdapter`

  A bridge that parses raw text streams coming from your LLM into `A2uiMessage` commands for the `SurfaceController`.

* `A2uiMessage`

  A message sent from the AI (parsed by the `A2uiTransportAdapter`) to the UI, instructing it to perform actions like `createSurface`, `surfaceUpdate`, `dataModelUpdate`, or `deleteSurface`.

* `SurfaceController`

  Handles the processing of `A2uiMessage`s, manages the `DataModel`, and maintains the state of UI surfaces.

## How it works

[#](#how-it-works)

The `Conversation` manages the interaction cycle:

1. **User input**

   The user provides a prompt (for example, through a text field). The app calls `conversation.sendMessage()`.

2. **AI invocation**

   The `Conversation` sends the user's message to the LLM SDK.

3. **AI response**

   The LLM, guided by the widget schemas provided in its system prompt, sends back responses.

4. **Stream handling**

   The text stream from the LLM SDK is fed into the `A2uiTransportAdapter`.

5. **UI state update**

   `A2uiMessages` parsed by the adapter are passed to `SurfaceController.handleMessage()`, which updates the UI state and `DataModel`.

6. **UI rendering**

   The `SurfaceController` broadcasts an update, and any `Surface` widgets listening for that surface ID will rebuild. Widgets are bound to the `DataModel`, so they update automatically when their data changes.

7. **Callbacks**

   Text responses and errors trigger callbacks on the `Conversation` or are handled by your specific LLM integration flow.

8. **User interaction**

   The user interacts with the newly generated UI (for example, by typing in a text field). This interaction directly updates the `DataModel`. If the interaction is an action (like a button click), the `Surface` captures the event and forwards it to the `SurfaceController`, which automatically creates a new `UserMessage` containing the current state of the data model and restarts the cycle.

For more detailed information on the implementation of GenUI SDK for Flutter, check out the [design doc](https://github.com/flutter/genui/blob/main/packages/genui/DESIGN.md).

The next section walks you through adding `genui` to your app.

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-08-19. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/ai/genui/components.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/ai/genui/components\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/ai/genui/components.md "Report an issue with this page").

----
url: https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.37.2
----

The 2.37.2 release of the Dart and Flutter DevTools includes the following changes among other general improvements. To learn more about DevTools, check out the [DevTools overview](/tools/devtools/overview).

## General updates

[#](#general-updates)

* Improved messaging when a screen is unavailable for the platform of the connected app. - [#7958](https://github.com/flutter/devtools/pull/7958)
* Fixed a bug where an infinite spinner was shown upon app disconnect. - [#7992](https://github.com/flutter/devtools/pull/7992)
* Fixed a bug where trying to reuse a disconnected DevTools instance would fail. - [#8009](https://github.com/flutter/devtools/pull/8009)

## Performance updates

[#](#performance-updates)

* Removed the "Raster Stats" feature. This tool did not work for the Impeller rendering engine, and the information it gave for the SKIA rendering engine was often misleading and unactionable. Users should follow the official Flutter guidance for [Performance and optimization](/perf) when debugging the rendering performance of their Flutter apps. - [#7981](https://github.com/flutter/devtools/pull/7981).

## Network profiler updates

[#](#network-profiler-updates)

* Fixed an issue where socket statistics were being reported as web sockets. - [#8061](https://github.com/flutter/devtools/pull/8061)

* Added query parameters to the request details view. - [#7825](https://github.com/flutter/devtools/pull/7825)

## VS Code Sidebar updates

[#](#vs-code-sidebar-updates)

* Added buttons for all DevTools tools in the sidebar by default, even when there are no debug sessions available. - [#7947](https://github.com/flutter/devtools/pull/7947)

## Full commit history

[#](#full-commit-history)

To find a complete list of changes in this release, check out the [DevTools git log](https://github.com/flutter/devtools/tree/v2.37.0).

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/release-notes/release-notes-2.37.2.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.37.2\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/release-notes/release-notes-2.37.2.md "Report an issue with this page").

----
url: https://docs.flutter.dev/add-to-app/macos/add-flutter-screen.md
----

\# Add a Flutter screen to an macOS app > Learn how to add a single Flutter screen to your existing macOS app. This guide describes how to add a single Flutter screen to an existing macOS app. ## Start a FlutterEngine and FlutterViewController To launch a Flutter screen from an existing macOS app, you start a \[\`FlutterEngine\`]\[] and a \[\`FlutterViewController\`]\[]. :::note The \`FlutterEngine\` serves as a host to the Dart VM and your Flutter runtime, and the \`FlutterViewController\` attaches to a \`FlutterEngine\` to pass input events into Flutter and to display frames rendered by the \`FlutterEngine\`. ::: The \`FlutterEngine\` might have the same lifespan as your \`FlutterViewController\` or outlive your \`FlutterViewController\`. :::tip It's generally recommended to pre-warm a long-lived \`FlutterEngine\` for your application because: \* The first frame appears faster when showing the \`FlutterViewController\`. \* Your Flutter and Dart state will outlive one \`FlutterViewController\`. \* Your application and your plugins can interact with Flutter and your Dart logic before showing the UI. ::: See \[Loading sequence and performance]\[] for more analysis on the latency and memory trade-offs of pre-warming an engine. ### Create a FlutterEngine Where you create a \`FlutterEngine\` depends on your host app. In this example, we create a \`FlutterEngine\` object inside a SwiftUI \[\`Observable\`]\[] object called \`FlutterDependencies\`. Pre-warm the engine by calling \`run()\`, and then inject this object into a \`ContentView\` using the \`environment()\` view modifier. \`\`\`swift title="MyApp.swift" import SwiftUI import FlutterMacOS // The following library connects plugins with macOS platform code to this app. import FlutterPluginRegistrant @Observable class FlutterDependencies { let flutterEngine = FlutterEngine(name: "my flutter engine", project: nil) init() { // Runs the default Dart entrypoint with a default Flutter route. flutterEngine.run(withEntrypoint: nil) // Connects plugins with macOS platform code to this app. RegisterGeneratedPlugins(registry: self.flutterEngine) } } @main struct MyApp: App { // flutterDependencies will be injected through the view environment. @State var flutterDependencies = FlutterDependencies() var body: some Scene { WindowGroup { ContentView() .environment(flutterDependencies) } } } \`\`\` As an example, we demonstrate creating a \`FlutterEngine\`, exposed as a property, on app startup in the app delegate. \`\`\`swift title="AppDelegate.swift" import Cocoa import FlutterMacOS // The following library connects plugins with macOS platform code to this app. import FlutterPluginRegistrant @main class AppDelegate: FlutterAppDelegate { lazy var flutterEngine = FlutterEngine(name: "my flutter engine", project: nil) override func applicationDidFinishLaunching(\_ aNotification: Notification) { flutterEngine.run(withEntrypoint: nil) RegisterGeneratedPlugins(registry: self.flutterEngine) } } \`\`\` ### Show a FlutterViewController with your FlutterEngine The following example shows a generic \`ContentView\` with a \[\`NavigationLink\`]\[] hooked to a flutter screen. First, create a \`FlutterViewControllerRepresentable\` to represent the \`FlutterViewController\`. The \`FlutterViewController\` constructor takes the pre-warmed \`FlutterEngine\` as an argument, which is injected through the view environment. \`\`\`swift title="ContentView\.swift" import SwiftUI import FlutterMacOS struct FlutterViewControllerRepresentable: NSViewControllerRepresentable { // Flutter dependencies are passed in through the view environment. @Environment(FlutterDependencies.self) var flutterDependencies func makeNSViewController(context: Context) -> FlutterViewController { return FlutterViewController( engine: flutterDependencies.flutterEngine, nibName: nil, bundle: nil ) } func updateNSViewController(\_ nsViewController: FlutterViewController, context: Context) {} } struct ContentView: View { var body: some View { NavigationStack { NavigationLink("My Flutter Feature") { FlutterViewControllerRepresentable() } } } } \`\`\` Now, you have a Flutter screen embedded in your macOS app. :::note In this example, your Dart \`main()\` entrypoint function runs when the \`FlutterDependencies\` observable is initialized. ::: The following example shows a generic \`ViewController\` with an \`NSButton\` hooked to present a \[\`FlutterViewController\`]\[]. The \`FlutterViewController\` uses the \`FlutterEngine\` instance created in the \`AppDelegate\`. \`\`\`swift title="ViewController.swift" import Cocoa import FlutterMacOS class ViewController: NSViewController { override func viewDidLoad() { super.viewDidLoad() // Make a button to call the showFlutter function when pressed. let button = NSButton(title: "Show Flutter!", target: self, action: #selector(showFlutter)) button.frame = CGRect(x: 202, y: 187, width: 160.0, height: 40.0) self.view\.addSubview(button) } @objc func showFlutter() { let flutterEngine = (NSApplication.shared.delegate as! AppDelegate).flutterEngine let flutterViewController = FlutterViewController(engine: flutterEngine, nibName: nil, bundle: nil) self.addChild(flutterViewController) flutterViewController.view\.frame = self.view\.bounds presentAsModalWindow(flutterViewController) } } \`\`\` Now, you have a Flutter screen embedded in your macOS app. :::note Using the previous example, the default \`main()\` entrypoint function of your default Dart library runs when calling \`run\` on the \`FlutterEngine\` created in the \`AppDelegate\`. ::: ### \_Alternatively\_ - Create a FlutterViewController with an implicit FlutterEngine As an alternative to the previous example, you can let the \`FlutterViewController\` implicitly create its own \`FlutterEngine\` without pre-warming one ahead of time. This is not usually recommended because creating a \`FlutterEngine\` on-demand could introduce a noticeable latency between when the \`FlutterViewController\` is presented and when it renders its first frame. This could, however, be useful if the Flutter screen is rarely shown, when there are no good heuristics to determine when the Dart VM should be started, and when Flutter doesn't need to persist state between view controllers. To let the \`FlutterViewController\` present without an existing \`FlutterEngine\`, omit the \`FlutterEngine\` construction, and create the \`FlutterViewController\` without an engine reference. \`\`\`swift title="ContentView\.swift" // Existing code omitted. func makeNSViewController(context: Context) -> FlutterViewController { return FlutterViewController() } \`\`\` \`\`\`swift title="ViewController.swift" // Existing code omitted. func showFlutter() { let flutterViewController = FlutterViewController() self.addChild(flutterViewController) flutterViewController.view\.frame = self.view\.bounds presentAsModalWindow(flutterViewController) } \`\`\` See \[Loading sequence and performance]\[] for more explorations on latency and memory usage. ## Using the FlutterAppDelegate Letting your application's \`UIApplicationDelegate\` subclass \`FlutterAppDelegate\` is recommended but not required. The \`FlutterAppDelegate\` performs functions such as: \* Forwarding application callbacks such as \[\`openURLs\`]\[] to plugins such as \[google\_sign\_in]\[]. ### Creating a FlutterAppDelegate subclass Creating a subclass of the \`FlutterAppDelegate\` in UIKit apps was shown in the \[Start a FlutterEngine and FlutterViewController section]\[]. In a SwiftUI app, you can create a subclass of the \`FlutterAppDelegate\` and annotate it with the \[\`Observable()\`]\[] macro as follows: \`\`\`swift title="MyApp.swift" import SwiftUI import FlutterMacOS @Observable class AppDelegate: FlutterAppDelegate { let flutterEngine = FlutterEngine(name: "my flutter engine", project: nil) override func applicationDidFinishLaunching(\_ aNotification: Notification) { // Runs the default Dart entrypoint with a default Flutter route. flutterEngine.run(withEntrypoint: nil) // Used to connect plugins (only if you have plugins // with macOS platform code). RegisterGeneratedPlugins(registry: self.flutterEngine) } } @main struct MyApp: App { // Use this property wrapper to tell SwiftUI // it should use the AppDelegate class for the application delegate @NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate var body: some Scene { WindowGroup { ContentView() } } } \`\`\` Then, in your view, the \`AppDelegate\` is accessible through the view environment. \`\`\`swift title="ContentView\.swift" import SwiftUI import FlutterMacOS struct FlutterViewControllerRepresentable: NSViewControllerRepresentable { // Access the AppDelegate through the view environment. @Environment(AppDelegate.self) var appDelegate func makeNSViewController(context: Context) -> FlutterViewController { return FlutterViewController( engine: appDelegate.flutterEngine, nibName: nil, bundle: nil ) } func updateNSViewController(\_ nsViewController: FlutterViewController, context: Context) {} } struct ContentView: View { var body: some View { NavigationStack { NavigationLink("My Flutter Feature") { FlutterViewControllerRepresentable() } } } } \`\`\` \[\`FlutterEngine\`]: https\://api.flutter.dev/macos-embedder/interface\_flutter\_engine.html \[\`FlutterViewController\`]: https\://api.flutter.dev/macos-embedder/interface\_flutter\_view\_controller.html \[Loading sequence and performance]: /add-to-app/performance \[google\_sign\_in]: https\://pub.dev/packages/google\_sign\_in \[\`openURLs\`]: https\://developer.apple.com/documentation/appkit/nsapplicationdelegate/application(\_:open:) \[Start a FlutterEngine and FlutterViewController section]:/add-to-app/macos/add-flutter-screen/#start-a-flutterengine-and-flutterviewcontroller \[\`Observable\`]: https\://developer.apple.com/documentation/observation/observable \[\`NavigationLink\`]: https\://developer.apple.com/documentation/swiftui/navigationlink \[\`Observable()\`]: https\://developer.apple.com/documentation/observation/observable()

----
url: https://docs.flutter.dev/install/manual.md
----

\# Install Flutter manually > Learn how to install and set up the Flutter SDK manually. Learn how to install and manually set up your Flutter development environment. :::tip If you're just looking to quickly install Flutter, consider \[installing Flutter with VS Code]\[with-vs-code] for a streamlined setup experience. ::: \[with-vs-code]: /install/with-vs-code ## Choose your development platform {: #dev-platform} The instructions on this page are configured to cover installing Flutter on a \*\*Windows\*\*{:.selected-os-text} device. If you'd like to follow the instructions for a different OS, please select one of the following. ## Download prerequisite software {: #download-prerequisites} Before installing the Flutter SDK, first complete the following setup. 1.

### Install Git for Windows

Download and install the latest version of \[Git for Windows]\[]. For help installing or troubleshooting Git, reference the \[Git documentation]\[git-install]. 1.

### Set up an editor or IDE

For the best experience developing Flutter apps, consider installing and setting up an \[editor or IDE with Flutter support]\[editors]{: target="\_blank"}. {: .steps .windows-only} 1.

### Install the Xcode command-line tools

Download the Xcode command-line tools to get access to the command-line tools that Flutter relies on, including Git. To download the tools, run the following command in your preferred terminal: \`\`\`console $ xcode-select --install \`\`\` If you haven't installed the tools already, a dialog should open that confirms you'd like to install them. Click \*\*Install\*\*, then once the installation is complete, click \*\*Done\*\*. 1.

### Set up an editor or IDE

For the best experience developing Flutter apps, consider installing and setting up an \[editor or IDE with Flutter support]\[editors]{: target="\_blank"}. {: .steps .macos-only} 1.

### Download and install prerequisite packages

Using your preferred package manager or mechanism, install the latest versions of the following packages: - \`curl\` - \`git\` - \`unzip\` - \`xz-utils\` - \`zip\` - \`libglu1-mesa\` On Debian-based distros with \`apt-get\`, such as Ubuntu, install these packages using the following commands: \`\`\`console $ sudo apt-get update -y && sudo apt-get upgrade -y $ sudo apt-get install -y curl git unzip xz-utils zip libglu1-mesa \`\`\` 1.

### Set up an editor or IDE

For the best experience developing Flutter apps, consider installing and setting up an \[editor or IDE with Flutter support]\[editors]{: target="\_blank"}. {: .steps .linux-only} 1.

### Set up Linux support

If you haven't set up Linux support on your Chromebook before, \[Turn on Linux support]\[chromeos-linux]. If you've already turned on Linux support, ensure it's up to date following the \[Fix problems with Linux]\[chromeos-linux-update] instructions. 1.

### Download and install prerequisite packages

Using \`apt-get\` or your preferred installation mechanism, install the latest versions of the following packages: - \`curl\` - \`git\` - \`unzip\` - \`xz-utils\` - \`zip\` - \`libglu1-mesa\` If you want to use \`apt-get\`, install these packages using the following commands: \`\`\`console $ sudo apt-get update -y && sudo apt-get upgrade -y $ sudo apt-get install -y curl git unzip xz-utils zip libglu1-mesa \`\`\` 1.

### Set up an editor or IDE

For the best experience developing Flutter apps, consider installing and setting up an \[editor or IDE with Flutter support]\[editors]{: target="\_blank"}. {: .steps .chromeos-only} \[editors]: /tools/editors \[Git for Windows]: https\://git-scm.com/downloads/win \[git-install]: https\://git-scm.com/book/en/v2/Getting-Started-Installing-Git \[chromeos-linux]: https\://support.google.com/chromebook/answer/9145439 \[chromeos-linux-update]: https\://support.google.com/chromebook/answer/9145439?hl=en#:\~:text=Fix%20problems%20with%20Linux ## Install and set up Flutter {: #install-flutter} To install the Flutter SDK, download the latest bundle from the SDK archive, then extract the SDK to where you want it stored. 1.

### Download the Flutter SDK bundle

Download the following installation bundle to get the latest stable release of the Flutter SDK. 1.

### Create a folder to store the SDK

Create or find a folder to store the extracted SDK in. Consider creating and using a directory at \`%USERPROFILE%\develop\` (\`C:\Users\\{username}\develop\`). :::note Select a location that doesn't have special characters or spaces in its path and doesn't require elevated privileges. ::: 1.

### Extract the SDK

Extract the SDK bundle you downloaded into the directory you want to store the Flutter SDK in. 1. Copy the following command. 1. Replace \`\` with the path to the bundle you downloaded. 1. Replace \`\` with the path to the folder you want the extracted SDK to be in. 1. Run the edited command in PowerShell: \`\`\`powershell Expand-Archive -Path -DestinationPath \`\`\` For example, if you downloaded the bundle for Flutter 3.47.5 into the \`$env:USERPROFILE\Downloads\` directory and want to store the extracted SDK in the \`$env:USERPROFILE\develop\` directory: \`\`\`powershell Expand-Archive \` -Path "$env:USERPROFILE\Downloads\flutter\_windows\_3.47.5-stable.zip" \` -DestinationPath "$env:USERPROFILE\develop" \`\`\` 1. Copy the following command. 1. Replace \`\` with the path to the bundle you downloaded. 1. Replace \`\` with the path to the folder you want the extracted SDK to be in. 1. Run the edited command in Command prompt: \`\`\`bat tar -xf -C \`\`\` For example, if you downloaded the bundle for Flutter 3.47.5 into the \`%USERPROFILE%\Downloads\` directory and want to store the extracted SDK in the \`%USERPROFILE%\develop\` directory: \`\`\`bat tar -xf "%USERPROFILE%\Downloads\flutter\_windows\_3.47.5-stable.zip" -C "%USERPROFILE%\develop" \`\`\` :::note If the \`flutter.bat\` file is missing from the \`bin\` directory after extraction, your antivirus software might have quarantined it. If this happens, configure your antivirus to trust the Flutter SDK directory, then extract the bundle again. ::: {: .steps .windows-only}

:::warning \*\*Note for Intel Macs:\*\* Flutter is deprecating support for Intel-based Macs (x64). Future Flutter releases will require Apple Silicon. For details, check out the \[macOS Intel deprecation strategy]\(https\://docs.google.com/document/d/1ty3js\_Eg2sNIbDuyYS\_aV7h4jYdx1hEpX\_mV135gO4s/edit?tab=t.0#heading=h.cx7d8y57ce6p). :::

1\.

### Download the Flutter SDK bundle

To choose the correct bundle, you need to know your Mac's processor type. \*(To check, go to \*\*Apple menu () > About This Mac\*\* and look at the \*\*Processor\*\* or \*\*Chip\*\* section.)\* Depending on your macOS device's cpu architecture, download one of the following installation bundles to get the latest stable release of the Flutter SDK. | Apple Silicon (ARM64) | Intel | |-----------------------------------------------------------------------|---------------------------------------------------------------------| | | | 1.

### Create a folder to store the SDK

Create or find a folder to store the extracted SDK in. Consider creating and using a directory at \`\~/develop/\`. 1.

### Extract the SDK

Extract the SDK bundle you downloaded into the directory you want to store the Flutter SDK in. 1. Copy the following command. 1. Replace \`\` with the path to the bundle you downloaded. 1. Replace \`\` with the path to the folder you want the extracted SDK to be in. 1. Run the edited command in your preferred terminal. \`\`\`console $ unzip -d \`\`\` For example, if you downloaded the bundle for Flutter 3.47.5 into the \`\~/Downloads\` directory and want to store the extracted SDK in the \`\~/develop\` directory: \`\`\`console $ unzip \~/Downloads/flutter\_macos\_3.47.5-stable.zip -d \~/develop/ \`\`\` {: .steps .macos-only} 1.

### Download the Flutter SDK bundle

Download the following installation bundle to get the latest stable release of the Flutter SDK. 1.

### Create a folder to store the SDK

Create or find a folder to store the extracted SDK in. Consider creating and using a directory at \`\~/develop/\`. 1.

### Extract the SDK

Extract the SDK bundle you downloaded into the directory you want to store the Flutter SDK in. 1. Copy the following command. 1. Replace \`\` with the path to the bundle you downloaded. 1. Replace \`\` with the path to the folder you want the extracted SDK to be in. 1. Run the edited command in your preferred terminal. \`\`\`console $ tar -xf -C \`\`\` For example, if you downloaded the bundle for Flutter 3.47.5 into the \`\~/Downloads\` directory and want to store the extracted SDK in the \`\~/develop\` directory: \`\`\`console $ tar -xf \~/Downloads/flutter\_linux\_3.47.5-stable.tar.xz -C \~/develop/ \`\`\` {: .steps .linux-only .chromeos-only} ## Add Flutter to your PATH {: #add-to-path} Now that you've downloaded the SDK, add the Flutter SDK's \`bin\` directory to your \`PATH\` environment variable. Adding Flutter to your \`PATH\` allows you to use the \`flutter\` and \`dart\` command-line tools in terminals and IDEs.

1\.

### Determine your Flutter SDK installation location

Copy the absolute path to the directory that you downloaded and extracted the Flutter SDK into. 1.

### Navigate to the environment variables settings

1\. Press `Windows` + `Pause`. If your keyboard lacks a `Pause` key, try `Windows` + `Fn` + `B`. The \*\*System > About\*\* dialog opens. 1. Click \*\*Advanced System Settings\*\* > \*\*Advanced\*\* > \*\*Environment Variables...\*\*. The \*\*Environment Variables\*\* dialog opens. 1.

### Add the Flutter SDK bin to your path

1\. In the \*\*User variables for (username)\*\* section of the \*\*Environment Variables\*\* dialog, look for the \*\*Path\*\* entry. 1. If the \*\*Path\*\* entry exists, double-click it. The \*\*Edit Environment Variable\*\* dialog should open. 1. Double-click inside an empty row. 1. Type the path to the \`bin\` directory of your Flutter installation. For example, if you downloaded Flutter into a \`develop\flutter\` folder inside your user directory, you'd type the following: \`\`\`plaintext %USERPROFILE%\develop\flutter\bin \`\`\` 1. Click the Flutter entry you added to select it. 1. Click \*\*Move Up\*\* until the Flutter entry sits at the top of the list. 1. To confirm your changes, click \*\*OK\*\* three times. {: type="a"} 1. If the entry doesn't exist, click \*\*New\...\*\*. The \*\*Edit Environment Variable\*\* dialog should open. 1. In the \*\*Variable Name\*\* box, type \`Path\`. 1. In the \*\*Variable Value\*\* box, type the path to the \`bin\` directory of your Flutter installation. For example, if you downloaded Flutter into a \`develop\flutter\` folder inside your user directory, you'd type the following: \`\`\`plaintext %USERPROFILE%\develop\flutter\bin \`\`\` 1. To confirm your changes, click \*\*OK\*\* three times. {: type="a"} 1.

### Apply your changes

To apply this change and get access to the \`flutter\` tool, close and reopen all open command prompts, sessions in your terminal apps, and IDEs. 1.

### Validate your setup

To ensure you successfully added the SDK to your \`PATH\`, open command prompt or your preferred terminal app, then try running the \`flutter\` and \`dart\` tools. \`\`\`console $ flutter --version $ dart --version \`\`\` If either command isn't found, check out \[Flutter installation troubleshooting]\[troubleshoot]. {: .steps} \[troubleshoot]: /install/troubleshoot

:::note The following steps assume you're using the \[default shell]\[zsh-mac] on macOS, Zsh. If you use another shell besides Zsh, check out \[this tutorial on setting your PATH]\[other-path]. ::: 1.

### Determine your Flutter SDK installation location

Copy the absolute path to the directory that you downloaded and extracted the Flutter SDK into. 1.

### Open or create the Zsh environment variable file

If it exists, open the \[Zsh environment variable file]\[zsh-files] \`\~/.zprofile\` in your preferred text editor. If it doesn't exist, create the \`\~/.zprofile\` file. 1.

### Add the Flutter SDK bin to your path

At the end of your \`\~/.zprofile\` file, use the built-in \`export\` command to update the \`PATH\` variable to include the \`bin\` directory of your Flutter installation. Replace \`\` with the path to your Flutter SDK installation. \`\`\`bash export PATH="/bin:$PATH" \`\`\` For example, if you downloaded Flutter into a \`develop/flutter\` folder inside your user directory, you'd add the following to the file: \`\`\`bash export PATH="$HOME/develop/flutter/bin:$PATH" \`\`\` 1.

### Save your changes

Save, then close, the \`\~/.zprofile\` file you edited. 1.

### Apply your changes

To apply this change and get access to the \`flutter\` tool, close and reopen all open Zsh sessions in your terminal apps and IDEs. 1.

### Validate your setup

To ensure you successfully added the SDK to your \`PATH\`, open a Zsh session in your preferred terminal, then try running the \`flutter\` and \`dart\` tools. \`\`\`console $ flutter --version $ dart --version \`\`\` If either command isn't found, check out \[Flutter installation troubleshooting]\[troubleshoot]. {: .steps} \[zsh-mac]: https\://support.apple.com/en-us/102360 \[zsh-files]: https\://zsh.sourceforge.io/Intro/intro\_3.html \[other-path]: https\://www\.cyberciti.biz/faq/unix-linux-adding-path/ \[troubleshoot]: /install/troubleshoot

1\.

### Determine your Flutter SDK installation location

Copy the absolute path to the directory that you downloaded and extracted the Flutter SDK into. 1.

### Determine your default shell

If you don't know what shell you use, check which shell starts when you open a new console window. \`\`\`console $ echo $SHELL \`\`\` 1.

### Add the Flutter SDK bin to your path

To add the \`bin\` directory of your Flutter installation to your \`PATH\`: 1. Expand the instructions for your default shell. 1. Copy the provided command. 1. Replace \`\` with the path to your Flutter SDK install. 1. Run the edited command in your preferred terminal with that shell.

***

Expand for `bash` instructions

\`\`\`console $ echo 'export PATH="/bin:$PATH"' >> \~/.bashrc \`\`\` For example, if you downloaded Flutter into a \`develop/flutter\` folder inside your user directory, you'd run the following: \`\`\`console $ echo 'export PATH="$HOME/develop/flutter/bin:$PATH"' >> \~/.bashrc \`\`\`

Expand for `zsh` instructions

\`\`\`console $ echo 'export PATH="/bin:$PATH"' >> \~/.zshenv \`\`\` For example, if you downloaded Flutter into a \`develop/flutter\` folder inside your user directory, you'd run the following: \`\`\`console $ echo 'export PATH="$HOME/develop/flutter/bin:$PATH"' >> \~/.zshenv \`\`\`

Expand for `fish` instructions

\`\`\`console $ fish\_add\_path -U -p /bin \`\`\` For example, if you downloaded Flutter into a \`develop/flutter\` folder inside your user directory, you'd run the following: \`\`\`console $ fish\_add\_path -U -p \~/develop/flutter/bin \`\`\`

Expand for `csh` instructions

\`\`\`console $ echo 'setenv PATH "/bin:$PATH"' >> \~/.cshrc \`\`\` For example, if you downloaded Flutter into a \`develop/flutter\` folder inside your user directory, you'd run the following: \`\`\`console $ echo 'setenv PATH "$HOME/develop/flutter/bin:$PATH"' >> \~/.cshrc \`\`\`

Expand for `tcsh` instructions

\`\`\`console $ echo 'setenv PATH "/bin:$PATH"' >> \~/.tcshrc \`\`\` For example, if you downloaded Flutter into a \`develop/flutter\` folder inside your user directory, you'd run the following: \`\`\`console $ echo 'setenv PATH "$HOME/develop/flutter/bin:$PATH"' >> \~/.tcshrc \`\`\`

Expand for `ksh` instructions

\`\`\`console $ echo 'export PATH="/bin:$PATH"' >> \~/.profile \`\`\` For example, if you downloaded Flutter into a \`develop/flutter\` folder inside your user directory, you'd run the following: \`\`\`console $ echo 'export PATH="$HOME/develop/flutter/bin:$PATH"' >> \~/.profile \`\`\`

Expand for `sh` instructions

\`\`\`console $ echo 'export PATH="/bin:$PATH"' >> \~/.profile \`\`\` For example, if you downloaded Flutter into a \`develop/flutter\` folder inside your user directory, you'd run the following: \`\`\`console $ echo 'export PATH="$HOME/develop/flutter/bin:$PATH"' >> \~/.profile \`\`\` 1.

### Apply your changes

To apply this change and get access to the \`flutter\` tool, close and reopen all open shell sessions in your terminal apps and IDEs. 1.

### Validate your setup

To ensure you successfully added the SDK to your \`PATH\`, open your preferred terminal with your default shell, then try running the \`flutter\` and \`dart\` tools. \`\`\`console $ flutter --version $ dart --version \`\`\` If either command isn't found, check out \[Flutter installation troubleshooting]\[troubleshoot]. {: .steps} \[troubleshoot]: /install/troubleshoot

:::note The following steps assume you've already \[turned on Linux support]\[chromeos-linux] and that you're using Bash or the default shell on ChromeOS. If you're using a different shell besides the default or Bash, follow the \[add to path instructions for Linux]\[linux-path]{: target="\_blank"} instead. ::: 1.

### Determine your Flutter SDK installation location

Copy the absolute path to the directory that you downloaded and extracted the Flutter SDK into. 1.

### Add the Flutter SDK bin to your path

To add the \`bin\` directory of your Flutter installation to your \`PATH\`: 1. Copy the following command. 1. Replace \`\` with the path to your Flutter SDK install. 1. Run the edited command in your preferred terminal. \`\`\`console $ echo 'export PATH=":$PATH"' >> \~/.bash\_profile \`\`\` For example, if you downloaded Flutter into a \`develop/flutter\` folder inside your user directory, you'd run the following: \`\`\`console $ echo 'export PATH="$HOME/develop/flutter/bin:$PATH"' >> \~/.bash\_profile \`\`\` 1.

### Apply your changes

To apply this change and get access to the \`flutter\` tool, close and reopen all open Zsh sessions in your terminal apps and IDEs. 1.

### Validate your setup

To ensure you successfully added the SDK to your \`PATH\`, open a Zsh session in your preferred terminal, then try running the \`flutter\` and \`dart\` tools. \`\`\`console $ flutter --version $ dart --version \`\`\` If either command isn't found, check out \[Flutter installation troubleshooting]\[troubleshoot]. {: .steps} \[chromeos-linux]: https\://support.google.com/chromebook/answer/9145439 \[linux-path]: /install/add-to-path#linux \[troubleshoot]: /install/troubleshoot

\## Continue your Flutter journey {: #next-steps} Now that you've successfully installed Flutter, set up development for at least one target platform to continue your journey with Flutter. :::recommend If you don't yet have a preferred platform to target during development, the Flutter team recommends you first try out \[developing on the web]\[web-setup]! ::: \[web-setup]: /platform-integration/web/setup

Set up a target platform

* [Target the web](/platform-integration/web/setup)
* [Target Android](/platform-integration/android/setup)
* [Target iOS](/platform-integration/ios/setup)
* [Target macOS](/platform-integration/macos/setup)
* [Target Windows](/platform-integration/windows/setup)
* [Target Linux](/platform-integration/linux/setup)

Learn Flutter development

* [Learn the fundamentals](/learn/pathway)
* [Explore Flutter widgets](https://www.youtube.com/watch?v=b_sQ9bMltGU\&list=PLjxrf2q8roU23XGwz3Km7sQZFTdB996iG)

Stay up to date with Flutter

* [Update Flutter](/install/upgrade)
* [Find out what's new](/release/release-notes)
* [Subscribe on YouTube](https://www.youtube.com/@flutterdev)

----
url: https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.12.1.md
----

\# DevTools 2.12.1 release notes > Release notes for Dart and Flutter DevTools version 2.12.1. The 2.12.1 release of the Dart and Flutter DevTools includes the following changes among other general improvements. To learn more about DevTools, check out the \[DevTools overview]\(https\://docs.flutter.dev/tools/devtools). ## General updates \* This release included a lot of cleanup and reduction in technical debt. ## Flutter inspector updates \* Added scrolling support to hover cards - \[#3923]\(https\://github.com/flutter/devtools/pull/3923) ## Performance updates \* Added documentation links to the "Enhance Tracing" and "More debugging options" menus. Read the \[enhance tracing documentation]\(https\://docs.flutter.dev/tools/devtools/performance#enhance-tracing) to learn more about these features - \[#3934]\(https\://github.com/flutter/devtools/pull/3934), \[#3936]\(https\://github.com/flutter/devtools/pull/3936) !\[enhance tracing documentation links]\(/assets/images/docs/tools/devtools/release-notes/images-2.12.1/image1.png "enhance tracing documentation links") ## Full commit history To find a complete list of changes since the previous release, check out \[the diff on GitHub]\(https\://github.com/flutter/devtools/compare/v2.11.2...v2.12.1).

----
url: https://docs.flutter.dev/cookbook/forms/focus.md
----

\# Focus and text fields > How focus works with text fields.

<!--?code-excerpt path-base="cookbook/forms/focus/"?-->

When a text field is selected and accepting input, it is said to have "focus." 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 you have a search screen with a text field. When the user navigates to the search screen, you can set the focus to the text field for the search term. This allows the user to start typing as soon as the screen is visible, without needing to manually tap the text field. In this recipe, learn how to give the focus to a text field as soon as it's visible, as well as how to give focus to a text field when a button is tapped. ## Focus a text field as soon as it's visible To give focus to a text field as soon as it's visible, use the \`autofocus\` property. \`\`\`dart TextField( autofocus: true, ); \`\`\` To learn more about handling input and creating text fields, consult the \[Forms]\[] section of the cookbook. ## Focus a text field when a button is tapped Rather than immediately shifting focus to a specific text field, you might need to give focus to a text field at a later point in time. In the real world, you might also need to give focus to a specific text field in response to an API call or a validation error. In this example, give focus to a text field after the user presses a button using the following steps: 1. Create a \`FocusNode\`. 1. Pass the \`FocusNode\` to a \`TextField\`. 1. Give focus to the \`TextField\` when a button is tapped. ### 1. Create a \`FocusNode\` First, create a \[\`FocusNode\`]\[]. Use the \`FocusNode\` to identify a specific \`TextField\` in Flutter's "focus tree." This allows you to give focus to the \`TextField\` in the next steps. Since focus nodes are long-lived objects, manage the lifecycle using a \`State\` object. Use the following instructions to create a \`FocusNode\` instance inside the \`initState()\` method of a \`State\` class, and clean it up in the \`dispose()\` method:

<!--?code-excerpt "lib/starter.dart (Starter)" remove="return Container();"?-->

\`\`\`dart // Define a custom Form widget. class MyCustomForm extends StatefulWidget { const MyCustomForm({super.key}); @override State createState() => \_MyCustomFormState(); } // 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. late final FocusNode myFocusNode; @override void initState() { super.initState(); myFocusNode = FocusNode(); } @override void dispose() { // Clean up the focus node when the Form is disposed. myFocusNode.dispose(); super.dispose(); } @override Widget build(BuildContext context) { // Fill this out in the next step. } } \`\`\` ### 2. Pass the \`FocusNode\` to a \`TextField\` Now that you have a \`FocusNode\`, pass it to a specific \`TextField\` in the \`build()\` method.

<!--?code-excerpt "lib/step2.dart (Build)"?-->

\`\`\`dart @override Widget build(BuildContext context) { return TextField(focusNode: myFocusNode); } \`\`\` ### 3. Give focus to the \`TextField\` when a button is tapped Finally, focus the text field when the user taps a floating action button. Use the \[\`requestFocus()\`]\[] method to perform this task.

<!--?code-excerpt "lib/step3.dart (FloatingActionButton)" replace="/^floatingActionButton\: //g"?-->

\`\`\`dart FloatingActionButton( // When the button is pressed, // give focus to the text field using myFocusNode. onPressed: () => myFocusNode.requestFocus(), tooltip: 'Focus Second Text Field', child: const Icon(Icons.edit), ), \`\`\` ## Interactive example

<!--?code-excerpt "lib/main.dart"?-->

\`\`\`dartpad title="Flutter text focus hands-on example in DartPad" run="true" import 'package:flutter/material.dart'; void main() => runApp(const MyApp()); class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return const MaterialApp(title: 'Text Field Focus', home: MyCustomForm()); } } // Define a custom Form widget. class MyCustomForm extends StatefulWidget { const MyCustomForm({super.key}); @override State createState() => \_MyCustomFormState(); } // 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. late final FocusNode myFocusNode; @override void initState() { super.initState(); myFocusNode = FocusNode(); } @override void dispose() { // Clean up the focus node when the Form is disposed. myFocusNode.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('Text Field Focus')), body: Padding( padding: const EdgeInsets.all(16), child: Column( children: \[ // The first text field is focused on as soon as the app starts. const TextField(autofocus: true), // The second text field is focused on when a user taps the // FloatingActionButton. TextField(focusNode: myFocusNode), ], ), ), floatingActionButton: FloatingActionButton( // When the button is pressed, // give focus to the text field using myFocusNode. onPressed: () => myFocusNode.requestFocus(), tooltip: 'Focus Second Text Field', child: const Icon(Icons.edit), ), ); } } \`\`\` \[\`FocusNode\`]: https\://api.flutter.dev/flutter/widgets/FocusNode-class.html \[Forms]: /cookbook/forms \[\`requestFocus()\`]: https\://api.flutter.dev/flutter/widgets/FocusNode/requestFocus.html

----
url: https://docs.flutter.dev/cookbook/testing/integration/introduction
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Integration testing concepts](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

Unit tests and widget tests validate individual classes, functions, or widgets. They don't validate how individual pieces work together in whole or capture the performance of an app running on a real device. To perform these tasks, use *integration tests*.

Integration tests verify the behavior of the complete app. This test can also be called end-to-end testing or GUI testing.

## Testing frameworks

[#](#testing-frameworks)

Two packages are commonly used to perform Flutter integration tests. These are:

* [integration\_test](https://github.com/flutter/flutter/tree/main/packages/integration_test) package: The official integration test package that is part of the Flutter SDK. Tests written with `integration_test` can perform the following tasks: run on the target device, run from the host machine with `flutter test integration_test`, and use `flutter_test` APIs. This makes integration tests similar to writing [widget tests](/testing/overview#widget-tests). However, `integration_test` can't interact with native platform UI.

* [patrol](https://pub.dev/packages/patrol) package: A popular third-party integration test package that has many of the features supported by the `integration_test` package, but can additionally interact with native platform UI such as permission dialogs, notifications, or the contents of platform views.

## Terminology

[#](#terminology)

* **host machine**

  The system on which you develop your app, like a desktop computer.

* **target device**

  The mobile device, browser, or desktop application that runs your Flutter app.

  If you run your app in a web browser or as a desktop application, the host machine and the target device are the same.

## Getting started

[#](#getting-started)

To use `integration_test`, add it as a dependency for your Flutter app test file.

To migrate existing projects that use `flutter_driver`, consult the [Migrating from flutter\_driver](/release/breaking-changes/flutter-driver-migration) guide.

To use `patrol`, follow the [Patrol setup guide](https://patrol.leancode.co/getting-started).

## Use cases for integration testing

[#](#use-cases-for-integration-testing)

The other guides in this section explain how to use integration tests to validate [functionality](/testing/integration-tests/) and [performance](/cookbook/testing/integration/profiling/).

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/testing/integration/introduction.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/cookbook/testing/integration/introduction\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/testing/integration/introduction.md "Report an issue with this page").

----
url: https://docs.flutter.dev/cookbook/persistence
----

[Store key-value data on disk](/cookbook/persistence/key-value)

[Learn how to use the shared\_preferences package to store key-value data.](/cookbook/persistence/key-value)

[Read and write files](/cookbook/persistence/reading-writing-files)

[How to read from and write to files on disk.](/cookbook/persistence/reading-writing-files)

[Persist data with SQLite](/cookbook/persistence/sqlite)

[How to use SQLite to store and retrieve data.](/cookbook/persistence/sqlite)

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/persistence/index.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/cookbook/persistence\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/persistence/index.md "Report an issue with this page").

----
url: https://docs.flutter.dev/platform-integration/web/building.md
----

\# Building a web application with Flutter > Instructions for creating a Flutter app for the web. This page provides an overview of how to configure, run, and build a web application using Flutter. ## Requirements Before you can build a web application with Flutter, make sure that you have the \[Flutter SDK]\[] and a web browser installed. Visit \[Set up web development for Flutter]\[Setup-web] instructions for details. ## Set up a Flutter project To set up your project, you can create a new Flutter project or add web support to an existing project. ### Create a new project To create a new app that includes web support, run the following command: \`\`\`console $ flutter create my\_app \`\`\` ### Add web support to an existing project If you already have a project, run the \`flutter create\` command in your project directory: \`\`\`console $ flutter create . --platforms web \`\`\` This creates a \`web/\` directory containing the web assets used to bootstrap and run your Flutter app. ## Run your app Check out the following sections to run your app. ### Run your app from the command line Select \[Chrome]\[] as your app's target device to run and debug a Flutter web app: \`\`\`console $ flutter run -d chrome \`\`\` You can also choose Chrome as a target device in your IDE. If you prefer, you can use the \`edge\` device type on Windows, or use \`web-server\` to navigate to a local URL in the browser of your choice. []():::note Hot reload on the web As of the Flutter 3.35 release, hot reload is enabled by default on the web. \[Hot restart]\[] is still available as well. If you discover any issues we ask that you file a bug using our \[Web Hot Reload issue template]\[]. Note this is in the Dart SDK repository where it's easier for us to track issues. Known issues can be seen in the associated \[GitHub project]\[]. ::: ### Run your app using WebAssembly You can pass the \`--wasm\` flag to run your app using WebAssembly: \`\`\`console $ flutter run -d chrome --wasm \`\`\` To learn more about WebAssembly compilation and server requirements, check out \[Support for WebAssembly (Wasm)]\[Wasm support]. ### Disable hot reload in VS Code To temporarily disable hot reload support from VS Code, update your \[\`launch.json\` file]\[] file with the flag \`--no-web-experimental-hot-reload\`. \`\`\`plaintext "configurations": \[ ... { "name": "Flutter for web (hot reload disabled)", "type": "dart", "request": "launch", "program": "lib/main.dart", "args": \[ "-d", "chrome", "--no-web-experimental-hot-reload", ] } ] \`\`\` ### Disable hot reload from the command line If you use \`flutter run\` from the command line, you can temporarily disable hot reload on the web with the following command: \`\`\`console flutter run -d chrome --no-web-experimental-hot-reload \`\`\` ### Use hot reload in DartPad Hot reload is also enabled in DartPad with a new "Reload" button. The feature is only available if Flutter is detected in the running application. You can begin a hot reloadable session by selecting a sample app provided by DartPad. \[Hot restart]: /tools/hot-reload \[How to switch channels]: /install/upgrade#switching-flutter-channels \[\`launch.json\` file]: https\://code.visualstudio.com/docs/debugtest/debugging-configuration \[Web Hot Reload issue template]: https\://github.com/dart-lang/sdk/issues/new?template=5\_web\_hot\_reload.yml \[GitHub project]: https\://github.com/orgs/dart-lang/projects/107/views/1 ## Build your app See the following sections to build your app. ### Build your app from the command line Run the following command to generate a release build: \`\`\`console $ flutter build web \`\`\` ### Build your app using WebAssembly You can also pass the \`--wasm\` flag to build your app using WebAssembly: \`\`\`console $ flutter build web --wasm \`\`\` This populates a \`build/web\` directory with built files, including an \`assets\` directory, which need to be served together. To learn more about how to deploy these assets to the web, visit \[Build and release a web app]\[]. For answers to other common questions, visit the \[Web FAQ]\[]. If you encounter errors when compiling your app with Wasm, check out \[Diagnosing Wasm compilation errors]\(/platform-integration/web/wasm#diagnosing-wasm-compilation-errors). ## Debugging Use \[Flutter DevTools]\[] for the following tasks: \* \[Debugging]\[] \* \[Logging]\[] \* \[Running Flutter inspector]\[] Use \[Chrome DevTools]\[] for the following tasks: \* \[Generating event timeline]\[] \* \[Analyzing performance]\[]—make sure to use a profile build ## Testing Use \[widget tests]\[Widget tests] or integration tests. To learn more about running integration tests in a browser, check out the \[Integration testing]\[] page. \[Analyzing performance]: https\://developers.google.com/web/tools/chrome-devtools/evaluate-performance \[Build and release a web app]: /deployment/web \[Chrome DevTools]: https\://developers.google.com/web/tools/chrome-devtools \[Chrome]: https\://www\.google.com/chrome/ \[Debugging]: /tools/devtools/debugger \[Flutter DevTools]: /tools/devtools \[Flutter SDK]: /install \[Generating event timeline]: https\://developers.google.com/web/tools/chrome-devtools/evaluate-performance/performance-reference \[Integration testing]: /testing/integration-tests#test-in-a-web-browser \[Logging]: /tools/devtools/logging \[Running Flutter inspector]: /tools/devtools/inspector \[Setup-web]: /platform-integration/web/setup \[Web FAQ]: /platform-integration/web/faq \[Wasm support]: /platform-integration/web/wasm \[Web renderers]: /platform-integration/web/renderers \[Widget tests]: /testing/overview#widget-tests

----
url: https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.58.0.md
----

\# DevTools 2.58.0 release notes The 2.58.0 release of the Dart and Flutter DevTools includes the following changes among other general improvements. To learn more about DevTools, check out the \[DevTools overview]\(/tools/devtools). ## Inspector updates - Deleted the option to use the legacy inspector. \[#9782]\(https\://github.com/flutter/devtools/pull/9782) - Fixed an issue where navigating the Inspector widget tree with the keyboard arrow keys did not update the selected widget in the connected Flutter app. \[#9810]\(https\://github.com/flutter/devtools/pull/9810) - Fixed an issue where clicking a widget row after collapsing a subtree with the left arrow key unexpectedly re-expanded the subtree. \[#9810]\(https\://github.com/flutter/devtools/pull/9810) - Fixed an issue where collapsing the Inspector widget tree to a single row with the left arrow key caused a loading spinner to appear instead of showing the root node. \[#9810]\(https\://github.com/flutter/devtools/pull/9810) ## Performance updates - Fixed an issue where 'More Debug Options' showed options as unselected in profile mode even when selected. \[#9813]\(https\://github.com/flutter/devtools/issues/9813) ## Debugger updates - Fixed an issue where long string values in the console/variables view would overflow and overlap with other elements. \[#7112]\(https\://github.com/flutter/devtools/issues/7112) ## Network profiler updates - Added response size column to the Network tab and displayed response size in the request inspector overview. \[#9744]\(https\://github.com/flutter/devtools/pull/9744) - Improved HTTP request status classification in the Network tab to better distinguish cancelled, completed, and in-flight requests (for example, avoiding some cases where cancelled requests appeared as pending). \[#9683]\(https\://github.com/flutter/devtools/pull/9683) - Added a filter setting to hide HTTP-profiler socket data. \[#9698]\(https\://github.com/flutter/devtools/pull/9698) ## Logging updates - Fixed an issue where log messages containing newline characters were incorrectly split into multiple separate entries in the Logging screen. \[#9757]\(https\://github.com/flutter/devtools/pull/9757) ## Deep links tool updates - Pluralized "domain" and "path" in the validation summary notification titles when multiple errors are present. \[#9790]\(https\://github.com/flutter/devtools/pull/9790) ## Full commit history To find a complete list of changes in this release, check out the \[DevTools git log]\(https\://github.com/flutter/devtools/tree/v2.58.0).

----
url: https://docs.flutter.dev/platform-integration/ios/ios-app-clip
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Adding an iOS App Clip target](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

This guide describes how to manually add another Flutter-rendering iOS App Clip target to your existing Flutter project or [add-to-app](/add-to-app) project.

To see a working sample, see the [App Clip sample](https://github.com/flutter/samples/tree/main/ios_app_clip) on GitHub.

## Step 1 - Open project

[#](#step-1-open-project)

Open your iOS Xcode project, such as `ios/Runner.xcworkspace` for full-Flutter apps.

## Step 2 - Add an App Clip target

[#](#step-2-add-an-app-clip-target)

**2.1**

Click on your project in the Project Navigator to show the project settings.

Press **+** at the bottom of the target list to add a new target.

**2.2**

Select the **App Clip** type for your new target.

**2.3**

Enter your new target detail in the dialog.

Select **Storyboard** for Interface.

Select the same language as your original target for **Language**.

(In other words, to simplify the setup, don't create a Swift App Clip target for an Objective-C main target, and vice versa.)

**2.4**

In the following dialog, activate the new scheme for the new target.

**2.5**

Back in the project settings, open the **Build Phases** tab. Drag **Embedded App Clips** to above **Thin Binary**.

[]()

## Step 3 - Remove unneeded files

[#](#step-3-remove-unneeded-files)

**3.1**

In the Project Navigator, in the newly created App Clip group, delete everything except `Info.plist` and `<app clip target>.entitlements`.

Move files to trash.

**3.2**

If you don't use the `SceneDelegate.swift` file, remove the reference to it in the `Info.plist`.

Open the `Info.plist` file in the App Clip group. Delete the entire dictionary entry for **Application Scene Manifest**.

## Step 4 - Share build configurations

[#](#step-4-share-build-configurations)

This step isn't necessary for add-to-app projects since add-to-app projects have their custom build configurations and versions.

**4.1**

Back in the project settings, select the project entry now rather than any targets.

In the **Info** tab, under the **Configurations** expandable group, expand the **Debug**, **Profile**, and **Release** entries.

For each, select the same value from the drop-down menu for the App Clip target as the entry selected for the normal app target.

This gives your App Clip target access to Flutter's required build settings.

Set **iOS Deployment Target** to at least **16.0** to take advantage of the 15MB size limit.

**4.2**

In the App Clip group's `Info.plist` file, set:

* `Build version string (short)` to `$(FLUTTER_BUILD_NAME)`
* `Bundle version` to `$(FLUTTER_BUILD_NUMBER)`

## Step 5 - Share code and assets

[#](#step-5-share-code-and-assets)

### Option 1 - Share everything

[#](#option-1-share-everything)

Assuming the intent is to show the same Flutter UI in the standard app as in the App Clip, share the same code and assets.

For each of the following: `Main.storyboard`, `Assets.xcassets`, `LaunchScreen.storyboard`, `GeneratedPluginRegistrant.m`, and `AppDelegate.swift` (and `Supporting Files/main.m` if using Objective-C), select the file, then in the first tab of the inspector, also include the App Clip target in the `Target Membership` checkbox group.

### Option 2 - Customize Flutter launch for App Clip

[#](#option-2-customize-flutter-launch-for-app-clip)

In this case, do not delete everything listed in [Step 3](#step-3). Instead, use the scaffolding and the [iOS add-to-app APIs](/add-to-app/ios/add-flutter-screen) to perform a custom launch of Flutter. For example to show a [custom Flutter route](/add-to-app/ios/add-flutter-screen#route).

## Step 6 - Add App Clip associated domains

[#](#step-6-add-app-clip-associated-domains)

This is a standard step for App Clip development. See the [official Apple documentation](https://developer.apple.com/documentation/app_clips/creating_an_app_clip_with_xcode#3604097).

**6.1**

Open the `<app clip target>.entitlements` file. Add an `Associated Domains` Array type. Add a row to the array with `appclips:<your bundle id>`.

**6.2**

The same associated domains entitlement needs to be added to your main app, as well.

Copy the `<app clip target>.entitlements` file from your App Clip group to your main app group and rename it to the same name as your main target such as `Runner.entitlements`.

Open the file and delete the `Parent Application Identifiers` entry for the main app's entitlement file (leave that entry for the App Clip's entitlement file).

**6.3**

Back in the project settings, select the main app's target, open the **Build Settings** tab. Set the **Code Signing Entitlements** setting to the relative path of the second entitlements file created for the main app.

## Step 7 - Integrate Flutter

[#](#step-7-integrate-flutter)

These steps are not necessary for add-to-app.

**7.1**

For the Swift target, set the `Objective-C Bridging Header` build setting to `Runner/Runner-Bridging-Header.h`

In other words, the same as the main app target's build settings.

**7.2**

Now open the **Build Phases** tab. Press the **+** sign and select **New Run Script Phase**.

Drag that new phase to below the **Dependencies** phase.

Expand the new phase and add this line to the script content:

bash

```
/bin/sh "$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh" build
```

Uncheck **Based on dependency analysis**.

In other words, the same as the main app target's build phases.

This ensures that your Flutter Dart code is compiled when running the App Clip target.

**7.3**

Press the **+** sign and select **New Run Script Phase** again. Leave it as the last phase.

This time, add:

bash

```
/bin/sh "$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh" embed_and_thin
```

Uncheck **Based on dependency analysis**.

In other words, the same as the main app target's build phases.

This ensures that your Flutter app and engine are embedded into the App Clip bundle.

## Step 8 - Integrate plugins

[#](#step-8-integrate-plugins)

**8.1**

Open the `Podfile` for your Flutter project or add-to-app host project.

For full-Flutter apps, replace the following section:

ruby

```
target 'Runner' do
  use_frameworks!
  use_modular_headers!
​
  flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
end
```

with:

ruby

```
use_frameworks!
use_modular_headers!
flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
​
target 'Runner'
target '<name of your App Clip target>'
```

At the top of the file, also uncomment `platform :ios, '13.0'` and set the version to the lowest of the two target's iOS Deployment Target.

For add-to-app, add to:

ruby

```
target 'MyApp' do
  install_all_flutter_pods(flutter_application_path)
end
```

with:

ruby

```
target 'MyApp' do
  install_all_flutter_pods(flutter_application_path)
end
​
target '<name of your App Clip target>'
  install_all_flutter_pods(flutter_application_path)
end
```

**8.2**

From the command line, enter your Flutter project directory and then install the pod:

```
cd ios
pod install
```

## Run

[#](#run)

You can now run your App Clip target from Xcode by selecting your App Clip target from the scheme drop-down, selecting an iOS 16 or higher device and pressing run.

To test launching an App Clip from the beginning, also consult Apple's doc on [Testing Your App Clip's Launch Experience](https://developer.apple.com/documentation/app_clips/testing_your_app_clip_s_launch_experience).

## Debugging, hot reload

[#](#debugging-hot-reload)

Unfortunately `flutter attach` cannot auto-discover the Flutter session in an App Clip due to networking permission restrictions.

You must then copy and paste it back into the `flutter attach` command to connect.

For example:

```
flutter attach --debug-uri <copied URI>
```

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/platform-integration/ios/ios-app-clip.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/platform-integration/ios/ios-app-clip\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/platform-integration/ios/ios-app-clip.md "Report an issue with this page").

----
url: https://docs.flutter.dev/cookbook/navigation/set-up-app-links.md
----

\# Set up app links for Android > Learn how to set up app links for an Android application built with Flutter. Deep linking is a mechanism for launching an app with a URI. This URI contains scheme, host, and path, and opens the app to a specific screen. An \_app link\_ is a type of deep link that uses \`http\` or \`https\` and is exclusive to Android devices. Setting up app links requires one to own a web domain. Otherwise, consider using \[Firebase Hosting]\[] or \[GitHub Pages]\[] as a temporary solution. Once you've set up your deep links, you can validate them. To learn more, see \[Validate deep links]\[]. ## 1. Customize a Flutter application Write a Flutter app that can handle an incoming URL. This example uses the \[go\_router]\[] package to handle the routing. The Flutter team maintains the \`go\_router\` package. It provides a simple API to handle complex routing scenarios. 1. To create a new application, type \`flutter create \`: \`\`\`console $ flutter create deeplink\_cookbook \`\`\` 2. To include \`go\_router\` package in your app, add a dependency for \`go\_router\` to the project: To add the \`go\_router\` package as a dependency, run \`flutter pub add\`: \`\`\`console $ flutter pub add go\_router \`\`\` 3. To handle the routing, create a \`GoRouter\` object in the \`main.dart\` file: \`\`\`dart title="main.dart" import 'package:material\_ui/material\_ui.dart'; import 'package:go\_router/go\_router.dart'; void main() => runApp(MaterialApp.router(routerConfig: router)); /// This handles '/' and '/details'. final router = GoRouter( routes: \[ GoRoute( path: '/', builder: (\_, \_) => Scaffold( appBar: AppBar(title: const Text('Home Screen')), ), routes: \[ GoRoute( path: 'details', builder: (\_, \_) => Scaffold( appBar: AppBar(title: const Text('Details Screen')), ), ), ], ), ], ); \`\`\` ## 2. Modify AndroidManifest.xml 1. Open the Flutter project with VS Code or Android Studio. 2. Navigate to \`android/app/src/main/AndroidManifest.xml\` file. 3. Add the following metadata tag and intent filter inside the \`\` tag with \`.MainActivity\`. Replace \`example.com\` with your own web domain. \`\`\`xml \`\`\` :::version-note If you use a Flutter version earlier than 3.27, you need to manually opt in to deep linking by adding the following metadata tag to \`\`: \`\`\`xml \`\`\` ::: :::note If you use a third-party plugin to handle deep links, such as \[app\_links]\[], Flutter's default deeplink handler will break these plugins. To opt out of using Flutter's default deep link handler, add the following metadata tag to \`\`: \`\`\`xml \`\`\` ::: ## 3. Hosting assetlinks.json file Host an \`assetlinks.json\` file in using a web server with a domain that you own. This file tells the mobile browser which Android application to open instead of the browser. To create the file, get the package name of the Flutter app you created in the previous step and the sha256 fingerprint of the signing key you will be using to build the APK. ### Package name Locate the package name in \`AndroidManifest.xml\`, the \`package\` property under \`\` tag. Package names are usually in the format of \`com.example.\*\`. ### sha256 fingerprint The process might differ depending on how the apk is signed. #### Using google play app signing You can find the sha256 fingerprint directly from play developer console. Open your app in the play console, under \*\*Release> Setup > App Integrity> App Signing tab\*\*: #### Using local keystore If you are storing the key locally, you can generate sha256 using the following command: \`\`\`console keytool -list -v -keystore \`\`\` ### assetlinks.json The hosted file should look similar to this: \`\`\`json \[{ "relation": \["delegate\_permission/common.handle\_all\_urls"], "target": { "namespace": "android\_app", "package\_name": "com.example.deeplink\_cookbook", "sha256\_cert\_fingerprints": \["FF:2A:CF:7B:DD:CC:F1:03:3E:E8:B2:27:7C:A2:E3:3C:DE:13:DB:AC:8E:EB:3A:B9:72:A1:0E:26:8A:F5:EC:AF"] } }] \`\`\` 1. Set the \`package\_name\` value to your Android application ID. 2. Set sha256\_cert\_fingerprints to the value you got from the previous step. 3. Host the file at a URL that resembles the following: \`/.well-known/assetlinks.json\` 4. Verify that your browser can access this file. :::note If you have multiple flavors, you can have many sha256\_cert\_fingerprint values in the sha256\_cert\_fingerprints field. Just add it to the sha256\_cert\_fingerprints list ::: ## Testing You can use a real device or the Emulator to test an app link, but first make sure you have executed \`flutter run\` at least once on the devices. This ensures that the Flutter application is installed. To test \*\*only\*\* the app setup, use the adb command: \`\`\`console adb shell 'am start -a android.intent.action.VIEW \ -c android.intent.category.BROWSABLE \ -d "http\:///details"' \ \`\`\` :::note This doesn't test whether the web files are hosted correctly, the command launches the app even if web files are not presented. ::: To test \*\*both\*\* web and app setup, you must click a link directly through web browser or another app. One way is to create a Google Doc, add the link, and tap on it. :::note If you are debugging locally (and not downloading the app from the Play Store), you might need to enable the toggle for \*\*Supported web addresses\*\* manually. ::: If everything is set up correctly, the Flutter application launches and displays the details screen: ## Appendix Source code: \[deeplink\_cookbook]\[] \[deeplink\_cookbook]: https\://github.com/flutter/codelabs/tree/main/deeplink\_cookbook \[Firebase Hosting]: https\://firebase.google.com/docs/hosting \[go\_router]: https\://pub.dev/packages/go\_router \[GitHub Pages]: https\://pages.github.com \[app\_links]: https\://pub.dev/packages/app\_links \[Signing the app]: /deployment/android#signing-the-app \[Validate deep links]: /tools/devtools/deep-links

----
url: https://docs.flutter.dev/platform-integration/android/setup
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Set up Android development](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

Learn how to set up your development environment to run, build, and deploy Flutter apps for Android devices.

## Choose your development platform

[#](#dev-platform)

The instructions on this page are configured to cover setting up Android development on a **Windows** device.

If you'd like to follow the instructions for a different OS, please select one of the following.

## Set up Android tooling

[#](#set-up-tooling)

With Android Studio, you can run Flutter apps on a physical Android device or an Android Emulator.

If you haven't done so already, install and set up the latest stable version of [Android Studio](https://developer.android.com/studio).

1. ### Install prerequisites libraries

   If you're developing on Linux, first install the [prerequisite collection of 32-bit libraries](https://developer.android.com/studio/install#64bit-libs) that Android Studio requires.

2. ### Install Android Studio

   If you haven't done so already, [install and set up](https://developer.android.com/studio/install) the latest stable version of [Android Studio](https://developer.android.com/studio).

   If you already have Android Studio installed, ensure that it's [up to date](https://developer.android.com/studio/intro/update).

3. ### Install Android SDK and tools

   1. Launch **Android Studio**.

   2. Open the **SDK Manager** settings dialog.

      1. If the **Welcome to Android Studio** dialog is open, click the **More Actions** button that follows the **New Project** and **Open** buttons, then click **SDK Manager** from the dropdown menu.

      2. If you have a project open, go to **Tools** > **SDK Manager**.

   3. If the **SDK Platforms** tab is not open, switch to it.

   4. Verify that the first entry with an **API Level** of **36** has been selected.

      If the **Status** column displays **Update available** or **Not installed**:

      1. Select the checkbox for that entry or row.

      2. Click **Apply**.

      3. When the **Confirm Change** dialog displays, click **OK**.

         The **SDK Component Installer** dialog displays with a progress indicator.

      4. When the installation finishes, click **Finish**.

   5. Switch to the **SDK Tools** tab.

   6. Verify that the following SDK Tools have been selected:

      * **Android SDK Build-Tools**
      * **Android SDK Command-line Tools**
      * **Android Emulator**
      * **Android SDK Platform-Tools**
      * **CMake**
      * **NDK (Side by side)**

   7. If the **Status** column for any of the preceding tools displays **Update available** or **Not installed**:

      1. Select the checkbox for the necessary tools.

      2. Click **Apply**.

      3. When the **Confirm Change** dialog displays, click **OK**.

         The **SDK Component Installer** dialog displays with a progress indicator.

      4. When the installation finishes, click **Finish**.

4. ### Agree to the Android licenses

   Before you can use Flutter and after you install all prerequisites, agree to the licenses of the Android SDK platform.

   1. Open your preferred terminal.

   2. Run the following command to review and sign the SDK licenses.

      ```
      $ flutter doctor --android-licenses
      ```

   3. Read and accept any necessary licenses.

      If you haven't accepted each of the SDK licenses previously, you'll need to review and agree to them before developing for Android.

      Before agreeing to the terms of each license, read each with care.

      Once you've accepted all the necessary licenses successfully, you should see output similar to the following:

      ```
      All SDK package licenses accepted.
      ```

## Set up an Android device

[#](#set-up-devices)

You can debug Flutter apps on physical Android devices or by running them on an Android emulator.

* [Android emulator](#195-tab-panel)
* [Physical device](#196-tab-panel)

To set up your development environment to run a Flutter app on an Android emulator, follow these steps:

1. ### Set up your development device

   Enable [VM acceleration](https://developer.android.com/studio/run/emulator-acceleration#accel-vm) on your development computer.

2. ### Set up a new emulator

   1. Start **Android Studio**.

   2. Open the **Device Manager** settings dialog.

      1. If the **Welcome to Android Studio** dialog is open, click the **More Actions** button that follows the **New Project** and **Open** buttons, then select **Virtual Device Manager** from the dropdown menu.

      2. If you have a project open, go to **Tools** > **Device Manager**.

   3. Click the **Create Virtual Device** button that appears as a `+` icon.

      The **Virtual Device Configuration** dialog displays.

   4. Select either **Phone** or **Tablet** under **Form Factor**.

   5. Select a device definition. You can browse or search for the device.

   6. Click **Next**.

   7. If the option is provided, select either **x86 Images** or **ARM Images** depending on if your development computer is an x64 or Arm64 device.

   8. Select one system image for the Android version you want to emulate.

      1. If the desired image has a **Download** icon to the left of the system image name, click it.

         The **SDK Component Installer** dialog displays with a progress indicator.

      2. When the download completes, click **Finish**.

   9. Click **Additional settings** in the top tab bar and scroll to **Emulated Performance**.

   10. From the **Graphics acceleration** dropdown menu, select an option that mentions **Hardware**.

       This enables [hardware acceleration](https://developer.android.com/studio/run/emulator-acceleration), improving render performance.

   11. Verify your virtual device configuration. If it is correct, click **Finish**.

       To learn more about virtual devices, check out [Create and manage virtual devices](https://developer.android.com/studio/run/managing-avds).

3. ### Try running the emulator

   In the **Device Manager** dialog, click the **Run** icon to the right of your desired virtual device.

   The emulator should start up and display the default canvas for your selected Android OS version and device.

To set up your development environment to run a Flutter app on a physical Android device, follow these steps:

1. ### Configure your device

   Enable **Developer options** and **USB debugging** on your device as described in [Configure on-device developer options](https://developer.android.com/studio/debug/dev-options).

2. ### Enable wireless debugging

   To leverage wireless debugging, enable **Wireless debugging** on your device as described in [Connect to your device using Wi-Fi](https://developer.android.com/studio/run/device#wireless).

3. ### Install platform prerequisites

   If you're developing on Windows, first install the necessary USB driver for your particular device as described in [Install OEM USB drivers](https://developer.android.com/studio/run/oem-usb).

4. ### Connect your device

   Plug your device into your computer. If your device prompts you, authorize your computer to access your Android device.

5. ### Verify the device connection

   To verify that Flutter recognizes your connected Android device, run `flutter devices` in your preferred terminal:

   ```
   $ flutter devices
   ```

   Your device should be found and show up as a connected device.

## Validate your setup

[#](#validate-setup)

1. ### Check for toolchain issues

   To check for any issues with your Android development setup, run the `flutter doctor` command in your preferred terminal:

   ```
   $ flutter doctor
   ```

   If you see any errors or tasks to complete under the **Android toolchain** or **Android Studio** sections,

   Complete any mentioned tasks and then run `flutter doctor` again to verify any changes.

2. ### Check for Android devices

   To ensure you set up your emulator and/or physical Android device correctly, run `flutter emulators` and `flutter devices` in your preferred terminal:

   ```
   $ flutter emulators && flutter devices
   ```

   Depending on if you set up an emulator or a device, at least one should output an entry with the platform marked as **android**.

3. ### Troubleshoot setup issues

   If you need help resolving any setup issues, check out [Install and setup troubleshooting](/install/troubleshoot#android-setup).

   If you still have issues or questions, reach out on one of the Flutter [community](https://flutter.dev/community) channels.

## Start developing for Android

[#](#start-developing)

Congratulations! Now that you've set up Android development for Flutter, you can continue your Flutter learning journey while testing on Android or begin improving integration with Android.

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-06-08. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/platform-integration/android/setup.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/platform-integration/android/setup\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/platform-integration/android/setup.md "Report an issue with this page").

----
url: https://docs.flutter.dev/platform-integration/web/embedding-flutter-web.md
----

\# Adding Flutter to any web application > Learn the different ways to embed Flutter views into web content. Flutter views and web content can be composed to produce a web application in different ways. Choose one of the following depending on your use-case: \* A Flutter view controls the full page (\[full page mode]\[]) \* Adding Flutter views to an existing web application (\[embedded mode]\[]) \[full page mode]: #full-page-mode \[embedded mode]: #embedded-mode ## Full page mode In full page mode, the Flutter web application takes control of the whole browser window and covers its viewport completely when rendering. This is the default embedding mode for new Flutter web projects, and no additional configuration is needed. \`\`\`html highlightLines=6 \`\`\` When Flutter web is launched without referencing \`multiViewEnabled\` or a \`hostElement\`, it uses full page mode. To learn more about the \`flutter\_bootstrap.js\` file, check out \[Customize app initialization]\[]. \[Customize app initialization]: /platform-integration/web/initialization ### \`iframe\` embedding Full page mode is recommended when embedding a Flutter web application through an \`iframe\`. The page that embeds the \`iframe\` can size and position it as needed, and Flutter will fill it completely. \`\`\`html \`\`\` To learn more about the pros and cons of an \`iframe\`, check out the \[Inline Frame element]\[] docs on MDN. \[Inline Frame element]: https\://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe ### Compare \`iframe\` and direct DOM embedding {: #compare-iframe-and-dom-embedding } When integrating Flutter into an existing web application, choose between \`iframe\` embedding and direct DOM embedding (\`hostElement\` or multi-view) based on your isolation, performance, and communication requirements: | Feature | \`iframe\` embedding | Direct DOM embedding (\`hostElement\` / multiview) | | --- | --- | --- | | \*\*Isolation & sandboxing\*\* | Full browser sandboxing with separate JS global scope, DOM tree, and CSS styles | Shares the same DOM tree and JavaScript context | | \*\*Memory & engine instances\*\* | Each \`iframe\` initializes its own Flutter engine, WebAssembly/JS runtime, and memory heap | A single Flutter engine instance and memory heap can manage one or more views (when using multiview) | | \*\*JavaScript interop\*\* | Communication requires asynchronous messaging (such as \`postMessage\`) | Direct synchronous communication using \`package:web\` and \`dart:js\_interop\` | | \*\*Styling & layout\*\* | Independent frame isolated from host page CSS | Direct integration into host page CSS layout (such as flexbox and grid) | \`CupertinoPageScaffold\` | | \*\*State sharing\*\* | State must be synchronized across window boundaries | Direct state sharin in Dart across all attached views | | \*\*Best for\*\* | Third-party widgets, untrusted content, isolated micro-frontends, or simple drop-in embeds. | Embedded UI components in existing web apps, multi-view dashboards, and tight host-app integration | {:.table .table-striped} ## Embedded mode Flutter web applications can also render content into an arbitrary number of elements (commonly \`div\`s) of another web application; this is called "embedded mode" (or "multi-view"). In this mode: \* A Flutter web application can launch, but doesn't render until the first "view" is added, with \`addView\`. \* The host application can add or remove views from the embedded Flutter web application. \* The Flutter application is notified when views are added or removed, so it can adjust its widgets accordingly. ### Enable multi-view mode Enable multi-view mode setting \`multiViewEnabled: true\` in the \`initializeEngine\` method as shown: \`\`\`js highlightLines=8 title="flutter\_bootstrap.js" {{flutter\_js}} {{flutter\_build\_config}} \_flutter.loader.load({ onEntrypointLoaded: async function onEntrypointLoaded(engineInitializer) { let engine = await engineInitializer.initializeEngine({ multiViewEnabled: true, // Enables embedded mode. }); let app = await engine.runApp(); // Make this \`app\` object available to your JS app. } }); \`\`\` ### Manage Flutter views from JS To add or remove views, use the \`app\` object returned by the \`runApp\` method: \`\`\`js highlightLines=2-4,7 // Adding a view\... let viewId = app.addView({ hostElement: document.querySelector('#some-element'), }); // Removing viewId... let viewConfig = app.removeView(viewId); \`\`\` ### Handling view changes from Dart View additions and removals are surfaced to Flutter through the \[\`didChangeMetrics\` method]\[] of the \`WidgetsBinding\` class. The complete list of views attached to your Flutter app is available through the \`WidgetsBinding.instance.platformDispatcher.views\` iterable. These views are of \[type \`FlutterView\`]\[]. To render content into each \`FlutterView\`, your Flutter app needs to create a \[\`View\` widget]\[]. \`View\` widgets can be grouped together under a \[\`ViewCollection\` widget]\[]. The following example, from the \_Multi View Playground\_, encapsulates the above in a \`MultiViewApp\` widget that can be used as the root widget for your app. A \[\`WidgetBuilder\` function]\[] runs for each \`FlutterView\`: \`\`\`dart highlightLines=25,39,46-49,56-61,72 title="multi\_view\_app.dart" import 'dart:ui' show FlutterView; import 'package:flutter/widgets.dart'; /// Calls \[viewBuilder] for every view added to the app to obtain the widget to /// render into that view. The current view can be looked up with \[View\.of]. class MultiViewApp extends StatefulWidget { const MultiViewApp({super.key, required this.viewBuilder}); final WidgetBuilder viewBuilder; @override State createState() => \_MultiViewAppState(); } class \_MultiViewAppState extends State with WidgetsBindingObserver { @override void initState() { super.initState(); WidgetsBinding.instance.addObserver(this); \_updateViews(); } @override void didUpdateWidget(MultiViewApp oldWidget) { super.didUpdateWidget(oldWidget); // Need to re-evaluate the viewBuilder callback for all views. \_views.clear(); \_updateViews(); } @override void didChangeMetrics() { \_updateViews(); } Map \_views = {}; void \_updateViews() { final Map newViews = {}; for (final FlutterView view in WidgetsBinding.instance.platformDispatcher.views) { final Widget viewWidget = \_views\[view\.viewId] ?? \_createViewWidget(view); newViews\[view\.viewId] = viewWidget; } setState(() { \_views = newViews; }); } Widget \_createViewWidget(FlutterView view) { return View( view: view, child: Builder( builder: widget.viewBuilder, ), ); } @override void dispose() { WidgetsBinding.instance.removeObserver(this); super.dispose(); } @override Widget build(BuildContext context) { return ViewCollection(views: \_views.values.toList(growable: false)); } } \`\`\` For more information, check out the \[\`WidgetsBinding\` mixin]\(https\://api.flutter.dev/flutter/widgets/WidgetsBinding-mixin.html) in the API docs. \[\`didChangeMetrics\` method]: https\://api.flutter.dev/flutter/widgets/WidgetsBindingObserver/didChangeMetrics.html \[type \`FlutterView\`]: https\://api.flutter.dev/flutter/dart-ui/FlutterView-class.html \[\`View\` widget]: https\://api.flutter.dev/flutter/widgets/View-class.html \[\`ViewCollection\` widget]: https\://api.flutter.dev/flutter/widgets/ViewCollection-class.html \[\`WidgetsBinding\` mixin]: https\://api.flutter.dev/flutter/widgets/WidgetsBinding-mixin.html \[\`WidgetBuilder\` function]: https\://api.flutter.dev/flutter/widgets/WidgetBuilder.html ### Replace \`runApp\` by \`runWidget\` in Dart Flutter's \[\`runApp\` function]\[] assumes that there's at least one view available to render into (the \`implicitView\`), however in Flutter web's multi-view mode, the \`implicitView\` doesn't exist anymore, so \`runApp\` will start failing with \`Unexpected null value\` errors. In multi-view mode, your \`main.dart\` must call the \[\`runWidget\` function]\[] instead. It doesn't require an \`implicitView\`, and will only render into the views that have been explicitly added into your app. The following example uses the \`MultiViewApp\` described above to render copies of the \`MyApp()\` widget on every \`FlutterView\` available: \`\`\`dart highlightLines=3 title="main.dart" void main() { runWidget( MultiViewApp( viewBuilder: (BuildContext context) => const MyApp(), ), ); } \`\`\` \[\`runApp\` function]: https\://api.flutter.dev/flutter/widgets/runApp.html \[\`runWidget\` function]: https\://api.flutter.dev/flutter/widgets/runWidget.html ### Identifying views Each \`FlutterView\` has an identifier assigned by Flutter when attached. This \`viewId\` can be used to uniquely identify each view, retrieve its initial configuration, or decide what to render in it. The \`viewId\` of the rendered \`FlutterView\` can be retrieved from its \`BuildContext\` like this: \`\`\`dart highlightLines=4-5 class SomeWidget extends StatelessWidget { @override Widget build(BuildContext context) { // Retrieve the \`viewId\` where this Widget is being built: final int viewId = View\.of(context).viewId; // ... \`\`\` Similarly, from the \`viewBuilder\` method of the \`MultiViewApp\`, the \`viewId\` can be retrieved like this: \`\`\`dart highlightLines=4 MultiViewApp( viewBuilder: (BuildContext context) { // Retrieve the \`viewId\` where this Widget is being built: final int viewId = View\.of(context).viewId; // Decide what to render based on \`viewId\`... }, ) \`\`\` Read more about the \[\`View\.of\` constructor]\[]. \[\`View\.of\` constructor]: https\://api.flutter.dev/flutter/widgets/View/of.html ### Initial view configuration Flutter views can receive any initialization data from JS when starting up. The values are passed through the \`initialData\` property of the \`addView\` method, as shown: \`\`\`js highlightLines=4-7 // Adding a view with initial data... let viewId = app.addView({ hostElement: someElement, initialData: { greeting: 'Hello, world!', randomValue: Math.floor(Math.random() \* 100), } }); \`\`\` In Dart, the \`initialData\` is available as a \`JSAny\` object, accessible through the top-level \`views\` property in the \`dart:ui\_web\` library. The data is accessed through the \`viewId\` of the current view, as shown: \`\`\`dart final initialData = ui\_web.views.getInitialData(viewId) as YourJsInteropType; \`\`\` To learn how to define the \`YourJsInteropType\` class to map the \`initialData\` object passed from JS so it's type-safe in your Dart program, check out: \[JS Interoperability]\[] on dart.dev. \[JS Interoperability]: https\://dart.dev/interop/js-interop ### View constraints By default, an embedded Flutter web view considers the size of its \`hostElement\` as an immutable property, and tightly constrains its layout to the available space. On the web, it's common for the intrinsic size of an element to affect the layout of the page (like \`img\` or \`p\` tags that can reflow content around them). When adding a view to Flutter web, you might configure it with constraints that inform Flutter of how the view needs to be laid out: \`\`\`js highlightLines=4-8 // Adding a view with initial data... let viewId = app.addView({ hostElement: someElement, viewConstraints: { maxWidth: 320, minHeight: 0, maxHeight: Infinity, } }); \`\`\` The view constraints passed from JS need to be compatible with the CSS styling of the \`hostElement\` where Flutter is being embedded. For example, Flutter won’t try to "fix" contradictory constants like passing \`max-height: 100px\` in CSS, but \`maxHeight: Infinity\` to Flutter. To learn more, check out the \[\`ViewConstraints\` class]\[], and \[Understanding constraints]\[]. \[\`ViewConstraints\` class]: https\://api.flutter.dev/flutter/dart-ui/ViewConstraints-class.html \[Understanding constraints]: /ui/layout/constraints ## Custom element (\`hostElement\`) You can embed a single-view Flutter web app into any HTML element of your web page. To tell Flutter web which element to render into, pass an object with a \`config\` field to the \`\_flutter.loader.load\` function that specifies a \`HTMLElement\` as the \`hostElement\`. \`\`\`js highlightLines=3 \_flutter.loader.load({ config: { hostElement: document.getElementById('flutter\_host'), } }); \`\`\` :::note Multi-view embedding also works with a single view. An advantage of embedding a single-view by using multi-view support, is that you can create and remove views dynamically. Also, if single view support is ever deprecated, it won't affect your app. ::: To learn more about other configuration options, check out \[Customizing web app initialization]\[]. \[Customizing web app initialization]: /platform-integration/web/initialization ## Host element CSS requirements {: #host-element-css-requirements } When embedding Flutter directly into a DOM element (using \`hostElement\` with single-view or \`addView\` in multi-view mode), Flutter sizes its rendering canvas based on the computed dimensions of the host container. HTML container elements like \`

\` default to \`height: auto\` and \`position: static\`. Without explicit CSS styling, an empty host element has a computed height of \`0px\`. This causes Flutter to compute the view height as zero, which collapses the Flutter view and prevents content from rendering. ### Mandatory CSS properties {: #mandatory-css-properties } To ensure Flutter renders correctly, apply the following CSS properties to your host element: \* \*\*\`width\` and \`height\`\*\*: Set explicit or relative dimensions (for example, \`width: 100%; height: 500px;\` or \`height: 100%\` within a sized parent). The element must evaluate to a non-zero width and height. \* \*\*\`position: relative\`\*\* (or \`position: absolute\` / \`position: fixed\`): Establishes a positioning context for the canvas and DOM overlay layers that Flutter creates inside the container. \* \*\*\`overflow: hidden\`\*\*: Prevents Flutter canvas and interaction elements from overflowing the container boundaries or causing unexpected scrollbars on the host page. ### Example CSS {: #example-css } \`\`\`css title="styles.css" #flutter\_host { width: 100%; height: 600px; position: relative; overflow: hidden; } \`\`\` \`\`\`html title="index.html"\`\`\`

----
url: https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.12.1
----

The 2.12.1 release of the Dart and Flutter DevTools includes the following changes among other general improvements. To learn more about DevTools, check out the [DevTools overview](https://docs.flutter.dev/tools/devtools).

## General updates

[#](#general-updates)

* This release included a lot of cleanup and reduction in technical debt.

## Flutter inspector updates

[#](#flutter-inspector-updates)

* Added scrolling support to hover cards - [#3923](https://github.com/flutter/devtools/pull/3923)

## Performance updates

[#](#performance-updates)

* Added documentation links to the "Enhance Tracing" and "More debugging options" menus. Read the [enhance tracing documentation](https://docs.flutter.dev/tools/devtools/performance#enhance-tracing) to learn more about these features - [#3934](https://github.com/flutter/devtools/pull/3934), [#3936](https://github.com/flutter/devtools/pull/3936)

## Full commit history

[#](#full-commit-history)

To find a complete list of changes since the previous release, check out [the diff on GitHub](https://github.com/flutter/devtools/compare/v2.11.2...v2.12.1).

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/release-notes/release-notes-2.12.1.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.12.1\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/release-notes/release-notes-2.12.1.md "Report an issue with this page").

----
url: https://docs.flutter.dev/learn/pathway/tutorial/user-input
----

Learn to build text inputs, manage text with controllers, and handle user actions with buttons.

text\_fields Build a text input widget with TextField

edit\_note Manage text with TextEditingController

center\_focus\_strong Control input focus for a better user experience

touch\_app Handle user actions with callbacks and buttons

***

## Steps

1### Introductionkeyboard\_arrow\_up

The app will display the user's guesses in the `Tile` widgets, but it needs a way for the user to input those guesses. In this lesson, build that functionality with two interaction widgets: [`TextField`](https://pub.dev/documentation/material_ui/latest/material_ui/TextField-class.html) and [`IconButton`](https://pub.dev/documentation/material_ui/latest/material_ui/IconButton-class.html).

2### Implement callback functionskeyboard\_arrow\_up

To allow users to type in their guesses, you'll create a dedicated widget named `GuessInput`. First, create the basic structure for your `GuessInput` widget that requires a callback function as an argument. Name the callback function `onSubmitGuess`.

Add the following code to your `main.dart` file.

dart

```
class GuessInput extends StatelessWidget {
  GuessInput({super.key, required this.onSubmitGuess});
​
  final void Function(String) onSubmitGuess;
​
  @override
  Widget build(BuildContext context) {
    // You'll build the UI in the next steps.
    return Container(); // Placeholder
  }
}
```

The line `final void Function(String) onSubmitGuess;` declares a `final` member of the class called `onSubmitGuess` that has the type `void Function(String)`. This function takes a single `String` argument (the user's guess) and doesn't return any value (denoted by `void`).

This callback tells us that the logic that actually handles the user's guess will be written elsewhere. It's a good practice for interactive widgets to use callback functions to keep the widget that handles interactions reusable and decoupled from any specific functionality.

By the end of this lesson, the passed-in `onSubmitGuess` function is called when a user enters a guess. First, you'll need to build the visual parts of this widget. This is what the widget will look like.

3### The `TextField` widgetkeyboard\_arrow\_up

Given that the text field and button are displayed side-by-side, create them as a `Row` widget. Replace the `Container` placeholder in your `build` method with a `Row` containing an `Expanded` `TextField`:

dart

```
class GuessInput extends StatelessWidget {
  GuessInput({super.key, required this.onSubmitGuess});
​
  final void Function(String) onSubmitGuess;
​
  @override
  Widget build(BuildContext context) {
    return Row(
      children: [
        Expanded(
          child: Padding(
            padding: const EdgeInsets.all(8.0),
            child: TextField(
              maxLength: 5,
              decoration: InputDecoration(
                border: OutlineInputBorder(
                  borderRadius: BorderRadius.all(Radius.circular(35)),
                ),
              ),
            ),
          ),
        ),
      ],
    );
  }
}
```

You have seen some of these widgets in previous lessons: `Row` and `Padding`. New, though, is the [`Expanded`](https://api.flutter.dev/flutter/widgets/Expanded-class.html) widget. When a child of a `Row` (or `Column`) is wrapped in `Expanded`, it tells that child to fill all the available space along the main axis (horizontal for`Row`, vertical for `Column`) that hasn't been taken by other children. This makes the `TextField` stretch to take up all the space *except* what's taken by other widgets in the row.

The `TextField` widget is also new in this lesson and is the star of the show. This is the basic Flutter widget for text input.

Thus far, `TextField` has the following configuration.

* It's decorated with a rounded border. Notice that the decoration configuration is very similar to how a `Container` and boxes are decorated.
* Its `maxLength` property is set to 5 because the game only allows guesses of 5-letter words.

4### Handle text with `TextEditingController`keyboard\_arrow\_up

Next, you need a way to manage the text that the user types into the input field. For this, use a [`TextEditingController`](https://api.flutter.dev/flutter/widgets/TextEditingController-class.html).

dart

```
class GuessInput extends StatelessWidget {
  GuessInput({super.key, required this.onSubmitGuess});
​
  final void Function(String) onSubmitGuess;
​
  // NEW
  final TextEditingController _textEditingController = TextEditingController();
​
  @override
  Widget build(BuildContext context) {
    return Row(
      children: [
        Expanded(
          child: Padding(
            padding: const EdgeInsets.all(8.0),
            child: TextField(
              maxLength: 5,
              decoration: InputDecoration(
                border: OutlineInputBorder(
                  borderRadius: BorderRadius.all(Radius.circular(35)),
                ),
              ),
            ),
          ),
        ),
        //
      ],
    );
  }
}
```

A `TextEditingController` is used to read, clear, and modify the text in a `TextField`. To use it, pass it into the `TextField`.

dart

```
class GuessInput extends StatelessWidget {
  GuessInput({super.key, required this.onSubmitGuess});
​
  final void Function(String) onSubmitGuess;
​
  final TextEditingController _textEditingController = TextEditingController();
​
  @override
  Widget build(BuildContext context) {
    return Row(
      children: [
        Expanded(
          child: Padding(
            padding: const EdgeInsets.all(8.0),
            child: TextField(
              maxLength: 5,
              decoration: const InputDecoration(
                border: OutlineInputBorder(
                  borderRadius: BorderRadius.all(Radius.circular(35)),
                ),
              ),
              controller: _textEditingController, // NEW
            ),
          ),
        ),
      ],
    );
  }
}
```

Now, when a user inputs text, you can capture it with the `_textEditingController`, but you'll need to know *when* to capture it. The simplest way to react to input is by using the `TextField.onSubmitted` argument. This argument accepts a callback, and the callback is triggered whenever the user presses the "Enter" key on the keyboard while the text field has focus.

For now, ensure that this works by adding the following callback to `TextField.onSubmitted`:

dart

```
class GuessInput extends StatelessWidget {
  GuessInput({super.key, required this.onSubmitGuess});
​
  final void Function(String) onSubmitGuess;
​
  final TextEditingController _textEditingController = TextEditingController();
​
  @override
  Widget build(BuildContext context) {
    return Row(
      children: [
        Expanded(
          child: Padding(
            padding: const EdgeInsets.all(8.0),
            child: TextField(
              maxLength: 5,
              decoration: const InputDecoration(
                border: OutlineInputBorder(
                  borderRadius: BorderRadius.all(Radius.circular(35)),
                ),
              ),
              controller: _textEditingController,
              onSubmitted: (input) {
                // NEW
                print(_textEditingController.text); // Temporary
              },
            ),
          ),
        ),
      ],
    );
  }
}
```

In this case, you could print the `input` passed to the `onSubmitted` callback directly, but a better user experience clears the text after each guess: You need a `TextEditingController` to do that. Update the code as follows:

dart

```
class GuessInput extends StatelessWidget {
  GuessInput({super.key, required this.onSubmitGuess});
​
  final void Function(String) onSubmitGuess;
​
  final TextEditingController _textEditingController = TextEditingController();
​
  @override
  Widget build(BuildContext context) {
    return Row(
      children: [
        Expanded(
          child: Padding(
            padding: const EdgeInsets.all(8.0),
            child: TextField(
              maxLength: 5,
              decoration: const InputDecoration(
                border: OutlineInputBorder(
                  borderRadius: BorderRadius.all(Radius.circular(35)),
                ),
              ),
              controller: _textEditingController,
              onSubmitted: (_) {
                // UPDATED
                print(_textEditingController.text); // Temporary
                _textEditingController.clear(); // NEW
              },
            ),
          ),
        ),
      ],
    );
  }
}
```

5### Gain input focuskeyboard\_arrow\_up

Often, you want a specific input or widget to automatically gain focus without the user taking action. In this app, for example, the only thing a user can do is enter a guess, so the `TextField` should be focused automatically when the app launches. And after the user enters a guess, the focus should stay in the `TextField` so they can enter their next guess.

To resolve the first focus issue, set up the `autofocus` property on the `TextField`.

dart

```
class GuessInput extends StatelessWidget {
  GuessInput({super.key, required this.onSubmitGuess});
​
  final void Function(String) onSubmitGuess;
​
  final TextEditingController _textEditingController = TextEditingController();
​
  @override
  Widget build(BuildContext context) {
    return Row(
      children: [
        Expanded(
          child: Padding(
            padding: const EdgeInsets.all(8.0),
            child: TextField(
              maxLength: 5,
              decoration: const InputDecoration(
                border: OutlineInputBorder(
                  borderRadius: BorderRadius.all(Radius.circular(35)),
                ),
              ),
              controller: _textEditingController,
              autofocus: true, // NEW
              onSubmitted: (input) {
                print(input); // Temporary
                _textEditingController.clear();
              },
            ),
          ),
        ),
      ],
    );
  }
}
```

The second issue requires you to use a [`FocusNode`](https://api.flutter.dev/flutter/widgets/FocusNode-class.html) to manage the keyboard focus. You can use `FocusNode` to request that a `TextField` gain focus, (making the keyboard appear on mobile), or to know when a field has focus.

First, create a `FocusNode` in the `GuessInput` class:

dart

```
class GuessInput extends StatelessWidget {
  GuessInput({super.key, required this.onSubmitGuess});
​
  final void Function(String) onSubmitGuess;
​
  final TextEditingController _textEditingController = TextEditingController();
​
  final FocusNode _focusNode = FocusNode(); // NEW
​
  @override
  Widget build(BuildContext context) {
    // ...
    return Container();
  }
}
```

Then, use the `FocusNode` to request focus whenever the `TextField` is submitted after the controller is cleared:

dart

```
class GuessInput extends StatelessWidget {
  GuessInput({super.key, required this.onSubmitGuess});
​
  final void Function(String) onSubmitGuess;
​
  final TextEditingController _textEditingController = TextEditingController();
​
  final FocusNode _focusNode = FocusNode();
​
  @override
  Widget build(BuildContext context) {
    return Row(
      children: [
        Expanded(
          child: Padding(
            padding: const EdgeInsets.all(8.0),
            child: TextField(
              maxLength: 5,
              decoration: const InputDecoration(
                border: OutlineInputBorder(
                  borderRadius: BorderRadius.all(Radius.circular(35)),
                ),
              ),
              controller: _textEditingController,
              autofocus: true,
              focusNode: _focusNode, // NEW
              onSubmitted: (input) {
                print(input); // Temporary
                _textEditingController.clear();
                _focusNode.requestFocus(); // NEW
              },
            ),
          ),
        ),
      ],
    );
  }
}
```

Now, when you press `Enter` after inputting text, you can continue typing.

6### Use the inputkeyboard\_arrow\_up

Finally, you need to handle the text that the user enters. Recall that the constructor for `GuessInput` requires a callback called `onSubmitGuess`. In `GuessInput`, you need to use that callback. Replace the `print` statement with a call to that function.

dart

```
class GuessInput extends StatelessWidget {
  GuessInput({super.key, required this.onSubmitGuess});
​
  final void Function(String) onSubmitGuess;
​
  final TextEditingController _textEditingController = TextEditingController();
​
  final FocusNode _focusNode = FocusNode();
​
  @override
  Widget build(BuildContext context) {
    return Row(
      children: [
        Expanded(
          child: Padding(
            padding: const EdgeInsets.all(8.0),
            child: TextField(
              maxLength: 5,
              decoration: const InputDecoration(
                border: OutlineInputBorder(
                  borderRadius: BorderRadius.all(Radius.circular(35)),
                ),
              ),
              controller: _textEditingController,
              autofocus: true,
              focusNode: _focusNode,
              onSubmitted: (input) {
                onSubmitGuess(_textEditingController.text.trim());
                _textEditingController.clear();
                _focusNode.requestFocus();
              },
            ),
          ),
        ),
      ],
    );
  }
}
```

The remaining functionality is handled in the parent widget, `GamePage`. In the `build` method of that class, under the `Row` widgets in the `Column` widget's children, add the `GuessInput` widget:

dart

```
class GamePage extends StatelessWidget {
  GamePage({super.key});
​
  final Game _game = Game();
​
  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(8.0),
      child: Column(
        spacing: 5.0,
        children: [
          for (final guess in _game.guesses)
            Row(
              spacing: 5.0,
              children: [
                for (final letter in guess) Tile(letter.char, letter.type),
              ],
            ),
          GuessInput(
            onSubmitGuess: (guess) {
              // TODO, handle guess
              print(guess); // Temporary
            },
          ),
        ],
      ),
    );
  }
}
```

For the moment, this only prints the guess to prove that it's wired up correctly. Submitting the guess requires using the functionality of a `StatefulWidget`, which you'll do in the next lesson.

7### Buttonskeyboard\_arrow\_up

To improve the UX on mobile and reflect well-known UI practices, there should also be a button that can submit the guess.

There are many button widgets built into Flutter, like [`TextButton`](https://pub.dev/documentation/material_ui/latest/material_ui/TextButton-class.html), [`ElevatedButton`](https://pub.dev/documentation/material_ui/latest/material_ui/ElevatedButton-class.html), and the button you'll use now: [`IconButton`](https://pub.dev/documentation/material_ui/latest/material_ui/IconButton-class.html). All of these buttons (and many other interaction widgets) require two arguments (in addition to their optional arguments):

* A callback function passed to `onPressed`.
* A widget that makes up the content of the button (often `Text` or an `Icon`).

Add an icon button to the row widget's children list in the `GuessInput` widget, and give it an [`Icon`](https://pub.dev/documentation/material_ui/latest/material_ui/Icons-class.html) widget to display. The `Icon` widget requires configuration; in this case, the `padding` property sets the padding between the edge of the button and the icon it wraps to zero. This removes the default padding and makes the button smaller.

dart

```
class GuessInput extends StatelessWidget {
  GuessInput({super.key, required this.onSubmitGuess});
​
  final void Function(String) onSubmitGuess;
​
  final TextEditingController _textEditingController = TextEditingController();
  final FocusNode _focusNode = FocusNode();
​
  @override
  Widget build(BuildContext context) {
    return Row(
      children: [
        Expanded(child: Container()),
        IconButton(
          padding: EdgeInsets.zero,
          icon: const Icon(Icons.arrow_circle_up),
          onPressed: null,
        ),
      ],
    );
  }
}
```

The `IconButton.onPressed` callback should look familiar:

dart

```
class GuessInput extends StatelessWidget {
  GuessInput({super.key, required this.onSubmitGuess});
​
  final void Function(String) onSubmitGuess;
​
  final TextEditingController _textEditingController = TextEditingController();
  final FocusNode _focusNode = FocusNode();
​
  @override
  Widget build(BuildContext context) {
    return Row(
      children: [
        Expanded(child: Container()),
        IconButton(
          padding: EdgeInsets.zero,
          icon: const Icon(Icons.arrow_circle_up),
          onPressed: () {
            onSubmitGuess(_textEditingController.text.trim());
            _textEditingController.clear();
            _focusNode.requestFocus();
          },
        ),
      ],
    );
  }
}
```

This method does the same as the `onSubmitted` callback on the `TextField`.

8### Reviewkeyboard\_arrow\_up

text\_fields Built a text input widget with TextField keyboard\_arrow\_up

You created a `GuessInput` widget with a `TextField` for text entry. You configured it with a rounded border, character limit, and used `Expanded` to make it fill available space in the row.

edit\_note Managed text with TextEditingController keyboard\_arrow\_up

`TextEditingController` lets you read and modify text field content. You used it to capture the user's input with `.text` and clear the field after submission with `.clear()`.

center\_focus\_strong Controlled input focus for a polished UX keyboard\_arrow\_up

You used `autofocus` to focus the text field on launch and `FocusNode` with `requestFocus()` to maintain focus after each guess. These details make your app feel responsive and well-built.

touch\_app Handled user actions with callbacks and buttons keyboard\_arrow\_up

To respond to user input, you specified callback functions like `onSubmitted` and `onPressed`. Passing callback functions as constructor arguments keeps your widgets reusable and decoupled from specific logic.

9### Test yourselfkeyboard\_arrow\_up

### User Input Quiz

1 / 2

**How do you programmatically read or clear the text in a TextField?**

1. Access the TextField's text property directly.

   Not quite.

   TextField doesn't expose a text property; you need a controller.

2. Use the TextEditingController attached to the TextField.

   That's right!

   TextEditingController provides the text property to read the value and clear() method to reset it.

3. Listen to the onChanged callback and store the value in a variable.

   Not quite.

   While onChanged works for reading, clearing requires a TextEditingController.

4. Call TextField.getText() method.

   Not quite.

   TextField doesn't have a getText method; use TextEditingController instead.

**How do you programmatically move focus to a specific TextField?**

1. Call `TextField.focus()` directly.

   Not quite.

   TextField doesn't have a focus method; you use a FocusNode.

2. Set the `autofocus` property to true at runtime.

   Not quite.

   The 'autofocus' property only works on initial build, not for moving focus later.

3. Use a FocusNode and call `requestFocus()` on it.

   That's right!

   A FocusNode gives you control over focus, and calling `requestFocus()` moves focus to its associated widget.

4. Wrap the TextField in a GestureDetector and tap programmatically.

   Not quite.

   This is not how focus is managed; FocusNode is the proper approach.

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-09-29. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/learn/pathway/tutorial/user-input.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/learn/pathway/tutorial/user-input\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/learn/pathway/tutorial/user-input.md "Report an issue with this page").

----
url: https://docs.flutter.dev/cookbook/animation/physics-simulation.md
----

\# Animate a widget using a physics simulation > How to implement a physics animation.

<!--?code-excerpt path-base="cookbook/animation/physics_simulation/"?-->

Physics simulations can make app interactions feel realistic and interactive. For example, you might want to animate a widget to act as if it were attached to a spring or falling with gravity. This recipe demonstrates how to move a widget from a dragged point back to the center using a spring simulation. This recipe uses these steps: 1. Set up an animation controller. 1. Move the widget using gestures. 1. Animate the widget. 1. Calculate the velocity to simulate a springing motion. ## Step 1: Set up an animation controller Start with a stateful widget called \`DraggableCard\`:

<!--?code-excerpt "lib/starter.dart"?-->

\`\`\`dart import 'package:flutter/material.dart'; void main() { runApp(const MaterialApp(home: PhysicsCardDragDemo())); } class PhysicsCardDragDemo extends StatelessWidget { const PhysicsCardDragDemo({super.key}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(), body: const DraggableCard(child: FlutterLogo(size: 128)), ); } } class DraggableCard extends StatefulWidget { const DraggableCard({required this.child, super.key}); final Widget child; @override State createState() => \_DraggableCardState(); } class \_DraggableCardState extends State { @override void initState() { super.initState(); } @override void dispose() { super.dispose(); } @override Widget build(BuildContext context) { return Align(child: Card(child: widget.child)); } } \`\`\` Make the \`\_DraggableCardState\` class extend from \[\`SingleTickerProviderStateMixin\`]\[]. Then construct an \[\`AnimationController\`]\[] in \`initState\` and set \`vsync\` to \`this\`. :::note Extending \`SingleTickerProviderStateMixin\` allows the state object to be a \`TickerProvider\` for the \`AnimationController\`. To learn more, consult the \[\`TickerProvider\`]\[] documentation. ::: \`\`\`dart diff - class \_DraggableCardState extends State { + class \_DraggableCardState extends State + with SingleTickerProviderStateMixin { + late AnimationController \_controller; + @override void initState() { super.initState(); + \_controller = + AnimationController(vsync: this, duration: const Duration(seconds: 1)); } @override void dispose() { + \_controller.dispose(); super.dispose(); } \`\`\` ## Step 2: Move the widget using gestures Make the widget move when it's dragged, and add an \[\`Alignment\`]\[] field to the \`\_DraggableCardState\` class: \`\`\`dart diff class \_DraggableCardState extends State with SingleTickerProviderStateMixin { late AnimationController \_controller; + Alignment \_dragAlignment = Alignment.center; \`\`\` Add a \[\`GestureDetector\`]\[] that handles the \`onPanDown\`, \`onPanUpdate\`, and \`onPanEnd\` callbacks. To adjust the alignment, use \[\`MediaQuery.sizeOf()\`]\[] to get the size of the widget, and divide by 2. (This converts units of "pixels dragged" to coordinates that \[\`Align\`]\[] uses.) Then, set the \`Align\` widget's \`alignment\` to \`\_dragAlignment\`: \`\`\`dart diff @override Widget build(BuildContext context) { - return Align( - child: Card( - child: widget.child, + final size = MediaQuery.sizeOf(context); + return GestureDetector( + onPanDown: (details) {}, + onPanUpdate: (details) { + setState(() { + \_dragAlignment += Alignment( + details.delta.dx / (size.width / 2), + details.delta.dy / (size.height / 2), + ); + }); + }, + onPanEnd: (details) {}, + child: Align( + alignment: \_dragAlignment, + child: Card( + child: widget.child, + ), ), ); } \`\`\` ## Step 3: Animate the widget When the widget is released, it should spring back to the center. Add an \`Animation\` field and a \`\_runAnimation()\` method. This method defines a \`Tween\` that interpolates between the point the widget was dragged to and the point in the center. \`\`\`dart diff class \_DraggableCardState extends State with SingleTickerProviderStateMixin { late AnimationController \_controller; + late Animation \_animation; Alignment \_dragAlignment = Alignment.center; \`\`\`

<!--?code-excerpt "lib/step3.dart (runAnimation)"?-->

\`\`\`dart void \_runAnimation() { \_animation = \_controller.drive( AlignmentTween(begin: \_dragAlignment, end: Alignment.center), ); \_controller.reset(); \_controller.forward(); } \`\`\` Next, update \`\_dragAlignment\` when the \`AnimationController\` produces a value: \`\`\`dart diff @override void initState() { super.initState(); \_controller = AnimationController(vsync: this, duration: const Duration(seconds: 1)); + \_controller.addListener(() { + setState(() { + \_dragAlignment = \_animation.value; + }); + }); } \`\`\` Next, make the \`Align\` widget use the \`\_dragAlignment\` field:

<!--?code-excerpt "lib/step3.dart (align)"?-->

\`\`\`dart child: Align( alignment: \_dragAlignment, child: Card(child: widget.child), ), \`\`\` Finally, update the \`GestureDetector\` to manage the animation controller: \`\`\`dart diff return GestureDetector( - onPanDown: (details) {}, + onPanDown: (details) { + \_controller.stop(); + }, onPanUpdate: (details) { // ... }, - onPanEnd: (details) {}, + onPanEnd: (details) { + \_runAnimation(); + }, child: Align( \`\`\` ## Step 4: Calculate the velocity to simulate a springing motion The last step is to do a little math, to calculate the velocity of the widget after it's finished being dragged. This is so that the widget realistically continues at that speed before being snapped back. (The \`\_runAnimation()\` method already sets the direction by setting the animation's start and end alignment.) First, import the \`physics\` package:

<!--?code-excerpt "lib/main.dart (import)"?-->

\`\`\`dart import 'package:flutter/physics.dart'; \`\`\` The \`onPanEnd\` callback provides a \[\`DragEndDetails\`]\[] object. This object provides the velocity of the pointer when it stopped contacting the screen. The velocity is in pixels per second, but the \`Align\` widget doesn't use pixels. It uses coordinate values between \[-1.0, -1.0] and \[1.0, 1.0], where \[0.0, 0.0] represents the center. The \`size\` calculated in step 2 is used to convert pixels to coordinate values in this range. Finally, \`AnimationController\` has an \`animateWith()\` method that can be given a \[\`SpringSimulation\`]\[]:

<!--?code-excerpt "lib/main.dart (runAnimation)"?-->

\`\`\`dart /// Calculates and runs a \[SpringSimulation]. void \_runAnimation(Offset pixelsPerSecond, Size size) { \_animation = \_controller.drive( AlignmentTween(begin: \_dragAlignment, end: Alignment.center), ); // Calculate the velocity relative to the unit interval, \[0,1], // used by the animation controller. final unitsPerSecondX = pixelsPerSecond.dx / size.width; final unitsPerSecondY = pixelsPerSecond.dy / size.height; final unitsPerSecond = Offset(unitsPerSecondX, unitsPerSecondY); final unitVelocity = unitsPerSecond.distance; const spring = SpringDescription(mass: 1, stiffness: 1, damping: 1); final simulation = SpringSimulation(spring, 0, 1, -unitVelocity); \_controller.animateWith(simulation); } \`\`\` :::tip You can also configure a \[\`SpringDescription\`]\[] using \[\`SpringDescription.withDurationAndBounce()\`]\[] instead of manually specifying \`mass\`, \`stiffness\`, and \`damping\`. ::: Don't forget to call \`\_runAnimation()\` with the velocity and size:

<!--?code-excerpt "lib/main.dart (onPanEnd)"?-->

\`\`\`dart onPanEnd: (details) { \_runAnimation(details.velocity.pixelsPerSecond, size); }, \`\`\` :::note Now that the animation controller uses a simulation, its \`duration\` argument is no longer required. ::: ## Interactive example

<!--?code-excerpt "lib/main.dart"?-->

\`\`\`dartpad title="Flutter physics simulation hands-on example in DartPad" run="true" import 'package:flutter/material.dart'; import 'package:flutter/physics.dart'; void main() { runApp(const MaterialApp(home: PhysicsCardDragDemo())); } class PhysicsCardDragDemo extends StatelessWidget { const PhysicsCardDragDemo({super.key}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(), body: const DraggableCard(child: FlutterLogo(size: 128)), ); } } /// A draggable card that moves back to \[Alignment.center] when it's /// released. class DraggableCard extends StatefulWidget { const DraggableCard({required this.child, super.key}); final Widget child; @override State createState() => \_DraggableCardState(); } class \_DraggableCardState extends State with SingleTickerProviderStateMixin { late AnimationController \_controller; /// The alignment of the card as it is dragged or being animated. /// /// While the card is being dragged, this value is set to the values computed /// in the GestureDetector onPanUpdate callback. If the animation is running, /// this value is set to the value of the \[\_animation]. Alignment \_dragAlignment = Alignment.center; late Animation \_animation; /// Calculates and runs a \[SpringSimulation]. void \_runAnimation(Offset pixelsPerSecond, Size size) { \_animation = \_controller.drive( AlignmentTween(begin: \_dragAlignment, end: Alignment.center), ); // Calculate the velocity relative to the unit interval, \[0,1], // used by the animation controller. final unitsPerSecondX = pixelsPerSecond.dx / size.width; final unitsPerSecondY = pixelsPerSecond.dy / size.height; final unitsPerSecond = Offset(unitsPerSecondX, unitsPerSecondY); final unitVelocity = unitsPerSecond.distance; const spring = SpringDescription(mass: 1, stiffness: 1, damping: 1); final simulation = SpringSimulation(spring, 0, 1, -unitVelocity); \_controller.animateWith(simulation); } @override void initState() { super.initState(); \_controller = AnimationController(vsync: this); \_controller.addListener(() { setState(() { \_dragAlignment = \_animation.value; }); }); } @override void dispose() { \_controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { final size = MediaQuery.sizeOf(context); return GestureDetector( onPanDown: (details) { \_controller.stop(); }, onPanUpdate: (details) { setState(() { \_dragAlignment += Alignment( details.delta.dx / (size.width / 2), details.delta.dy / (size.height / 2), ); }); }, onPanEnd: (details) { \_runAnimation(details.velocity.pixelsPerSecond, size); }, child: Align( alignment: \_dragAlignment, child: Card(child: widget.child), ), ); } } \`\`\` \[\`Align\`]: https\://api.flutter.dev/flutter/widgets/Align-class.html \[\`Alignment\`]: https\://api.flutter.dev/flutter/painting/Alignment-class.html \[\`AnimationController\`]: https\://api.flutter.dev/flutter/animation/AnimationController-class.html \[\`DragEndDetails\`]: https\://api.flutter.dev/flutter/gestures/DragEndDetails-class.html \[\`GestureDetector\`]: https\://api.flutter.dev/flutter/widgets/GestureDetector-class.html \[\`MediaQuery.sizeOf()\`]: https\://api.flutter.dev/flutter/widgets/MediaQuery/sizeOf.html \[\`SingleTickerProviderStateMixin\`]: https\://api.flutter.dev/flutter/widgets/SingleTickerProviderStateMixin-mixin.html \[\`SpringDescription\`]: https\://api.flutter.dev/flutter/physics/SpringDescription-class.html \[\`SpringDescription.withDurationAndBounce()\`]: https\://api.flutter.dev/flutter/physics/SpringDescription/SpringDescription.withDurationAndBounce.html \[\`SpringSimulation\`]: https\://api.flutter.dev/flutter/physics/SpringSimulation-class.html \[\`TickerProvider\`]: https\://api.flutter.dev/flutter/scheduler/TickerProvider-class.html

----
url: https://docs.flutter.dev/ui/interactivity/gestures/drag-outside.md
----

\# Drag outside an app > How to drag from an app to another app or the operating system. You might want to implement drag and drop somewhere in your app. You have a couple potential approaches that you can take. One directly uses Flutter widgets and the other uses a package (\[super\_drag\_and\_drop]\[]), available on \[pub.dev]\[]. \[pub.dev]: https\://pub.dev \[super\_drag\_and\_drop]: https\://pub.dev/packages/super\_drag\_and\_drop ## Create draggable widgets within your app If you want to implement drag and drop within your application, you can use the \[\`Draggable\`]\[] widget. For insight into this approach, see the \[Drag a UI element within an app]\[] recipe. An advantage of using \`Draggable\` and \`DragTarget\` is that you can supply Dart code to decide whether to accept a drop. For more information, check out the \[\`Draggable\` widget of the week]\[video] video. \[Drag a UI element within an app]: /cookbook/effects/drag-a-widget \[\`Draggable\`]: https\://api.flutter.dev/flutter/widgets/Draggable-class.html \[\`DragTarget\`]: https\://api.flutter.dev/flutter/widgets/DragTarget-class.html \[local data]: https\://pub.dev/documentation/super\_drag\_and\_drop/latest/super\_drag\_and\_drop/DragItem/localData.html \[video]: https\://youtu.be/q4x2G\_9-Mu0?si=T4679e90U2yrloCs ## Implement drag and drop between apps If you want to implement drag and drop within your application and \_also\_ between your application and another (possibly non-Flutter) app, check out the \[super\_drag\_and\_drop]\[] package. To avoid implementing two styles of drag and drop, one for drags outside of the app and another for dragging inside the app, you can supply \[local data]\[] to the package to perform drags within your app. Another difference between this approach and using \`Draggable\` directly, is that you must tell the package up front what data your app accepts because the platform APIs need a synchronous response, which doesn't allow an asynchronous response from the framework. An advantage of using this approach is that it works across desktop, mobile, \_and\_ web.

----
url: https://docs.flutter.dev/tools/widget-previewer.md
----

\# Flutter Widget Previewer > Learn how to use the Flutter Widget Previewer to see your widgets render in real-time, separate from your full app. :::version-note The Flutter Widget Previewer is stable as of Flutter 3.47. ::: In this guide, you will learn how to use the Flutter Widget Previewer. ## Overview With the Flutter Widget Previewer, you can instantly render, inspect, and iterate on individual UI components in real-time, separate from a full app, in your IDE or browser. The following sections explain how to open the previewer, preview your widgets, search and filter previews, and customize preview configurations. ## Opening the previewer ### IDEs Android Studio, IntelliJ, and Visual Studio Code automatically start the Flutter Widget Previewer on launch. #### Android Studio and IntelliJ To open the Widget Previewer in Android Studio or IntelliJ, open the "Flutter Widget Preview" tab in the sidebar: !\[Flutter Widget Previewer in Android Studio]\(/assets/images/docs/tools/widget-previewer/android-studio.png "Android Studio") #### Visual Studio Code To open the Widget Previewer in Visual Studio Code, open the "Flutter Widget Preview" tab in the sidebar: !\[Flutter Widget Previewer in Visual Studio Code]\(/assets/images/docs/tools/widget-previewer/vscode.png "Visual Studio Code") ### Command line To start the Flutter Widget Previewer from your terminal, navigate to your Flutter project's root directory and run the following command: \`\`\`shell flutter widget-preview start \`\`\` This launches a local server and opens a real-time preview environment in your browser. To optimize startup times, the previewer automatically caches project builds in a \`.widget\_preview/\` folder in your project root. ## Preview a widget After you've started the previewer, to look at a widget, you must use the \[\`@Preview\`]\[] annotation defined in \`package:flutter/widget\_previews.dart\`. This annotation can be applied to: - \*\*Top-level functions\*\* that return a \`Widget\` or \`WidgetBuilder\`. - \*\*Static methods\*\* within a class that return a \`Widget\` or \`WidgetBuilder\`. - \*\*Public Widget constructors and factories\*\* with no required arguments. Here is a basic example of how to use the \`@Preview\` annotation to preview a \`Text\` widget: \`\`\`dart import 'package:flutter/widget\_previews.dart'; import 'package:material\_ui/material\_ui.dart'; // For Material widgets @Preview(name: 'My Sample Text') Widget mySampleText() { return const Text('Hello, World!'); } \`\`\` !\[Sample widget in Flutter Widget Previewer]\(/assets/images/docs/tools/widget-previewer/widget-previewer.png "Example widget") Each preview instance provides various controls for interacting with the previewed widget. From left to right: - \*\*Zoom in:\*\* Magnifies the widget in the preview. - \*\*Zoom out:\*\* Reduces the magnification of the widget in the preview. - \*\*Reset zoom:\*\* Returns the widget preview to its default zoom level. - \*\*Toggle between light and dark mode:\*\* Switches the preview's theme between a light and dark color scheme. - \*\*Perform a hot restart for the individual preview:\*\* Restarts only the specific widget preview, allowing changes to be applied quickly without restarting the entire application. For the case where global state has been modified (for example, a static initializer has been changed), you can hot restart the entire widget previewer using the button at the bottom right of the environment. ## Search and filter previews The Widget Previewer environment includes search and filtering capabilities to help navigate projects with many annotated previews. ### Search bar and filtering criteria Use the search bar at the top of the environment to filter previews in real-time. Click the filter dropdown next to the search field to choose which criteria to match against: - \*\*Preview name\*\*: Filters by the descriptive name set in the \[\`@Preview\`]\[] annotation. - \*\*Group name\*\*: Filters by the group name specified in the \`@Preview(group: ...)\` parameter. - \*\*Containing script\*\*: Filters by the URI of the Dart file containing the preview. - \*\*Containing package\*\*: Filters by the package name containing the preview. For example, the parameters in the following preview annotation match against the \*\*Preview name\*\* (\`'Submit Button'\`) and \*\*Group name\*\* (\`'Form Controls'\`) filters: \`\`\`dart @Preview(name: 'Submit Button', group: 'Form Controls') Widget submitButtonPreview() => const SubmitButton(); \`\`\` ### Filter previews by selected file in IDEs When viewing previews within an IDE, the widget previewer can also filter previews based on the currently selected file: !\[Filter previews by selected file in Flutter Widget Previewer]\(/assets/images/docs/tools/widget-previewer/filter-by-file.gif "Filter previews by selected file") To toggle this behavior, select the \*\*Filter previews by selected file\*\* option at the bottom of the environment. ## Customize a preview The \[\`@Preview\`]\[] annotation has several parameters you can use to customize the preview: - \*\*\`name\`\*\*: A descriptive name for the preview. - \*\*\`group\`\*\*: A name used to group related previews together in the widget previewer. - \*\*\`size\`\*\*: Artificial size constraints using a \`Size\` object. - \*\*\`textScaleFactor\`\*\*: A custom font scale. - \*\*\`wrapper\`\*\*: A function that wraps your previewed widget in a specific widget tree (for example, to inject application state into the widget tree with an \`InheritedWidget\`). - \*\*\`theme\`\*\*: A function returning a \`PreviewThemeData\` instance to provide custom and generic theming data with sequential theme layering support for complex matrix testing. - \*\*\`brightness\`\*\*: The initial theme brightness. - \*\*\`localizations\`\*\*: A function to apply a localization configuration. ## Create custom preview annotations To reduce the amount of boilerplate needed to define previews with a common set of properties, the \[\`Preview\`]\[] annotation class can be extended to create custom preview annotations tailored for your project. Here's an example of a custom preview annotation that provides custom theming data to material widgets: \`\`\`dart final class const MyCustomPreview({ super.name, super.group, super.size, super.textScaleFactor, super.wrapper, super.brightness, super.localizations, }) extends Preview { this : super(theme: PreviewMaterialThemeData.new); } final class const PreviewMaterialThemeData() extends PreviewThemeData { @override Widget apply(BuildContext context, Widget child) => Theme( data: .from(colorScheme: .fromSeed(seedColor: Colors.lime)), child: child, ); } \`\`\` !\[Providing ThemeData using custom Preview annotation in Flutter Widget Previewer]\(/assets/images/docs/tools/widget-previewer/custom-preview\.png "Injecting ThemeData in Preview annotation") Extending the \[\`Preview\`]\[] annotation class also allows for overriding the \[\`Preview\.transform()\`]\[] method. This method is invoked by the widget previewer and can be used to modify the preview at runtime, allowing for preview configurations that would not otherwise be possible in a \`const\` context: \`\`\`dart final class const TransformativePreview({ super.name, super.group, super.size, super.textScaleFactor, super.wrapper, super.brightness, super.localizations, }) extends Preview { PreviewThemeData \_themeBuilder() => PreviewMaterialThemeData(brightness: brightness); @override Preview transform() { final originalPreview = super.transform(); final themeVariant = switch (brightness) { null => 'Responsive', final b => b.name, }; // Creates a PreviewBuilder that can be used to modify // the preview contents. final builder = originalPreview\.toBuilder(); builder ..name = 'Transformed - ${originalPreview\.name}\[$themeVariant]' ..theme = \_themeBuilder; // Returns the updated Preview instance. return builder.build(); } } final class const PreviewMaterialThemeData({final Brightness? \_brightness}) extends PreviewThemeData { @override Widget apply(BuildContext context, Widget child) { return Theme( data: .from( colorScheme: .fromSeed( seedColor: Colors.lime, brightness: \_brightness ?? MediaQuery.platformBrightnessOf(context), ), ), child: child, ); } } \`\`\` !\[Runtime transformations to Preview annotation in Flutter Widget Previewer]\(/assets/images/docs/tools/widget-previewer/transformative-preview\.webp "Runtime transformations in Preview annotation") ## Creating multiple preview configurations Creating multiple previews with different configurations can be as simple as applying multiple \[\`@Preview\`]\[] annotations to a single function or constructor: \`\`\`dart @Preview( group: 'Brightness', name: 'Example - light', brightness: Brightness.light, ) @Preview( group: 'Brightness', name: 'Example - dark', brightness: Brightness.dark, ) Widget buttonPreview() => const ButtonShowcase(); \`\`\` !\[Multiple previews in Flutter Widget Previewer]\(/assets/images/docs/tools/widget-previewer/multi-preview\.png "Multiple preview example") To simplify creating multiple previews with common configurations, you can extend the \[\`MultiPreview\`]\[] to define a custom annotation that creates multiple previews. The following \[\`MultiPreview\`]\[] creates the same two previews as the previous example: \`\`\`dart /// Creates light and dark mode previews. final class MultiBrightnessPreview extends MultiPreview { const MultiBrightnessPreview(); @override List get previews => const \[ Preview( group: 'Brightness', name: 'Example - light', brightness: Brightness.light, ), Preview( group: 'Brightness', name: 'Example - dark', brightness: Brightness.dark, ), ]; } @MultiBrightnessPreview() Widget buttonPreview() => const ButtonShowcase(); \`\`\` Like \[\`Preview\`]\[], \[\`MultiPreview\`]\[] also provides a \[\`MultiPreview\.transform()\`]\[] method to perform transformations on each preview at runtime: \`\`\`dart /// Creates light and dark mode previews. final class MultiBrightnessPreview extends MultiPreview { const MultiBrightnessPreview({required this.name}); final String name; @override List get previews => const \[ Preview(brightness: Brightness.light), Preview(brightness: Brightness.dark), ]; @override List transform() { final previews = super.transform(); return previews.map((preview) { final builder = preview\.toBuilder() ..group = 'Brightness' // Building names based on values provided to the annotation // isn't possible within a constant constructor. However, // there's no such restriction when building a Preview at // runtime. ..name = '$name - ${preview\.brightness!.name}'; return builder.toPreview(); }).toList(); } } @MultiBrightnessPreview(name: 'Example') Widget buttonPreview() => const ButtonShowcase(); \`\`\` ## Restrictions and limitations The Flutter Widget Previewer has certain restrictions you should be aware of: - \*\*Public callback names\*\*: All callback arguments provided to preview annotations must be public and constant. This is required for the previewer's code generation implementation to work correctly. - \*\*Unsupported APIs\*\*: Native plugins and any APIs from the \`dart:io\` or \`dart:ffi\` libraries are not supported. This is because the widget previewer is built with Flutter Web, which doesn't have access to the underlying native platform APIs. While web plugins might work in your browser, there is no guarantee that they will work in your IDE. Widgets with transitive dependencies on \`dart:io\` or \`dart:ffi\` will load correctly, but all APIs from these libraries will throw an exception when invoked. See the \[Dart documentation on conditional imports] for details on how to structure your application to cleanly support platform-specific libraries when targeting multiple platforms. - \*\*Asset paths\*\*: When using \`fromAsset\` APIs from \`dart:ui\` to load resources, you must use \*\*package-based paths\*\* instead of direct local paths. This ensures that the assets can be correctly located and loaded within the previewer's web environment. For example, use \`'packages/my\_package\_name/assets/my\_image.png'\` instead of \`'assets/my\_image.png'\`. When previewing web widgets, the previewer automatically synchronizes assets from your host project's \`web/\` directory, applying custom theming or \`index.html\` customizations. - \*\*Unconstrained widgets\*\*: Unconstrained widgets are automatically constrained to approximately half the height and width of the widget previewer. This behavior is likely to change in the future, so constraints should be applied using the \`size\` parameter when possible. - \*\*Multi-project support in IDEs\*\*: The widget previewer currently only supports displaying previews contained within a single project or Pub workspace. We’re actively investigating options to support IDE sessions with multiple Flutter projects (\[#173550]\[]). \[\`@Preview\`]: https\://api.flutter.dev/flutter/widget\_previews/Preview-class.html \[\`Preview\`]: https\://api.flutter.dev/flutter/widget\_previews/Preview-class.html \[\`Preview\.transform()\`]: https\://api.flutter.dev/flutter/widget\_previews/Preview/transform.html \[\`MultiPreview\`]: https\://api.flutter.dev/flutter/widget\_previews/MultiPreview-class.html \[\`MultiPreview\.transform()\`]: https\://api.flutter.dev/flutter/widget\_previews/MultiPreview/transform.html \[Dart documentation on conditional imports]: https\://dart.dev/tools/pub/create-packages#conditionally-importing-and-exporting-library-files \[#173550]: https\://github.com/flutter/flutter/issues/173550

----
url: https://docs.flutter.dev/learn/pathway/tutorial.md
----

\# Flutter tutorial > Learn how to use Flutter to build pixel-perfect applications from scratch that run on mobile, desktop, and web.

Welcome to the Flutter tutorial! This tutorial teaches you how to build applications from scratch that run on mobile, desktop, and web. You'll start from the very beginning: creating a blank Flutter application. By the end, you'll have built a handful of small apps that demonstrate the critical features of Flutter development (and more!) ### Before you begin This tutorial is the third step in the Flutter learning pathway, and therefor assumes that you have: 1. A Flutter environment set up 2. Basic understanding of the Dart programming language If either of those aren't true, please start at the \[Learning pathway page]\(/learn/pathway).

\## Contents [Start learning](/learn/pathway/tutorial/create-an-app)

----
url: https://docs.flutter.dev/cookbook/lists/horizontal-list
----

You might want to create a list that scrolls horizontally rather than vertically. The [`ListView`](https://api.flutter.dev/flutter/widgets/ListView-class.html) widget supports horizontal lists.

Use the standard `ListView` constructor, passing in a horizontal `scrollDirection`, which overrides the default vertical direction.

dart

```
ListView(
  scrollDirection: Axis.horizontal,
  children: [
    for (final color in Colors.primaries)
      Container(width: 160, color: color),
  ],
),
```

## Interactive example

[#](#interactive-example)

```
import 'dart:ui';

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    const title = 'Horizontal list';

    return MaterialApp(
      title: title,
      home: Scaffold(
        appBar: AppBar(title: const Text(title)),
        body: Container(
          margin: const EdgeInsets.symmetric(vertical: 20),
          height: 200,
          child: ScrollConfiguration(
            // Add a custom scroll behavior that
            // allows all devices to drag the list.
            behavior: const MaterialScrollBehavior().copyWith(
              dragDevices: {...PointerDeviceKind.values},
            ),
            child: ListView(
              scrollDirection: Axis.horizontal,
              children: [
                for (final color in Colors.primaries)
                  Container(width: 160, color: color),
              ],
            ),
          ),
        ),
      ),
    );
  }
}
```

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/lists/horizontal-list.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/cookbook/lists/horizontal-list\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/lists/horizontal-list.md "Report an issue with this page").

----
url: https://docs.flutter.dev/perf/impeller.md
----

\# Impeller rendering engine > What is Impeller and how to enable it? :::note As of the 3.27 release, Impeller is the default rendering engine for both iOS and Android API 29+. To see \_detailed\_ info on where Impeller is currently supported, check out the \[Can I use Impeller?]\[] page. ::: \[Can I use Impeller?]: https\://flutter.dev/go/can-i-use-impeller ## What is Impeller? Impeller provides a new rendering runtime for Flutter. Impeller precompiles a \[smaller, simpler set of shaders]\[] at engine-build time so they don't compile at runtime. \[smaller, simpler set of shaders]: https\://github.com/flutter/flutter/issues/77412 For a video introduction to Impeller, check out the following talk from Google I/O 2023. Impeller has the following objectives: \* \*\*Predictable performance\*\*: Impeller compiles all shaders and reflection offline at build time. It builds all pipeline state objects upfront. The engine controls caching and caches explicitly. \* \*\*Instrumentable\*\*: Impeller tags and labels all graphics resources, such as textures and buffers. It can capture and persist animations to disk without affecting per-frame rendering performance. \* \*\*Portable\*\*: Flutter doesn't tie Impeller to a specific client-rendering API. You can author shaders once and convert them to backend-specific formats, as necessary. \* \*\*Leverages modern graphics APIs\*\*: Impeller uses, but doesn't depend on, features available in modern APIs like Metal and Vulkan. \* \*\*Leverages concurrency\*\*: Impeller can distribute single-frame workloads across multiple threads, if necessary. ## Availability Where can you use Impeller? For \_detailed\_ info, check out the \[Can I use Impeller?]\[] page. ### iOS Impeller is the \*\*only supported\*\* rendering engine on iOS with no ability to switch to Skia. ### Android Impeller is \*\*available and enabled by default on Android API 29+\*\*. On devices running lower versions of Android or don't support Vulkan, Impeller falls back to the legacy OpenGL renderer. No action on your part is necessary for this fallback behavior. \* To \_disable\_ Impeller when debugging, pass \`--no-enable-impeller\` to the \`flutter run\` command. \`\`\`console flutter run --no-enable-impeller \`\`\` \* To \_disable\_ Impeller when deploying your app, add the following setting to your project's \`AndroidManifest.xml\` file under the \`\` tag: \`\`\`xml \`\`\` ### Web Flutter on the web currently uses Skia for rendering. It might use Impeller in the future. ### macOS Impeller is \*\*available and enabled by default as of Flutter 3.47\*\*. In a future release, the ability to opt out of using Impeller will be removed. To disable Impeller on macOS when debugging, pass \`--no-enable-impeller\` to the \`flutter run\` command: \`\`\`console flutter run --no-enable-impeller \`\`\` To disable Impeller on macOS when deploying your app, add the following tags under the top-level \`\` tag in your app's \`Info.plist\` file: \`\`\`xml FLTEnableImpeller \`\`\` ### Linux Impeller is \*\*available and enabled by default as of Flutter 3.47\*\*. In a future release, the ability to opt out of using Impeller will be removed. To disable Impeller on Linux when debugging, pass \`--no-enable-impeller\` to the \`flutter run\` command: \`\`\`console flutter run --no-enable-impeller \`\`\` To disable Impeller on Linux when deploying your app, add the following setup to your project in \`linux/runner/my\_application.cc\`: \`\`\`c diff g\_autoptr(FlDartProject) project = fl\_dart\_project\_new(); + fl\_dart\_project\_set\_enable\_impeller(project, FALSE); \`\`\` ### Windows Impeller is \*\*available and enabled by default as of Flutter 3.47\*\*. In a future release, the ability to opt out of using Impeller will be removed. To disable Impeller on Windows when debugging, pass \`--no-enable-impeller\` to the \`flutter run\` command. \`\`\`console flutter run --no-enable-impeller \`\`\` To disable Impeller on Windows when deploying your app, add the following setup to your project in \`windows\runner\main.cpp\`: \`\`\`cpp diff flutter::DartProject project(L"data"); + project.set\_impeller\_switch(flutter::ImpellerSwitch::Disabled); \`\`\` ### Bugs and issues The team continues to improve Impeller support. If you encounter performance or fidelity issues with Impeller on any platform, file an issue in the \[GitHub tracker]\[file-issue]. Prefix the issue title with \`\[Impeller]\` and include a small reproducible test case. Please include the following information when submitting an issue for Impeller: \* The device you are running on, including the chip information. \* Screenshots or recordings of any visible issues. \* An \[export of the performance trace]\[]. Zip the file and attach it to the GitHub issue. \[export of the performance trace]:/tools/devtools/performance#import-and-export \[file-issue]: https\://github.com/flutter/flutter/issues/new/choose \[Impeller project board]: https\://github.com/orgs/flutter/projects/21 ## Architecture To learn more details about Impeller's design and architecture, check out the \[README.md]\[] file in the source tree. \[README.md]: https\://github.com/flutter/flutter/blob/main/engine/src/flutter/impeller/README.md ## Additional information \* \[Impeller anti-aliasing]\[impeller-antialiasing] \* \[Frequently asked questions]\[impeller-faq] \* \[Impeller's coordinate system]\[impeller-coords] \* \[How to set up Xcode for GPU frame captures with metal]\[impeller-xcode-capture] \* \[Learning to read GPU frame captures]\[impeller-read-capture] \* \[How to enable metal validation for command line apps]\[impeller-metal-validation] \* \[How Impeller works around the lack of uniform buffers in Open GL ES 2.0]\[impeller-ubo-gles2] \* \[Guidance for writing efficient shaders]\[impeller-shader-optimization] \* \[How color blending works in Impeller]\[impeller-blending] \[impeller-antialiasing]: /perf/antialiasing \[impeller-faq]: https\://github.com/flutter/flutter/blob/main/docs/engine/impeller/docs/faq.md \[impeller-coords]: https\://github.com/flutter/flutter/blob/main/docs/engine/impeller/docs/coordinate\_system.md \[impeller-xcode-capture]: https\://github.com/flutter/flutter/blob/main/docs/engine/impeller/docs/xcode\_frame\_capture.md \[impeller-read-capture]: https\://github.com/flutter/flutter/blob/main/docs/engine/impeller/docs/read\_frame\_captures.md \[impeller-metal-validation]: https\://github.com/flutter/flutter/blob/main/docs/engine/impeller/docs/metal\_validation.md \[impeller-ubo-gles2]: https\://github.com/flutter/flutter/blob/main/docs/engine/impeller/docs/ubo\_gles2.md \[impeller-shader-optimization]: https\://github.com/flutter/flutter/blob/main/docs/engine/impeller/docs/shader\_optimization.md \[impeller-blending]: https\://github.com/flutter/flutter/blob/main/docs/engine/impeller/docs/blending.md

----
url: https://docs.flutter.dev/cookbook/effects/shimmer-loading.md
----

\# Create a shimmer loading effect > How to implement a shimmer loading effect.

<!--?code-excerpt path-base="cookbook/effects/shimmer_loading"?-->

Loading times are unavoidable in application development. From a user experience (UX) perspective, the most important thing is to show your users that loading is taking place. One popular approach to communicate to users that data is loading is to display a chrome color with a shimmer animation over the shapes that approximate the type of content that is loading. The following animation shows the app's behavior: !\[Gif showing the UI loading]\(/assets/images/docs/cookbook/effects/UILoadingAnimation.webp){:.site-mobile-screenshot} This recipe begins with the content widgets defined and positioned. There is also a Floating Action Button (FAB) in the bottom-right corner that toggles between a loading mode and a loaded mode so that you can easily validate your implementation. ## Draw the shimmer shapes The shapes that shimmer in this effect are independent from the actual content that eventually loads. Therefore, the goal is to display shapes that represent the eventual content as accurately as possible. Displaying accurate shapes is easy in situations where the content has a clear boundary. For example, in this recipe, there are some circular images and some rounded rectangle images. You can draw shapes that precisely match the outlines of those images. On the other hand, consider the text that appears beneath the rounded rectangle images. You won't know how many lines of text exist until the text loads. Therefore, there is no point in trying to draw a rectangle for every line of text. Instead, while the data is loading, you draw a couple of very thin rounded rectangles that represent the text that will appear. The shape and size doesn't quite match, but that is OK. Start with the circular list items at the top of the screen. Ensure that each \`CircleListItem\` widget displays a circle with a color while the image is loading.

<!--?code-excerpt "lib/main.dart (CircleListItem)"?-->

\`\`\`dart class CircleListItem extends StatelessWidget { const CircleListItem({super.key}); @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 8), child: Container( width: 54, height: 54, decoration: const BoxDecoration( color: Colors.black, shape: BoxShape.circle, ), child: ClipOval( child: Image.network( 'https\://docs.flutter.dev/assets/images/' 'exercise/effects/split-check/Avatar1.jpg', fit: BoxFit.cover, ), ), ), ); } } \`\`\` As long as your widgets display some kind of shape, you can apply the shimmer effect in this recipe. Similar to the \`CircleListItem\` widgets, ensure that the \`CardListItem\` widgets display a color where the image will appear. Also, in the \`CardListItem\` widget, switch between the display of the text and the rectangles based on the current loading status.

<!--?code-excerpt "lib/main.dart (CardListItem)"?-->

\`\`\`dart class CardListItem extends StatelessWidget { const CardListItem({super.key, required this.isLoading}); final bool isLoading; @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: \[\_buildImage(), const SizedBox(height: 16), \_buildText()], ), ); } Widget \_buildImage() { return AspectRatio( aspectRatio: 16 / 9, child: Container( width: double.infinity, decoration: BoxDecoration( color: Colors.black, borderRadius: BorderRadius.circular(16), ), child: ClipRRect( borderRadius: BorderRadius.circular(16), child: Image.network( 'https\://docs.flutter.dev/assets/images/' 'exercise/effects/split-check/Food1.jpg', fit: BoxFit.cover, ), ), ), ); } Widget \_buildText() { if (isLoading) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: \[ Container( width: double.infinity, height: 24, decoration: BoxDecoration( color: Colors.black, borderRadius: BorderRadius.circular(16), ), ), const SizedBox(height: 16), Container( width: 250, height: 24, decoration: BoxDecoration( color: Colors.black, borderRadius: BorderRadius.circular(16), ), ), ], ); } else { return const Padding( padding: EdgeInsets.symmetric(horizontal: 8), child: Text( 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do ' 'eiusmod tempor incididunt ut labore et dolore magna aliqua.', ), ); } } } \`\`\` Your UI now renders itself differently depending on whether it's loading or loaded. By temporarily commenting out the image URLs, you can see the two ways your UI renders. !\[Gif showing the shimmer animation]\(/assets/images/docs/cookbook/effects/LoadingShimmer.webp){:.site-mobile-screenshot} The next goal is to paint all of the colored areas with a single gradient that looks like a shimmer. ## Paint the shimmer gradient The key to the effect achieved in this recipe is to use a widget called \[\`ShaderMask\`]\[]. The \`ShaderMask\` widget, as the name suggests, applies a shader to its child, but only in the areas where the child already painted something. For example, you'll apply a shader to only the black shapes that you configured earlier. Define a chrome-colored, linear gradient that gets applied to the shimmer shapes.

<!--?code-excerpt "lib/main.dart (shimmerGradient)"?-->

\`\`\`dart const \_shimmerGradient = LinearGradient( colors: \[Color(0xFFEBEBF4), Color(0xFFF4F4F4), Color(0xFFEBEBF4)], stops: \[0.1, 0.3, 0.4], begin: Alignment(-1.0, -0.3), end: Alignment(1.0, 0.3), tileMode: TileMode.clamp, ); \`\`\` Define a new stateful widget called \`ShimmerLoading\` that wraps a given \`child\` widget with a \`ShaderMask\`. Configure the \`ShaderMask\` widget to apply the shimmer gradient as a shader with a \`blendMode\` of \`srcATop\`. The \`srcATop\` blend mode replaces any color that your \`child\` widget painted with the shader color.

<!--?code-excerpt "lib/main.dart (ShimmerLoading)"?-->

\`\`\`dart class ShimmerLoading extends StatefulWidget { const ShimmerLoading({ super.key, required this.isLoading, required this.child, }); final bool isLoading; final Widget child; @override State createState() => \_ShimmerLoadingState(); } class \_ShimmerLoadingState extends State { @override Widget build(BuildContext context) { if (!widget.isLoading) { return widget.child; } return ShaderMask( blendMode: BlendMode.srcATop, shaderCallback: (bounds) { return \_shimmerGradient.createShader(bounds); }, child: widget.child, ); } } \`\`\` Wrap your \`CircleListItem\` widgets with a \`ShimmerLoading\` widget.

<!--?code-excerpt "lib/shimmer_loading_items.dart (buildTopRowItem)"?-->

\`\`\`dart Widget \_buildTopRowItem() { return ShimmerLoading(isLoading: \_isLoading, child: const CircleListItem()); } \`\`\` Wrap your \`CardListItem\` widgets with a \`ShimmerLoading\` widget.

<!--?code-excerpt "lib/shimmer_loading_items.dart (buildListItem)"?-->

\`\`\`dart Widget \_buildListItem() { return ShimmerLoading( isLoading: \_isLoading, child: CardListItem(isLoading: \_isLoading), ); } \`\`\` When your shapes are loading, they now display the shimmer gradient that is returned from the \`shaderCallback\`. This is a big step in the right direction, but there's a problem with this gradient display. Each \`CircleListItem\` widget and each \`CardListItem\` widget displays a new version of the gradient. For this recipe, the entire screen should look like one, big shimmering surface. You solve this problem in the next step. ## Paint one big shimmer To paint one big shimmer across the screen, each \`ShimmerLoading\` widget needs to paint the same full-screen gradient based on the position of that \`ShimmerLoading\` widget on the screen. To be more precise, rather than assume that the shimmer should take up the entire screen, there should be some area that shares the shimmer. Maybe that area takes up the entire screen, or maybe it doesn't. The way to solve this kind of problem in Flutter is to define another widget that sits above all of the \`ShimmerLoading\` widgets in the widget tree, and call it \`Shimmer\`. Then, each \`ShimmerLoading\` widget gets a reference to the \`Shimmer\` ancestor and requests the desired size and gradient to display. Define a new stateful widget called \`Shimmer\` that takes in a \[\`LinearGradient\`]\[] and provides descendants with access to its \`State\` object.

<!--?code-excerpt "lib/main.dart (Shimmer)"?-->

\`\`\`dart class Shimmer extends StatefulWidget { static ShimmerState? of(BuildContext context) { return context.findAncestorStateOfType(); } const Shimmer({super.key, required this.linearGradient, this.child}); final LinearGradient linearGradient; final Widget? child; @override ShimmerState createState() => ShimmerState(); } class ShimmerState extends State { @override Widget build(BuildContext context) { return widget.child ?? const SizedBox(); } } \`\`\` Add methods to the \`ShimmerState\` class in order to provide access to the \`linearGradient\`, the size of the \`ShimmerState\`'s \`RenderBox\`, and look up the position of a descendant within the \`ShimmerState\`'s \`RenderBox\`.

<!--?code-excerpt "lib/shimmer_state.dart (ShimmerState)"?-->

\`\`\`dart class ShimmerState extends State { Gradient get gradient => LinearGradient( colors: widget.linearGradient.colors, stops: widget.linearGradient.stops, begin: widget.linearGradient.begin, end: widget.linearGradient.end, ); bool get isSized => (context.findRenderObject() as RenderBox?)?.hasSize ?? false; Size get size => (context.findRenderObject() as RenderBox).size; Offset getDescendantOffset({ required RenderBox descendant, Offset offset = Offset.zero, }) { final shimmerBox = context.findRenderObject() as RenderBox; return descendant.localToGlobal(offset, ancestor: shimmerBox); } @override Widget build(BuildContext context) { return widget.child ?? const SizedBox(); } } \`\`\` Wrap all of your screen's content with the \`Shimmer\` widget.

<!--?code-excerpt "lib/main.dart (ExampleUiAnimationState)"?-->

\`\`\`dart class \_ExampleUiLoadingAnimationState extends State { @override Widget build(BuildContext context) { return Scaffold( body: Shimmer( linearGradient: \_shimmerGradient, child: ListView( // ListView Contents ), ), ); } } \`\`\` Use the \`Shimmer\` widget within your \`ShimmerLoading\` widget to paint the shared gradient.

<!--?code-excerpt "lib/shimmer_loading_state_pt2.dart (ShimmerLoadingStatePt2)"?-->

\`\`\`dart class \_ShimmerLoadingState extends State { @override Widget build(BuildContext context) { if (!widget.isLoading) { return widget.child; } // Collect ancestor shimmer information. final shimmer = Shimmer.of(context)!; if (!shimmer.isSized) { // The ancestor Shimmer widget isn't laid // out yet. Return an empty box. return const SizedBox(); } final shimmerSize = shimmer.size; final gradient = shimmer.gradient; final offsetWithinShimmer = shimmer.getDescendantOffset( descendant: context.findRenderObject() as RenderBox, ); return ShaderMask( blendMode: BlendMode.srcATop, shaderCallback: (bounds) { return gradient.createShader( Rect.fromLTWH( -offsetWithinShimmer.dx, -offsetWithinShimmer.dy, shimmerSize.width, shimmerSize.height, ), ); }, child: widget.child, ); } } \`\`\` Your \`ShimmerLoading\` widgets now display a shared gradient that takes up all of the space within the \`Shimmer\` widget. ## Animate the shimmer The shimmer gradient needs to move in order to give the appearance of a shimmering shine. The \`LinearGradient\` has a property called \`transform\` that can be used to transform the appearance of the gradient, for example, to move it horizontally. The \`transform\` property accepts a \`GradientTransform\` instance. Define a class called \`\_SlidingGradientTransform\` that implements \`GradientTransform\` to achieve the appearance of horizontal sliding.

<!--?code-excerpt "lib/original_example.dart (sliding-gradient-transform)"?-->

\`\`\`dart class \_SlidingGradientTransform extends GradientTransform { const \_SlidingGradientTransform({required this.slidePercent}); final double slidePercent; @override Matrix4? transform(Rect bounds, {TextDirection? textDirection}) { return Matrix4.translationValues(bounds.width \* slidePercent, 0.0, 0.0); } } \`\`\` The gradient slide percentage changes over time in order to create the appearance of motion. To change the percentage, configure an \[\`AnimationController\`]\[] in the \`ShimmerState\` class.

<!--?code-excerpt "lib/original_example.dart (shimmer-state-animation)" replace="/\/\/ code-excerpt-closing-bracket/}/g"?-->

\`\`\`dart class ShimmerState extends State with SingleTickerProviderStateMixin { late AnimationController \_shimmerController; @override void initState() { super.initState(); \_shimmerController = AnimationController.unbounded(vsync: this) ..repeat(min: -0.5, max: 1.5, period: const Duration(milliseconds: 1000)); } @override void dispose() { \_shimmerController.dispose(); super.dispose(); } } \`\`\` Apply the \`\_SlidingGradientTransform\` to the \`gradient\` by using the \`\_shimmerController\`'s \`value\` as the \`slidePercent\`.

<!--?code-excerpt "lib/original_example.dart (linear-gradient)"?-->

\`\`\`dart LinearGradient get gradient => LinearGradient( colors: widget.linearGradient.colors, stops: widget.linearGradient.stops, begin: widget.linearGradient.begin, end: widget.linearGradient.end, transform: \_SlidingGradientTransform( slidePercent: \_shimmerController.value, ), ); \`\`\` The gradient now animates, but your individual \`ShimmerLoading\` widgets don't repaint themselves as the gradient changes. Therefore, it looks like nothing is happening. Expose the \`\_shimmerController\` from \`ShimmerState\` as a \[\`Listenable\`]\[].

<!--?code-excerpt "lib/original_example.dart (shimmer-changes)"?-->

\`\`\`dart Listenable get shimmerChanges => \_shimmerController; \`\`\` In \`ShimmerLoading\`, listen for changes to the ancestor \`ShimmerState\`'s \`shimmerChanges\` property, and repaint the shimmer gradient.

<!--?code-excerpt "lib/original_example.dart (shimmer-loading-state)" replace="/\/\/ code-excerpt-closing-bracket/}/g"?-->

\`\`\`dart class \_ShimmerLoadingState extends State { Listenable? \_shimmerChanges; @override void didChangeDependencies() { super.didChangeDependencies(); if (\_shimmerChanges != null) { \_shimmerChanges!.removeListener(\_onShimmerChange); } \_shimmerChanges = Shimmer.of(context)?.shimmerChanges; if (\_shimmerChanges != null) { \_shimmerChanges!.addListener(\_onShimmerChange); } } @override void dispose() { \_shimmerChanges?.removeListener(\_onShimmerChange); super.dispose(); } void \_onShimmerChange() { if (widget.isLoading) { setState(() { // Update the shimmer painting. }); } } } \`\`\` Congratulations! You now have a full-screen, animated shimmer effect that turns on and off as the content loads. ## Interactive example

<!--?code-excerpt "lib/original_example.dart" remove="code-excerpt-closing-bracket"?-->

\`\`\`dartpad title="Flutter shimmer loading hands-on example in DartPad" run="true" import 'package:flutter/material.dart'; void main() { runApp( const MaterialApp( home: ExampleUiLoadingAnimation(), debugShowCheckedModeBanner: false, ), ); } const \_shimmerGradient = LinearGradient( colors: \[Color(0xFFEBEBF4), Color(0xFFF4F4F4), Color(0xFFEBEBF4)], stops: \[0.1, 0.3, 0.4], begin: Alignment(-1.0, -0.3), end: Alignment(1.0, 0.3), tileMode: TileMode.clamp, ); class ExampleUiLoadingAnimation extends StatefulWidget { const ExampleUiLoadingAnimation({super.key}); @override State createState() => \_ExampleUiLoadingAnimationState(); } class \_ExampleUiLoadingAnimationState extends State { bool \_isLoading = true; void \_toggleLoading() { setState(() { \_isLoading = !\_isLoading; }); } @override Widget build(BuildContext context) { return Scaffold( body: Shimmer( linearGradient: \_shimmerGradient, child: ListView( physics: \_isLoading ? const NeverScrollableScrollPhysics() : null, children: \[ const SizedBox(height: 16), \_buildTopRowList(), const SizedBox(height: 16), \_buildListItem(), \_buildListItem(), \_buildListItem(), ], ), ), floatingActionButton: FloatingActionButton( onPressed: \_toggleLoading, child: Icon(\_isLoading ? Icons.hourglass\_full : Icons.hourglass\_bottom), ), ); } Widget \_buildTopRowList() { return SizedBox( height: 72, child: ListView( physics: \_isLoading ? const NeverScrollableScrollPhysics() : null, scrollDirection: Axis.horizontal, shrinkWrap: true, children: \[ const SizedBox(width: 16), \_buildTopRowItem(), \_buildTopRowItem(), \_buildTopRowItem(), \_buildTopRowItem(), \_buildTopRowItem(), \_buildTopRowItem(), ], ), ); } Widget \_buildTopRowItem() { return ShimmerLoading(isLoading: \_isLoading, child: const CircleListItem()); } Widget \_buildListItem() { return ShimmerLoading( isLoading: \_isLoading, child: CardListItem(isLoading: \_isLoading), ); } } class Shimmer extends StatefulWidget { static ShimmerState? of(BuildContext context) { return context.findAncestorStateOfType(); } const Shimmer({super.key, required this.linearGradient, this.child}); final LinearGradient linearGradient; final Widget? child; @override ShimmerState createState() => ShimmerState(); } class ShimmerState extends State with SingleTickerProviderStateMixin { late AnimationController \_shimmerController; @override void initState() { super.initState(); \_shimmerController = AnimationController.unbounded(vsync: this) ..repeat(min: -0.5, max: 1.5, period: const Duration(milliseconds: 1000)); } @override void dispose() { \_shimmerController.dispose(); super.dispose(); } LinearGradient get gradient => LinearGradient( colors: widget.linearGradient.colors, stops: widget.linearGradient.stops, begin: widget.linearGradient.begin, end: widget.linearGradient.end, transform: \_SlidingGradientTransform( slidePercent: \_shimmerController.value, ), ); bool get isSized => (context.findRenderObject() as RenderBox?)?.hasSize ?? false; Size get size => (context.findRenderObject() as RenderBox).size; Offset getDescendantOffset({ required RenderBox descendant, Offset offset = Offset.zero, }) { final shimmerBox = context.findRenderObject() as RenderBox?; return descendant.localToGlobal(offset, ancestor: shimmerBox); } Listenable get shimmerChanges => \_shimmerController; @override Widget build(BuildContext context) { return widget.child ?? const SizedBox(); } } class \_SlidingGradientTransform extends GradientTransform { const \_SlidingGradientTransform({required this.slidePercent}); final double slidePercent; @override Matrix4? transform(Rect bounds, {TextDirection? textDirection}) { return Matrix4.translationValues(bounds.width \* slidePercent, 0.0, 0.0); } } class ShimmerLoading extends StatefulWidget { const ShimmerLoading({ super.key, required this.isLoading, required this.child, }); final bool isLoading; final Widget child; @override State createState() => \_ShimmerLoadingState(); } class \_ShimmerLoadingState extends State { Listenable? \_shimmerChanges; @override void didChangeDependencies() { super.didChangeDependencies(); if (\_shimmerChanges != null) { \_shimmerChanges!.removeListener(\_onShimmerChange); } \_shimmerChanges = Shimmer.of(context)?.shimmerChanges; if (\_shimmerChanges != null) { \_shimmerChanges!.addListener(\_onShimmerChange); } } @override void dispose() { \_shimmerChanges?.removeListener(\_onShimmerChange); super.dispose(); } void \_onShimmerChange() { if (widget.isLoading) { setState(() { // Update the shimmer painting. }); } } @override Widget build(BuildContext context) { if (!widget.isLoading) { return widget.child; } // Collect ancestor shimmer info. final shimmer = Shimmer.of(context)!; if (!shimmer.isSized) { // The ancestor Shimmer widget has not laid // itself out yet. Return an empty box. return const SizedBox(); } final shimmerSize = shimmer.size; final gradient = shimmer.gradient; final offsetWithinShimmer = shimmer.getDescendantOffset( descendant: context.findRenderObject() as RenderBox, ); return ShaderMask( blendMode: BlendMode.srcATop, shaderCallback: (bounds) { return gradient.createShader( Rect.fromLTWH( -offsetWithinShimmer.dx, -offsetWithinShimmer.dy, shimmerSize.width, shimmerSize.height, ), ); }, child: widget.child, ); } } //----------- List Items --------- class CircleListItem extends StatelessWidget { const CircleListItem({super.key}); @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 8), child: Container( width: 54, height: 54, decoration: const BoxDecoration( color: Colors.black, shape: BoxShape.circle, ), child: ClipOval( child: Image.network( 'https\://docs.flutter.dev/assets/images/' 'exercise/effects/split-check/Avatar1.jpg', fit: BoxFit.cover, ), ), ), ); } } class CardListItem extends StatelessWidget { const CardListItem({super.key, required this.isLoading}); final bool isLoading; @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: \[\_buildImage(), const SizedBox(height: 16), \_buildText()], ), ); } Widget \_buildImage() { return AspectRatio( aspectRatio: 16 / 9, child: Container( width: double.infinity, decoration: BoxDecoration( color: Colors.black, borderRadius: BorderRadius.circular(16), ), child: ClipRRect( borderRadius: BorderRadius.circular(16), child: Image.network( 'https\://docs.flutter.dev/assets/images/' 'exercise/effects/split-check/Food1.jpg', fit: BoxFit.cover, ), ), ), ); } Widget \_buildText() { if (isLoading) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: \[ Container( width: double.infinity, height: 24, decoration: BoxDecoration( color: Colors.black, borderRadius: BorderRadius.circular(16), ), ), const SizedBox(height: 16), Container( width: 250, height: 24, decoration: BoxDecoration( color: Colors.black, borderRadius: BorderRadius.circular(16), ), ), ], ); } else { return const Padding( padding: EdgeInsets.symmetric(horizontal: 8), child: Text( 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do ' 'eiusmod tempor incididunt ut labore et dolore magna aliqua.', ), ); } } } \`\`\` \[\`AnimationController\`]: https\://api.flutter.dev/flutter/animation/AnimationController-class.html \[cloning the example code]: https\://github.com/flutter/codelabs \[issue 44152]: https\://github.com/flutter/flutter/issues/44152 \[\`LinearGradient\`]: https\://api.flutter.dev/flutter/painting/LinearGradient-class.html \[\`Listenable\`]: https\://api.flutter.dev/flutter/foundation/Listenable-class.html \[\`ShaderMask\`]: https\://api.flutter.dev/flutter/widgets/ShaderMask-class.html

----
url: https://docs.flutter.dev/cookbook/testing/integration
----

[Integration testing concepts](/cookbook/testing/integration/introduction)

[Learn about integration testing in Flutter.](/cookbook/testing/integration/introduction)

[Measure performance with an integration test](/cookbook/testing/integration/profiling)

[How to profile performance for a Flutter app.](/cookbook/testing/integration/profiling)

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/testing/integration/index.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/cookbook/testing/integration\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/testing/integration/index.md "Report an issue with this page").

----
url: https://docs.flutter.dev/ui/widgets/material
----

Flutter's `material_ui` provides a variety of visual, behavioral, and motion-rich widgets that implement the [Material 3](https://m3.material.io/) design specification. Material enables you to design and build beautiful, usable apps that can adapt to any platform.

Previously, Material was part of the core Flutter SDK, but it has been decoupled from the framework. You can access Material from the [`material_ui`](http://pub.dev/packages/material_ui) package on pub.dev. For more information, visit the [decoupling migration guide](/release/breaking-changes/material-ui-and-cupertino-ui).

## Actions

[Common buttons](https://pub.dev/documentation/material_ui/latest/material_ui/ButtonStyle-class.html#material-3-button-types)

[Clickable blocks that start an action, such as sending an email, sharing a document, or liking a comment.](https://pub.dev/documentation/material_ui/latest/material_ui/ButtonStyle-class.html#material-3-button-types)

[Floating​Action​Button](https://pub.dev/documentation/material_ui/latest/material_ui/FloatingActionButton-class.html)

[Clickable block containing an icon that keeps a key action always in reach.](https://pub.dev/documentation/material_ui/latest/material_ui/FloatingActionButton-class.html)

[Extended Floating​Action​Button](https://pub.dev/documentation/material_ui/latest/material_ui/FloatingActionButton/FloatingActionButton.extended.html)

[Clickable block that triggers an action. These wider blocks can fit a text label and provide a larger target area.](https://pub.dev/documentation/material_ui/latest/material_ui/FloatingActionButton/FloatingActionButton.extended.html)

[Icon​Button](https://pub.dev/documentation/material_ui/latest/material_ui/IconButton-class.html)

[Clickable icons to prompt app users to take supplementary actions.](https://pub.dev/documentation/material_ui/latest/material_ui/IconButton-class.html)

[Segmented​Button](https://pub.dev/documentation/material_ui/latest/material_ui/SegmentedButton-class.html)

[Single or multiple selected clickable blocks to help people select options, switch views, or sort elements.](https://pub.dev/documentation/material_ui/latest/material_ui/SegmentedButton-class.html)

## Communication

[Badge](https://pub.dev/documentation/material_ui/latest/material_ui/Badge-class.html)

[Icon-like block that conveys dynamic content such as counts or status. It can include labels or numbers.](https://pub.dev/documentation/material_ui/latest/material_ui/Badge-class.html)

[Linear​Progress​Indicator](https://pub.dev/documentation/material_ui/latest/material_ui/LinearProgressIndicator-class.html)

[Vertical line that changes color as an ongoing process, such as loading an app or submitting a form, completes.](https://pub.dev/documentation/material_ui/latest/material_ui/LinearProgressIndicator-class.html)

[Snack​Bar](https://pub.dev/documentation/material_ui/latest/material_ui/SnackBar-class.html)

[Brief messages about app processes that display at the bottom of the screen.](https://pub.dev/documentation/material_ui/latest/material_ui/SnackBar-class.html)

## Containment

[Alert​Dialog](https://pub.dev/documentation/material_ui/latest/material_ui/AlertDialog-class.html)

[Hovering containers that prompt app users to provide more data or make a decision.](https://pub.dev/documentation/material_ui/latest/material_ui/AlertDialog-class.html)

[Bottom sheet](https://pub.dev/documentation/material_ui/latest/material_ui/BottomSheet-class.html)

[Containers that anchor supplementary content to the bottom of the screen.](https://pub.dev/documentation/material_ui/latest/material_ui/BottomSheet-class.html)

[Card](https://pub.dev/documentation/material_ui/latest/material_ui/Card-class.html)

[Container for short, related pieces of content displayed in a box with rounded corners and a drop shadow.](https://pub.dev/documentation/material_ui/latest/material_ui/Card-class.html)

[Divider](https://pub.dev/documentation/material_ui/latest/material_ui/Divider-class.html)

[Thin line that groups content in lists and containers.](https://pub.dev/documentation/material_ui/latest/material_ui/Divider-class.html)

[List​Tile](https://pub.dev/documentation/material_ui/latest/material_ui/ListTile-class.html)

[A single fixed-height row that typically contains some text as well as a leading or trailing icon.](https://pub.dev/documentation/material_ui/latest/material_ui/ListTile-class.html)

[App​Bar](https://pub.dev/documentation/material_ui/latest/material_ui/AppBar-class.html)

[Container that displays content and actions at the top of a screen.](https://pub.dev/documentation/material_ui/latest/material_ui/AppBar-class.html)

[Bottom app bar](https://pub.dev/documentation/material_ui/latest/material_ui/BottomAppBar-class.html)

[Container that displays navigation and key actions at the bottom of a screen.](https://pub.dev/documentation/material_ui/latest/material_ui/BottomAppBar-class.html)

[Navigation​Bar](https://pub.dev/documentation/material_ui/latest/material_ui/NavigationBar-class.html)

[Persistent container that enables switching between primary destinations in an app.](https://pub.dev/documentation/material_ui/latest/material_ui/NavigationBar-class.html)

[Navigation​Drawer](https://pub.dev/documentation/material_ui/latest/material_ui/NavigationDrawer-class.html)

[Container that slides from the leading edge of the app to navigate to other sections in an app.](https://pub.dev/documentation/material_ui/latest/material_ui/NavigationDrawer-class.html)

[Navigation rail](https://pub.dev/documentation/material_ui/latest/material_ui/NavigationRail-class.html)

[Persistent container on the leading edge of tablet and desktop screens to navigate to parts of an app.](https://pub.dev/documentation/material_ui/latest/material_ui/NavigationRail-class.html)

[Tab​Bar](https://pub.dev/documentation/material_ui/latest/material_ui/TabBar-class.html)

[Layered containers that organize content across different screens, data sets, and other interactions.](https://pub.dev/documentation/material_ui/latest/material_ui/TabBar-class.html)

## Selection

[Checkbox](https://pub.dev/documentation/material_ui/latest/material_ui/Checkbox-class.html)

[Form control that app users can set or clear to select one or more options from a set.](https://pub.dev/documentation/material_ui/latest/material_ui/Checkbox-class.html)

[Chip](https://pub.dev/documentation/material_ui/latest/material_ui/Chip-class.html)

[Small blocks that simplify entering information, making selections, filtering content, or triggering actions.](https://pub.dev/documentation/material_ui/latest/material_ui/Chip-class.html)

[Date​Picker](https://pub.dev/documentation/material_ui/latest/material_ui/showDatePicker.html)

[Calendar interface used to select a date or a range of dates.](https://pub.dev/documentation/material_ui/latest/material_ui/showDatePicker.html)

[Menu](https://pub.dev/documentation/material_ui/latest/material_ui/MenuAnchor-class.html)

[Container that displays a list of choices on a temporary surface.](https://pub.dev/documentation/material_ui/latest/material_ui/MenuAnchor-class.html)

[Radio](https://pub.dev/documentation/material_ui/latest/material_ui/Radio-class.html)

[Form control that app users can set or clear to select only one option from a set.](https://pub.dev/documentation/material_ui/latest/material_ui/Radio-class.html)

[Slider](https://pub.dev/documentation/material_ui/latest/material_ui/Slider-class.html)

[Form control that enables selecting a range of values.](https://pub.dev/documentation/material_ui/latest/material_ui/Slider-class.html)

[Switch](https://pub.dev/documentation/material_ui/latest/material_ui/Switch-class.html)

[Toggle control that changes the state of a single item to on or off.](https://pub.dev/documentation/material_ui/latest/material_ui/Switch-class.html)

[Time​Picker](https://pub.dev/documentation/material_ui/latest/material_ui/showTimePicker.html)

[Clock interface used to select and set a specific time.](https://pub.dev/documentation/material_ui/latest/material_ui/showTimePicker.html)

## Text inputs

[Text​Field](https://pub.dev/documentation/material_ui/latest/material_ui/TextField-class.html)

[Box into which app users can enter text. They appear in forms and dialogs.](https://pub.dev/documentation/material_ui/latest/material_ui/TextField-class.html)

Find more widgets in the [widget catalog](/ui/widgets).

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-09-29. [Report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/ui/widgets/material "Report an issue with this page").

----
url: https://docs.flutter.dev/testing/plugins-in-tests
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Plugins in Flutter tests](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

Almost all [Flutter plugins](/packages-and-plugins/using-packages) have two parts:

* Dart code, which provides the API your code calls.
* Code written in a platform-specific (or "host") language, such as Kotlin or Swift, which implements those APIs.

In fact, the native (or host) language code distinguishes a plugin package from a standard package.

Building and registering the host portion of a plugin is part of the Flutter application build process, so plugins only work when your code is running in your application, such as with `flutter run` or when running [integration tests](/cookbook/testing/integration/introduction). When running [Dart unit tests](/cookbook/testing/unit/introduction) or [widget tests](https://api.flutter.dev/flutter/flutter_test/flutter_test-library.html), the host code isn't available. If the code you are testing calls any plugins, this often results in errors like the following:

```
MissingPluginException(No implementation found for method someMethodName on channel some_channel_name)
```

When unit testing code that uses plugins, there are several options to avoid this exception. The following solutions are listed in order of preference.

## Wrap the plugin

[#](#wrap-the-plugin)

In most cases, the best approach is to wrap plugin calls in your own API, and provide a way of [mocking](/cookbook/testing/unit/mocking) your own API in tests.

This has several advantages:

* If the plugin API changes, you won't need to update your tests.
* You are only testing your own code, so your tests can't fail due to behavior of a plugin you're using.
* You can use the same approach regardless of how the plugin is implemented, or even for non-plugin package dependencies.

## Mock the plugin's public API

[#](#mock-the-plugins-public-api)

If the plugin's API is already based on class instances, you can mock it directly, with the following caveats:

* This won't work if the plugin uses non-class functions or static methods.
* Tests will need to be updated when the plugin API changes.

## Mock the plugin's platform interface

[#](#mock-the-plugins-platform-interface)

If the plugin is a [federated plugin](/packages-and-plugins/developing-packages#federated-plugins), it will include a platform interface that allows registering implementations of its internal logic. You can register a mock of that platform interface implementation instead of the public API with the following caveats:

* This won't work if the plugin isn't federated.
* Your tests will include part of the plugin's code, so plugin behavior could cause problems for your tests. For instance, if a plugin writes files as part of an internal cache, your test behavior might change based on whether you had run the test previously.
* Tests might need to be updated when the platform interface changes.

An example of when this might be necessary is mocking the implementation of a plugin used by a package that you rely on, rather than your own code, so you can't change how it's called. However, if possible, you should mock the dependency that uses the plugin instead.

## Mock the platform channel

[#](#mock-the-platform-channel)

If the plugin uses [platform channels](/platform-integration/platform-channels), you can mock the platform channels using [`TestDefaultBinaryMessenger`](https://api.flutter.dev/flutter/flutter_test/TestDefaultBinaryMessenger-class.html). This should only be used if, for some reason, none of the methods above are available, as it has several drawbacks:

* Only implementations that use platform channels can be mocked. This means that if some implementations don't use platform channels, your tests will unexpectedly use real implementations when run on some platforms.
* Platform channels are usually internal implementation details of plugins. They might change substantially even in a bugfix update to a plugin, breaking your tests unexpectedly.
* Platform channels might differ in each implementation of a federated plugin. For instance, you might set up mock platform channels to make tests pass on a Windows machine, then find that they fail if run on macOS or Linux.
* Platform channels aren't strongly typed. For example, method channels often use dictionaries and you have to read the plugin's implementation to know what the key strings and value types are.

Because of these limitations, `TestDefaultBinaryMessenger` is mainly useful in the internal tests of plugin implementations, rather than tests of code using plugins.

You might also want to check out [Testing plugins](/testing/testing-plugins).

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/testing/plugins-in-tests.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/testing/plugins-in-tests\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/testing/plugins-in-tests.md "Report an issue with this page").

----
url: https://docs.flutter.dev/ai/ai-toolkit/custom-llm-providers.md
----

\# Custom LLM providers > How to integrate with other Flutter features. The protocol connecting an LLM and the \`LlmChatView\` is expressed in the \[\`LlmProvider\` interface]\[]: \`\`\`dart abstract class LlmProvider implements Listenable { Stream generateStream(String prompt, {Iterable attachments}); Stream sendMessageStream(String prompt, {Iterable attachments}); Iterable get history; set history(Iterable history); } \`\`\` The LLM could be in the cloud or local, it could be hosted in the Google Cloud Platform or on some other cloud provider, it could be a proprietary LLM or open source. Any LLM or LLM-like endpoint that can be used to implement this interface can be plugged into the chat view as an LLM provider. The AI Toolkit comes with two providers out of the box, both of which implement the \`LlmProvider\` interface that is required to plug the provider into the following: \* The \[Firebase AI Logic provider]\[], which wraps the \`firebase\_ai\` package \* The \[Echo provider]\[], which is useful as a minimal provider example \[Echo provider]: https\://pub.dev/documentation/flutter\_ai\_toolkit/latest/flutter\_ai\_toolkit/EchoProvider-class.html \[\`LlmProvider\` interface]: https\://pub.dev/documentation/flutter\_ai\_toolkit/latest/flutter\_ai\_toolkit/LlmProvider-class.html \[Firebase AI Logic provider]: https\://pub.dev/documentation/flutter\_ai\_toolkit/latest/flutter\_ai\_toolkit/FirebaseProvider-class.html ## Implementation To build your own provider, you need to implement the \`LlmProvider\` interface with these things in mind: 1. Providing for full configuration support 1. Handling history 1. Translating messages and attachments to the underlying LLM 1. Calling the underlying LLM 1. Configuration To support full configurability in your custom provider, you should allow the user to create the underlying model and pass that in as a parameter, as the MyLlmProvider does: \`\`\`dart class MyLlmProvider extends LlmProvider ... { @immutable MyLlmProvider({ required GenerativeModel model, ... }) : \_model = model, ... final GenerativeModel \_model; ... } \`\`\` In this way, no matter what changes come to the underlying model in the future, the configuration knobs will all be available to the user of your custom provider. 2. History History is a big part of any provider—not only does the provider need to allow history to be manipulated directly, but it has to notify listeners as it changes. In addition, to support serialization and changing provider parameters, it must also support saving history as part of the construction process. The Firebase provider handles this as shown: \`\`\`dart class MyLlmProvider extends LlmProvider with ChangeNotifier { @immutable MyLlmProvider({ required GenerativeModel model, Iterable? history, ... }) : \_model = model, \_history = history?.toList() ?? \[], ... { ... } final GenerativeModel \_model; final List \_history; ... @override Stream sendMessageStream( String prompt, { Iterable attachments = const \[], }) async\* { final userMessage = ChatMessage.user(prompt, attachments); final llmMessage = ChatMessage.llm(); \_history.addAll(\[userMessage, llmMessage]); final response = \_generateStream( prompt: prompt, attachments: attachments, contentStreamGenerator: \_chat!.sendMessageStream, ); yield\* response.map((chunk) { llmMessage.append(chunk); return chunk; }); notifyListeners(); } @override Iterable get history => \_history; @override set history(Iterable history) { \_history.clear(); \_history.addAll(history); \_chat = \_startChat(history); notifyListeners(); } ... } \`\`\` You'll notice several things in this code: \* The use of \`ChangeNotifier\` to implement the \`Listenable\` method requirements from the \`LlmProvider\` interface \* The ability to pass initial history in as a constructor parameter \* Notifying listeners when there's a new user prompt/LLM response pair \* Notifying listeners when the history is changed manually \* Creating a new chat when the history changes, using the new history Essentially, a custom provider manages the history for a single chat session with the underlying LLM. As the history changes, the underlying chat either needs to be kept up to date automatically (as the Firebase provider does when you call the underlying chat-specific methods) or manually recreated (as the Firebase provider does whenever the history is set manually). 3. Messages and attachments Attachments must be mapped from the standard \`ChatMessage\` class exposed by the \`LlmProvider\` type to whatever is handled by the underlying LLM. For example, the Firebase provider maps from the \`ChatMessage\` class from the AI Toolkit to the \`Content\` type provided by the Firebase Logic AI SDK, as shown in the following example: \`\`\`dart import 'package:firebase\_ai/firebase\_ai.dart'; ... class MyLlmProvider extends LlmProvider with ChangeNotifier { ... static Part \_partFrom(Attachment attachment) => switch (attachment) { (final FileAttachment a) => DataPart(a.mimeType, a.bytes), (final LinkAttachment a) => FilePart(a.url), }; static Content \_contentFrom(ChatMessage message) => Content( message.origin.isUser ? 'user' : 'model', \[ TextPart(message.text ?? ''), ...message.attachments.map(\_partFrom), ], ); } \`\`\` The \`\_contentFrom\` method is called whenever a user prompt needs to be sent to the underlying LLM. Every provider needs to provide for its own mapping. 4. Calling the LLM How you call the underlying LLM to implement \`generateStream\` and \`sendMessageStream\` methods depends on the protocol it exposes. The Firebase provider in the AI Toolkit handles configuration and history but calls to \`generateStream\` and \`sendMessageStream\` each end up in a call to an API from the Firebase Logic AI SDK: \`\`\`dart class MyLlmProvider extends LlmProvider with ChangeNotifier { ... @override Stream generateStream( String prompt, { Iterable attachments = const \[], }) => \_generateStream( prompt: prompt, attachments: attachments, contentStreamGenerator: (c) => \_model.generateContentStream(\[c]), ); @override Stream sendMessageStream( String prompt, { Iterable attachments = const \[], }) async\* { final userMessage = ChatMessage.user(prompt, attachments); final llmMessage = ChatMessage.llm(); \_history.addAll(\[userMessage, llmMessage]); final response = \_generateStream( prompt: prompt, attachments: attachments, contentStreamGenerator: \_chat!.sendMessageStream, ); yield\* response.map((chunk) { llmMessage.append(chunk); return chunk; }); notifyListeners(); } Stream \_generateStream({ required String prompt, required Iterable attachments, required Stream Function(Content) contentStreamGenerator, }) async\* { final content = Content('user', \[ TextPart(prompt), ...attachments.map(\_partFrom), ]); final response = contentStreamGenerator(content); yield\* response .map((chunk) => chunk.text) .where((text) => text != null) .cast(); } @override Iterable get history => \_history; @override set history(Iterable history) { \_history.clear(); \_history.addAll(history); \_chat = \_startChat(history); notifyListeners(); } } \`\`\` ## Examples The \[Firebase provider]\[] implementation provides a good starting point for your own custom provider. If you'd like to see an example provider implementation with all of the calls to the underlying LLM stripped away, check out the \[Echo example app]\[], which simply formats the user's prompt and attachments as Markdown to send back to the user as its response. \[Echo example app]: https\://github.com/flutter/ai/blob/main/lib/src/providers/implementations/echo\_provider.dart \[Firebase provider]: https\://pub.dev/documentation/flutter\_ai\_toolkit/latest/flutter\_ai\_toolkit/FirebaseProvider-class.html

----
url: https://docs.flutter.dev/ui/dot-shorthands
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Dot shorthands in Flutter](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

The **dot shorthands** feature allows you to omit the explicit type when accessing static members, constructors, or enum values, provided the compiler can infer the type from the surrounding context.

## Why dot shorthands matter

[#](#why-dot-shorthands-matter)

Building layouts in Flutter often involves deeply nested widget trees. Historically, this meant repeatedly typing explicit class and enum names for properties like colors, typography, and alignment. Dot shorthands reduces this boilerplate, making your code easier to read and faster to write.

Here is a side-by-side comparison of building a simple `Container`:

### Without dot shorthands

[#](#without-dot-shorthands)

dart

```
Container(
  alignment: Alignment.center,
  padding: const EdgeInsets.all(16.0),
  child: Column(
    mainAxisAlignment: MainAxisAlignment.center,
    crossAxisAlignment: CrossAxisAlignment.start,
    children: [
      Text(
        'Hello World',
        style: TextStyle(
          fontWeight: FontWeight.bold,
        ),
      ),
    ],
  ),
);
```

### With dot shorthands

[#](#with-dot-shorthands)

dart

```
Container(
  alignment: .center, // Instead of Alignment.center,
  padding: const .all(16.0), // Instead of EdgeInsets.all(16.0)
  child: Column(
    mainAxisAlignment: .center, // Instead of MainAxisAlignment.center
    crossAxisAlignment: .start, // Instead of CrossAxisAlignment.start
    children: [
      Text(
        'Hello World',
        style: TextStyle(
          fontWeight: .bold, // Instead of FontWeight.bold
        ),
      ),
    ],
  ),
);
```

## Where to use dot shorthands

[#](#where-to-use-dot-shorthands)

Dot shorthands work anywhere the Dart compiler has a clear "context type", meaning it knows exactly what type it expects. In Flutter, this is almost everywhere inside a widget's property list.

The most common targets for dot shorthands in Flutter are:

* **Enums**: `MainAxisAlignment`, `CrossAxisAlignment`, `BoxFit`, `TextDirection`.
* **Static properties and methods**: `FontWeight` (constants like `.bold`).
* **Constructors**: `EdgeInsets.all()`, `BorderRadius.circular()`.

### Example: enums

[#](#example-enums)

When a property expects an `enum`, such as `mainAxisAlignment`, you can omit the enum's name and just provide the value preceded by a dot (`.`):

dart

```
Row(
  mainAxisAlignment: .spaceEvenly, // Infers MainAxisAlignment.spaceEvenly
  children: [ /* ... */ ],
)
```

### Example: static properties

[#](#example-static-properties)

Static properties work when the context type is exactly the class that defines the property. A common example is text styling with `FontWeight`:

dart

```
Text(
  'Feature highlights',
  style: TextStyle(
    fontWeight: .bold, // Infers FontWeight.bold
  ),
)
```

### Example: constructors

[#](#example-constructors)

You can also use dot shorthands for named constructors. Many Flutter layout properties accept a base class like `EdgeInsetsGeometry`. To support dot shorthands, Flutter adds redirecting constructors to these base classes that point to the appropriate subclasses.

dart

```
Padding(
  padding: .symmetric(horizontal: 16.0, vertical: 8.0), // Infers EdgeInsetsGeometry.symmetric
  child: Text('Spaced out text'),
)
```

You can even use `.new` to call an unnamed constructor, though this is less common in standard widget trees:

dart

```
class _MyState extends State<MyWidget> {
  final ScrollController _scrollController = .new(); // Infers ScrollController()
  // ...
}
```

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-06-18. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/ui/dot-shorthands.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/ui/dot-shorthands\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/ui/dot-shorthands.md "Report an issue with this page").

----
url: https://docs.flutter.dev/app-architecture.md
----

\# Architecting Flutter apps > Learn how to structure Flutter apps.

Architecture is an important part of building a maintainable, resilient, and scalable Flutter app. In this guide, you'll learn app architecture principles and best practices for building Flutter apps. 'Architecture' is a word that's hard to define. It's a broad term and can refer to any number of topics depending on the context. In this guide, 'architecture' refers to how to structure, organize, and design your Flutter app in order to scale as your project requirements and team grow.

\## What you'll learn \* Benefits of intentional architecture \* Common architectural principles \* The Flutter team's recommended app architecture \* MVVM and state management \* Dependency injection \* Common design patterns for writing robust Flutter applications ## Benefits of intentional architecture Good app architecture provides a number of benefits to engineering teams and their end users. \* Maintainability - App architecture makes it easier to modify, update, and fix issues over time. \* Scalability - A well thought out application allows more people to contribute to the same codebase concurrently, with minimal code conflicts. \* Testability - Applications with intentional architecture generally have simpler classes with well-defined inputs and outputs, which makes them easier to mock and test. \* Lower cognitive load - Developers who are new to the project will be more productive in a shorter amount of time, and code reviews are generally less time-consuming when code is easier to understand. \* A better user experience - Features can ship faster and with fewer bugs. ## How to use this guide This is a guide for building scalable Flutter applications and was written for teams that have multiple developers contributing to the same code base, who're building a feature-rich application. If you're writing a Flutter app that has a \*growing team and codebase\*, this guidance is for you. Along with general architectural advice, this guide gives concrete examples of best practices and includes specific recommendations. Some libraries can be swapped out, and very large teams with unique complexity might find that some parts don't apply. In either case, the ideas remain sound. This is the recommended way to build a Flutter app. In the first part of this guide, you'll learn about common architectural principles from a high level. In the second part, the guide walks through specific and concrete recommendations of architecting Flutter apps. Finally, at the end of the guide, you'll find a list of design patterns and sample code that shows the recommendations in action. \[Common architectural principles]: /app-architecture/concepts \[recommended app architecture]: /app-architecture/guide \[MVVM]: /app-architecture/guide#mvvm ## Feedback As this section of the website is evolving, we \[welcome your feedback]\[]! \[welcome your feedback]: https\://google.qualtrics.com/jfe/form/SV\_4T0XuR9Ts29acw6?page="index"

----
url: https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.45.0.md
----

\# DevTools 2.45.0 release notes > Release notes for Dart and Flutter DevTools version 2.45.0. The 2.45.0 release of the Dart and Flutter DevTools includes the following changes among other general improvements. To learn more about DevTools, check out the \[DevTools overview]\(/tools/devtools/overview). ## General updates \* Added a memory pressure warning that allows you to reduce the memory usage of DevTools in order to avoid an OOM crash. - \[#8989]\(https\://github.com/flutter/devtools/pull/8989), \[#8997]\(https\://github.com/flutter/devtools/pull/8997), \[#8998]\(https\://github.com/flutter/devtools/pull/8998) \* Fix a bug with the review history on disconnect experience. - \[#8985]\(https\://github.com/flutter/devtools/pull/8985) \* Fixed bug where DevTools would automatically resume instead of pausing on breakpoint on connection. - \[#8991]\(https\://github.com/flutter/devtools/pull/8991) \* Prevented text inputs from stealing focus from the IDE. - \[#9091]\(https\://github.com/flutter/devtools/pull/9091) ## Inspector updates \* Fixed bug where errors in the inspector tree (e.g. RenderFlex overflow errors) were not removed after a hot-reload. - \[#9106]\(https\://github.com/flutter/devtools/pull/9106) ## Debugger updates \* Combine the Pause and Resume buttons into a single button. - \[#9095]\(https\://github.com/flutter/devtools/pull/9095) ## Deep links tool updates \* Fixed an issue with Windows file paths showing incorrectly in the Deep Links page \[#9027]\(https\://github.com/flutter/devtools/pull/9027). \* Fixed an issue with the Deep Links page crashing when no iOS configuration is present \[#9027]\(https\://github.com/flutter/devtools/pull/9027). ## Full commit history To find a complete list of changes in this release, check out the \[DevTools git log]\(https\://github.com/flutter/devtools/tree/v2.45.0).

----
url: https://docs.flutter.dev/tools/devtools/memory
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Use the Memory view](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

The memory view provides insights into details of the application's memory allocation and tools to detect and debug specific issues.

For information on how to locate DevTools screens in different IDEs, check out the [DevTools overview](/tools/devtools).

To better understand the insights found on this page, the first section explains how Dart manages memory. If you already understand Dart's memory management, you can skip to the [Memory view guide](#memory-view-guide).

## Reasons to use the memory view

[#](#reasons-to-use-the-memory-view)

Use the memory view for preemptive memory optimization or when your application experiences one of the following conditions:

* Crashes when it runs out of memory
* Slows down
* Causes the device to slow down or become unresponsive
* Shuts down because it exceeded the memory limit, enforced by operating system
* Exceeds memory usage limit
  * This limit can vary depending on the type of devices your app targets.
* Suspect a memory leak

## Basic memory concepts

[#](#basic-memory-concepts)

Dart objects created using a class constructor (for example, by using `MyClass()`) live in a portion of memory called the *heap*. The memory in the heap is managed by the Dart VM (virtual machine). The Dart VM allocates memory for the object at the moment of the object creation, and releases (or deallocates) the memory when the object is no longer used (see [Dart garbage collection](https://flutter.dev/blog/flutter-dont-fear-the-garbage-collector)).

### Object types

[#](#object-types)

#### Disposable object

[#](#disposable-object)

A disposable object is any Dart object that defines a `dispose()` method. To avoid memory leaks, invoke `dispose` when the object isn't needed anymore.

#### Memory-risky object

[#](#memory-risky-object)

A memory-risky object is an object that *might* cause a memory leak, if it is not disposed properly or disposed but not GCed.

### Root object, retaining path, and reachability

[#](#root-object-retaining-path-and-reachability)

#### Root object

[#](#root-object)

Every Dart application creates a *root object* that references, directly or indirectly, all other objects the application allocates.

#### Reachability

[#](#reachability)

If, at some moment of the application run, the root object stops referencing an allocated object, the object becomes *unreachable*, which is a signal for the garbage collector (GC) to deallocate the object's memory.

#### Retaining path

[#](#retaining-path)

The sequence of references from root to an object is called the object's *retaining* path, as it retains the object's memory from the garbage collection. One object can have many retaining paths. Objects with at least one retaining path are called *reachable* objects.

#### Example

[#](#example)

The following example illustrates the concepts:

dart

```
class Child{}
​
class Parent {
  Child? child;
}
​
Parent parent1 = Parent();
​
void myFunction() {
​
  Child? child = Child();
​
  // The `child` object was allocated in memory.
  // It's now retained from garbage collection
  // by one retaining path (root …-> myFunction -> child).
​
  Parent? parent2 = Parent()..child = child;
  parent1.child = child;
​
  // At this point the `child` object has three retaining paths:
  // root …-> myFunction -> child
  // root …-> myFunction -> parent2 -> child
  // root -> parent1 -> child
​
  child = null;
  parent1.child = null;
  parent2 = null;
​
  // At this point, the `child` instance is unreachable
  // and will eventually be garbage collected.
​
  …
}
```

### Shallow size vs retained size

[#](#shallow-size-vs-retained-size)

**Shallow size** includes only the size of the object and its references, while **retained size** also includes the size of the retained objects.

The **retained size** of the root object includes all reachable Dart objects.

In the following example, the size of `myHugeInstance` isn't part of the parent's or child's shallow sizes, but is part of their retained sizes:

dart

```
class Child{
  /// The instance is part of both [parent] and [parent.child]
  /// retained sizes.
  final myHugeInstance = MyHugeInstance();
}
​
class Parent {
  Child? child;
}
​
Parent parent = Parent()..child = Child();
```

In DevTools calculations, if an object has more than one retaining path, its size is assigned as retained only to the members of the shortest retaining path.

In this example the object `x` has two retaining paths:

```
root -> a -> b -> c -> x
root -> d -> e -> x (shortest retaining path to `x`)
```

Only members of the shortest path (`d` and `e`) will include `x` into their retaining size.

### Memory leaks happen in Dart?

[#](#memory-leaks-happen-in-dart)

Garbage collector cannot prevent all types of memory leaks, and developers still need to watch objects to have leak-free lifecycle.

#### Why can't the garbage collector prevent all leaks?

[#](#why-cant-the-garbage-collector-prevent-all-leaks)

While the garbage collector takes care of all unreachable objects, it's the responsibility of the application to ensure that unneeded objects are no longer reachable (referenced from the root).

So, if non-needed objects are left referenced (in a global or static variable, or as a field of a long-living object), the garbage collector can't recognize them, the memory allocation grows progressively, and the app eventually crashes with an `out-of-memory` error.

#### Why closures require extra attention

[#](#why-closures-require-extra-attention)

One hard-to-catch leak pattern relates to using closures. In the following code, a reference to the designed-to-be short-living `myHugeObject` is implicitly stored in the closure context and passed to `setHandler`. As a result, `myHugeObject` won't be garbage collected as long as `handler` is reachable.

dart

```
  final handler = () => print(myHugeObject.name);
  setHandler(handler);
```

#### Why `BuildContext` requires extra attention

[#](#why-buildcontext-requires-extra-attention)

An example of a large, short-living object that might squeeze into a long-living area and thus cause leaks, is the `context` parameter passed to Flutter's `build` method.

The following code is leak prone, as `useHandler` might store the handler in a long-living area:

dart

```
// BAD: DO NOT DO THIS
// This code is leak prone:
@override
Widget build(BuildContext context) {
  final handler = () => apply(Theme.of(context));
  useHandler(handler);
…
```

#### How to fix leak prone code?

[#](#how-to-fix-leak-prone-code)

The following code is not leak prone, because:

1. The closure doesn't use the large and short-living `context` object.
2. The `theme` object (used instead) is long-living. It is created once and shared between `BuildContext` instances.

dart

```
// GOOD
@override
Widget build(BuildContext context) {
  final theme = Theme.of(context);
  final handler = () => apply(theme);
  useHandler(handler);
…
```

#### General rule for `BuildContext`

[#](#general-rule-for-buildcontext)

In general, use the following rule for a `BuildContext`: if the closure doesn't outlive the widget, it's ok to pass the context to the closure.

Stateful widgets require extra attention. They consist of two classes: the [widget and the widget state](/ui/interactivity#creating-a-stateful-widget), where the widget is short living, and the state is long living. The build context, owned by the widget, should never be referenced from the state's fields, as the state won't be garbage collected together with the widget, and can significantly outlive it.

### Memory leak vs memory bloat

[#](#memory-leak-vs-memory-bloat)

In a memory leak, an application progressively uses memory, for example, by repeatedly creating a listener, but not disposing it.

Memory bloat uses more memory than is necessary for optimal performance, for example, by using overly large images or keeping streams open through their lifetime.

Both leaks and bloats, when large, cause an application to crash with an `out-of-memory` error. However, leaks are more likely to cause memory issues, because even a small leak, if repeated many times, leads to a crash.

## Memory view guide

[#](#memory-view-guide)

The DevTools memory view helps you investigate memory allocations (both in the heap and external), memory leaks, memory bloat, and more. The view has the following features:

* [**Expandable chart**](#expandable-chart)

  Get a high-level trace of memory allocation, and view both standard events (like garbage collection) and custom events (like image allocation).

* [**Profile Memory** tab](#profile-memory-tab)

  See current memory allocation listed by class and memory type.

* [**Diff Snapshots** tab](#diff-snapshots-tab)

  Detect and investigate a feature's memory management issues.

* [**Trace Instances** tab](#trace-instances-tab)

  Investigate a feature's memory management for a specified set of classes.

### Expandable chart

[#](#expandable-chart)

The expandable chart provides the following features:

#### Memory anatomy

[#](#memory-anatomy)

A timeseries graph visualizes the state of Flutter memory at successive intervals of time. Each data point on the chart corresponds to the timestamp (x-axis) of measured quantities (y-axis) of the heap. For example, usage, capacity, external, garbage collection, and resident set size are captured.

#### Memory overview chart

[#](#memory-overview-chart)

The memory overview chart is a timeseries graph of collected memory statistics. It visually presents the state of the Dart or Flutter heap and Dart's or Flutter's native memory over time.

The chart's x-axis is a timeline of events (timeseries). The data plotted in the y-axis all has a timestamp of when the data was collected. In other words, it shows the polled state (capacity, used, external, RSS (resident set size), and GC (garbage collection)) of the memory every 500 ms. This helps provide a live appearance on the state of the memory as the application is running.

Clicking the **Legend** button displays the collected measurements, symbols, and colors used to display the data.

The **Memory Size Scale** y-axis automatically adjusts to the range of data collected in the current visible chart range.

The quantities plotted on the y-axis are as follows:

* **Dart/Flutter Heap**

  Objects (Dart and Flutter objects) in the heap.

* **Dart/Flutter Native**

  Memory that isn't in the Dart/Flutter heap but is still part of the total memory footprint. Objects in this memory would be native objects (for example, from reading a file into memory, or a decoded image). The native objects are exposed to the Dart VM from the native OS (such as Android, Linux, Windows, iOS) using a Dart embedder. The embedder creates a Dart wrapper with a finalizer, allowing Dart code to communicate with these native resources. Flutter has an embedder for Android and iOS. For more information, see [Command-line and server apps](https://dart.dev/server), [Dart on the server with Dart Frog](https://dartfrog.vgv.dev/), [Custom Flutter Engine Embedders](https://github.com/flutter/flutter/blob/main/docs/engine/Custom-Flutter-Engine-Embedders.md), [Dart web server deployment with Heroku](https://www.youtube.com/watch?v=nkTUMVNelXA).

* **Timeline**

  The timestamps of all collected memory statistics and events at a particular point in time (timestamp).

* **Raster Cache**

  The size of the Flutter engine's raster cache layer(s) or picture(s), while performing the final rendering after compositing. For more information, see the [Flutter architectural overview](/resources/architectural-overview) and [DevTools Performance view](/tools/devtools/performance).

* **Allocated**

  The current total capacity of all Dart heaps. This is typically slightly larger than the total size of all heap objects.

* **RSS - Resident Set Size**

  The resident set size displays the amount of memory for a process. It doesn't include memory that is swapped out. It includes memory from shared libraries that are loaded, as well as all stack and heap memory. For more information, see [Dart VM internals](https://mrale.ph/dartvm/).

[]()

### Profile Memory tab

[#](#profile-memory-tab)

Use the **Profile Memory** tab to see current memory allocation by class and memory type. For a deeper analysis in Google Sheets or other tools, download the data in CSV format. Toggle **Refresh on GC**, to see allocation in real time.

### Diff Snapshots tab

[#](#diff-snapshots-tab)

Use the **Diff Snapshots** tab to investigate a feature's memory management. Follow the guidance on the tab to take snapshots before and after interaction with the application, and diff the snapshots:

Tap the **Filter classes and packages** button, to narrow the data:

For a deeper analysis in Google Sheets or other tools, download the data in CSV format.

[]()

### Trace Instances tab

[#](#trace-instances-tab)

Use the **Trace Instances** tab to investigate what methods allocate memory for a set of classes during feature execution:

1. Select classes to trace
2. Interact with your app to trigger the code you are interested in
3. Tap **Refresh**
4. Select a traced class
5. Review the collected data

#### Bottom up vs call tree view

[#](#bottom-up-vs-call-tree-view)

Switch between bottom-up and call tree views depending on specifics of your tasks.

The call tree view shows the method allocations for each instance. The view is a top-down representation of the call stack, meaning that a method can be expanded to show its callees.

The bottom-up view shows the list of different call stacks that have allocated the instances.

## Other resources

[#](#other-resources)

For more information, check out the following resources:

* To learn how to monitor an app's memory usage and detect memory leaks using DevTools, check out a guided [Memory View tutorial](https://medium.com/@fluttergems/mastering-dart-flutter-devtools-memory-view-part-7-of-8-e7f5aaf07e15).
* To understand Android memory structure, check out [Android: Memory allocation among processes](https://developer.android.com/topic/performance/memory-management).

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-07-31. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/memory.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/tools/devtools/memory\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/memory.md "Report an issue with this page").

----
url: https://docs.flutter.dev/add-to-app/android
----

[Manage plugins and dependencies in add-to-app](/add-to-app/android/plugin-setup)

[Learn how to use plugins and share your plugin's library dependencies with your existing app.](/add-to-app/android/plugin-setup)

[Add a Flutter Fragment to an Android app](/add-to-app/android/add-flutter-fragment)

[Learn how to add a Flutter Fragment to your existing Android app.](/add-to-app/android/add-flutter-fragment)

[Integrate a Flutter module into your Android project](/add-to-app/android/project-setup)

[Learn how to integrate a Flutter module into your existing Android project.](/add-to-app/android/project-setup)

[Add a Flutter View to an Android app](/add-to-app/android/add-flutter-view)

[Learn how to perform advanced integrations via Flutter Views.](/add-to-app/android/add-flutter-view)

[Add a Flutter screen to an Android app](/add-to-app/android/add-flutter-screen)

[Learn how to add a single Flutter screen to your existing Android app.](/add-to-app/android/add-flutter-screen)

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/add-to-app/android/index.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/add-to-app/android\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/add-to-app/android/index.md "Report an issue with this page").

----
url: https://docs.flutter.dev/ui/accessibility/accessibility-testing
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Accessibility testing](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

## Accessibility regulations

[#](#accessibility-regulations)

To ensure your app is accessible, check it against public standards like the [Web Content Accessibility Guidelines (WCAG) 2](https://www.w3.org/WAI/standards-guidelines/wcag/), the [EN 301 549](https://www.etsi.org/deliver/etsi_en/301500_301599/301549/03.02.01_60/en_301549v030201p.pdf), and use resources like the [Voluntary Product Accessibility Template (VPAT)](https://www.itic.org/policy/accessibility/vpat) to self-assess your product. For more details on these regulations, check out the main [accessibility page](/ui/accessibility).

## Inspecting accessibility support

[#](#inspecting-accessibility-support)

We recommend using automated accessibility scanners to test the following:

* For Android:

  1. Install the [Accessibility Scanner](https://play.google.com/store/apps/details?id=com.google.android.apps.accessibility.auditor\&hl=en) for Android
  2. Enable the Accessibility Scanner from **Android Settings > Accessibility > Accessibility Scanner > On**.
  3. Navigate to the Accessibility Scanner 'checkbox' icon button to initiate a scan.

* For iOS:

  1. Open the `iOS` folder of your Flutter app in Xcode.
  2. Select a Simulator as the target, and click the **Run** button.
  3. In Xcode, select **Xcode > Open Developer Tools > Accessibility Inspector**.
  4. In the Accessibility Inspector, select **Inspection > Enable Point to Inspect**, and then select the various user interface elements in your running Flutter app to inspect their accessibility attributes.
  5. In the Accessibility Inspector, select **Audit** in the toolbar, and then select **Run Audit** to get a report of potential issues.

* For web:

  1. Open Chrome DevTools (or similar tools in other browsers).
  2. Inspect the HTML tree under semantics host, containing the ARIA attributes generated by Flutter.
  3. In Chrome, the "Elements" tab has an "Accessibility" sub-tab that can be used to inspect the data exported to semantics tree.

## Testing accessibility on mobile

[#](#testing-accessibility-on-mobile)

Test your app using Flutter's [Accessibility Guideline API](https://api.flutter.dev/flutter/flutter_test/AccessibilityGuideline-class.html). This API checks if your app's UI meets Flutter's accessibility recommendations. These cover recommendations for text contrast, target size, and target labels.

The following snippet shows how to use the Guideline API on a sample widget named `AccessibleApp`:

test/a11y\_test.dart

dart

```
import 'package:flutter_test/flutter_test.dart';
import 'package:your_accessible_app/main.dart';
​
void main() {
  testWidgets('Follows a11y guidelines', (tester) async {
    final SemanticsHandle handle = tester.ensureSemantics();
    await tester.pumpWidget(const AccessibleApp());
​
    // Checks that tappable nodes have a minimum size of 48 by 48 pixels
    // for Android.
    await expectLater(tester, meetsGuideline(androidTapTargetGuideline));
​
    // Checks that tappable nodes have a minimum size of 44 by 44 pixels
    // for iOS.
    await expectLater(tester, meetsGuideline(iOSTapTargetGuideline));
​
    // Checks that touch targets with a tap or long press action are labeled.
    await expectLater(tester, meetsGuideline(labeledTapTargetGuideline));
​
    // Checks whether semantic nodes meet the minimum text contrast levels.
    // The recommended text contrast is 3:1 for larger text
    // (18 point and above regular).
    await expectLater(tester, meetsGuideline(textContrastGuideline));
    handle.dispose();
  });
}
```

To try these tests out, run them on a [new app created with `flutter create`](/reference/create-new-app). Each button on that app's main screen serves as a tappable target with text rendered in an 18-point font.

You can add Guideline API tests alongside other [widget tests](/testing/overview#widget-tests), or in a separate file, such as `test/a11y_test.dart` in this example.

## Testing accessibility on the web

[#](#testing-accessibility-on-the-web)

You can debug accessibility by visualizing the semantic nodes created for your web app using the following command line flag in profile and release modes:

```
flutter run -d chrome --profile --dart-define=FLUTTER_WEB_DEBUG_SHOW_SEMANTICS=true
```

With the flag activated, the semantic nodes appear on top of the widgets; you can verify that the semantic elements are placed where they should be. If the semantic nodes are incorrectly placed, please [file a bug report](https://goo.gle/flutter_web_issue).

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/ui/accessibility/accessibility-testing.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/ui/accessibility/accessibility-testing\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/ui/accessibility/accessibility-testing.md "Report an issue with this page").

----
url: https://docs.flutter.dev/tools/devtools/custom-tool
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Build custom tooling in Flutter and Dart DevTools](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

Have you ever wanted to build developer tooling for Dart and Flutter but didn’t know where to start? Or maybe you didn’t want to go through all the work of establishing a connection to a running Dart or Flutter application to access debugging data? Then, even if you did create a development tool, how would you deploy it or give users easy access to it? You can create developer tooling without all these hurdles.

With the Dart & Flutter DevTools extensions framework, you can easily build developer tooling that is tightly integrated with the existing DevTools tooling suite. Extensions are built using Flutter web and leverage existing frameworks and utilities from DevTools to simplify the developer tool authoring experience.

## How do DevTools extensions work?

[#](#how-do-devtools-extensions-work)

Extensions are shipped as part of a pub package. You can add a DevTools extension to an existing pub package, or you can create a new package that provides a DevTools extension only. In both these scenarios, the end-user must list a dependency on the package providing the DevTools extension in order to see the extension in DevTools.

For example, imagine we have some `package:foo`, and this package provides a DevTools extension. When a user depends on `package:foo` in their app, they automatically get access to the DevTools extension provided by this package. When DevTools detects the `package:foo` extension is available, based on information from the user’s app or from their IDE, a new tab "Foo" will be added to DevTools that contains the developer tools provided by `package:foo`.

Some examples of packages that have added a DevTools extension to an existing package are [`package:shared_preferences`](https://pub.dev/packages/shared_preferences), [`package:provider`](https://pub.dev/packages/provider), [`package:patrol`](https://pub.dev/packages/patrol), and [`package:drift`](https://pub.dev/packages/drift).

## What types of tools are supported?

[#](#what-types-of-tools-are-supported)

With the DevTools extensions framework you can build many types of tools, including:

* Companion tools for existing packages.
* New tools that are shipped as their own package.
* Tools that interact with a running application.
* Tools that interact with project files opened in the IDE.
* Tools that interact with the Analysis server.

The DevTools Extensions framework comes with out-of-the-box features that make distributing your extension to users seamless. For example, users can:

* Use your tool from DevTools in the browser.
* Use your tool embedded directly in their IDE.
* Discover and open your tool from Dart and Flutter supported IDEs.

Next, learn how to write a DevTools extension.

***

## Write a DevTools extension

[#](#write-a-devtools-extension)

Before you get started, you need:

* Flutter SDK >= 3.17 & Dart SDK >= 3.2.
* A [pub](https://pub.dev) package that (in your opinion) needs a custom DevTools extension.

### Set up your package hierarchy

[#](#set-up-your-package-hierarchy)

You will provide either a standalone extension or a companion extension.

#### Standalone extension

[#](#standalone-extension)

For a standalone extension (that isn't being shipped as part of an existing pub package), your extension can include source code in the same package that the extension is shipped with. This simplifies development, and since users of your package will add your package as a dev\_dependency, the size of your package won't affect the user's app size. Your package structure will look like the following:

yaml

```
my_new_tool
  extension/
    devtools/
      build/
        ...  # pre-compiled output of the Flutter web app under lib/
      config.yaml
  lib/  # source code for your extension Flutter web app
    src/
      ...
```

Since the extension is built as a Flutter web app, use `flutter create` to generate the package:

```
flutter create --template app --platforms web my_new_tool
```

Next, use the `my_new_tool` package to configure your extension in the next step.

#### Companion extensions

[#](#companion-extensions)

For a companion extension (that is shipped as part of an existing pub package), consider placing your extension source code outside of your pub package. This keeps your package size as small as possible, to avoid inflating the size of user apps that depend on your package. Here is the recommended package structure:

yaml

```
foo/  # formerly the repository root of your pub package
  packages/
    foo/  # your pub package
      extension/
        devtools/
          build/
            ...  # pre-compiled output of foo_devtools_extension/lib
          config.yaml
    foo_devtools_extension/
      lib/  # source code for your extension Flutter web app
```

## Configure your extension

[#](#configure-your-extension)

In the Dart package that provides the DevTools extension to users, add a top-level extension directory:

yaml

```
foo/
  extension/
  lib/
  ...
```

Under the `extension` directory, create the following structure **exactly as shown**:

yaml

```
extension/
  devtools/
    build/
    config.yaml
```

The `config.yaml` file contains metadata that DevTools needs to load the extension:

yaml

```
name: foo
version: 0.0.1
issueTracker: <link_to_your_issue_tracker.com>
materialIconCodePoint: '0xe0b1'
requiresConnection: true  # optional field - defaults to true
```

Copy the `config.yaml` file content as shown and paste it into the `config.yaml` file you just created in your package. **It's important that you use the exact file name and field names as shown, or else your extension might fail to load in DevTools**.

For each key, fill in the appropriate value for your package.

* `name`: The package name for this DevTools extension. The value of this field is used in the extension page title bar. \[**required**]
* `version`: The version of your DevTools extension. This version number should evolve over time as you ship new features for your extension. The value of this field is used in the extension page title bar. \[**required**]
* `issueTracker`: The URL for your issue tracker. When a user clicks the **Report an issue** link in the DevTools UI, they are directed to this URL. \[**required**]

- `materialIconCodePoint`: Corresponds to the codepoint value of an icon from [`material/icons.dart`](https://github.com/flutter/flutter/blob/master/packages/flutter/lib/src/material/icons.dart). This icon is used for the extension’s tab in the top-level DevTools tab bar. \[**required**]

* `requiresConnection`: Indicates whether the extension requires a connected Dart or Flutter app to use. This is an optional field that will default to `true` if unspecified. \[**optional**]

For the most up-to-date documentation on the `config.yaml` spec, visit [extension\_config\_spec.md](https://github.com/flutter/devtools/blob/master/packages/devtools_extensions/extension_config_spec.md).

## Build your extension

[#](#build-your-extension)

Use the following steps to build an extension.

### Create the Flutter web app

[#](#create-the-flutter-web-app)

From the directory where you want your extension source code to live, run the following command, replacing `foo_devtools_extension` with `<your_package_name>_devtools_extension`:

```
flutter create --template app --platforms web foo_devtools_extension
```

### Add the `package:devtools_extensions` dependency

[#](#add-the-package-devtools-extensions-dependency)

```
flutter pub add devtools_extensions
```

You will likely also want to add a dependency on [`package:devtools_app_shared`](https://pub.dev/packages/devtools_app_shared), which contains shared services, utilities, and UI components to use while building your extension. Visit [`devtools_app_shared/example`](https://github.com/flutter/devtools/tree/master/packages/devtools_app_shared/example) for sample usages.

```
flutter pub add devtools_app_shared
```

### Add the `DevToolsExtension` widget

[#](#add-the-devtoolsextension-widget)

In `lib/main.dart`, add the following imports:

dart

```
import 'package:devtools_extensions/devtools_extensions.dart';
import 'package:material_ui/material_ui.dart';
​
void main() {
  runApp(const FooDevToolsExtension());
}
​
class FooDevToolsExtension extends StatelessWidget {
  const FooDevToolsExtension({super.key});
​
  @override
  Widget build(BuildContext context) {
    return const DevToolsExtension(
      child: Placeholder(), // Build your extension here
    );
  }
}
```

The `DevToolsExtension` widget automatically initializes all extensions required to interact with DevTools. From anywhere in your extension web app, you can access the following globals:

* `extensionManager`: a manager for interacting with DevTools or the extensions framework.
* `serviceManager`: a manager for interacting with the connected vm service, if present.
* `dtdManager`: a manager for interacting with the Dart Tooling Daemon, if present.

## Debug your extension

[#](#debug-your-extension)

When developing and maintaining your DevTools extension, you’ll want to run, debug, and test your extension Flutter web app. You have a couple of different options for this, outlined below.

### Option A: Use the Simulated DevTools Environment (recommended for development)

[#](#option-a-use-the-simulated-devtools-environment-recommended-for-development)

For debugging purposes, you will likely want to use the "simulated DevTools environment". This is a simulated environment that allows you to build your extension without having to develop it as an embedded iFrame in DevTools. Running your extension this way will wrap your extension with an environment that simulates the DevTools-to-DevTools extension connection. It also gives you access to hot restart and a faster development cycle.

1. *Your DevTools extension.*
2. *The VM service URI for a test app that your DevTools extension will interact with. This app should depend on your extension’s parent package (`package:foo` in this example).*
3. *Buttons to perform actions that a user may trigger from DevTools.*
4. *Logs showing the messages that will be sent between your extension and DevTools.*

The simulated environment is enabled by the environment parameter `use_simulated_environment`. To run your extension web app with this flag enabled, add a configuration to your `launch.json` file in VS code:

json

```
{
    ...
    "configurations": [
        ...
        {
            "name": "foo_devtools_extension + simulated environment",
            "cwd": "packages/foo_devtools_extension",
            "request": "launch",
            "type": "dart",
            "args": [
                "--dart-define=use_simulated_environment=true"
            ],
        },
    ]
}
```

or launch your app from the command line with the added flag:

```
flutter run -d chrome -dart-define=use_simulated_environment=true
```

### Option B: Use a real DevTools environment

[#](#option-b-use-a-real-devtools-environment)

Once you develop your extension to a point where you are ready to test your changes in a real DevTools environment, you need to perform a series of setup steps:

1. Develop your extension to a point where you are ready to test your changes in a real DevTools environment. Build your flutter web app and copy the built assets from \`your\_extension\_web\_app/build/web\` to your pub package's \`extension/devtools/build directory\`.

   Use the `build_and_copy` command from `package:devtools_extensions` to help with this step.

   `console cd your_extension_web_app; flutter pub get; dart run devtools_extensions build_and_copy --source=. --dest=path/to/your_pub_package/extension/devtools`

   To ensure that your extension is setup properly for loading in DevTools, run the `validate` command from `package:devtools_extensions`. The `--package` argument should point to the root of the Dart package that this extension will be published with.

   `console cd your_extension_web_app; flutter pub get; dart run devtools_extensions validate --package=path/to/your_pub_package`

2. Prepare a test environment with a dependency on your pub package that is providing the extension.

   In the Dart or Flutter project where you are adding a dependency on your package, add a [`path`](https://dart.dev/tools/pub/dependencies#path-packages) dependency that points to your local package source code (the package that contains the `extension/devtools/` directory with your extension's assets). Once you have done this, run `pub get` on the package.

   \* **If your extension requires a running application**, then you’ll need to run the app that depends on your extension. \* **If your extension does not require a running application**, then you will need to open the test Dart or Flutter project that depends on your package in a supported IDE (VS Code or IntelliJ / Android Studio).

3. Start DevTools

   Use one of the following ways to start DevTools:

   \* **If your extension requires a running application**, you can open DevTools either from the URI that was printed to the command line when you ran the test app, or from the IDE where you ran your test app. \* **If your extension does not require a running application**, you can open your Dart or Flutter project that depends on your package in a supported IDE (VS Code or IntelliJ / Android Studio). Open DevTools from the IDE to view your extension in the browser. \* **If you need local or unreleased changes from DevTools**, you’ll need to build and run DevTools from source. See the DevTools [CONTRIBUTING.md](https://github.com/flutter/devtools/tree/master/packages/devtools_extensions) for a guide on how to do this. You’ll need to build DevTools with the server and the front end to test extensions ([instructions](https://github.com/flutter/devtools/blob/master/CONTRIBUTING.md#development-devtools-server--devtools-flutter-web-app)).

4. Connect your test app to DevTools if it is not connected already, and you should see a tab in the DevTools app bar for your extension. The enabled or disabled state of your extension is managed by DevTools, which is exposed from an \*\*Extensions\*\* menu in DevTools, available from the action buttons in the upper right corner of the screen.

Once you've opened DevTools, a tab in the DevTools app bar should appear for your extension. The enabled or disabled state of your extension is managed by DevTools, which is exposed from an "Extensions" menu, available from the action buttons in the upper right corner of the screen.

## Publish your package with a DevTools extension

[#](#publish-your-package-with-a-devtools-extension)

For a package to provide a DevTools extension to its users, it must be published with the expected content in the `your_pub_package/extension/devtools/` directory (as described in the preceding setup instructions).

1. Ensure the \`extension/devtools/config.yaml\` file exists and is configured per the specifications above. You can run the \`validate\` command from \`package:devtools\_extensions\` to verify.
   ```
   cd your_extension_web_app;
   flutter pub get;
   dart run devtools_extensions validate --package=path/to/pkg_providing_your_extension_assets
   ```

2. Use the \`build\_and\_copy\` command provided by \`package:devtools\_extensions\` to build your extension and copy the output to the \`extension/devtools\` directory:

   `console cd your_extension_web_app; flutter pub get; dart run devtools_extensions build_and_copy --source=. --dest=path/to/your_pub_package/extension/devtools`

Then, publish your package on [`pub.dev`](https://pub.dev):

```
flutter pub publish
```

When running `pub publish`, you will see a warning if you do not have the `config.yaml` file and a non-empty `build` directory as required.

For additional guidance around publishing your package, visit the `package:devtools_extensions` [publishing guide](https://pub.dev/packages/devtools_extensions#publish-your-package-with-a-devtools-extension).

***

That’s it! Now, when a user depends on the latest version of your package, they will automatically get access to the tools you provide in your DevTools extension.

You might find the following links useful:

* For the latest info on this feature, visit the [DevTools Extensions README](https://github.com/flutter/devtools/blob/master/packages/devtools_extensions/README.md) on GitHub.
* For issues and feature requests, [file an issue](https://github.com/flutter/devtools/issues) on the DevTools issue tracker.
* For general support and access to the community of DevTools extension authors, check out the [#devtools-extension-authors](https://discord.com/channels/608014603317936148/1159561514072690739) Discord channel (you will first need to join the [Flutter Discord server](https://github.com/flutter/flutter/wiki/Chat)).

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-09-29. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/custom-tool.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/tools/devtools/custom-tool\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/custom-tool.md "Report an issue with this page").

----
url: https://docs.flutter.dev/resources/news-toolkit
----

The Flutter News Toolkit is now maintained by Very Good Ventures (VGV).

For the latest information, see the [news\_toolkit repository on GitHub](https://github.com/VGVentures/news_toolkit).

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/resources/news-toolkit.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/resources/news-toolkit\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/resources/news-toolkit.md "Report an issue with this page").

----
url: https://docs.flutter.dev/cookbook/effects/nested-nav.md
----

\# Create a nested navigation flow > How to implement a flow with nested navigation.

<!--?code-excerpt path-base="cookbook/effects/nested_nav"?-->

Apps accumulate dozens and then hundreds of routes over time. Some of your routes make sense as top-level (global) routes. For example, "/", "profile", "contact", "social\_feed" are all possible top-level routes within your app. But, imagine that you defined every possible route in your top-level \`Navigator\` widget. The list would be very long, and many of these routes would be better handled nested within another widget. Consider an Internet of Things (IoT) setup flow for a wireless light bulb that you control with your app. This setup flow consists of four pages: \* \`find\_devices\` page: Find nearby bulbs. \* \`select\_device\` page: Select the bulb that you want to add. \* \`connecting\` page: Add the bulb. \* \`finished\` page: Complete the setup. You could orchestrate this behavior from your top-level \`Navigator\` widget. However, it makes more sense to define a second, nested \`Navigator\` widget within your \`SetupFlow\` widget, and let the nested \`Navigator\` take ownership over the four pages in the setup flow. This delegation of navigation facilitates greater local control, which is generally preferable when developing software. The following animation shows the app's behavior: !\[Gif showing the nested "setup" flow]\(/assets/images/docs/cookbook/effects/NestedNavigator.webp){:.site-mobile-screenshot} In this recipe, you implement a four-page IoT setup flow that maintains its own navigation nested beneath the top-level \`Navigator\` widget. ## Prepare for navigation This IoT app has two top-level screens, along with the setup flow. Define these route names as constants so that they can be referenced within code.

<!--?code-excerpt "lib/main.dart (routes)"?-->

\`\`\`dart const routeHome = '/'; const routeSettings = '/settings'; const routePrefixDeviceSetup = '/setup/'; const routeDeviceSetupStart = '/setup/$routeDeviceSetupStartPage'; const routeDeviceSetupStartPage = 'find\_devices'; const routeDeviceSetupSelectDevicePage = 'select\_device'; const routeDeviceSetupConnectingPage = 'connecting'; const routeDeviceSetupFinishedPage = 'finished'; \`\`\` The home and settings screens are referenced with static names. The setup flow pages, however, use two paths to create their route names: a \`/setup/\` prefix followed by the name of the specific page. By combining the two paths, your \`Navigator\` can determine that a route name is intended for the setup flow without recognizing all the individual pages associated with the setup flow. The top-level \`Navigator\` isn't responsible for identifying individual setup flow pages. Therefore, your top-level \`Navigator\` needs to parse the incoming route name to identify the setup flow prefix. Needing to parse the route name means that you can't use the \`routes\` property of your top-level \`Navigator\`. Instead, you must provide a function for the \`onGenerateRoute\` property. Implement \`onGenerateRoute\` to return the appropriate widget for each of the three top-level paths.

<!--?code-excerpt "lib/main.dart (OnGenerateRoute)"?-->

\`\`\`dart onGenerateRoute: (settings) { final Widget page; if (settings.name == routeHome) { page = const HomeScreen(); } else if (settings.name == routeSettings) { page = const SettingsScreen(); } else if (settings.name!.startsWith(routePrefixDeviceSetup)) { final subRoute = settings.name!.substring( routePrefixDeviceSetup.length, ); page = SetupFlow(setupPageRoute: subRoute); } else { throw Exception('Unknown route: ${settings.name}'); } return MaterialPageRoute( builder: (context) { return page; }, settings: settings, ); }, \`\`\` Notice that the home and settings routes are matched with exact route names. However, the setup flow route condition only checks for a prefix. If the route name contains the setup flow prefix, then the rest of the route name is ignored and passed on to the \`SetupFlow\` widget to process. This splitting of the route name is what allows the top-level \`Navigator\` to be agnostic toward the various subroutes within the setup flow. Create a stateful widget called \`SetupFlow\` that accepts a route name.

<!--?code-excerpt "lib/setupflow.dart (SetupFlow)" replace="/@override\n*.*\n\s*return const SizedBox\(\);\n\s*}/\/\/.../g"?-->

\`\`\`dart class SetupFlow extends StatefulWidget { const SetupFlow({super.key, required this.setupPageRoute}); final String setupPageRoute; @override State createState() => SetupFlowState(); } class SetupFlowState extends State { //... } \`\`\` ## Display an app bar for the setup flow The setup flow displays a persistent app bar that appears across all pages. Return a \`Scaffold\` widget from your \`SetupFlow\` widget's \`build()\` method, and include the desired \`AppBar\` widget.

<!--?code-excerpt "lib/setupflow2.dart (SetupFlow2)"?-->

\`\`\`dart @override Widget build(BuildContext context) { return Scaffold(appBar: \_buildFlowAppBar(), body: const SizedBox()); } PreferredSizeWidget \_buildFlowAppBar() { return AppBar(title: const Text('Bulb Setup')); } \`\`\` The app bar displays a back arrow and exits the setup flow when the back arrow is pressed. However, exiting the flow causes the user to lose all progress. Therefore, the user is prompted to confirm whether they want to exit the setup flow. Prompt the user to confirm exiting the setup flow, and ensure that the prompt appears when the user presses the hardware back button on their device.

<!--?code-excerpt "lib/prompt_user.dart (PromptUser)"?-->

\`\`\`dart Future \_onExitPressed() async { final isConfirmed = await \_isExitDesired(); if (isConfirmed && mounted) { \_exitSetup(); } } Future \_isExitDesired() async { return await showDialog( context: context, builder: (context) { return AlertDialog( title: const Text('Are you sure?'), content: const Text( 'If you exit device setup, your progress will be lost.', ), actions: \[ TextButton( onPressed: () { Navigator.of(context).pop(true); }, child: const Text('Leave'), ), TextButton( onPressed: () { Navigator.of(context).pop(false); }, child: const Text('Stay'), ), ], ); }, ) ?? false; } void \_exitSetup() { Navigator.of(context).pop(); } @override Widget build(BuildContext context) { return PopScope( canPop: false, onPopInvokedWithResult: (didPop, \_) async { if (didPop) return; if (await \_isExitDesired() && context.mounted) { \_exitSetup(); } }, child: Scaffold(appBar: \_buildFlowAppBar(), body: const SizedBox()), ); } PreferredSizeWidget \_buildFlowAppBar() { return AppBar( leading: IconButton( onPressed: \_onExitPressed, icon: const Icon(Icons.chevron\_left), ), title: const Text('Bulb Setup'), ); } \`\`\` When the user taps the back arrow in the app bar, or presses the back button on their device, an alert dialog pops up to confirm that the user wants to leave the setup flow. If the user presses \*\*Leave\*\*, then the setup flow pops itself from the top-level navigation stack. If the user presses \*\*Stay\*\*, then the action is ignored. You might notice that the \`Navigator.pop()\` is invoked by both the \*\*Leave\*\* and \*\*Stay\*\* buttons. To be clear, this \`pop()\` action pops the alert dialog off the navigation stack, not the setup flow. ## Generate nested routes The setup flow's job is to display the appropriate page within the flow. Add a \`Navigator\` widget to \`SetupFlow\`, and implement the \`onGenerateRoute\` property.

<!--?code-excerpt "lib/add_navigator.dart (AddNavigator)"?-->

\`\`\`dart final \_navigatorKey = GlobalKey(); void \_onDiscoveryComplete() { \_navigatorKey.currentState!.pushNamed(routeDeviceSetupSelectDevicePage); } void \_onDeviceSelected(String deviceId) { \_navigatorKey.currentState!.pushNamed(routeDeviceSetupConnectingPage); } void \_onConnectionEstablished() { \_navigatorKey.currentState!.pushNamed(routeDeviceSetupFinishedPage); } @override Widget build(BuildContext context) { return PopScope( canPop: false, onPopInvokedWithResult: (didPop, \_) async { if (didPop) return; if (await \_isExitDesired() && context.mounted) { \_exitSetup(); } }, child: Scaffold( appBar: \_buildFlowAppBar(), body: Navigator( key: \_navigatorKey, initialRoute: widget.setupPageRoute, onGenerateRoute: \_onGenerateRoute, ), ), ); } Route \_onGenerateRoute(RouteSettings settings) { final page = switch (settings.name) { routeDeviceSetupStartPage => WaitingPage( message: 'Searching for nearby bulb...', onWaitComplete: \_onDiscoveryComplete, ), routeDeviceSetupSelectDevicePage => SelectDevicePage( onDeviceSelected: \_onDeviceSelected, ), routeDeviceSetupConnectingPage => WaitingPage( message: 'Connecting...', onWaitComplete: \_onConnectionEstablished, ), routeDeviceSetupFinishedPage => FinishedPage(onFinishPressed: \_exitSetup), \_ => throw StateError('Unexpected route name: ${settings.name}!'), }; return MaterialPageRoute( builder: (context) { return page; }, settings: settings, ); } \`\`\` The \`\_onGenerateRoute\` function works the same as for a top-level \`Navigator\`. A \`RouteSettings\` object is passed into the function, which includes the route's \`name\`. Based on that route name, one of four flow pages is returned. The first page, called \`find\_devices\`, waits a few seconds to simulate network scanning. After the wait period, the page invokes its callback. In this case, that callback is \`\_onDiscoveryComplete\`. The setup flow recognizes that, when device discovery is complete, the device selection page should be shown. Therefore, in \`\_onDiscoveryComplete\`, the \`\_navigatorKey\` instructs the nested \`Navigator\` to navigate to the \`select\_device\` page. The \`select\_device\` page asks the user to select a device from a list of available devices. In this recipe, only one device is presented to the user. When the user taps a device, the \`onDeviceSelected\` callback is invoked. The setup flow recognizes that, when a device is selected, the connecting page should be shown. Therefore, in \`\_onDeviceSelected\`, the \`\_navigatorKey\` instructs the nested \`Navigator\` to navigate to the \`"connecting"\` page. The \`connecting\` page works the same way as the \`find\_devices\` page. The \`connecting\` page waits for a few seconds and then invokes its callback. In this case, the callback is \`\_onConnectionEstablished\`. The setup flow recognizes that, when a connection is established, the final page should be shown. Therefore, in \`\_onConnectionEstablished\`, the \`\_navigatorKey\` instructs the nested \`Navigator\` to navigate to the \`finished\` page. The \`finished\` page provides the user with a \*\*Finish\*\* button. When the user taps \*\*Finish\*\*, the \`\_exitSetup\` callback is invoked, which pops the entire setup flow off the top-level \`Navigator\` stack, taking the user back to the home screen. Congratulations! You implemented nested navigation with four subroutes. ## Interactive example Run the app: \* On the \*\*Add your first bulb\*\* screen, click the FAB, shown with a plus sign, \*\*+\*\*. This brings you to the \*\*Select a nearby device\*\* screen. A single bulb is listed. \* Click the listed bulb. A \*\*Finished!\*\* screen appears. \* Click the \*\*Finished\*\* button to return to the first screen.

<!--?code-excerpt "lib/main.dart"?-->

\`\`\`dartpad title="Flutter nested navigation hands-on example in DartPad" run="true" height="640px" import 'package:flutter/material.dart'; const routeHome = '/'; const routeSettings = '/settings'; const routePrefixDeviceSetup = '/setup/'; const routeDeviceSetupStart = '/setup/$routeDeviceSetupStartPage'; const routeDeviceSetupStartPage = 'find\_devices'; const routeDeviceSetupSelectDevicePage = 'select\_device'; const routeDeviceSetupConnectingPage = 'connecting'; const routeDeviceSetupFinishedPage = 'finished'; void main() { runApp( MaterialApp( theme: ThemeData( brightness: Brightness.dark, appBarTheme: const AppBarTheme(backgroundColor: Colors.blue), floatingActionButtonTheme: const FloatingActionButtonThemeData( backgroundColor: Colors.blue, ), ), onGenerateRoute: (settings) { final Widget page; if (settings.name == routeHome) { page = const HomeScreen(); } else if (settings.name == routeSettings) { page = const SettingsScreen(); } else if (settings.name!.startsWith(routePrefixDeviceSetup)) { final subRoute = settings.name!.substring( routePrefixDeviceSetup.length, ); page = SetupFlow(setupPageRoute: subRoute); } else { throw Exception('Unknown route: ${settings.name}'); } return MaterialPageRoute( builder: (context) { return page; }, settings: settings, ); }, debugShowCheckedModeBanner: false, ), ); } @immutable class SetupFlow extends StatefulWidget { static SetupFlowState of(BuildContext context) { return context.findAncestorStateOfType()!; } const SetupFlow({super.key, required this.setupPageRoute}); final String setupPageRoute; @override SetupFlowState createState() => SetupFlowState(); } class SetupFlowState extends State { final \_navigatorKey = GlobalKey(); @override void initState() { super.initState(); } void \_onDiscoveryComplete() { \_navigatorKey.currentState!.pushNamed(routeDeviceSetupSelectDevicePage); } void \_onDeviceSelected(String deviceId) { \_navigatorKey.currentState!.pushNamed(routeDeviceSetupConnectingPage); } void \_onConnectionEstablished() { \_navigatorKey.currentState!.pushNamed(routeDeviceSetupFinishedPage); } Future \_onExitPressed() async { final isConfirmed = await \_isExitDesired(); if (isConfirmed && mounted) { \_exitSetup(); } } Future \_isExitDesired() async { return await showDialog( context: context, builder: (context) { return AlertDialog( title: const Text('Are you sure?'), content: const Text( 'If you exit device setup, your progress will be lost.', ), actions: \[ TextButton( onPressed: () { Navigator.of(context).pop(true); }, child: const Text('Leave'), ), TextButton( onPressed: () { Navigator.of(context).pop(false); }, child: const Text('Stay'), ), ], ); }, ) ?? false; } void \_exitSetup() { Navigator.of(context).pop(); } @override Widget build(BuildContext context) { return PopScope( canPop: false, onPopInvokedWithResult: (didPop, \_) async { if (didPop) return; if (await \_isExitDesired() && context.mounted) { \_exitSetup(); } }, child: Scaffold( appBar: \_buildFlowAppBar(), body: Navigator( key: \_navigatorKey, initialRoute: widget.setupPageRoute, onGenerateRoute: \_onGenerateRoute, ), ), ); } Route \_onGenerateRoute(RouteSettings settings) { final page = switch (settings.name) { routeDeviceSetupStartPage => WaitingPage( message: 'Searching for nearby bulb...', onWaitComplete: \_onDiscoveryComplete, ), routeDeviceSetupSelectDevicePage => SelectDevicePage( onDeviceSelected: \_onDeviceSelected, ), routeDeviceSetupConnectingPage => WaitingPage( message: 'Connecting...', onWaitComplete: \_onConnectionEstablished, ), routeDeviceSetupFinishedPage => FinishedPage(onFinishPressed: \_exitSetup), \_ => throw StateError('Unexpected route name: ${settings.name}!'), }; return MaterialPageRoute( builder: (context) { return page; }, settings: settings, ); } PreferredSizeWidget \_buildFlowAppBar() { return AppBar( leading: IconButton( onPressed: \_onExitPressed, icon: const Icon(Icons.chevron\_left), ), title: const Text('Bulb Setup'), ); } } class SelectDevicePage extends StatelessWidget { const SelectDevicePage({super.key, required this.onDeviceSelected}); final void Function(String deviceId) onDeviceSelected; @override Widget build(BuildContext context) { return Scaffold( body: Center( child: Padding( padding: const EdgeInsets.symmetric(horizontal: 24), child: Column( mainAxisSize: MainAxisSize.min, children: \[ Text( 'Select a nearby device:', style: Theme.of(context).textTheme.titleLarge, ), const SizedBox(height: 24), SizedBox( width: double.infinity, height: 54, child: ElevatedButton( style: ButtonStyle( backgroundColor: WidgetStateColor.resolveWith((states) { return const Color(0xFF222222); }), ), onPressed: () { onDeviceSelected('22n483nk5834'); }, child: const Text( 'Bulb 22n483nk5834', style: TextStyle(fontSize: 24), ), ), ), ], ), ), ), ); } } class WaitingPage extends StatefulWidget { const WaitingPage({ super.key, required this.message, required this.onWaitComplete, }); final String message; final VoidCallback onWaitComplete; @override State createState() => \_WaitingPageState(); } class \_WaitingPageState extends State { @override void initState() { super.initState(); \_startWaiting(); } Future \_startWaiting() async { await Future.delayed(const Duration(seconds: 3)); if (mounted) { widget.onWaitComplete(); } } @override Widget build(BuildContext context) { return Scaffold( body: Center( child: Padding( padding: const EdgeInsets.symmetric(horizontal: 24), child: Column( mainAxisSize: MainAxisSize.min, children: \[ const CircularProgressIndicator(), const SizedBox(height: 32), Text(widget.message), ], ), ), ), ); } } class FinishedPage extends StatelessWidget { const FinishedPage({super.key, required this.onFinishPressed}); final VoidCallback onFinishPressed; @override Widget build(BuildContext context) { return Scaffold( body: Center( child: Padding( padding: const EdgeInsets.symmetric(horizontal: 24), child: SingleChildScrollView( child: Column( mainAxisSize: MainAxisSize.min, children: \[ Container( width: 200, height: 200, decoration: const BoxDecoration( shape: BoxShape.circle, color: Color(0xFF222222), ), child: const Center( child: Icon( Icons.lightbulb, size: 140, color: Colors.white, ), ), ), const SizedBox(height: 32), const Text( 'Bulb added!', textAlign: TextAlign.center, style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold), ), const SizedBox(height: 32), ElevatedButton( style: ButtonStyle( padding: WidgetStateProperty.resolveWith((states) { return const EdgeInsets.symmetric( horizontal: 24, vertical: 12, ); }), backgroundColor: WidgetStateColor.resolveWith((states) { return const Color(0xFF222222); }), shape: WidgetStateProperty.resolveWith((states) { return const StadiumBorder(); }), ), onPressed: onFinishPressed, child: const Text('Finish', style: TextStyle(fontSize: 24)), ), ], ), ), ), ), ); } } @immutable class HomeScreen extends StatelessWidget { const HomeScreen({super.key}); @override Widget build(BuildContext context) { return Scaffold( appBar: \_buildAppBar(context), body: Center( child: Padding( padding: const EdgeInsets.symmetric(horizontal: 24), child: Column( mainAxisSize: MainAxisSize.min, children: \[ Container( width: 200, height: 200, decoration: const BoxDecoration( shape: BoxShape.circle, color: Color(0xFF222222), ), child: Center( child: Icon( Icons.lightbulb, size: 140, color: Theme.of(context).scaffoldBackgroundColor, ), ), ), const SizedBox(height: 32), const Text( 'Add your first bulb', textAlign: TextAlign.center, style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold), ), ], ), ), ), floatingActionButton: FloatingActionButton( onPressed: () { Navigator.of(context).pushNamed(routeDeviceSetupStart); }, child: const Icon(Icons.add), ), ); } PreferredSizeWidget \_buildAppBar(BuildContext context) { return AppBar( title: const Text('Welcome'), actions: \[ IconButton( icon: const Icon(Icons.settings), onPressed: () { Navigator.pushNamed(context, routeSettings); }, ), ], ); } } class SettingsScreen extends StatelessWidget { const SettingsScreen({super.key}); @override Widget build(BuildContext context) { return Scaffold( appBar: \_buildAppBar(), body: SingleChildScrollView( child: Column( mainAxisSize: MainAxisSize.min, children: List.generate(8, (index) { return Container( width: double.infinity, height: 54, margin: const EdgeInsets.only(left: 16, right: 16, top: 16), decoration: BoxDecoration( borderRadius: BorderRadius.circular(8), color: const Color(0xFF222222), ), ); }), ), ), ); } PreferredSizeWidget \_buildAppBar() { return AppBar(title: const Text('Settings')); } } \`\`\`

----
url: https://docs.flutter.dev/search
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

Want results from additional Flutter-related sites, like api.flutter.dev and dart.dev? [Search more sites.](/search-all)

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/search.html) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/search\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/search.html "Report an issue with this page").

----
url: https://docs.flutter.dev/platform-integration/android/compose-activity
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Launching a Jetpack Compose activity from your Flutter application](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

Native Android activities allow you to launch fullscreen UIs that are entirely run by and on the Android platform. You will only write Kotlin code in those views (though they might pass messages to and receive messages from your Dart code) and you will have access to the full breadth of native Android functionality.

Adding this functionality requires making several changes to your Flutter app and its internal, generated Android app. On the Flutter side, you will need to create a new platform method channel and call its `invokeMethod` method. On the Android side, you will need to register a matching native `MethodChannel` to receive the signal from Dart and then launch a new activity. Recall that all Flutter apps (when running on Android) exist within an Android activity that is completely consumed by the Flutter app. Thus, as you will see in the code sample, the job of the native `MethodChannel` callback is to launch a second activity.

Not all Android activities use Jetpack Compose, but this tutorial assumes you want to use Compose.

## On the Dart side

[#](#on-the-dart-side)

On the Dart side, create a method channel and invoke it from a specific user interaction, like tapping a button.

dart

```
import 'package:flutter/services.dart';
import 'package:material_ui/material_ui.dart';
​
// SECTION 1: START COPYING HERE
const platformMethodChannel = MethodChannel(
  // Note: You can change this string value, but it must match
  // the `CHANNEL` attribute in the next step.
  'com.example.flutter_android_activity',
);
// SECTION 1: END COPYING HERE
​
void main() {
  runApp(const MainApp());
}
​
class MainApp extends StatelessWidget {
  const MainApp({super.key});
​
  // SECTION 2: START COPYING HERE
  void _launchAndroidActivity() {
    platformMethodChannel.invokeMethod(
      // Note: You can change this value, but it must match
      // the `call.method` value in the next section.
      'launchActivity',
​
      // Note: You can pass any primitive data types you like.
      // To pass complex types, use package:pigeon to generate
      // matching Dart and Kotlin classes that share serialization logic.
      {'message': 'Hello from Flutter'},
    );
  }
  // SECTION 2: END COPYING HERE
​
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: const Center(child: Text('Hello World!')),
        floatingActionButton: FloatingActionButton(
          // SECTION 3: Call `_launchAndroidActivity` somewhere.
          onPressed: _launchAndroidActivity,
​
          // SECTION 3: End
          tooltip: 'Launch Android activity',
          child: const Icon(Icons.launch),
        ),
      ),
    );
  }
}
```

There are 3 important values that must match across your Dart and Kotlin code:

1. The channel name (in this sample, the value is `"com.example.flutter_android_activity"`).
2. The method name (in this sample, the value is `"launchActivity"`).
3. The structure of the data which Dart passes and the structure of the data which Kotlin expects to receive. In this case, the data is a map with a single `"message"` key.

## On the Android side

[#](#on-the-android-side)

You must make changes to 4 files in the generated Android app to ready it for launching fresh Compose activities.

The first file requiring modifications is `android/app/build.gradle`.

1. Add the following to the existing `android` block:

   * [Kotlin](#199-tab-panel)
   * [Groovy](#200-tab-panel)

   android/app/build.gradle.kts

   kotlin

   ```
   android {
     // Begin adding here
     buildFeatures {
       compose = true
     }
     composeOptions {
       // https://developer.android.com/jetpack/androidx/releases/compose-kotlin
       kotlinCompilerExtensionVersion = "1.4.8"
     }
     // End adding here
   }
   ```

   android/app/build.gradle

   groovy

   ```
   android {
     // Begin adding here
     buildFeatures {
       compose true
     }
     composeOptions {
       // https://developer.android.com/jetpack/androidx/releases/compose-kotlin
       kotlinCompilerExtensionVersion = "1.4.8"
     }
     // End adding here
   }
   ```

   Visit the [developer.android.com](https://developer.android.com/jetpack/androidx/releases/compose-kotlin) link in the code snippet and adjust `kotlinCompilerExtensionVersion`, as necessary. You should only need to do this if you receive errors during `flutter run` and those errors tell you which versions are installed on your machine.

2. Next, add the following block at the bottom of the file, at the root level:

   * [Kotlin](#201-tab-panel)
   * [Groovy](#202-tab-panel)

   android/app/build.gradle.kts

   kotlin

   ```
   dependencies {
       implementation("androidx.core:core-ktx:1.10.1")
       implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.6.1")
       implementation("androidx.activity:activity-compose")
       implementation(platform("androidx.compose:compose-bom:2024.06.00"))
       implementation("androidx.compose.ui:ui")
       implementation("androidx.compose.ui:ui-graphics")
       implementation("androidx.compose.ui:ui-tooling-preview")
       implementation("androidx.compose.material:material")
       implementation("androidx.compose.material3:material3")
       testImplementation("junit:junit:4.13.2")
       androidTestImplementation("androidx.test.ext:junit:1.1.5")
       androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
       androidTestImplementation(platform("androidx.compose:compose-bom:2024.06.00"))
       androidTestImplementation("androidx.compose.ui:ui-test-junit4")
       androidTestImplementation("androidx.compose.ui:ui-test-junit4")
       debugImplementation("androidx.compose.ui:ui-tooling")
       debugImplementation("androidx.compose.ui:ui-test-manifest")
   }
   ```

   android/app/build.gradle

   groovy

   ```
   dependencies {
       implementation "androidx.core:core-ktx:1.10.1"
       implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.6.1"
       implementation "androidx.activity:activity-compose"
       implementation platform("androidx.compose:compose-bom:2024.06.00")
       implementation "androidx.compose.ui:ui"
       implementation "androidx.compose.ui:ui-graphics"
       implementation "androidx.compose.ui:ui-tooling-preview"
       implementation "androidx.compose.material:material"
       implementation "androidx.compose.material3:material3"
       testImplementation "junit:junit:4.13.2"
       androidTestImplementation "androidx.test.ext:junit:1.1.5"
       androidTestImplementation "androidx.test.espresso:espresso-core:3.5.1"
       androidTestImplementation platform("androidx.compose:compose-bom:2023.08.00")
       androidTestImplementation "androidx.compose.ui:ui-test-junit4"
       debugImplementation "androidx.compose.ui:ui-tooling"
       debugImplementation "androidx.compose.ui:ui-test-manifest"
   }
   ```

   The second file requiring modifications is `android/build.gradle`.

3. Add the following buildscript block at the top of the file:

   * [Kotlin](#203-tab-panel)
   * [Groovy](#204-tab-panel)

   android/build.gradle.kts

   kotlin

   ```
   buildscript {
       dependencies {
           // Replace with the latest version.
           classpath("com.android.tools.build:gradle:8.1.1")
       }
       repositories {
           google()
           mavenCentral()
       }
   }
   ```

   android/build.gradle

   groovy

   ```
   buildscript {
       dependencies {
           // Replace with the latest version.
           classpath 'com.android.tools.build:gradle:8.1.1'
       }
       repositories {
           google()
           mavenCentral()
       }
   }
   ```

   The third file requiring modifications is `android/app/src/main/AndroidManifest.xml`.

4. In the root application block, add the following `<activity>` declaration:

   android/app/src/main/AndroidManifest.xml

   xml

   ```
   <manifest xmlns:android="http://schemas.android.com/apk/res/android">
       <application
           android:label="flutter_android_activity"
           android:name="${applicationName}"
           android:icon="@mipmap/ic_launcher">
   ​
          // START COPYING HERE
           <activity android:name=".SecondActivity" android:exported="true" android:theme="@style/LaunchTheme"></activity>
          // END COPYING HERE
   ​
          <activity android:name=".MainActivity" …></activity>
         …
   </manifest>
   ```

   The fourth and final code requiring modifications is `android/app/src/main/kotlin/com/example/flutter_android_activity/MainActivity.kt`. Here you'll write Kotlin code for your desired Android functionality.

5. Add the necessary imports at the top of the file:

   MainActivity.kt

   kotlin

   ```
   package com.example.flutter_android_activity
   ​
   import android.content.Intent
   import android.os.Bundle
   import androidx.activity.ComponentActivity
   import androidx.activity.compose.setContent
   import androidx.compose.foundation.layout.Column
   import androidx.compose.foundation.layout.fillMaxSize
   import androidx.compose.material3.Button
   import androidx.compose.material3.MaterialTheme
   import androidx.compose.material3.Surface
   import androidx.compose.material3.Text
   import androidx.compose.ui.Modifier
   import androidx.core.app.ActivityCompat
   import io.flutter.embedding.android.FlutterActivity
   import io.flutter.embedding.engine.FlutterEngine
   import io.flutter.plugin.common.MethodCall
   import io.flutter.plugin.common.MethodChannel
   import io.flutter.plugins.GeneratedPluginRegistrant
   ```

6. Modify the generated `MainActivity` class by adding a `CHANNEL` field and a `configureFlutterEngine` method:

   MainActivity.kt

   kotlin

   ```
   class MainActivity: FlutterActivity() {
       // This value must match the `MethodChannel` name in your Dart code.
       private val CHANNEL = "com.example.flutter_android_activity"
   ​
       override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
           GeneratedPluginRegistrant.registerWith(flutterEngine)
   ​
           MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler {
               call: MethodCall, result: MethodChannel.Result ->
                   when (call.method) {
                       // Note: This must match the first parameter passed to
                       // `platformMethodChannel.invokeMethod` in your Dart code.
                       "launchActivity" -> {
                           try {
                               // Takes an object, in this case a String.
                               val message = call.arguments
                               val intent = Intent(this@MainActivity, SecondActivity::class.java)
                               intent.putExtra("message", message.toString())
                               startActivity(intent)
                           } catch (e: Exception){}
                               result.success(true)
                           }
                           else -> {}
                   }
           }
       }
   }
   ```

7. Add a second `Activity` to the bottom of the file, which you referenced in the previous changes to `AndroidManifest.xml`:

   MainActivity.kt

   kotlin

   ```
   class SecondActivity : ComponentActivity() {
       override fun onCreate(savedInstanceState: Bundle?) {
           super.onCreate(savedInstanceState)
   ​
           setContent {
               Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background) {
                   Column {
                       Text(text = "Second Activity")
                       // Note: This must match the shape of the data passed from your Dart code.
                       Text("" + getIntent()?.getExtras()?.getString("message"))
                       Button(onClick = {  finish() }) {
                           Text("Exit")
                       }
                   }
               }
           }
       }
   }
   ```

These steps show how to launch a native Android activity from a Flutter app, which can sometimes be an easy way to connect to specific Android functionality.

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-09-29. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/platform-integration/android/compose-activity.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/platform-integration/android/compose-activity\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/platform-integration/android/compose-activity.md "Report an issue with this page").

----
url: https://docs.flutter.dev/ai/genui/get-started
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Get started with the GenUI SDK for Flutter](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

This guide explains how to get started with GenUI SDK for Flutter and its series of packages. The SDK's key components are described in the [main components](/ai/genui/components) page.

Use the following instructions to add [`genui`](https://pub.dev/packages/genui) to your Flutter app. The code examples show how to perform the instructions on a brand new app created by running [`flutter create`](/reference/create-new-app), but you can follow the same steps for your existing Flutter app.

## Configure your agent provider

[#](#configure-your-agent-provider)

The `genui` package can connect to a variety of agent providers. Available providers include the following:

* **Firebase AI Logic**

  Useful for production apps where interactions with the LLM are all in your Flutter client, without requiring a server. Firebase also makes it easier to ship your AI features securely since Firebase handles the management of your Gemini API key.

* **GenUI A2UI**

  Useful for client/server architectures where your agent is running on the server.

* **Build your own**

  You can also build your own adapter to connect to your preferred LLM provider. Expect more from us and the community soon.

- [Firebase AI Logic](#229-tab-panel)
- [GenUI A2UI](#230-tab-panel)
- [Build your own](#231-tab-panel)

To connect to Gemini using the Firebase AI Logic SDK, follow these instructions:

1. [Create a new Firebase project](https://support.google.com/appsheet/answer/10104995) using the Firebase Console.

2. [Enable the Gemini API](https://firebase.google.com/docs/gemini-in-firebase/set-up-gemini) for that project.

3. Follow the first three steps in [Firebase's Flutter setup guide](https://firebase.google.com/docs/flutter/setup) to add Firebase to your app.

4. Use `dart pub add` to add `genui` and `firebase_ai` as dependencies in your `pubspec.yaml` file:

   ```
   $ dart pub add genui firebase_ai
   ```

5. In your app's `main` method, ensure that the widget bindings are initialized and then initialize Firebase:

   dart

   ```
   import 'package:material_ui/material_ui.dart';
   import 'package:firebase_core/firebase_core.dart';
   import 'firebase_options.dart';
   ​
   void main() async {
     WidgetsFlutterBinding.ensureInitialized();
     await Firebase.initializeApp(
       options: DefaultFirebaseOptions.currentPlatform,
     );
     runApp(const MyApp());
   }
   ```

6. Create an instance of the Firebase AI Logic generative model and wrap it with your `SurfaceController` and `A2uiTransportAdapter`:

   dart

   ```
   import 'package:genui/genui.dart';
   import 'package:firebase_ai/firebase_ai.dart';
   ​
   final catalog = Catalog([
     // ...
   ]);
   final catalogs = [catalog];
   ​
   final surfaceController = SurfaceController(catalogs: catalogs);
   ​
   final promptBuilder = PromptBuilder.chat(
     catalog: catalog,
     systemPromptFragments: ['You are a helpful assistant.'],
   );
   ​
   final model = FirebaseAI.vertexAI().generativeModel(
     model: 'gemini-3.5-flash',
     systemInstruction: Content.system(promptBuilder.systemPromptJoined()),
   );
   ​
   // The Conversation wires transport -> controller internally.
   late final A2uiTransportAdapter transportAdapter;
   transportAdapter = A2uiTransportAdapter(onSend: (message) async {
     // final stream = model.generateContentStream(...);
     // await for (final chunk in stream) {
     //   transportAdapter.addChunk(chunk.text ?? '');
     // }
   });
   ​
   final conversation = Conversation(
     controller: surfaceController,
     transport: transportAdapter,
   );
   ```

An integration package for [`genui`](https://pub.dev/packages/genui) and the [A2UI Streaming UI Protocol](https://a2ui.org/). This package allows Flutter applications to connect to an Agent-to-Agent (A2UI) server and render dynamic user interfaces generated by an AI agent using the `genui` framework.

The main components in this package include:

* `A2uiAgentConnector`: Handles the low-level web socket communication with the A2A server, including sending messages and parsing stream events.
* `AgentCard`: A data class that holds metadata about the connected AI agent.

Follow these instructions:

1. Set up dependencies: Use `dart pub add` to add `genui` and `genui_a2a` as dependencies in your `pubspec.yaml` file.

   ```
   $ dart pub add genui genui_a2a
   ```

2. Initialize `SurfaceController`: Set up `SurfaceController` with your widget `Catalog`s.

3. Create `A2uiTransportAdapter`: Instantiate `A2uiTransportAdapter` to parse the messages.

4. Create `A2uiAgentConnector`: Instantiate `A2uiAgentConnector`, providing the A2A server URI.

5. Create `Conversation`: Pass the adapter and controller to the `Conversation`.

6. Render with `Surface`: Use `Surface` widgets in your UI to display the agent-generated content.

7. Send Messages: Use `connector.connectAndSend` or `Conversation.sendMessage` to send user input to the agent-generated content.

   dart

   ```
   import 'package:material_ui/material_ui.dart';
   import 'package:genui/genui.dart';
   import 'package:genui_a2a/genui_a2a.dart';
   import 'package:logging/logging.dart';
   ​
   void main() {
     // Setup logging.
     Logger.root.level = Level.ALL;
     Logger.root.onRecord.listen((record) {
       print('${record.level.name}: ${record.time}: ${record.message}');
       if (record.error != null) {
         print(record.error);
       }
       if (record.stackTrace != null) {
         print(record.stackTrace);
       }
     });
   ​
     runApp(const GenUIExampleApp());
   }
   ​
   class GenUIExampleApp extends StatelessWidget {
     const GenUIExampleApp({super.key});
   ​
     @override
     Widget build(BuildContext context) {
       return MaterialApp(
         title: 'A2UI Example',
         theme: ThemeData(
           primarySwatch: Colors.blue,
         ),
         home: const ChatScreen(),
       );
     }
   }
   ​
   class ChatScreen extends StatefulWidget {
     const ChatScreen({super.key});
   ​
     @override
     State<ChatScreen> createState() => _ChatScreenState();
   }
   ​
   class _ChatScreenState extends State<ChatScreen> {
     final TextEditingController _textController = TextEditingController();
     final SurfaceController _surfaceController =
         SurfaceController(catalogs: [BasicCatalogItems.asCatalog()]);
     late final A2uiTransportAdapter _transportAdapter;
     late final Conversation _uiAgent;
     late final A2uiAgentConnector _connector;
     final List<ChatMessage> _messages = [];
   ​
     @override
     void initState() {
       super.initState();
   ​
       // The Conversation wires transport -> controller internally.
       _transportAdapter = A2uiTransportAdapter(onSend: (message) async {
         // Implement sending to LLM if needed, or handled by connector
       });
   ​
       _connector = A2uiAgentConnector(
         // TODO: Replace with your A2A server URL.
         url: Uri.parse('http://localhost:8080'),
       );
       _uiAgent = Conversation(
         controller: _surfaceController,
         transport: _transportAdapter,
       );
   ​
       // Listen for messages from the remote agent.
       _connector.stream.listen(_surfaceController.handleMessage);
   ​
     }
   ​
     @override
     void dispose() {
       _textController.dispose();
       _uiAgent.dispose();
       _transportAdapter.dispose();
       _surfaceController.dispose();
       _connector.dispose();
       super.dispose();
     }
   ​
     void _handleSubmitted(String text) async {
       if (text.isEmpty) return;
       _textController.clear();
       final message = ChatMessage.user(text);
       setState(() {
         _messages.insert(0, message);
       });
   ​
       final responseText = await _connector.connectAndSend(message);
   ​
       // Handling response depends on your app's logic
     }
   ​
     @override
     Widget build(BuildContext context) {
       return Scaffold(
         appBar: AppBar(
           title: const Text('A2UI Example'),
         ),
         body: Column(
           children: <Widget>[
             Expanded(
               child: ListView.builder(
                 padding: const EdgeInsets.all(8.0),
                 reverse: true,
                 itemBuilder: (_, int index) =>
                     _buildMessage(_messages[index]),
                 itemCount: _messages.length,
               ),
             ),
             const Divider(height: 1.0),
             Container(
               decoration: BoxDecoration(color: Theme.of(context).cardColor),
               child: _buildTextComposer(),
             ),
             // Surface for the main AI-generated UI:
             SizedBox(
               height: 300,
               child: Surface(
                 surfaceContext: _surfaceController.contextFor('main_surface'),
               ),
             ),
           ],
         ),
       );
     }
   ​
     Widget _buildMessage(ChatMessage message) {
       return Container(
         margin: const EdgeInsets.symmetric(vertical: 10.0),
         child: Row(
           crossAxisAlignment: CrossAxisAlignment.start,
           children: <Widget>[
             Container(
               margin: const EdgeInsets.only(right: 16.0),
               child: CircleAvatar(child: Text(message.role == ChatMessageRole.user ? 'U' : 'A')),
             ),
             Expanded(
               child: Column(
                 crossAxisAlignment: CrossAxisAlignment.start,
                 children: <Widget>[
                   Text(message.role == ChatMessageRole.user ? 'User' : 'Agent',
                       style: const TextStyle(fontWeight: FontWeight.bold)),
                   Container(
                     margin: const EdgeInsets.only(top: 5.0),
                     child: Text(message.parts.whereType<TextPart>().map((e) => e.text).join('\n')),
                   ),
                 ],
               ),
             ),
           ],
         ),
       );
     }
   ​
     Widget _buildTextComposer() {
       return IconTheme(
         data: IconThemeData(color: Theme.of(context).colorScheme.secondary),
         child: Container(
           margin: const EdgeInsets.symmetric(horizontal: 8.0),
           child: Row(
             children: <Widget>[
               Flexible(
                 child: TextField(
                   controller: _textController,
                   onSubmitted: _handleSubmitted,
                   decoration:
                       const InputDecoration.collapsed(hintText: 'Send a message'),
                 ),
               ),
               Container(
                 margin: const EdgeInsets.symmetric(horizontal: 4.0),
                 child: IconButton(
                   icon: const Icon(Icons.send),
                   onPressed: () => _handleSubmitted(_textController.text),
                 ),
               ),
             ],
           ),
         ),
       );
     }
   }
   ```

The [example](https://pub.dev/packages/genui_a2a/example) directory on pub.dev contains a complete application demonstrating how to use this package.

To use `genui` with another agent provider, follow that provider's SDK documentation to implement a connection, and stream its results into an `A2uiTransportAdapter`.

## Create the connection to an agent

[#](#create-the-connection-to-an-agent)

If you build your Flutter project for iOS or macOS, add this key to your `{ios,macos}/Runner/*.entitlements` file(s) to enable outbound network requests:

xml

```
<dict>
...
<key>com.apple.security.network.client</key>
<true/>
</dict>
```

Next, use the following instructions to connect your app to your chosen agent provider.

1. Create a `SurfaceController`, and provide it with the catalogs of widgets that you want to make available to the agent. Create an `A2uiTransportAdapter` to parse messages and connect it.

2. Create a `PromptBuilder`, and provide it with a system instruction and the tools (functions you want the agent to be able to invoke). You should always include the tools provided by `SurfaceController`, but feel free to include others. Add this to your LLM system prompt.

3. Create a `Conversation` using the instances of `SurfaceController` and `A2uiTransportAdapter`. Your app will primarily interact with this object to get things done.

   For example:

   dart

   ```
   class _MyHomePageState extends State<MyHomePage> {
     late final SurfaceController _surfaceController;
     late final A2uiTransportAdapter _transportAdapter;
     late final Conversation _conversation;
   ​
     @override
     void initState() {
       super.initState();
   ​
       // Create a SurfaceController with a widget catalog.
       // The BasicCatalogItems contain basic widgets for text, markdown, and images.
       _surfaceController = SurfaceController(catalogs: [BasicCatalogItems.asCatalog()]);
   ​
       // The Conversation wires transport -> controller internally.
       _transportAdapter = A2uiTransportAdapter(onSend: (message) async {
         // Implement sending to LLM and pipe chunks back.
       });
   ​
       final catalog = BasicCatalogItems.asCatalog();
       final promptBuilder = PromptBuilder.chat(
         catalog: catalog,
         systemPromptFragments: [
           '''
           You are an expert in creating funny riddles. Every time I give you a word,
           you should generate UI that displays one new riddle related to that word.
           Each riddle should have both a question and an answer.
           '''
         ],
       );
   ​
       // ... initialize your LLM Client of choice using promptBuilder.systemPromptJoined()
   ​
       // Create the Conversation to orchestrate everything.
       _conversation = Conversation(
         controller: _surfaceController,
         transport: _transportAdapter,
       );
   ​
       // Listen for surface lifecycle events:
       _conversation.events.listen((event) {
         if (event is ConversationSurfaceAdded) {
           _onSurfaceAdded(event);
         } else if (event is ConversationSurfaceRemoved) {
           _onSurfaceDeleted(event);
         }
       });
     }
   ​
     @override
     void dispose() {
       _textController.dispose();
       _conversation.dispose();
       _transportAdapter.dispose();
   ​
       super.dispose();
     }
   }
   ```

## Send messages and display the agent's responses

[#](#send-messages-and-display-the-agents-responses)

Send a request to the agent using the `sendRequest` method in the `Conversation` class, or by directly streaming into your LLM Client and pumping the result stream to the adapter by using `_transportAdapter.addChunk`.

To receive and display generated UI:

1. Listen to the `events` stream in `Conversation` to track the addition and removal of UI surfaces as they are generated. These events include a *surface ID* for each surface.

2. Build a `Surface` widget for each active surface using the surface IDs received in the previous step.

   For example:

   dart

   ```
   class _MyHomePageState extends State<MyHomePage> {
     // ...
   ​
     final _textController = TextEditingController();
     final _surfaceIds = <String>[];
   ​
     // Send a request containing the user's [text] to the agent.
     void _sendMessage(String text) async {
       if (text.trim().isEmpty) return;
       // await _conversation.sendRequest(ChatMessage.user(TextPart(text)));
     }
   ​
     // Invoked by the events stream listener when a new
     // UI surface is generated. Here, the ID is stored so the
     // build method can create a Surface to display it.
     void _onSurfaceAdded(ConversationSurfaceAdded update) {
       setState(() {
         _surfaceIds.add(update.surfaceId);
       });
     }
   ​
     // Invoked by the events stream listener when a UI surface is removed.
     void _onSurfaceDeleted(ConversationSurfaceRemoved update) {
       setState(() {
         _surfaceIds.remove(update.surfaceId);
       });
     }
   ​
     @override
     Widget build(BuildContext context) {
       return Scaffold(
         appBar: AppBar(
           backgroundColor: Theme.of(context).colorScheme.inversePrimary,
           title: Text(widget.title),
         ),
         body: Column(
           children: [
             Expanded(
               child: ListView.builder(
                 itemCount: _surfaceIds.length,
                 itemBuilder: (context, index) {
                   // For each surface, create a Surface to display it.
                   final id = _surfaceIds[index];
                   return Surface(surfaceContext: _surfaceController.contextFor(id));
                 },
               ),
             ),
             SafeArea(
               child: Padding(
                 padding: const EdgeInsets.symmetric(horizontal: 16.0),
                 child: Row(
                   children: [
                     Expanded(
                       child: TextField(
                         controller: _textController,
                         decoration: const InputDecoration(
                           hintText: 'Enter a message',
                         ),
                       ),
                     ),
                     const SizedBox(width: 16),
                     ElevatedButton(
                       onPressed: () {
                         // Send the user's text to the agent.
                         _sendMessage(_textController.text);
                         _textController.clear();
                       },
                       child: const Text('Send'),
                     ),
                   ],
                 ),
               ),
             ),
           ],
         ),
       );
     }
   }
   ```

## Add your own widgets to the catalog

[#](#custom-widgets)

For your convenience, you can use the provided core catalog of widgets. However, most production apps will want to define a custom catalog of widgets.

To add your own widgets, use the following instructions.

1. Depend on the `json_schema_builder` package

   Use `dart pub add` to add `json_schema_builder` as a dependency in your `pubspec.yaml` file:

   ```
   $ dart pub add json_schema_builder
   ```

2. Create the new widget's schema

   Each catalog item needs a schema that defines the data required to populate it. Using the `json_schema_builder` package, define one for the new widget.

   dart

   ```
   import 'package:json_schema_builder/json_schema_builder.dart';
   import 'package:material_ui/material_ui.dart';
   import 'package:genui/genui.dart';
   ​
   final _schema = S.object(
     properties: {
       'question': S.string(description: 'The question part of a riddle.'),
       'answer': S.string(description: 'The answer part of a riddle.'),
     },
     required: ['question', 'answer'],
   );
   ```

3. Create a `CatalogItem`

   Each `CatalogItem` represents a type of widget that the agent is allowed to generate. To do that, it combines a name, a schema, and a builder function that produces the widgets that compose the generated UI.

   dart

   ```
   final riddleCard = CatalogItem(
     name: 'RiddleCard',
     dataSchema: _schema,
     widgetBuilder:
         (itemContext) {
           final json = itemContext.data as Map<String, Object?>;
           final question = json['question'] as String;
           final answer = json['answer'] as String;
   ​
           final context = itemContext.buildContext;
   ​
           return Container(
             constraints: const BoxConstraints(maxWidth: 400),
             decoration: BoxDecoration(border: Border.all()),
             padding: const EdgeInsets.all(16),
             child: Column(
               crossAxisAlignment: CrossAxisAlignment.start,
               children: [
                 Text(question, style: Theme.of(context).textTheme.headlineMedium),
                 const SizedBox(height: 8.0),
                 Text(answer, style: Theme.of(context).textTheme.headlineSmall),
               ],
             ),
           );
         },
   );
   ```

4. Add the `CatalogItem` to the catalog

   Include your catalog items when instantiating `SurfaceController`.

   dart

   ```
   _surfaceController = SurfaceController(
     catalogs: [BasicCatalogItems.asCatalog().copyWith(newItems: [riddleCard])],
   );
   ```

5. Update the system instruction to use the new widget

   To make sure that the agent knows to use your new widget, tell the system instruction how and when to do so. Provide the name from the `CatalogItem` when you do.

   dart

   ```
   final promptBuilder = PromptBuilder.chat(
     catalog: catalog,
     systemPromptFragments: [
       '''
       You are an expert in creating funny riddles. Every time I give you a word,
       generate a RiddleCard that displays one new riddle related to that word.
       Each riddle should have both a question and an answer.
       '''
     ],
   );
   ​
   // Pass promptBuilder.systemPromptJoined() to your LLM Config
   ```

## Data model and data binding

[#](#data-model-and-data-binding)

A core concept in `genui` is the `DataModel`, a centralized, observable store for all dynamic UI state. Instead of each widget managing its own state, its state is stored in the `DataModel`.

Widgets are *bound* to data in this model. When data in the model changes, only the widgets that depend on that specific piece of data are rebuilt. This is achieved through a `DataContext` object passed to each widget's builder function.

### Binding to the data model

[#](#binding-to-the-data-model)

To bind a widget's property to the data model, specify a special JSON object in the data sent from the AI. This object can contain standard JSON primitives (for static values) or an object with a `path` property (to bind to a value in the data model).

For example, to display a user's name in a `Text` widget, the AI would generate:

json

```
{
  "id": "welcome-text",
  "component": "Text",
  "text": "Welcome to GenUI",
  "variant": "h1"
}
```

### Image

[#](#image)

json

```
{
  "id": "image",
  "component": "Image",
  "url": "https://example.com/image.png",
  "variant": "mediumFeature"
}
```

### Updating the data model

[#](#updating-the-data-model)

Input widgets, like `TextField`, update the DataModel directly. When the user types in a text field that is bound to `/user/name`, the `DataModel` updates, and any other widgets bound to that same path will automatically rebuild to show the new value.

This reactive data flow simplifies state management and creates a powerful, high-bandwidth interaction loop between the user, the UI, and the AI.

## Next steps

[#](#next-steps)

Check out the [examples](https://github.com/flutter/genui/blob/main/examples) included in the `genui` repo. The [travel app](https://github.com/flutter/genui/blob/main/examples/travel_app) shows how to define your own widget catalog that the agent can use to generate domain-specific UI.

If something is unclear or missing, please [create an issue](https://github.com/flutter/genui/issues/new/choose).

## System instructions

[#](#system-instructions)

The `genui` package gives the LLM a set of tools it can use to generate UI. To get the LLM to use these tools, the system instructions provided through `PromptBuilder` must explicitly tell it to do so.

This is why the [earlier example](/ai/genui/get-started#create-the-connection-to-an-agent) includes a system instruction for the agent with the line "Every time I give you a word, you should generate UI that...":

dart

```
final promptBuilder = PromptBuilder.chat(
  catalog: catalog,
  systemPromptFragments: [
    '''
    You are an expert in creating funny riddles.
    Every time I give you a word, you should generate UI that
    displays one new riddle related to that word.
    Each riddle should have both a question and an answer.
    '''
  ],
);
```

## Troubleshooting/FAQ

[#](#troubleshoot)

### How can I configure logging?

[#](#how-can-i-configure-logging)

To observe communication between your app and the agent, enable logging in your `main` method.

dart

```
import 'package:logging/logging.dart';
import 'package:genui/genui.dart';
​
final logger = configureLogging(level: Level.ALL);
​
void main() async {
  logger.onRecord.listen((record) {
    debugPrint('${record.loggerName}: ${record.message}');
  });
​
  // Additional initialization of bindings and Firebase.
}
```

### I'm getting errors about my minimum macOS/iOS version.

[#](#im-getting-errors-about-my-minimum-macosios-version)

Firebase has a [minimum version requirement](https://firebase.google.com/support/release-notes/ios) for Apple's platforms, which might be higher than Flutter's default. Check your `Podfile` (for iOS) and `CMakeLists.txt` (for macOS) to ensure that you're targeting a version that meets or exceeds Firebase's requirements.

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-09-29. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/ai/genui/get-started.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/ai/genui/get-started\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/ai/genui/get-started.md "Report an issue with this page").

----
url: https://docs.flutter.dev/tools/vs-code
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Visual Studio Code](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

* [Android Studio and IntelliJ](/tools/android-studio)
* [Visual Studio Code]()

[]()

## Installation and setup

[#](#setup)

[VS Code](https://code.visualstudio.com/) is a code editor to build and debug apps. With the Flutter extension installed, you can compile, deploy, and debug Flutter apps.

To install the latest version of VS Code, follow Microsoft's instructions for the relevant platform:

* [Install on macOS](https://code.visualstudio.com/docs/setup/mac)
* [Install on Windows](https://code.visualstudio.com/docs/setup/windows)
* [Install on Linux](https://code.visualstudio.com/docs/setup/linux)

### Install the Flutter extension

[#](#install-extension)

1. Start **VS Code**.

2. Open a browser and go to the [Flutter extension](https://marketplace.visualstudio.com/items?itemName=Dart-Code.flutter) page on the Visual Studio Marketplace.

3. Click **Install**. Installing the Flutter extension also installs the Dart extension.

### Validate your VS Code setup

[#](#validate-setup)

1. Go to **View** > **Command Palette...**.

   You can also press `Ctrl` / `Cmd` + `Shift` + `P`.

2. Type `doctor`.

3. Select **Flutter: Run Flutter Doctor**.

   Once you select this command, VS Code does the following:

   * Opens the **Output** panel.
   * Displays **flutter (flutter)** in the dropdown on the upper right of this panel.
   * Displays the output of `flutter doctor` command.

### Updating the extension

[#](#updating)

Updates to the extensions are shipped on a regular basis. By default, VS Code automatically updates extensions when updates are available.

To install updates yourself:

1. Click **Extensions** in the Side Bar.
2. If the Flutter extension has an available update, click **Update** and then **Reload**.
3. Restart VS Code.

## Creating projects

[#](#creating-projects)

There are a couple ways to create a new project.

### Creating a new project

[#](#creating-a-new-project)

To create a new Flutter project from the Flutter starter app template:

1. Go to **View** > **Command Palette...**.

   You can also press `Ctrl` / `Cmd` + `Shift` + `P`.

2. Type `flutter`.

3. Select the **Flutter: New Project**.

4. Press `Enter`.

5. Select **Application**.

6. Press `Enter`.

7. Select a **Project location**.

8. Enter your desired **Project name**.

### Opening a project from existing source code

[#](#opening-a-project-from-existing-source-code)

To open an existing Flutter project:

1. Go to **File** > **Open**.

   You can also press `Ctrl` / `Cmd` + `O`

2. Browse to the directory holding your existing Flutter source code files.

3. Click **Open**.

## Editing code and viewing issues

[#](#editing-code-and-viewing-issues)

The Flutter extension performs code analysis. The code analysis can:

* Highlight language syntax

* Complete code based on rich type analysis

* Navigate to type declarations

  * Go to **Go** > **Go to Definition**.
  * You can also press `F12`.

* Find type usages.

  * Press `Shift` + `F12`.

* View all current source code problems.

  * Go to **View** > **Problems**.
  * You can also press `Ctrl` / `Cmd` + `Shift` + `M`.
  * The Problems pane displays any analysis issues:

## Running and debugging

[#](#running-and-debugging)

Start debugging by clicking **Run > Start Debugging** from the main IDE window, or press `F5`.

### Selecting a target device

[#](#selecting-a-target-device)

When a Flutter project is open in VS Code, you should see a set of Flutter specific entries in the status bar, including a Flutter SDK version and a device name (or the message **No Devices**):

The Flutter extension automatically selects the last device connected. However, if you have multiple devices/simulators connected, click **device** in the status bar to see a pick-list at the top of the screen. Select the device you want to use for running or debugging.

### Run app without breakpoints

[#](#run-app-without-breakpoints)

Go to **Run** > **Start Without Debugging**.

You can also press `Ctrl` + `F5`.

### Run app with breakpoints

[#](#run-app-with-breakpoints)

1. If desired, set breakpoints in your source code.

2. Click **Run** > **Start Debugging**. You can also press `F5`. The status bar turns orange to show you are in a debug session.

   * The left **Debug Sidebar** shows stack frames and variables.
   * The bottom **Debug Console** pane shows detailed logging output.
   * Debugging is based on a default launch configuration. To customize, click the cog at the top of the **Debug Sidebar** to create a `launch.json` file. You can then modify the values.

### Run app in debug, profile, or release mode

[#](#run-app-in-debug-profile-or-release-mode)

Flutter offers many different build modes to run your app in. You can read more about them in [Flutter's build modes](/testing/build-modes).

1. Open the `launch.json` file in VS Code.

   If you don't have a `launch.json` file:

   1. Go to **View** > **Run**.

      You can also press `Ctrl` / `Cmd` + `Shift` + `D`

      The **Run and Debug** panel displays.

   2. Click **create a launch.json file**.

2. In the `configurations` section, change the `flutterMode` property to the build mode you want to target.

   For example, if you want to run in debug mode, your `launch.json` might look like this:

   json

   ```
   "configurations": [
     {
       "name": "Flutter",
       "request": "launch",
       "type": "dart",
       "flutterMode": "debug"
     }
   ]
   ```

3. Run the app through the **Run** panel.

## Fast edit and refresh development cycle

[#](#fast-edit-and-refresh-development-cycle)

Flutter offers a best-in-class developer cycle enabling you to see the effect of your changes almost instantly with the *Stateful Hot Reload* feature. To learn more, check out [Hot reload](/tools/hot-reload).

## Advanced debugging

[#](#advanced-debugging)

You might find the following advanced debugging tips useful:

### Debugging visual layout issues

[#](#debugging-visual-layout-issues)

During a debug session, several additional debugging commands are added to the [Command Palette](https://code.visualstudio.com/docs/getstarted/userinterface#_command-palette) and to the [Flutter inspector](/tools/devtools/inspector). When space is limited, the icon is used as the visual version of the label.

* **Toggle Baseline Painting**

  Causes each RenderBox to paint a line at each of its baselines.

* **Toggle Repaint Rainbow**

  Shows rotating colors on layers when repainting.

* **Toggle Slow Animations**

  Slows down animations to enable visual inspection.

* **Toggle Debug Mode Banner**

  Hides the debug mode banner even when running a debug build.

### Debugging external libraries

[#](#debugging-external-libraries)

By default, debugging an external library is disabled in the Flutter extension. To enable:

1. Select **Settings > Extensions > Dart Configuration**.
2. Check the `Debug External Libraries` option.

## Editing tips for Flutter code

[#](#editing-tips-for-flutter-code)

If you have additional tips we should share, [let us know](https://github.com/flutter/website/issues/new)!

### Assists & quick fixes

[#](#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. To invoke the assist, click the lightbulb as shown in the following screenshot:

You can also press `Ctrl` / `Cmd` + `.`

Quick fixes are similar, only they are shown with a piece of code has an error and they can assist in correcting it.

* **Wrap with new widget assist**

  This can be used when you have a widget that you want to wrap in a surrounding widget, for example if you want to wrap a widget in a `Row` or `Column`.

* **Wrap widget list with new widget assist**

  Similar to the assist above, but for wrapping an existing list of widgets rather than an individual widget.

* **Convert child to children assist**

  Changes a child argument to a children argument, and wraps the argument value in a list.

* **Convert StatelessWidget to StatefulWidget assist**

  Changes the implementation of a `StatelessWidget` to that of a `StatefulWidget`, by creating the `State` class and moving the code there.

### Snippets

[#](#snippets)

Snippets can be used to speed up entering typical code structures. They are invoked by typing their prefix, and then selecting from the code completion window:

The Flutter extension includes the following snippets:

* Prefix `stless`: Create a new subclass of -StatelessWidget\`.
* Prefix `stful`: Create a new subclass of `StatefulWidget` and its associated State subclass.
* Prefix `stanim`: Create a new subclass of `StatefulWidget`, and its associated State subclass including a field initialized with an `AnimationController`.

The Dart extension includes the following snippets:

| Prefix    | Description                                     | Code Example                                                             |
| --------- | ----------------------------------------------- | ------------------------------------------------------------------------ |
| `main`    | Insert a main function, used as an entry point. | `void main(List<String> args) { }`                                       |
| `try`     | Insert a try/catch block.                       | `try { } catch (e) { }`                                                  |
| `if`      | Insert an if statement.                         | `if (condition) { }`                                                     |
| `ife`     | Insert an if statement with an else block.      | `if (condition) { } else { }`                                            |
| `switch`  | Insert a switch statement.                      | `switch (variable) { case value1: break; case value2: break; default: }` |
| `for`     | Insert a for loop.                              | `for (var i = 0; i < 10; i++) { }`                                       |
| `fori`    | Insert a for-in loop.                           | `for (var item in list) { }`                                             |
| `while`   | Insert a while loop.                            | `while (condition) { }`                                                  |
| `do`      | Insert a do-while loop.                         | `do { } while (condition);`                                              |
| `fun`     | Insert a function definition.                   | `void myFunction(String name) { }`                                       |
| `class`   | Insert a class definition.                      | `class MyClass { }`                                                      |
| `typedef` | Insert a typedef.                               | `typedef MyFunction = void Function(String);`                            |
| `test`    | Insert a test block.                            | `test('My test description', () { });`                                   |
| `group`   | Insert a test group block.                      | `group('My test group', () { });`                                        |

You can also define custom snippets by executing **Configure User Snippets** from the [Command Palette](https://code.visualstudio.com/docs/getstarted/userinterface#_command-palette).

### Keyboard shortcuts

[#](#keyboard-shortcuts)

* **Hot reload**

  To perform a hot reload during a debug session, click **Hot Reload** on the **Debug Toolbar**.

  You can also press `Ctrl` + `F5` (`Cmd` + `F5` on macOS).

  Keyboard mappings can be changed by executing the **Open Keyboard Shortcuts** command from the [Command Palette](https://code.visualstudio.com/docs/getstarted/userinterface#_command-palette).

### Hot reload vs. hot restart

[#](#hot-reload-vs-hot-restart)

Hot reload works by injecting updated source code files into the running Dart VM (Virtual Machine). This includes not only adding new classes, but also adding methods and fields to existing classes, and changing existing functions. A few types of code changes cannot be hot reloaded though:

* Global variable initializers
* Static field initializers
* The `main()` method of the app

For these changes, restart your app without ending your debugging session. To perform a hot restart, run the **Flutter: Hot Restart** command from the [Command Palette](https://code.visualstudio.com/docs/getstarted/userinterface#_command-palette).

You can also press `Ctrl` + `Shift` + `F5` or `Cmd` + `Shift` + `F5` on macOS.

### Customize closing labels

[#](#closing-labels)

The Dart extension displays labels after the closing delimiters of multi-line constructs, such as constructor invocations, method calls, and list literals. Closing labels are editor annotations and don't change your source code, even though the default `//` prefix makes them look similar to comments.

To change the prefix:

1. Open the [Command Palette](https://code.visualstudio.com/docs/getstarted/userinterface#_command-palette).
2. Select **Preferences: Open Settings (UI)**.
3. Search for `dart.closingLabels`.
4. In **Dart: Closing Labels Prefix**, enter your preferred prefix.

For example, to remove the comment marker while keeping space between the code and label, set the prefix to a single space (` `):

json

```
{
  "dart.closingLabelsPrefix": " "
}
```

To hide closing labels, turn off **Dart: Closing Labels**. For the complete list of closing label options, see the [extension's settings reference](https://dartcode.org/docs/settings/#dartclosinglabels).

## Flutter Property Editor

[#](#property-editor)

The Flutter Property Editor is a powerful tool provided by the [Flutter extension](https://marketplace.visualstudio.com/items?itemName=Dart-Code.flutter) that lets you view and modify widget properties directly from its visual interface.

### How to open the Flutter Property Editor in VS Code

[#](#how-to-open-the-flutter-property-editor-in-vs-code)

1. Click on the Flutter Property Editor **icon** in the VS Code sidebar.
2. The Flutter Property Editor will load in the side panel.
3. Please refer to the Flutter Property Editor [documentation](/tools/property-editor) for a detailed usage guide.

## Troubleshooting

[#](#troubleshooting)

### Known issues and feedback

[#](#known-issues-and-feedback)

All known bugs are tracked in the issue tracker: [Dart and Flutter extensions GitHub issue tracker](https://github.com/Dart-Code/Dart-Code/issues). We welcome feedback, both on bugs/issues and feature requests.

Prior to filing new issues:

* Do a quick search in the issue trackers to see if the issue is already tracked.
* Make sure you are [up to date](#updating) with the most recent version of the plugin.

When filing new issues, include [flutter doctor](/resources/bug-reports/#provide-some-flutter-diagnostics) output.

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-07-31. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/vs-code.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/tools/vs-code\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/vs-code.md "Report an issue with this page").

----
url: https://docs.flutter.dev/install/upgrade.md
----

\# Upgrade Flutter > Learn how to upgrade Flutter and switch to another channel. No matter which one of the Flutter release channels you follow, you can use the \`flutter\` command to upgrade your Flutter SDK or the packages that your app depends on. []()## Upgrade the Flutter SDK To update the Flutter SDK use the \`flutter upgrade\` command: \`\`\`console $ flutter upgrade \`\`\` This command gets the most recent version of the Flutter SDK that's available on your current Flutter channel. If you are using the \*\*stable\*\* channel and want an even more recent version of the Flutter SDK, switch to the \*\*beta\*\* channel using \`flutter channel beta\`, and then run \`flutter upgrade\`. []()### Keep informed We publish \[migration guides]\[] for known breaking changes. We send announcements regarding these changes to the \[Flutter announcements mailing list]\[flutter-announce]. To avoid being broken by future versions of Flutter, consider submitting your tests to our \[test registry]\[]. ## Switching Flutter channels Flutter has two release channels: \*\*stable\*\* and \*\*beta\*\*. ### The \*\*stable\*\* channel We recommend the \*\*stable\*\* channel for new users and for production app releases. The team updates this channel about every three months. The channel might receive occasional hot fixes for high-severity or high-impact issues. The continuous integration for the Flutter team's plugins and packages includes testing against the latest \*\*stable\*\* release. The latest documentation for the \*\*stable\*\* branch is at: ### The \*\*beta\*\* channel The \*\*beta\*\* channel has the latest stable release. This is the most recent version of Flutter that we have heavily tested. This channel has passed all our public testing, has been verified against test suites for Google products that use Flutter, and has been vetted against \[contributed private test suites]\[test registry]. The \*\*beta\*\* channel receives regular hot fixes to address newly discovered important issues. The \*\*beta\*\* channel is essentially the same as the \*\*stable\*\* channel but updated monthly instead of quarterly. Indeed, when the \*\*stable\*\* channel is updated, it is updated to the latest \*\*beta\*\* release. ### Other channels We currently have one other channel, \*\*main\*\* (previously known as \*\*master\*\*). People who \[contribute to Flutter]\[] use this channel. This channel is not as thoroughly tested as the \*\*beta\*\* and \*\*stable\*\* channels. We do not recommend using this channel as it is more likely to contain serious regressions. The latest documentation for the \*\*main\*\* branch is at: []()### Change channels To view your current channel, use the following command: \`\`\`console $ flutter channel \`\`\` To change to another channel, use \`flutter channel \`. Once you've changed your channel, use \`flutter upgrade\` to download the latest Flutter SDK and dependent packages for that channel. For example: \`\`\`console $ flutter channel beta $ flutter upgrade \`\`\` []()## Switch to a specific Flutter version To switch to a specific Flutter version: 1. Find your desired \*\*Flutter version\*\* on the \[Flutter SDK archive]\[]. 1. Navigate to the Flutter SDK: \`\`\`console $ cd /path/to/flutter \`\`\` :::tip You can find the Flutter SDK's path using \`flutter doctor --verbose\`. ::: 1. Use \`git checkout\` to switch to your desired \*\*Flutter version\*\*: \`\`\`console $ git checkout \`\`\` []()## Upgrade packages If you've modified your \`pubspec.yaml\` file, or you want to update only the packages that your app depends upon (instead of both the packages and Flutter itself), then use one of the \`flutter pub\` commands. To update to the \_latest compatible versions\_ of all the dependencies listed in the \`pubspec.yaml\` file, use the \`upgrade\` command: \`\`\`console $ flutter pub upgrade \`\`\` To update to the \_latest possible version\_ of all the dependencies listed in the \`pubspec.yaml\` file, use the \`upgrade --major-versions\` command: \`\`\`console $ flutter pub upgrade --major-versions \`\`\` This also automatically update the constraints in the \`pubspec.yaml\` file. To identify out-of-date package dependencies and get advice on how to update them, use the \`outdated\` command. For details, see the Dart \[\`pub outdated\` documentation]\(https\://dart.dev/tools/pub/cmd/pub-outdated). \`\`\`console $ flutter pub outdated \`\`\` ## Troubleshooting ### Windows: "Filename too long" error When running \`flutter upgrade\` on Windows, you might encounter an error like the following: \`\`\`text error: unable to create file ...: Filename too long \`\`\` This occurs because the path to a file in the Flutter SDK exceeds the default maximum path length limit on Windows. To resolve this issue, consider installing the Flutter SDK at a shorter path. For example, install Flutter at \`C:\Flutter\` instead of something longer like \`C:\Users\\\Documents\flutter\`. Otherwise, do the following: 1. Enable long paths support in Git: \`\`\`console $ git config --system core.longpaths true \`\`\` If the command fails with a permission error, try running your terminal as an administrator. 1. Enable long paths in Windows: \`\`\`console New-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\FileSystem" -Name "LongPathsEnabled" -Value 1 -PropertyType DWORD -Force \`\`\` This command requires administrator privileges. \[Flutter SDK archive]: /install/archive \[flutter-announce]: https\://groups.google.com/forum/#!forum/flutter-announce \[pubspec.yaml]: https\://dart.dev/tools/pub/pubspec \[test registry]: https\://github.com/flutter/tests \[contribute to Flutter]: https\://github.com/flutter/flutter/blob/main/CONTRIBUTING.md \[migration guides]: /release/breaking-changes

----
url: https://docs.flutter.dev/ui/layout
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Layouts in Flutter](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

## Overview

[#](#overview)

The core of Flutter's layout mechanism is widgets. In Flutter, almost everything is a widget—even layout models are widgets. The images, icons, and text that you see in a Flutter app are all widgets. But things you don't see are also widgets, such as the rows, columns, and grids that arrange, constrain, and align the visible widgets. You create a layout by composing widgets to build more complex widgets.

## Conceptual example

[#](#conceptual-example)

In the following example, the first screenshot displays three icons with labels and the second screenshot includes the visual layout for rows and columns. In the second screenshot, `debugPaintSizeEnabled` is set to `true` so you can see the visual layout.

Here's a diagram of the widget tree for the previous example:

Most of this should look as you might expect, but you might be wondering about the containers (shown in pink). [`Container`](https://api.flutter.dev/flutter/widgets/Container-class.html) is a widget class that allows you to customize its child widget. Use a `Container` when you want to add padding, margins, borders, or background color, to name some of its capabilities.

Each [`Text`](https://api.flutter.dev/flutter/widgets/Text-class.html) widget is placed in a `Container` to add margins. The entire [`Row`](https://api.flutter.dev/flutter/widgets/Row-class.html) is also placed in a `Container` to add padding around the row.

The rest of the UI is controlled by properties. Set an [`Icon`](https://pub.dev/documentation/material_ui/latest/material_ui/Icons-class.html)'s color using its `color` property. Use the `Text.style` property to set the font, its color, weight, and so on. Columns and rows have properties that allow you to specify how their children are aligned vertically or horizontally, and how much space the children should occupy.

## Lay out a widget

[#](#lay-out-a-widget)

How do you lay out a single widget in Flutter? This section shows you how to create and display a simple widget. It also shows the entire code for a simple Hello World app.

In Flutter, it takes only a few steps to put text, an icon, or an image on the screen.

### 1. Select a layout widget

[#](#1-select-a-layout-widget)

Choose from a variety of [layout widgets](/ui/widgets/layout) based on how you want to align or constrain a visible widget, as these characteristics are typically passed on to the contained widget.

For example, you could use the [`Center`](https://api.flutter.dev/flutter/widgets/Center-class.html) layout widget to center a visible widget horizontally and vertically:

dart

```
Center(
  // Content to be centered here.
)
```

### 2. Create a visible widget

[#](#2-create-a-visible-widget)

Choose a [visible widget](/ui/widgets) for your app to contain visible elements, such as [text](https://api.flutter.dev/flutter/widgets/Text-class.html), [images](https://api.flutter.dev/flutter/widgets/Image-class.html), or [icons](https://pub.dev/documentation/material_ui/latest/material_ui/Icons-class.html).

For example, you could use the [`Text`](https://api.flutter.dev/flutter/widgets/Text-class.html) widget display some text:

dart

```
Text('Hello World')
```

### 3. Add the visible widget to the layout widget

[#](#3-add-the-visible-widget-to-the-layout-widget)

All layout widgets have either of the following:

* A `child` property if they take a single child—for example, `Center` or `Container`
* A `children` property if they take a list of widgets—for example, `Row`, `Column`, `ListView`, or `Stack`.

Add the `Text` widget to the `Center` widget:

dart

```
const Center(
  child: Text('Hello World'),
),
```

### 4. Add the layout widget to the page

[#](#4-add-the-layout-widget-to-the-page)

A Flutter app is itself a widget, and most widgets have a [`build()`](https://api.flutter.dev/flutter/widgets/StatelessWidget/build.html) method. Instantiating and returning a widget in the app's `build()` method displays the widget.

[]()[]()[]()

* [Standard apps](#0-tab-panel)
* [Material apps](#1-tab-panel)
* [Cupertino apps](#2-tab-panel)

For a general app, you can add the `Container` widget to the app's `build()` method:

dart

```
class MyApp extends StatelessWidget {
  const MyApp({super.key});
​
  @override
  Widget build(BuildContext context) {
    return Container(
      decoration: const BoxDecoration(color: Colors.white),
      child: const Center(
        child: Text(
          'Hello World',
          textDirection: TextDirection.ltr,
          style: TextStyle(fontSize: 32, color: Colors.black87),
        ),
      ),
    );
  }
}
```

By default, a general app doesn't include an `AppBar`, title, or background color. If you want these features in a general app, you have to build them yourself. This app changes the background color to white and the text to dark grey to mimic a Material app.

For a `Material` app, you can use a [`Scaffold`](https://pub.dev/documentation/material_ui/latest/material_ui/Scaffold-class.html) widget; it provides a default banner, background color, and has API for adding drawers, snack bars, and bottom sheets. Then you can add the `Center` widget directly to the `body` property for the home page.

dart

```
class MyApp extends StatelessWidget {
  const MyApp({super.key});
​
  @override
  Widget build(BuildContext context) {
    const String appTitle = 'Flutter layout demo';
    return MaterialApp(
      title: appTitle,
      home: Scaffold(
        appBar: AppBar(title: const Text(appTitle)),
        body: const Center(
          child: Text('Hello World'),
        ),
      ),
    );
  }
}
```

To create a `Cupertino` app, use the `CupertinoApp` and [`CupertinoPageScaffold`](https://pub.dev/documentation/cupertino_ui/latest/cupertino_ui/CupertinoPageScaffold-class.html) widgets.

Unlike `Material`, it doesn't provide a default banner or background color. You need to set these yourself.

* To set default colors, pass in a configured [`CupertinoThemeData`](https://pub.dev/documentation/cupertino_ui/latest/cupertino_ui/CupertinoThemeData-class.html) to your app's `theme` property.

* To add an iOS-styled navigation bar to the top of your app, add a [`CupertinoNavigationBar`](https://pub.dev/documentation/cupertino_ui/latest/cupertino_ui/CupertinoNavigationBar-class.html) widget to the `navigationBar` property of your scaffold. You can use the colors that [`CupertinoColors`](https://pub.dev/documentation/cupertino_ui/latest/cupertino_ui/CupertinoColors-class.html) provides to configure your widgets to match iOS design.

* To lay out the body of your app, set the `child` property of your scaffold with the desired widget as its value, like `Center` or `Column`.

To learn what other UI components you can add, check out the [Cupertino library](https://pub.dev/documentation/cupertino_ui/latest/cupertino_ui).

dart

```
class MyApp extends StatelessWidget {
  const MyApp({super.key});
​
  @override
  Widget build(BuildContext context) {
    return const CupertinoApp(
      title: 'Flutter layout demo',
      theme: CupertinoThemeData(
        brightness: Brightness.light,
        primaryColor: CupertinoColors.systemBlue,
      ),
      home: CupertinoPageScaffold(
        navigationBar: CupertinoNavigationBar(
          backgroundColor: CupertinoColors.systemGrey,
          middle: Text('Flutter layout demo'),
        ),
        child: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [Text('Hello World')],
          ),
        ),
      ),
    );
  }
}
```

### 5. Run your app

[#](#5-run-your-app)

After you've added your widgets, run your app. When you run the app, you should see *Hello World*.

App source code:

* [Material app](https://github.com/flutter/website/tree/main/examples/layout/base)
* [Non-Material app](https://github.com/flutter/website/tree/main/examples/layout/non_material)

***

## Lay out multiple widgets vertically and horizontally

[#](#lay-out-multiple-widgets-vertically-and-horizontally)

One of the most common layout patterns is to arrange widgets vertically or horizontally. You can use a `Row` widget to arrange widgets horizontally, and a `Column` widget to arrange widgets vertically.

To create a row or column in Flutter, you add a list of children widgets to a [`Row`](https://api.flutter.dev/flutter/widgets/Row-class.html) or [`Column`](https://api.flutter.dev/flutter/widgets/Column-class.html) widget. In turn, each child can itself be a row or column, and so on. The following example shows how it is possible to nest rows or columns inside of rows or columns.

This layout is organized as a `Row`. The row contains two children: a column on the left, and an image on the right:

The left column's widget tree nests rows and columns.

You'll implement some of Pavlova's layout code in [Nesting rows and columns](#nesting-rows-and-columns).

### Aligning widgets

[#](#aligning-widgets)

You control how a row or column aligns its children using the `mainAxisAlignment` and `crossAxisAlignment` properties. For a row, the main axis runs horizontally and the cross axis runs vertically. For a column, the main axis runs vertically and the cross axis runs horizontally.

The [`MainAxisAlignment`](https://api.flutter.dev/flutter/rendering/MainAxisAlignment.html) and [`CrossAxisAlignment`](https://api.flutter.dev/flutter/rendering/CrossAxisAlignment.html) enums offer a variety of constants for controlling alignment.

In the following example, each of the 3 images is 100 pixels wide. The render box (in this case, the entire screen) is more than 300 pixels wide, so setting the main axis alignment to `spaceEvenly` divides the free horizontal space evenly between, before, and after each image.

dart

```
Row(
  mainAxisAlignment: MainAxisAlignment.spaceEvenly,
  children: [
    Image.asset('images/pic1.jpg'),
    Image.asset('images/pic2.jpg'),
    Image.asset('images/pic3.jpg'),
  ],
);
```

**App source:** [row\_column](https://github.com/flutter/website/tree/main/examples/layout/row_column)

Columns work the same way as rows. The following example shows a column of 3 images, each is 100 pixels high. The height of the render box (in this case, the entire screen) is more than 300 pixels, so setting the main axis alignment to `spaceEvenly` divides the free vertical space evenly between, above, and below each image.

dart

```
Column(
  mainAxisAlignment: MainAxisAlignment.spaceEvenly,
  children: [
    Image.asset('images/pic1.jpg'),
    Image.asset('images/pic2.jpg'),
    Image.asset('images/pic3.jpg'),
  ],
);
```

**App source:** [row\_column](https://github.com/flutter/website/tree/main/examples/layout/row_column)

### Sizing widgets

[#](#sizing-widgets)

When a layout is too large to fit a device, a yellow and black striped pattern appears along the affected edge. Here is an [example](https://github.com/flutter/website/tree/main/examples/layout/sizing) of a row that is too wide:

Widgets can be sized to fit within a row or column by using the [`Expanded`](https://api.flutter.dev/flutter/widgets/Expanded-class.html) widget. To fix the previous example where the row of images is too wide for its render box, wrap each image with an `Expanded` widget.

dart

```
Row(
  crossAxisAlignment: CrossAxisAlignment.center,
  children: [
    Expanded(child: Image.asset('images/pic1.jpg')),
    Expanded(child: Image.asset('images/pic2.jpg')),
    Expanded(child: Image.asset('images/pic3.jpg')),
  ],
);
```

**App source:** [sizing](https://github.com/flutter/website/tree/main/examples/layout/sizing)

Perhaps you want a widget to occupy twice as much space as its siblings. For this, use the `Expanded` widget `flex` property, an integer that determines the flex factor for a widget. The default flex factor is 1. The following code sets the flex factor of the middle image to 2:

dart

```
Row(
  crossAxisAlignment: CrossAxisAlignment.center,
  children: [
    Expanded(child: Image.asset('images/pic1.jpg')),
    Expanded(flex: 2, child: Image.asset('images/pic2.jpg')),
    Expanded(child: Image.asset('images/pic3.jpg')),
  ],
);
```

**App source:** [sizing](https://github.com/flutter/website/tree/main/examples/layout/sizing)

### Packing widgets

[#](#packing-widgets)

By default, a row or column occupies as much space along its main axis as possible, but if you want to pack the children closely together, set its `mainAxisSize` to `MainAxisSize.min`. The following example uses this property to pack the star icons together.

dart

```
Row(
  mainAxisSize: MainAxisSize.min,
  children: [
    Icon(Icons.star, color: Colors.green[500]),
    Icon(Icons.star, color: Colors.green[500]),
    Icon(Icons.star, color: Colors.green[500]),
    const Icon(Icons.star, color: Colors.black),
    const Icon(Icons.star, color: Colors.black),
  ],
)
```

**App source:** [pavlova](https://github.com/flutter/website/tree/main/examples/layout/pavlova)

### Nesting rows and columns

[#](#nesting-rows-and-columns)

The layout framework allows you to nest rows and columns inside of rows and columns as deeply as you need. Let's look at the code for the outlined section of the following layout:

The outlined section is implemented as two rows. The ratings row contains five stars and the number of reviews. The icons row contains three columns of icons and text.

The widget tree for the ratings row:

The `ratings` variable creates a row containing a smaller row of 5-star icons, and text:

dart

```
final stars = Row(
  mainAxisSize: MainAxisSize.min,
  children: [
    Icon(Icons.star, color: Colors.green[500]),
    Icon(Icons.star, color: Colors.green[500]),
    Icon(Icons.star, color: Colors.green[500]),
    const Icon(Icons.star, color: Colors.black),
    const Icon(Icons.star, color: Colors.black),
  ],
);
​
final ratings = Container(
  padding: const EdgeInsets.all(20),
  child: Row(
    mainAxisAlignment: MainAxisAlignment.spaceEvenly,
    children: [
      stars,
      const Text(
        '170 Reviews',
        style: TextStyle(
          color: Colors.black,
          fontWeight: FontWeight.w800,
          fontFamily: 'Roboto',
          letterSpacing: 0.5,
          fontSize: 20,
        ),
      ),
    ],
  ),
);
```

The icons row, below the ratings row, contains 3 columns; each column contains an icon and two lines of text, as you can see in its widget tree:

The `iconList` variable defines the icons row:

dart

```
const descTextStyle = TextStyle(
  color: Colors.black,
  fontWeight: FontWeight.w800,
  fontFamily: 'Roboto',
  letterSpacing: 0.5,
  fontSize: 18,
  height: 2,
);
​
// DefaultTextStyle.merge() allows you to create a default text
// style that is inherited by its child and all subsequent children.
final iconList = DefaultTextStyle.merge(
  style: descTextStyle,
  child: Container(
    padding: const EdgeInsets.all(20),
    child: Row(
      mainAxisAlignment: MainAxisAlignment.spaceEvenly,
      children: [
        Column(
          children: [
            Icon(Icons.kitchen, color: Colors.green[500]),
            const Text('PREP:'),
            const Text('25 min'),
          ],
        ),
        Column(
          children: [
            Icon(Icons.timer, color: Colors.green[500]),
            const Text('COOK:'),
            const Text('1 hr'),
          ],
        ),
        Column(
          children: [
            Icon(Icons.restaurant, color: Colors.green[500]),
            const Text('FEEDS:'),
            const Text('4-6'),
          ],
        ),
      ],
    ),
  ),
);
```

The `leftColumn` variable contains the ratings and icons rows, as well as the title and text that describes the Pavlova:

dart

```
final leftColumn = Container(
  padding: const EdgeInsets.fromLTRB(20, 30, 20, 20),
  child: Column(children: [titleText, subTitle, ratings, iconList]),
);
```

The left column is placed in a `SizedBox` to constrain its width. Finally, the UI is constructed with the entire row (containing the left column and the image) inside a `Card`.

The [Pavlova image](https://pixabay.com/en/photos/pavlova) is from [Pixabay](https://pixabay.com/en/photos/pavlova). You can embed an image from the net using `Image.network()` but, for this example, the image is saved to an images directory in the project, added to the [pubspec file](https://github.com/flutter/website/tree/main/examples/layout/pavlova/pubspec.yaml), and accessed using `Images.asset()`. For more information, see [Adding assets and images](/ui/assets/assets-and-images).

dart

```
body: Center(
  child: Container(
    margin: const EdgeInsets.fromLTRB(0, 40, 0, 30),
    height: 600,
    child: Card(
      child: Row(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          SizedBox(width: 440, child: leftColumn),
          mainImage,
        ],
      ),
    ),
  ),
),
```

**App source:** [pavlova](https://github.com/flutter/website/tree/main/examples/layout/pavlova)

***

## Common layout widgets

[#](#common-layout-widgets)

Flutter has a rich library of layout widgets. Here are a few of those most commonly used. The intent is to get you up and running as quickly as possible, rather than overwhelm you with a complete list. For information on other available widgets, refer to the [Widget catalog](/ui/widgets), or use the Search box in the [API reference docs](https://api.flutter.dev/flutter). Also, the widget pages in the API docs often make suggestions about similar widgets that might better suit your needs.

The following widgets fall into two categories: standard widgets from the [widgets library](https://api.flutter.dev/flutter/widgets/widgets-library.html), and specialized widgets from the [Material library](https://pub.dev/documentation/material_ui/latest/material_ui). Any app can use the widgets library but only Material apps can use the Material Components library.

[]()[]()

* [Standard widgets](#3-tab-panel)
* [Material widgets](#4-tab-panel)
* [Cupertino widgets](#5-tab-panel)

- [`Container`](#container)

  Adds padding, margins, borders, background color, or other decorations to a widget.

- [`GridView`](#gridview)

  Lays widgets out as a scrollable grid.

- [`ListView`](#listview)

  Lays widgets out as a scrollable list.

- [`Stack`](#stack)

  Overlaps a widget on top of another.

* [`Scaffold`](https://pub.dev/documentation/material_ui/latest/material_ui/Scaffold-class.html)

  Provides a structured layout framework with slots for common Material Design app elements.

* [`AppBar`](https://pub.dev/documentation/material_ui/latest/material_ui/AppBar-class.html)

  Creates a horizontal bar that's typically displayed at the top of a screen.

* [`Card`](#card)

  Organizes related info into a box with rounded corners and a drop shadow.

* [`ListTile`](#listtile)

  Organizes up to 3 lines of text, and optional leading and trailing icons, into a row.

- [`CupertinoPageScaffold`](https://pub.dev/documentation/cupertino_ui/latest/cupertino_ui/CupertinoPageScaffold-class.html)

  Provides the basic layout structure for an iOS-style page.

- [`CupertinoNavigationBar`](https://pub.dev/documentation/cupertino_ui/latest/cupertino_ui/CupertinoNavigationBar-class.html)

  Creates an iOS-style navigation bar at the top of the screen.

- [`CupertinoSegmentedControl`](https://pub.dev/documentation/cupertino_ui/latest/cupertino_ui/CupertinoSegmentedControl-class.html)

  Creates a segmented control for selecting.

- [`CupertinoTabBar`](https://pub.dev/documentation/cupertino_ui/latest/cupertino_ui/CupertinoTabBar-class.html) and [`CupertinoTabScaffold`](https://pub.dev/documentation/cupertino_ui/latest/cupertino_ui/CupertinoTabScaffold-class.html)

  Creates the characteristic iOS bottom tab bar.

### Container

[#](#container)

Many layouts make liberal use of [`Container`](https://api.flutter.dev/flutter/widgets/Container-class.html)s to separate widgets using padding, or to add borders or margins. You can change the device's background by placing the entire layout into a `Container` and changing its background color or image.

#### Summary (Container)

[#](#summary-container)

* Add padding, margins, borders
* Change background color or image
* Contains a single child widget, but that child can be a `Row`, `Column`, or even the root of a widget tree

#### Examples (Container)

[#](#examples-container)

This layout consists of a column with two rows, each containing 2 images. A [`Container`](https://api.flutter.dev/flutter/widgets/Container-class.html) is used to change the background color of the column to a lighter grey.

dart

```
Widget _buildImageColumn() {
  return Container(
    decoration: const BoxDecoration(color: Colors.black26),
    child: Column(children: [_buildImageRow(1), _buildImageRow(3)]),
  );
}
```

A `Container` is also used to add a rounded border and margins to each image:

dart

```
Widget _buildDecoratedImage(int imageIndex) => Expanded(
  child: Container(
    decoration: BoxDecoration(
      border: Border.all(width: 10, color: Colors.black38),
      borderRadius: const BorderRadius.all(Radius.circular(8)),
    ),
    margin: const EdgeInsets.all(4),
    child: Image.asset('images/pic$imageIndex.jpg'),
  ),
);
​
Widget _buildImageRow(int imageIndex) => Row(
  children: [
    _buildDecoratedImage(imageIndex),
    _buildDecoratedImage(imageIndex + 1),
  ],
);
```

You can find more `Container` examples in the [tutorial](/ui/layout/tutorial).

**App source:** [container](https://github.com/flutter/website/tree/main/examples/layout/container)

***

### GridView

[#](#gridview)

Use [`GridView`](https://api.flutter.dev/flutter/widgets/GridView-class.html) to lay widgets out as a two-dimensional list. `GridView` provides two pre-fabricated lists, or you can build your own custom grid. When a `GridView` detects that its contents are too long to fit the render box, it automatically scrolls.

#### Summary (GridView)

[#](#summary-gridview)

* Lays widgets out in a grid

* Detects when the column content exceeds the render box and automatically provides scrolling

* Build your own custom grid, or use one of the provided grids:

  * `GridView.count` allows you to specify the number of columns
  * `GridView.extent` allows you to specify the maximum pixel width of a tile

#### Examples (GridView)

[#](#examples-gridview)

Uses `GridView.extent` to create a grid with tiles a maximum 150 pixels wide.

**App source:** [grid\_and\_list](https://github.com/flutter/website/tree/main/examples/layout/grid_and_list)

Uses `GridView.count` to create a grid that's 2 tiles wide in portrait mode, and 3 tiles wide in landscape mode. The titles are created by setting the `footer` property for each [`GridTile`](https://pub.dev/documentation/material_ui/latest/material_ui/GridTile-class.html).

**Dart code:** [`grid_list_demo.dart`](https://github.com/flutter/website/tree/main/examples/layout/gallery/lib/grid_list_demo.dart)

dart

```
Widget _buildGrid() => GridView.extent(
  maxCrossAxisExtent: 150,
  padding: const EdgeInsets.all(4),
  mainAxisSpacing: 4,
  crossAxisSpacing: 4,
  children: _buildGridTileList(30),
);
​
// The images are saved with names pic0.jpg, pic1.jpg...pic29.jpg.
// The List.generate() constructor allows an easy way to create
// a list when objects have a predictable naming pattern.
List<Widget> _buildGridTileList(int count) =>
    List.generate(count, (i) => Image.asset('images/pic$i.jpg'));
```

***

### ListView

[#](#listview)

[`ListView`](https://api.flutter.dev/flutter/widgets/ListView-class.html), a column-like widget, automatically provides scrolling when its content is too long for its render box.

#### Summary (ListView)

[#](#summary-listview)

* A specialized [`Column`](https://api.flutter.dev/flutter/widgets/Column-class.html) for organizing a list of boxes
* Can be laid out horizontally or vertically
* Detects when its content won't fit and provides scrolling
* Less configurable than `Column`, but easier to use and supports scrolling

#### Examples (ListView)

[#](#examples-listview)

Uses `ListView` to display a list of businesses using `ListTile`s. A `Divider` separates the theaters from the restaurants.

**App source:** [grid\_and\_list](https://github.com/flutter/website/tree/main/examples/layout/grid_and_list)

Uses `ListView` to display the [`Colors`](https://pub.dev/documentation/material_ui/latest/material_ui/Colors-class.html) from the [Material 2 Design palette](https://m2.material.io/design/color/the-color-system.html#tools-for-picking-colors) for a particular color family.

**Dart code:** [`colors_demo.dart`](https://github.com/flutter/website/tree/main/examples/layout/gallery/lib/colors_demo.dart)

dart

```
Widget _buildList() {
  return ListView(
    children: [
      _tile('CineArts at the Empire', '85 W Portal Ave', Icons.theaters),
      _tile('The Castro Theater', '429 Castro St', Icons.theaters),
      _tile('Alamo Drafthouse Cinema', '2550 Mission St', Icons.theaters),
      _tile('Roxie Theater', '3117 16th St', Icons.theaters),
      _tile(
        'United Artists Stonestown Twin',
        '501 Buckingham Way',
        Icons.theaters,
      ),
      _tile('AMC Metreon 16', '135 4th St #3000', Icons.theaters),
      const Divider(),
      _tile('K\'s Kitchen', '757 Monterey Blvd', Icons.restaurant),
      _tile('Emmy\'s Restaurant', '1923 Ocean Ave', Icons.restaurant),
      _tile('Chaiya Thai Restaurant', '272 Claremont Blvd', Icons.restaurant),
      _tile('La Ciccia', '291 30th St', Icons.restaurant),
    ],
  );
}
​
ListTile _tile(String title, String subtitle, IconData icon) {
  return ListTile(
    title: Text(
      title,
      style: const TextStyle(fontWeight: FontWeight.w500, fontSize: 20),
    ),
    subtitle: Text(subtitle),
    leading: Icon(icon, color: Colors.blue[500]),
  );
}
```

***

### Stack

[#](#stack)

Use [`Stack`](https://api.flutter.dev/flutter/widgets/Stack-class.html) to arrange widgets on top of a base widget—often an image. The widgets can completely or partially overlap the base widget.

#### Summary (Stack)

[#](#summary-stack)

* Use for widgets that overlap another widget
* The first widget in the list of children is the base widget; subsequent children are overlaid on top of that base widget
* A `Stack`'s content can't scroll
* You can choose to clip children that exceed the render box

#### Examples (Stack)

[#](#examples-stack)

Uses `Stack` to overlay a `Container` (that displays its `Text` on a translucent black background) on top of a `CircleAvatar`. The `Stack` offsets the text using the `alignment` property and `Alignment`s.

**App source:** [card\_and\_stack](https://github.com/flutter/website/tree/main/examples/layout/card_and_stack)

Uses `Stack` to overlay an icon on top of an image.

**Dart code:** [`bottom_navigation_demo.dart`](https://github.com/flutter/website/tree/main/examples/layout/gallery/lib/bottom_navigation_demo.dart)

dart

```
Widget _buildStack() {
  return Stack(
    alignment: const Alignment(0.6, 0.6),
    children: [
      const CircleAvatar(
        backgroundImage: AssetImage('images/pic.jpg'),
        radius: 100,
      ),
      Container(
        decoration: const BoxDecoration(color: Colors.black45),
        child: const Text(
          'Mia B',
          style: TextStyle(
            fontSize: 20,
            fontWeight: FontWeight.bold,
            color: Colors.white,
          ),
        ),
      ),
    ],
  );
}
```

***

### Card

[#](#card)

A [`Card`](https://pub.dev/documentation/material_ui/latest/material_ui/Card-class.html), from the [Material library](https://pub.dev/documentation/material_ui/latest/material_ui), contains related nuggets of information and can be composed of almost any widget, but is often used with [`ListTile`](https://pub.dev/documentation/material_ui/latest/material_ui/ListTile-class.html). `Card` has a single child, but its child can be a column, row, list, grid, or other widget that supports multiple children. By default, a `Card` shrinks its size to 0 by 0 pixels. You can use [`SizedBox`](https://api.flutter.dev/flutter/widgets/SizedBox-class.html) to constrain the size of a card.

In Flutter, a `Card` features slightly rounded corners and a drop shadow, giving it a 3D effect. Changing a `Card`'s `elevation` property allows you to control the drop shadow effect. Setting the elevation to 24, for example, visually lifts the `Card` further from the surface and causes the shadow to become more dispersed. For a list of supported elevation values, see [Elevation](https://m3.material.io/styles/elevation) in the [Material guidelines](https://m3.material.io/styles). Specifying an unsupported value disables the drop shadow entirely.

#### Summary (Card)

[#](#summary-card)

* Implements a [Material card](https://m3.material.io/components/cards)
* Used for presenting related nuggets of information
* Accepts a single child, but that child can be a `Row`, `Column`, or other widget that holds a list of children
* Displayed with rounded corners and a drop shadow
* A `Card`'s content can't scroll
* From the [Material library](https://pub.dev/documentation/material_ui/latest/material_ui)

#### Examples (Card)

[#](#examples-card)

A `Card` containing 3 ListTiles and sized by wrapping it with a `SizedBox`. A `Divider` separates the first and second `ListTiles`.

**App source:** [card\_and\_stack](https://github.com/flutter/website/tree/main/examples/layout/card_and_stack)

A `Card` containing an image and text.

**Dart code:** [`cards_demo.dart`](https://github.com/flutter/website/tree/main/examples/layout/gallery/lib/cards_demo.dart)

dart

```
Widget _buildCard() {
  return SizedBox(
    height: 210,
    child: Card(
      child: Column(
        children: [
          ListTile(
            title: const Text(
              '1625 Main Street',
              style: TextStyle(fontWeight: FontWeight.w500),
            ),
            subtitle: const Text('My City, CA 99984'),
            leading: Icon(Icons.restaurant_menu, color: Colors.blue[500]),
          ),
          const Divider(),
          ListTile(
            title: const Text(
              '(408) 555-1212',
              style: TextStyle(fontWeight: FontWeight.w500),
            ),
            leading: Icon(Icons.contact_phone, color: Colors.blue[500]),
          ),
          ListTile(
            title: const Text('costa@example.com'),
            leading: Icon(Icons.contact_mail, color: Colors.blue[500]),
          ),
        ],
      ),
    ),
  );
}
```

***

### ListTile

[#](#listtile)

Use [`ListTile`](https://pub.dev/documentation/material_ui/latest/material_ui/ListTile-class.html), a specialized row widget from the [Material library](https://pub.dev/documentation/material_ui/latest/material_ui), for an easy way to create a row containing up to 3 lines of text and optional leading and trailing icons. `ListTile` is most commonly used in [`Card`](https://pub.dev/documentation/material_ui/latest/material_ui/Card-class.html) or [`ListView`](https://api.flutter.dev/flutter/widgets/ListView-class.html), but can be used elsewhere.

#### Summary (ListTile)

[#](#summary-listtile)

* A specialized row that contains up to 3 lines of text and optional icons
* Less configurable than `Row`, but easier to use
* From the [Material library](https://pub.dev/documentation/material_ui/latest/material_ui)

#### Examples (ListTile)

[#](#examples-listtile)

A `Card` containing 3 `ListTile`s.

**App source:** [card\_and\_stack](https://github.com/flutter/website/tree/main/examples/layout/card_and_stack)

Uses `ListTile` with leading widgets.

**Dart code:** [`list_demo.dart`](https://github.com/flutter/website/tree/main/examples/layout/gallery/lib/list_demo.dart)

***

## Constraints

[#](#constraints)

To fully understand Flutter's layout system, you need to learn how Flutter positions and sizes the components in a layout. For more information, see [Understanding constraints](/ui/layout/constraints).

## Videos

[#](#videos)

The following videos, part of the [Flutter in Focus](https://www.youtube.com/watch?v=wgTBLj7rMPM\&list=PLjxrf2q8roU2HdJQDjJzOeO6J3FoFLWr2) series, explain `Stateless` and `Stateful` widgets.

[Watch on YouTube in a new tab: "How to create stateless widgets"](https://www.youtube.com/watch/wE7khGHVkYY)

[Watch on YouTube in a new tab: "How and when stateful widgets are best used"](https://www.youtube.com/watch/AqCMFXEmf3w)

[Flutter in Focus playlist](https://www.youtube.com/playlist?list=PLjxrf2q8roU2HdJQDjJzOeO6J3FoFLWr2)

***

Each episode of the [Widget of the Week series](https://www.youtube.com/playlist?list=PLjxrf2q8roU23XGwz3Km7sQZFTdB996iG) focuses on a widget. Several of them include layout widgets.

[Watch on YouTube in a new tab: "Introducing widget of the week"](https://www.youtube.com/watch/b_sQ9bMltGU)

[Flutter Widget of the Week playlist](https://www.youtube.com/playlist?list=PLjxrf2q8roU23XGwz3Km7sQZFTdB996iG)

## Other resources

[#](#other-resources)

The following resources might help when writing layout code.

* [Layout tutorial](/ui/layout/tutorial)

  Learn how to build a layout.

* [Widget catalog](/ui/widgets)

  Describes many of the widgets available in Flutter.

* [HTML/CSS Analogs in Flutter](/flutter-for/web-devs)

  For those familiar with web programming, this page maps HTML/CSS functionality to Flutter features.

* [API reference docs](https://api.flutter.dev/flutter)

  Reference documentation for all of the Flutter libraries.

* [Adding assets and images](/ui/assets/assets-and-images)

  Explains how to add images and other assets to your app's package.

* [Zero to One with Flutter](https://medium.com/@mravn/zero-to-one-with-flutter-43b13fd7b354)

  One person's experience writing their first Flutter app.

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-09-29. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/ui/layout/index.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/ui/layout\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/ui/layout/index.md "Report an issue with this page").

----
url: https://docs.flutter.dev/app-architecture/recommendations
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Architecture recommendations and resources](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

This page presents architecture best practices, why they matter, and whether we recommend them for your Flutter application. You should treat these recommendations as recommendations, and not steadfast rules, and you should adapt them to your app's unique requirements.

The best practices on this page have a priority, which reflects how strongly the Flutter team recommends it.

* **Strongly recommend:** You should always implement this recommendation if you're starting to build a new application. You should strongly consider refactoring an existing app to implement this practice unless doing so would fundamentally clash with your current approach.
* **Recommend**: This practice will likely improve your app.
* **Conditional**: This practice can improve your app in certain circumstances.

## Separation of concerns

[#](#separation-of-concerns)

You should separate your app into a UI layer and a data layer. Within those layers, you should further separate logic into classes by responsibility.

| Recommendation                                                               | Description                                                                                                                                                                                                                                                                                                                                                                                                                        |
| ---------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Use clearly defined data and UI layers.Strongly recommend                    | Separation of concerns is the most important architectural principle. The data layer exposes application data to the rest of the app, and contains most of the business logic in your application. The UI layer displays application data and listens for user events from users. The UI layer contains separate classes for UI logic and widgets.                                                                                 |
| Use the repository pattern in the data layer.Strongly recommend              | The repository pattern is a software design pattern that isolates the data access logic from the rest of the application. It creates an abstraction layer between the application's business logic and the underlying data storage mechanisms (databases, APIs, file systems, etc.). In practice, this means creating Repository classes and Service classes.                                                                      |
| Use ViewModels and Views in the UI layer. (MVVM)Strongly recommend           | Separation of concerns is the most important architectural principle. This particular separation makes your code much less error prone because your widgets remain "dumb".                                                                                                                                                                                                                                                         |
| Use `ChangeNotifiers` and `Listenables` to handle widget updates.Conditional | The `ChangeNotifier` API is part of the Flutter SDK, and is a convenient way to have your widgets observe changes in your ViewModels.There are many options to handle state-management, and ultimately the decision comes down to personal preference. Read about [our ChangeNotifier recommendation](/get-started/fwe/state-management) or [other popular options](https://docs.flutter.dev/data-and-backend/state-mgmt/options). |
| Do not put logic in widgets.Strongly recommend                               | Logic should be encapsulated in methods on the ViewModel. The only logic a view should contain is:- Simple if-statements to show and hide widgets based on a flag or nullable field in the ViewModel
- Animation logic that relies on the widget to calculate
- Layout logic based on device information, like screen size or orientation.
- Simple routing logic                                                                  |
| Use a domain layer.Conditional                                               | A domain layer is only needed if your application has exceeding complex logic that crowds your ViewModels, or if you find yourself repeating logic in ViewModels. In very large apps, use-cases are useful, but in most apps they add unnecessary overhead.Use in apps with complex logic requirements.                                                                                                                            |

## Handling data

[#](#handling-data)

Handling data with care makes your code easier to understand, less error prone, and prevents malformed or unexpected data from being created.

| Recommendation                                                          | Description                                                                                                                                                                                                                                                                                                                                                                                              |
| ----------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Use unidirectional data flow\.Strongly recommend                        | Data updates should only flow from the data layer to the UI layer. Interactions in the UI layer are sent to the data layer where they're processed.                                                                                                                                                                                                                                                      |
| Use `Commands` to handle events from user interaction.Recommend         | Commands prevent rendering errors in your app, and standardize how the UI layer sends events to the data layer. Read about commands in the [architecture case study](/app-architecture/guide).                                                                                                                                                                                                           |
| Use immutable data models.Strongly recommend                            | Immutable data is crucial in ensuring that any necessary changes occur only in the proper place, usually the data or domain layer. Because immutable objects can't be modified after creation, you must create a new instance to reflect changes. This process prevents accidental updates in the UI layer and supports a clear, unidirectional data flow.                                               |
| Use freezed or built\_value to generate immutable data models.Recommend | You can use packages to help generate useful functionality in your data models, [freezed](https://pub.dev/packages/freezed) or [built\_value](https://pub.dev/packages/built_value). These can generate common model methods like JSON ser/des, deep equality checking and copy methods. These code generation packages can add significant build time to your applications if you have a lot of models. |
| Create separate API models and domain models.Conditional                | Using separate models adds verbosity, but prevents complexity in ViewModels and use-cases.Use in large apps.                                                                                                                                                                                                                                                                                             |

## App structure

[#](#app-structure)

Well organized code benefits both the health of the app itself, and the team working on the code.

| Recommendation                                                                   | Description                                                                                                                                                                                                                                                                                                                                                                                                                   |
| -------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Use dependency injection.Strongly recommend                                      | Dependency injection prevents your app from having globally accessible objects, which makes your code less error prone. We recommend you use the [provider](https://pub.dev/packages/provider) package to handle dependency injection.                                                                                                                                                                                        |
| Use [go\_router](https://pub.dev/packages/go_router) for navigation.Recommend    | Go\_router is the preferred way to write 90% of Flutter applications. There are some specific use-cases that go\_router doesn't solve, in which case you can use the [Flutter Navigator API](https://docs.flutter.dev/ui/navigation) directly or try other packages found on [pub.dev](https://pub.dev).                                                                                                                      |
| Use standardized naming conventions for classes, files and directories.Recommend | We recommend naming classes for the architectural component they represent. For example, you may have the following classes:- HomeViewModel
- HomeScreen
- UserRepository
- ClientApiServiceFor clarity, we do not recommend using names that can be confused with objects from the Flutter SDK. For example, you should put your shared widgets in a directory called `ui/core/`, rather than a directory called `/widgets`. |
| Use abstract repository classesStrongly recommend                                | Repository classes are the sources of truth for all data in your app, and facilitate communication with external APIs. Creating abstract repository classes allows you to create different implementations, which can be used for different app environments, such as "development" and "staging".                                                                                                                            |

## Testing

[#](#testing)

Good testing practices makes your app flexible. It also makes it straightforward and low risk to add new logic and new UI.

| Recommendation                                                                           | Description                                                                                                                                                                                                                                                                               |
| ---------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Test architectural components separately, and together.Strongly recommend                | * Write unit tests for every service, repository and ViewModel class. These tests should test the logic of every method individually.
* Write widget tests for views. Testing routing and dependency injection are particularly important.                                                |
| Make fakes for testing (and write code that takes advantage of fakes.)Strongly recommend | Fakes aren't concerned with the inner workings of any given method as much as they're concerned with inputs and outputs. If you have this in mind while writing application code, you're forced to write modular, lightweight functions and classes with well defined inputs and outputs. |

[]()

## Recommended resources

[#](#resources)

* Code and templates

  * [Compass app source code](https://github.com/flutter/samples/tree/main/compass_app) - Source code of a full-featured, robust Flutter application that implements many of these recommendations.
  * [very\_good\_cli](https://cli.vgv.dev/) - A Flutter application template made by the Flutter experts Very Good Ventures. This template generates a similar app structure.

* Documentation
  * [Very Good Engineering architecture documentation](https://engineering.verygood.ventures/architecture/architecture/) - Very Good Engineering is a documentation site by VGV that has technical articles, demos, and open-sourced projects. It includes documentation on architecting Flutter applications.

* Tooling

  * [Flutter developer tools](/tools/devtools) - DevTools is a suite of performance and debugging tools for Dart and Flutter.
  * [flutter\_lints](https://pub.dev/packages/flutter_lints) - A package that contains the lints for Flutter apps recommended by the Flutter team. Use this package to encourage good coding practices across a team.

## Feedback

[#](#feedback)

As this section of the website is evolving, we [welcome your feedback](https://google.qualtrics.com/jfe/form/SV_4T0XuR9Ts29acw6?page=%22recommendations%22)!

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/app-architecture/recommendations.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/app-architecture/recommendations\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/app-architecture/recommendations.md "Report an issue with this page").

----
url: https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.49.0
----

The 2.49.0 release of the Dart and Flutter DevTools includes the following changes among other general improvements. To learn more about DevTools, check out the [DevTools overview](/tools/devtools/overview).

## General updates

[#](#general-updates)

* Fixed a bug preventing the buttons at the top of an embedded DevTools screen from being clicked under certain circumstances. - [#9327](https://github.com/flutter/devtools/pull/9327)

## Full commit history

[#](#full-commit-history)

To find a complete list of changes in this release, check out the [DevTools git log](https://github.com/flutter/devtools/tree/v2.49.0).

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/release-notes/release-notes-2.49.0.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.49.0\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/release-notes/release-notes-2.49.0.md "Report an issue with this page").

----
url: https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.34.1
----

The 2.34.1 release of the Dart and Flutter DevTools includes the following changes among other general improvements. To learn more about DevTools, check out the [DevTools overview](/tools/devtools).

## General updates

[#](#general-updates)

* Fixed an issue preventing DevTools from connecting to Flutter apps that are not launched from Flutter Tools. - [#6848](https://github.com/flutter/devtools/issues/6848)
* Improved performance of the FlatTable. - [#7391](https://github.com/flutter/devtools/pull/7391)

## Inspector updates

[#](#inspector-updates)

* Fixes an edge case where widgets from other packages could show up in the inspector tree. - [#7353](https://github.com/flutter/devtools/pull/7353)

## Performance updates

[#](#performance-updates)

* Add a setting to include CPU samples in the Timeline. - [#7333](https://github.com/flutter/devtools/pull/7333), [#7369](https://github.com/flutter/devtools/pull/7369)

* Removed the legacy trace viewer. The legacy trace viewer was replaced with the embedded Perfetto trace viewer in DevTools version 2.21.1, but was available behind a setting to ensure a smooth rollout. This release of DevTools removes the legacy trace viewer entirely. - [#7316](https://github.com/flutter/devtools/pull/7316)

* Updated the Perfetto trace viewer build. - [#7445](https://github.com/flutter/devtools/pull/7445), [#7456](https://github.com/flutter/devtools/pull/7456), [#7480](https://github.com/flutter/devtools/pull/7480)

* Added a loading message to show when refreshing the timeline. - [#7463](https://github.com/flutter/devtools/pull/7463)

## Memory updates

[#](#memory-updates)

* Enabled export of snapshots and improved snapshotting performance. - [#7197](https://github.com/flutter/devtools/pull/7197), [#7439](https://github.com/flutter/devtools/pull/7439), [#7449](https://github.com/flutter/devtools/pull/7449)

* Fixed failures during disconnect in tracing. - [#7440](https://github.com/flutter/devtools/pull/7440)

* Made class filter shared between the panes `Profile Memory` and `Diff Snapshots`. - [#7462](https://github.com/flutter/devtools/pull/7462)

## Network profiler updates

[#](#network-profiler-updates)

* Improved Network profiler performance. - [#7266](https://github.com/flutter/devtools/pull/7266)
* Fixed a bug where selected pending requests weren't refreshing the tab once updated. - [#7266](https://github.com/flutter/devtools/pull/7266)
* Fixed the JSON viewer so multiline strings are visible in their row, and through a tooltip. - [#7389](https://github.com/flutter/devtools/pull/7389)
* Fixed JsonViewer where all of the expanded sections would snap closed. [#7367](https://github.com/flutter/devtools/pull/7367)

## Deep Links tool updates

[#](#deep-links-tool-updates)

* Automatically populate a list of Flutter projects from the connected IDE. - [#7415](https://github.com/flutter/devtools/pull/7415), [#7431](https://github.com/flutter/devtools/pull/7431)

## Full commit history

[#](#full-commit-history)

To find a complete list of changes in this release, check out the [DevTools git log](https://github.com/flutter/devtools/tree/v2.34.1).

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/release-notes/release-notes-2.34.1.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.34.1\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/release-notes/release-notes-2.34.1.md "Report an issue with this page").

----
url: https://docs.flutter.dev/resources/support.md
----

\# Flutter support > Where can you get support when developing with Flutter. Welcome! There are many different ways to get support when developing with Flutter. As Flutter is an open source project, your options range from filing issues when you run into bugs in the software, to connecting with your local community, to browsing and identifying a list of commercial agencies and consultants who offer support services. ## I want to report an issue with Flutter Visit how to \[create a useful bug report]\(/resources/bug-reports). Note that filing bugs is \_not\_ an efficient way to get tech support. ## I want to ask questions about how to use Flutter Your best bet is to consult \[Flutter Forum]\(https\://forum.itsallwidgets.com/), \[Stack Overflow]\(https\://stackoverflow\.com/questions/tagged/flutter), or \[Discord]\(https\://discord.com/invite/rflutterdev). While the community is active in these channels, there is no official SLA on responding to new posts or chats in these channels. ## I want professional services support with Flutter Check out the \[Flutter consultants]\(https\://flutter.dev/consultants) directory. ## I want support from a Flutter expert Google has a cadre of \[Google Developer Experts]\[] (GDEs), non-Google employees with expertise in various areas of Google technologies, such as Flutter and Dart. GDEs are \_not\_ free consultants, but they often give lectures, host meetups, run workshops, or make videos. As part of the Dart and Flutter world-wide community, they are a great resource. \[Google Developer Experts]: https\://developers.google.com/community/experts/directory?specialization=dart%2Cflutter ## I want to get paid support from Google or the Flutter project As an open source project, we're not set up to offer paid support. We recommend checking out the \[consultancy program]\(https\://flutter.dev/consultants).

----
url: https://docs.flutter.dev/ai/get-started
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Get started developing with AI](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

AI coding assistants can accelerate your Flutter development workflow by writing code, fixing errors, and building complete features.

By default, general-purpose AI assistants might not have the latest context on Flutter patterns, project diagnostics, or third-party packages. Installing the official Flutter agent plugin equips your assistant with dedicated tools and recipes tailored for Flutter and Dart development.

## How Flutter AI plugins work

[#](#how-flutter-ai-plugins-work)

An **agent plugin** bundles the tools and knowledge that an AI assistant needs to understand and modify your Flutter codebase.

When you install an official Flutter agent plugin, it connects your assistant to four core capabilities:

* **[Agent skills](/ai/tools#agent-skills)**: on-demand procedural guides from the official Flutter and Dart repositories that teach the assistant how to perform specific tasks, such as creating responsive layouts, managing state, or writing widget tests.
* **[Agent rules](/ai/tools#agent-rules)**: persistent instructions that guide an assistant's responses, such as proactively triggering stateful hot reload when editing Flutter widgets.
* **[Dart and Flutter MCP server](/ai/tools#dart-and-flutter-mcp-server)**: a Model Context Protocol (MCP) server that connects the assistant to the Dart SDK, giving it real-time access to analyzer diagnostics, symbol resolution, test runners, and runtime inspection.
* **[Specialized agents](/ai/tools#specialized-agents)**: focused assistant personas tailored for dedicated workflows, such as the Flutter Accessibility (`a11y`) agent for automated accessibility audits and code fixes.

These tools work together automatically: the assistant uses MCP tools to query live project state and static analysis, while using agent skills to guide its coding strategies and best practices.

In addition to core Flutter plugins, you can also equip your assistant with skills that are shipped directly by third-party `pub.dev` packages using [package skills](https://dart.dev/ai/package-skills).

To learn more about the underlying architecture and capabilities of each tool, check out [How Flutter AI tools work](/ai/tools).

## Choose your AI coding agent

[#](#choose-your-ai-coding-agent)

Select your agent below for instructions on how to install official plugins and configure tools for Flutter development.

* [Antigravity](#223-tab-panel)
* [Claude Code](#224-tab-panel)
* [Cursor](#225-tab-panel)
* [Codex](#226-tab-panel)
* [GitHub Copilot](#227-tab-panel)
* [Other](#228-tab-panel)

[Antigravity](https://antigravity.google/docs/build-with-google/#dart-and-flutter) is an agentic development platform by Google that includes the Antigravity IDE, IDE extensions (for VS Code and other editors), and the Antigravity CLI.

**Antigravity IDE and extensions**

Equip the Antigravity IDE or Antigravity extension (such as in VS Code) with official Dart and Flutter tools:

1. Open **Settings** in Antigravity by clicking the gear icon or pressing `Cmd/Ctrl` + `,`.
2. Click the **Customizations** tab.
3. In the **Build with Google Plugins** section, click **Customize**.
4. Click **Download** next to the **Dart and Flutter** integration.

To verify the installation, open the **Agent** panel (`Cmd/Ctrl` + `L`) and ask the agent:

```
Summarize the Dart and Flutter tools and skills available in this project.
```

**Antigravity CLI**

The Antigravity CLI runs as the `agy` command in your terminal.

1. Configure the Dart and Flutter MCP server in your project's `.agents/mcp_config.json` file (or globally in `~/.gemini/config/mcp_config.json`):

   json

   ```
   {
     "mcpServers": {
       "dart": {
         "command": "dart",
         "args": ["mcp-server"]
       }
     }
   }
   ```

2. Install official Flutter and Dart skills into your workspace:

   bash

   ```
   # Install Flutter skills
   npx skills add flutter/agent-plugins --skill '*' --agent universal --yes

   # Install Dart skills
   npx skills add dart-lang/skills --skill '*' --agent universal --yes
   ```

3. Start an interactive session in your project root:

   bash

   ```
   agy
   ```

[Claude Code](https://code.claude.com/) is an agentic coding assistant from Anthropic that runs in your terminal.

**Install the official plugin**

Equip Claude Code with official Flutter and Dart skills and MCP configuration:

1. Add the marketplace for Claude Code plugins:

   bash

   ```
   claude plugin marketplace add flutter/agent-plugins
   ```

2. Install the Flutter and Dart plugin:

   bash

   ```
   claude plugin install dart-flutter@dart-flutter
   ```

**Configure agent rules**

Claude Code plugins discover skills and MCP servers, but don't automatically load plugin rules. To equip Claude Code with official Flutter rules, add the rules you want from the [official rules repository](https://github.com/flutter/agent-plugins/tree/main/rules) to your project's `CLAUDE.md` file.

**Verify installation**

Verify that the plugin is active in Claude Code:

bash

```
claude plugin marketplace list
```

[Cursor](https://cursor.com/) is an AI-powered code editor built on top of VS Code.

**Install the official plugin**

Equip Cursor with official Flutter and Dart skills and MCP configuration using any of the following methods:

* Visit the [Dart and Flutter plugin](https://cursor.com/marketplace/flutter) on the Cursor Marketplace and select **Add to Cursor**.
* In Cursor, open **Customize**, then search for and add **Dart and Flutter**.
* In a Cursor chat session, run `/add-plugin dart-flutter`.

Cursor automatically discovers the bundled skills and configures the Dart and Flutter MCP server.

**Configure agent rules**

Cursor supports file-scoped rules in the `.cursor/rules/` directory using `.mdc` files. To equip Cursor with official Flutter rules, download the `.mdc` files from the [official rules repository](https://github.com/flutter/agent-plugins/tree/main/rules) into your project's `.cursor/rules/` directory.

[Codex](https://chatgpt.com/codex) is an agentic coding assistant designed for terminal and IDE workflows.

**Install the official plugin**

Equip Codex with official Flutter and Dart skills and MCP configuration:

1. Add the Dart and Flutter marketplace for Codex plugins:

   bash

   ```
   codex plugin marketplace add flutter/agent-plugins
   ```

2. Install the Dart and Flutter plugin:

   bash

   ```
   codex plugin add dart-flutter@dart-flutter
   ```

**Configure agent rules**

Codex plugins install skills and MCP servers, but don't automatically bundle rules. To equip Codex with official Flutter rules, download the `.md` rule files from the [official rules repository](https://github.com/flutter/agent-plugins/tree/main/rules) into your project's `.agent/rules/` directory, or append the rule instructions to `CODEX.md`.

**Verify installation**

Verify that the plugin is active in Codex:

bash

```
codex plugin list
```

[GitHub Copilot](https://github.com/features/copilot) in VS Code supports agentic coding, tool calling via MCP, and custom workspace instructions.

**Configure the MCP server**

1. Ensure the **Model Context Protocol (MCP)** extension is enabled in VS Code.

2. Create or open `.vscode/mcp.json` in your workspace and add the Dart MCP server:

   json

   ```
   {
     "servers": {
       "dart": {
         "command": "dart",
         "args": ["mcp-server"]
       }
     }
   }
   ```

**Install agent skills**

In your Flutter project root, install official skills using the universal flag:

bash

```
# Install Flutter skills
npx skills add flutter/agent-plugins --skill '*' --agent universal --yes

# Install Dart skills
npx skills add dart-lang/skills --skill '*' --agent universal --yes
```

**Configure agent rules**

GitHub Copilot supports custom workspace instructions in `.github/copilot-instructions.md`. To equip Copilot with official Flutter rules, add the rules you want from the [official rules repository](https://github.com/flutter/agent-plugins/tree/main/rules) to your project's `.github/copilot-instructions.md` file.

If you're using another coding assistant (such as Windsurf, Zed, or Cline) that supports the Model Context Protocol (MCP) or Agent Skills specification, configure the tools manually.

**Configure the MCP server**

Most MCP-compatible clients use a standard JSON configuration format. Add the following entry to your client's MCP configuration file:

json

```
{
  "mcpServers": {
    "dart": {
      "command": "dart",
      "args": ["mcp-server"]
    }
  }
}
```

For client-specific setup guides, command-line arguments, and troubleshooting, check out the [Dart and Flutter MCP server repository](https://github.com/dart-lang/ai/tree/main/pkgs/dart_mcp_server).

**Install agent skills**

Use the `skills` CLI to download skills into your workspace's standard `.agents/skills` directory:

bash

```
# Install Flutter skills
npx skills add flutter/agent-plugins --skill '*' --agent universal --yes

# Install Dart skills
npx skills add dart-lang/skills --skill '*' --agent universal --yes
```

**Configure agent rules**

If your coding assistant supports project instructions or rules (such as Windsurf `.windsurfrules` or Cline `.clinerules`), copy or download rules from the [official rules repository](https://github.com/flutter/agent-plugins/tree/main/rules) into your assistant's configuration file.

## Next steps

[#](#next-steps)

* To learn more about how skills and tools interact, check out [How Flutter AI tools work](/ai/tools).
* To discover and install skills from dependencies, refer to [Package skills](https://dart.dev/ai/package-skills).
* To publish skills with your own packages, refer to [Ship skills with packages](https://dart.dev/tools/pub/package-skills).
* To give your assistant search access to official Flutter and Dart documentation, connect to the [Developer Knowledge MCP server](https://developers.google.com/knowledge/mcp).
* To explore the official plugins, check out the [flutter/agent-plugins](https://github.com/flutter/agent-plugins) repository.

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-09-14. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/ai/get-started.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/ai/get-started\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/ai/get-started.md "Report an issue with this page").

----
url: https://docs.flutter.dev/platform-integration/ios/apple-frameworks
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Leveraging Apple's system APIs and frameworks](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

When you come from iOS development, you might need to find Flutter plugins that offer the same abilities as Apple's system libraries. This might include accessing device hardware or interacting with specific frameworks like `HealthKit`.

For an overview of how the SwiftUI framework compares to Flutter, see [Flutter for SwiftUI developers](/flutter-for/swiftui-devs).

## Introducing Flutter plugins

[#](#introducing-flutter-plugins)

Dart calls libraries that contain platform-specific code *plugins*, short for "plugin package". When developing an app with Flutter, you use *plugins* to interact with system libraries.

In your Dart code, you use the plugin's Dart API to call the native code from the system library being used. This means that you can write the code to call the Dart API. The API then makes it work for all platforms that the plugin supports.

To learn more about plugins, see [Using packages](/packages-and-plugins/using-packages). Though this page links to some popular plugins, you can find thousands more, along with examples, on [pub.dev](https://pub.dev/packages). The following table does not endorse any particular plugin. If you can't find a package that meets your needs, you can create your own or use platform channels directly in your project. To learn more, check out [Writing platform-specific code](/platform-integration/platform-channels).

## Adding a plugin to your project

[#](#adding-a-plugin-to-your-project)

To use an Apple framework within your native project, import it into your Swift or Objective-C file.

To add a Flutter plugin, run `flutter pub add package_name` from the root of your project. This adds the dependency to your [`pubspec.yaml`](/tools/pubspec) file. After you add the dependency, add an `import` statement for the package in your Dart file.

You might need to change app settings or initialization logic. If that's needed, the package's "Readme" page on [pub.dev](https://pub.dev/packages) should provide details.

### Flutter plugins and Apple frameworks

[#](#flutter-plugins-and-apple-frameworks)

| Use Case                                       | Apple Framework or Class                                                              | Flutter integration                                                            |
| ---------------------------------------------- | ------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------ |
| Access the photo library                       | `PhotoKit` using the `Photos` and `PhotosUI `frameworks and `UIImagePickerController` | [`image_picker`](https://pub.dev/packages/image_picker)                        |
| Access the camera                              | `UIImagePickerController` using the `.camera` `sourceType`                            | [`image_picker`](https://pub.dev/packages/image_picker)                        |
| Use advanced camera features                   | `AVFoundation`                                                                        | [`camera`](https://pub.dev/packages/camera)                                    |
| Offer In-app purchases                         | `StoreKit`                                                                            | [`in_app_purchase`](https://pub.dev/packages/in_app_purchase) [1](#fn-1)       |
| Process payments                               | `PassKit`                                                                             | [`pay`](https://pub.dev/packages/pay) [2](#fn-2)                               |
| Send push notifications                        | `UserNotifications`                                                                   | [`firebase_messaging`](https://pub.dev/packages/firebase_messaging) [3](#fn-3) |
| Access GPS coordinates                         | `CoreLocation`                                                                        | [`geolocator`](https://pub.dev/packages/geolocator)                            |
| Access sensor data[4](#fn-4)                   | `CoreMotion`                                                                          | [`sensors_plus`](https://pub.dev/packages/sensors_plus)                        |
| Make network requests                          | `URLSession`                                                                          | [`http`](https://pub.dev/packages/http)                                        |
| Store key-values                               | `@AppStorage` property wrapper and `NSUserDefaults`                                   | [`shared_preferences`](https://pub.dev/packages/shared_preferences)            |
| Persist to a database                          | `CoreData` or SQLite                                                                  | [`sqflite`](https://pub.dev/packages/sqflite)                                  |
| Access health data                             | `HealthKit`                                                                           | [`health`](https://pub.dev/packages/health)                                    |
| Use machine learning                           | `CoreML`                                                                              | [`google_ml_kit`](https://pub.dev/packages/google_ml_kit) [5](#fn-5)           |
| Recognize text                                 | `VisionKit`                                                                           | [`google_ml_kit`](https://pub.dev/packages/google_ml_kit) [5](#fn-5)           |
| Recognize speech                               | `Speech`                                                                              | [`speech_to_text`](https://pub.dev/packages/speech_to_text)                    |
| Use augmented reality                          | `ARKit`                                                                               | [`arkit_plugin`](https://pub.dev/packages/arkit_plugin)                        |
| Access weather data                            | `WeatherKit`                                                                          | [`weather`](https://pub.dev/packages/weather) [6](#fn-6)                       |
| Access and manage contacts                     | `Contacts`                                                                            | [`flutter_contacts`](https://pub.dev/packages/flutter_contacts)                |
| Expose quick actions on the home screen        | `UIApplicationShortcutItem`                                                           | [`quick_actions`](https://pub.dev/packages/quick_actions)                      |
| Configure, update and communicate with Widgets | `WidgetKit`                                                                           | [`home_widget`](https://pub.dev/packages/home_widget)                          |
| Automate app actions with Siri/Shortcuts       | `AppIntents`                                                                          | [`intelligence`](https://pub.dev/packages/intelligence)                        |

1. Supports both Google Play Store on Android and Apple App Store on iOS. [↩](#fnref-1)

2. Adds Google Pay payments on Android and Apple Pay payments on iOS. [↩](#fnref-2)

3. Uses Firebase Cloud Messaging and integrates with APNs. [↩](#fnref-3)

4. Includes sensors like accelerometer, gyroscope, etc. [↩](#fnref-4)

5. Uses Google's ML Kit and supports various features like text recognition, face detection, image labeling, landmark recognition, and barcode scanning. You can also create a custom model with Firebase. To learn more, see [Use a custom TensorFlow Lite model with Flutter](https://firebase.google.com/docs/ml/flutter/use-custom-models). [↩](#fnref-5) [↩2](#fnref-5-2)

6. Uses the [OpenWeatherMap API](https://openweathermap.org/api). Other packages exist that can pull from different weather APIs. [↩](#fnref-6)

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-07-21. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/platform-integration/ios/apple-frameworks.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/platform-integration/ios/apple-frameworks\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/platform-integration/ios/apple-frameworks.md "Report an issue with this page").

----
url: https://docs.flutter.dev/cookbook/design/cupertino-sheets
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Display a Cupertino sheet](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

A Cupertino sheet is an iOS-style modal bottom sheet used to present content or options. It slides up from the bottom of the screen and can be pulled down to dismiss.

In Flutter, this is the job of [`showCupertinoSheet`](https://pub.dev/documentation/cupertino_ui/latest/cupertino_ui/showCupertinoSheet.html). This recipe implements a Cupertino sheet using the following steps:

1. Create a `CupertinoApp` or `MaterialApp`.
2. Display the sheet content.

## 1. Create a `CupertinoApp`

[#](#1-create-a-cupertinoapp)

When creating apps that follow the iOS design guidelines, you can use `CupertinoApp`. The following example provides a button in the center of the screen that triggers the modal.

dart

```
class CupertinoSheetDemo extends StatelessWidget {
  const CupertinoSheetDemo({super.key});
​
  @override
  Widget build(BuildContext context) {
    return const CupertinoApp(
      title: 'CupertinoSheet Demo',
      home: CupertinoSheetPage(),
    );
  }
}
```

## 2. Display the sheet content

[#](#2-display-the-sheet-content)

With the basic app structure in place, display the sheet. To show it, call `showCupertinoSheet` and provide a `scrollableBuilder` that returns the content for the sheet, such as a `SingleChildScrollView`.

dart

```
showCupertinoSheet<void>(
  context: context,
  scrollableBuilder: (context, scrollController) {
    return SingleChildScrollView(
      controller: scrollController,
      child: Center(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            const SizedBox(height: 100),
            const Text('This is a Cupertino sheet'),
            const SizedBox(height: 20),
            CupertinoButton(
              child: const Text('Close'),
              onPressed: () {
                Navigator.pop(context);
              },
            ),
          ],
        ),
      ),
    );
  },
);
```

## Interactive example

[#](#interactive-example)

```
import 'package:flutter/cupertino.dart';

void main() => runApp(const CupertinoSheetDemo());

class CupertinoSheetDemo extends StatelessWidget {
  const CupertinoSheetDemo({super.key});

  @override
  Widget build(BuildContext context) {
    return const CupertinoApp(
      title: 'CupertinoSheet Demo',
      home: CupertinoSheetPage(),
    );
  }
}

class CupertinoSheetPage extends StatelessWidget {
  const CupertinoSheetPage({super.key});

  @override
  Widget build(BuildContext context) {
    return CupertinoPageScaffold(
      navigationBar: const CupertinoNavigationBar(
        middle: Text('CupertinoSheet Demo'),
      ),
      child: Center(
        child: CupertinoButton.filled(
          onPressed: () {
            showCupertinoSheet<void>(
              context: context,
              scrollableBuilder: (context, scrollController) {
                return SingleChildScrollView(
                  controller: scrollController,
                  child: Center(
                    child: Column(
                      mainAxisSize: MainAxisSize.min,
                      mainAxisAlignment: MainAxisAlignment.center,
                      children: <Widget>[
                        const SizedBox(height: 100),
                        const Text('This is a Cupertino sheet'),
                        const SizedBox(height: 20),
                        CupertinoButton(
                          child: const Text('Close'),
                          onPressed: () {
                            Navigator.pop(context);
                          },
                        ),
                      ],
                    ),
                  ),
                );
              },
            );
          },
          child: const Text('Show Sheet'),
        ),
      ),
    );
  }
}
```

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-09-29. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/design/cupertino-sheets.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/cookbook/design/cupertino-sheets\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/design/cupertino-sheets.md "Report an issue with this page").

----
url: https://docs.flutter.dev/platform-integration/android/compose-activity.md
----

\# Launching a Jetpack Compose activity from your Flutter application > Learn how to launch native Android activities in your Flutter app.

<!--?code-excerpt path-base="platform_integration/compose_activities"?-->

Native Android activities allow you to launch fullscreen UIs that are entirely run by and on the Android platform. You will only write Kotlin code in those views (though they might pass messages to and receive messages from your Dart code) and you will have access to the full breadth of native Android functionality. Adding this functionality requires making several changes to your Flutter app and its internal, generated Android app. On the Flutter side, you will need to create a new platform method channel and call its \`invokeMethod\` method. On the Android side, you will need to register a matching native \`MethodChannel\` to receive the signal from Dart and then launch a new activity. Recall that all Flutter apps (when running on Android) exist within an Android activity that is completely consumed by the Flutter app. Thus, as you will see in the code sample, the job of the native \`MethodChannel\` callback is to launch a second activity. :::note This page discusses how to launch native Android activities within a Flutter app. If you'd like to host native Android views in your Flutter app, check out \[Hosting native Android views]\[]. ::: \[Hosting native Android views]: /platform-integration/android/platform-views Not all Android activities use Jetpack Compose, but this tutorial assumes you want to use Compose. ## On the Dart side On the Dart side, create a method channel and invoke it from a specific user interaction, like tapping a button.

<!--?code-excerpt "lib/launch_compose_activity_example_1.dart"?-->

\`\`\`dart import 'package:flutter/services.dart'; import 'package:material\_ui/material\_ui.dart'; // SECTION 1: START COPYING HERE const platformMethodChannel = MethodChannel( // Note: You can change this string value, but it must match // the \`CHANNEL\` attribute in the next step. 'com.example.flutter\_android\_activity', ); // SECTION 1: END COPYING HERE void main() { runApp(const MainApp()); } class MainApp extends StatelessWidget { const MainApp({super.key}); // SECTION 2: START COPYING HERE void \_launchAndroidActivity() { platformMethodChannel.invokeMethod( // Note: You can change this value, but it must match // the \`call.method\` value in the next section. 'launchActivity', // Note: You can pass any primitive data types you like. // To pass complex types, use package:pigeon to generate // matching Dart and Kotlin classes that share serialization logic. {'message': 'Hello from Flutter'}, ); } // SECTION 2: END COPYING HERE @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( body: const Center(child: Text('Hello World!')), floatingActionButton: FloatingActionButton( // SECTION 3: Call \`\_launchAndroidActivity\` somewhere. onPressed: \_launchAndroidActivity, // SECTION 3: End tooltip: 'Launch Android activity', child: const Icon(Icons.launch), ), ), ); } } \`\`\` There are 3 important values that must match across your Dart and Kotlin code: 1. The channel name (in this sample, the value is \`"com.example.flutter\_android\_activity"\`). 2. The method name (in this sample, the value is \`"launchActivity"\`). 3. The structure of the data which Dart passes and the structure of the data which Kotlin expects to receive. In this case, the data is a map with a single \`"message"\` key. ## On the Android side You must make changes to 4 files in the generated Android app to ready it for launching fresh Compose activities. The first file requiring modifications is \`android/app/build.gradle\`. 1. Add the following to the existing \`android\` block: \`\`\`kotlin title="android/app/build.gradle.kts" android { // Begin adding here buildFeatures { compose = true } composeOptions { // https\://developer.android.com/jetpack/androidx/releases/compose-kotlin kotlinCompilerExtensionVersion = "1.4.8" } // End adding here } \`\`\` \`\`\`groovy title="android/app/build.gradle" android { // Begin adding here buildFeatures { compose true } composeOptions { // https\://developer.android.com/jetpack/androidx/releases/compose-kotlin kotlinCompilerExtensionVersion = "1.4.8" } // End adding here } \`\`\` Visit the \[developer.android.com]\[] link in the code snippet and adjust \`kotlinCompilerExtensionVersion\`, as necessary. You should only need to do this if you receive errors during \`flutter run\` and those errors tell you which versions are installed on your machine. \[developer.android.com]: https\://developer.android.com/jetpack/androidx/releases/compose-kotlin 2. Next, add the following block at the bottom of the file, at the root level: \`\`\`kotlin title="android/app/build.gradle.kts" dependencies { implementation("androidx.core:core-ktx:1.10.1") implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.6.1") implementation("androidx.activity:activity-compose") implementation(platform("androidx.compose:compose-bom:2024.06.00")) implementation("androidx.compose.ui:ui") implementation("androidx.compose.ui:ui-graphics") implementation("androidx.compose.ui:ui-tooling-preview") implementation("androidx.compose.material:material") implementation("androidx.compose.material3:material3") testImplementation("junit:junit:4.13.2") androidTestImplementation("androidx.test.ext:junit:1.1.5") androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1") androidTestImplementation(platform("androidx.compose:compose-bom:2024.06.00")) androidTestImplementation("androidx.compose.ui:ui-test-junit4") androidTestImplementation("androidx.compose.ui:ui-test-junit4") debugImplementation("androidx.compose.ui:ui-tooling") debugImplementation("androidx.compose.ui:ui-test-manifest") } \`\`\` \`\`\`groovy title="android/app/build.gradle" dependencies { implementation "androidx.core:core-ktx:1.10.1" implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.6.1" implementation "androidx.activity:activity-compose" implementation platform("androidx.compose:compose-bom:2024.06.00") implementation "androidx.compose.ui:ui" implementation "androidx.compose.ui:ui-graphics" implementation "androidx.compose.ui:ui-tooling-preview" implementation "androidx.compose.material:material" implementation "androidx.compose.material3:material3" testImplementation "junit:junit:4.13.2" androidTestImplementation "androidx.test.ext:junit:1.1.5" androidTestImplementation "androidx.test.espresso:espresso-core:3.5.1" androidTestImplementation platform("androidx.compose:compose-bom:2023.08.00") androidTestImplementation "androidx.compose.ui:ui-test-junit4" debugImplementation "androidx.compose.ui:ui-tooling" debugImplementation "androidx.compose.ui:ui-test-manifest" } \`\`\` The second file requiring modifications is \`android/build.gradle\`. 1. Add the following buildscript block at the top of the file: \`\`\`kotlin title="android/build.gradle.kts" buildscript { dependencies { // Replace with the latest version. classpath("com.android.tools.build:gradle:8.1.1") } repositories { google() mavenCentral() } } \`\`\` \`\`\`groovy title="android/build.gradle" buildscript { dependencies { // Replace with the latest version. classpath 'com.android.tools.build:gradle:8.1.1' } repositories { google() mavenCentral() } } \`\`\` The third file requiring modifications is \`android/app/src/main/AndroidManifest.xml\`. 1. In the root application block, add the following \`\` declaration: \`\`\`xml title="android/app/src/main/AndroidManifest.xml" // START COPYING HERE // END COPYING HERE … \`\`\` The fourth and final code requiring modifications is \`android/app/src/main/kotlin/com/example/flutter\_android\_activity/MainActivity.kt\`. Here you'll write Kotlin code for your desired Android functionality. 1. Add the necessary imports at the top of the file: :::note Your imports might vary if library versions have changed or if you introduce different Compose classes when you write your own Kotlin code. Follow your IDE's hints for the correct imports you require. ::: \`\`\`kotlin title="MainActivity.kt" package com.example.flutter\_android\_activity import android.content.Intent import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.material3.Button import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.ui.Modifier import androidx.core.app.ActivityCompat import io.flutter.embedding.android.FlutterActivity import io.flutter.embedding.engine.FlutterEngine import io.flutter.plugin.common.MethodCall import io.flutter.plugin.common.MethodChannel import io.flutter.plugins.GeneratedPluginRegistrant \`\`\` 1. Modify the generated \`MainActivity\` class by adding a \`CHANNEL\` field and a \`configureFlutterEngine\` method: \`\`\`kotlin title="MainActivity.kt" class MainActivity: FlutterActivity() { // This value must match the \`MethodChannel\` name in your Dart code. private val CHANNEL = "com.example.flutter\_android\_activity" override fun configureFlutterEngine(flutterEngine: FlutterEngine) { GeneratedPluginRegistrant.registerWith(flutterEngine) MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler { call: MethodCall, result: MethodChannel.Result -> when (call.method) { // Note: This must match the first parameter passed to // \`platformMethodChannel.invokeMethod\` in your Dart code. "launchActivity" -> { try { // Takes an object, in this case a String. val message = call.arguments val intent = Intent(this\@MainActivity, SecondActivity::class.java) intent.putExtra("message", message.toString()) startActivity(intent) } catch (e: Exception){} result.success(true) } else -> {} } } } } \`\`\` 1. Add a second \`Activity\` to the bottom of the file, which you referenced in the previous changes to \`AndroidManifest.xml\`: \`\`\`kotlin title="MainActivity.kt" class SecondActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background) { Column { Text(text = "Second Activity") // Note: This must match the shape of the data passed from your Dart code. Text("" + getIntent()?.getExtras()?.getString("message")) Button(onClick = { finish() }) { Text("Exit") } } } } } } \`\`\` These steps show how to launch a native Android activity from a Flutter app, which can sometimes be an easy way to connect to specific Android functionality.

----
url: https://docs.flutter.dev/add-to-app/debugging
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Debug your add-to-app module](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

Once you've integrated the Flutter module to your project and used Flutter's platform APIs to run the Flutter engine and/or UI, you can then build and run your Android or iOS app the same way you run normal Android or iOS apps.

Flutter now powers the UI wherever your code includes `FlutterActivity` or `FlutterViewController`.

## Overview

[#](#overview)

You might be used to having your suite of favorite Flutter debugging tools available when running `flutter run` or an equivalent command from an IDE. But you can also use all your Flutter [debugging functionalities](/testing/debugging) such as hot reload, performance overlays, DevTools, and setting breakpoints in add-to-app scenarios.

The `flutter attach` command provides these functionalities. To run this command, you can use the SDK's CLI tools, VS Code or IntelliJ IDEA or Android Studio.

The `flutter attach` command connects once you run your `FlutterEngine`. It remains attached until you dispose your `FlutterEngine`. You can invoke `flutter attach` before starting your engine. The `flutter attach` command waits for the next available Dart VM that your engine hosts.

## Debug from the Terminal

[#](#debug-from-the-terminal)

To attach from the terminal, run `flutter attach`. To select a specific target device, add `-d <deviceId>`.

```
$ flutter attach
```

The command should print output resembling the following:

```
Syncing files to device iPhone 15 Pro...
 7,738ms (!)

To hot reload the changes while running, press "r".
To hot restart (and rebuild state). press "R".
```

## Debug iOS extension in Xcode and VS Code

[#](#debug-ios-extension-in-xcode-and-vs-code)

#### Build the iOS version of the Flutter app in the Terminal

[#](#build-the-ios-version-of-the-flutter-app-in-the-terminal)

To generate the needed iOS platform dependencies, run the `flutter build` command.

```
$ flutter build ios --config-only --no-codesign --debug
```

```
Warning: Building for device with codesigning disabled. You will have to manually codesign before deploying to device.
Building com.example.myApp for device (ios)...
```

* [Start from VS Code](#119-tab-panel)
* [Start from Xcode](#120-tab-panel)

#### Start debugging with VS Code first

[#](#vscode-ios)

If you use VS Code to debug most of your code, start with this section.

##### Start the Dart debugger in VS Code

[#](#start-the-dart-debugger-in-vs-code)

##### Enable automatic attachment

[#](#enable-automatic-attachment)

You can configure VS Code to attach to your Flutter module project whenever you start debugging. To enable this feature, create a `.vscode/launch.json` file in your Flutter module project.

1. Go to **View** > **Run**.

   You can also press `Ctrl` / `Cmd` + `Shift` + `D`.

   VS Code displays the **Run and Debug** sidebar.

2. In this sidebar, click **create a launch.json file**.

   VS Code displays the **Select debugger** menu at the top.

3. Select **Dart & Flutter**.

   VS Code creates then opens the `.vscode/launch.json` file.

   Expand to see an example launch.json file

   json

   ```
   {
       // Use IntelliSense to learn about possible attributes.
       // Hover to view descriptions of existing attributes.
       // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
       "version": "0.2.0",
       "configurations": [
           {
               "name": "my_app",
               "request": "launch",
               "type": "dart"
           },
           {
               "name": "my_app (profile mode)",
               "request": "launch",
               "type": "dart",
               "flutterMode": "profile"
           },
           {
               "name": "my_app (release mode)",
               "request": "launch",
               "type": "dart",
               "flutterMode": "release"
           }
       ]
   }
   ```

4. To attach, go to **Run** > **Start Debugging**.

   You can also press `F5`.

##### Attach to the Flutter process in Xcode

[#](#attach-to-the-flutter-process-in-xcode)

To attach to the Flutter app in Xcode:

1. Go to **Debug** > **Attach to Process**.

2. Select **Runner**. It should be at the top of the **Attach to Process** menu under the **Likely Targets** heading.

#### Start debugging with Xcode first

[#](#xcode-ios)

If you use Xcode to debug most of your code, start with this section.

##### Start the Xcode debugger

[#](#start-the-xcode-debugger)

1. Open `ios/Runner.xcworkspace` from your Flutter app directory.

2. Select the correct device using the **Scheme** menu in the toolbar.

   If you have no preference, choose **iPhone Pro 14**.

3. Run this Runner as a normal app in Xcode.

   When the run completes, the **Debug** area at the bottom of Xcode displays a message with the Dart VM service URI. It resembles the following response:

   ```
   2023-07-12 14:55:39.966191-0500 Runner[58361:53017145]
       flutter: The Dart VM service is listening on
       http://127.0.0.1:50642/00wEOvfyff8=/
   ```

4. Copy the Dart VM service URI.

##### Attach to the Dart VM in VS Code

[#](#attach-to-the-dart-vm-in-vs-code)

1. To open the command palette, go to **View** > **Command Palette...**

   You can also press `Cmd` + `Shift` + `P`.

2. Type `debug`.

3. Click the **Debug: Attach to Flutter on Device** command.

4. In the **Paste an VM Service URI** box, paste the URI you copied from Xcode and press `Enter`.

## Debug Android extension in Android Studio

[#](#debug-android-extension-in-android-studio)

1. To open the Flutter app directory, go to **File** > **Open...** and choose the `my_app` directory.

2. Open the `lib/main.dart` file.

3. Choose a virtual Android device. Go to the toolbar, open the leftmost dropdown menu, and click on **Open Android Emulator: \<device>**.

   You can choose any installed emulator that's doesn't include `arm64`.

4. From that same menu, select the virtual Android device.

5. From the toolbar, click **Run 'main.dart'**.

   You can also press `Ctrl` + `Shift` + `R`.

   After the app displays in the emulator, continue to the next step.

## Debug without USB connection

[#](#wireless-debugging)

To debug your app over Wi-Fi on an iOS or Android device, use `flutter attach`.

### Debug over Wi-Fi on iOS devices

[#](#debug-over-wi-fi-on-ios-devices)

For an iOS target, complete the follow steps:

1. Verify your device connects to Xcode over Wi-Fi as described in the [iOS setup guide](/platform-integration/ios/setup).

2. On your macOS development machine, open **Xcode** > **Product** > **Scheme** > **Edit Scheme...**.

   You can also press `Cmd` + `<`.

3. Click **Run**.

4. Click **Arguments**.

5. In **Arguments Passed On Launch**, Click **+**.

   1. If your dev machine uses IPv4, add `--vm-service-host=0.0.0.0`.

   2. If your dev machine uses IPv6, add `--vm-service-host=::0`.

   \<DashImage figure img-class="site-mobile-screenshot border" image="development/add-to-app/debugging/wireless-port.png" caption="Arguments Passed On Launch with an IPv4 network added", width="100%" />

#### To determine if you're on an IPv6 network

[#](#to-determine-if-youre-on-an-ipv6-network)

1. Open **Settings** > **Wi-Fi**.

2. Click on your connected network.

3. Click **Details...**

4. Click **TCP/IP**.

5. Check for an **IPv6 address** section.

   WiFi dialog box for macOS System Settings

### Debug over Wi-Fi on Android devices

[#](#debug-over-wi-fi-on-android-devices)

Verify your device connects to Android Studio over Wi-Fi as described in the [Android setup guide](/platform-integration/android/setup#set-up-devices).

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/add-to-app/debugging.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/add-to-app/debugging\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/add-to-app/debugging.md "Report an issue with this page").

----
url: https://docs.flutter.dev/platform-integration/legacy-ffi-plugin
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Bind to native code using the legacy FFI plugin template](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

Flutter mobile and desktop apps can use the [`dart:ffi`](https://api.dart.dev/dart-ffi/dart-ffi-library.html) library to call native C APIs. *FFI* stands for [*foreign function interface.*](https://en.wikipedia.org/wiki/Foreign_function_interface) Other terms for similar functionality include *native interface* and *language bindings.*

Before your library or program can use the FFI library to bind to native code, you must ensure that the native code is loaded and its symbols are visible to Dart. This page focuses on compiling, packaging, and loading native code within a Flutter plugin or app.

This tutorial demonstrates how to bundle C/C++ sources in a Flutter plugin and bind to them using the Dart FFI library. In this walkthrough, you'll create a C function that implements 32-bit addition and then exposes it through a Dart plugin named `native_add`.

## Dynamic versus static linking

[#](#dynamic-versus-static-linking)

A native library can be linked into an app either dynamically or statically. A statically linked library is embedded into the app's executable image, and is loaded when the app starts.

Symbols from a statically linked library can be loaded using [`DynamicLibrary.executable`](https://api.dart.dev/dart-ffi/DynamicLibrary/DynamicLibrary.executable.html) or [`DynamicLibrary.process`](https://api.dart.dev/dart-ffi/DynamicLibrary/DynamicLibrary.process.html).

A dynamically linked library, by contrast, is distributed in a separate file or folder within the app, and loaded on-demand. The distribution format depends on the platform:

* On Android, a dynamically linked library is distributed as a set of `.so` (ELF) files, one for each architecture. Only dynamic libraries are supported, because the main executable is the JVM, which Flutter doesn't link to statically.
* On iOS and macOS, the dynamically linked library is distributed as a `.framework` folder.

A dynamically linked library can be loaded into Dart using [`DynamicLibrary.open`](https://api.dart.dev/dart-ffi/DynamicLibrary/DynamicLibrary.open.html).

## Create an FFI plugin

[#](#create-an-ffi-plugin)

To create an FFI plugin called `native_add`, use `flutter create` with the `plugin_ffi` template:

```
$ flutter create --platforms=android,ios,macos,windows,linux --template=plugin_ffi native_add
```

This creates a plugin with C/C++ sources in `native_add/src`. These sources are built by the native build files in the various OS build folders.

The FFI library can only bind against C symbols, so in C++ these symbols are marked `extern "C"`.

You should also add attributes to indicate that the symbols are referenced from Dart, to prevent the linker from discarding the symbols during link-time optimization: `__attribute__((visibility("default"))) __attribute__((used))`.

The platform-specific build file links the code:

* On Android, `native_add/android/build.gradle`.
* On iOS, `native_add/ios/native_add.podspec`.
* On macOS, `native_add/macos/native_add.podspec`.
* On Linux, `native_add/linux/CMakeLists.txt`.
* On Windows, `native_add/windows/CMakeLists.txt`.

The native code is invoked from Dart in `lib/native_add_bindings_generated.dart`.

The bindings are generated with [`package:ffigen`](https://pub.dev/packages/ffigen).

## Other use cases

[#](#other-use-cases)

### iOS

[#](#ios)

The dynamic linker automatically loads dynamically linked libraries when the app starts. Their constituent symbols can be resolved using [`DynamicLibrary.process`](https://api.dart.dev/dart-ffi/DynamicLibrary/DynamicLibrary.process.html). You can also get a handle to the library with [`DynamicLibrary.open`](https://api.dart.dev/dart-ffi/DynamicLibrary/DynamicLibrary.open.html) to restrict the scope of symbol resolution, but it's unclear how Apple's review process handles this.

Symbols statically linked into the application binary can be resolved using [`DynamicLibrary.executable`](https://api.dart.dev/dart-ffi/DynamicLibrary/DynamicLibrary.executable.html) or [`DynamicLibrary.process`](https://api.dart.dev/dart-ffi/DynamicLibrary/DynamicLibrary.process.html).

#### Platform library

[#](#ios-platform-library)

To link against a platform library, use the following instructions:

1. In Xcode, open `Runner.xcworkspace`.
2. Select the target platform.
3. Click **+** in the **Linked Frameworks and Libraries** section.
4. Select the system library to link against.

#### First-party library

[#](#ios-first-party-library)

A first-party native library can be included either as source or as a (signed) `.framework` file. It's probably possible to include statically linked archives as well, but it requires testing.

#### Source code

[#](#ios-source-code)

To link directly to source code, use the following instructions:

1. In Xcode, open `Runner.xcworkspace`.

2. Add the C/C++/Objective-C/Swift source files to the Xcode project.

3. Add the following prefix to the exported symbol declarations to ensure they are visible to Dart:

   **C/C++/Objective-C:**

   objc

   ```
   extern "C" /* <= C++ only */ __attribute__((visibility("default"))) __attribute__((used))
   ```

   **Swift:**

   swift

   ```
   @_cdecl("myFunctionName")
   ```

#### Compiled (dynamic) library

[#](#ios-compiled-dynamic-library)

To link to a compiled dynamic library, use the following instructions:

1. If a properly signed `Framework` file is present, open `Runner.xcworkspace`.
2. Add the framework file to the **Frameworks, Libraries, and Embedded Content** section of the target in Xcode.
3. Under the **Embed** column, select **Embed & Sign**.

#### Open-source third-party library

[#](#ios-open-source-third-party-library)

To create a Flutter plugin that includes both C/C++/Objective-C *and* Dart code, use the following instructions:

1. In your plugin project, open `ios/<myproject>.podspec`.
2. Add the native code to the `source_files` field.

The native code is then statically linked into the application binary of any app that uses this plugin.

#### Closed-source third-party library

[#](#ios-closed-source-third-party-library)

To create a Flutter plugin that includes Dart source code, but distribute the C/C++ library in binary form, use the following instructions:

1. In your plugin project, open `ios/<myproject>.podspec`.
2. Add a `vendored_frameworks` field. See the [CocoaPods example](https://github.com/CocoaPods/CocoaPods/blob/master/examples/Vendored%20Framework%20Example/Example%20Pods/VendoredFrameworkExample.podspec).

#### Stripping symbols

[#](#ios-stripping-symbols)

When creating a release build, Xcode strips the symbols.

1. In Xcode, select the **Runner** target, then go to **Build Settings > Strip Style**.
2. Change from **All Symbols** to **Non-Global Symbols**.

### macOS

[#](#macos)

The dynamic linker automatically loads dynamically linked libraries when the app starts. Their constituent symbols can be resolved using [`DynamicLibrary.process`](https://api.dart.dev/dart-ffi/DynamicLibrary/DynamicLibrary.process.html). You can also get a handle to the library with [`DynamicLibrary.open`](https://api.dart.dev/dart-ffi/DynamicLibrary/DynamicLibrary.open.html) to restrict the scope of symbol resolution, but it's unclear how Apple's review process handles this.

Symbols statically linked into the application binary can be resolved using [`DynamicLibrary.executable`](https://api.dart.dev/dart-ffi/DynamicLibrary/DynamicLibrary.executable.html) or [`DynamicLibrary.process`](https://api.dart.dev/dart-ffi/DynamicLibrary/DynamicLibrary.process.html).

#### Platform library

[#](#macos-platform-library)

To link against a platform library, use the following instructions:

1. In Xcode, open `Runner.xcworkspace`.
2. Select the target platform.
3. Click **+** in the **Linked Frameworks and Libraries** section.
4. Select the system library to link against.

#### First-party library

[#](#macos-first-party-library)

A first-party native library can be included either as source or as a (signed) `.framework` file. It's probably possible to include statically linked archives as well, but it requires testing.

#### Source code

[#](#macos-source-code)

To link directly to source code, use the following instructions:

1. In Xcode, open `Runner.xcworkspace`.

2. Add the C/C++/Objective-C/Swift source files to the Xcode project.

3. Add the following prefix to the exported symbol declarations to ensure they are visible to Dart:

   **C/C++/Objective-C:**

   objc

   ```
   extern "C" /* <= C++ only */ __attribute__((visibility("default"))) __attribute__((used))
   ```

   **Swift:**

   swift

   ```
   @_cdecl("myFunctionName")
   ```

#### Compiled (dynamic) library

[#](#macos-compiled-dynamic-library)

To link to a compiled dynamic library, use the following instructions:

1. If a properly signed `Framework` file is present, open `Runner.xcworkspace`.
2. Add the framework file to the **Frameworks, Libraries, and Embedded Content** section of the target in Xcode.
3. Under the **Embed** column, select **Embed & Sign**.

#### Compiled (dynamic) library, closed source

[#](#macos-compiled-dynamic-library-closed-source)

To add a closed source library to a [Flutter macOS Desktop](/platform-integration/macos/building) app, use the following instructions:

1. Follow the instructions for Flutter desktop to create a Flutter desktop app.

2. Open the `yourapp/macos/Runner.xcworkspace` in Xcode.

   1. Drag your precompiled library (`libyourlibrary.dylib`) into `Runner/Frameworks`.

   2. Click `Runner` and go to the `Build Phases` tab.

      1. Drag `libyourlibrary.dylib` into the `Copy Bundle Resources` list.
      2. Under `Embed Libraries`, check `Code Sign on Copy`.
      3. Under `Link Binary With Libraries`, set status to `Optional`. (We use dynamic linking, no need to statically link.)

   3. Click `Runner` and go to the `General` tab.

      1. Drag `libyourlibrary.dylib` into the **Frameworks, Libraries, and Embedded Content** list.
      2. Select **Embed & Sign**.

   4. Click **Runner** and go to the **Build Settings** tab.
      1. In the **Search Paths** section configure the **Library Search Paths** to include the path where `libyourlibrary.dylib` is located.

3. Edit `lib/main.dart`.

   1. Use `DynamicLibrary.open('libyourlibrary.dylib')` to dynamically link to the symbols.
   2. Call your native function somewhere in a widget.

4. Run `flutter run` and check that your native function gets called.

5. Run `flutter build macos` to build a self-contained release version of your app.

#### Stripping symbols

[#](#macos-stripping-symbols)

When creating a release build, Xcode strips the symbols.

1. In Xcode, select the **Runner** target, then go to **Build Settings > Strip Style**.
2. Change from **All Symbols** to **Non-Global Symbols**.

### Android

[#](#android)

#### Platform library

[#](#android-platform-library)

To link against a platform library, use the following instructions:

1. Find the desired library in the [Android NDK Native APIs](https://developer.android.com/ndk/guides/stable_apis) list in the Android docs. This lists stable native APIs.

2. Load the library using [`DynamicLibrary.open`](https://api.dart.dev/dart-ffi/DynamicLibrary/DynamicLibrary.open.html). For example, to load OpenGL ES (v3):

   dart

   ```
   DynamicLibrary.open('libGLES_v3.so');
   ```

You might need to update the Android manifest file of the app or plugin if indicated by the documentation.

#### First-party library

[#](#android-first-party-library)

The process for including native code in source code or binary form is the same for an app or plugin.

#### Open-source third-party library

[#](#android-open-source-third-party-library)

Follow the [Add C and C++ code to your project](https://developer.android.com/studio/projects/add-native-code) instructions in the Android docs to add native code and support for the native code toolchain (either CMake or `ndk-build`).

#### Closed-source third-party library

[#](#android-closed-source-third-party-library)

To create a Flutter plugin that includes Dart source code, but distribute the C/C++ library in binary form, use the following instructions:

1. Open the `android/build.gradle` file for your project.
2. Add the AAR artifact as a dependency. **Don't** include the artifact in your Flutter package. Instead, it should be downloaded from a repository, such as Maven Central.

#### Android APK size (shared object compression)

[#](#android-apk-size-shared-object-compression)

[Android guidelines](https://developer.android.com/topic/performance/reduce-apk-size#extract-false) in general recommend distributing native shared objects uncompressed because that actually saves on device space. Shared objects can be directly loaded from the APK instead of unpacking them on the device into a temporary location and then loading. APKs are additionally packed in transit—that's why you should be looking at download size.

By default, Flutter APKs compress `libflutter.so` and `libapp.so`, which leads to a smaller APK size but a larger on-device size. To control whether native libraries are stored compressed and extracted at install time, set the Android Gradle plugin's `useLegacyPackaging` option. For current recommendations, see the [Android guidelines](https://developer.android.com/topic/performance/reduce-apk-size#extract-false).

## Other resources

[#](#other-resources)

To learn more about C interoperability, check out these videos:

* [C interoperability with Dart FFI](https://www.youtube.com/watch?v=2MMK7YoFgaA)
* [How to Use Dart FFI to Build a Retro Audio Player](https://www.youtube.com/watch?v=05Wn2oM_nWw)

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-07-31. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/platform-integration/legacy-ffi-plugin.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/platform-integration/legacy-ffi-plugin\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/platform-integration/legacy-ffi-plugin.md "Report an issue with this page").

----
url: https://docs.flutter.dev/platform-integration/ios/platform-views.md
----

\# Host native iOS views in your Flutter app with platform views > Learn how to host native iOS views in your Flutter app with platform views.

<!--?code-excerpt path-base="platform_integration/platform_views"?-->

Platform views allow you to embed native views in a Flutter app, so you can apply transforms, clips, and opacity to the native view from Dart. This allows you, for example, to use the native Google Maps from the Android and iOS SDKs directly inside your Flutter app. :::note This page discusses how to host your own native iOS views within a Flutter app. If you'd like to embed native Android views in your Flutter app, see \[Hosting native Android views]\[]. If you'd like to embed native macOS views in your Flutter app, see \[Hosting native macOS views]\[]. ::: \[Hosting native Android views]: /platform-integration/android/platform-views \[Hosting native macOS views]: /platform-integration/macos/platform-views iOS only uses Hybrid composition, which means that the native \`UIView\` is appended to the view hierarchy. To create a platform view on iOS, use the following instructions: ## On the Dart side On the Dart side, create a \`Widget\` and add the build implementation, as shown in the following steps. In the Dart widget file, make changes similar to those shown in \`native\_view\_example.dart\`:

1. Add the following imports:
   <!--?code-excerpt "lib/native_view_example_3.dart (import)"?-->
   \`\`\`dart import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; \`\`\`
2. Implement a \`build()\` method:
   <!--?code-excerpt "lib/native_view_example_3.dart (ios-composition)"?-->
   \`\`\`dart Widget build(BuildContext context) { // This is used in the platform side to register the view. const String viewType = ''; // Pass parameters to the platform side. final Map creationParams = {}; return UiKitView( viewType: viewType, layoutDirection: TextDirection.ltr, creationParams: creationParams, creationParamsCodec: const StandardMessageCodec(), ); } \`\`\`

For more information, see the API docs for: \[\`UIKitView\`]\[]. \[\`UIKitView\`]: https\://api.flutter.dev/flutter/widgets/UiKitView-class.html ## On the platform side On the platform side, use either Swift or Objective-C: Implement the factory and the platform view. The \`FLNativeViewFactory\` creates the platform view, and the platform view provides a reference to the \`UIView\`. For example, implement the factory class: \`\`\`swift import Flutter import UIKit class FLNativeViewFactory: NSObject, FlutterPlatformViewFactory { private var messenger: FlutterBinaryMessenger init(messenger: FlutterBinaryMessenger) { self.messenger = messenger super.init() } func create( withFrame frame: CGRect, viewIdentifier viewId: Int64, arguments args: Any? ) -> FlutterPlatformView { return FLNativeView( frame: frame, viewIdentifier: viewId, arguments: args, binaryMessenger: messenger) } /// Implementing this method is only necessary when the \`arguments\` in \`createWithFrame\` is not \`nil\`. public func createArgsCodec() -> FlutterMessageCodec & NSObjectProtocol { return FlutterStandardMessageCodec.sharedInstance() } } \`\`\` Depending on the UI framework you are using, choose one of the following implementations for \`FLNativeView\`: For UIKit, implement the \`FLNativeView\` class: \`\`\`swift class FLNativeView: NSObject, FlutterPlatformView { private var \_view: UIView init( frame: CGRect, viewIdentifier viewId: Int64, arguments args: Any?, binaryMessenger messenger: FlutterBinaryMessenger? ) { \_view = UIView() super.init() // iOS views can be created here createNativeView(view: \_view) } func view() -> UIView { return \_view } func createNativeView(view \_view: UIView){ \_view\.backgroundColor = UIColor.blue let nativeLabel = UILabel() nativeLabel.text = "Native text from iOS" nativeLabel.textColor = UIColor.white nativeLabel.textAlignment = .center nativeLabel.frame = CGRect(x: 0, y: 0, width: 180, height: 48.0) \_view\.addSubview(nativeLabel) } } \`\`\` To display SwiftUI views within a platform view on iOS, wrap the SwiftUI view inside a \`UIHostingController\`. Since \`UIHostingController\` is a view controller, you can retrieve its view using the \`view\` property, and return it from the platform view's \`view()\` method. To prevent premature deallocation, store the \`UIHostingController\` instance as a property of \`FLNativeView\`. Use the \`frame\` passed to the initializer to set the frame of the hosting controller's view so Flutter sizes it correctly. For SwiftUI, implement the \`FLNativeView\` class and its SwiftUI view: \`\`\`swift import SwiftUI class FLNativeView: NSObject, FlutterPlatformView { private let hostingController: UIHostingController init( frame: CGRect, viewIdentifier viewId: Int64, arguments args: Any?, binaryMessenger messenger: FlutterBinaryMessenger? ) { hostingController = UIHostingController(rootView: MySwiftUIView()) super.init() hostingController.view\.frame = frame } func view() -> UIView { return hostingController.view } } struct MySwiftUIView: View { var body: some View { Text("Native text from iOS (SwiftUI)") .frame(maxWidth: .infinity, maxHeight: .infinity) .background(Color.blue) .foregroundColor(.white) } } \`\`\` Finally, register the platform view. This can be done in an app or a plugin. :::version-note In Flutter 3.41 and later, iOS apps use the \`UIScene\` lifecycle by default. For apps using \`UIScene\`, you must implement \`FlutterImplicitEngineDelegate\` and register your platform views inside \`didInitializeImplicitFlutterEngine(\_:)\`. For more information, refer to the \[UISceneDelegate adoption guide]\[]. ::: For app registration, modify the App's \`AppDelegate.swift\`: \`\`\`swift import Flutter import UIKit @main @objc class AppDelegate: FlutterAppDelegate, FlutterImplicitEngineDelegate { func didInitializeImplicitFlutterEngine(\_ engineBridge: FlutterImplicitEngineBridge) { GeneratedPluginRegistrant.register(with: engineBridge.pluginRegistry) guard let pluginRegistrar = engineBridge.pluginRegistry.registrar(forPlugin: "plugin-name") else { return } let factory = FLNativeViewFactory(messenger: pluginRegistrar.messenger()) pluginRegistrar.register( factory, withId: "") } } \`\`\` For plugin registration, modify the plugin's main file (for example, \`FLPlugin.swift\`): \`\`\`swift import Flutter import UIKit class FLPlugin: NSObject, FlutterPlugin { public static func register(with registrar: FlutterPluginRegistrar) { let factory = FLNativeViewFactory(messenger: registrar.messenger()) registrar.register(factory, withId: "") } } \`\`\` In Objective-C, add the headers for the factory and the platform view. For example, as shown in \`FLNativeView\.h\`: \`\`\`objc #import @interface FLNativeViewFactory : NSObject - (instancetype)initWithMessenger:(NSObject\*)messenger; @end @interface FLNativeView : NSObject - (instancetype)initWithFrame:(CGRect)frame viewIdentifier:(int64\_t)viewId arguments:(id \_Nullable)args binaryMessenger:(NSObject\*)messenger; - (UIView\*)view; @end \`\`\` Implement the factory and the platform view. The \`FLNativeViewFactory\` creates the platform view, and the platform view provides a reference to the \`UIView\`. For example, \`FLNativeView\.m\`: \`\`\`objc #import "FLNativeView\.h" @implementation FLNativeViewFactory { NSObject\* \_messenger; } - (instancetype)initWithMessenger:(NSObject\*)messenger { self = \[super init]; if (self) { \_messenger = messenger; } return self; } - (NSObject\*)createWithFrame:(CGRect)frame viewIdentifier:(int64\_t)viewId arguments:(id \_Nullable)args { return \[\[FLNativeView alloc] initWithFrame:frame viewIdentifier:viewId arguments:args binaryMessenger:\_messenger]; } /// Implementing this method is only necessary when the \`arguments\` in \`createWithFrame\` is not \`nil\`. - (NSObject\*)createArgsCodec { return \[FlutterStandardMessageCodec sharedInstance]; } @end @implementation FLNativeView { UIView \*\_view; } - (instancetype)initWithFrame:(CGRect)frame viewIdentifier:(int64\_t)viewId arguments:(id \_Nullable)args binaryMessenger:(NSObject\*)messenger { if (self = \[super init]) { \_view = \[\[UIView alloc] init]; } return self; } - (UIView\*)view { return \_view; } @end \`\`\` Finally, register the platform view. This can be done in an app or a plugin. :::version-note In Flutter 3.41 and later, iOS apps use the \`UIScene\` lifecycle by default. For apps using \`UIScene\`, you must implement \`FlutterImplicitEngineDelegate\` and register your platform views inside \`didInitializeImplicitFlutterEngine:\`. For more information, refer to the \[UISceneDelegate adoption guide]\[]. ::: For app registration, modify the App's \`AppDelegate.m\`: \`\`\`objc #import "AppDelegate.h" #import "FLNativeView\.h" #import "GeneratedPluginRegistrant.h" @implementation AppDelegate - (void)didInitializeImplicitFlutterEngine:(NSObject\*)engineBridge { \[GeneratedPluginRegistrant registerWithRegistry:engineBridge.pluginRegistry]; NSObject\* registrar = \[engineBridge.pluginRegistry registrarForPlugin:@"plugin-name"]; FLNativeViewFactory\* factory = \[\[FLNativeViewFactory alloc] initWithMessenger:registrar.messenger]; \[registrar registerViewFactory:factory withId:@""]; } @end \`\`\` For plugin registration, modify the main plugin file (for example, \`FLPlugin.m\`): \`\`\`objc #import #import "FLNativeView\.h" @interface FLPlugin : NSObject @end @implementation FLPlugin + (void)registerWithRegistrar:(NSObject\*)registrar { FLNativeViewFactory\* factory = \[\[FLNativeViewFactory alloc] initWithMessenger:registrar.messenger]; \[registrar registerViewFactory:factory withId:@""]; } @end \`\`\` For more information, see the API docs for: \* \[\`FlutterPlatformViewFactory\`]\[] \* \[\`FlutterPlatformView\`]\[] \* \[\`PlatformView\`]\[] \[\`FlutterPlatformView\`]: https\://api.flutter.dev/ios-embedder/protocol\_flutter\_platform\_view-p.html \[\`FlutterPlatformViewFactory\`]: https\://api.flutter.dev/ios-embedder/protocol\_flutter\_platform\_view\_factory-p.html \[\`PlatformView\`]: https\://api.flutter.dev/javadoc/io/flutter/plugin/platform/PlatformView\.html ## Putting it together When implementing the \`build()\` method in Dart, you can use \[\`defaultTargetPlatform\`]\[] to detect the platform, and decide which widget to use:

<!--?code-excerpt "lib/native_view_example_3.dart (together-widget)"?-->

\`\`\`dart Widget build(BuildContext context) { // This is used in the platform side to register the view. const String viewType = ''; // Pass parameters to the platform side. final Map creationParams = {}; switch (defaultTargetPlatform) { case TargetPlatform.android: // return widget on Android. case TargetPlatform.iOS: // return widget on iOS. case TargetPlatform.macOS: // return widget on macOS. default: throw UnsupportedError('Unsupported platform view'); } } \`\`\` ## Performance Platform views in Flutter come with performance trade-offs. For complex cases, there are some techniques that can be used to mitigate performance issues. For example, you could use a placeholder texture while an animation is happening in Dart. In other words, if an animation is slow while a platform view is rendered, then consider taking a screenshot of the native view and rendering it as a texture. ## Composition limitations There are some limitations when composing iOS Platform Views. - The \[\`ShaderMask\`]\[] and \[\`ColorFiltered\`]\[] widgets are not supported. - The \[\`BackdropFilter\`]\[] widget is supported, but there are some limitations on how it can be used. For more details, check out the \[iOS Platform View Backdrop Filter Blur design doc]\[design-doc]. \[\`ShaderMask\`]: https\://api.flutter.dev/flutter/foundation/ShaderMask.html \[\`ColorFiltered\`]: https\://api.flutter.dev/flutter/foundation/ColorFiltered.html \[\`BackdropFilter\`]: https\://api.flutter.dev/flutter/foundation/BackdropFilter.html \[\`defaultTargetPlatform\`]: https\://api.flutter.dev/flutter/foundation/defaultTargetPlatform.html \[design-doc]: https\://flutter.dev/go/ios-platformview-backdrop-filter-blur \[UISceneDelegate adoption guide]: /release/breaking-changes/uiscenedelegate#migrate-appdelegate

----
url: https://docs.flutter.dev/platform-integration/windows/building.md
----

\# Building Windows apps with Flutter > Platform-specific considerations for building for Windows with Flutter. This page discusses considerations unique to building Windows apps with Flutter, including shell integration and distribution of Windows apps through the Microsoft Store on Windows. ## Integrating with Windows The Windows programming interface combines traditional Win32 APIs, COM interfaces and more modern Windows Runtime libraries. As all these provide a C-based ABI, you can call into the services provided by the operating system using Dart's Foreign Function Interface library (\`dart:ffi\`). FFI is designed to enable Dart programs to efficiently call into C libraries. It provides Flutter apps with the ability to allocate native memory with \`malloc\` or \`calloc\`, support for pointers, structs and callbacks, and ABI types like \`long\` and \`size\_t\`. For more information about calling C libraries from Flutter, see \[C interop using \`dart:ffi\`]. To bundle and bind your own native C or C++ code with your app, see \[Bind to native code using FFI]\[]. In practice, while it is relatively straightforward to call basic Win32 APIs from Dart in this way, it is easier to use a wrapper library that abstracts the intricacies of the COM programming model. The \[win32 package] provides a library for accessing thousands of common Windows APIs, using metadata provided by Microsoft for consistency and correctness. The package also includes examples of a variety of common use cases, such as WMI, disk management, shell integration, and system dialogs. A number of other packages build on this foundation, providing idiomatic Dart access for the \[Windows registry], \[gamepad support], \[biometric storage], \[taskbar integration], and \[serial port access], to name a few. More generally, many other \[packages support Windows], including common packages such as \[\`url\_launcher\`], \[\`shared\_preferences\`], \[\`file\_selector\`], and \[\`path\_provider\`]. \[C interop using \`dart:ffi\`]: https\://dart.dev/guides/libraries/c-interop \[Bind to native code using FFI]: /platform-integration/bind-native-code \[win32 package]: https\://pub.dev/packages/win32 \[Windows registry]: https\://pub.dev/packages/win32\_registry \[gamepad support]: https\://pub.dev/packages/win32\_gamepad \[biometric storage]: https\://pub.dev/packages/biometric\_storage \[taskbar integration]: https\://pub.dev/packages/windows\_taskbar \[serial port access]: https\://pub.dev/packages/serial\_port\_win32 \[packages support Windows]: https\://pub.dev/packages?q=platform%3Awindows \[\`url\_launcher\`]: https\://pub.dev/packages/url\_launcher \[\`shared\_preferences\`]: https\://pub.dev/packages/shared\_preferences \[\`file\_selector\`]: https\://pub.dev/packages/file\_selector \[\`path\_provider\`]: https\://pub.dev/packages/path\_provider ## Supporting Windows UI guidelines While you can use any visual style or theme you choose, including Material, some app authors might wish to build an app that matches the conventions of Microsoft's \[Fluent design system]\[]. The \[fluent\_ui]\[] package, a \[Flutter Favorite]\[], provides support for visuals and common controls that are commonly found in modern Windows apps, including navigation views, content dialogs, flyouts, date pickers, and tree view widgets. In addition, Microsoft offers \[fluentui\_system\_icons]\[], a package that provides easy access to thousands of Fluent icons for use in your Flutter app. Lastly, the \[bitsdojo\_window]\[] package provides support for "owner draw" title bars, allowing you to replace the standard Windows title bar with a custom one that matches the rest of your app. \[Fluent design system]: https\://docs.microsoft.com/en-us/windows/apps/design/ \[fluent\_ui]: https\://pub.dev/packages/fluent\_ui \[Flutter Favorite]: /packages-and-plugins/favorites \[fluentui\_system\_icons]: https\://pub.dev/packages/fluentui\_system\_icons \[bitsdojo\_window]: https\://pub.dev/packages/bitsdojo\_window ## Customizing the Windows host application When you create a Windows app, Flutter generates a small C++ application that hosts Flutter. This "runner app" is responsible for creating and sizing a traditional Win32 window, initializing the Flutter engine and any native plugins, and running the Windows message loop (passing relevant messages on to Flutter for further processing). You can, of course, make changes to this code to suit your needs, including modifying the app name and icon, and setting the window's initial size and location. The relevant code is in main.cpp, where you will find code similar to the following: \`\`\`cpp Win32Window::Point origin(10, 10); Win32Window::Size size(1280, 720); if (!window\.CreateAndShow(L"myapp", origin, size)) { return EXIT\_FAILURE; } \`\`\` Replace \`myapp\` with the title you would like displayed in the Windows caption bar, as well as optionally adjusting the dimensions for size and the window coordinates. To change the Windows application icon, replace the \`app\_icon.ico\` file in the \`windows\runner\resources\` directory with an icon of your preference. The generated Windows executable filename can be changed by editing the \`BINARY\_NAME\` variable in \`windows/CMakeLists.txt\`: \`\`\`cmake cmake\_minimum\_required(VERSION 3.14) project(windows\_desktop\_app LANGUAGES CXX) # The name of the executable created for the application. # Change this to change the on-disk name of your application. set(BINARY\_NAME "YourNewApp") cmake\_policy(SET CMP0063 NEW) \`\`\` When you run \`flutter build windows\`, the executable file generated in the \`build\windows\runner\Release\` directory will match the newly given name. Finally, further properties for the app executable itself can be found in the \`Runner.rc\` file in the \`windows\runner\` directory. Here you can change the copyright information and application version that is embedded in the Windows app, which is displayed in the Windows Explorer properties dialog box. To change the version number, edit the \`VERSION\_AS\_NUMBER\` and \`VERSION\_AS\_STRING\` properties; other information can be edited in the \`StringFileInfo\` block. ## Compiling with Visual Studio For most apps, it's sufficient to allow Flutter to handle the compilation process using the \`flutter run\` and \`flutter build\` commands. If you are making significant changes to the runner app or integrating Flutter into an existing app, you might want to load or compile the Flutter app in Visual Studio itself. Follow these steps: 1. Run \`flutter build windows\` to create the \`build\\\` directory. 1. Open the Visual Studio solution file for the Windows runner, which can now be found in the \`build\windows\` directory, named according to the parent Flutter app. 1. In Solution Explorer, you will see a number of projects. Right-click the one that has the same name as the Flutter app, and choose \*\*Set as Startup Project\*\*. 1. To generate the necessary dependencies, run \*\*Build\*\* > \*\*Build Solution\*\* You can also press/ `Ctrl` + `Shift` + `B`. To run the Windows app from Visual Studio, go to \*\*Debug\*\* > \*\*Start Debugging\*\*. You can also press `F5`. 1. Use the toolbar to switch between Debug and Release configurations as appropriate. ## Distributing Windows apps There are various approaches you can use for distributing your Windows application. Here are some options: \* Use tooling to construct an MSIX installer (described in the next section) for your application and distribute it through the Microsoft Windows App Store. You don't need to manually create a signing certificate for this option as it is handled for you. \* Construct an MSIX installer and distribute it through your own website. For this option, you need to give your application a digital signature in the form of a \`.pfx\` certificate. \* Collect all of the necessary pieces and build your own zip file. ### MSIX packaging \[MSIX]\[], the new Windows application package format, provides a modern packaging format and installer. This format can either be used to ship applications to the Microsoft Store on Windows, or you can distribute app installers directly. The easiest way to create an MSIX distribution for a Flutter project is to use the \[\`msix\` pub package]\[msix package]. For an example of using the \`msix\` package from a Flutter desktop app, see the \[Desktop Photo Search]\[] sample. \[MSIX]: https\://docs.microsoft.com/en-us/windows/msix/overview \[msix package]: https\://pub.dev/packages/msix \[Desktop Photo Search]: https\://github.com/flutter/samples/tree/main/desktop\_photo\_search #### Create a self-signed .pfx certificate for local testing For private deployment and testing with the help of the MSIX installer, you need to give your application a digital signature in the form of a \`.pfx\` certificate. For deployment through the Windows Store, generating a \`.pfx\` certificate is not required. The Windows Store handles creation and management of certificates for applications distributed through its store. Distributing your application by self hosting it on a website requires a certificate signed by a Certificate Authority known to Windows. Use the following instructions to generate a self-signed \`.pfx\` certificate. 1. If you haven't already, download the \[OpenSSL]\[] toolkit to generate your certificates. 1. Go to where you installed OpenSSL, for example, \`C:\Program Files\OpenSSL-Win64\bin\`. 1. Set an environment variable so that you can access \`OpenSSL\` from anywhere:\
\`"C:\Program Files\OpenSSL-Win64\bin"\` 1. Generate a private key as follows:\
\`openssl genrsa -out mykeyname.key 2048\` 1. Generate a certificate signing request (CSR) file using the private key:\
\`openssl req -new -key mykeyname.key -out mycsrname.csr\` 1. Generate the signed certificate (CRT) file using the private key and CSR file:\
\`openssl x509 -in mycsrname.csr -out mycrtname.crt -req -signkey mykeyname.key -days 10000\` 1. Generate the \`.pfx\` file using the private key and CRT file:\
\`openssl pkcs12 -export -out CERTIFICATE.pfx -inkey mykeyname.key -in mycrtname.crt\` 1. Install the \`.pfx\` certificate first on the local machine in \`Certificate store\` as \`Trusted Root Certification Authorities\` before installing the app. \[OpenSSL]: https\://slproweb.com/products/Win32OpenSSL.html ### Building your own zip file for Windows The Flutter executable, \`.exe\`, can be found in your project under \`build\windows\runner\\\\\`. In addition to that executable, you need the following: \* From the same directory: \* all the \`.dll\` files \* the \`data\` directory \* The Visual C++ redistributables. You can use any of the methods shown in the \[deployment example walkthroughs]\[] on the Microsoft site to ensure that end users have the C++ redistributables. If you use the \`application-local\` option, you need to copy: \* \`msvcp140.dll\` \* \`vcruntime140.dll\` \* \`vcruntime140\_1.dll\` Place the DLL files in the directory next to the executable and the other DLLs, and bundle them together in a zip file. The resulting structure looks something like this: \`\`\`plaintext Release │ flutter\_windows.dll │ msvcp140.dll │ my\_app.exe │ vcruntime140.dll │ vcruntime140\_1.dll │ └───data │ │ app.so │ │ icudtl.dat ... \`\`\` At this point if desired it would be relatively simple to add this folder to a Windows installer such as Inno Setup, WiX, etc. ## Additional resources To learn how to build an \`.exe\` using Inno Setup to distribute your Flutter desktop app for Windows, check out the step-by-step \[Windows packaging guide]\[windows\_packaging\_guide]. \[deployment example walkthroughs]: https\://docs.microsoft.com/en-us/cpp/windows/deployment-examples \[windows\_packaging\_guide]: https\://medium.com/@fluttergems/packaging-and-distributing-flutter-desktop-apps-the-missing-guide-part-2-windows-0b468d5e9e70

----
url: https://docs.flutter.dev/cookbook/plugins/play-video
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Play and pause a video](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

Playing videos is a common task in app development, and Flutter apps are no exception. To play videos, the Flutter team provides the [`video_player`](https://pub.dev/packages/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.

On iOS, the `video_player` plugin makes use of [`AVPlayer`](https://developer.apple.com/documentation/avfoundation/avplayer) to handle playback. On Android, it uses [`ExoPlayer`](https://google.github.io/ExoPlayer/).

This recipe demonstrates how to use the `video_player` package to stream a video from the internet with basic play and pause controls using the following steps:

1. Add the `video_player` dependency.
2. Add permissions to your app.
3. Create and initialize a `VideoPlayerController`.
4. Display the video player.
5. Play and pause the video.

## 1. Add the `video_player` dependency

[#](#1-add-the-video-player-dependency)

This recipe depends on one Flutter plugin: `video_player`. First, add this dependency to your project.

To add the `video_player` package as a dependency, run `flutter pub add`:

```
$ flutter pub add video_player
```

## 2. Add permissions to your app

[#](#2-add-permissions-to-your-app)

Next, update your `android` and `ios` configurations to ensure that your app has the correct permissions to stream videos from the internet.

### Android

[#](#android)

Add the following permission to the `AndroidManifest.xml` file just after the `<application>` definition. The `AndroidManifest.xml` file is found at `<project root>/android/app/src/main/AndroidManifest.xml`.

xml

```
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    <application ...>
​
    </application>
​
    <uses-permission android:name="android.permission.INTERNET"/>
</manifest>
```

### iOS

[#](#ios)

For iOS, add the following to the `Info.plist` file found at `<project root>/ios/Runner/Info.plist`.

xml

```
<key>NSAppTransportSecurity</key>
<dict>
  <key>NSAllowsArbitraryLoads</key>
  <true/>
</dict>
```

### macOS

[#](#macos)

If you use network-based videos, [add the `com.apple.security.network.client` entitlement](/platform-integration/macos/building#entitlements-and-the-app-sandbox).

### Web

[#](#web)

Flutter web does **not** support `dart:io`, so avoid using the `VideoPlayerController.file` constructor for the plugin. Using this constructor attempts to create a`VideoPlayerController.file` that throws an `UnimplementedError`.

Different web browsers might have different video-playback capabilities, such as supported formats or autoplay. Check the [video\_player\_web](https://pub.dev/packages/video_player_web) package for more web-specific information.

The `VideoPlayerOptions.mixWithOthers` option can't be implemented in web, at least at the moment. If you use this option in web it will be silently ignored.

## 3. Create and initialize a `VideoPlayerController`

[#](#3-create-and-initialize-a-videoplayercontroller)

Now that you have the `video_player` plugin installed with the correct 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.

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`
3. Add a variable to the `State` class to store the `Future` returned from `VideoPlayerController.initialize`
4. Create and initialize the controller in the `initState` method
5. Dispose of the controller in the `dispose` method

dart

```
class VideoPlayerScreen extends StatefulWidget {
  const VideoPlayerScreen({super.key});
​
  @override
  State<VideoPlayerScreen> createState() => _VideoPlayerScreenState();
}
​
class _VideoPlayerScreenState extends State<VideoPlayerScreen> {
  late VideoPlayerController _controller;
  late Future<void> _initializeVideoPlayerFuture;
​
  @override
  void initState() {
    super.initState();
​
    // Create and store the VideoPlayerController. The VideoPlayerController
    // offers several different constructors to play videos from assets, files,
    // or the internet.
    _controller = VideoPlayerController.networkUrl(
      Uri.parse(
        'https://flutter.github.io/assets-for-api-docs/assets/videos/butterfly.mp4',
      ),
    );
​
    _initializeVideoPlayerFuture = _controller.initialize();
  }
​
  @override
  void dispose() {
    // Ensure disposing of the VideoPlayerController to free up resources.
    _controller.dispose();
​
    super.dispose();
  }
​
  @override
  Widget build(BuildContext context) {
    // Complete the code in the next step.
    return Container();
  }
}
```

## 4. Display the video player

[#](#4-display-the-video-player)

Now, display the video. The `video_player` plugin provides the [`VideoPlayer`](https://pub.dev/documentation/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 specific aspect ratio, such as 16x9 or 4x3.

Therefore, wrap the `VideoPlayer` widget in an [`AspectRatio`](https://api.flutter.dev/flutter/widgets/AspectRatio-class.html) widget to ensure that the video has the correct proportions.

Furthermore, you must display the `VideoPlayer` widget after the `_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 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 video.
      return AspectRatio(
        aspectRatio: _controller.value.aspectRatio,
        // Use the VideoPlayer widget to display the video.
        child: VideoPlayer(_controller),
      );
    } else {
      // If the VideoPlayerController is still initializing, show a
      // loading spinner.
      return const Center(child: CircularProgressIndicator());
    }
  },
)
```

## 5. Play and pause the video

[#](#5-play-and-pause-the-video)

By default, the video starts in a paused state. To begin playback, call the [`play()`](https://pub.dev/documentation/video_player/latest/video_player/VideoPlayerController/play.html) method provided by the `VideoPlayerController`. To pause playback, call the [`pause()`](https://pub.dev/documentation/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.

dart

```
FloatingActionButton(
  onPressed: () {
    // 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.
        _controller.play();
      }
    });
  },
  // Display the correct icon depending on the state of the player.
  child: Icon(
    _controller.value.isPlaying ? Icons.pause : Icons.play_arrow,
  ),
)
```

## Complete example

[#](#complete-example)

```
import 'dart:async';

import 'package:flutter/material.dart';
import 'package:video_player/video_player.dart';

void main() => runApp(const VideoPlayerApp());

class VideoPlayerApp extends StatelessWidget {
  const VideoPlayerApp({super.key});

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      title: 'Video Player Demo',
      home: VideoPlayerScreen(),
    );
  }
}

class VideoPlayerScreen extends StatefulWidget {
  const VideoPlayerScreen({super.key});

  @override
  State<VideoPlayerScreen> createState() => _VideoPlayerScreenState();
}

class _VideoPlayerScreenState extends State<VideoPlayerScreen> {
  late VideoPlayerController _controller;
  late Future<void> _initializeVideoPlayerFuture;

  @override
  void initState() {
    super.initState();

    // Create and store the VideoPlayerController. The VideoPlayerController
    // offers several different constructors to play videos from assets, files,
    // or the internet.
    _controller = VideoPlayerController.networkUrl(
      Uri.parse(
        'https://flutter.github.io/assets-for-api-docs/assets/videos/butterfly.mp4',
      ),
    );

    // Initialize the controller and store the Future for later use.
    _initializeVideoPlayerFuture = _controller.initialize();

    // Use the controller to loop the video.
    _controller.setLooping(true);
  }

  @override
  void dispose() {
    // Ensure disposing of the VideoPlayerController to free up resources.
    _controller.dispose();

    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Butterfly Video')),
      // 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.
            return AspectRatio(
              aspectRatio: _controller.value.aspectRatio,
              // Use the VideoPlayer widget to display the video.
              child: VideoPlayer(_controller),
            );
          } else {
            // If the VideoPlayerController is still initializing, show a
            // loading spinner.
            return const Center(child: CircularProgressIndicator());
          }
        },
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          // 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.
              _controller.play();
            }
          });
        },
        // Display the correct icon depending on the state of the player.
        child: Icon(
          _controller.value.isPlaying ? Icons.pause : Icons.play_arrow,
        ),
      ),
    );
  }
}
```

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-07-21. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/plugins/play-video.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/cookbook/plugins/play-video\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/plugins/play-video.md "Report an issue with this page").

----
url: https://docs.flutter.dev/ui/adaptive-responsive/best-practices
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Best practices for adaptive design](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

Recommended best practices for adaptive design include:

## Design considerations

[#](#design-considerations)

### Break down your widgets

[#](#break-down-your-widgets)

While designing your app, try to break down large, complex widgets into smaller, simpler ones.

Refactoring widgets can reduce the complexity of adopting an adaptive UI by sharing core pieces of code. There are other benefits as well:

* On the performance side, having lots of small `const` widgets improves rebuild times over having large, complex widgets.
* Flutter can reuse `const` widget instances, while a larger complex widget has to be set up for every rebuild.
* From a code health perspective, organizing your UI into smaller bite sized pieces helps keep the complexity of each `Widget` down. A less-complex `Widget` is more readable, easier to refactor, and less likely to have surprising behavior.

To learn more, check out the 3 steps of adaptive design in [General approach](/ui/adaptive-responsive/general).

### Design to the strengths of each form factor

[#](#design-to-the-strengths-of-each-form-factor)

Beyond screen size, you should also spend time considering the unique strengths and weaknesses of different form factors. It isn't always ideal for your multiplatform app to offer identical functionality everywhere. Consider whether it makes sense to focus on specific capabilities, or even remove certain features, on some device categories.

For example, mobile devices are portable and have cameras, but they aren't well suited for detailed creative work. With this in mind, you might focus more on capturing content and tagging it with location data for a mobile UI, but focus on organizing or manipulating that content for a tablet or desktop UI.

Another example is leveraging the web's extremely low barrier for sharing. If you're deploying a web app, decide which [deep links](/ui/navigation/deep-linking) to support, and design your navigation routes with those in mind.

The key takeaway here is to think about what each platform does best and see if there are unique capabilities you can leverage.

### Solve touch first

[#](#solve-touch-first)

Building a great touch UI can often be more difficult than a traditional desktop UI due, in part, to the lack of input accelerators like right-click, scroll wheel, or keyboard shortcuts.

One way to approach this challenge is to focus initially on a great touch-oriented UI. You can still do most of your testing using the desktop target for its iteration speed. But, remember to switch frequently to a mobile device to verify that everything feels right.

After you have the touch interface polished, you can tweak the visual density for mouse users, and then layer on all the additional inputs. Approach these other inputs as accelerator—alternatives that make a task faster. The important thing to consider is what a user expects when using a particular input device, and work to reflect that in your app.

## Implementation details

[#](#implementation-details)

### Don't lock the orientation of your app.

[#](#dont-lock-the-orientation-of-your-app)

An adaptive app should look good on windows of different sizes and shapes. While locking an app to portrait mode on phones can help narrow the scope of a minimum viable product, it can increase the effort required to make the app adaptive in the future.

For example, the assumption that phones will only render your app in a full screen portrait mode is not a guarantee. Multi window app support is becoming common, and foldables have many use cases that work best with multiple apps running side by side.

If you absolutely must lock your app in portrait mode (but don't), use the `Display` API instead of something like `MediaQuery` to get the physical dimensions of the screen.

To summarize:

* Locked screens can be [an accessibility issue](https://www.w3.org/WAI/WCAG21/Understanding/orientation.html) for some users
* Android large format tiers require portrait and landscape support at the [lowest level](https://developer.android.com/docs/quality-guidelines/large-screen-app-quality#T3-8).
* Android devices can [override a locked screen](https://developer.android.com/guide/topics/large-screens/large-screen-compatibility-mode#per-app_overrides)
* Apple guidelines say [aim to support both orientations](https://www.w3.org/WAI/WCAG21/Understanding/orientation.html)

### Avoid device orientation-based layouts

[#](#avoid-device-orientation-based-layouts)

Avoid using `MediaQuery`'s orientation field or `OrientationBuilder` near the top of your widget tree to switch between different app layouts. This is similar to the guidance of not checking device types to determine screen size. The device's orientation also doesn't necessarily inform you of how much space your app window has.

Instead, use `MediaQuery`'s `sizeOf` or `LayoutBuilder`, as discussed in the [General approach](/ui/adaptive-responsive/general) page. Then use adaptive breakpoints like the ones that [Material](https://m3.material.io/foundations/layout/applying-layout/window-size-classes) recommends.

### Don't gobble up all of the horizontal space

[#](#dont-gobble-up-all-of-the-horizontal-space)

Apps that use the full width of the window to display boxes or text fields don't play well when these apps run on large screens.

To learn how to avoid this, check out [Layout with GridView](/ui/adaptive-responsive/large-screens#layout-with-gridview).

### Avoid checking for hardware types

[#](#avoid-checking-for-hardware-types)

Avoid writing code that checks whether the device you're running on is a "phone" or a "tablet", or any other type of device when making layout decisions.

What space your app is actually given to render in isn't always tied to the full screen size of the device. Flutter can run on many different platforms, and your app might be running in a resizeable window on ChromeOS, side by side with another app on tablets in a multi-window mode, or even in a picture-in-picture on phones. Therefore, device type and app window size aren't really strongly connected.

Instead, use `MediaQuery` to get the size of the window your app is currently running in.

This isn't only helpful for UI code. To learn how abstracting out device capabilities can help your business logic code, check out the 2022 Google I/O talk, [Flutter lessons for federated plugin development](https://www.youtube.com/watch?v=GAnSNplNpCA).

### Support a variety of input devices

[#](#support-a-variety-of-input-devices)

Apps should support basic mice, trackpads, and keyboard shortcuts. The most common user flows should support keyboard navigation to ensure accessibility. In particular, your app follow accessible best practices for keyboards on large devices.

The Material library provides widgets with excellent default behavior for touch, mouse, and keyboard interaction.

To learn how to add this support to custom widgets, check out [User input & accessibility](/ui/adaptive-responsive/input).

### Restore List state

[#](#restore-list-state)

To maintain the scroll position in a list that doesn't change its layout when the device's orientation changes, use the [`PageStorageKey`](https://api.flutter.dev/flutter/widgets/PageStorageKey-class.html) class. [`PageStorageKey`](https://api.flutter.dev/flutter/widgets/PageStorageKey-class.html) persists the widget state in storage after the widget is destroyed and restores state when recreated.

You can see an example of this in the [Wonderous app](https://github.com/gskinnerTeam/flutter-wonderous-app/blob/8a29d6709668980340b1b59c3d3588f123edd4d8/lib/ui/screens/wonder_events/widgets/_events_list.dart#L64), where it stores the list's state in the `SingleChildScrollView` widget.

If the `List` widget changes its layout when the device's orientation changes, you might have to do a bit of math ([example](https://github.com/gskinnerTeam/flutter-wonderous-app/blob/34e49a08084fbbe69ed67be948ab00ef23819313/lib/ui/screens/collection/widgets/_collection_list.dart#L39)) to change the scroll position on screen rotation.

## Save app state

[#](#save-app-state)

Apps should retain or restore [app state](https://developer.android.com/jetpack/compose/state#store-state) as the device rotates, changes window size, or folds and unfolds. By default, an app should maintain state.

If your app loses state during device configuration, verify that the plugins and native extensions that your app uses support the device type, such as a large screen. Some native extensions might lose state when the device changes position.

For more information on a real-world case where this occurred, check out [Problem: Folding/unfolding causes state loss](https://blog.flutter.dev/developing-flutter-apps-for-large-screens-53b7b0e17f10#:~:text=Problem%3A%20Folding/Unfolding%20causes%20state%2Dloss) in [Developing Flutter apps for Large screens](https://blog.flutter.dev/developing-flutter-apps-for-large-screens-53b7b0e17f10), a post on the Flutter blog.

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-07-31. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/ui/adaptive-responsive/best-practices.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/ui/adaptive-responsive/best-practices\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/ui/adaptive-responsive/best-practices.md "Report an issue with this page").

----
url: https://docs.flutter.dev/ui/accessibility
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Accessibility](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

## Background

[#](#background)

Ensuring that apps are accessible to a broad range of users is an essential part of building a high-quality app. Applications that are poorly designed create barriers to people of all ages. The [UN Convention on the Rights of Persons with Disabilities](https://social.desa.un.org/issues/disability/crpd/article-9-accessibility) states the moral and legal imperative to ensure universal access to information systems; countries around the world enforce accessibility as a requirement; and companies recognize the business advantages of maximizing access to their services.

We strongly encourage you to include an accessibility checklist as a key criteria before shipping your app. Flutter is committed to supporting developers in making their apps more accessible, and includes first-class framework support for accessibility in addition to that provided by the underlying operating system, including:

[UI Design and styling](/ui/accessibility/ui-design-and-styling)

[Assistive Technologies (Screen Reader) supports](/ui/accessibility/assistive-technologies)

## Accessibility regulations

[#](#accessibility-regulations)

Accessibility standards and regulations help ensure that products are accessible to people with disabilities. Many of these have been enacted into laws and policies, making them requirements for products and services.

* **WCAG 2**: [Web Content Accessibility Guidelines (WCAG) 2](https://www.w3.org/WAI/standards-guidelines/wcag/) is an internationally recognized standard for making web content more accessible to people with disabilities. It is a stable, technical standard developed by the World Wide Web Consortium (W3C).

* **EN 301 549**: [EN 301 549](https://www.etsi.org/deliver/etsi_en/301500_301599/301549/03.02.01_60/en_301549v030201p.pdf) is the European harmonized standard for accessibility requirements for Information and Communication Technology (ICT) products and services.

* **VPAT**: The [Voluntary Product Accessibility Template (VPAT)](https://www.itic.org/policy/accessibility/vpat) is a free template that translates accessibility requirements and standards into actionable testing criteria for products and services.

Laws around the world require digital content and services to be accessible to people with disabilities. In the U.S., the [Americans with Disabilities Act (ADA)](https://www.ada.gov/) prohibits discrimination in public accommodations. [Section 508 of the Rehabilitation Act ](https://www.section508.gov/)requires federal agencies and their contractors to meet WCAG standards for all ICT.

In the EU, the [European Accessibility Act (EAA)](https://commission.europa.eu/strategy-and-policy/policies/justice-and-fundamental-rights/disability/union-equality-strategy-rights-persons-disabilities-2021-2030/european-accessibility-act_en) requires a wide range of public and private sector services to be accessible, primarily using the [EN 301 549](https://www.etsi.org/deliver/etsi_en/301500_301599/301549/03.02.01_60/en_301549v030201p.pdf) as its technical basis.

## Building with accessibility in mind

[#](#building-with-accessibility-in-mind)

Ensuring that your app can be used by everyone means building accessibility into it from the start. For some apps, that's easier said than done. In the video below, two of our engineers take a mobile app from a dire accessibility state to one that takes advantage of Flutter's built-in widgets to offer a dramatically more accessible experience.

[Watch on YouTube in a new tab: "Building Flutter apps with accessibility in mind"](https://www.youtube.com/watch/bWbBgbmAdQs)

## Accessibility release checklist

[#](#accessibility-release-checklist)

Here is a non-exhaustive list of things to consider as you prepare your app for release.

* **Active interactions**. Ensure that all active interactions do something. Any button that can be pushed should do something when pushed. For example, if you have a no-op callback for an `onPressed` event, change it to show a `SnackBar` on the screen explaining which control you just pushed.
* **Screen reader testing**. The screen reader should be able to describe all controls on the page when you tap on them, and the descriptions should be intelligible. Test your app with [TalkBack](https://support.google.com/accessibility/android/answer/6283677?hl=en) (Android) and [VoiceOver](https://www.apple.com/lae/accessibility/iphone/vision/) (iOS).
* **Contrast ratios**. We encourage you to have a contrast ratio of at least 4.5:1 between controls or text and the background, with the exception of disabled components. Images should also be vetted for sufficient contrast.
* **Context switching**. Nothing should change the user's context automatically while typing in information. Generally, the widgets should avoid changing the user's context without some sort of confirmation action.
* **Tappable targets**. All tappable targets should be at least 48x48 pixels.
* **Errors**. Important actions should be able to be undone. In fields that show errors, suggest a correction if possible.
* **Color vision deficiency testing**. Controls should be usable and legible in colorblind and grayscale modes.
* **Scale factors**. The UI should remain legible and usable at very large scale factors for text size and display scaling.

## Learn more

[#](#learn-more)

To learn more about Flutter and accessibility, check out the following articles written by community members:

* [A deep dive into Flutter's accessibility widgets](https://medium.com/flutter-community/a-deep-dive-into-flutters-accessibility-widgets-eb0ef9455bc)
* [Flutter: Crafting a great experience for screen readers](https://blog.gskinner.com/archives/2022/09/flutter-crafting-a-great-experience-for-screen-readers.html)

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-07-31. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/ui/accessibility/index.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/ui/accessibility\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/ui/accessibility/index.md "Report an issue with this page").

----
url: https://docs.flutter.dev/app-architecture/design-patterns/result
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Error handling with Result objects](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

Dart provides a built-in error handling mechanism with the ability to throw and catch exceptions.

As mentioned in the [Error handling documentation](https://dart.dev/language/error-handling), Dart's exceptions are unhandled exceptions. This means that methods that throw exceptions don’t need to declare them, and calling methods aren't required to catch them either.

This can lead to situations where exceptions are not handled properly. In large projects, developers might forget to catch exceptions, and the different application layers and components could throw exceptions that aren’t documented. This can lead to errors and crashes.

In this guide, you will learn about this limitation and how to mitigate it using the *result* pattern.

## Error flow in Flutter applications

[#](#error-flow-in-flutter-applications)

Applications following the [Flutter architecture guidelines](/app-architecture) are usually composed of view models, repositories, and services, among other parts. When a function in one of these components fails, it should communicate the error to the calling component.

Typically, that's done with exceptions. For example, an API client service failing to communicate with the remote server might throw an HTTP Error Exception. The calling component, for example a Repository, would have to either capture this exception or ignore it and let the calling view model handle it.

This can be observed in the following example. Consider these classes:

* A service, `ApiClientService`, performs API calls to a remote service.
* A repository, `UserProfileRepository`, provides the `UserProfile` provided by the `ApiClientService`.
* A view model, `UserProfileViewModel`, uses the `UserProfileRepository`.

The `ApiClientService` contains a method, `getUserProfile`, that throws exceptions in certain situations:

* The method throws an `HttpException` if the response code isn’t 200.
* The JSON parsing method throws an exception if the response isn't formatted correctly.
* The HTTP client might throw an exception due to networking issues.

The following code tests for a variety of possible exceptions:

dart

```
class ApiClientService {
  // ···
​
  Future<UserProfile> getUserProfile() async {
    try {
      final request = await client.get(_host, _port, '/user');
      final response = await request.close();
      if (response.statusCode == 200) {
        final stringData = await response.transform(utf8.decoder).join();
        return UserProfile.fromJson(jsonDecode(stringData));
      } else {
        throw const HttpException('Invalid response');
      }
    } finally {
      client.close();
    }
  }
}
```

The `UserProfileRepository` doesn’t need to handle the exceptions from the `ApiClientService`. In this example, it just returns the value from the API Client.

dart

```
class UserProfileRepository {
  // ···
​
  Future<UserProfile> getUserProfile() async {
    return await _apiClientService.getUserProfile();
  }
}
```

Finally, the `UserProfileViewModel` should capture all exceptions and handle the errors.

This can be done by wrapping the call to the `UserProfileRepository` with a try-catch:

dart

```
class UserProfileViewModel extends ChangeNotifier {
  // ···
​
  Future<void> load() async {
    try {
      _userProfile = await userProfileRepository.getUserProfile();
      notifyListeners();
    } on Exception catch (exception) {
      // handle exception
    }
  }
}
```

In reality, a developer might forget to properly capture exceptions and end up with the following code. It compiles and runs, but crashes if one of the exceptions mentioned previously occurs:

dart

```
class UserProfileViewModel extends ChangeNotifier {
  // ···
​
  Future<void> load() async {
    _userProfile = await userProfileRepository.getUserProfile();
    notifyListeners();
  }
}
```

You can attempt to solve this by documenting the `ApiClientService`, warning about the possible exceptions it might throw. However, since the view model doesn’t use the service directly, other developers working in the codebase might miss this information.

## Using the result pattern

[#](#using-the-result-pattern)

An alternative to throwing exceptions is to wrap the function output in a `Result` object.

When the function runs successfully, the `Result` contains the returned value. However, if the function does not complete successfully, the `Result` object contains the error.

A `Result` is a [`sealed`](https://dart.dev/language/class-modifiers#sealed) class that can either subclass `Ok` or the `Error` class. Return the successful value with the subclass `Ok`, and the captured error with the subclass `Error`.

The following code shows a sample `Result` class that has been simplified for demo purposes. A full implementation is at the end of this page.

dart

```
/// Utility class that simplifies handling errors.
///
/// Return a [Result] from a function to indicate success or failure.
///
/// A [Result] is either an [Ok] with a value of type [T]
/// or an [Error] with an [Exception].
///
/// Use [Result.ok] to create a successful result with a value of type [T].
/// Use [Result.error] to create an error result with an [Exception].
sealed class Result<T> {
  const Result();
​
  /// Creates an instance of Result containing a value
  factory Result.ok(T value) => Ok(value);
​
  /// Create an instance of Result containing an error
  factory Result.error(Exception error) => Error(error);
}
​
/// Subclass of Result for values
final class Ok<T> extends Result<T> {
  const Ok(this.value);
​
  /// Returned value in result
  final T value;
}
​
/// Subclass of Result for errors
final class Error<T> extends Result<T> {
  const Error(this.error);
​
  /// Returned error in result
  final Exception error;
}
```

In this example, the `Result` class uses a generic type `T` to represent any return value, which can be a primitive Dart type like `String` or an `int` or a custom class like `UserProfile`.

### Creating a `Result` object

[#](#creating-a-result-object)

For functions using the `Result` class to return values, instead of a value, the function returns a `Result` object containing the value.

For example, in the `ApiClientService`, `getUserProfile` is changed to return a `Result`:

dart

```
class ApiClientService {
  // ···
​
  Future<Result<UserProfile>> getUserProfile() async {
    // ···
  }
}
```

Instead of returning the `UserProfile` directly, it returns a `Result` object containing a `UserProfile`.

To facilitate using the `Result` class, it contains two named constructors, `Result.ok` and `Result.error`. Use them to construct the `Result` depending on desired output. As well, capture any exceptions thrown by the code and wrap them into the `Result` object.

For example, here the `getUserProfile()` method has been changed to use the `Result` class:

dart

```
class ApiClientService {
  // ···
​
  Future<Result<UserProfile>> getUserProfile() async {
    try {
      final request = await client.get(_host, _port, '/user');
      final response = await request.close();
      if (response.statusCode == 200) {
        final stringData = await response.transform(utf8.decoder).join();
        return Result.ok(UserProfile.fromJson(jsonDecode(stringData)));
      } else {
        return const Result.error(HttpException('Invalid response'));
      }
    } on Exception catch (exception) {
      return Result.error(exception);
    } finally {
      client.close();
    }
  }
}
```

The original return statement was replaced with a statement that returns the value using `Result.ok`. The `throw HttpException()` was replaced with a statement that returns `Result.error(HttpException())`, wrapping the error into a `Result`. As well, the method is wrapped with a `try-catch` block to capture any exceptions thrown by the Http client or the JSON parser into a `Result.error`.

The repository class also needs to be modified, and instead of returning a `UserProfile` directly, now it returns a `Result<UserProfile>`.

dart

```
Future<Result<UserProfile>> getUserProfile() async {
  return await _apiClientService.getUserProfile();
}
```

### Unwrapping the Result object

[#](#unwrapping-the-result-object)

Now the view model doesn't receive the `UserProfile` directly, but instead it receives a `Result` containing a `UserProfile`.

This forces the developer implementing the view model to unwrap the `Result` to obtain the `UserProfile`, and avoids having uncaught exceptions.

dart

```
class UserProfileViewModel extends ChangeNotifier {
  // ···
​
  UserProfile? userProfile;
​
  Exception? error;
​
  Future<void> load() async {
    final result = await userProfileRepository.getUserProfile();
    switch (result) {
      case Ok<UserProfile>():
        userProfile = result.value;
      case Error<UserProfile>():
        error = result.error;
    }
    notifyListeners();
  }
}
```

The `Result` class is implemented using a `sealed` class, meaning it can only be of type `Ok` or `Error`. This allows the code to evaluate the result with a [switch result or expression](https://dart.dev/language/branches#switch-statements).

In the `Ok<UserProfile>` case, obtain the value using the `value` property.

In the `Error<UserProfile>` case, obtain the error object using the `error` property.

## Improving control flow

[#](#improving-control-flow)

Wrapping code in a `try-catch` block ensures that thrown exceptions are caught and not propagated to other parts of the code.

Consider the following code.

dart

```
class UserProfileRepository {
  // ···
​
  Future<UserProfile> getUserProfile() async {
    try {
      return await _apiClientService.getUserProfile();
    } catch (e) {
      try {
        return await _databaseService.createTemporaryUser();
      } catch (e) {
        throw Exception('Failed to get user profile');
      }
    }
  }
}
```

In this method, the `UserProfileRepository` attempts to obtain the `UserProfile` using the `ApiClientService`. If it fails, it tries to create a temporary user in a `DatabaseService`.

Because either service method can fail, the code must catch the exceptions in both cases.

This can be improved using the `Result` pattern:

dart

```
Future<Result<UserProfile>> getUserProfile() async {
  final apiResult = await _apiClientService.getUserProfile();
  if (apiResult is Ok) {
    return apiResult;
  }
​
  final databaseResult = await _databaseService.createTemporaryUser();
  if (databaseResult is Ok) {
    return databaseResult;
  }
​
  return Result.error(Exception('Failed to get user profile'));
}
```

In this code, if the `Result` object is an `Ok` instance, then the function returns that object; otherwise, it returns `Result.Error`.

## Putting it all together

[#](#putting-it-all-together)

In this guide, you have learned how to use a `Result` class to return result values.

The key takeaways are:

* `Result` classes force the calling method to check for errors, reducing the amount of bugs caused by uncaught exceptions.
* `Result` classes help improve control flow compared to try-catch blocks.
* `Result` classes are `sealed` and can only return `Ok` or `Error` instances, allowing the code to unwrap them with a switch statement.

Below you can find the full `Result` class as implemented in the [Compass App example](https://github.com/flutter/samples/tree/main/compass_app) for the [Flutter architecture guidelines](/app-architecture).

dart

````
/// Utility class that simplifies handling errors.
///
/// Return a [Result] from a function to indicate success or failure.
///
/// A [Result] is either an [Ok] with a value of type [T]
/// or an [Error] with an [Exception].
///
/// Use [Result.ok] to create a successful result with a value of type [T].
/// Use [Result.error] to create an error result with an [Exception].
///
/// Evaluate the result using a switch statement:
/// ```dart
/// switch (result) {
///   case Ok(): {
///     print(result.value);
///   }
///   case Error(): {
///     print(result.error);
///   }
/// }
/// ```
sealed class Result<T> {
  const Result();
​
  /// Creates a successful [Result], completed with the specified [value].
  const factory Result.ok(T value) = Ok._;
​
  /// Creates an error [Result], completed with the specified [error].
  const factory Result.error(Exception error) = Error._;
}
​
/// A successful [Result] with a returned [value].
final class Ok<T> extends Result<T> {
  const Ok._(this.value);
​
  /// The returned value of this result.
  final T value;
​
  @override
  String toString() => 'Result<$T>.ok($value)';
}
​
/// An error [Result] with a resulting [error].
final class Error<T> extends Result<T> {
  const Error._(this.error);
​
  /// The resulting error of this result.
  final Exception error;
​
  @override
  String toString() => 'Result<$T>.error($error)';
}
````

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/app-architecture/design-patterns/result.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/app-architecture/design-patterns/result\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/app-architecture/design-patterns/result.md "Report an issue with this page").

----
url: https://docs.flutter.dev/platform-integration/ios/setup.md
----

\# Set up iOS development > Configure your development environment to run, build, and deploy Flutter apps for iOS devices. Learn how to set up your development environment to run, build, and deploy Flutter apps for iOS devices. :::note If you haven't set up Flutter already, visit and follow \[Install Flutter]\[] first. If you've already installed Flutter, ensure that it's \[up to date]\[]. ::: \[Install Flutter]: /install \[up to date]: /install/upgrade ## Set up iOS tooling {: #set-up-tooling} With Xcode, you can run Flutter apps on an iOS physical device or on the iOS Simulator. 1.

### Install Xcode

If you haven't done so already, \[install and set up the latest version of Xcode]\[xcode]. If you've already installed Xcode, update it to the latest version using the same installation method you used originally. 1.

### Set up Xcode command-line tools

To configure the Xcode command-line tools to use the version of Xcode you installed, run the following command in your preferred terminal: \`\`\`console $ sudo sh -c 'xcode-select -s /Applications/Xcode.app/Contents/Developer && xcodebuild -runFirstLaunch' \`\`\` If you downloaded Xcode elsewhere or need to use a different version, replace \`/Applications/Xcode.app\` with the path to your Xcode installation. 1.

### Agree to the Xcode licenses

After you've set up Xcode and configured its command-line tools, agree to the Xcode licenses. 1. Open your preferred terminal. 1. Run the following command to review and sign the Xcode licenses. \`\`\`console $ sudo xcodebuild -license \`\`\` 1. Read and agree to all necessary licenses. Before agreeing to the terms of each license, read each with care. 1.

### Download prerequisite tooling

To download iOS platform support and the latest iOS Simulator runtimes, run the following command in your preferred terminal. \`\`\`console $ xcodebuild -downloadPlatform iOS \`\`\` 1.

### Install CocoaPods

To support \[Flutter plugins]\[] that use native iOS or macOS code, install the latest version of \[CocoaPods]\[]. Install CocoaPods by following the \[CocoaPods installation guide]\[]. If you've already installed CocoaPods, update it by following the \[CocoaPods update guide]\[]. {: .steps} \[xcode]: https\://developer.apple.com/xcode/ \[cocoapods]: https\://guides.cocoapods.org/using/getting-started.html#installation \[Flutter plugins]: /packages-and-plugins/developing-packages#types \[CocoaPods installation guide]: https\://guides.cocoapods.org/using/getting-started.html#installation \[CocoaPods update guide]: https\://guides.cocoapods.org/using/getting-started.html#updating-cocoapods ## Set up an iOS device {: #set-up-devices} It's easiest to first set up the simulator, but be sure to also test your app on a physical device. Start the simulator (renamed to Device Hub on Xcode 27): \`\`\`console $ open -a DeviceHub \`\`\` On Xcode 26 or earlier, start the Simulator app: \`\`\`console $ open -a Simulator \`\`\` If you need to install a simulator for a different OS version, check out \[Downloading and installing additional Xcode components]\[] on the Apple Developer site. \[Downloading and installing additional Xcode components]: https\://developer.apple.com/documentation/xcode/downloading-and-installing-additional-xcode-components Set up each iOS device on which you want to test. 1.

### Configure your physical iOS device

1\. Attach your iOS device to the USB port on your Mac. 1. On first connecting an iOS device to your Mac, your device displays the \*\*Trust this computer?\*\* dialog. 1. Click \*\*Trust\*\*. !\[Trust Mac]\(/assets/images/docs/setup/trust-computer.png) 1.

### Enable Developer Mode

Apple requires enabling \*\*\[Developer Mode]\[]\*\* on the device to protect against malicious software. :::note If the \*\*Developer Mode\*\* option is not visible under \*\*Settings > Privacy & Security\*\*, connect the device to your Mac, unlock it, and trust the computer. Open Xcode (or run \`flutter run\`) to initiate device configuration, which makes the option appear. ::: 1. Tap on \*\*Settings\*\* > \*\*Privacy & Security\*\* > \*\*Developer Mode\*\*. 1. Tap to toggle \*\*Developer Mode\*\* to \*\*On\*\*. 1. Restart the device. 1. When the \*\*Turn on Developer Mode?\*\* dialog appears, tap \*\*Turn On\*\*. 1.

### Create a developer code signing certificate

To send your app to a physical iOS device, \_even\_ for testing, you must establish trust between your Mac and the device. In addition to trusting the device when that popup appears, you must upload a signed developer certificate to your device. To create a signed development certificate, you need an Apple ID. If you don't have one, \[create one]\[apple-account-new]. You must also enroll in the \[Apple Developer program]\[] and create an \[Apple Developer account]\[]. If you're just \_testing\_ your app on an iOS device, a personal Apple Developer account is free and works. :::note Apple Developer program When you want to \_deploy\_ your app to the App Store, you must upgrade your personal Apple Developer account to a professional account. ::: 1.

### Prepare the device

1\. Find the \*\*VPN & Device Management\*\* menu under \*\*Settings\*\*. Toggle your certificate to \*\*Enable\*\*. :::note If you can't find the \*\*VPN & Device Management\*\* menu, run your app on your iOS device once, then try again. ::: 1. Under the \*\*Developer App\*\* heading, you should find your certificate. 1. Tap the certificate. 1. Tap \*\*Trust "\<certificate>"\*\*. 1. When the dialog displays, tap \*\*Trust\*\*. If the \*\*codesign wants to access key...\*\* dialog appears: 1. Enter your macOS password. 1. Tap \*\*Always Allow\*\*. {: .steps} \[apple-account-new]: https\://support.apple.com/en-us/108647 \[Developer Mode]: https\://developer.apple.com/documentation/xcode/enabling-developer-mode-on-a-device \[Apple Developer program]: https\://developer.apple.com/programs/ \[Apple Developer account]: https\://developer.apple.com/account --- ## Start developing for iOS {: #start-developing} \*\*Congratulations.\*\* Now that you've set up iOS development for Flutter, you can continue your Flutter learning journey while testing on iOS or begin improving integration with iOS.

----
url: https://docs.flutter.dev/ai/genui/get-started.md
----

\# Get started with the GenUI SDK for Flutter > Learn how to use GenUI SDK for Flutter and add it to your existing Flutter app. This guide explains how to get started with GenUI SDK for Flutter and its series of packages. The SDK's key components are described in the \[main components]\[] page. :::experimental The \`genui\` package is in alpha and is likely to change. ::: Use the following instructions to add \[\`genui\`]\[] to your Flutter app. The code examples show how to perform the instructions on a brand new app created by running \[\`flutter create\`]\[], but you can follow the same steps for your existing Flutter app. \[\`genui\`]: https\://pub.dev/packages/genui \[main components]: /ai/genui/components \[\`flutter create\`]: /reference/create-new-app ## Configure your agent provider The \`genui\` package can connect to a variety of agent providers. Available providers include the following: \*\*Firebase AI Logic\*\* : Useful for production apps where interactions with the LLM are all in your Flutter client, without requiring a server. Firebase also makes it easier to ship your AI features securely since Firebase handles the management of your Gemini API key. \*\*GenUI A2UI\*\* : Useful for client/server architectures where your agent is running on the server. \*\*Build your own\*\* : You can also build your own adapter to connect to your preferred LLM provider. Expect more from us and the community soon. To connect to Gemini using the Firebase AI Logic SDK, follow these instructions: 1. \[Create a new Firebase project]\[] using the Firebase Console. 2. \[Enable the Gemini API]\[] for that project. 3. Follow the first three steps in \[Firebase's Flutter setup guide]\[] to add Firebase to your app. 4. Use \`dart pub add\` to add \`genui\` and \`firebase\_ai\` as dependencies in your \`pubspec.yaml\` file: \`\`\`console $ dart pub add genui firebase\_ai \`\`\` 5. In your app's \`main\` method, ensure that the widget bindings are initialized and then initialize Firebase: \`\`\`dart import 'package:material\_ui/material\_ui.dart'; import 'package:firebase\_core/firebase\_core.dart'; import 'firebase\_options.dart'; void main() async { WidgetsFlutterBinding.ensureInitialized(); await Firebase.initializeApp( options: DefaultFirebaseOptions.currentPlatform, ); runApp(const MyApp()); } \`\`\` 6. Create an instance of the Firebase AI Logic generative model and wrap it with your \`SurfaceController\` and \`A2uiTransportAdapter\`: \`\`\`dart import 'package:genui/genui.dart'; import 'package:firebase\_ai/firebase\_ai.dart'; final catalog = Catalog(\[ // ... ]); final catalogs = \[catalog]; final surfaceController = SurfaceController(catalogs: catalogs); final promptBuilder = PromptBuilder.chat( catalog: catalog, systemPromptFragments: \['You are a helpful assistant.'], ); final model = FirebaseAI.vertexAI().generativeModel( model: 'gemini-3.5-flash', systemInstruction: Content.system(promptBuilder.systemPromptJoined()), ); // The Conversation wires transport -> controller internally. late final A2uiTransportAdapter transportAdapter; transportAdapter = A2uiTransportAdapter(onSend: (message) async { // final stream = model.generateContentStream(...); // await for (final chunk in stream) { // transportAdapter.addChunk(chunk.text ?? ''); // } }); final conversation = Conversation( controller: surfaceController, transport: transportAdapter, ); \`\`\` \[Create a new Firebase project]: https\://support.google.com/appsheet/answer/10104995 \[Enable the Gemini API]: https\://firebase.google.com/docs/gemini-in-firebase/set-up-gemini \[Firebase's Flutter setup guide]: https\://firebase.google.com/docs/flutter/setup \[\`firebase\_ai\_logic\`]: https\://pub.dev/packages/firebase\_ai\_logic An integration package for \[\`genui\`]\[] and the \[A2UI Streaming UI Protocol]\[]. This package allows Flutter applications to connect to an Agent-to-Agent (A2UI) server and render dynamic user interfaces generated by an AI agent using the \`genui\` framework. The main components in this package include: \* \`A2uiAgentConnector\`: Handles the low-level web socket communication with the A2A server, including sending messages and parsing stream events. \* \`AgentCard\`: A data class that holds metadata about the connected AI agent. Follow these instructions: 1. Set up dependencies: Use \`dart pub add\` to add \`genui\` and \`genui\_a2a\` as dependencies in your \`pubspec.yaml\` file. \`\`\`console $ dart pub add genui genui\_a2a \`\`\` 2. Initialize \`SurfaceController\`: Set up \`SurfaceController\` with your widget \`Catalog\`s. 3. Create \`A2uiTransportAdapter\`: Instantiate \`A2uiTransportAdapter\` to parse the messages. 4. Create \`A2uiAgentConnector\`: Instantiate \`A2uiAgentConnector\`, providing the A2A server URI. 5. Create \`Conversation\`: Pass the adapter and controller to the \`Conversation\`. 6. Render with \`Surface\`: Use \`Surface\` widgets in your UI to display the agent-generated content. 7. Send Messages: Use \`connector.connectAndSend\` or \`Conversation.sendMessage\` to send user input to the agent-generated content. \`\`\`dart import 'package:material\_ui/material\_ui.dart'; import 'package:genui/genui.dart'; import 'package:genui\_a2a/genui\_a2a.dart'; import 'package:logging/logging.dart'; void main() { // Setup logging. Logger.root.level = Level.ALL; Logger.root.onRecord.listen((record) { print('${record.level.name}: ${record.time}: ${record.message}'); if (record.error != null) { print(record.error); } if (record.stackTrace != null) { print(record.stackTrace); } }); runApp(const GenUIExampleApp()); } class GenUIExampleApp extends StatelessWidget { const GenUIExampleApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( title: 'A2UI Example', theme: ThemeData( primarySwatch: Colors.blue, ), home: const ChatScreen(), ); } } class ChatScreen extends StatefulWidget { const ChatScreen({super.key}); @override State createState() => \_ChatScreenState(); } class \_ChatScreenState extends State { final TextEditingController \_textController = TextEditingController(); final SurfaceController \_surfaceController = SurfaceController(catalogs: \[BasicCatalogItems.asCatalog()]); late final A2uiTransportAdapter \_transportAdapter; late final Conversation \_uiAgent; late final A2uiAgentConnector \_connector; final List \_messages = \[]; @override void initState() { super.initState(); // The Conversation wires transport -> controller internally. \_transportAdapter = A2uiTransportAdapter(onSend: (message) async { // Implement sending to LLM if needed, or handled by connector }); \_connector = A2uiAgentConnector( // TODO: Replace with your A2A server URL. url: Uri.parse('http\://localhost:8080'), ); \_uiAgent = Conversation( controller: \_surfaceController, transport: \_transportAdapter, ); // Listen for messages from the remote agent. \_connector.stream.listen(\_surfaceController.handleMessage); } @override void dispose() { \_textController.dispose(); \_uiAgent.dispose(); \_transportAdapter.dispose(); \_surfaceController.dispose(); \_connector.dispose(); super.dispose(); } void \_handleSubmitted(String text) async { if (text.isEmpty) return; \_textController.clear(); final message = ChatMessage.user(text); setState(() { \_messages.insert(0, message); }); final responseText = await \_connector.connectAndSend(message); // Handling response depends on your app's logic } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('A2UI Example'), ), body: Column( children: \[ Expanded( child: ListView\.builder( padding: const EdgeInsets.all(8.0), reverse: true, itemBuilder: (\_, int index) => \_buildMessage(\_messages\[index]), itemCount: \_messages.length, ), ), const Divider(height: 1.0), Container( decoration: BoxDecoration(color: Theme.of(context).cardColor), child: \_buildTextComposer(), ), // Surface for the main AI-generated UI: SizedBox( height: 300, child: Surface( surfaceContext: \_surfaceController.contextFor('main\_surface'), ), ), ], ), ); } Widget \_buildMessage(ChatMessage message) { return Container( margin: const EdgeInsets.symmetric(vertical: 10.0), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: \[ Container( margin: const EdgeInsets.only(right: 16.0), child: CircleAvatar(child: Text(message.role == ChatMessageRole.user ? 'U' : 'A')), ), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: \[ Text(message.role == ChatMessageRole.user ? 'User' : 'Agent', style: const TextStyle(fontWeight: FontWeight.bold)), Container( margin: const EdgeInsets.only(top: 5.0), child: Text(message.parts.whereType().map((e) => e.text).join('\n')), ), ], ), ), ], ), ); } Widget \_buildTextComposer() { return IconTheme( data: IconThemeData(color: Theme.of(context).colorScheme.secondary), child: Container( margin: const EdgeInsets.symmetric(horizontal: 8.0), child: Row( children: \[ Flexible( child: TextField( controller: \_textController, onSubmitted: \_handleSubmitted, decoration: const InputDecoration.collapsed(hintText: 'Send a message'), ), ), Container( margin: const EdgeInsets.symmetric(horizontal: 4.0), child: IconButton( icon: const Icon(Icons.send), onPressed: () => \_handleSubmitted(\_textController.text), ), ), ], ), ), ); } } \`\`\` The \[example]\[] directory on pub.dev contains a complete application demonstrating how to use this package. \[example]: https\://pub.dev/packages/genui\_a2a/example \[A2UI Streaming UI Protocol]: https\://a2ui.org/ To use \`genui\` with another agent provider, follow that provider's SDK documentation to implement a connection, and stream its results into an \`A2uiTransportAdapter\`. :::warning \`PromptBuilder.chat()\` generates a system prompt that might be \*\*3,000–5,000+ tokens\*\* long, which can exceed the context window of on-device or small models. If you are targeting an on-device LLM, consider: - Writing a compact custom system prompt that covers only the A2UI \`createSurface\` to \`updateComponents\` flow. - Using \`systemPromptFragments\` to pass only the portions of the schema your use case requires. ::: ## Create the connection to an agent If you build your Flutter project for iOS or macOS, add this key to your \`{ios,macos}/Runner/\*.entitlements\` file(s) to enable outbound network requests: \`\`\`xml ... com.apple.security.network.client \`\`\` Next, use the following instructions to connect your app to your chosen agent provider. 1. Create a \`SurfaceController\`, and provide it with the catalogs of widgets that you want to make available to the agent. Create an \`A2uiTransportAdapter\` to parse messages and connect it. 2. Create a \`PromptBuilder\`, and provide it with a system instruction and the tools (functions you want the agent to be able to invoke). You should always include the tools provided by \`SurfaceController\`, but feel free to include others. Add this to your LLM system prompt. 3. Create a \`Conversation\` using the instances of \`SurfaceController\` and \`A2uiTransportAdapter\`. Your app will primarily interact with this object to get things done. For example: \`\`\`dart class \_MyHomePageState extends State { late final SurfaceController \_surfaceController; late final A2uiTransportAdapter \_transportAdapter; late final Conversation \_conversation; @override void initState() { super.initState(); // Create a SurfaceController with a widget catalog. // The BasicCatalogItems contain basic widgets for text, markdown, and images. \_surfaceController = SurfaceController(catalogs: \[BasicCatalogItems.asCatalog()]); // The Conversation wires transport -> controller internally. \_transportAdapter = A2uiTransportAdapter(onSend: (message) async { // Implement sending to LLM and pipe chunks back. }); final catalog = BasicCatalogItems.asCatalog(); final promptBuilder = PromptBuilder.chat( catalog: catalog, systemPromptFragments: \[ ''' You are an expert in creating funny riddles. Every time I give you a word, you should generate UI that displays one new riddle related to that word. Each riddle should have both a question and an answer. ''' ], ); // ... initialize your LLM Client of choice using promptBuilder.systemPromptJoined() // Create the Conversation to orchestrate everything. \_conversation = Conversation( controller: \_surfaceController, transport: \_transportAdapter, ); // Listen for surface lifecycle events: \_conversation.events.listen((event) { if (event is ConversationSurfaceAdded) { \_onSurfaceAdded(event); } else if (event is ConversationSurfaceRemoved) { \_onSurfaceDeleted(event); } }); } @override void dispose() { \_textController.dispose(); \_conversation.dispose(); \_transportAdapter.dispose(); super.dispose(); } } \`\`\` ## Send messages and display the agent's responses Send a request to the agent using the \`sendRequest\` method in the \`Conversation\` class, or by directly streaming into your LLM Client and pumping the result stream to the adapter by using \`\_transportAdapter.addChunk\`. To receive and display generated UI: 1. Listen to the \`events\` stream in \`Conversation\` to track the addition and removal of UI surfaces as they are generated. These events include a \_surface ID\_ for each surface. 2. Build a \`Surface\` widget for each active surface using the surface IDs received in the previous step. For example: \`\`\`dart class \_MyHomePageState extends State { // ... final \_textController = TextEditingController(); final \_surfaceIds = \[]; // Send a request containing the user's \[text] to the agent. void \_sendMessage(String text) async { if (text.trim().isEmpty) return; // await \_conversation.sendRequest(ChatMessage.user(TextPart(text))); } // Invoked by the events stream listener when a new // UI surface is generated. Here, the ID is stored so the // build method can create a Surface to display it. void \_onSurfaceAdded(ConversationSurfaceAdded update) { setState(() { \_surfaceIds.add(update.surfaceId); }); } // Invoked by the events stream listener when a UI surface is removed. void \_onSurfaceDeleted(ConversationSurfaceRemoved update) { setState(() { \_surfaceIds.remove(update.surfaceId); }); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( backgroundColor: Theme.of(context).colorScheme.inversePrimary, title: Text(widget.title), ), body: Column( children: \[ Expanded( child: ListView\.builder( itemCount: \_surfaceIds.length, itemBuilder: (context, index) { // For each surface, create a Surface to display it. final id = \_surfaceIds\[index]; return Surface(surfaceContext: \_surfaceController.contextFor(id)); }, ), ), SafeArea( child: Padding( padding: const EdgeInsets.symmetric(horizontal: 16.0), child: Row( children: \[ Expanded( child: TextField( controller: \_textController, decoration: const InputDecoration( hintText: 'Enter a message', ), ), ), const SizedBox(width: 16), ElevatedButton( onPressed: () { // Send the user's text to the agent. \_sendMessage(\_textController.text); \_textController.clear(); }, child: const Text('Send'), ), ], ), ), ), ], ), ); } } \`\`\` ## Add your own widgets to the catalog {:#custom-widgets} For your convenience, you can use the provided core catalog of widgets. However, most production apps will want to define a custom catalog of widgets. To add your own widgets, use the following instructions. 1. Depend on the \`json\_schema\_builder\` package Use \`dart pub add\` to add \`json\_schema\_builder\` as a dependency in your \`pubspec.yaml\` file: \`\`\`console $ dart pub add json\_schema\_builder \`\`\` 2. Create the new widget's schema Each catalog item needs a schema that defines the data required to populate it. Using the \`json\_schema\_builder\` package, define one for the new widget. \`\`\`dart import 'package:json\_schema\_builder/json\_schema\_builder.dart'; import 'package:material\_ui/material\_ui.dart'; import 'package:genui/genui.dart'; final \_schema = S.object( properties: { 'question': S.string(description: 'The question part of a riddle.'), 'answer': S.string(description: 'The answer part of a riddle.'), }, required: \['question', 'answer'], ); \`\`\` 3. Create a \`CatalogItem\` Each \`CatalogItem\` represents a type of widget that the agent is allowed to generate. To do that, it combines a name, a schema, and a builder function that produces the widgets that compose the generated UI. \`\`\`dart final riddleCard = CatalogItem( name: 'RiddleCard', dataSchema: \_schema, widgetBuilder: (itemContext) { final json = itemContext.data as Map; final question = json\['question'] as String; final answer = json\['answer'] as String; final context = itemContext.buildContext; return Container( constraints: const BoxConstraints(maxWidth: 400), decoration: BoxDecoration(border: Border.all()), padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: \[ Text(question, style: Theme.of(context).textTheme.headlineMedium), const SizedBox(height: 8.0), Text(answer, style: Theme.of(context).textTheme.headlineSmall), ], ), ); }, ); \`\`\` 4. Add the \`CatalogItem\` to the catalog Include your catalog items when instantiating \`SurfaceController\`. \`\`\`dart \_surfaceController = SurfaceController( catalogs: \[BasicCatalogItems.asCatalog().copyWith(newItems: \[riddleCard])], ); \`\`\` 5. Update the system instruction to use the new widget To make sure that the agent knows to use your new widget, tell the system instruction how and when to do so. Provide the name from the \`CatalogItem\` when you do. \`\`\`dart final promptBuilder = PromptBuilder.chat( catalog: catalog, systemPromptFragments: \[ ''' You are an expert in creating funny riddles. Every time I give you a word, generate a RiddleCard that displays one new riddle related to that word. Each riddle should have both a question and an answer. ''' ], ); // Pass promptBuilder.systemPromptJoined() to your LLM Config \`\`\` {:.steps} ## Data model and data binding A core concept in \`genui\` is the \`DataModel\`, a centralized, observable store for all dynamic UI state. Instead of each widget managing its own state, its state is stored in the \`DataModel\`. Widgets are \_bound\_ to data in this model. When data in the model changes, only the widgets that depend on that specific piece of data are rebuilt. This is achieved through a \`DataContext\` object passed to each widget's builder function. ### Binding to the data model To bind a widget's property to the data model, specify a special JSON object in the data sent from the AI. This object can contain standard JSON primitives (for static values) or an object with a \`path\` property (to bind to a value in the data model). For example, to display a user's name in a \`Text\` widget, the AI would generate: \`\`\`json { "id": "welcome-text", "component": "Text", "text": "Welcome to GenUI", "variant": "h1" } \`\`\` ### Image \`\`\`json { "id": "image", "component": "Image", "url": "https\://example.com/image.png", "variant": "mediumFeature" } \`\`\` ### Updating the data model Input widgets, like \`TextField\`, update the DataModel directly. When the user types in a text field that is bound to \`/user/name\`, the \`DataModel\` updates, and any other widgets bound to that same path will automatically rebuild to show the new value. This reactive data flow simplifies state management and creates a powerful, high-bandwidth interaction loop between the user, the UI, and the AI. ## Next steps Check out the \[examples]\[] included in the \`genui\` repo. The \[travel app]\[] shows how to define your own widget catalog that the agent can use to generate domain-specific UI. If something is unclear or missing, please \[create an issue]\[]. \[examples]: https\://github.com/flutter/genui/blob/main/examples \[travel app]: https\://github.com/flutter/genui/blob/main/examples/travel\_app \[create an issue]: https\://github.com/flutter/genui/issues/new/choose ## System instructions The \`genui\` package gives the LLM a set of tools it can use to generate UI. To get the LLM to use these tools, the system instructions provided through \`PromptBuilder\` must explicitly tell it to do so. This is why the \[earlier example]\[instruction-example] includes a system instruction for the agent with the line "Every time I give you a word, you should generate UI that...": \`\`\`dart highlightLines=5-6 final promptBuilder = PromptBuilder.chat( catalog: catalog, systemPromptFragments: \[ ''' You are an expert in creating funny riddles. Every time I give you a word, you should generate UI that displays one new riddle related to that word. Each riddle should have both a question and an answer. ''' ], ); \`\`\` \[instruction-example]: /ai/genui/get-started#create-the-connection-to-an-agent ## Troubleshooting/FAQ {:#troubleshoot} ### How can I configure logging? To observe communication between your app and the agent, enable logging in your \`main\` method. \`\`\`dart import 'package:logging/logging.dart'; import 'package:genui/genui.dart'; final logger = configureLogging(level: Level.ALL); void main() async { logger.onRecord.listen((record) { debugPrint('${record.loggerName}: ${record.message}'); }); // Additional initialization of bindings and Firebase. } \`\`\` ### I'm getting errors about my minimum macOS/iOS version. Firebase has a \[minimum version requirement]\[] for Apple's platforms, which might be higher than Flutter's default. Check your \`Podfile\` (for iOS) and \`CMakeLists.txt\` (for macOS) to ensure that you're targeting a version that meets or exceeds Firebase's requirements. \[Create a new Firebase project]: https\://support.google.com/appsheet/answer/10104995 \[create an issue]: https\://github.com/flutter/genui/issues/new/choose \[Enable the Gemini API]: https\://firebase.google.com/docs/gemini-in-firebase/set-up-gemini \[examples]: https\://github.com/flutter/genui/blob/main/examples \[Firebase's Flutter setup guide]: https\://firebase.google.com/docs/flutter/setup \[\`genui\`]: https\://pub.dev/packages/genui \[Key components]: /ai/genui/components \[minimum version requirement]: https\://firebase.google.com/support/release-notes/ios

----
url: https://docs.flutter.dev/data-and-backend/networking.md
----

\# Networking > Internet network calls in Flutter. ## Cross-platform http networking The \[\`http\`]\[] package provides the simplest way to issue http requests. This package is supported on Android, iOS, macOS, Windows, Linux and the web. ## Platform notes Some platforms require additional steps, as detailed below. ### Android Android apps must \[declare their use of the internet]\[declare] in the Android manifest (\`AndroidManifest.xml\`): \`\`\`xml ... \`\`\` ### macOS macOS apps must allow network access in the relevant \`\*.entitlements\` files. \`\`\`xml com.apple.security.network.client \`\`\` Learn more about \[setting up entitlements]\[]. \[setting up entitlements]: /platform-integration/macos/building#setting-up-entitlements ## Samples For a practical sample of various networking tasks (incl. fetching data, WebSockets, and parsing data in the background) see the \[networking cookbook recipes]\(/cookbook/networking). \[declare]: https\://developer.android.com/training/basics/network-ops/connecting \[\`http\`]: https\://pub.dev/packages/http

----
url: https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.57.0
----

The 2.57.0 release of the Dart and Flutter DevTools includes the following changes among other general improvements. To learn more about DevTools, check out the [DevTools overview](https://docs.flutter.dev/tools/devtools).

## Network profiler updates

[#](#network-profiler-updates)

* Added a filter setting to hide HTTP-profiler socket data. [#9698](https://github.com/flutter/devtools/pull/9698)

## Logging updates

[#](#logging-updates)

* Added support for searching within the log details view (raw text mode). [#9712](https://github.com/flutter/devtools/pull/9712)

## App size tool updates

[#](#app-size-tool-updates)

* Added documentation links and improved handling for null files. [#9689](https://github.com/flutter/devtools/pull/9689)

## Full commit history

[#](#full-commit-history)

To find a complete list of changes in this release, check out the [DevTools git log](https://github.com/flutter/devtools/tree/v2.57.0).

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/release-notes/release-notes-2.57.0.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.57.0\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/release-notes/release-notes-2.57.0.md "Report an issue with this page").

----
url: https://docs.flutter.dev/cookbook/forms
----

[Retrieve the value of a text field](/cookbook/forms/retrieve-input)

[How to retrieve text from a text field.](/cookbook/forms/retrieve-input)

[Build a form with validation](/cookbook/forms/validation)

[How to build a form that validates input.](/cookbook/forms/validation)

[Focus and text fields](/cookbook/forms/focus)

[How focus works with text fields.](/cookbook/forms/focus)

[Create and style a text field](/cookbook/forms/text-input)

[How to implement a text field.](/cookbook/forms/text-input)

[Handle changes to a text field](/cookbook/forms/text-field-changes)

[How to detect changes to a text field.](/cookbook/forms/text-field-changes)

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/forms/index.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/cookbook/forms\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/forms/index.md "Report an issue with this page").

----
url: https://docs.flutter.dev/ui/animations/implicit-animations.md
----

\# Implicit animations > Where to find more information on using implicit animations in Flutter. With Flutter's \[animation library]\[], you can add motion and create visual effects for the widgets in your UI. One part of the library is an assortment of widgets that manage animations for you. These widgets are collectively referred to as \_implicit animations\_, or \_implicitly animated widgets\_, deriving their name from the \[\`ImplicitlyAnimatedWidget\`]\[] class that they implement. With implicit animations, you don't need to manage an \`AnimationController\` or \`Ticker\`. Instead, you pass a \`duration\` and an optional \`curve\` to an implicitly animated widget, such as \`AnimatedOpacity\`. When you update the widget with a new target value, such as after calling \`setState()\`, the widget automatically animates to that value:

<!--?code-excerpt "animation/implicit/lib/main.dart (fade-box-demo)"?-->

\`\`\`dart import 'package:material\_ui/material\_ui.dart'; class FadeBoxDemo extends StatefulWidget { const FadeBoxDemo({super.key}); @override State createState() => \_FadeBoxDemoState(); } class \_FadeBoxDemoState extends State { bool \_visible = true; @override Widget build(BuildContext context) { return Column( mainAxisAlignment: MainAxisAlignment.center, children: \[ AnimatedOpacity( opacity: \_visible ? 1 : 0, duration: const Duration(milliseconds: 500), curve: Curves.easeInOut, child: const FlutterLogo(size: 100), ), const SizedBox(height: 16), ElevatedButton( onPressed: () => setState(() => \_visible = !\_visible), child: const Text('Toggle opacity'), ), ], ); } } \`\`\` \*\*App source:\*\* \[implicit animation example]\[] When no built-in implicitly animated widget covers the property you want to animate, you can build a custom implicit animation with \[\`TweenAnimationBuilder\`]\[]. The following set of resources provides many ways to learn about implicit animations in Flutter. \[animation library]: https\://api.flutter.dev/flutter/animation/animation-library.html \[implicit animation example]: https\://github.com/flutter/website/tree/main/examples/animation/implicit ## Documentation \[Animations in Flutter codelab]\[] : Learn about implicit and explicit animations and get hands-on experience adding implicit animations to a complete Flutter app. \[\`AnimatedContainer\` sample]\[] : A step-by-step recipe for using the \[\`AnimatedContainer\`]\[] implicitly animated widget. \[Fade a widget in and out]\[] : A cookbook recipe showing how to fade a widget using \[\`AnimatedOpacity\`]\[]. \[\`TweenAnimationBuilder\`]\[] API page : Create custom implicit animations for any property using a \`Tween\` and a \`builder\` callback. \[\`ImplicitlyAnimatedWidget\`]\[] API page : All implicit animations extend the \`ImplicitlyAnimatedWidget\` class. \[Animations in Flutter codelab]: https\://codelabs.developers.google.com/advanced-flutter-animations \[\`AnimatedContainer\` sample]: /cookbook/animation/animated-container \[\`AnimatedContainer\`]: https\://api.flutter.dev/flutter/widgets/AnimatedContainer-class.html \[\`AnimatedOpacity\`]: https\://api.flutter.dev/flutter/widgets/AnimatedOpacity-class.html \[Fade a widget in and out]: /cookbook/animation/opacity-animation \[\`ImplicitlyAnimatedWidget\`]: https\://api.flutter.dev/flutter/widgets/ImplicitlyAnimatedWidget-class.html \[\`TweenAnimationBuilder\`]: https\://api.flutter.dev/flutter/widgets/TweenAnimationBuilder-class.html ## Flutter in Focus videos Flutter in Focus videos feature 5-10 minute tutorials with real code that cover techniques that every Flutter dev needs to know from top to bottom. The following videos cover topics that are relevant to implicit animations. ## The Boring Show Watch the Boring Show to follow Google engineers building apps from scratch in Flutter. The following episode covers using implicit animations in a news aggregator app. ## Widget of the Week videos A weekly series of short animated videos each showing the important features of one particular widget. In about 60 seconds, you'll see real code for each widget with a demo about how it works. The following Widget of the Week videos cover implicitly animated widgets:

----
url: https://docs.flutter.dev/cookbook/networking/web-sockets
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Communicate with WebSockets](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

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, connect to a [test WebSocket server](https://websocket.org/tools/websocket-echo-server/). The server sends back the same message you send to it. This recipe uses the following steps:

1. Connect to a WebSocket server.
2. Listen for messages from the server.
3. Send data to the server.
4. Close the WebSocket connection.

## 1. Connect to a WebSocket server

[#](#1-connect-to-a-websocket-server)

The [`web_socket_channel`](https://pub.dev/packages/web_socket_channel) 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.

In Flutter, use the following line to create a `WebSocketChannel` that connects to a server:

dart

```
final WebSocketChannel channel = WebSocketChannel.connect(
  Uri.parse('wss://echo.websocket.org'),
);
```

## 2. Listen for messages from the server

[#](#2-listen-for-messages-from-the-server)

Now that you've established a connection, listen to messages from the server.

After sending a message to the test server, it sends the same message back.

In this example, use a [`StreamBuilder`](https://api.flutter.dev/flutter/widgets/StreamBuilder-class.html) widget to listen for new messages, and a [`Text`](https://api.flutter.dev/flutter/widgets/Text-class.html) widget to display them.

dart

```
StreamBuilder(
  stream: channel.stream,
  builder: (context, snapshot) {
    return Text(snapshot.hasData ? '${snapshot.data}' : '');
  },
),
```

### How this works

[#](#how-this-works)

The `WebSocketChannel` provides a [`Stream`](https://api.flutter.dev/flutter/dart-async/Stream-class.html) of messages from the server.

The `Stream` class is a fundamental part of the `dart:async` package. It provides a way to listen to async events from a data source. Unlike `Future`, which returns a single async response, the `Stream` class can deliver many events over time.

The [`StreamBuilder`](https://api.flutter.dev/flutter/widgets/StreamBuilder-class.html) 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

[#](#3-send-data-to-the-server)

To send data to the server, `add()` messages to the `sink` provided by the `WebSocketChannel`.

dart

```
channel.sink.add('Hello!');
```

### How this works

[#](#how-this-works-1)

The `WebSocketChannel` provides a [`StreamSink`](https://api.flutter.dev/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.

## 4. Close the WebSocket connection

[#](#4-close-the-websocket-connection)

After you're done using the WebSocket, close the connection:

dart

```
channel.sink.close();
```

## Complete example

[#](#complete-example)

dart

```
import 'package:material_ui/material_ui.dart';
import 'package:web_socket_channel/web_socket_channel.dart';
​
void main() => runApp(const MyApp());
​
class MyApp extends StatelessWidget {
  const MyApp({super.key});
​
  @override
  Widget build(BuildContext context) {
    const title = 'WebSocket Demo';
    return const MaterialApp(
      title: title,
      home: MyHomePage(title: title),
    );
  }
}
​
class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});
​
  final String title;
​
  @override
  State<MyHomePage> createState() => _MyHomePageState();
}
​
class _MyHomePageState extends State<MyHomePage> {
  final TextEditingController _controller = TextEditingController();
  final WebSocketChannel _channel = WebSocketChannel.connect(
    Uri.parse('wss://echo.websocket.org'),
  );
​
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text(widget.title)),
      body: Padding(
        padding: const EdgeInsets.all(20),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Form(
              child: TextFormField(
                controller: _controller,
                decoration: const InputDecoration(labelText: 'Send a message'),
              ),
            ),
            const SizedBox(height: 24),
            StreamBuilder(
              stream: _channel.stream,
              builder: (context, snapshot) {
                return Text(snapshot.hasData ? '${snapshot.data}' : '');
              },
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _sendMessage,
        tooltip: 'Send message',
        child: const Icon(Icons.send),
      ), // This trailing comma makes auto-formatting nicer for build methods.
    );
  }
​
  void _sendMessage() {
    if (_controller.text.isNotEmpty) {
      _channel.sink.add(_controller.text);
    }
  }
​
  @override
  void dispose() {
    _channel.sink.close();
    _controller.dispose();
    super.dispose();
  }
}
```

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-09-29. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/networking/web-sockets.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/cookbook/networking/web-sockets\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/networking/web-sockets.md "Report an issue with this page").

----
url: https://docs.flutter.dev/platform-integration/web/building
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Building a web application with Flutter](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

This page provides an overview of how to configure, run, and build a web application using Flutter.

## Requirements

[#](#requirements)

Before you can build a web application with Flutter, make sure that you have the [Flutter SDK](/install) and a web browser installed. Visit [Set up web development for Flutter](/platform-integration/web/setup) instructions for details.

## Set up a Flutter project

[#](#set-up-a-flutter-project)

To set up your project, you can create a new Flutter project or add web support to an existing project.

### Create a new project

[#](#create-a-new-project)

To create a new app that includes web support, run the following command:

```
$ flutter create my_app
```

### Add web support to an existing project

[#](#add-web-support-to-an-existing-project)

If you already have a project, run the `flutter create` command in your project directory:

```
$ flutter create . --platforms web
```

This creates a `web/` directory containing the web assets used to bootstrap and run your Flutter app.

## Run your app

[#](#run-your-app)

Check out the following sections to run your app.

### Run your app from the command line

[#](#run-your-app-from-the-command-line)

Select [Chrome](https://www.google.com/chrome/) as your app's target device to run and debug a Flutter web app:

```
$ flutter run -d chrome
```

You can also choose Chrome as a target device in your IDE.

If you prefer, you can use the `edge` device type on Windows, or use `web-server` to navigate to a local URL in the browser of your choice.

[]()

### Run your app using WebAssembly

[#](#run-your-app-using-webassembly)

You can pass the `--wasm` flag to run your app using WebAssembly:

```
$ flutter run -d chrome --wasm
```

To learn more about WebAssembly compilation and server requirements, check out [Support for WebAssembly (Wasm)](/platform-integration/web/wasm).

### Disable hot reload in VS Code

[#](#disable-hot-reload-in-vs-code)

To temporarily disable hot reload support from VS Code, update your [`launch.json` file](https://code.visualstudio.com/docs/debugtest/debugging-configuration) file with the flag `--no-web-experimental-hot-reload`.

```
"configurations": [
    ...
    {
      "name": "Flutter for web (hot reload disabled)",
      "type": "dart",
      "request": "launch",
      "program": "lib/main.dart",
      "args": [
        "-d",
        "chrome",
        "--no-web-experimental-hot-reload",
      ]
    }
  ]
```

### Disable hot reload from the command line

[#](#disable-hot-reload-from-the-command-line)

If you use `flutter run` from the command line, you can temporarily disable hot reload on the web with the following command:

```
flutter run -d chrome --no-web-experimental-hot-reload
```

### Use hot reload in DartPad

[#](#use-hot-reload-in-dartpad)

Hot reload is also enabled in DartPad with a new "Reload" button. The feature is only available if Flutter is detected in the running application. You can begin a hot reloadable session by selecting a sample app provided by DartPad.

## Build your app

[#](#build-your-app)

See the following sections to build your app.

### Build your app from the command line

[#](#build-your-app-from-the-command-line)

Run the following command to generate a release build:

```
$ flutter build web
```

### Build your app using WebAssembly

[#](#build-your-app-using-webassembly)

You can also pass the `--wasm` flag to build your app using WebAssembly:

```
$ flutter build web --wasm
```

This populates a `build/web` directory with built files, including an `assets` directory, which need to be served together.

To learn more about how to deploy these assets to the web, visit [Build and release a web app](/deployment/web). For answers to other common questions, visit the [Web FAQ](/platform-integration/web/faq).

If you encounter errors when compiling your app with Wasm, check out [Diagnosing Wasm compilation errors](/platform-integration/web/wasm#diagnosing-wasm-compilation-errors).

## Debugging

[#](#debugging)

Use [Flutter DevTools](/tools/devtools) for the following tasks:

* [Debugging](/tools/devtools/debugger)
* [Logging](/tools/devtools/logging)
* [Running Flutter inspector](/tools/devtools/inspector)

Use [Chrome DevTools](https://developers.google.com/web/tools/chrome-devtools) for the following tasks:

* [Generating event timeline](https://developers.google.com/web/tools/chrome-devtools/evaluate-performance/performance-reference)
* [Analyzing performance](https://developers.google.com/web/tools/chrome-devtools/evaluate-performance)—make sure to use a profile build

## Testing

[#](#testing)

Use [widget tests](/testing/overview#widget-tests) or integration tests. To learn more about running integration tests in a browser, check out the [Integration testing](/testing/integration-tests#test-in-a-web-browser) page.

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-08-04. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/platform-integration/web/building.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/platform-integration/web/building\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/platform-integration/web/building.md "Report an issue with this page").

----
url: https://docs.flutter.dev/learn/pathway/tutorial/change-notifier
----

Learn to create a ViewModel with ChangeNotifier and manage loading, success, and error states.

layers Create a ViewModel with ChangeNotifier

toggle\_on Manage loading, success, and error states

notifications\_active Signal UI updates with notifyListeners

***

## Steps

1### Introductionkeyboard\_arrow\_up

When developers talk about state-management in Flutter, they're essentially referring to the pattern by which your app updates the data it needs to render correctly and then tells Flutter to re-render the UI with that new data.

In MVVM, this responsibility falls to the ViewModel layer, which sits between and connects your UI to your Model layer. In Flutter, ViewModels use Flutter's `ChangeNotifier` class to notify the UI when data changes.

To use [`ChangeNotifier`](https://api.flutter.dev/flutter/foundation/ChangeNotifier-class.html), extend it in your state management class to gain access to the `notifyListeners()` method, which triggers UI rebuilds when called.

2### Create the basic view model structurekeyboard\_arrow\_up

Create the `ArticleViewModel` class with its basic structure and state properties:

dart

```
class ArticleViewModel extends ChangeNotifier {
  final ArticleModel model;
  Summary? summary;
  Exception? error;
  bool isLoading = false;
​
  ArticleViewModel(this.model);
}
```

The `ArticleViewModel` holds three pieces of state:

* `summary`: The current Wikipedia article data.
* `error`: Any error that occurred during data fetching.
* `isLoading`: A flag to show progress indicators.

3### Add constructor initializationkeyboard\_arrow\_up

Update the constructor to automatically fetch content when the `ArticleViewModel` is created:

dart

```
class ArticleViewModel extends ChangeNotifier {
  final ArticleModel model;
  Summary? summary;
  Exception? error;
  bool isLoading = false;
​
  ArticleViewModel(this.model) {
    fetchArticle();
  }
​
  // Methods will be added next.
  Future<void> fetchArticle() async {}
}
```

This constructor initialization provides immediate content when a `ArticleViewModel` object is created. Because constructors can't be asynchronous, it delegates initial content fetching to a separate method.

4### Set up the `fetchArticle` methodkeyboard\_arrow\_up

Add the `fetchArticle` method that fetches data and manages state updates:

dart

```
class ArticleViewModel extends ChangeNotifier {
  final ArticleModel model;
  Summary? summary;
  Exception? error;
  bool isLoading = false;
​
  ArticleViewModel(this.model) {
    fetchArticle();
  }
​
  Future<void> fetchArticle() async {
    isLoading = true;
    notifyListeners();
​
    // TODO: Add data fetching logic
​
    isLoading = false;
    notifyListeners();
  }
}
```

The ViewModel updates the `isLoading` property and calls `notifyListeners()` to inform the UI of the update. When the operation completes, it toggles the property back. When you build the UI, you'll use this `isLoading` property to show a loading indicator while fetching a new article.

5### Retrieve an article from the `ArticleModel`keyboard\_arrow\_up

Complete the `fetchArticle` method to fetch an article summary. Use a [try-catch block](https://dart.dev/language/error-handling#catch) to gracefully handle network errors and store error messages that the UI can display to users. The method clears previous errors on success and clears the previous article summary on error to maintain a consistent state.

dart

```
class ArticleViewModel extends ChangeNotifier {
  final ArticleModel model;
  Summary? summary;
  Exception? error;
  bool isLoading = false;
​
  ArticleViewModel(this.model) {
    fetchArticle();
  }
​
  Future<void> fetchArticle() async {
    isLoading = true;
    notifyListeners();
    try {
      summary = await model.getRandomArticleSummary();
      error = null; // Clear any previous errors.
    } on HttpException catch (e) {
      error = e;
      summary = null;
    }
    isLoading = false;
    notifyListeners();
  }
}
```

6### Test the ViewModelkeyboard\_arrow\_up

Before building the full UI, test that your HTTP requests work by printing results to the console. First, update the `fetchArticle` method to print the results:

dart

```
Future<void> fetchArticle() async {
  isLoading = true;
  notifyListeners();
  try {
    summary = await model.getRandomArticleSummary();
    print('Article loaded: ${summary!.titles.normalized}'); // Temporary
    error = null; // Clear any previous errors.
  } on HttpException catch (e) {
    print('Error loading article: ${e.message}'); // Temporary
    error = e;
    summary = null;
  }
  isLoading = false;
  notifyListeners();
}
```

Then, update the `MainApp` widget to create the `ArticleViewModel`, which calls the `fetchArticle` method on creation:

dart

```
class MainApp extends StatelessWidget {
  const MainApp({super.key});
​
  @override
  Widget build(BuildContext context) {
    // Instantiate your `ArticleViewModel` to test its HTTP requests.
    final viewModel = ArticleViewModel(ArticleModel());
​
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: const Text('Wikipedia Flutter')),
        body: const Center(child: Text('Check console for article data')),
      ),
    );
  }
}
```

Hot reload your app and check your console output. You should see either an article title or an error message, which confirms that your Model and ViewModel are wired up correctly.

7### Reviewkeyboard\_arrow\_up

layers Created the ArticleViewModel with ChangeNotifier keyboard\_arrow\_up

The ViewModel sits between your UI and Model, managing state and connecting the two layers. By extending `ChangeNotifier`, your ViewModel gains the ability to notify listeners when data changes.

toggle\_on Managed loading, success, and error states keyboard\_arrow\_up

Your ViewModel tracks three pieces of state: `isLoading`, `summary`, and `error`. Using `try` and `catch`, you handle network errors gracefully and maintain consistent state for each possible outcome.

notifications\_active Used notifyListeners to signal UI updates keyboard\_arrow\_up

Calling `notifyListeners()` tells any listening widgets to rebuild. You call it after setting `loading = true` and again after the operation completes. This is how you can implement reactive UI updates in Flutter.

8### Test yourselfkeyboard\_arrow\_up

### State Management Quiz

1 / 2

**What is a ChangeNotifier?**

1. A widget that displays notifications to the user.

   Not quite.

   ChangeNotifier is not a widget; it's a class for managing state.

2. A class that can notify listeners when its data changes, enabling reactive UI updates.

   That's right!

   ChangeNotifier provides the notifyListeners method to signal widgets to rebuild when state changes.

3. A built-in Dart class for sending push notifications.

   Not quite.

   ChangeNotifier is for in-app state management, not push notifications.

4. A type of animation controller in Flutter.

   Not quite.

   Animation controllers are separate; ChangeNotifier is for state management.

**What does calling `notifyListeners()` do in a ChangeNotifier?**

1. Saves the current state to local storage.

   Not quite.

   `notifyListeners()` signals UI updates; persistence requires separate implementation.

2. Tells any listening widgets to rebuild and reflect the new state.

   That's right!

   Calling `notifyListeners()` triggers a rebuild of all widgets listening to this ChangeNotifier.

3. Logs the state change to the console for debugging.

   Not quite.

   It doesn't log anything; it signals listeners to rebuild.

4. Resets all state properties to their default values.

   Not quite.

   `notifyListeners()` doesn't modify state; it just signals that state has changed.

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/learn/pathway/tutorial/change-notifier.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/learn/pathway/tutorial/change-notifier\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/learn/pathway/tutorial/change-notifier.md "Report an issue with this page").

----
url: https://docs.flutter.dev/cookbook/navigation/set-up-universal-links
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Set up universal links for iOS](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

Deep linking allows an app user to launch an app with a URI. This URI contains scheme, host, and path, and opens the app to a specific screen.

A *universal link*, a type of deep link exclusive to iOS devices, uses only the `http` or `https` protocols.

To set up universal links, you need to own a web domain. As a temporary solution, consider using [Firebase Hosting](https://firebase.google.com/docs/hosting) or [GitHub Pages](https://pages.github.com).

Once you've set up your deep links, you can validate them. To learn more, see [Validate deep links](/tools/devtools/deep-links).

## Create or modify a Flutter app

[#](#create-or-modify-a-flutter-app)

Write a Flutter app that can handle an incoming URL.

This example uses the [go\_router](https://pub.dev/packages/go_router) package to handle the routing. The Flutter team maintains the `go_router` package. It provides a simple API to handle complex routing scenarios.

1. To create a new application, type `flutter create <app-name>`.

   ```
   $ flutter create deeplink_cookbook
   ```

2. To include the `go_router` package as a dependency, run `flutter pub add`:

   ```
   $ flutter pub add go_router
   ```

3. To handle the routing, create a `GoRouter` object in the `main.dart` file:

   main.dart

   dart

   ```
   import 'package:material_ui/material_ui.dart';
   import 'package:go_router/go_router.dart';
   ​
   void main() => runApp(MaterialApp.router(routerConfig: router));
   ​
   /// This handles '/' and '/details'.
   final router = GoRouter(
     routes: [
       GoRoute(
         path: '/',
         builder: (_, _) => Scaffold(
           appBar: AppBar(title: const Text('Home Screen')),
         ),
         routes: [
           GoRoute(
             path: 'details',
             builder: (_, _) => Scaffold(
               appBar: AppBar(title: const Text('Details Screen')),
             ),
           ),
         ],
       ),
     ],
   );
   ```

## Adjust iOS build settings

[#](#adjust-ios-build-settings)

1. Launch Xcode.

2. Open the `ios/Runner.xcworkspace` file inside the Flutter project's `ios` folder.

### Add associated domains

[#](#add-associated-domains)

* [Xcode](#150-tab-panel)
* [Other editors](#151-tab-panel)

1. Launch Xcode if necessary.

2. Click the top-level **Runner**.

3. In the Editor, click the **Runner** target.

4. Click **Signing & Capabilities**.

5. To add a new domain, click **+ Capability** under **Signing & Capabilities**.

6. Click **Associated Domains**.

7. In the **Associated Domains** section, click **+**.

8. Enter `applinks:<web domain>`. Replace `<web domain>` with your own domain name.

1) Open the `ios/Runner/Runner.entitlements` XML file in your preferred editor.

2) Add an associated domain inside the `<dict>` tag.

   xml

   ```
   <?xml version="1.0" encoding="UTF-8"?>
   <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
   <plist version="1.0">
   <dict>
     <key>com.apple.developer.associated-domains</key>
     <array>
       <string>applinks:example.com</string>
     </array>
   </dict>
   </plist>
   ```

3) Save the `ios/Runner/Runner.entitlements` file.

To check that the associated domains you created are available, perform the following steps:

1. Launch Xcode if necessary.

2. Click the top-level **Runner**.

3. In the Editor, click the **Runner** target.

4. Click **Signing & Capabilities**. The domains should appear in the **Associated Domains** section.

You have finished configuring the application for deep linking.

## Associate your app with your web domain

[#](#associate-your-app-with-your-web-domain)

You need to host an `apple-app-site-association` file in the web domain. This file tells the mobile browser which iOS application to open instead of the browser. To create the file, find the `appID` of the Flutter app you created in the previous section.

### Locate components of the `appID`

[#](#locate-components-of-the-appid)

Apple formats the `appID` as `<team id>.<bundle id>`.

* Locate the bundle ID in the Xcode project.
* Locate the team ID in the [developer account](https://developer.apple.com/account).

**For example:** Given a team ID of `S8QB4VV633` and a bundle ID of `com.example.deeplinkCookbook`, you would enter an `appID` entry of `S8QB4VV633.com.example.deeplinkCookbook`.

### Create and host `apple-app-site-association` JSON file

[#](#create-and-host-apple-app-site-association-json-file)

This file uses the JSON format. Don't include the `.json` file extension when you save this file. Per [Apple's documentation](https://developer.apple.com/documentation/xcode/supporting-associated-domains), this file should resemble the following content:

json

```
{
  "applinks": {
    "details": [
      {
        "appIDs": [
          "S8QB4VV633.com.example.deeplinkCookbook"
        ],
        "components": [
          {
            "/": "/*"
          }
        ]
      }
    ]
  }
}
```

1. Set one value in the `appIDs` array to `<team id>.<bundle id>`.

2. Set the `components` array to specify the allowed paths. In this example, the `/` key represents the URL path. The value `/*` matches and redirects any path to the Flutter app. If needed, customize the `components` array to match only the paths appropriate for your app.

3. Host the file at a URL that resembles the following structure.

   `<webdomain>/.well-known/apple-app-site-association`

4. Verify that your browser can access this file.

## Test the universal link

[#](#test-the-universal-link)

Test a universal link using a physical iOS device or the Simulator.

1. Before testing, install the Flutter app on the iOS device or Simulator, Use `flutter run` on the desired device.

   When complete, the Flutter app displays on the home screen of the iOS device or Simulator.

2. If you test using the Simulator, use the Xcode CLI:

   ```
   $ xcrun simctl openurl booted https://<web domain>/details
   ```

3. If you test with a physical iOS device:

   1. Launch the **Note** app.
   2. Type the URL in the **Note** app.
   3. Click the resulting link.

   If successful, the Flutter app launches and displays its details screen.

## Find the source code

[#](#find-the-source-code)

You can find the source code for the [deeplink\_cookbook](https://github.com/flutter/codelabs/tree/main/deeplink_cookbook) recipe in the GitHub repo.

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-09-29. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/navigation/set-up-universal-links.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/cookbook/navigation/set-up-universal-links\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/navigation/set-up-universal-links.md "Report an issue with this page").

----
url: https://docs.flutter.dev/ui/animations/staggered-animations.md
----

\# Staggered animations > How to write a staggered animation in Flutter. :::secondary What you'll learn \* A staggered animation consists of sequential or overlapping animations. \* To create a staggered animation, use multiple \`Animation\` objects. \* One \`AnimationController\` controls all of the \`Animation\` objects. \* Each \`Animation\` object specifies the animation during an \`Interval\`. \* For each property being animated, create a \`Tween\`. ::: :::tip Terminology If the concept of tweens or tweening is new to you, refer to the \[Animations in Flutter tutorial]\[]. ::: Staggered animations are a straightforward concept: visual changes happen as a series of operations, rather than all at once. The animation might be purely sequential, with one change occurring after the next, or it might partially or completely overlap. It might also have gaps, where no changes occur. This guide shows how to build a staggered animation in Flutter that applies a series of sequential and overlapping animations to a single widget. Tapping the screen begins an animation that changes opacity, size, shape, color, and padding. The following video demonstrates this staggered animation: In the video, you see the following animation of a single widget, which begins as a bordered blue square with slightly rounded corners. The square runs through changes in the following order: 1. Fades in 1. Widens 1. Becomes taller while moving upwards 1. Transforms into a bordered circle 1. Changes color to orange After running forward, the animation runs in reverse. :::secondary New to Flutter? This page assumes you know how to create a layout using Flutter's widgets. To learn more, visit \[Building Layouts in Flutter]\[]. ::: ## Basic structure of a staggered animation :::secondary What's the point? \* All of the animations are driven by the same \[\`AnimationController\`]\[]. \* Regardless of how long the animation lasts in real time, the controller's values must be between 0.0 and 1.0, inclusive. \* Each animation has an \[\`Interval\`]\[] between 0.0 and 1.0, inclusive. \* For each property that animates in an interval, create a \[\`Tween\`]\[]. The \`Tween\` specifies the start and end values for that property. \* The \`Tween\` produces an \[\`Animation\`]\[] object that is managed by the controller. ::: The following diagram shows the \`Interval\` objects used in the staggered animation example. You might notice the following characteristics: \* The opacity changes during the first 10% of the timeline. \* A tiny gap occurs between the change in opacity and the change in width. \* Nothing animates during the last 25% of the timeline. \* Increasing the padding makes the widget appear to rise upward. \* Increasing the border radius to \`75.0\` transforms the \`150.0\` x \`150.0\` square with rounded corners into a circle. \* The padding and height changes occur during the exact same interval, but they don't have to. !\[Diagram showing the interval specified for each motion]\(/assets/images/docs/ui/animations/StaggeredAnimationIntervals.png) To set up the animation: \* Create an \`AnimationController\` that manages all of the \`Animation\` objects. \* Create a \`Tween\` for each property being animated. \* The \`Tween\` defines a range of values. \* The \`animate()\` method of \`Tween\` takes a parent animation and produces an \`Animation\` for that property. \* Specify the interval with the \`curve\` property of \`CurvedAnimation\`. When the controlling animation's value changes, the new animation's value changes, triggering the UI to update. The following code creates a tween for the \`width\` property. It builds a \[\`CurvedAnimation\`]\[], specifying an eased curve. For other available predefined animation curves, consult \[\`Curves\`]\[].

<!--?code-excerpt "animation/staggered_animation/lib/main.dart (width)"?-->

\`\`\`dart width = Tween(begin: 50, end: 150).animate( CurvedAnimation( parent: controller, curve: const Interval(0.125, 0.250, curve: Curves.ease), ), ), \`\`\` The \`begin\` and \`end\` values don't have to be doubles. The following code builds the tween for the \`borderRadius\` property (which controls the roundness of the square's corners), using \`BorderRadius.circular()\`.

<!--?code-excerpt "animation/staggered_animation/lib/main.dart (border-radius)"?-->

\`\`\`dart borderRadius = BorderRadiusTween( begin: BorderRadius.circular(4), end: BorderRadius.circular(75), ).animate( CurvedAnimation( parent: controller, curve: const Interval(0.375, 0.500, curve: Curves.ease), ), ), \`\`\` ### Complete staggered animation Like all interactive widgets, the complete animation consists of a widget pair: a stateless and a stateful widget. The stateless widget specifies the \`Tween\` objects, defines the \`Animation\` objects, and provides a \`build()\` function responsible for building the animating portion of the widget tree. The stateful widget creates the controller, plays the animation, and builds the non-animating portion of the widget tree. The animation begins when a tap is detected anywhere on the screen. \*\*App source:\*\* \[staggered animation example]\[] ### Stateless widget: StaggerAnimation In the stateless widget, \`StaggerAnimation\`, the \`build()\` function instantiates an \[\`AnimatedBuilder\`]\[]—a general-purpose widget for building animations. The \`AnimatedBuilder\` builds a widget and configures it using the current values of the \`Animation\` objects. The example creates a function named \`\_buildAnimation()\` (which performs the actual UI updates), and assigns it to its \`builder\` property. \`AnimatedBuilder\` listens to notifications from the animation controller, marking the widget tree dirty as values change. For each tick of the animation, the values are updated, resulting in a call to \`\_buildAnimation()\`. :::tip This example uses an \`Opacity\` widget inside \`AnimatedBuilder\` to keep all animated properties in a single builder. When animating opacity on its own, prefer \[\`FadeTransition\`]\[] to avoid rebuilding the child widget on each frame. :::

<!--?code-excerpt "animation/staggered_animation/lib/main.dart (stagger-animation)"?-->

\`\`\`dart highlightLines=6,12,63-69,74,94-95 import 'dart:async'; import 'package:flutter/scheduler.dart' show timeDilation; import 'package:material\_ui/material\_ui.dart'; class StaggerAnimation extends StatelessWidget { StaggerAnimation({super.key, required this.controller}) : // Each animation defined here transforms its value during the subset // of the controller's duration defined by the animation's interval. // For example, the opacity animation transforms its value during // the first 10% of the controller's duration. opacity = Tween(begin: 0, end: 1).animate( CurvedAnimation( parent: controller, curve: const Interval(0, 0.100, curve: Curves.ease), ), ), width = Tween(begin: 50, end: 150).animate( CurvedAnimation( parent: controller, curve: const Interval(0.125, 0.250, curve: Curves.ease), ), ), height = Tween(begin: 50, end: 150).animate( CurvedAnimation( parent: controller, curve: const Interval(0.250, 0.375, curve: Curves.ease), ), ), padding = EdgeInsetsTween( begin: const EdgeInsets.only(bottom: 16), end: const EdgeInsets.only(bottom: 75), ).animate( CurvedAnimation( parent: controller, curve: const Interval(0.250, 0.375, curve: Curves.ease), ), ), borderRadius = BorderRadiusTween( begin: BorderRadius.circular(4), end: BorderRadius.circular(75), ).animate( CurvedAnimation( parent: controller, curve: const Interval(0.375, 0.500, curve: Curves.ease), ), ), color = ColorTween(begin: Colors.indigo\[100], end: Colors.orange\[400]) .animate( CurvedAnimation( parent: controller, curve: const Interval(0.500, 0.750, curve: Curves.ease), ), ); final Animation controller; final Animation opacity; final Animation width; final Animation height; final Animation padding; final Animation borderRadius; final Animation color; // This function is called each time the controller "ticks" a new frame. // When it runs, all of the animation's values will have been // updated to reflect the controller's current value. Widget \_buildAnimation(BuildContext context, Widget? child) { return Container( padding: padding.value, alignment: Alignment.bottomCenter, child: Opacity( opacity: opacity.value, child: Container( width: width.value, height: height.value, decoration: BoxDecoration( color: color.value, border: Border.all(color: Colors.indigo\[300]!, width: 3), borderRadius: borderRadius.value, ), ), ), ); } @override Widget build(BuildContext context) { return AnimatedBuilder(builder: \_buildAnimation, animation: controller); } } \`\`\` ### Stateful widget: StaggerDemo The stateful widget, \`StaggerDemo\`, creates the \`AnimationController\`, specifying a 2000 ms duration. It plays the animation, and builds the non-animating portion of the widget tree. The animation begins when a tap is detected on the screen. The animation runs forward, then backward. Taps during playback are ignored so the sequence can finish.

<!--?code-excerpt "animation/staggered_animation/lib/main.dart (stagger-demo)"?-->

\`\`\`dart highlightLines=1,28,32-33,40 class StaggerDemo extends StatefulWidget { const StaggerDemo({super.key}); @override State createState() => \_StaggerDemoState(); } class \_StaggerDemoState extends State with SingleTickerProviderStateMixin { late final AnimationController \_controller; @override void initState() { super.initState(); \_controller = AnimationController( duration: const Duration(milliseconds: 2000), vsync: this, ); } @override void dispose() { \_controller.dispose(); super.dispose(); } Future \_playAnimation() async { if (\_controller.isAnimating) return; try { await \_controller.forward().orCancel; await \_controller.reverse().orCancel; } on TickerCanceled { // The animation got canceled, probably because it was disposed of. } } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('Staggered Animation')), body: GestureDetector( behavior: HitTestBehavior.opaque, onTap: \_playAnimation, child: Center( child: Container( width: 300, height: 300, decoration: BoxDecoration( color: Colors.black.withValues(alpha: 0.1), border: Border.all(color: Colors.black.withValues(alpha: 0.5)), ), child: StaggerAnimation(controller: \_controller.view), ), ), ), ); } } void main() { timeDilation = 10; // 1 is normal animation speed. runApp(const MaterialApp(home: StaggerDemo())); } \`\`\` \[\`Animation\`]: https\://api.flutter.dev/flutter/animation/Animation-class.html \[\`AnimationController\`]: https\://api.flutter.dev/flutter/animation/AnimationController-class.html \[\`AnimatedBuilder\`]: https\://api.flutter.dev/flutter/widgets/AnimatedBuilder-class.html \[Animations in Flutter tutorial]: /ui/animations/tutorial \[Building Layouts in Flutter]: /ui/layout \[\`CurvedAnimation\`]: https\://api.flutter.dev/flutter/animation/CurvedAnimation-class.html \[\`Curves\`]: https\://api.flutter.dev/flutter/animation/Curves-class.html \[\`FadeTransition\`]: https\://api.flutter.dev/flutter/widgets/FadeTransition-class.html \[\`Interval\`]: https\://api.flutter.dev/flutter/animation/Interval-class.html \[staggered animation example]: https\://github.com/flutter/website/tree/main/examples/animation/staggered\_animation \[\`Tween\`]: https\://api.flutter.dev/flutter/animation/Tween-class.html

----
url: https://docs.flutter.dev/cookbook/testing/widget/tap-drag.md
----

\# Tap, drag, and enter text > How to test widgets for user interaction.

<!--?code-excerpt path-base="cookbook/testing/widget/tap_drag/"?-->

Many widgets not only display information, but also respond to user interaction. This includes buttons that can be tapped, and \[\`TextField\`]\[] for entering text. To test these interactions, you need a way to simulate them in the test environment. For this purpose, use the \[\`WidgetTester\`]\[] library. The \`WidgetTester\` provides methods for entering text, tapping, and dragging. \* \[\`enterText()\`]\[] \* \[\`tap()\`]\[] \* \[\`drag()\`]\[] 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()\`]\[] or \[\`pumpAndSettle()\`]\[] methods provided by the \`WidgetTester\`. This recipe uses the following steps: 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 For this example, create a basic todo app that tests three features: 1. Entering text into a \`TextField\`. 2. Tapping a \`FloatingActionButton\` to add the text to a list of todos. 3. Swiping-to-dismiss to remove the item from the list. 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]\[] \* \[Handle taps]\[] \* \[Create a basic list]\[] \* \[Implement swipe to dismiss]\[]

<!--?code-excerpt "test/main_test.dart (TodoList)"?-->

\`\`\`dart class TodoList extends StatefulWidget { const TodoList({super.key}); @override State createState() => \_TodoListState(); } class \_TodoListState extends State { static const \_appTitle = 'Todo List'; final todos = \[]; final controller = TextEditingController(); @override Widget build(BuildContext context) { return MaterialApp( title: \_appTitle, home: Scaffold( appBar: AppBar(title: const Text(\_appTitle)), body: Column( children: \[ TextField(controller: controller), Expanded( child: ListView\.builder( itemCount: todos.length, itemBuilder: (context, index) { final todo = todos\[index]; return Dismissible( key: Key('$todo$index'), onDismissed: (direction) => todos.removeAt(index), background: Container(color: Colors.red), child: ListTile(title: Text(todo)), ); }, ), ), ], ), floatingActionButton: FloatingActionButton( onPressed: () { setState(() { todos.add(controller.text); controller.clear(); }); }, child: const Icon(Icons.add), ), ), ); } } \`\`\` ## 2. Enter text in the text field Now that you have a todo app, begin writing the test. Start by entering text into the \`TextField\`. Accomplish this task by: 1. Building the widget in the test environment. 2. Using the \[\`enterText()\`]\[] method from the \`WidgetTester\`.

<!--?code-excerpt "test/main_steps.dart (TestWidgetStep2)"?-->

\`\`\`dart testWidgets('Add and remove a todo', (tester) async { // Build the widget await tester.pumpWidget(const TodoList()); // 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: \* \[Introduction to widget testing]\[] \* \[Finding widgets in a widget test]\[] ::: ## 3. Ensure tapping a button adds the todo After entering text into the \`TextField\`, ensure that tapping the \`FloatingActionButton\` adds the item to the list. This involves three steps: 1. Tap the add button using the \[\`tap()\`]\[] method. 2. Rebuild the widget after the state has changed using the \[\`pump()\`]\[] method. 3. Ensure that the list item appears on screen.

<!--?code-excerpt "test/main_steps.dart (TestWidgetStep3)"?-->

\`\`\`dart testWidgets('Add and remove a todo', (tester) async { // Enter text code... // Tap the add button. await tester.tap(find.byType(FloatingActionButton)); // Rebuild the widget after the state has changed. await tester.pump(); // Expect to find the item on screen. expect(find.text('hi'), findsOneWidget); }); \`\`\` ## 4. Ensure swipe-to-dismiss removes the todo 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()\`]\[] method to perform a swipe-to-dismiss action. 2. Use the \[\`pumpAndSettle()\`]\[] method to continually rebuild the widget tree until the dismiss animation is complete. 3. Ensure that the item no longer appears on screen.

<!--?code-excerpt "test/main_steps.dart (TestWidgetStep4)"?-->

\`\`\`dart testWidgets('Add and remove a todo', (tester) async { // Enter text and add the item... // Swipe the item to dismiss it. await tester.drag(find.byType(Dismissible), const Offset(500, 0)); // Build the widget until the dismiss animation ends. await tester.pumpAndSettle(); // Ensure that the item is no longer on screen. expect(find.text('hi'), findsNothing); }); \`\`\` ## Complete example

<!--?code-excerpt "test/main_test.dart"?-->

\`\`\`dart import 'package:flutter\_test/flutter\_test.dart'; import 'package:material\_ui/material\_ui.dart'; void main() { testWidgets('Add and remove a todo', (tester) async { // Build the widget. await tester.pumpWidget(const TodoList()); // Enter 'hi' into the TextField. await tester.enterText(find.byType(TextField), 'hi'); // Tap the add button. await tester.tap(find.byType(FloatingActionButton)); // Rebuild the widget with the new item. await tester.pump(); // Expect to find the item on screen. expect(find.text('hi'), findsOneWidget); // Swipe the item to dismiss it. await tester.drag(find.byType(Dismissible), const Offset(500, 0)); // Build the widget until the dismiss animation ends. await tester.pumpAndSettle(); // Ensure that the item is no longer on screen. expect(find.text('hi'), findsNothing); }); } class TodoList extends StatefulWidget { const TodoList({super.key}); @override State createState() => \_TodoListState(); } class \_TodoListState extends State { static const \_appTitle = 'Todo List'; final todos = \[]; final controller = TextEditingController(); @override Widget build(BuildContext context) { return MaterialApp( title: \_appTitle, home: Scaffold( appBar: AppBar(title: const Text(\_appTitle)), body: Column( children: \[ TextField(controller: controller), Expanded( child: ListView\.builder( itemCount: todos.length, itemBuilder: (context, index) { final todo = todos\[index]; return Dismissible( key: Key('$todo$index'), onDismissed: (direction) => todos.removeAt(index), background: Container(color: Colors.red), child: ListTile(title: Text(todo)), ); }, ), ), ], ), floatingActionButton: FloatingActionButton( onPressed: () { setState(() { todos.add(controller.text); controller.clear(); }); }, child: const Icon(Icons.add), ), ), ); } } \`\`\` \[Create a basic list]: /cookbook/lists/basic-list \[Create and style a text field]: /cookbook/forms/text-input \[\`drag()\`]: https\://api.flutter.dev/flutter/flutter\_test/WidgetController/drag.html \[\`enterText()\`]: https\://api.flutter.dev/flutter/flutter\_test/WidgetTester/enterText.html \[Finding widgets in a widget test]: /cookbook/testing/widget/finders \[Handle taps]: /cookbook/gestures/handling-taps \[Implement swipe to dismiss]: /cookbook/gestures/dismissible \[Introduction to widget testing]: /cookbook/testing/widget/introduction \[\`pump()\`]: https\://api.flutter.dev/flutter/flutter\_test/WidgetTester/pump.html \[\`pumpAndSettle()\`]: https\://api.flutter.dev/flutter/flutter\_test/WidgetTester/pumpAndSettle.html \[\`tap()\`]: https\://api.flutter.dev/flutter/flutter\_test/WidgetController/tap.html \[\`TextField\`]: https\://pub.dev/documentation/material\_ui/latest/material\_ui/TextField-class.html \[\`WidgetTester\`]: https\://api.flutter.dev/flutter/flutter\_test/WidgetTester-class.html

----
url: https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.57.0.md
----

\# DevTools 2.57.0 release notes The 2.57.0 release of the Dart and Flutter DevTools includes the following changes among other general improvements. To learn more about DevTools, check out the \[DevTools overview]\(https\://docs.flutter.dev/tools/devtools). ## Network profiler updates - Added a filter setting to hide HTTP-profiler socket data. \[#9698]\(https\://github.com/flutter/devtools/pull/9698) ## Logging updates - Added support for searching within the log details view (raw text mode). \[#9712]\(https\://github.com/flutter/devtools/pull/9712) !\[Search in log details]\(/assets/images/docs/tools/devtools/release-notes/images-2.57.0/log\_details\_search.png "Searching within the log details view") ## App size tool updates - Added documentation links and improved handling for null files. \[#9689]\(https\://github.com/flutter/devtools/pull/9689) ## Full commit history To find a complete list of changes in this release, check out the \[DevTools git log]\(https\://github.com/flutter/devtools/tree/v2.57.0).

----
url: https://docs.flutter.dev/cookbook/lists/grid-lists
----

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`](https://api.flutter.dev/flutter/widgets/GridView-class.html) widget.

The simplest way to get started using grids is by using the [`GridView.count()`](https://api.flutter.dev/flutter/widgets/GridView/GridView.count.html) constructor, because it allows you to specify how many rows or columns you'd like.

To visualize how `GridView` works, generate a list of 100 widgets that display their index in the list.

dart

```
GridView.count(
  // Create a grid with 2 columns.
  // If you change the scrollDirection to horizontal,
  // this produces 2 rows.
  crossAxisCount: 2,
  // Generate 100 widgets that display their index in the list.
  children: List.generate(100, (index) {
    return Center(
      child: Text(
        'Item $index',
        style: TextTheme.of(context).headlineSmall,
      ),
    );
  }),
),
```

## Interactive example

[#](#interactive-example)

```
import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    const title = 'Grid List';

    return MaterialApp(
      title: title,
      home: Scaffold(
        appBar: AppBar(title: const Text(title)),
        body: GridView.count(
          // Create a grid with 2 columns.
          // If you change the scrollDirection to horizontal,
          // this produces 2 rows.
          crossAxisCount: 2,
          // Generate 100 widgets that display their index in the list.
          children: List.generate(100, (index) {
            return Center(
              child: Text(
                'Item $index',
                style: TextTheme.of(context).headlineSmall,
              ),
            );
          }),
        ),
      ),
    );
  }
}
```

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/lists/grid-lists.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/cookbook/lists/grid-lists\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/lists/grid-lists.md "Report an issue with this page").

----
url: https://docs.flutter.dev/cookbook/animation/opacity-animation
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Fade a widget in and out](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

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, fade elements in and out with an opacity animation to create a smooth experience.

The [`AnimatedOpacity`](https://api.flutter.dev/flutter/widgets/AnimatedOpacity-class.html) widget makes it easy to perform opacity animations. This recipe uses the following steps:

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

[#](#1-create-a-box-to-fade-in-and-out)

First, create something to fade in and out. For this example, draw a green box on screen.

dart

```
Container(width: 200, height: 200, color: Colors.green)
```

## 2. Define a `StatefulWidget`

[#](#2-define-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`](https://api.flutter.dev/flutter/widgets/StatefulWidget-class.html).

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 have one piece of data: a boolean representing whether the green box is visible.

To construct a `StatefulWidget`, create two classes: A `StatefulWidget` and a corresponding `State` class. Pro tip: The Flutter plugins for Android Studio and VS Code include the `stful` snippet to quickly generate this code.

dart

```
// 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 {
  const MyHomePage({super.key, required this.title});
​
  final String title;
​
  @override
  State<MyHomePage> createState() => _MyHomePageState();
}
​
// 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<MyHomePage> {
  // Whether the green box should be visible.
  bool _visible = true;
​
  @override
  Widget build(BuildContext context) {
    // The green box goes here with some other Widgets.
  }
}
```

## 3. Display a button that toggles the visibility

[#](#3-display-a-button-that-toggles-the-visibility)

Now that you have some data to determine whether the green box should be visible, you need a way to update that data. In this example, if the box is visible, hide it. If the box is hidden, show it.

To handle 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()`](https://api.flutter.dev/flutter/widgets/State/setState.html), which is a method on the `State` class. This tells Flutter to rebuild the widget.

To learn more about working with user input, consult the [Gestures](/cookbook/gestures) section of the cookbook.

dart

```
FloatingActionButton(
  onPressed: () {
    // Call setState. This tells Flutter to rebuild the
    // UI with the changes.
    setState(() {
      _visible = !_visible;
    });
  },
  tooltip: 'Toggle Opacity',
  child: const Icon(Icons.flip),
)
```

## 4. Fade the box in and out

[#](#4-fade-the-box-in-and-out)

You have a green box on screen and a button to toggle the visibility to `true` or `false`. How to fade the box in and out? With an [`AnimatedOpacity`](https://api.flutter.dev/flutter/widgets/AnimatedOpacity-class.html) widget.

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 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).
  opacity: _visible ? 1.0 : 0.0,
  duration: const Duration(milliseconds: 500),
  // The green box must be a child of the AnimatedOpacity widget.
  child: Container(width: 200, height: 200, color: Colors.green),
)
```

## Interactive example

[#](#interactive-example)

```
import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    const appTitle = 'Opacity Demo';
    return const MaterialApp(
      title: appTitle,
      home: MyHomePage(title: appTitle),
    );
  }
}

// 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 {
  const MyHomePage({super.key, required this.title});

  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

// 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<MyHomePage> {
  // Whether the green box should be visible
  bool _visible = true;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text(widget.title)),
      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).
          opacity: _visible ? 1.0 : 0.0,
          duration: const Duration(milliseconds: 500),
          // The green box must be a child of the AnimatedOpacity widget.
          child: Container(width: 200, height: 200, color: Colors.green),
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          // Call setState. This tells Flutter to rebuild the
          // UI with the changes.
          setState(() {
            _visible = !_visible;
          });
        },
        tooltip: 'Toggle Opacity',
        child: const Icon(Icons.flip),
      ),
    );
  }
}
```

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-10-07. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/animation/opacity-animation.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/cookbook/animation/opacity-animation\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/animation/opacity-animation.md "Report an issue with this page").

----
url: https://docs.flutter.dev/perf.md
----

\# Performance > Evaluating the performance of your app from several angles. :::note If your app has a performance issue and you are trying to debug it, check out the DevTool's page on \[Using the Performance view]\[]. ::: \[Using the Performance view]: /tools/devtools/performance What is performance? Why is performance important? How do I improve performance? Our goal is to answer those three questions (mainly the third one), and anything related to them. This document should serve as the single entry point or the root node of a tree of resources that addresses any questions that you have about performance. The answers to the first two questions are mostly philosophical, and not as helpful to many developers who visit this page with specific performance issues that need to be solved. Therefore, the answers to those questions are in the \[appendix]\(/perf/appendix). To improve performance, you first need metrics: some measurable numbers to verify the problems and improvements. In the \[metrics]\(/perf/metrics) page, you'll see which metrics are currently used, and which tools and APIs are available to get the metrics. There is a list of \[Frequently asked questions]\(/perf/faq), so you can find out if the questions you have or the problems you're having were already answered or encountered, and whether there are existing solutions. (Alternatively, you can check the Flutter GitHub issue database using the \[performance]\[performance] label.) Finally, the performance issues are divided into four categories. They correspond to the four labels that are used in the Flutter GitHub issue database: "\[perf: speed]\[speed]", "\[perf: memory]\[memory]", "\[perf: app size]\[size]", "\[perf: energy]\[energy]". The rest of the content is organized using those four categories. ## Speed Are your animations janky (not smooth)? Learn how to evaluate and fix rendering issues. \[Improving rendering performance]\(/perf/rendering-performance) ## App size How to measure your app's size. The smaller the size, the quicker it is to download. \[Measuring your app's size]\[] \[Measuring your app's size]: /perf/app-size \[speed]: https\://github.com/flutter/flutter/issues?q=is%3Aopen+label%3A%22perf%3A+speed%22+sort%3Aupdated-asc+ \[energy]: https\://github.com/flutter/flutter/issues?q=is%3Aopen+label%3A%22perf%3A+energy%22+sort%3Aupdated-asc+ \[memory]: https\://github.com/flutter/flutter/issues?q=is%3Aopen+label%3A%22perf%3A+memory%22+sort%3Aupdated-asc+ \[size]: https\://github.com/flutter/flutter/issues?q=is%3Aopen+label%3A%22perf%3A+app+size%22+sort%3Aupdated-asc+ \[performance]: https\://github.com/flutter/flutter/issues?q=+label%3A%22severe%3A+performance%22

----
url: https://docs.flutter.dev/testing/native-debugging
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Use a native language debugger](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

If you write Flutter apps only with Dart code, you can debug your code using your IDE's debugger. The Flutter team recommends VS Code.

If you write a platform-specific plugin or use platform-specific libraries, you can debug that portion of your code with a native debugger.

* To debug iOS or macOS code written in Swift or Objective-C, you can use Xcode.
* To debug Android code written in Java or Kotlin, you can use Android Studio.
* To debug Windows code written in C++, you can use Visual Studio.

This guide shows you how you can connect *two* debuggers to your Dart app, one for Dart, and one for the native code.

## Debug Dart code

[#](#debug-dart-code)

This guide describes how to use VS Code to debug your Flutter app. You can also use your preferred IDE with the Flutter and Dart plugins installed and configured.

## Debug Dart code using VS Code

[#](#debug-dart-code-using-vs-code)

The following procedure explains how to use the Dart debugger with the default sample Flutter app. The featured components in VS Code work and appear when debugging your own Flutter project as well.

1. Create a basic Flutter app.

   ```
   $ flutter create my_app
   ```

   ```
   Creating project my_app...
   Resolving dependencies in my_app...
   Got dependencies in my_app.
   Wrote 129 files.

   All done!
   You can find general documentation for Flutter at: https://docs.flutter.dev/
   Detailed API documentation is available at: https://api.flutter.dev/
   If you prefer video documentation, consider: https://www.youtube.com/c/flutterdev

   In order to run your application, type:

     $ cd my_app
     $ flutter run

   Your application code is in my_app/lib/main.dart.
   ```

   ```
   $ cd my_app
   ```

2. Open the `lib\main.dart` file in the Flutter app using VS Code.

3. Click the bug icon (). This opens the following panes in VS Code:

   * **Debug**
   * **Debug Console**
   * **Widget Inspector**

   The first time you run the debugger takes the longest.

4. Test the debugger.

   a. In `main.dart`, click on this line:

   dart

   ```
   _counter++;
   ```

   b. Press `Shift` + `F9`. This adds a breakpoint where the `_counter` variable increments.

   c. In the app, click the **+** button to increment the counter. The app pauses.

   d. At this point, VS Code displays:

   * In the **Editor Groups**:

     * The highlighted breakpoint in `main.dart`
     * The widget hierarchy for the Flutter app in the **Widget Tree** of the **Widget Inspector**

   * In the **side bar**:

     * The state of the app in the **Call Stack** section
     * The value of the `this` local variable in the **Variables** section

   * In the **panel**:
     * The log of the Flutter app in the **Debug console**

### VS Code Flutter debugger

[#](#vs-code-flutter-debugger)

The Flutter plugin for VS Code adds a number of components to the VS Code user interface.

#### Changes to VS Code interface

[#](#changes-to-vs-code-interface)

When launched, the Flutter debugger adds debugging tools to the VS Code interface.

The following screenshot and table explain the purpose of each tool.

| Highlight Color in Screenshot | Bar, Panel, or Tab  | Contents                                                                     |
| ----------------------------- | ------------------- | ---------------------------------------------------------------------------- |
| **Yellow**                    | Variables           | List of current values of variables in the Flutter app                       |
|                               | Watch               | List of items you chose to track in the Flutter app                          |
|                               | Call Stack          | Stack of active subroutines in the Flutter app                               |
|                               | Breakpoints         | List of exceptions and set breakpoints that you set                          |
| **Green**                     | `<Flutter files>`   | Files that you are editing                                                   |
| **Pink**                      | Widget Inspector    | Hierarchy of widgets in the running Flutter app                              |
| **Blue**                      | Layout Explorer     | Visual of how Flutter placed the widget you selected in the Widget Inspector |
|                               | Widget Details Tree | List of properties of the widget selected in the Widget Inspector            |
| **Orange**                    | Problems            | List of issues the Dart analyzer found in the current Dart file              |
|                               | Output              | Response that the Flutter app returns when building an app                   |
|                               | Debug Console       | Logs or error messages that the Flutter app generates while debugging        |
|                               | Terminal            | System shell prompt contained in VS Code                                     |

To change where the panel (in **orange**) appears in VS Code, go to **View** > **Appearance** > **Panel Position**.

#### VS Code Flutter debugging toolbar

[#](#vs-code-flutter-debugging-toolbar)

The toolbar allows you to debug using any debugger. You can step in, out, and over Dart statements, hot reload, or resume the app.

| Icon                  | Action                     | Default keyboard shortcut |
| --------------------- | -------------------------- | ------------------------- |
|                       |                            |                           |
| Start or Resume       | `F5`                       |                           |
|                       |                            |                           |
| Pause                 | `F6`                       |                           |
|                       |                            |                           |
| Step Over             | `F10`                      |                           |
|                       |                            |                           |
| Step Into             | `F11`                      |                           |
|                       |                            |                           |
| Step Out              | `Shift` + `F11`            |                           |
|                       |                            |                           |
| Hot Reload            | `Ctrl` + `F5`              |                           |
|                       |                            |                           |
| Hot Restart           | `Shift` + `Special` + `F5` |                           |
|                       |                            |                           |
| Stop                  | `Shift` + `F5`             |                           |
|                       |                            |                           |
| Open Widget Inspector |                            |                           |

## Update test Flutter app

[#](#update-test-flutter-app)

For the remainder of this guide, you need to update the test Flutter app. This update adds native code to debug.

1. Open the `lib/main.dart` file using your preferred IDE.

2. Replace the contents of `main.dart` with the following code.

   Expand to see Flutter code for this example

   lib/main.dart

   dart

   ```
   // Copyright 2023 The Flutter Authors. All rights reserved.
   // Use of this source code is governed by a BSD-style license that can be
   // found in the LICENSE file.
   ​
   import 'package:flutter/material.dart';
   import 'package:url_launcher/url_launcher.dart';
   ​
   void main() {
     runApp(const MyApp());
   }
   ​
   class MyApp extends StatelessWidget {
     const MyApp({super.key});
   ​
     @override
     Widget build(BuildContext context) {
       return MaterialApp(
         title: 'URL Launcher',
         theme: ThemeData(
           colorSchemeSeed: Colors.purple,
           brightness: Brightness.light,
         ),
         home: const MyHomePage(title: 'URL Launcher'),
       );
     }
   }
   ​
   class MyHomePage extends StatefulWidget {
     const MyHomePage({super.key, required this.title});
     final String title;
   ​
     @override
     State<MyHomePage> createState() => _MyHomePageState();
   }
   ​
   class _MyHomePageState extends State<MyHomePage> {
     Future<void>? _launched;
   ​
     Future<void> _launchInBrowser(Uri url) async {
       if (!await launchUrl(
         url,
         mode: LaunchMode.externalApplication,
       )) {
         throw Exception('Could not launch $url');
       }
     }
   ​
     Future<void> _launchInWebView(Uri url) async {
       if (!await launchUrl(
         url,
         mode: LaunchMode.inAppWebView,
       )) {
         throw Exception('Could not launch $url');
       }
     }
   ​
     Widget _launchStatus(BuildContext context, AsyncSnapshot<void> snapshot) {
       if (snapshot.hasError) {
         return Text('Error: ${snapshot.error}');
       } else {
         return const Text('');
       }
     }
   ​
     @override
     Widget build(BuildContext context) {
       final Uri toLaunch = Uri(
           scheme: 'https',
           host: 'docs.flutter.dev',
           path: 'testing/native-debugging');
       return Scaffold(
         appBar: AppBar(
           title: Text(widget.title),
         ),
         body: Center(
           child: Column(
             mainAxisAlignment: MainAxisAlignment.center,
             children: <Widget>[
               Padding(
                 padding: const EdgeInsets.all(16),
                 child: Text(toLaunch.toString()),
               ),
               FilledButton(
                 onPressed: () => setState(() {
                   _launched = _launchInBrowser(toLaunch);
                 }),
                 child: const Text('Launch in browser'),
               ),
               const Padding(padding: EdgeInsets.all(16)),
               FilledButton(
                 onPressed: () => setState(() {
                   _launched = _launchInWebView(toLaunch);
                 }),
                 child: const Text('Launch in app'),
               ),
               const Padding(padding: EdgeInsets.all(16.0)),
               FutureBuilder<void>(future: _launched, builder: _launchStatus),
             ],
           ),
         ),
       );
     }
   }
   ```

3. To add the `url_launcher` package as a dependency, run `flutter pub add`:

   ```
   $ flutter pub add url_launcher
   ```

   ```
   Resolving dependencies...
     collection 1.17.1 (1.17.2 available)
   + flutter_web_plugins 0.0.0 from sdk flutter
     matcher 0.12.15 (0.12.16 available)
     material_color_utilities 0.2.0 (0.8.0 available)
   + plugin_platform_interface 2.1.4
     source_span 1.9.1 (1.10.0 available)
     stream_channel 2.1.1 (2.1.2 available)
     test_api 0.5.1 (0.6.1 available)
   + url_launcher 6.1.11
   + url_launcher_android 6.0.36
   + url_launcher_ios 6.1.4
   + url_launcher_linux 3.0.5
   + url_launcher_macos 3.0.5
   + url_launcher_platform_interface 2.1.3
   + url_launcher_web 2.0.17
   + url_launcher_windows 3.0.6
   Changed 10 dependencies!
   ```

4. To check what changed with the codebase:

   1. In Linux or macOS, run this `find` command.

      ```
      $ find ./ -mmin -120
      ```

      ```
      ./ios/Flutter/Debug.xcconfig
      ./ios/Flutter/Release.xcconfig
      ./linux/flutter/generated_plugin_registrant.cc
      ./linux/flutter/generated_plugins.cmake
      ./macos/Flutter/Flutter-Debug.xcconfig
      ./macos/Flutter/Flutter-Release.xcconfig
      ./macos/Flutter/GeneratedPluginRegistrant.swift
      ./pubspec.lock
      ./pubspec.yaml
      ./windows/flutter/generated_plugin_registrant.cc
      ./windows/flutter/generated_plugins.cmake
      ```

   2. In Windows, run this command in the command prompt.

      ```
      Get-ChildItem C:\dev\example\ -Rescurse | Where-Object {$_.LastWriteTime -gt (Get-Date).AddDays(-1)}
      ```

      ```
      C:\dev\example\ios\Flutter\


      Mode                LastWriteTime         Length Name
      ----                -------------         ------ ----
                      8/1/2025   9:15 AM                Debug.xcconfig
                      8/1/2025   9:15 AM                Release.xcconfig

      C:\dev\example\linux\flutter\


      Mode                LastWriteTime         Length Name
      ----                -------------         ------ ----
                      8/1/2025   9:15 AM                generated_plugin_registrant.cc
                      8/1/2025   9:15 AM                generated_plugins.cmake

      C:\dev\example\macos\Flutter\


      Mode                LastWriteTime         Length Name
      ----                -------------         ------ ----
                      8/1/2025   9:15 AM                Flutter-Debug.xcconfig
                      8/1/2025   9:15 AM                Flutter-Release.xcconfig
                      8/1/2025   9:15 AM                GeneratedPluginRegistrant.swift

      C:\dev\example\


      Mode                LastWriteTime         Length Name
      ----                -------------         ------ ----
                      8/1/2025   9:15 AM                pubspec.lock
                      8/1/2025   9:15 AM                pubspec.yaml

      C:\dev\example\windows\flutter\


      Mode                LastWriteTime         Length Name
      ----                -------------         ------ ----
                      8/1/2025   9:15 AM                generated_plugin_registrant.cc
                      8/1/2025   9:15 AM                generated_plugins.cmake
      ```

Installing `url_launcher` added config files and code files for all target platforms in the Flutter app directory.

## Debug Dart and native language code at the same time

[#](#debug-dart-and-native-language-code-at-the-same-time)

This section explains how to debug the Dart code in your Flutter app and any native code with its regular debugger. This capability allows you to leverage Flutter's hot reload when editing native code.

### Debug Dart and Android code using Android Studio

[#](#debug-dart-and-android-code-using-android-studio)

To debug native Android code, you need a Flutter app that contains Android code. In this section, you learn how to connect the Dart, Java, and Kotlin debuggers to your app. You don't need VS Code to debug both Dart and Android code. This guide includes the VS Code instructions to be consistent with the Xcode and Visual Studio guides.

These section uses the same example Flutter `url_launcher` app created in [Update test Flutter app](#update-test-flutter-app).

#### Build the Android version of the Flutter app in the Terminal

[#](#build-the-android-version-of-the-flutter-app-in-the-terminal)

To generate the needed Android platform dependencies, run the `flutter build` command.

```
flutter build appbundle --debug
```

```
Running Gradle task 'bundleDebug'...                               27.1s
✓ Built build/app/outputs/bundle/debug/app-debug.aab.
```

* [Start from VS Code](#169-tab-panel)
* [Start from Android Studio](#170-tab-panel)

#### Start debugging with VS Code first

[#](#from-vscode-to-android-studio)

If you use VS Code to debug most of your code, start with this section.

#### Attach to the Flutter process in Android Studio

[#](#attach-to-the-flutter-process-in-android-studio)

1. Click the **Attach debugger to Android process** button. ()

   :::tip If this button doesn't appear in the **Projects** menu bar, verify that you opened Flutter *application* project but *not a Flutter plugin*. :::

2. The **process** dialog displays one entry for each connected device. Select **show all processes** to display available processes for each device.

3. Choose the process to which you want to attach. For this guide, select the `com.example.my_app` process using the **Emulator Pixel\_5\_API\_33**.

4. Locate the tab for **Android Debugger** in the **Debug** pane.

5. In the **Project** pane, expand **my\_app\_android** > **android** > **app** > **src** > **main** > **java** > **io.flutter plugins**.

6. Double click **GeneratedProjectRegistrant** to open the Java code in the **Edit** pane.

At the end of this procedure, both the Dart and Android debuggers interact with the same process. Use either, or both, to set breakpoints, examine stack, resume execution and the like. In other words, debug!

#### Start debugging with Android Studio first

[#](#from-android-studio)

If you use Android Studio to debug most of your code, start with this section.

1. To open the Flutter app directory, go to **File** > **Open...** and choose the `my_app` directory.

2. Open the `lib/main.dart` file.

3. Choose a virtual Android device. Go to the toolbar, open the leftmost dropdown menu, and click on **Open Android Emulator: \<device>**.

   You can choose any installed emulator that's doesn't include `arm64`.

4. From that same menu, select the virtual Android device.

5. From the toolbar, click **Run 'main.dart'**.

   You can also press `Ctrl` + `Shift` + `R`.

   After the app displays in the emulator, continue to the next step.

6. Click the **Attach debugger to Android process** button. ()

   :::tip If this button doesn't appear in the **Projects** menu bar, verify that you opened Flutter *application* project but *not a Flutter plugin*. :::

7. The **process** dialog displays one entry for each connected device. Select **show all processes** to display available processes for each device.

8. Choose the process to which you want to attach. For this guide, select the `com.example.my_app` process using the **Emulator Pixel\_5\_API\_33**.

9. Locate the tab for **Android Debugger** in the **Debug** pane.

10. In the **Project** pane, expand **my\_app\_android** > **android** > **app** > **src** > **main** > **java** > **io.flutter plugins**.

11. Double click **GeneratedProjectRegistrant** to open the Java code in the **Edit** pane.

At the end of this procedure, both the Dart and Android debuggers interact with the same process. Use either, or both, to set breakpoints, examine stack, resume execution and the like. In other words, debug!

### Debug Dart and iOS code using Xcode

[#](#debug-dart-and-ios-code-using-xcode)

To debug iOS code, you need a Flutter app that contains iOS code. In this section, you learn to connect two debuggers to your app: Flutter via VS Code and Xcode. You need to run both VS Code and Xcode.

These section uses the same example Flutter `url_launcher` app created in [Update test Flutter app](#update-test-flutter-app).

#### Build the iOS version of the Flutter app in the Terminal

[#](#build-the-ios-version-of-the-flutter-app-in-the-terminal)

To generate the needed iOS platform dependencies, run the `flutter build` command.

```
$ flutter build ios --config-only --no-codesign --debug
```

```
Warning: Building for device with codesigning disabled. You will have to manually codesign before deploying to device.
Building com.example.myApp for device (ios)...
```

* [Start from VS Code](#171-tab-panel)
* [Start from Xcode](#172-tab-panel)

#### Start debugging with VS Code first

[#](#vscode-ios)

If you use VS Code to debug most of your code, start with this section.

##### Start the Dart debugger in VS Code

[#](#start-the-dart-debugger-in-vs-code)

1. To open the Flutter app directory, go to **File** > **Open Folder...** and choose the `my_app` directory.

2. Open the `lib/main.dart` file.

3. If you can build an app for more than one device, you must select the device first.

   Go to **View** > **Command Palette...**

   You can also press `Ctrl` / `Cmd` + `Shift` + `P`.

4. Type `flutter select`.

5. Click the **Flutter: Select Device** command.

6. Choose your target device.

7. Click the debug icon (). This opens the **Debug** pane and launches the app. Wait for the app to launch on the device and for the debug pane to indicate **Connected**. The debugger takes longer to launch the first time. Subsequent launches start faster.

   This Flutter app contains two buttons:

   * **Launch in browser**: This button opens this page in the default browser of your device.
   * **Launch in app**: This button opens this page within your app. This button only works for iOS or Android. Desktop apps launch a browser.

##### Attach to the Flutter process in Xcode

[#](#attach-to-the-flutter-process-in-xcode)

To attach to the Flutter app in Xcode:

1. Go to **Debug** > **Attach to Process**.

2. Select **Runner**. It should be at the top of the **Attach to Process** menu under the **Likely Targets** heading.

#### Start debugging with Xcode first

[#](#xcode-ios)

If you use Xcode to debug most of your code, start with this section.

##### Start the Xcode debugger

[#](#start-the-xcode-debugger)

1. Open `ios/Runner.xcworkspace` from your Flutter app directory.

2. Select the correct device using the **Scheme** menu in the toolbar.

   If you have no preference, choose **iPhone Pro 14**.

3. Run this Runner as a normal app in Xcode.

   When the run completes, the **Debug** area at the bottom of Xcode displays a message with the Dart VM service URI. It resembles the following response:

   ```
   2023-07-12 14:55:39.966191-0500 Runner[58361:53017145]
       flutter: The Dart VM service is listening on
       http://127.0.0.1:50642/00wEOvfyff8=/
   ```

4. Copy the Dart VM service URI.

##### Attach to the Dart VM in VS Code

[#](#attach-to-the-dart-vm-in-vs-code)

1. To open the command palette, go to **View** > **Command Palette...**

   You can also press `Cmd` + `Shift` + `P`.

2. Type `debug`.

3. Click the **Debug: Attach to Flutter on Device** command.

4. In the **Paste an VM Service URI** box, paste the URI you copied from Xcode and press `Enter`.

### Debug Dart and macOS code using Xcode

[#](#debug-dart-and-macos-code-using-xcode)

To debug macOS code, you need a Flutter app that contains macOS code. In this section, you learn to connect two debuggers to your app: Flutter via VS Code and Xcode. You need to run both VS Code and Xcode.

These section uses the same example Flutter `url_launcher` app created in [Update test Flutter app](#update-test-flutter-app).

#### Build the macOS version of the Flutter app in the Terminal

[#](#build-the-macos-version-of-the-flutter-app-in-the-terminal)

To generate the needed macOS platform dependencies, run the `flutter build` command.

```
flutter build macos --debug
```

```
Building macOS application...
```

* [Start from VS Code](#173-tab-panel)
* [Start from XCode](#174-tab-panel)

#### Start debugging with VS Code first

[#](#vscode-macos)

##### Start the debugger in VS Code

[#](#start-the-debugger-in-vs-code)

1. To open the Flutter app directory, go to **File** > **Open Folder...** and choose the `my_app` directory.

2. Open the `lib/main.dart` file.

3. If you can build an app for more than one device, you must select the device first.

   Go to **View** > **Command Palette...**

   You can also press `Ctrl` / `Cmd` + `Shift` + `P`.

4. Type `flutter select`.

5. Click the **Flutter: Select Device** command.

6. Choose your target device.

7. Click the debug icon (). This opens the **Debug** pane and launches the app. Wait for the app to launch on the device and for the debug pane to indicate **Connected**. The debugger takes longer to launch the first time. Subsequent launches start faster.

   This Flutter app contains two buttons:

   * **Launch in browser**: This button opens this page in the default browser of your device.
   * **Launch in app**: This button opens this page within your app. This button only works for iOS or Android. Desktop apps launch a browser.

##### Attach to the Flutter process in Xcode

[#](#attach-to-the-flutter-process-in-xcode-1)

1. To attach to the Flutter app, go to **Debug** > **Attach to Process** > **Runner**.

   **Runner** should be at the top of the **Attach to Process** menu under the **Likely Targets** heading.

#### Start debugging with Xcode first

[#](#xcode-macos)

##### Start the debugger in Xcode

[#](#start-the-debugger-in-xcode)

1. Open `macos/Runner.xcworkspace` from your Flutter app directory.

2. Run this Runner as a normal app in Xcode.

   When the run completes, the **Debug** area at the bottom of Xcode displays a message with the Dart VM service URI. It resembles the following response:

   ```
   2023-07-12 14:55:39.966191-0500 Runner[58361:53017145]
       flutter: The Dart VM service is listening on
       http://127.0.0.1:50642/00wEOvfyff8=/
   ```

3. Copy the Dart VM service URI.

##### Attach to the Dart VM in VS Code

[#](#attach-to-the-dart-vm-in-vs-code-1)

1. To open the command palette, go to **View** > **Command Palette...**

   You can also press `Cmd` + `Shift` + `P`.

2. Type `debug`.

3. Click the **Debug: Attach to Flutter on Device** command.

4. In the **Paste an VM Service URI** box, paste the URI you copied from Xcode and press `Enter`.

### Debug Dart and C++ code using Visual Studio

[#](#debug-dart-and-c-code-using-visual-studio)

To debug C++ code, you need a Flutter app that contains C++ code. In this section, you learn to connect two debuggers to your app: Flutter via VS Code and Visual Studio. You need to run both VS Code and Visual Studio.

These section uses the same example Flutter `url_launcher` app created in [Update test Flutter app](#update-test-flutter-app).

#### Build the Windows version of the Flutter app in PowerShell or the Command Prompt

[#](#build-the-windows-version-of-the-flutter-app-in-powershell-or-the-command-prompt)

To generate the needed Windows platform dependencies, run the `flutter build` command.

```
C:\> flutter build windows --debug
```

```
Building Windows application...                                    31.4s
√  Built build\windows\runner\Debug\my_app.exe.
```

* [Start from VS Code](#175-tab-panel)
* [Start from Visual Studio](#176-tab-panel)

#### Start debugging with VS Code first

[#](#vscode-windows)

If you use VS Code to debug most of your code, start with this section.

##### Start the debugger in VS Code

[#](#start-the-debugger-in-vs-code-1)

1. To open the Flutter app directory, go to **File** > **Open Folder...** and choose the `my_app` directory.

2. Open the `lib/main.dart` file.

3. If you can build an app for more than one device, you must select the device first.

   Go to **View** > **Command Palette...**

   You can also press `Ctrl` / `Cmd` + `Shift` + `P`.

4. Type `flutter select`.

5. Click the **Flutter: Select Device** command.

6. Choose your target device.

7. Click the debug icon (). This opens the **Debug** pane and launches the app. Wait for the app to launch on the device and for the debug pane to indicate **Connected**. The debugger takes longer to launch the first time. Subsequent launches start faster.

   This Flutter app contains two buttons:

   * **Launch in browser**: This button opens this page in the default browser of your device.
   * **Launch in app**: This button opens this page within your app. This button only works for iOS or Android. Desktop apps launch a browser.

##### Attach to the Flutter process in Visual Studio

[#](#attach-to-the-flutter-process-in-visual-studio)

1. To open the project solution file, go to **File** > **Open** > **Project/Solution…**

   You can also press `Ctrl` + `Shift` + `O`.

2. Choose the `build/windows/my_app.sln` file in your Flutter app directory.

3. Go to **Debug** > **Attach to Process**.

   You can also press `Ctrl` + `Alt` + `P`.

4. From the **Attach to Process** dialog box, choose `my_app.exe`.

   Visual Studio starts monitoring the Flutter app.

#### Start debugging with Visual Studio first

[#](#start-debugging-with-visual-studio-first)

If you use Visual Studio to debug most of your code, start with this section.

##### Start the local Windows debugger

[#](#start-the-local-windows-debugger)

1. To open the project solution file, go to **File** > **Open** > **Project/Solution…**

   You can also press `Ctrl` + `Shift` + `O`.

2. Choose the `build/windows/my_app.sln` file in your Flutter app directory.

3. Set `my_app` as the startup project. In the **Solution Explorer**, right-click on `my_app` and select **Set as Startup Project**.

4. Click **Local Windows Debugger** to start debugging.

   You can also press `F5`.

   When the Flutter app has started, a console window displays a message with the Dart VM service URI. It resembles the following response:

   ```
   flutter: The Dart VM service is listening on http://127.0.0.1:62080/KPHEj2qPD1E=/
   ```

5. Copy the Dart VM service URI.

##### Attach to the Dart VM in VS Code

[#](#attach-to-the-dart-vm-in-vs-code-2)

1. To open the command palette, go to **View** > **Command Palette...**

   You can also press `Cmd` + `Shift` + `P`.

2. Type `debug`.

3. Click the **Debug: Attach to Flutter on Device** command.

4. In the **Paste an VM Service URI** box, paste the URI you copied from Visual Studio and press `Enter`.

## Resources

[#](#resources)

Check out the following resources on debugging Flutter, iOS, Android, macOS and Windows:

### Flutter

[#](#flutter)

* [Debugging Flutter apps](/testing/debugging)
* [Flutter inspector](/tools/devtools/inspector) and the [DevTools](/tools/devtools) docs
* [Performance profiling](/perf/ui-performance)

### Android

[#](#android)

You can find the following debugging resources on [developer.android.com](https://developer.android.com).

* [Debug your app](https://developer.android.com/studio/debug)
* [Android Debug Bridge (adb)](https://developer.android.com/studio/command-line/adb)

### iOS and macOS

[#](#ios-and-macos)

You can find the following debugging resources on [developer.apple.com](https://developer.apple.com).

* [Debugging](https://developer.apple.com/support/debugging/)
* [Instruments Help](https://help.apple.com/instruments/mac/current/)

### Windows

[#](#windows)

You can find debugging resources on [Microsoft Learn](https://learn.microsoft.com/visualstudio/).

* [Visual Studio Debugger](https://learn.microsoft.com/visualstudio/debugger/?view=vs-2022)
* [Learn to debug C++ code using Visual Studio](https://learn.microsoft.com/visualstudio/debugger/getting-started-with-the-debugger-cpp?view=vs-2022)

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/testing/native-debugging.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/testing/native-debugging\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/testing/native-debugging.md "Report an issue with this page").

----
url: https://docs.flutter.dev/ai/flutter-bench
----

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-09-01. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/ai/flutter-bench/index.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/ai/flutter-bench\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/ai/flutter-bench/index.md "Report an issue with this page").

----
url: https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.50.0.md
----

\# DevTools 2.50.0 release notes > Release notes for Dart and Flutter DevTools version 2.50.0. The 2.50.0 release of the Dart and Flutter DevTools includes the following changes among other general improvements. To learn more about DevTools, check out the \[DevTools overview]\(/tools/devtools/overview). ## CPU profiler updates - Deleted the "Profile app start up" button in favor of the new Dart/Flutter \`--profile-startup\` CLI flags. - \[#9358]\(https\://github.com/flutter/devtools/pull/9358) ## Full commit history To find a complete list of changes in this release, check out the \[DevTools git log]\(https\://github.com/flutter/devtools/tree/v2.50.0).

----
url: https://docs.flutter.dev/resources/architectural-overview
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Flutter architectural overview](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

This article is intended to provide a high-level overview of the architecture of Flutter, including the core principles and concepts that form its design. If you are interested in how to architect a Flutter app, check out [Architecting Flutter apps](/app-architecture).

Flutter is a cross-platform UI toolkit that is designed to allow code reuse across operating systems such as iOS, Android, web, and desktop, while also allowing applications to interface directly with underlying platform services. The goal is to enable developers to deliver high-performance apps that feel natural on different platforms, embracing differences where they exist while sharing as much code as possible.

During development, Flutter apps run in a VM that offers stateful hot reload of changes without needing a full recompile. For release, Flutter apps are compiled directly to machine code, whether Intel x64 or ARM instructions, or to JavaScript if targeting the web. The framework is open source, with a permissive BSD license, and has a thriving ecosystem of third-party packages that supplement the core library functionality.

This overview is divided into a number of sections:

1. The **layer model**: The pieces from which Flutter is constructed.
2. **Reactive user interfaces**: A core concept for Flutter user interface development.
3. An introduction to **widgets**: The fundamental building blocks of Flutter user interfaces.
4. The **rendering process**: How Flutter turns UI code into pixels.
5. An overview of the **platform embedders**: The code that lets mobile and desktop OSes execute Flutter apps.
6. **Integrating Flutter with other code**: Information about different techniques available to Flutter apps.
7. **Support for the web**: Concluding remarks about the characteristics of Flutter in a browser environment.

## Architectural layers

[#](#architectural-layers)

Flutter is designed as an extensible, layered system. It exists as a series of independent libraries that each depend on the underlying layer. No layer has privileged access to the layer below, and every part of the framework level is designed to be optional and replaceable.

To the underlying operating system, Flutter applications are packaged in the same way as any other native application. A platform-specific embedder provides an entrypoint; coordinates with the underlying operating system for access to services like rendering surfaces, accessibility, and input; and manages the message event loop. The embedder is written in a language that is appropriate for the platform: currently Java and C++ for Android, Swift and Objective-C/Objective-C++ for iOS and macOS, and C++ for Windows and Linux. Using the embedder, Flutter code can be integrated into an existing application as a module, or the code might be the entire content of the application. Flutter includes a number of embedders for common target platforms, but other embedders also exist.

At the core of Flutter is the **Flutter engine**, which is mostly written in C++ and supports the primitives necessary to support all Flutter applications. The engine is responsible for rasterizing composited scenes whenever a new frame needs to be painted. It provides the low-level implementation of Flutter's core API, including graphics text layout, file and network I/O, a Dart runtime, and compile toolchain.

The engine is exposed to the Flutter framework through [`dart:ui`](https://github.com/flutter/flutter/tree/main/engine/src/flutter/lib/ui), which wraps the underlying C++ code in Dart classes. This library exposes the lowest-level primitives, such as classes for driving input, graphics, and text rendering subsystems.

Typically, developers interact with Flutter through the **Flutter framework**, which provides a modern, reactive framework written in the Dart language. It includes a rich set of platform, layout, and foundational libraries, composed of a series of layers. Working from the bottom to the top, we have:

* Basic **[foundational](https://api.flutter.dev/flutter/foundation/foundation-library.html)** classes, and building block services such as **[animation](https://api.flutter.dev/flutter/animation/animation-library.html), [painting](https://api.flutter.dev/flutter/painting/painting-library.html), and [gestures](https://api.flutter.dev/flutter/gestures/gestures-library.html)** that offer commonly used abstractions over the underlying foundation.
* The **[rendering layer](https://api.flutter.dev/flutter/rendering/rendering-library.html)** provides an abstraction for dealing with layout. With this layer, you can build a tree of renderable objects. You can manipulate these objects dynamically, with the tree automatically updating the layout to reflect your changes.
* The **[widgets layer](https://api.flutter.dev/flutter/widgets/widgets-library.html)**, part of the core Flutter SDK, is a composition abstraction. Each render object in the rendering layer has a corresponding class in the widgets layer. In addition, the widgets layer allows you to define combinations of classes that you can reuse. This is the layer at which the reactive programming model is introduced.
* The **[Material](https://pub.dev/packages/material_ui)** and **[Cupertino](https://pub.dev/packages/cupertino_ui)** packages offer comprehensive sets of controls that use the widget layer's composition primitives to implement the Material or iOS design languages.

The Flutter framework is relatively small; many higher-level features that developers might use are implemented as packages, including platform plugins like [camera](https://pub.dev/packages/camera) and [webview](https://pub.dev/packages/webview_flutter), as well as platform-agnostic features like [characters](https://pub.dev/packages/characters), [http](https://pub.dev/packages/http), and [animations](https://pub.dev/packages/animations) that build upon the core Dart and Flutter libraries. Additionally, the Material and Cupertino UI packages are found on pub.dev, allowing you to either import them or build your own design system on top of the core `widgets` library without bloating your app. Other packages come from the broader ecosystem, covering services like [in-app payments](https://pub.dev/packages/square_in_app_payments), [Apple authentication](https://pub.dev/packages/sign_in_with_apple), and [animations](https://pub.dev/packages/lottie).

For more information about Flutter's approach to UI design, visit [Buiding user interfaces with Flutter](/ui).

The rest of this overview broadly navigates down the layers, starting with the reactive paradigm of UI development. Then, we describe how widgets are composed together and converted into objects that can be rendered as part of an application. We describe how Flutter interoperates with other code at a platform level, before giving a brief summary of how Flutter's web support differs from other targets.

## Anatomy of an app

[#](#anatomy-of-an-app)

The following diagram gives an overview of the pieces that make up a regular Flutter app generated by `flutter create`. It shows where the Flutter Engine sits in this stack, highlights API boundaries, and identifies the repositories where the individual pieces live. The legend below clarifies some of the terminology commonly used to describe the pieces of a Flutter app.

**Dart App**

* Composes widgets into the desired UI.
* Implements business logic.
* Owned by app developer.

**Framework** ([source code](https://github.com/flutter/flutter/tree/main/packages/flutter/lib))

* Provides higher-level API to build high-quality apps (for example, style-neutral widgets, hit-testing, gesture detection, accessibility, text input).
* Composites the app's widget tree into a scene.

**Engine** ([source code](https://github.com/flutter/flutter/tree/main/engine/src/flutter/shell/common))

* Responsible for rasterizing composited scenes.
* Provides low-level implementation of Flutter's core APIs (for example, graphics, text layout, Dart runtime).
* Exposes its functionality to the framework using the **dart:ui API**.
* Integrates with a specific platform using the Engine's **Embedder API**.

**Embedder** ([source code](https://github.com/flutter/flutter/tree/main/engine/src/flutter/shell/platform))

* Coordinates with the underlying operating system for access to services like rendering surfaces, accessibility, and input.
* Manages the event loop.
* Exposes **platform-specific API** to integrate the Embedder into apps.

**Runner**

* Composes the pieces exposed by the platform-specific API of the Embedder into an app package runnable on the target platform.
* Part of app template generated by `flutter create`, owned by app developer.

## Reactive user interfaces

[#](#reactive-user-interfaces)

On the surface, Flutter is [a reactive, declarative UI framework](/resources/faq#what-programming-paradigm-does-flutters-framework-use), in which the developer provides a mapping from application state to interface state, and the framework takes on the task of updating the interface at runtime when the application state changes. This model is inspired by [work that came from Facebook for their own React framework](https://www.youtube.com/watch?time_continue=2\&v=x7cQ3mrcKaY\&feature=emb_logo), which includes a rethinking of many traditional design principles.

In most traditional UI frameworks, the user interface's initial state is described once and then separately updated by user code at runtime, in response to events. One challenge of this approach is that, as the application grows in complexity, the developer needs to be aware of how state changes cascade throughout the entire UI. For example, consider the following UI:

There are many places where the state can be changed: the color box, the hue slider, the radio buttons. As the user interacts with the UI, changes must be reflected in every other place. Worse, unless care is taken, a minor change to one part of the user interface can cause ripple effects to seemingly unrelated pieces of code.

One solution to this is an approach like MVC, where you push data changes to the model through the controller, and then the model pushes the new state to the view through the controller. However, this also is problematic, since creating and updating UI elements are two separate steps that can easily get out of sync.

Flutter, along with other reactive frameworks, takes an alternative approach to this problem, by explicitly decoupling the user interface from its underlying state. With React-style APIs, you only create the UI description, and the framework takes care of using that one configuration to both create and/or update the user interface as appropriate.

In Flutter, widgets (akin to components in React) are represented by immutable classes that are used to configure a tree of objects. These widgets are used to manage a separate tree of objects for layout, which is then used to manage a separate tree of objects for compositing. Flutter is, at its core, a series of mechanisms for efficiently walking the modified parts of trees, converting trees of objects into lower-level trees of objects, and propagating changes across these trees.

A widget declares its user interface by overriding the `build()` method, which is a function that converts state to UI:

```
UI = f(state)
```

The `build()` method is by design fast to execute and should be free of side effects, allowing it to be called by the framework whenever needed (potentially as often as once per rendered frame).

This approach relies on certain characteristics of a language runtime (in particular, fast object instantiation and deletion). Fortunately, [Dart is particularly well suited for this task](https://blog.flutter.dev/flutter-dont-fear-the-garbage-collector-d69b3ff1ca30).

## Widgets

[#](#widgets)

As mentioned, Flutter emphasizes widgets as a unit of composition. Widgets are the building blocks of a Flutter app's user interface, and each widget is an immutable declaration of part of the user interface.

Widgets form a hierarchy based on composition. Each widget nests inside its parent and can receive context from the parent. This structure carries all the way up to the root widget (the container that hosts the Flutter app, typically `MaterialApp` or `CupertinoApp`), as this trivial example shows:

dart

```
import 'package:flutter/services.dart';
import 'package:material_ui/material_ui.dart';
​
void main() => runApp(const MyApp());
​
class MyApp extends StatelessWidget {
  const MyApp({super.key});
​
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: const Text('My Home Page')),
        body: Center(
          child: Builder(
            builder: (context) {
              return Column(
                children: [
                  const Text('Hello World'),
                  const SizedBox(height: 20),
                  ElevatedButton(
                    onPressed: () {
                      print('Click!');
                    },
                    child: const Text('A button'),
                  ),
                ],
              );
            },
          ),
        ),
      ),
    );
  }
}
```

In the preceding code, all instantiated classes are widgets.

Apps update their user interface in response to events (such as a user interaction) by telling the framework to replace a widget in the hierarchy with another widget. The framework then compares the new and old widgets, and efficiently updates the user interface.

Flutter has its own implementations of each UI control, rather than deferring to those provided by the system: for example, there is a pure [Dart implementation](https://pub.dev/documentation/cupertino_ui/latest/cupertino_ui/CupertinoSwitch-class.html) of both the [iOS Toggle control](https://developer.apple.com/design/human-interface-guidelines/toggles) and the [one for](https://pub.dev/documentation/material_ui/latest/material_ui/Switch-class.html) the [Android equivalent](https://m3.material.io/components/switch).

This approach provides several benefits:

* Provides for unlimited extensibility. A developer who wants a variant of the Switch control can create one in any arbitrary way, and is not limited to the extension points provided by the OS.
* Avoids a significant performance bottleneck by allowing Flutter to composite the entire scene at once, without transitioning back and forth between Flutter code and platform code.
* Decouples the application behavior from any operating system dependencies. The application looks and feels the same on all versions of the OS, even if the OS changed the implementations of its controls.

### Composition

[#](#composition)

Widgets are typically composed of many other small, single-purpose widgets that combine to produce powerful effects.

Where possible, the number of design concepts is kept to a minimum while allowing the total vocabulary to be large. For example, in the widgets layer, Flutter uses the same core concept (a `Widget`) to represent drawing to the screen, layout (positioning and sizing), user interactivity, state management, theming, animations, and navigation. In the animation layer, a pair of concepts, `Animation`s and `Tween`s, cover most of the design space. In the rendering layer, `RenderObject`s are used to describe layout, painting, hit testing, and accessibility. In each of these cases, the corresponding vocabulary ends up being large: there are hundreds of widgets and render objects, and dozens of animation and tween types.

The class hierarchy is deliberately shallow and broad to maximize the possible number of combinations, focusing on small, composable widgets that each do one thing well. Core features are abstract, with even basic features like padding and alignment being implemented as separate components rather than being built into the core. (This also contrasts with more traditional APIs where features like padding are built in to the common core of every layout component.) So, for example, to center a widget, rather than adjusting a notional `Align` property, you wrap it in a [`Center`](https://api.flutter.dev/flutter/widgets/Center-class.html) widget.

There are widgets for padding, alignment, rows, columns, and grids. These layout widgets do not have a visual representation of their own. Instead, their sole purpose is to control some aspect of another widget's layout. Flutter also includes utility widgets that take advantage of this compositional approach.

For example, [`Container`](https://api.flutter.dev/flutter/widgets/Container-class.html), a commonly used widget, is made up of several widgets responsible for layout, painting, positioning, and sizing. Specifically, `Container` is made up of the [`LimitedBox`](https://api.flutter.dev/flutter/widgets/LimitedBox-class.html), [`ConstrainedBox`](https://api.flutter.dev/flutter/widgets/ConstrainedBox-class.html), [`Align`](https://api.flutter.dev/flutter/widgets/Align-class.html), [`Padding`](https://api.flutter.dev/flutter/widgets/Padding-class.html), [`DecoratedBox`](https://api.flutter.dev/flutter/widgets/DecoratedBox-class.html), and [`Transform`](https://api.flutter.dev/flutter/widgets/Transform-class.html) widgets, as you can see by reading its source code. A defining characteristic of Flutter is that you can drill down into the source for any widget and examine it. So, rather than subclassing `Container` to produce a customized effect, you can compose it and other widgets in novel ways, or just create a new widget using `Container` as inspiration.

### Building widgets

[#](#building-widgets)

As mentioned earlier, you determine the visual representation of a widget by overriding the [`build()`](https://api.flutter.dev/flutter/widgets/StatelessWidget/build.html) function to return a new element tree. This tree represents the widget's part of the user interface in more concrete terms. For example, a toolbar widget might have a build function that returns a [horizontal layout](https://api.flutter.dev/flutter/widgets/Row-class.html) of some [text](https://api.flutter.dev/flutter/widgets/Text-class.html) and [various](https://pub.dev/documentation/material_ui/latest/material_ui/IconButton-class.html) [buttons](https://pub.dev/documentation/material_ui/latest/material_ui/PopupMenuButton-class.html). As needed, the framework recursively asks each widget to build until the tree is entirely described by [concrete renderable objects](https://api.flutter.dev/flutter/widgets/RenderObjectWidget-class.html). The framework then stitches together the renderable objects into a renderable object tree.

A widget's build function should be free of side effects. Whenever the function is asked to build, the widget should return a new tree of widgets[1](#fn-1), regardless of what the widget previously returned. The framework does the heavy lifting work to determine which build methods need to be called based on the render object tree (described in more detail later). More information about this process can be found in the [Inside Flutter topic](/resources/inside-flutter#linear-reconciliation).

On each rendered frame, Flutter can recreate just the parts of the UI where the state has changed by calling that widget's `build()` method. Therefore it is important that build methods should return quickly, and heavy computational work should be done in some asynchronous manner and then stored as part of the state to be used by a build method.

While relatively naive in approach, this automated comparison is quite effective, enabling high-performance, interactive apps. And, the design of the build function simplifies your code by focusing on declaring what a widget is made of, rather than the complexities of updating the user interface from one state to another.

### Widget state

[#](#widget-state)

The framework introduces two major classes of widget: *stateful* and *stateless* widgets.

Many widgets have no mutable state: they don't have any properties that change over time (for example, an icon or a label). These widgets subclass [`StatelessWidget`](https://api.flutter.dev/flutter/widgets/StatelessWidget-class.html).

However, if the unique characteristics of a widget needs to change based on user interaction or other factors, that widget is *stateful*. For example, if a widget has a counter that increments whenever the user taps a button, then the value of the counter is the state for that widget. When that value changes, the widget needs to be rebuilt to update its part of the UI. These widgets subclass [`StatefulWidget`](https://api.flutter.dev/flutter/widgets/StatefulWidget-class.html), and (because the widget itself is immutable) they store mutable state in a separate class that subclasses [`State`](https://api.flutter.dev/flutter/widgets/State-class.html). `StatefulWidget`s don't have a build method; instead, their user interface is built through their `State` object.

Whenever you mutate a `State` object (for example, by incrementing the counter), you must call [`setState()`](https://api.flutter.dev/flutter/widgets/State/setState.html) to signal the framework to update the user interface by calling the `State`'s build method again.

Having separate state and widget objects lets other widgets treat both stateless and stateful widgets in exactly the same way, without being concerned about losing state. Instead of needing to hold on to a child to preserve its state, the parent can create a new instance of the child at any time without losing the child's persistent state. The framework does all the work of finding and reusing existing state objects when appropriate.

### State management

[#](#state-management)

So, if many widgets can contain state, how is state managed and passed around the system?

As with any other class, you can use a constructor in a widget to initialize its data, so a `build()` method can ensure that any child widget is instantiated with the data it needs:

dart

```
@override
Widget build(BuildContext context) {
   return ContentWidget(importantState);
}
```

Where `importantState` is a placeholder for the class that contains the state important to the `Widget`.

As widget trees get deeper, however, passing state information up and down the tree hierarchy becomes cumbersome. So, a third widget type, [`InheritedWidget`](https://api.flutter.dev/flutter/widgets/InheritedWidget-class.html), provides an easy way to grab data from a shared ancestor. You can use `InheritedWidget` to create a state widget that wraps a common ancestor in the widget tree, as shown in this example:

Whenever one of the `ExamWidget` or `GradeWidget` objects needs data from `StudentState`, it can now access it with a command such as:

dart

```
final studentState = StudentState.of(context);
```

The `of(context)` call takes the build context (a handle to the current widget location), and returns [the nearest ancestor in the tree](https://api.flutter.dev/flutter/widgets/BuildContext/dependOnInheritedWidgetOfExactType.html) that matches the `StudentState` type. `InheritedWidget`s also offer an `updateShouldNotify()` method, which Flutter calls to determine whether a state change should trigger a rebuild of child widgets that use it.

Flutter itself uses `InheritedWidget` extensively as part of the framework for shared state, such as the application's *visual theme*, which includes [properties like color and type styles](https://pub.dev/documentation/material_ui/latest/material_ui/ThemeData-class.html) that are pervasive throughout an application. The `MaterialApp` `build()` method inserts a theme in the tree when it builds, and then deeper in the hierarchy a widget can use the `.of()` method to look up the relevant theme data.

For example:

dart

```
Container(
  color: Theme.of(context).secondaryHeaderColor,
  child: Text(
    'Text with a background color',
    style: Theme.of(context).textTheme.titleLarge,
  ),
);
```

As applications grow, more advanced state management approaches that reduce the ceremony of creating and using stateful widgets become more attractive. Many Flutter apps use utility packages like [provider](https://pub.dev/packages/provider), which provides a wrapper around `InheritedWidget`. Flutter's layered architecture also enables alternative approaches to implement the transformation of state into UI, such as the [flutter\_hooks](https://pub.dev/packages/flutter_hooks) package.

## Rendering and layout

[#](#rendering-and-layout)

This section describes the rendering pipeline, which is the series of steps that Flutter takes to convert a hierarchy of widgets into the actual pixels painted onto a screen.

### Flutter's rendering model

[#](#flutters-rendering-model)

You might be wondering: if Flutter is a cross-platform framework, then how can it offer comparable performance to single-platform frameworks?

It's useful to start by thinking about how traditional Android apps work. When drawing, you first call the Java code of the Android framework. The Android system libraries provide the components responsible for drawing themselves to a `Canvas` object, which Android can then render with [Skia](https://skia.org), a graphics engine written in C/C++ that calls the CPU or GPU to complete the drawing on the device.

Cross-platform frameworks *typically* work by creating an abstraction layer over the underlying native Android and iOS UI libraries, attempting to smooth out the inconsistencies of each platform representation. App code is often written in an interpreted language like JavaScript, which must in turn interact with the Java-based Android or Objective-C-based iOS system libraries to display UI. All this adds overhead that can be significant, particularly where there is a lot of interaction between the UI and the app logic.

By contrast, Flutter minimizes those abstractions, bypassing the system UI widget libraries in favor of its own widget set. The Dart code that paints Flutter's visuals is compiled into native code, which uses Impeller for rendering. Impeller is shipped along with the application, allowing the developer to upgrade their app to stay updated with the latest performance improvements even if the phone hasn't been updated with a new Android version. The same is true for Flutter on other native platforms, such as Windows or macOS.

### From user input to the GPU

[#](#from-user-input-to-the-gpu)

The overriding principle that Flutter applies to its rendering pipeline is that **simple is fast**. Flutter has a straightforward pipeline for how data flows to the system, as shown in the following sequencing diagram:

Let's take a look at some of these phases in greater detail.

### Build: from Widget to Element

[#](#build-from-widget-to-element)

Consider this code fragment that demonstrates a widget hierarchy:

dart

```
Container(
  color: Colors.blue,
  child: Row(
    children: [
      Image.network('https://www.example.com/1.png'),
      const Text('A'),
    ],
  ),
);
```

When Flutter needs to render this fragment, it calls the `build()` method, which returns a subtree of widgets that renders UI-based on the current app state. During this process, the `build()` method can introduce new widgets, as necessary, based on its state. As an example, in the preceding code fragment, `Container` has `color` and `child` properties. From looking at the [source code](https://github.com/flutter/flutter/blob/02efffc134ab4ce4ff50a9ddd86c832efdb80462/packages/flutter/lib/src/widgets/container.dart#L401) for `Container`, you can see that if the color is not null, it inserts a `ColoredBox` representing the color:

dart

```
if (color != null)
  current = ColoredBox(color: color!, child: current);
```

Correspondingly, the `Image` and `Text` widgets might insert child widgets such as `RawImage` and `RichText` during the build process. The eventual widget hierarchy might therefore be deeper than what the code represents, as in this case[2](#fn-2):

This explains why, when you examine the tree through a debug tool such as the [Flutter inspector](/tools/devtools/inspector), part of the Flutter/Dart DevTools, you might see a structure that is considerably deeper than what is in your original code.

During the build phase, Flutter translates the widgets expressed in code into a corresponding **element tree**, with one element for every widget. Each element represents a specific instance of a widget in a given location of the tree hierarchy. There are two basic types of elements:

* `ComponentElement`, a host for other elements.
* `RenderObjectElement`, an element that participates in the layout or paint phases.

`RenderObjectElement`s are an intermediary between their widget analog and the underlying `RenderObject`, which we'll come to later.

The element for any widget can be referenced through its `BuildContext`, which is a handle to the location of a widget in the tree. This is the `context` in a function call such as `Theme.of(context)`, and is supplied to the `build()` method as a parameter.

Because widgets are immutable, including the parent/child relationship between nodes, any change to the widget tree (such as changing `Text('A')` to `Text('B')` in the preceding example) causes a new set of widget objects to be returned. But that doesn't mean the underlying representation must be rebuilt. The element tree is persistent from frame to frame, and therefore plays a critical performance role, allowing Flutter to act as if the widget hierarchy is fully disposable while caching its underlying representation. By only walking through the widgets that changed, Flutter can rebuild just the parts of the element tree that require reconfiguration.

### Layout and rendering

[#](#layout-and-rendering)

It would be a rare application that drew only a single widget. An important part of any UI framework is therefore the ability to efficiently lay out a hierarchy of widgets, determining the size and position of each element before they are rendered on the screen.

The base class for every node in the render tree is [`RenderObject`](https://api.flutter.dev/flutter/rendering/RenderObject-class.html), which defines an abstract model for layout and painting. This is extremely general: it does not commit to a fixed number of dimensions or even a Cartesian coordinate system (demonstrated by [this example of a polar coordinate system](https://dartpad.dev/?id=596b1d6331e3b9d7b00420085fab3e27)). Each `RenderObject` knows its parent, but knows little about its children other than how to *visit* them and their constraints. This provides `RenderObject` with sufficient abstraction to be able to handle a variety of use cases.

During the build phase, Flutter creates or updates an object that inherits from `RenderObject` for each `RenderObjectElement` in the element tree. `RenderObject`s are primitives: [`RenderParagraph`](https://api.flutter.dev/flutter/rendering/RenderParagraph-class.html) renders text, [`RenderImage`](https://api.flutter.dev/flutter/rendering/RenderImage-class.html) renders an image, and [`RenderTransform`](https://api.flutter.dev/flutter/rendering/RenderTransform-class.html) applies a transformation before painting its child.

Most Flutter widgets are rendered by an object that inherits from the `RenderBox` subclass, which represents a `RenderObject` of fixed size in a 2D Cartesian space. `RenderBox` provides the basis of a *box constraint model*, establishing a minimum and maximum width and height for each widget to be rendered.

To perform layout, Flutter walks the render tree in a depth-first traversal and **passes down size constraints** from parent to child. In determining its size, the child *must* respect the constraints given to it by its parent. Children respond by **passing up a size** to their parent object within the constraints the parent established.

At the end of this single walk through the tree, every object has a defined size within its parent's constraints and is ready to be painted by calling the [`paint()`](https://api.flutter.dev/flutter/rendering/RenderObject/paint.html) method.

The box constraint model is very powerful as a way to layout objects in *O(n)* time:

* Parents can dictate the size of a child object by setting maximum and minimum constraints to the same value. For example, the topmost render object in a phone app constrains its child to be the size of the screen. (Children can choose how to use that space. For example, they might just center what they want to render within the dictated constraints.)
* A parent can dictate the child's width but give the child flexibility over height (or dictate height but offer flexibility over width). A real-world example is flow text, which might have to fit a horizontal constraint but vary vertically depending on the quantity of text.

This model works even when a child object needs to know how much space it has available to decide how it will render its content. By using a [`LayoutBuilder`](https://api.flutter.dev/flutter/widgets/LayoutBuilder-class.html) widget, the child object can examine the passed-down constraints and use those to determine how it will use them, for example:

dart

```
Widget build(BuildContext context) {
  return LayoutBuilder(
    builder: (context, constraints) {
      if (constraints.maxWidth < 600) {
        return const OneColumnLayout();
      } else {
        return const TwoColumnLayout();
      }
    },
  );
}
```

More information about the constraint and layout system, along with working examples, can be found in the [Understanding constraints](/ui/layout/constraints) topic.

The root of all `RenderObject`s is the `RenderView`, which represents the total output of the render tree. When the platform demands a new frame to be rendered (for example, because of a [vsync](https://source.android.com/devices/graphics/implement-vsync) or because a texture decompression/upload is complete), a call is made to the `compositeFrame()` method, which is part of the `RenderView` object at the root of the render tree. This creates a `SceneBuilder` to trigger an update of the scene. When the scene is complete, the `RenderView` object passes the composited scene to the `Window.render()` method in `dart:ui`, which passes control to the GPU to render it.

Further details of the composition and rasterization stages of the pipeline are beyond the scope of this high-level article, but more information can be found [in this talk on the Flutter rendering pipeline](https://www.youtube.com/watch?v=UUfXWzp0-DU).

## Platform embedding

[#](#platform-embedding)

As we've seen, rather than being translated into the equivalent OS widgets, Flutter user interfaces are built, laid out, composited, and painted by Flutter itself. The mechanism for obtaining the texture and participating in the app lifecycle of the underlying operating system inevitably varies depending on the unique concerns of that platform. The engine is platform-agnostic, presenting a [stable ABI (Application Binary Interface)](https://github.com/flutter/flutter/blob/main/engine/src/flutter/shell/platform/embedder/embedder.h). that provides a *platform embedder* with a way to set up and use Flutter.

The platform embedder is the native OS application that hosts all Flutter content, and acts as the glue between the host operating system and Flutter. When you start a Flutter app, the embedder provides the entrypoint, initializes the Flutter engine, obtains threads for UI and rastering, and creates a texture that Flutter can write to. The embedder is also responsible for the app lifecycle, including input gestures (such as mouse, keyboard, touch), window sizing, thread management, and platform messages. Flutter includes platform embedders for Android, iOS, Windows, macOS, and Linux; you can also create a custom platform embedder, as in [this worked example](https://github.com/chinmaygarde/fluttercast) that supports remoting Flutter sessions through a VNC-style framebuffer or [this worked example for Raspberry Pi](https://github.com/ardera/flutter-pi).

Each platform has its own set of APIs and constraints. Some brief platform-specific notes:

* As of Flutter 3.29, the UI and platform threads are merged on iOS and Android. Specifically, the UI thread is removed and the Dart code runs on the native platform thread. For more information, see [The great thread merge](https://youtu.be/miW7vCmQwnw?si=9EYvRDxtkpkPrcSO) video.
* On iOS and macOS, Flutter is loaded into the embedder as a `UIViewController` or `NSViewController`, respectively. The platform embedder creates a `FlutterEngine`, which serves as a host to the Dart VM and your Flutter runtime, and a `FlutterViewController`, which attaches to the `FlutterEngine` to pass UIKit or Cocoa input events into Flutter and to display frames rendered by the `FlutterEngine` using Metal or OpenGL.
* On Android, Flutter is, by default, loaded into the embedder as an `Activity`. The view is controlled by a [`FlutterView`](https://api.flutter.dev/javadoc/io/flutter/embedding/android/FlutterView.html), which renders Flutter content either as a view or a texture, depending on the composition and z-ordering requirements of the Flutter content.
* On Windows, Flutter is hosted in a traditional Win32 app, and content is rendered using [ANGLE](https://chromium.googlesource.com/angle/angle/+/master/README.md), a library that translates OpenGL API calls to the DirectX 11 equivalents.

## Integrating with other code

[#](#integrating-with-other-code)

Flutter provides a variety of interoperability mechanisms, whether you're accessing code or APIs written in a language like Kotlin or Swift, calling a native C-based API, embedding native controls in a Flutter app, or embedding Flutter in an existing application.

### Platform channels

[#](#platform-channels)

For mobile and desktop apps, Flutter allows you to call into custom code through a *platform channel*, which is a mechanism for communicating between your Dart code and the platform-specific code of your host app. By creating a common channel (encapsulating a name and a codec), you can send and receive messages between Dart and a platform component written in a language like Kotlin or Swift. Data is serialized from a Dart type like `Map` into a standard format, and then deserialized into an equivalent representation in Kotlin (such as `HashMap`) or Swift (such as `Dictionary`).

The following is a short platform channel example of a Dart call to a receiving event handler in Kotlin (Android) or Swift (iOS):

dart

```
// Dart side
const channel = MethodChannel('foo');
final greeting = await channel.invokeMethod('bar', 'world') as String;
print(greeting);
```

kotlin

```
// Android (Kotlin)
val channel = MethodChannel(flutterView, "foo")
channel.setMethodCallHandler { call, result ->
  when (call.method) {
    "bar" -> result.success("Hello, ${call.arguments}")
    else -> result.notImplemented()
  }
}
```

swift

```
// iOS (Swift)
let channel = FlutterMethodChannel(name: "foo", binaryMessenger: flutterView)
channel.setMethodCallHandler {
  (call: FlutterMethodCall, result: FlutterResult) -> Void in
  switch (call.method) {
    case "bar": result("Hello, \(call.arguments as! String)")
    default: result(FlutterMethodNotImplemented)
  }
}
```

Further examples of using platform channels, including examples for desktop platforms, can be found in the [flutter/packages](https://github.com/flutter/packages) repository. There are also [thousands of plugins already available](https://pub.dev/flutter) for Flutter that cover many common scenarios, ranging from Firebase to ads to device hardware like camera and Bluetooth.

### Foreign Function Interface (FFI)

[#](#foreign-function-interface-ffi)

For C-based APIs, including those that can be generated for code written in modern languages like Rust or Go, Dart provides a direct mechanism for binding to native code using the `dart:ffi` library. The foreign function interface (FFI) model can be considerably faster than platform channels, because no serialization is required to pass data. Instead, the Dart runtime provides the ability to allocate memory on the heap that is backed by a Dart object and make calls to statically or dynamically linked libraries. FFI is available for all platforms other than web, where the [JS interop libraries](https://dart.dev/interop/js-interop) and [`package:web`](https://pub.dev/packages/web) serve a similar purpose.

To use FFI, you create a `typedef` for each of the Dart and unmanaged method signatures, and instruct the Dart VM to map between them. As an example, here's a fragment of code to call the traditional Win32 `MessageBox()` API:

dart

```
import 'dart:ffi';
​
import 'package:ffi/ffi.dart'; // contains .toNativeUtf16() extension method
​
typedef MessageBoxNative = Int32 Function(
  IntPtr hWnd,
  Pointer<Utf16> lpText,
  Pointer<Utf16> lpCaption,
  Int32 uType,
);
​
typedef MessageBoxDart = int Function(
  int hWnd,
  Pointer<Utf16> lpText,
  Pointer<Utf16> lpCaption,
  int uType,
);
​
void exampleFfi() {
  final user32 = DynamicLibrary.open('user32.dll');
  final messageBox = user32.lookupFunction<MessageBoxNative, MessageBoxDart>(
    'MessageBoxW',
  );
​
  final result = messageBox(
    0, // No owner window
    'Test message'.toNativeUtf16(), // Message
    'Window caption'.toNativeUtf16(), // Window title
    0, // OK button only
  );
}
```

### Rendering native controls in a Flutter app

[#](#rendering-native-controls-in-a-flutter-app)

Because Flutter content is drawn to a texture and its widget tree is entirely internal, there's no place for something like an Android view to exist within Flutter's internal model or render interleaved within Flutter widgets. That's a problem for developers that would like to include existing platform components in their Flutter apps, such as a browser control.

Flutter solves this by introducing platform view widgets ([`AndroidView`](https://api.flutter.dev/flutter/widgets/AndroidView-class.html) and [`UiKitView`](https://api.flutter.dev/flutter/widgets/UiKitView-class.html)) that let you embed this kind of content on each platform. Platform views can be integrated with other Flutter content[3](#fn-3). Each of these widgets acts as an intermediary to the underlying operating system. For example, on Android, `AndroidView` serves three primary functions:

* Making a copy of the graphics texture rendered by the native view and presenting it to Flutter for composition as part of a Flutter-rendered surface each time the frame is painted.
* Responding to hit testing and input gestures, and translating those into the equivalent native input.
* Creating an analog of the accessibility tree, and passing commands and responses between the native and Flutter layers.

Inevitably, there is a certain amount of overhead associated with this synchronization. In general, therefore, this approach is best suited for complex controls like Google Maps where reimplementing in Flutter isn't practical.

Typically, a Flutter app instantiates these widgets in a `build()` method based on a platform test. As an example, from the [google\_maps\_flutter](https://pub.dev/packages/google_maps_flutter) plugin:

dart

```
if (defaultTargetPlatform == TargetPlatform.android) {
  return AndroidView(
    viewType: 'plugins.flutter.io/google_maps',
    onPlatformViewCreated: onPlatformViewCreated,
    gestureRecognizers: gestureRecognizers,
    creationParams: creationParams,
    creationParamsCodec: const StandardMessageCodec(),
  );
} else if (defaultTargetPlatform == TargetPlatform.iOS) {
  return UiKitView(
    viewType: 'plugins.flutter.io/google_maps',
    onPlatformViewCreated: onPlatformViewCreated,
    gestureRecognizers: gestureRecognizers,
    creationParams: creationParams,
    creationParamsCodec: const StandardMessageCodec(),
  );
}
return Text(
  '$defaultTargetPlatform is not yet supported by the maps plugin',
);
```

Communicating with the native code underlying the `AndroidView` or `UiKitView` typically occurs using the platform channels mechanism, as previously described.

At present, platform views aren't available for desktop platforms, but this is not an architectural limitation; support might be added in the future.

### Hosting Flutter content in a parent app

[#](#hosting-flutter-content-in-a-parent-app)

The converse of the preceding scenario is embedding a Flutter widget in an existing Android or iOS app. As described in an earlier section, a newly created Flutter app running on a mobile device is hosted in an Android activity or iOS `UIViewController`. Flutter content can be embedded into an existing Android or iOS app using the same embedding API.

The Flutter module template is designed for easy embedding; you can either embed it as a source dependency into an existing Gradle or Xcode build definition, or you can compile it into an Android Archive or iOS Framework binary for use without requiring every developer to have Flutter installed.

The Flutter engine takes a short while to initialize, because it needs to load Flutter shared libraries, initialize the Dart runtime, create and run a Dart isolate, and attach a rendering surface to the UI. To minimize any UI delays when presenting Flutter content, it's best to initialize the Flutter engine during the overall app initialization sequence, or at least ahead of the first Flutter screen, so that users don't experience a sudden pause while the first Flutter code is loaded. In addition, separating the Flutter engine allows it to be reused across multiple Flutter screens and share the memory overhead involved with loading the necessary libraries.

More information about how Flutter is loaded into an existing Android or iOS app can be found at the [Load sequence, performance and memory topic](/add-to-app/performance).

## Flutter web support

[#](#flutter-web-support)

While the general architectural concepts apply to all platforms that Flutter supports, there are some unique characteristics of Flutter's web support that are worthy of comment.

Dart has been compiling to JavaScript for as long as the language has existed, with a toolchain optimized for both development and production purposes. Many important apps compile from Dart to JavaScript and run in production today, including the [advertiser tooling for Google Ads](https://ads.google.com/home/). Because the Flutter framework is written in Dart, compiling it to JavaScript was relatively straightforward.

However, the Flutter engine, written in C++, is designed to interface with the underlying operating system rather than a web browser. A different approach is therefore required.

On the web, Flutter compiles your application code into either JavaScript or [WebAssembly](https://dart.dev/web/wasm) (when building with `--wasm`), rendering graphics using a WebAssembly build of the Skia engine.

Perhaps the most notable difference compared to other platforms on which Flutter runs is that there is no need for Flutter to provide a Dart runtime. Instead, the Flutter framework (along with any code you write) is compiled to JavaScript or WebAssembly. It's also worthy to note that Dart has very few language semantic differences across all of its modes (JIT versus AOT, native versus web compilation), and most developers will never write a line of code that runs into such a difference.

During development time, Flutter web uses [`dartdevc`](https://dart.dev/tools/dartdevc), a compiler that supports incremental compilation and therefore allows hot restart and \[hot reload behind a flag]\[]. Conversely, when you are ready to create a production app for the web, [`dart2js`](https://dart.dev/tools/dart2js) or `dart2wasm` compiles the Flutter core and framework along with your application into optimized output that can be deployed to any web server. Code can be offered in a single file or split into multiple files through [deferred imports](https://dart.dev/language/libraries#lazily-loading-a-library).

For more information on Flutter web, check out [Web support for Flutter](/platform-integration/web).

## Further information

[#](#further-information)

For those interested in more information about the internals of Flutter, the [Inside Flutter](/resources/inside-flutter) whitepaper provides a useful guide to the framework's design philosophy.

1. While the `build` function returns a fresh tree, you only need to return something *different* if there's some new configuration to incorporate. If the configuration is in fact the same, you can just return the same widget. [↩](#fnref-1)

2. This is a slight simplification for ease of reading. In practice, the tree might be more complex. [↩](#fnref-2)

3. There are some limitations with this approach, for example, transparency doesn't composite the same way for a platform view as it would for other Flutter widgets. [↩](#fnref-3)

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-09-29. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/resources/architectural-overview.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/resources/architectural-overview\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/resources/architectural-overview.md "Report an issue with this page").

----
url: https://docs.flutter.dev/ui/widgets/basics
----

Widgets to know before building your first Flutter app.

[App​Bar](https://pub.dev/documentation/material_ui/latest/material_ui/AppBar-class.html)

[Container that displays content and actions at the top of a screen.](https://pub.dev/documentation/material_ui/latest/material_ui/AppBar-class.html)

[Column](https://api.flutter.dev/flutter/widgets/Column-class.html)

[Layout a list of child widgets in the vertical direction.](https://api.flutter.dev/flutter/widgets/Column-class.html)

[Container](https://api.flutter.dev/flutter/widgets/Container-class.html)

[A convenience widget that combines common painting, positioning, and sizing widgets.](https://api.flutter.dev/flutter/widgets/Container-class.html)

[Elevated​Button](https://pub.dev/documentation/material_ui/latest/material_ui/ElevatedButton-class.html)

[A Material Design elevated button. A filled button whose material elevates when pressed.](https://pub.dev/documentation/material_ui/latest/material_ui/ElevatedButton-class.html)

[Flutter​Logo](https://api.flutter.dev/flutter/widgets/FlutterLogo-class.html)

[The Flutter logo, in widget form. This widget respects the IconTheme.](https://api.flutter.dev/flutter/widgets/FlutterLogo-class.html)

[Icon](https://api.flutter.dev/flutter/widgets/Icon-class.html)

[A Material Design icon.](https://api.flutter.dev/flutter/widgets/Icon-class.html)

[Image](https://api.flutter.dev/flutter/widgets/Image-class.html)

[A widget that displays an image.](https://api.flutter.dev/flutter/widgets/Image-class.html)

[Placeholder](https://api.flutter.dev/flutter/widgets/Placeholder-class.html)

[A widget that draws a box that represents where other widgets will one day be added.](https://api.flutter.dev/flutter/widgets/Placeholder-class.html)

[Row](https://api.flutter.dev/flutter/widgets/Row-class.html)

[Layout a list of child widgets in the horizontal direction.](https://api.flutter.dev/flutter/widgets/Row-class.html)

[Scaffold](https://pub.dev/documentation/material_ui/latest/material_ui/Scaffold-class.html)

[Implements the basic Material Design visual layout structure. This class provides APIs for showing drawers, snack bars, and bottom sheets.](https://pub.dev/documentation/material_ui/latest/material_ui/Scaffold-class.html)

[Text](https://api.flutter.dev/flutter/widgets/Text-class.html)

[A run of text with a single style.](https://api.flutter.dev/flutter/widgets/Text-class.html)

Find more widgets in the [widget catalog](/ui/widgets).

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-09-29. [Report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/ui/widgets/basics "Report an issue with this page").

----
url: https://docs.flutter.dev/cookbook/effects/drag-a-widget
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Drag a UI element](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

Drag and drop is a common mobile app interaction. As the user long presses (sometimes called *touch & hold*) on a widget, another widget appears beneath the user's finger, and the user drags the widget to a final location and releases it. In this recipe, you'll build a drag-and-drop interaction where the user long presses on a choice of food, and then drags that food to the picture of the customer who is paying for it.

The following animation shows the app's behavior:

This recipe begins with a prebuilt list of menu items and a row of customers. The first step is to recognize a long press and display a draggable photo of a menu item.

## Press and drag

[#](#press-and-drag)

Flutter provides a widget called [`LongPressDraggable`](https://api.flutter.dev/flutter/widgets/LongPressDraggable-class.html) that provides the exact behavior that you need to begin a drag-and-drop interaction. A `LongPressDraggable` widget recognizes when a long press occurs and then displays a new widget near the user's finger. As the user drags, the widget follows the user's finger. `LongPressDraggable` gives you full control over the widget that the user drags.

Each menu list item is displayed with a custom `MenuListItem` widget.

dart

```
MenuListItem(
  name: item.name,
  price: item.formattedTotalItemPrice,
  photoProvider: item.imageProvider,
)
```

Wrap the `MenuListItem` widget with a `LongPressDraggable` widget.

dart

```
LongPressDraggable<Item>(
  data: item,
  dragAnchorStrategy: pointerDragAnchorStrategy,
  feedback: DraggingListItem(
    dragKey: _draggableKey,
    photoProvider: item.imageProvider,
  ),
  child: MenuListItem(
    name: item.name,
    price: item.formattedTotalItemPrice,
    photoProvider: item.imageProvider,
  ),
);
```

In this case, when the user long presses on the `MenuListItem` widget, the `LongPressDraggable` widget displays a `DraggingListItem`. This `DraggingListItem` displays a photo of the selected food item, centered beneath the user's finger.

The `dragAnchorStrategy` property is set to [`pointerDragAnchorStrategy`](https://api.flutter.dev/flutter/widgets/pointerDragAnchorStrategy.html). This property value instructs `LongPressDraggable` to base the `DraggableListItem`'s position on the user's finger. As the user moves a finger, the `DraggableListItem` moves with it.

Dragging and dropping is of little use if no information is transmitted when the item is dropped. For this reason, `LongPressDraggable` takes a `data` parameter. In this case, the type of `data` is `Item`, which holds information about the food menu item that the user pressed on.

The `data` associated with a `LongPressDraggable` is sent to a special widget called `DragTarget`, where the user releases the drag gesture. You'll implement the drop behavior next.

## Drop the draggable

[#](#drop-the-draggable)

The user can drop a `LongPressDraggable` wherever they choose, but dropping the draggable has no effect unless it's dropped on top of a `DragTarget`. When the user drops a draggable on top of a `DragTarget` widget, the `DragTarget` widget can either accept or reject the data from the draggable.

In this recipe, the user should drop a menu item on a `CustomerCart` widget to add the menu item to the user's cart.

dart

```
CustomerCart(
  hasItems: customer.items.isNotEmpty,
  highlighted: candidateItems.isNotEmpty,
  customer: customer,
);
```

Wrap the `CustomerCart` widget with a `DragTarget` widget.

dart

```
DragTarget<Item>(
  builder: (context, candidateItems, rejectedItems) {
    return CustomerCart(
      hasItems: customer.items.isNotEmpty,
      highlighted: candidateItems.isNotEmpty,
      customer: customer,
    );
  },
  onAcceptWithDetails: (details) {
    _itemDroppedOnCustomerCart(item: details.data, customer: customer);
  },
)
```

The `DragTarget` displays your existing widget and also coordinates with `LongPressDraggable` to recognize when the user drags a draggable on top of the `DragTarget`. The `DragTarget` also recognizes when the user drops a draggable on top of the `DragTarget` widget.

When the user drags a draggable on the `DragTarget` widget, `candidateItems` contains the data items that the user is dragging. This draggable allows you to change what your widget looks like when the user is dragging over it. In this case, the `Customer` widget turns red whenever any items are dragged above the `DragTarget` widget. The red visual appearance is configured with the `highlighted` property within the `CustomerCart` widget.

When the user drops a draggable on the `DragTarget` widget, the `onAcceptWithDetails` callback is invoked. This is when you get to decide whether or not to accept the data that was dropped. In this case, the item is always accepted and processed. You might choose to inspect the incoming item to make a different decision.

Notice that the type of item dropped on `DragTarget` must match the type of the item dragged from `LongPressDraggable`. If the types are not compatible, then the `onAcceptWithDetails` method isn't invoked.

With a `DragTarget` widget configured to accept your desired data, you can now transmit data from one part of your UI to another by dragging and dropping.

In the next step, you update the customer's cart with the dropped menu item.

## Add a menu item to a cart

[#](#add-a-menu-item-to-a-cart)

Each customer is represented by a `Customer` object, which maintains a cart of items and a price total.

dart

```
class Customer {
  Customer({required this.name, required this.imageProvider, List<Item>? items})
    : items = items ?? [];
​
  final String name;
  final ImageProvider imageProvider;
  final List<Item> items;
​
  String get formattedTotalItemPrice {
    final totalPriceCents = items.fold<int>(
      0,
      (prev, item) => prev + item.totalPriceCents,
    );
    return '\$${(totalPriceCents / 100.0).toStringAsFixed(2)}';
  }
}
```

The `CustomerCart` widget displays the customer's photo, name, total, and item count based on a `Customer` instance.

To update a customer's cart when a menu item is dropped, add the dropped item to the associated `Customer` object.

dart

```
void _itemDroppedOnCustomerCart({
  required Item item,
  required Customer customer,
}) {
  setState(() {
    customer.items.add(item);
  });
}
```

The `_itemDroppedOnCustomerCart` method is invoked in `onAcceptWithDetails()` when the user drops a menu item on a `CustomerCart` widget. By adding the dropped item to the `customer` object, and invoking `setState()` to cause a layout update, the UI refreshes with the new customer's price total and item count.

Congratulations! You have a drag-and-drop interaction that adds food items to a customer's shopping cart.

## Interactive example

[#](#interactive-example)

Run the app:

* Scroll through the food items.
* Press and hold on one with your finger or click and hold with the mouse.
* While holding, the food item's image will appear above the list.
* Drag the image and drop it on one of the people at the bottom of the screen. The text under the image updates to reflect the charge for that person. You can continue to add food items and watch the charges accumulate.

```
import 'package:flutter/material.dart';

void main() {
  runApp(
    const MaterialApp(
      home: ExampleDragAndDrop(),
      debugShowCheckedModeBanner: false,
    ),
  );
}

const _urlPrefix =
    'https://docs.flutter.dev/assets/images/exercise/effects/split-check';

const List<Item> _items = [
  Item(
    name: 'Spinach Pizza',
    totalPriceCents: 1299,
    uid: '1',
    imageProvider: NetworkImage('$_urlPrefix/Food1.jpg'),
  ),
  Item(
    name: 'Veggie Delight',
    totalPriceCents: 799,
    uid: '2',
    imageProvider: NetworkImage('$_urlPrefix/Food2.jpg'),
  ),
  Item(
    name: 'Chicken Parmesan',
    totalPriceCents: 1499,
    uid: '3',
    imageProvider: NetworkImage('$_urlPrefix/Food3.jpg'),
  ),
];

@immutable
class ExampleDragAndDrop extends StatefulWidget {
  const ExampleDragAndDrop({super.key});

  @override
  State<ExampleDragAndDrop> createState() => _ExampleDragAndDropState();
}

class _ExampleDragAndDropState extends State<ExampleDragAndDrop>
    with TickerProviderStateMixin {
  final List<Customer> _people = [
    Customer(
      name: 'Makayla',
      imageProvider: const NetworkImage('$_urlPrefix/Avatar1.jpg'),
    ),
    Customer(
      name: 'Nathan',
      imageProvider: const NetworkImage('$_urlPrefix/Avatar2.jpg'),
    ),
    Customer(
      name: 'Emilio',
      imageProvider: const NetworkImage('$_urlPrefix/Avatar3.jpg'),
    ),
  ];

  final GlobalKey _draggableKey = GlobalKey();

  void _itemDroppedOnCustomerCart({
    required Item item,
    required Customer customer,
  }) {
    setState(() {
      customer.items.add(item);
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: const Color(0xFFF7F7F7),
      appBar: _buildAppBar(),
      body: _buildContent(),
    );
  }

  PreferredSizeWidget _buildAppBar() {
    return AppBar(
      iconTheme: const IconThemeData(color: Color(0xFFF64209)),
      title: Text(
        'Order Food',
        style: Theme.of(context).textTheme.headlineMedium?.copyWith(
          fontSize: 36,
          color: const Color(0xFFF64209),
          fontWeight: FontWeight.bold,
        ),
      ),
      backgroundColor: const Color(0xFFF7F7F7),
      elevation: 0,
    );
  }

  Widget _buildContent() {
    return Stack(
      children: [
        SafeArea(
          child: Column(
            children: [
              Expanded(child: _buildMenuList()),
              _buildPeopleRow(),
            ],
          ),
        ),
      ],
    );
  }

  Widget _buildMenuList() {
    return ListView.separated(
      padding: const EdgeInsets.all(16),
      itemCount: _items.length,
      separatorBuilder: (context, index) {
        return const SizedBox(height: 12);
      },
      itemBuilder: (context, index) {
        final item = _items[index];
        return _buildMenuItem(item: item);
      },
    );
  }

  Widget _buildMenuItem({required Item item}) {
    return LongPressDraggable<Item>(
      data: item,
      dragAnchorStrategy: pointerDragAnchorStrategy,
      feedback: DraggingListItem(
        dragKey: _draggableKey,
        photoProvider: item.imageProvider,
      ),
      child: MenuListItem(
        name: item.name,
        price: item.formattedTotalItemPrice,
        photoProvider: item.imageProvider,
      ),
    );
  }

  Widget _buildPeopleRow() {
    return Container(
      padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 20),
      child: Row(children: _people.map(_buildPersonWithDropZone).toList()),
    );
  }

  Widget _buildPersonWithDropZone(Customer customer) {
    return Expanded(
      child: Padding(
        padding: const EdgeInsets.symmetric(horizontal: 6),
        child: DragTarget<Item>(
          builder: (context, candidateItems, rejectedItems) {
            return CustomerCart(
              hasItems: customer.items.isNotEmpty,
              highlighted: candidateItems.isNotEmpty,
              customer: customer,
            );
          },
          onAcceptWithDetails: (details) {
            _itemDroppedOnCustomerCart(item: details.data, customer: customer);
          },
        ),
      ),
    );
  }
}

class CustomerCart extends StatelessWidget {
  const CustomerCart({
    super.key,
    required this.customer,
    this.highlighted = false,
    this.hasItems = false,
  });

  final Customer customer;
  final bool highlighted;
  final bool hasItems;

  @override
  Widget build(BuildContext context) {
    final textColor = highlighted ? Colors.white : Colors.black;

    return Transform.scale(
      scale: highlighted ? 1.075 : 1.0,
      child: Material(
        elevation: highlighted ? 8 : 4,
        borderRadius: BorderRadius.circular(22),
        color: highlighted ? const Color(0xFFF64209) : Colors.white,
        child: Padding(
          padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 24),
          child: Column(
            mainAxisSize: MainAxisSize.min,
            children: [
              ClipOval(
                child: SizedBox(
                  width: 46,
                  height: 46,
                  child: Image(
                    image: customer.imageProvider,
                    fit: BoxFit.cover,
                  ),
                ),
              ),
              const SizedBox(height: 8),
              Text(
                customer.name,
                style: Theme.of(context).textTheme.titleMedium?.copyWith(
                  color: textColor,
                  fontWeight: hasItems ? FontWeight.normal : FontWeight.bold,
                ),
              ),
              Visibility(
                visible: hasItems,
                maintainState: true,
                maintainAnimation: true,
                maintainSize: true,
                child: Column(
                  children: [
                    const SizedBox(height: 4),
                    Text(
                      customer.formattedTotalItemPrice,
                      style: Theme.of(context).textTheme.bodySmall!.copyWith(
                        color: textColor,
                        fontSize: 16,
                        fontWeight: FontWeight.bold,
                      ),
                    ),
                    const SizedBox(height: 4),
                    Text(
                      '${customer.items.length} item${customer.items.length != 1 ? 's' : ''}',
                      style: Theme.of(context).textTheme.titleMedium!
                          .copyWith(color: textColor, fontSize: 12),
                    ),
                  ],
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

class MenuListItem extends StatelessWidget {
  const MenuListItem({
    super.key,
    this.name = '',
    this.price = '',
    required this.photoProvider,
    this.isDepressed = false,
  });

  final String name;
  final String price;
  final ImageProvider photoProvider;
  final bool isDepressed;

  @override
  Widget build(BuildContext context) {
    return Material(
      elevation: 12,
      borderRadius: BorderRadius.circular(20),
      child: Padding(
        padding: const EdgeInsets.all(12),
        child: Row(
          mainAxisSize: MainAxisSize.max,
          children: [
            ClipRRect(
              borderRadius: BorderRadius.circular(12),
              child: SizedBox(
                width: 120,
                height: 120,
                child: Center(
                  child: AnimatedContainer(
                    duration: const Duration(milliseconds: 100),
                    curve: Curves.easeInOut,
                    height: isDepressed ? 115 : 120,
                    width: isDepressed ? 115 : 120,
                    child: Image(image: photoProvider, fit: BoxFit.cover),
                  ),
                ),
              ),
            ),
            const SizedBox(width: 30),
            Expanded(
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Text(
                    name,
                    style: Theme.of(context).textTheme.titleMedium
                        ?.copyWith(fontSize: 18),
                  ),
                  const SizedBox(height: 10),
                  Text(
                    price,
                    style: Theme.of(context).textTheme.titleMedium
                        ?.copyWith(fontWeight: FontWeight.bold, fontSize: 18),
                  ),
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }
}

class DraggingListItem extends StatelessWidget {
  const DraggingListItem({
    super.key,
    required this.dragKey,
    required this.photoProvider,
  });

  final GlobalKey dragKey;
  final ImageProvider photoProvider;

  @override
  Widget build(BuildContext context) {
    return FractionalTranslation(
      translation: const Offset(-0.5, -0.5),
      child: ClipRRect(
        key: dragKey,
        borderRadius: BorderRadius.circular(12),
        child: SizedBox(
          height: 150,
          width: 150,
          child: Opacity(
            opacity: 0.85,
            child: Image(image: photoProvider, fit: BoxFit.cover),
          ),
        ),
      ),
    );
  }
}

@immutable
class Item {
  const Item({
    required this.totalPriceCents,
    required this.name,
    required this.uid,
    required this.imageProvider,
  });
  final int totalPriceCents;
  final String name;
  final String uid;
  final ImageProvider imageProvider;
  String get formattedTotalItemPrice =>
      '\$${(totalPriceCents / 100.0).toStringAsFixed(2)}';
}

class Customer {
  Customer({required this.name, required this.imageProvider, List<Item>? items})
    : items = items ?? [];

  final String name;
  final ImageProvider imageProvider;
  final List<Item> items;

  String get formattedTotalItemPrice {
    final totalPriceCents = items.fold<int>(
      0,
      (prev, item) => prev + item.totalPriceCents,
    );
    return '\$${(totalPriceCents / 100.0).toStringAsFixed(2)}';
  }
}
```

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-08-24. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/effects/drag-a-widget.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/cookbook/effects/drag-a-widget\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/effects/drag-a-widget.md "Report an issue with this page").

----
url: https://docs.flutter.dev/platform-integration/ios/ios-debugging
----

Due to security around [local network permissions in iOS 14 or later](https://developer.apple.com/news/?id=0oi77447), you must accept a permission dialog box to enable Flutter debugging functionalities such as hot-reload and DevTools.

This affects debug and profile builds only and won't appear in release builds. You can also allow this permission by enabling **Settings > Privacy > Local Network > Your App**.

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/platform-integration/ios/ios-debugging.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/platform-integration/ios/ios-debugging\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/platform-integration/ios/ios-debugging.md "Report an issue with this page").

----
url: https://docs.flutter.dev/testing/debugging
----

There's a wide variety of tools and features to help debug Flutter applications. Here are some of the available tools:

* [VS Code](/tools/vs-code#run-app-with-breakpoints) (recommended) and [Android Studio/IntelliJ](/tools/android-studio#run-app-with-breakpoints), (enabled with the Flutter and Dart plugins) support a built-in source-level debugger with the ability to set breakpoints, step through code, and examine values.
* [DevTools](/tools/devtools), a suite of performance and profiling tools that run in a browser.
* [Flutter inspector](/tools/devtools/inspector), a widget inspector available in DevTools, and also directly from Android Studio and IntelliJ (enabled with the Flutter plugin). The inspector allows you to examine a visual representation of the widget tree, inspect individual widgets and their property values, enable the performance overlay, and more.

## Other resources

[#](#other-resources)

You might find the following docs useful:

* [Performance best practices](/perf/best-practices)
* [Flutter performance profiling](/perf/ui-performance)
* [Use a native debugger](/testing/native-debugging)
* [Flutter's modes](/testing/build-modes)
* [Debugging Flutter apps programmatically](/testing/code-debugging)

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/testing/debugging.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/testing/debugging\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/testing/debugging.md "Report an issue with this page").

----
url: https://docs.flutter.dev/testing/overview.md
----

\# Testing Flutter apps > Learn more about the different types of testing and how to write them. The more features your app has, the harder it is to test manually. Automated tests help ensure that your app performs correctly before you publish it, while retaining your feature and bug fix velocity. Automated testing falls into a few categories: \* A \[\_unit test\_]\(#unit-tests) tests a single function, method, or class. \* A \[\_widget test\_]\(#widget-tests) (in other UI frameworks referred to as \_component test\_) tests a single widget. \* An \[\_integration test\_]\(#integration-tests) tests a complete app or a large part of an app. Generally speaking, a well-tested app has many unit and widget tests, tracked by \[code coverage]\[], plus enough integration tests to cover all the important use cases. This advice is based on the fact that there are trade-offs between different kinds of testing, seen below. | Tradeoff | Unit | Widget | Integration | |----------------------|--------|--------|-------------| | \*\*Confidence\*\* | Low | Higher | Highest | | \*\*Maintenance cost\*\* | Low | Higher | Highest | | \*\*Dependencies\*\* | Few | More | Most | | \*\*Execution speed\*\* | Quick | Quick | Slow | {:.table .table-striped} ## Unit tests A \_unit test\_ tests a single function, method, or class. The goal of a unit test is to verify the correctness of a unit of logic under a variety of conditions. External dependencies of the unit under test are generally \[mocked out]\(/cookbook/testing/unit/mocking). Unit tests generally don't read from or write to disk, render to screen, or receive user actions from outside the process running the test. For more information regarding unit tests, you can view the following recipes or run \`flutter test --help\` in your terminal. :::note If you're writing unit tests for code that uses plugins and you want to avoid crashes, check out \[Plugins in Flutter tests]\[]. If you want to test your Flutter plugin, check out \[Testing plugins]\[]. ::: \[Plugins in Flutter tests]: /testing/plugins-in-tests \[Testing plugins]: /testing/testing-plugins ### Recipes {:.no\_toc} - \[Introduction to unit testing]\(/cookbook/testing/unit/introduction) - \[Mock dependencies using Mockito]\(/cookbook/testing/unit/mocking) ## Widget tests A \_widget test\_ (in other UI frameworks referred to as \_component test\_) tests a single widget. The goal of a widget test is to verify that the widget's UI looks and interacts as expected. Testing a widget involves multiple classes and requires a test environment that provides the appropriate widget lifecycle context. For example, the Widget being tested should be able to receive and respond to user actions and events, perform layout, and instantiate child widgets. A widget test is therefore more comprehensive than a unit test. However, like a unit test, a widget test's environment is replaced with an implementation much simpler than a full-blown UI system. ### Recipes {:.no\_toc} - \[Introduction to widget testing]\(/cookbook/testing/widget/introduction) - \[Find widgets using finders]\(/cookbook/testing/widget/finders) - \[Handling scrolling in widget tests]\(/cookbook/testing/widget/scrolling) - \[Tap, drag, and enter text in widget tests]\(/cookbook/testing/widget/tap-drag) - \[Test different orientations]\(/cookbook/testing/widget/orientation) ## Integration tests An \_integration test\_ tests a complete app or a large part of an app. The goal of an integration test is to verify that all the widgets and services being tested work together as expected. Furthermore, you can use integration tests to verify your app's performance. Generally, an \_integration test\_ runs on a real device or an OS emulator, such as iOS Simulator or Android Emulator. The app under test is typically isolated from the test driver code to avoid skewing the results. The Flutter SDK includes the \[\`integration\_test\`]\[] package. However, this package can't interact with native platform UI, such as permission dialogs, notifications, or platform views. For apps that need native interactions, you can use the \[\`patrol\`]\[] package, an open-source framework that extends Flutter's testing capabilities with native platform support. For more information on how to write integration tests, see the \[integration testing page]\[]. \[\`integration\_test\`]: https\://github.com/flutter/flutter/tree/main/packages/integration\_test \[\`patrol\`]: https\://pub.dev/packages/patrol ### Recipes {:.no\_toc} - \[Integration testing concepts]\(/cookbook/testing/integration/introduction) - \[Write and run a Flutter integration test]\(/testing/integration-tests) - \[Write and run a Patrol integration test]\(https\://patrol.leancode.co/documentation/write-your-first-test) - \[Measure performance with an integration test]\(/cookbook/testing/integration/profiling) ## Continuous integration services Continuous integration (CI) services allow you to run your tests automatically when pushing new code changes. This provides timely feedback on whether the code changes work as expected and do not introduce bugs. For information on running tests on various continuous integration services, see the following: \* \[Continuous delivery using fastlane with Flutter]\[] \* \[Test Flutter apps on Appcircle]\[] \* \[Test Flutter apps on Travis]\[] \* \[Test Flutter apps on Cirrus]\[] \* \[Codemagic CI/CD for Flutter]\[] \* \[Codemagic CI/CD for Patrol]\[] \* \[Flutter CI/CD with Bitrise]\[] \[code coverage]: https\://en.wikipedia.org/wiki/Code\_coverage \[Codemagic CI/CD for Flutter]: https\://blog.codemagic.io/getting-started-with-codemagic/ \[Codemagic CI/CD for Patrol]: https\://docs.codemagic.io/integrations/patrol-integration/ \[Continuous delivery using fastlane with Flutter]: /deployment/cd#fastlane \[Flutter CI/CD with Bitrise]: https\://devcenter.bitrise.io/en/getting-started/quick-start-guides/getting-started-with-flutter-apps \[Test Flutter apps on Appcircle]: https\://blog.appcircle.io/article/flutter-ci-cd-github-ios-android-web# \[Test Flutter apps on Cirrus]: https\://cirrus-ci.org/examples/#flutter \[Test Flutter apps on Travis]: https\://blog.flutter.dev/test-flutter-apps-on-travis-3fd5142ecd8c \[integration testing page]: /cookbook/testing/integration/introduction

----
url: https://docs.flutter.dev/tools/devtools/android-studio
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Run DevTools from Android Studio](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

## Install the Flutter plugin

[#](#install-the-flutter-plugin)

Add the Flutter plugin if you don't already have it installed. This can be done using the normal **Plugins** page in the IntelliJ and Android Studio settings. Once that page is open, you can search the marketplace for the Flutter plugin.

## Start an app to debug

[#](#run-and-debug)

To open DevTools, you first need to run a Flutter app. This can be accomplished by opening a Flutter project, ensuring that you have a device connected, and clicking the **Run** or **Debug** toolbar buttons.

## Launch DevTools from the toolbar/menu

[#](#launch-devtools-from-the-toolbarmenu)

Once an app is running, you can start DevTools using one of the following techniques:

* Select the **Open DevTools** toolbar action in the Run view.
* Select the **Open DevTools** toolbar action in the Debug view. (if debugging)
* Select the **Open DevTools** action from the **More Actions** menu in the Flutter Inspector view.

## Launch DevTools from an action

[#](#launch-devtools-from-an-action)

You can also open DevTools from an IntelliJ action. Open the **Find Action...** dialog (on macOS, press `Cmd` + `Shift` + `A`), and search for the **Open DevTools** action. When you select that action, the DevTools server launches and a browser instance opens pointing to the DevTools app.

When opened with an IntelliJ action, DevTools is not connected to a Flutter app. You'll need to provide a service protocol port for a currently running app. You can do this using the inline **Connect to a running app** dialog.

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/android-studio.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/tools/devtools/android-studio\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/android-studio.md "Report an issue with this page").

----
url: https://docs.flutter.dev/ui/adaptive-responsive/platform-adaptations.md
----

\# Automatic platform adaptations > Learn more about Flutter's platform adaptiveness. ## Adaptation philosophy In general, two cases of platform adaptiveness exist: 1. Things that are behaviors of the OS environment (such as text editing and scrolling) and that would be 'wrong' if a different behavior took place. 2. Things that are conventionally implemented in apps using the OEM's SDKs (such as using parallel tabs on iOS or showing an \[\`android.app.AlertDialog\`]\[] on Android). This article mainly covers the automatic adaptations provided by Flutter in case 1 on Android and iOS. For case 2, Flutter bundles the means to produce the appropriate effects of the platform conventions but doesn't adapt automatically when app design choices are needed. For a discussion, see \[issue #8410]\[] and the \[Material/Cupertino adaptive widget problem definition]\[]. For an example of an app using different information architecture structures on Android and iOS but sharing the same content code, see the \[platform\_design code samples]\[]. :::secondary Preliminary guides addressing case 2 are being added to the UI components section. You can request additional guides by commenting on \[issue #8427]\[8427]. ::: \[\`android.app.AlertDialog\`]: https\://developer.android.com/reference/android/app/AlertDialog.html \[issue #8410]: https\://github.com/flutter/flutter/issues/8410#issuecomment-468034023 \[Material/Cupertino adaptive widget problem definition]: https\://bit.ly/flutter-adaptive-widget-problem \[platform\_design code samples]: https\://github.com/flutter/samples/tree/main/platform\_design ## Page navigation Flutter provides the navigation patterns seen on Android and iOS and also automatically adapts the navigation animation to the current platform. ### Navigation transitions On \*\*Android\*\*, the default \[\`Navigator.push()\`]\[] transition is modeled after \[\`startActivity()\`]\[], which generally has one bottom-up animation variant. On \*\*iOS\*\*: \* The default \[\`Navigator.push()\`]\[] API produces an iOS Show/Push style transition that animates from end-to-start depending on the locale's RTL setting. The page behind the new route also parallax-slides in the same direction as in iOS. \* A separate bottom-up transition style exists when pushing a page route where \[\`PageRoute.fullscreenDialog\`]\[] is true. This represents iOS's Present/Modal style transition and is typically used on fullscreen modal pages.\[\`Navigator.push()\`]: https\://api.flutter.dev/flutter/widgets/Navigator/push.html \[\`startActivity()\`]: https\://developer.android.com/reference/kotlin/android/app/Activity#startactivity \[\`PageRoute.fullscreenDialog\`]: https\://api.flutter.dev/flutter/widgets/PageRoute-class.html ### Platform-specific transition details On \*\*Android\*\*, Flutter uses the \[\`ZoomPageTransitionsBuilder\`]\[] animation. When the user taps on an item, the UI zooms in to a screen that features that item. When the user taps to go back, the UI zooms out to the previous screen. On \*\*iOS\*\* when the push style transition is used, Flutter's bundled \[\`CupertinoNavigationBar\`]\[] and \[\`CupertinoSliverNavigationBar\`]\[] nav bars automatically animate each subcomponent to its corresponding subcomponent on the next or previous page's \`CupertinoNavigationBar\` or \`CupertinoSliverNavigationBar\`.\[\`ZoomPageTransitionsBuilder\`]: https\://pub.dev/documentation/material\_ui/latest/material\_ui/ZoomPageTransitionsBuilder-class.html \[\`CupertinoNavigationBar\`]: https\://pub.dev/documentation/cupertino\_ui/latest/cupertino\_ui/CupertinoNavigationBar-class.html \[\`CupertinoSliverNavigationBar\`]: https\://pub.dev/documentation/cupertino\_ui/latest/cupertino\_ui/CupertinoSliverNavigationBar-class.html ### Back navigation On \*\*Android\*\*, the OS back button, by default, is sent to Flutter and pops the top route of the \[\`WidgetsApp\`]\[]'s Navigator. On \*\*iOS\*\*, an edge swipe gesture can be used to pop the top route.\[\`WidgetsApp\`]: https\://api.flutter.dev/flutter/widgets/WidgetsApp-class.html ## Scrolling Scrolling is an important part of the platform's look and feel, and Flutter automatically adjusts the scrolling behavior to match the current platform. ### Physics simulation Android and iOS both have complex scrolling physics simulations that are difficult to describe verbally. Generally, iOS's scrollable has more weight and dynamic friction but Android has more static friction. Therefore iOS gains high speed more gradually but stops less abruptly and is more slippery at slow speeds.### Overscroll behavior On \*\*Android\*\*, scrolling past the edge of a scrollable shows an \[overscroll glow indicator]\[] (based on the color of the current Material theme). On \*\*iOS\*\*, scrolling past the edge of a scrollable \[overscrolls]\[] with increasing resistance and snaps back.\[overscroll glow indicator]: https\://api.flutter.dev/flutter/widgets/GlowingOverscrollIndicator-class.html \[overscrolls]: https\://api.flutter.dev/flutter/widgets/BouncingScrollPhysics-class.html ### Scrollbars On \*\*Material-based platforms\*\* (such as Android and web), scrollbars are typically visible during scrolling and may remain visible depending on the platform and theme. On \*\*Cupertino-based platforms\*\* (such as iOS), scrollbars are more minimal and generally only appear briefly while the user is actively scrolling, fading out when interaction stops. This difference reflects each platform’s visual conventions and helps maintain a native look and feel across devices. ### Momentum On \*\*iOS\*\*, repeated flings in the same direction stacks momentum and builds more speed with each successive fling. There is no equivalent behavior on Android.### Return to top On \*\*iOS\*\*, tapping the OS status bar scrolls the primary scroll controller to the top position. There is no equivalent behavior on Android.## Typography When using the Material package, the typography automatically defaults to the font family appropriate for the platform. Android uses the Roboto font. iOS uses the San Francisco font. When using the Cupertino package, the \[default theme]\[] uses the San Francisco font. The San Francisco font license limits its usage to software running on iOS, macOS, or tvOS only. Therefore a fallback font is used when running on Android if the platform is debug-overridden to iOS or the default Cupertino theme is used. You might choose to adapt the text styling of Material widgets to match the default text styling on iOS. You can see widget-specific examples in the \[UI Component section]\(#ui-components).\[default theme]: https\://github.com/flutter/flutter/blob/main/packages/flutter/lib/src/cupertino/text\_theme.dart ## Iconography When using the Material package, certain icons automatically show different graphics depending on the platform. For instance, the overflow button's three dots are horizontal on iOS and vertical on Android. The back button is a simple chevron on iOS and has a stem/shaft on Android.The material library also provides a set of platform-adaptive icons through \[\`Icons.adaptive\`]\[]. \[\`Icons.adaptive\`]: https\://pub.dev/documentation/material\_ui/latest/material\_ui/PlatformAdaptiveIcons-class.html ## Haptic feedback The Material and Cupertino packages automatically trigger the platform appropriate haptic feedback in certain scenarios. For instance, a word selection via text field long-press triggers a 'buzz' vibrate on Android and not on iOS. Scrolling through picker items on iOS triggers a 'light impact' knock and no feedback on Android. ## Text editing Both the Material and Cupertino Text Input fields support spellcheck and adapt to use the proper spellcheck configuration for the platform, and the proper spell check menu and highlight colors. Flutter also makes the below adaptations while editing the content of text fields to match the current platform. ### Keyboard gesture navigation On \*\*Android\*\*, horizontal swipes can be made on the soft keyboard's `space` key to move the cursor in Material and Cupertino text fields. On \*\*iOS\*\* devices with 3D Touch capabilities, a force-press-drag gesture could be made on the soft keyboard to move the cursor in 2D via a floating cursor. This works on both Material and Cupertino text fields.### Text selection toolbar With \*\*Material on Android\*\*, the Android style selection toolbar is shown when a text selection is made in a text field. With \*\*Material on iOS\*\* or when using \*\*Cupertino\*\*, the iOS style selection toolbar is shown when a text selection is made in a text field.### Single tap gesture With \*\*Material on Android\*\*, a single tap in a text field puts the cursor at the location of the tap. A collapsed text selection also shows a draggable handle to subsequently move the cursor. With \*\*Material on iOS\*\* or when using \*\*Cupertino\*\*, a single tap in a text field puts the cursor at the nearest edge of the word tapped. Collapsed text selections don't have draggable handles on iOS.### Long-press gesture With \*\*Material on Android\*\*, a long press selects the word under the long press. The selection toolbar is shown upon release. With \*\*Material on iOS\*\* or when using \*\*Cupertino\*\*, a long press places the cursor at the location of the long press. The selection toolbar is shown upon release.### Long-press drag gesture With \*\*Material on Android\*\*, dragging while holding the long press expands the words selected. With \*\*Material on iOS\*\* or when using \*\*Cupertino\*\*, dragging while holding the long press moves the cursor.### Double tap gesture On both Android and iOS, a double tap selects the word receiving the double tap and shows the selection toolbar.## UI components This section includes preliminary recommendations on how to adapt Material widgets to deliver a natural and compelling experience on iOS. Your feedback is welcomed on \[issue #8427]\[8427]. \[8427]: https\://github.com/flutter/website/issues/8427 ### Widgets with .adaptive() constructors Several widgets support \`.adaptive()\` constructors. The following table lists these widgets. Adaptive constructors substitute the corresponding Cupertino components when the app is run on an iOS device. Widgets in the following table are used primarily for input, selection, and to display system information. Because these controls are tightly integrated with the operating system, users have been trained to recognize and respond to them. Therefore, we recommend that you follow platform conventions. | Material widget | Cupertino widget | Adaptive constructor | |---|---|---| |\
\`Switch\`|\
\`CupertinoSwitch\`|\[\`Switch.adaptive()\`]\[]| |\
\`Slider\`|\
\`CupertinoSlider\`|\[\`Slider.adaptive()\`]\[]| |\
\`CircularProgressIndicator\`|\
\`CupertinoActivityIndicator\`|\[\`CircularProgressIndicator.adaptive()\`]\[]| |\
\`RefreshProgressIndicator\`|\
\`CupertinoActivityIndicator\`|\[\`RefreshIndicator.adaptive()\`]\[]| |\
\`Checkbox\`|\
\`CupertinoCheckbox\`|\[\`Checkbox.adaptive()\`]\[]| |\
\`Radio\`|\
\`CupertinoRadio\`|\[\`Radio.adaptive()\`]\[]| |\
\`AlertDialog\`|\
\`CupertinoAlertDialog\`|\[\`AlertDialog.adaptive()\`]\[]| \[\`AlertDialog.adaptive()\`]: https\://pub.dev/documentation/material\_ui/latest/material\_ui/AlertDialog/AlertDialog.adaptive.html \[\`Checkbox.adaptive()\`]: https\://pub.dev/documentation/material\_ui/latest/material\_ui/Checkbox/Checkbox.adaptive.html \[\`Radio.adaptive()\`]: https\://pub.dev/documentation/material\_ui/latest/material\_ui/Radio/Radio.adaptive.html \[\`Switch.adaptive()\`]: https\://pub.dev/documentation/material\_ui/latest/material\_ui/Switch/Switch.adaptive.html \[\`Slider.adaptive()\`]: https\://pub.dev/documentation/material\_ui/latest/material\_ui/Slider/Slider.adaptive.html \[\`CircularProgressIndicator.adaptive()\`]: https\://pub.dev/documentation/material\_ui/latest/material\_ui/CircularProgressIndicator/CircularProgressIndicator.adaptive.html \[\`RefreshIndicator.adaptive()\`]: https\://pub.dev/documentation/material\_ui/latest/material\_ui/RefreshIndicator/RefreshIndicator.adaptive.html ### Top app bar and navigation bar Since Android 12, the default UI for top app bars follows the design guidelines defined in \[Material 3]\[mat-appbar]. On iOS, an equivalent component called "Navigation Bars" is defined in \[Apple's Human Interface Guidelines]\[hig-appbar] (HIG).Certain properties of app bars in Flutter apps should be adapted, like system icons and page transitions. These are already automatically adapted when using the Material \`AppBar\` and \`SliverAppBar\` widgets. You can also further customize the properties of these widgets to better match iOS platform styles, as shown below. \`\`\`dart // Map the text theme to iOS styles TextTheme cupertinoTextTheme = TextTheme( headlineMedium: CupertinoThemeData() .textTheme .navLargeTitleTextStyle // fixes a small bug with spacing .copyWith(letterSpacing: -1.5), titleLarge: CupertinoThemeData().textTheme.navTitleTextStyle) ... // Use iOS text theme on iOS devices ThemeData( textTheme: Platform.isIOS ? cupertinoTextTheme : null, ... ) ... // Modify AppBar properties AppBar( surfaceTintColor: Platform.isIOS ? Colors.transparent : null, shadowColor: Platform.isIOS ? CupertinoColors.darkBackgroundGray : null, scrolledUnderElevation: Platform.isIOS ? .1 : null, toolbarHeight: Platform.isIOS ? 44 : null, ... ), \`\`\` But, because app bars are displayed alongside other content in your page, it's only recommended to adapt the styling so long as it's cohesive with the rest of your application. You can see additional code samples and a further explanation in \[the GitHub discussion on app bar adaptations]\[appbar-post]. \[mat-appbar]: https\://m3.material.io/components/top-app-bar/overview \[hig-appbar]: https\://developer.apple.com/design/human-interface-guidelines/components/navigation-and-search/navigation-bars/ \[appbar-post]: https\://github.com/flutter/uxr/discussions/93 ### Bottom navigation bars Since Android 12, the default UI for bottom navigation bars follow the design guidelines defined in \[Material 3]\[mat-navbar]. On iOS, an equivalent component called "Tab Bars" is defined in \[Apple's Human Interface Guidelines]\[hig-tabbar] (HIG).Since tab bars are persistent across your app, they should match your own branding. However, if you choose to use Material's default styling on Android, you might consider adapting to the default iOS tab bars. To implement platform-specific bottom navigation bars, you can use Flutter's \`NavigationBar\` widget on Android and the \`CupertinoTabBar\` widget on iOS. Below is a code snippet you can adapt to show a platform-specific navigation bars. \`\`\`dart final Map \_navigationItems = { 'Menu': Platform.isIOS ? Icon(CupertinoIcons.house\_fill) : Icon(Icons.home), 'Order': Icon(Icons.adaptive.share), }; ... Scaffold( body: \_currentWidget, bottomNavigationBar: Platform.isIOS ? CupertinoTabBar( currentIndex: \_currentIndex, onTap: (index) { setState(() => \_currentIndex = index); \_loadScreen(); }, items: \_navigationItems.entries .map( (entry) => BottomNavigationBarItem( icon: entry.value, label: entry.key, )) .toList(), ) : NavigationBar( selectedIndex: \_currentIndex, onDestinationSelected: (index) { setState(() => \_currentIndex = index); \_loadScreen(); }, destinations: \_navigationItems.entries .map((entry) => NavigationDestination( icon: entry.value, label: entry.key, )) .toList(), )); \`\`\` \[mat-navbar]: https\://m3.material.io/components/navigation-bar/overview \[hig-tabbar]: https\://developer.apple.com/design/human-interface-guidelines/components/navigation-and-search/tab-bars/ ### Text fields Since Android 12, text fields follow the \[Material 3]\[m3-text-field] (M3) design guidelines. On iOS, Apple's \[Human Interface Guidelines]\[hig-text-field] (HIG) define an equivalent component.Since text fields require user input, their design should follow platform conventions. To implement a platform-specific \`TextField\` in Flutter, you can adapt the styling of the Material \`TextField\`. \`\`\`dart Widget \_createAdaptiveTextField() { final \_border = OutlineInputBorder( borderSide: BorderSide(color: CupertinoColors.lightBackgroundGray), ); final iOSDecoration = InputDecoration( border: \_border, enabledBorder: \_border, focusedBorder: \_border, filled: true, fillColor: CupertinoColors.white, hoverColor: CupertinoColors.white, contentPadding: EdgeInsets.fromLTRB(10, 0, 0, 0), ); return Platform.isIOS ? SizedBox( height: 36.0, child: TextField( decoration: iOSDecoration, ), ) : TextField(); } \`\`\` To learn more about adapting text fields, check out \[the GitHub discussion on text fields]\[text-field-post]. You can leave feedback or ask questions in the discussion. \[text-field-post]: https\://github.com/flutter/uxr/discussions/95 \[m3-text-field]: https\://m3.material.io/components/text-fields/overview \[hig-text-field]: https\://developer.apple.com/design/human-interface-guidelines/text-fields

----
url: https://docs.flutter.dev/deployment/flavors-windows.md
----

\# Set up Flutter flavors for Windows > How to create Flutter flavors for Windows desktop apps. This guide shows you how to create Flutter flavors for Windows desktop apps. :::note To create app flavors for other platforms, visit the pages on setting up flavors for: \* \[Android]\[] \* \[iOS and macOS]\[] \* \[Linux]\[] ::: ## Overview A Flutter flavor represents a collection of settings that define how a specific version of your app builds and runs. For example, a flavor can determine the window title, application icon, API endpoint, asset set, and logging configuration for a build. :::version-note Built-in support for flavors on Windows requires Flutter 3.47 or later. ::: On Windows, Flutter uses \[CMake]\[] to configure and build the native desktop runner. When you run \`flutter run\` or \`flutter build\` with the \`--flavor\` flag, Flutter writes the flavor name into \`windows/flutter/ephemeral/generated\_config.cmake\` as the \`FLUTTER\_APP\_FLAVOR\` variable, and isolates the build outputs in a flavor-specific directory. :::important The \`generated\_config.cmake\` file is included from \`windows/flutter/CMakeLists.txt\`, so \`FLUTTER\_APP\_FLAVOR\` only becomes visible in your top-level \`CMakeLists.txt\` \*\*after\*\* the \`add\_subdirectory(${FLUTTER\_MANAGED\_DIR})\` line. Any \`if(DEFINED FLUTTER\_APP\_FLAVOR)\` block placed before that line is silently skipped. ::: The following table illustrates the build directories that Flutter creates when a project defines two flavors (\`staging\`, \`production\`) and two build modes (\`debug\`, \`release\`): | Flavor | Build mode | Output directory | |------------|------------|---------------------------------------------------------| | staging | debug | \`build/windows//staging/runner/Debug/\` | | production | debug | \`build/windows//production/runner/Debug/\` | | staging | release | \`build/windows//staging/runner/Release/\` | | production | release | \`build/windows//production/runner/Release/\` | {:.table .table-striped} \[CMake]: https\://cmake.org/ ## Create and run flavors Passing \`--flavor\` works on Windows without any CMake configuration. Existing Flutter projects support flavors with no template updates or configuration changes required. When you pass \`--flavor\`, Flutter isolates the build directory, populates \`appFlavor\`, and filters flavor-specific assets automatically. To try flavors with a sample project, complete the following steps: 1. Create a new Flutter project called \`flavors\_example\`: \`\`\`console $ flutter create flavors\_example $ cd flavors\_example \`\`\` 1. Verify that your flavors run correctly: Run the \`staging\` flavor: \`\`\`console $ flutter run -d windows --flavor staging \`\`\` Run the \`production\` flavor: \`\`\`console $ flutter run -d windows --flavor production \`\`\` To customize native runner settings for each flavor—such as the window title or application icon—refer to \[Customize configurations]\(#customize-configurations). ## Launch a flavor After you define flavors for your app, run, build, or test a specific flavor using the \`--flavor\` flag with the Flutter CLI. ### Run a flavor in debug mode {: #run-a-flavor } To run a specific flavor during development, pass the \`--flavor\` option to \`flutter run\`: \`\`\`console $ flutter run -d windows --flavor \`\`\` Replace \`\` with the name of your flavor (for example, \`staging\` or \`production\`). ### Build a release binary {: #build-a-flavor } To build a release executable for a specific flavor, pass the \`--flavor\` option to \`flutter build\`: \`\`\`console $ flutter build windows --flavor \`\`\` Flutter outputs the compiled executable to \`build/windows///runner/Release/\`. ### Run tests with a flavor {: #test-a-flavor } The \`--flavor\` option also works with \`flutter test\` and \`flutter drive\`, allowing you to run unit, widget, or integration tests against a specific flavor configuration: \`\`\`console $ flutter test --flavor \`\`\` ## Use flavors in Flutter code {: #use-flavors-in-code } After adding flavors, you can adjust app behavior—such as selecting API endpoints, toggling features, or setting analytics keys—based on the active flavor. The Flutter framework provides the \`appFlavor\` constant in the \`services\` library, which identifies the flavor used to run or build your app. 1. \*\*Import the services library:\*\* Add the following import to your Dart file: \`\`\`dart import 'package:flutter/services.dart'; \`\`\` 1. \*\*Read the flavor value:\*\* Use the \`appFlavor\` constant in your application logic (often in \`main()\`) to handle flavor-specific configuration: \`\`\`dart void main() { if (appFlavor == 'production') { Config.apiUrl = 'https\://api.example.com'; } else if (appFlavor == 'staging') { Config.apiUrl = 'https\://staging.api.example.com'; } runApp(const MyApp()); } \`\`\` :::note The value of \`appFlavor\` matches the string passed to the \`--flavor\` flag. If you omit \`--flavor\`, \`appFlavor\` matches the \[default flavor]\(#set-default-flavor) configured in \`pubspec.yaml\`. If neither is specified, \`appFlavor\` is \`null\`. ::: ## Customize configurations To differentiate the native Windows runner per flavor—such as giving each flavor a unique window title or application icon—configure CMake to generate the runner source and resource files using \`configure\_file\`. ### Configure CMake for native settings {: #configure-cmake-native } CMake evaluates \`windows/runner/CMakeLists.txt\` after \`FLUTTER\_APP\_FLAVOR\` is defined. You can inspect this variable to configure flavor-specific values, then use \`configure\_file\` to substitute them into template files for the C++ runner (\`main.cpp\`) and the Windows resource script (\`Runner.rc\`). In \`windows/runner/CMakeLists.txt\`, add the following block before the \`add\_executable(${BINARY\_NAME} ...)\` line: \`\`\`cmake title="windows/runner/CMakeLists.txt" # Configure flavor-specific settings. if(DEFINED FLUTTER\_APP\_FLAVOR) if(FLUTTER\_APP\_FLAVOR STREQUAL "staging") set(RUNNER\_APP\_ICON "staging.ico") set(WINDOW\_TITLE "Staging App") elseif(FLUTTER\_APP\_FLAVOR STREQUAL "production") set(RUNNER\_APP\_ICON "production.ico") set(WINDOW\_TITLE "Production App") endif() else() set(RUNNER\_APP\_ICON "app\_icon.ico") set(WINDOW\_TITLE "${BINARY\_NAME}") endif() configure\_file( "${CMAKE\_CURRENT\_SOURCE\_DIR}/main.cpp.in" "${CMAKE\_CURRENT\_SOURCE\_DIR}/main.cpp" @ONLY ) configure\_file( "${CMAKE\_CURRENT\_SOURCE\_DIR}/Runner.rc.in" "${CMAKE\_CURRENT\_SOURCE\_DIR}/Runner.rc" @ONLY ) \`\`\` :::note Because \`configure\_file\` generates \`main.cpp\` and \`Runner.rc\` in the source directory at build time, add both generated files to your project's \`.gitignore\`: \`\`\`gitignore title=".gitignore" windows/runner/main.cpp windows/runner/Runner.rc \`\`\` Commit the \`.in\` template files to version control instead. ::: ### Create distinct window titles To display a flavor-specific window title when the app launches: 1. Rename \`windows/runner/main.cpp\` to \`windows/runner/main.cpp.in\`. 1. In \`windows/runner/main.cpp.in\`, find where \`window\.Create\` is called and replace the hardcoded title with \`@WINDOW\_TITLE@\`: \`\`\`cpp title="windows/runner/main.cpp.in" if (!window\.Create(L"@WINDOW\_TITLE@", origin, size)) { return EXIT\_FAILURE; } \`\`\` When CMake configures the build, it replaces \`@WINDOW\_TITLE@\` with the window title for the selected flavor. ### Create distinct app icons To provide unique desktop icons for each flavor: 1. Prepare your icon files in \`.ico\` format (for example, \`staging.ico\` and \`production.ico\`) and place them in \`windows/runner/resources/\` alongside the default \`app\_icon.ico\`. 1. Rename \`windows/runner/Runner.rc\` to \`windows/runner/Runner.rc.in\`. 1. In \`windows/runner/Runner.rc.in\`, locate the \`IDI\_APP\_ICON\` line and update the icon path to use \`@RUNNER\_APP\_ICON@\`: \`\`\`rc title="windows/runner/Runner.rc.in" IDI\_APP\_ICON ICON "resources\\\\@RUNNER\_APP\_ICON@" \`\`\` You can also update the metadata strings, such as \`FileDescription\` and \`ProductName\`, to use \`@WINDOW\_TITLE@\`: \`\`\`rc title="windows/runner/Runner.rc.in" VALUE "FileDescription", "@WINDOW\_TITLE@" "\0" VALUE "ProductName", "@WINDOW\_TITLE@" "\0" \`\`\` ### Bundle assets by flavor {: #bundle-assets } If you have assets that only apply to a specific flavor, configure Flutter to only bundle those assets when building that flavor. This prevents unused assets from increasing your application bundle size. To bundle assets conditionally, add the \`flavors\` list to an asset entry in \`pubspec.yaml\`: \`\`\`yaml title="pubspec.yaml" flutter: assets: - assets/common/ - path: assets/staging/ flavors: - staging - path: assets/production/ flavors: - production \`\`\` To learn more, consult the \[\`assets\` field]\[] in \[Flutter pubspec options]\[]. \[\`assets\` field]: /tools/pubspec#assets \[Flutter pubspec options]: /tools/pubspec ### Set a default flavor {: #set-default-flavor } To specify a flavor to use when running or building without the \`--flavor\` flag, add the \`default-flavor\` property to \`pubspec.yaml\`: \`\`\`yaml title="pubspec.yaml" flutter: default-flavor: staging \`\`\` To learn more, consult the \[\`default-flavor\` field]\[] in \[Flutter pubspec options]\[]. \[\`default-flavor\` field]: /tools/pubspec#default-flavor ## More information For more information on flavors and desktop deployment, consult the following resources: \* \[Set up Flutter flavors for Android]\[Android] \* \[Set up Flutter flavors for iOS and macOS]\[iOS and macOS] \* \[Set up Flutter flavors for Linux]\[Linux] \* \[Build and release a Windows desktop app]\[] \[Android]: /deployment/flavors \[iOS and macOS]: /deployment/flavors-ios \[Linux]: /deployment/flavors-linux \[Build and release a Windows desktop app]: /deployment/windows

----
url: https://docs.flutter.dev/learn/pathway/tutorial/layout.md
----

\# Layout > Learn about common layout widgets in Flutter. Learn how to build layouts with common widgets like Scaffold, AppBar, Column, and Row. title: What you'll accomplish items: - title: Structure an app with Scaffold and AppBar icon: web\_asset - title: Arrange widgets using Column and Row icon: view\_column - title: Generate widgets dynamically from data icon: repeat - title: Build a grid layout for the game board icon: grid\_view --- ### Introduction Given that Flutter is a UI toolkit, you'll spend a lot of time creating layouts with Flutter widgets. In this section, you'll learn how to build layouts with some of the most common layout widgets. This includes high-level widgets like \[\`Scaffold\`]\[] and \[\`AppBar\`]\[], which lay out the structure of a screen, as well as lower-level widgets like \[\`Column\`]\[] or \[\`Row\`]\[] that lay out widgets vertically or horizontally. \[\`Scaffold\`]: https\://pub.dev/documentation/material\_ui/latest/material\_ui/Scaffold-class.html \[\`AppBar\`]: https\://pub.dev/documentation/material\_ui/latest/material\_ui/AppBar-class.html \[\`Column\`]: https\://api.flutter.dev/flutter/widgets/Column-class.html \[\`Row\`]: https\://api.flutter.dev/flutter/widgets/Row-class.html ### \`Scaffold\` and \`AppBar\` Mobile applications often have a bar at the top called an "app bar" that can display a title, navigation controls, and/or actions. The simplest way to add an app bar to your app is by using two widgets: \`Scaffold\` and \`AppBar\`. \`Scaffold\` is a convenience widget that provides a Material-style page layout, making it simple to add an app bar, drawer, navigation bar, and more to a page of your app. \`AppBar\` is, of course, the app bar. Add an \`AppBar\` to the \`Scaffold\` widget within your \`MainApp\` widget's \`build\` method. The following code uses an additional layout widget: \[\`Align\`]\[]. This positions the title to the left, which would be centered by default. The \`Text\` widget contains the title itself. Passing an enum or static property directly (like \`Alignment.centerLeft\`) can also be shortened using \[Dart's dot shorthands]\[] syntax, which you can read more about on both the official Dart documentation and the \[Flutter shorthands overview]\[]. \[Dart's dot shorthands]: https\://dart.dev/language/dot-shorthands \[Flutter shorthands overview]: /ui/dot-shorthands \`\`\`dart class MainApp extends StatelessWidget { const MainApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( appBar: AppBar( title: const Align( alignment: Alignment.centerLeft, child: Text('Birdle'), ), ), body: const Center( child: Tile('A', HitType.hit), ), ), ); } } \`\`\` \[\`Align\`]: https\://api.flutter.dev/flutter/widgets/Align-class.html #### An updated widget tree Considering your app's widget tree gets more important as your app grows. At this point, there's a "branch" in the widget tree for the first time, and it now looks like the following figure: ### Create a widget for the game page layout Add the following code for a new widget, called \`GamePage\`, to your \`main.dart\` file. This widget will eventually display the UI elements needed for the game itself.

<!--?code-excerpt "fwe/birdle/lib/step3a_main.dart (GamePage)"?-->

\`\`\`dart title="lib/main.dart" class GamePage extends StatelessWidget { GamePage({super.key}); // This object is part of the game.dart file. // It manages wordle logic, and is outside the scope of this tutorial. final Game \_game = Game(); @override Widget build(BuildContext context) { // TODO: Replace with screen contents return Container(); } } \`\`\` Then update your \`MainApp\` widget to create and display a \`GamePage\` widget instead of the \`Tile\` widget.

<!--?code-excerpt "fwe/birdle/lib/step3_main.dart (MainApp)"?-->

\`\`\`dart highlightLines=14 class MainApp extends StatelessWidget { const MainApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( appBar: AppBar( title: const Align( alignment: Alignment.centerLeft, child: Text('Birdle'), ), ), body: Center(child: GamePage()), ), ); } } \`\`\` :::note Resolving \`const\` constructor errors Because \`GamePage\` initializes a non-constant field (\`final Game \_game = Game();\`), its constructor cannot be \`const\`. If you previously had const Center(...) or const MaterialApp(...), Dart reports an error: "The constructor being called isn't a const constructor." To resolve this, remove the const keyword from Center (or MaterialApp) (or the enclosing parent widget) and only use \`const\` on widgets with constant arguments, like \`const Align(...)\`. ::: ### Arrange widgets with \`Column\` and \`Row\` The \`GamePage\` layout contains the grid of tiles that display a user's guesses. There are a number of ways you can build this layout. The simplest is with the \`Column\` and \`Row\` widgets. Each row contains five tiles that represent the five letters in a guess, with five rows total. So you'll need a single \`Column\` with five \`Row\` widgets as children, where each row contains five children. To get started, replace the \`Container\` in \`GamePage.build\` with a \`Padding\` widget with a \`Column\` widget as its child:

<!--?code-excerpt "fwe/birdle/lib/step3b_main.dart (GamePage)"?-->

\`\`\`dart class GamePage extends StatelessWidget { GamePage({super.key}); // This manages game logic, and is out of scope for this lesson. final Game \_game = Game(); @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.all(8.0), child: Column( spacing: 5.0, children: \[ // Add children next. ], ), ); } } \`\`\` The \`spacing\` property puts five pixels between each element on the main axis. Within \`Column.children\`, for each element in the \`\_game.guesses\` list, add a \`Row\` widget as a child. :::note This \`guesses\` list is a \*\*fixed-size\*\* list, starting with five elements, one for each \*potential\* guess. The list will always contain exactly five elements, and therefore will always render five rows. :::

<!--?code-excerpt "fwe/birdle/lib/step3c_main.dart (GamePage)"?-->

\`\`\`dart class GamePage extends StatelessWidget { GamePage({super.key}); // This manages game logic, and is out of scope for this lesson. final Game \_game = Game(); @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.all(8.0), child: Column( spacing: 5.0, children: \[ for (final guess in \_game.guesses) Row( spacing: 5.0, children: \[ // We'll add the tiles here later. ], ), ], ), ); } } \`\`\` The \`for\` loop in the \`children\` list is called a \[collection for element]\[], a Dart syntax that allows you to iteratively add items to a collection when it is built at runtime. This syntactic sugar makes it easier for you to work with collections of widgets, providing a declarative alternative to the following: \`\`\`dart \[...\_game.guesses.map((guess) => Row(/\* ... \*/))], \`\`\` In this case, it adds five \`Row\` widgets to the column, one for each guess on the \`Game\` object. \[collection for element]: https\://dart.dev/language/collections#for-element #### An updated widget tree The widget tree for this app has expanded significantly in this lesson. Now, it looks more like the following (abridged) figure: :::note Challenge Add a \`Tile\` to each row for each letter allowed in the guess. Each element in \`guess\` is a \[record]\[] with the type \`({String char, HitType type})\`. Use a nested loop to iterate over the letters in each guess. \*\*Solution:\*\*

<!--?code-excerpt "fwe/birdle/lib/step3_main.dart (GamePage)"?-->

\`\`\`dart title="lib/main.dart" collapsed class GamePage extends StatelessWidget { GamePage({super.key}); // This manages game logic, and is out of scope for this lesson. final Game \_game = Game(); @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.all(8.0), child: Column( spacing: 5.0, children: \[ for (final guess in \_game.guesses) Row( spacing: 5.0, children: \[ for (final letter in guess) Tile(letter.char, letter.type), ], ), ], ), ); } } \`\`\` ::: When you reload your app, you should see a 5x5 grid of white squares. \[record]: https\://dart.dev/language/records ### Review title: What you accomplished subtitle: Here's a summary of what you built and learned in this lesson. completed: true items: - title: Structured your app with Scaffold and AppBar icon: web\_asset details: >- You used \`Scaffold\` to provide a Material-style page layout and \`AppBar\` to add a title bar at the top of your app. These high-level widgets give your app a standard, yet polished structure. - title: Arranged widgets using Column and Row icon: view\_column details: >- \`Column\` arranges widgets vertically and \`Row\` arranges them horizontally. These are fundamental layout widgets you'll use constantly in Flutter. The \`spacing\` property adds consistent gaps between children. - title: Generated widgets dynamically from data icon: repeat details: >- You used a collection for element to build widgets from a list. This declarative approach lets you build user interfaces that automatically and visually reflect your data, a pattern central to Flutter development. - title: Built the game board grid icon: grid\_view details: >- By nesting \`Row\` widgets inside a \`Column\` and using nested loops, you created a 5x5 grid of \`Tile\` widgets. Your app now displays the complete game board layout! ### Test yourself - question: What is the primary difference between a Column and a Row widget? options: - text: Column is for scrolling content; Row is for static content. correct: false explanation: Both Column and Row are for layout, not scrolling. Use ListView or SingleChildScrollView for scrolling. - text: Column arranges children vertically; Row arranges children horizontally. correct: true explanation: Column lays out its children along the vertical axis, while Row uses the horizontal axis. - text: Column can have unlimited children; Row is limited to two. correct: false explanation: Both widgets can have any number of children. - text: Column requires a Scaffold parent; Row does not. correct: false explanation: Neither widget requires a Scaffold as a parent. - question: What does the Scaffold widget provide in a Flutter app? options: - text: Only a background color for the page. correct: false explanation: Scaffold provides much more, including structure for app bars, drawers, and more. - text: A Material-style page layout with slots for app bar, body, drawer, and more. correct: true explanation: Scaffold is a convenience widget that provides a standard Material page structure. - text: A way to navigate between different pages. correct: false explanation: Navigation is handled by Navigator, not Scaffold. - text: Automatic state management for the page. correct: false explanation: Scaffold doesn't manage state; you use StatefulWidget or state management solutions for that.

----
url: https://docs.flutter.dev/ui/widgets/cupertino
----

Beautiful and high-fidelity widgets that align with Apple's Human Interface Guidelines for iOS and macOS.

[Cupertino​Theme​Data](https://pub.dev/documentation/cupertino_ui/latest/cupertino_ui/CupertinoThemeData-class.html)

[Styling specifications for a CupertinoTheme.](https://pub.dev/documentation/cupertino_ui/latest/cupertino_ui/CupertinoThemeData-class.html)


[Cupertino​Scrollbar](https://pub.dev/documentation/cupertino_ui/latest/cupertino_ui/CupertinoScrollbar-class.html)

[An iOS-style scrollbar that indicates which portion of a scrollable widget is currently visible.](https://pub.dev/documentation/cupertino_ui/latest/cupertino_ui/CupertinoScrollbar-class.html)

[Cupertino​Scroll​Behavior](https://pub.dev/documentation/cupertino_ui/latest/cupertino_ui/CupertinoScrollBehavior-class.html)

[Describes how Scrollable widgets behave for CupertinoApps.](https://pub.dev/documentation/cupertino_ui/latest/cupertino_ui/CupertinoScrollBehavior-class.html)

[Cupertino​Thumb​Painter](https://pub.dev/documentation/cupertino_ui/latest/cupertino_ui/CupertinoThumbPainter-class.html)

[Paints an iOS-style slider thumb or switch thumb.](https://pub.dev/documentation/cupertino_ui/latest/cupertino_ui/CupertinoThumbPainter-class.html)

[Cupertino​Timer​Picker](https://pub.dev/documentation/cupertino_ui/latest/cupertino_ui/CupertinoTimerPicker-class.html)

[An iOS-style countdown timer picker.](https://pub.dev/documentation/cupertino_ui/latest/cupertino_ui/CupertinoTimerPicker-class.html)

Find more widgets in the [widget catalog](/ui/widgets).

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-09-29. [Report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/ui/widgets/cupertino "Report an issue with this page").

----
url: https://docs.flutter.dev/cookbook/testing/widget/scrolling
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Handle scrolling](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

Many apps feature lists of content, from email clients to music apps and beyond. To verify that lists contain the expected content using widget tests, you need a way to scroll through lists to search for particular items.

To scroll through lists using the widget tests, use the methods provided by the [`WidgetTester`](https://api.flutter.dev/flutter/flutter_test/WidgetTester-class.html) class, which is included in the [`flutter_test`](https://api.flutter.dev/flutter/flutter_test/flutter_test-library.html) package:

In this recipe, learn how to scroll through a list of items to verify a specific widget is being displayed, and the pros and cons of different approaches.

This recipe uses the following steps:

1. Create an app with a list of items.
2. Write a test that scrolls through the list.
3. Run the test.

## 1. Create an app with a list of items

[#](#1-create-an-app-with-a-list-of-items)

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 [Work with long lists](/cookbook/lists/long-lists) recipe. If you're unsure of how to work with long lists, see that recipe for an introduction.

Add keys to the widgets you want to interact with inside the widget tests.

dart

```
import 'package:material_ui/material_ui.dart';
​
void main() {
  runApp(MyApp(items: List<String>.generate(10000, (i) => 'Item $i')));
}
​
class MyApp extends StatelessWidget {
  final List<String> items;
​
  const MyApp({super.key, required this.items});
​
  @override
  Widget build(BuildContext context) {
    const title = 'Long List';
​
    return MaterialApp(
      title: title,
      home: Scaffold(
        appBar: AppBar(title: const Text(title)),
        body: ListView.builder(
          // Add a key to the ListView. This makes it possible to
          // find the list and scroll through it in the tests.
          key: const 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 makes
                // it possible to look for a particular item in the list
                // and verify that the text is correct
                key: Key('item_${index}_text'),
              ),
            );
          },
        ),
      ),
    );
  }
}
```

## 2. Write a test that scrolls through the list

[#](#2-write-a-test-that-scrolls-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 [`WidgetTester`](https://api.flutter.dev/flutter/flutter_test/WidgetTester-class.html) class provides the [`scrollUntilVisible()`](https://api.flutter.dev/flutter/flutter_test/WidgetController/scrollUntilVisible.html) method, which scrolls through a list until a specific widget is visible. This is useful because the height of the items in the list can change depending on the device.

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, the `scrollUntilVisible()` method repeatedly scrolls through a list of items until it finds what it's 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/widget_test.dart`.

dart

```
​
// This is a basic Flutter widget test.
//
// To perform an interaction with a widget in your test, use the WidgetTester
// utility that Flutter provides. For example, you can send tap and scroll
// gestures. You can also use WidgetTester to find child widgets in the widget
// tree, read text, and verify that the values of widget properties are correct.
​
import 'package:flutter_test/flutter_test.dart';
import 'package:material_ui/material_ui.dart';
import 'package:scrolling/main.dart';
​
void main() {
  testWidgets('finds a deep item in a long list', (tester) async {
    // Build our app and trigger a frame.
    await tester.pumpWidget(
      MyApp(items: List<String>.generate(10000, (i) => 'Item $i')),
    );
​
    final listFinder = find.byType(Scrollable);
    final itemFinder = find.byKey(const ValueKey('item_50_text'));
​
    // Scroll until the item to be found appears.
    await tester.scrollUntilVisible(itemFinder, 500.0, scrollable: listFinder);
​
    // Verify that the item contains the correct text.
    expect(itemFinder, findsOneWidget);
  });
}
```

## 3. Run the test

[#](#3-run-the-test)

Run the test using the following command from the root of the project:

```
flutter test test/widget_test.dart
```

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-09-29. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/testing/widget/scrolling.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/cookbook/testing/widget/scrolling\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/testing/widget/scrolling.md "Report an issue with this page").

----
url: https://docs.flutter.dev/cookbook/animation/page-route-animation
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Animate a page route transition](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

A design language, such as Material, defines standard behaviors when transitioning between routes (or screens). Sometimes, though, a custom transition between screens can make an app more unique. To help, [`PageRouteBuilder`](https://api.flutter.dev/flutter/widgets/PageRouteBuilder-class.html) provides an [`Animation`](https://api.flutter.dev/flutter/animation/Animation-class.html) object. This `Animation` can be used with [`Tween`](https://api.flutter.dev/flutter/animation/Tween-class.html) and [`Curve`](https://api.flutter.dev/flutter/animation/Curve-class.html) objects to customize the transition animation. This recipe shows how to transition between routes by animating the new route into view from the bottom of the screen.

To create a custom page route transition, this recipe uses the following steps:

1. Set up a `PageRouteBuilder`.
2. Create a `Tween`.
3. Add an `AnimatedWidget`.
4. Use a `CurveTween`.
5. Combine the two `Tween`s.

## 1. Set up a `PageRouteBuilder`

[#](#1-set-up-a-pageroutebuilder)

To start, use a [`PageRouteBuilder`](https://api.flutter.dev/flutter/widgets/PageRouteBuilder-class.html) to create a [`Route`](https://api.flutter.dev/flutter/widgets/Route-class.html). `PageRouteBuilder` has two callbacks, one to build the content of the route (`pageBuilder`), and one to build the route's transition (`transitionsBuilder`).

The following example creates two routes: a home route with a "Go!" button, and a second route titled "Page 2".

dart

```
import 'package:flutter/material.dart';
​
void main() {
  runApp(const MaterialApp(home: Page1()));
}
​
class Page1 extends StatelessWidget {
  const Page1({super.key});
​
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(),
      body: Center(
        child: ElevatedButton(
          onPressed: () {
            Navigator.of(context).push(_createRoute());
          },
          child: const Text('Go!'),
        ),
      ),
    );
  }
}
​
Route<void> _createRoute() {
  return PageRouteBuilder(
    pageBuilder: (context, animation, secondaryAnimation) => const Page2(),
    transitionsBuilder: (context, animation, secondaryAnimation, child) {
      return child;
    },
  );
}
​
class Page2 extends StatelessWidget {
  const Page2({super.key});
​
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(),
      body: const Center(child: Text('Page 2')),
    );
  }
}
```

## 2. Create a `Tween`

[#](#2-create-a-tween)

To make the new page animate in from the bottom, it should animate from `Offset(0.0, 1.0)` to `Offset(0.0, 0.0)` (usually defined using the `Offset.zero` constructor). In this case, the `Offset` is a 2D vector for the [`FractionalTranslation`](https://api.flutter.dev/flutter/widgets/FractionalTranslation-class.html) widget. Setting the `dy` argument to 1 represents a vertical translation one full height of the page.

The `transitionsBuilder` callback has an `animation` parameter. It's an `Animation<double>` that produces values between 0 and 1. Convert the `Animation<double>` into an `Animation<Offset>` using a `Tween`:

dart

```
transitionsBuilder: (context, animation, secondaryAnimation, child) {
  const begin = Offset(0.0, 1.0);
  const end = Offset.zero;
  final tween = Tween(begin: begin, end: end);
  final offsetAnimation = animation.drive(tween);
  return child;
},
```

## 3. Use an `AnimatedWidget`

[#](#3-use-an-animatedwidget)

Flutter has a set of widgets extending [`AnimatedWidget`](https://api.flutter.dev/flutter/widgets/AnimatedWidget-class.html) that rebuild themselves when the value of the animation changes. For instance, `SlideTransition` takes an `Animation<Offset>` and translates its child (using a `FractionalTranslation` widget) whenever the value of the animation changes.

Return a [`SlideTransition`](https://api.flutter.dev/flutter/widgets/SlideTransition-class.html) with the `Animation<Offset>` and the child widget:

dart

```
transitionsBuilder: (context, animation, secondaryAnimation, child) {
  const begin = Offset(0.0, 1.0);
  const end = Offset.zero;
  final tween = Tween(begin: begin, end: end);
  final offsetAnimation = animation.drive(tween);
​
  return SlideTransition(position: offsetAnimation, child: child);
},
```

## 4. Use a `CurveTween`

[#](#4-use-a-curvetween)

Flutter provides a selection of easing curves that adjust the rate of the animation over time. The [`Curves`](https://api.flutter.dev/flutter/animation/Curves-class.html) class provides a predefined set of commonly used curves. For example, `Curves.easeOut` makes the animation start quickly and end slowly.

To use a `Curve`, create a new [`CurveTween`](https://api.flutter.dev/flutter/animation/CurveTween-class.html) and pass it a `Curve`:

dart

```
const curve = Curves.ease;
final curveTween = CurveTween(curve: curve);
```

This new `Tween` still produces values from 0 to 1. In the next step, combine it with the `Tween<Offset>` from step 2.

## 5. Combine the two `Tween`s

[#](#5-combine-the-two-tweens)

To combine the tweens, use [`chain()`](https://api.flutter.dev/flutter/animation/Animatable/chain.html):

dart

```
const begin = Offset(0.0, 1.0);
const end = Offset.zero;
const curve = Curves.ease;
​
final tween = Tween(
  begin: begin,
  end: end,
).chain(CurveTween(curve: curve));
```

Then use this tween by passing it to `animation.drive()`. This creates a new `Animation<Offset>` that can be given to the `SlideTransition` widget:

dart

```
return SlideTransition(position: animation.drive(tween), child: child);
```

This new `Tween` (or `Animatable`) produces `Offset` values by first evaluating the `CurveTween`, then evaluating the `Tween<Offset>`. When the animation runs, the values are computed in this order:

1. The animation (provided to the `transitionsBuilder` callback) produces values from 0 to 1.
2. The `CurveTween` maps those values to new values between 0 and 1 based on its curve.
3. The `Tween<Offset>` maps the `double` values to `Offset` values.

Another way to create an `Animation<Offset>` with an easing curve is to use a [`CurvedAnimation`](https://api.flutter.dev/flutter/animation/CurvedAnimation-class.html):

dart

```
transitionsBuilder: (context, animation, secondaryAnimation, child) {
  const begin = Offset(0.0, 1.0);
  const end = Offset.zero;
  const curve = Curves.ease;
​
  final tween = Tween(begin: begin, end: end);
  final curvedAnimation = CurvedAnimation(parent: animation, curve: curve);
​
  return SlideTransition(
    position: tween.animate(curvedAnimation),
    child: child,
  );
}
```

## Interactive example

[#](#interactive-example)

```
import 'package:flutter/material.dart';

void main() {
  runApp(const MaterialApp(home: Page1()));
}

class Page1 extends StatelessWidget {
  const Page1({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(),
      body: Center(
        child: ElevatedButton(
          onPressed: () {
            Navigator.of(context).push(_createRoute());
          },
          child: const Text('Go!'),
        ),
      ),
    );
  }
}

Route<void> _createRoute() {
  return PageRouteBuilder(
    pageBuilder: (context, animation, secondaryAnimation) => const Page2(),
    transitionsBuilder: (context, animation, secondaryAnimation, child) {
      const begin = Offset(0.0, 1.0);
      const end = Offset.zero;
      const curve = Curves.ease;

      final tween = Tween(
        begin: begin,
        end: end,
      ).chain(CurveTween(curve: curve));

      return SlideTransition(position: animation.drive(tween), child: child);
    },
  );
}

class Page2 extends StatelessWidget {
  const Page2({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(),
      body: const Center(child: Text('Page 2')),
    );
  }
}
```

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-10-07. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/animation/page-route-animation.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/cookbook/animation/page-route-animation\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/animation/page-route-animation.md "Report an issue with this page").

----
url: https://docs.flutter.dev/cookbook/images/fading-in-images
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Fade in images with a placeholder](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

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 to display a placeholder at first, and images would fade in as they're loaded? Use the [`FadeInImage`](https://api.flutter.dev/flutter/widgets/FadeInImage-class.html) widget for exactly this purpose.

`FadeInImage` works with images of any type: in-memory, local assets, or images from the internet.

## In-memory

[#](#in-memory)

To use a single-pixel, transparent PNG as the placeholder image, complete the following steps:

1. **Create the transparent image data.**

   Import `dart:convert` and `dart:typed_data`, then decode the image from a Base64-encoded string:

   dart

   ```
   import 'dart:convert';
   import 'dart:typed_data';
   final Uint8List transparentImage = base64Decode(
     'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4'
     'nGMAAQAABQABDQottAAAAABJRU5ErkJggg==',
   );
   ```

2. **Use the image data as a placeholder.**

   Pass the decoded image data to the `placeholder` parameter:

   dart

   ```
   FadeInImage.memoryNetwork(
     placeholder: transparentImage,
     image: 'https://picsum.photos/250?image=9',
   ),
   ```

### Complete example

[#](#complete-example)

dart

```
import 'dart:convert';
import 'dart:typed_data';
​
import 'package:material_ui/material_ui.dart';
​
final Uint8List transparentImage = base64Decode(
  'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4'
  'nGMAAQAABQABDQottAAAAABJRU5ErkJggg==',
);
​
void main() {
  runApp(const MyApp());
}
​
class MyApp extends StatelessWidget {
  const MyApp({super.key});
​
  @override
  Widget build(BuildContext context) {
    const title = 'Fade in images';
​
    return MaterialApp(
      title: title,
      home: Scaffold(
        appBar: AppBar(title: const Text(title)),
        body: Stack(
          children: <Widget>[
            const Center(child: CircularProgressIndicator()),
            Center(
              child: FadeInImage.memoryNetwork(
                placeholder: transparentImage,
                image: 'https://picsum.photos/250?image=9',
              ),
            ),
          ],
        ),
      ),
    );
  }
}
```

## From asset bundle

[#](#from-asset-bundle)

To use a local image asset as the placeholder image, complete the following steps:

1. **Add the placeholder to the asset bundle.**

   Add a placeholder image to the project's `assets` directory, such as `assets/loading.gif`. Then, declare the asset in the project's `pubspec.yaml` file. For more details, see [Adding assets and images](/ui/assets/assets-and-images).

   yaml

   ```
   flutter:
     assets:
       - assets/loading.gif
   ```

2. **Use the asset as a placeholder.**

   Pass the asset path to the `placeholder` parameter of the [`FadeInImage.assetNetwork`](https://api.flutter.dev/flutter/widgets/FadeInImage/FadeInImage.assetNetwork.html) constructor:

   dart

   ```
   FadeInImage.assetNetwork(
     placeholder: 'assets/loading.gif',
     image: 'https://picsum.photos/250?image=9',
   ),
   ```

### Complete example

[#](#complete-example-1)

dart

```
import 'package:material_ui/material_ui.dart';
​
void main() {
  runApp(const MyApp());
}
​
class MyApp extends StatelessWidget {
  const MyApp({super.key});
​
  @override
  Widget build(BuildContext context) {
    const title = 'Fade in images';
​
    return MaterialApp(
      title: title,
      home: Scaffold(
        appBar: AppBar(title: const Text(title)),
        body: Center(
          child: FadeInImage.assetNetwork(
            placeholder: 'assets/loading.gif',
            image: 'https://picsum.photos/250?image=9',
          ),
        ),
      ),
    );
  }
}
```

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-09-29. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/images/fading-in-images.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/cookbook/images/fading-in-images\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/images/fading-in-images.md "Report an issue with this page").

----
url: https://docs.flutter.dev/app-architecture
----

Architecture is an important part of building a maintainable, resilient, and scalable Flutter app. In this guide, you'll learn app architecture principles and best practices for building Flutter apps.

'Architecture' is a word that's hard to define. It's a broad term and can refer to any number of topics depending on the context. In this guide, 'architecture' refers to how to structure, organize, and design your Flutter app in order to scale as your project requirements and team grow.

## What you'll learn

[#](#what-youll-learn)

* Benefits of intentional architecture
* Common architectural principles
* The Flutter team's recommended app architecture
* MVVM and state management
* Dependency injection
* Common design patterns for writing robust Flutter applications

## Benefits of intentional architecture

[#](#benefits-of-intentional-architecture)

Good app architecture provides a number of benefits to engineering teams and their end users.

* Maintainability - App architecture makes it easier to modify, update, and fix issues over time.
* Scalability - A well thought out application allows more people to contribute to the same codebase concurrently, with minimal code conflicts.
* Testability - Applications with intentional architecture generally have simpler classes with well-defined inputs and outputs, which makes them easier to mock and test.
* Lower cognitive load - Developers who are new to the project will be more productive in a shorter amount of time, and code reviews are generally less time-consuming when code is easier to understand.
* A better user experience - Features can ship faster and with fewer bugs.

## How to use this guide

[#](#how-to-use-this-guide)

This is a guide for building scalable Flutter applications and was written for teams that have multiple developers contributing to the same code base, who're building a feature-rich application. If you're writing a Flutter app that has a *growing team and codebase*, this guidance is for you.

Along with general architectural advice, this guide gives concrete examples of best practices and includes specific recommendations. Some libraries can be swapped out, and very large teams with unique complexity might find that some parts don't apply. In either case, the ideas remain sound. This is the recommended way to build a Flutter app.

In the first part of this guide, you'll learn about common architectural principles from a high level. In the second part, the guide walks through specific and concrete recommendations of architecting Flutter apps. Finally, at the end of the guide, you'll find a list of design patterns and sample code that shows the recommendations in action.

## Feedback

[#](#feedback)

As this section of the website is evolving, we [welcome your feedback](https://google.qualtrics.com/jfe/form/SV_4T0XuR9Ts29acw6?page=%22index%22)!

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/app-architecture/index.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/app-architecture\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/app-architecture/index.md "Report an issue with this page").

----
url: https://docs.flutter.dev/testing/testing-plugins.md
----

\# Testing plugins > Learn how to test your plugin package. All of the \[usual types of Flutter tests]\[] apply to plugin packages as well, but because plugins contain native code they often also require other kinds of tests to test all of their functionality. \[usual types of Flutter tests]: /testing/overview :::note To learn how to test your plugin code, read on. To learn how to avoid crashes from a plugin when testing your Flutter app, check out \[Plugins in Flutter tests]\[]. ::: \[Plugins in Flutter tests]: /testing/plugins-in-tests ## Types of plugin tests To see examples of each of these types of tests, you can \[create a new plugin from the plugin template]\[plugin-tests] and look in the indicated directories. \* \*\*Dart \[unit tests]\[] and \[widget tests]\[]\*\*. These tests allow you to test the Dart portion of your plugin just as you would test the Dart code of a non-plugin package. However, the plugin's native code \[won't be loaded]\[], so any calls to platform channels need to be \[mocked in tests]\[]. See the \`test\` directory for an example. \* \*\*Dart \[integration tests]\[]\*\*. Since integration tests run in the context of a Flutter application (the example app), they can test both the Dart and native code, as well as the interaction between them. They are also useful for unit testing web implementation code that needs to run in a browser. These are often the most important tests for a plugin. However, Dart integration tests using \`integration\_test\` can't interact with native UI, such as native dialogs or the contents of platform views. For native component interaction support, consider using \[\`patrol\`]\[] See the \`example/integration\_test\` directory for an example. \* \*\*Native unit tests.\*\* Just as Dart unit tests can test the Dart portions of a plugin in isolation, native unit tests can test the native parts in isolation. Each platform has its own native unit test system, and the tests are written in the same native languages as the code it is testing. Native unit tests can be especially valuable if you need to mock out APIs wrapped by your plugin code, which isn't possible in a Dart integration test. You can set up and use any native test frameworks you are familiar with for each platform, but the following are already configured in the plugin template: \* \*\*Android\*\*: \[JUnit]\[] tests can be found in \`android/src/test/\`. \* \*\*iOS\*\* and \*\*macOS\*\*: \[XCTest]\[] tests can be found in \`example/ios/RunnerTests/\` and \`example/macos/RunnerTests/\` respectively. These are in the example directory, not the top-level package directory, because they are run via the example app's project. \* \*\*Linux\*\* and \*\*Windows\*\*: \[GoogleTest]\[] tests can be found in \`linux/test/\` and \`windows/test/\`, respectively. Other types of tests, which aren't currently pre-configured in the template, are \*\*native UI tests\*\*. Running your application under a native UI testing framework, such as \[Espresso]\[] or \[XCUITest]\[], enables tests that interact with both native and Flutter UI elements, so can be useful if your plugin can't be tested without native UI interactions. \[Espresso]: https\://github.com/flutter/packages/tree/main/packages/espresso \[GoogleTest]: https\://github.com/google/googletest \[integration tests]: /cookbook/testing/integration/introduction \[JUnit]: https\://github.com/junit-team/junit4/wiki/Getting-started \[mocked in tests]: /testing/plugins-in-tests#mock-the-platform-channel \[\`patrol\`]: https\://pub.dev/packages/patrol \[plugin-tests]: /packages-and-plugins/developing-packages#step-1-create-the-package-1 \[unit tests]: /cookbook/testing/unit/introduction \[widget tests]: /cookbook/testing/widget/introduction \[won't be loaded]: /testing/plugins-in-tests \[XCTest]: https\://developer.apple.com/documentation/xctest \[XCUITest]: https\://developer.apple.com/library/archive/documentation/DeveloperTools/Conceptual/testing\_with\_xcode/chapters/09-ui\_testing.html ## Running tests ### Dart unit tests These can be run like any other Flutter unit tests, either from your preferred Flutter IDE, or using \`flutter test\`. ### Integration tests For information on running this type of test, check out the \[integration test documentation]\[]. The commands must be run in the \`example\` directory. \[integration test documentation]: /cookbook/testing/integration/introduction ### Native unit tests For all platforms, you need to build the example application at least once before running the unit tests, to ensure that all of the platform-specific build files have been created. \*\*Android JUnit\*\*\
If you have the example opened as an Android project in Android Studio, you can run the unit tests using the \[Android Studio test UI]\[]. To run the tests from the command line, use the following command in the \`example/android\` directory: \`\`\`sh ./gradlew testDebugUnitTest \`\`\` \*\*iOS and macOS XCTest\*\*\
If you have the example app opened in Xcode, you can run the unit tests using the \[Xcode Test UI]\[]. To run the tests from the command line, use the following command in the \`example/ios\` (for iOS) or \`example/macos\` (for macOS) directory: \`\`\`sh xcodebuild test -workspace Runner.xcworkspace -scheme Runner -configuration Debug \`\`\` For iOS tests, you might need to first open \`Runner.xcworkspace\` in Xcode to configure code signing. \*\*Linux GoogleTest\*\*\
To run the tests from the command line, use the following command in the example directory, replacing "my\_plugin" with your plugin project name: \`\`\`sh build/linux/plugins/x64/debug/my\_plugin/my\_plugin\_test \`\`\` If you built the example app in release mode rather than debug, replace "debug" with "release". \*\*Windows GoogleTest\*\*\
If you have the example app opened in Visual Studio, you can run the unit tests using the \[Visual Studio test UI]\[]. To run the tests from the command line, use the following command in the example directory, replacing "my\_plugin" with your plugin project name: \`\`\`sh build/windows/plugins/my\_plugin/Debug/my\_plugin\_test.exe \`\`\` If you built the example app in release mode rather than debug, replace "Debug" with "Release". ## What types of tests to add The \[general advice for testing Flutter projects]\[general advice] applies to plugins as well. Some extra considerations for plugin testing: \* Since only integration tests can test the communication between Dart and the native languages, try to have at least one integration test of each platform channel call. \* If some flows can't be tested using the \`integration\_test\` package (for example if they require interacting with native UI or mocking device state), consider using the \[\`patrol\`]\[] package for native UI interactions, or writing "end to end" tests of the two halves using unit tests: \* Native unit tests that set up the necessary mocks, then call into the method channel entry point with a synthesized call and validate the method response. \* Dart unit tests that mock the platform channel, then call the plugin's public API and validate the results. \[Android Studio test UI]: https\://developer.android.com/studio/test/test-in-android-studio \[general advice]: /testing/overview \[Visual Studio test UI]: https\://learn.microsoft.com/en-us/visualstudio/test/getting-started-with-unit-testing?view=vs-2022\&tabs=dotnet%2Cmstest#run-unit-tests \[Xcode Test UI]: https\://developer.apple.com/library/archive/documentation/DeveloperTools/Conceptual/testing\_with\_xcode/chapters/05-running\_tests.html

----
url: https://docs.flutter.dev/tools/devtools/extensions
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top DevTools extensions](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

## What are DevTools extensions?

[#](#what-are-devtools-extensions)

[DevTools extensions](https://pub.dev/packages/devtools_extensions) are developer tools provided by third-party packages that are tightly integrated into the DevTools tooling suite. Extensions are distributed as part of a pub package, and they are dynamically loaded into DevTools when a user is debugging their app.

## Use a DevTools extension

[#](#use-a-devtools-extension)

If your app depends on a package that provides a DevTools extension, the extension automatically shows up in a new tab when you open DevTools.

### Configure extension enablement states

[#](#configure-extension-enablement-states)

You need to manually enable the extension before it loads for the first time. Make sure the extension is provided by a source you trust before enabling it.

When you open the extension for the first time, you'll see a prompt to enable the extension:

You can modify the setting at any time from the DevTools Extensions dialog:

Extension enablement states are stored in a `devtools_options.yaml` file in the root of the user's project (similar to `analysis_options.yaml`).

yaml

```
description: This file stores settings for Dart & Flutter DevTools.
documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states
extensions:
  - provider: true
  - shared_preferences: true
  - foo: false
```

This file stores per-project (or optionally, per user) settings for DevTools.

If this file is **checked into source control**, the specified options are configured for the project. This means that anyone who pulls a project's source code and works on the project uses the same settings.

If this file is **omitted from source control**, for example by adding `devtools_options.yaml` as an entry in the `.gitignore` file, then the specified options are configured separately for each user. Since each user or contributor to the project uses a local copy of the `devtools_options.yaml` file in this case, the specified options might differ between project contributors.

## Build a DevTools extension

[#](#build-a-devtools-extension)

For an in-depth guide on how to build a DevTools extension, visit [Build custom tooling in Flutter and Dart DevTools](/tools/devtools/custom-tool)

You might also check out the following video:

[Watch on YouTube in a new tab: "Building DevTools extensions | Flutter Build Show"](https://www.youtube.com/watch/gOrSc4s4RWY)

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/extensions.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/tools/devtools/extensions\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/extensions.md "Report an issue with this page").

----
url: https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.46.0
----

The 2.46.0 release of the Dart and Flutter DevTools includes the following changes among other general improvements. To learn more about DevTools, check out the [DevTools overview](/tools/devtools/overview).

## General updates

[#](#general-updates)

* Fixed a bug which caused web apps to remain paused after triggering a hot-restart from DevTools. - [#9125](https://github.com/flutter/devtools/pull/9125)
* Landed a change to dismiss stale banner messages when the connected app state changes. - [#9148](https://github.com/flutter/devtools/pull/9148)
* Fixed a focus traversal issue with search fields. [#9166](https://github.com/flutter/devtools/pull/9166)

## Performance updates

[#](#performance-updates)

* Fixed a bug where the Performance page would hang when connected to a paused Flutter app. - [#9162](https://github.com/flutter/devtools/pull/9162)

## Full commit history

[#](#full-commit-history)

To find a complete list of changes in this release, check out the [DevTools git log](https://github.com/flutter/devtools/tree/v2.46.0).

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/release-notes/release-notes-2.46.0.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.46.0\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/release-notes/release-notes-2.46.0.md "Report an issue with this page").

----
url: https://docs.flutter.dev/ui/navigation/url-strategies.md
----

\# Configuring the URL strategy on the web > Use hash or path URL strategies on the web Flutter web apps support two ways of configuring URL-based navigation on the web: \*\*Hash (default)\*\* : Paths are read and written to the \[hash fragment]\[]. For example, \`flutterexample.dev/#/path/to/screen\`. \*\*Path\*\* : Paths are read and written without a hash. For example, \`flutterexample.dev/path/to/screen\`. ## Configuring the URL strategy To configure Flutter to use the path instead, use the \[usePathUrlStrategy]\[] function provided by the \[flutter\_web\_plugins]\[] library, which is part of the Flutter SDK. You can't directly add \`flutter\_web\_plugins\` using \`pub add\`. Include it as a Flutter \[SDK dependency]\[] in your \`pubspec.yaml\` file: \`\`\`yaml highlightLines=4-5 dependencies: flutter: sdk: flutter flutter\_web\_plugins: sdk: flutter \`\`\` Then call the \`usePathUrlStrategy\` function before \`runApp\`: \`\`\`dart highlightLines=4 import 'package:flutter\_web\_plugins/url\_strategy.dart'; void main() { usePathUrlStrategy(); runApp(ExampleApp()); } \`\`\` \[SDK dependency]: https\://dart.dev/tools/pub/dependencies#sdk ## Configuring your web server PathUrlStrategy uses the \[History API]\[], which requires additional configuration for web servers. To configure your web server to support PathUrlStrategy, check your web server's documentation to rewrite requests to \`index.html\`. Check your web server's documentation for details on how to configure single-page apps. If you are using Firebase Hosting, choose the "Configure as a single-page app" option when initializing your project. For more information see Firebase's \[Configure rewrites]\[] documentation. The local dev server created by running \`flutter run -d chrome\` is configured to handle any path gracefully and fallback to your app's \`index.html\` file. ## Hosting a Flutter app at a non-root location Update the \`\` tag in \`web/index.html\` to the path where your app is hosted. For example, to host your Flutter app at \`my\_app.dev/flutter\_app\`, change this tag to \`\`. Relative \`base href\` tags are supported for release builds but they must take into account the full URL where the page was served from. This means a relative \`base href\` for a request to \`/flutter\_app/\`, \`/flutter\_app/nested/route\`, and \`/flutter\_app/nested/route/\` will be different (for example \`"."\`, \`".."\`, and \`"../.."\` respectively). \[hash fragment]: https\://en.wikipedia.org/wiki/Uniform\_Resource\_Locator#Syntax \[\`HashUrlStrategy\`]: https\://api.flutter.dev/flutter/flutter\_web\_plugins/HashUrlStrategy-class.html \[\`PathUrlStrategy\`]: https\://api.flutter.dev/flutter/flutter\_web\_plugins/PathUrlStrategy-class.html \[\`setUrlStrategy\`]: https\://api.flutter.dev/flutter/flutter\_web\_plugins/setUrlStrategy.html \[usePathUrlStrategy]: https\://api.flutter.dev/flutter/flutter\_web\_plugins/usePathUrlStrategy.html \[flutter\_web\_plugins]: https\://api.flutter.dev/flutter/flutter\_web\_plugins/flutter\_web\_plugins-library.html \[History API]: https\://developer.mozilla.org/en-US/docs/Web/API/History\_API \[Configure rewrites]: https\://firebase.google.com/docs/hosting/full-config#rewrites

----
url: https://docs.flutter.dev/tools/devtools/inspector.md
----

\# Use the Flutter inspector > Learn how to use the Flutter inspector to explore a Flutter app's widget tree.

<!--?code-excerpt path-base="visual_debugging/"?-->

:::note The inspector works with all Flutter applications. ::: For information on how to locate DevTools screens in different IDEs, check out the \[DevTools overview]\(/tools/devtools). ## What is it? The Flutter widget inspector is a powerful tool for visualizing and exploring Flutter widget trees. The Flutter framework uses widgets as the core building block for anything from controls (such as text, buttons, and toggles), to layout (such as centering, padding, rows, and columns). The inspector helps you visualize and explore Flutter widget trees, and can be used for the following: \* understanding existing layouts \* diagnosing layout issues !\[Screenshot of the Flutter inspector window]\(/assets/images/docs/tools/devtools/inspector\_screenshot.png){:width="100%"} ## The Flutter inspector {:#new} The Flutter inspector is enabled by default. It can be configured from the \[inspector settings dialog]\[]. \[inspector settings dialog]: #inspector-settings \[filing a bug]: https\://github.com/flutter/devtools/issues/new ### Debugging layout issues visually The following is a guide to the features available in the inspector's toolbar. When space is limited, the icon is used as the visual version of the label. !\[Select widget mode button]\(/assets/images/docs/tools/devtools/select-widget-mode-button.png) \*\*Select widget mode\*\* : Enable this button in order to select a widget on the device to inspect it. To learn more, check out \[Inspecting a widget]\(#inspecting-a-widget). !\[Show implementation widgets button]\(/assets/images/docs/tools/devtools/show-implementation-widgets-button.png) \*\*Show implementation widgets\*\* : Enable this button in to show implementation widgets in the widget tree. To learn more, check out \[Use the Widget Tree]\(#use-the-widget-tree). !\[Refresh tree icon]\(/assets/images/docs/tools/devtools/refresh-tree-icon.png){:.theme-icon width="20px"} \*\*Refresh tree\*\* : Reload the current widget info. !\[Slow animations icon]\(/assets/images/docs/tools/devtools/slow-animations-icon.png){:.theme-icon width="20px"} \*\*\[Slow animations]\[]\*\* : Run animations 5 times slower to help fine-tune them. !\[Show guidelines mode icon]\(/assets/images/docs/tools/devtools/debug-paint-mode-icon.png){:.theme-icon width="20px"} \*\*\[Show guidelines]\[]\*\* : Overlay guidelines to assist with fixing layout issues. !\[Show baselines icon]\(/assets/images/docs/tools/devtools/paint-baselines-icon.png){:.theme-icon width="20px"} \*\*\[Show baselines]\[]\*\* : Show baselines, which are used for aligning text. Can be useful for checking if text is aligned. !\[Highlight repaints icon]\(/assets/images/docs/tools/devtools/repaint-rainbow-icon.png){:.theme-icon width="20px"} \*\*\[Highlight repaints]\[]\*\* : Show borders that change color when elements repaint. Useful for finding unnecessary repaints. !\[Highlight oversized images icon]\(/assets/images/docs/tools/devtools/invert\_oversized\_images\_icon.png){:.theme-icon width="20px"} \*\*\[Highlight oversized images]\[]\*\* : Highlights images that are using too much memory by inverting colors and flipping them. \[Slow animations]: #slow-animations \[Show guidelines]: #show-guidelines \[Show baselines]: #show-baselines \[Highlight repaints]: #highlight-repaints \[Highlight oversized images]: #highlight-oversized-images ## Inspecting a widget You can browse the interactive widget tree to view nearby widgets and see their field values. To locate individual UI elements in the widget tree, click the \*\*Select Widget Mode\*\* button in the toolbar. This puts the app on the device into a "widget select" mode. Click any widget in the app's UI; this selects the widget on the app's screen, and scrolls the widget tree to the corresponding node. Toggle the \*\*Select Widget Mode\*\* button again to exit widget select mode. When debugging layout issues, the key fields to look at are the \`size\` and \`constraints\` fields. The constraints flow down the tree, and the sizes flow back up. For more information on how this works, see \[Understanding constraints]\[]. ## Flutter Widget Tree The Flutter Widget Tree allows you to visualize, understand and navigate your app's Widget tree. !\[Image of Flutter inspector with Widget Tree highlighted]\(/assets/images/docs/tools/devtools/inspector-widget-tree.png){:width="100%"} ### Use the Widget Tree #### Viewing widgets created in your project By default, the Flutter Widget Tree includes all the widgets created in your root project's directory. The parent-children relationships of the widgets are represented by a single vertical line (if the parent widget only has a single child) or through indentation (if the parent widget has multiple children.) For example, for the following section of a widget tree: !\[Image of widget tree section]\(/assets/images/docs/tools/devtools/widget-tree.png){:width="100%"} \* \`Padding\` has a single child \`Row\` \* \`Row\` has three children: \`Icon\`, \`SizedBox\`, and \`Flexible\` \* \`Flexible\` has a single child \`Column\` \* \`Column\` has four children: \`Text\`, \`Text\`, \`SizedBox\`, and \`Divider\` #### Viewing all widgets To instead view all the widgets in your widget tree, including those that were created outside of your project, toggle on "Show implementation widgets". The implementation widgets are shown in a lighter font than the widgets created in your project, thereby visually distinguishing them. They are also hidden behind collapsible groups which can be expanded via the inline expand buttons. For example, here is the same section of a widget tree as above with implementation widgets shown: !\[Image of widget tree section showing implementation widgets]\(/assets/images/docs/tools/devtools/widget-tree-with-implementation-widgets.png){:width="100%"} \* \`Icon\` has five implementation widgets collapsed beneath it \* Both \`Text\` widgets have \`RichText\` implementation widget children \* \`Divider\` has nine implementation widgets collapsed beneath it ## Flutter Widget Explorer The Flutter Widget Explorer helps you to better understand the inspected widget. !\[Image of Flutter inspector with Widget Explorer highlighted]\(/assets/images/docs/tools/devtools/inspector-widget-explorer.png){:width="100%"} ### Use the Widget Explorer From the Flutter inspector, select a widget. The Widget Explorer will be shown on the right side of the window. Depending on the selected widget, the Widget Explorer will include one or more of the following: \* Widget properties tab \* Flex explorer tab \* Render object tab #### Widget properties tab !\[Image of widget properties tab]\(/assets/images/docs/tools/devtools/widget-properties-tab.png){:width="100%"} The properties tab shows you mini-view of your widget layout, including width, height, and padding, along with a list of properties on that widget. These properties include whether or not the value matches the default value for the property argument. #### Render object tab !\[Image of render object tab]\(/assets/images/docs/tools/devtools/render-object-tab.png){:width="100%"} The render object tab displays all the properties set on the render object of the selected Flutter widget. #### Flex explorer tab !\[Image of flex explorer tab]\(/assets/images/docs/tools/devtools/flex-explorer-tab.png){:width="100%"} When you select a flex widget (for example, \[\`Row\`]\[], \[\`Column\`]\[], \[\`Flex\`]\[]) or a direct child of a flex widget, the flex explorer tool will appear in the Widget Explorer. The flex explorer tool visualizes how \[\`Flex\`]\[] widgets and their children are laid out. The explorer identifies the main axis and cross axis, as well as the current alignment for each (for example, start, end, and spaceBetween). It also shows details like flex factor, flex fit, and layout constraints. Additionally, the explorer shows layout constraint violations and render overflow errors. Violated layout constraints are colored red, and overflow errors are presented in the standard "yellow-tape" pattern, as you might see on a running device. These visualizations aim to improve understanding of why overflow errors occur as well as how to fix them. !\[The flex explorer showing errors and device inspector]\(/assets/images/docs/tools/devtools/layout\_explorer\_errors\_and\_device.webp){:width="100%"} Clicking a widget in the flex explorer mirrors the selection on the on-device inspector. \*\*Select Widget Mode\*\* needs to be enabled for this. To enable it, click on the \*\*Select Widget Mode\*\* button in the inspector. !\[The Select Widget Mode button in the inspector]\(/assets/images/docs/tools/devtools/select-widget-mode-button.png) For some properties, like flex factor, flex fit, and alignment, you can modify the value via dropdown lists in the explorer. When modifying a widget property, you see the new value reflected not only in the flex explorer, but also on the device running your Flutter app. The explorer animates on property changes so that the effect of the change is clear. Widget property changes made from the layout explorer don't modify your source code and are reverted on hot reload. ##### Interactive Properties The flex explorer supports modifying \[\`mainAxisAlignment\`]\[], \[\`crossAxisAlignment\`]\[], and \[\`FlexParentData.flex\`]\[]. In the future, we may add support for additional properties such as \[\`mainAxisSize\`]\[], \[\`textDirection\`]\[], and \[\`FlexParentData.fit\`]\[]. ###### mainAxisAlignment !\[The flex explorer changing main axis alignment]\(/assets/images/docs/tools/devtools/layout\_explorer\_main\_axis\_alignment.webp){:width="100%"} Supported values: - \`MainAxisAlignment.start\` - \`MainAxisAlignment.end\` - \`MainAxisAlignment.center\` - \`MainAxisAlignment.spaceBetween\` - \`MainAxisAlignment.spaceAround\` - \`MainAxisAlignment.spaceEvenly\` ###### crossAxisAlignment !\[The flex explorer changing cross axis alignment]\(/assets/images/docs/tools/devtools/layout\_explorer\_cross\_axis\_alignment.webp){:width="100%"} Supported values: - \`CrossAxisAlignment.start\` - \`CrossAxisAlignment.center\` - \`CrossAxisAlignment.end\` - \`CrossAxisAlignment.stretch\` ###### FlexParentData.flex !\[The flex explorer changing flex factor]\(/assets/images/docs/tools/devtools/layout\_explorer\_flex.webp){:width="100%"} The flex explorer supports 7 flex options in the UI (null, 0, 1, 2, 3, 4, 5), but technically the flex factor of a flex widget's child can be any int. ###### Flexible.fit !\[The flex explorer changing fit]\(/assets/images/docs/tools/devtools/layout\_explorer\_fit.webp){:width="100%"} The flex explorer supports the two different types of \[\`FlexFit\`]\[]: \`loose\` and \`tight\`. ## Visual debugging The Flutter Inspector provides several options for visually debugging your app. !\[Inspector visual debugging options]\(/assets/images/docs/tools/devtools/visual\_debugging\_options.png){:width="100%"} ### Slow animations When enabled, this option runs animations 5 times slower for easier visual inspection. This can be useful if you want to carefully observe and tweak an animation that doesn't look quite right. This can also be set in code:

<!--?code-excerpt "lib/slow_animations.dart"?-->

\`\`\`dart import 'package:flutter/scheduler.dart'; void setSlowAnimations() { timeDilation = 5.0; } \`\`\` This slows the animations by 5x. #### See also The following links provide more info. \* \[Flutter documentation: timeDilation property]\(https\://api.flutter.dev/flutter/scheduler/timeDilation.html) The following screen recordings show before and after slowing an animation. !\[Screen recording showing normal animation speed]\(/assets/images/docs/tools/devtools/debug-toggle-slow-animations-disabled.webp) !\[Screen recording showing slowed animation speed]\(/assets/images/docs/tools/devtools/debug-toggle-slow-animations-enabled.webp) ### Show guidelines This feature draws guidelines over your app that display render boxes, alignments, paddings, scroll views, clippings and spacers. This tool can be used for better understanding your layout. For instance, by finding unwanted padding or understanding widget alignment. You can also enable this in code:

<!--?code-excerpt "lib/layout_guidelines.dart"?-->

\`\`\`dart import 'package:flutter/rendering.dart'; void showLayoutGuidelines() { debugPaintSizeEnabled = true; } \`\`\` #### Render boxes Widgets that draw to the screen create a \[render box]\[], the building blocks of Flutter layouts. They're shown with a bright blue border: !\[Screenshot of render box guidelines]\(/assets/images/docs/tools/devtools/debug-toggle-guideline-render-box.png) #### Alignments Alignments are shown with yellow arrows. These arrows show the vertical and horizontal offsets of a widget relative to its parent. For example, this button's icon is shown as being centered by the four arrows: !\[Screenshot of alignment guidelines]\(/assets/images/docs/tools/devtools/debug-toggle-guidelines-alignment.png) #### Padding Padding is shown with a semi-transparent blue background: !\[Screenshot of padding guidelines]\(/assets/images/docs/tools/devtools/debug-toggle-guidelines-padding.png) #### Scroll views Widgets with scrolling contents (such as list views) are shown with green arrows: !\[Screenshot of scroll view guidelines]\(/assets/images/docs/tools/devtools/debug-toggle-guidelines-scroll.png) #### Clipping Clipping, for example when using the \[ClipRect widget]\[], are shown with a dashed pink line with a scissors icon: \[ClipRect widget]: https\://api.flutter.dev/flutter/widgets/ClipRect-class.html !\[Screenshot of clip guidelines]\(/assets/images/docs/tools/devtools/debug-toggle-guidelines-clip.png) #### Spacers Spacer widgets are shown with a grey background, such as this \`SizedBox\` without a child: !\[Screenshot of spacer guidelines]\(/assets/images/docs/tools/devtools/debug-toggle-guidelines-spacer.png) ### Show baselines This option makes all baselines visible. Baselines are horizontal lines used to position text. This can be useful for checking whether text is precisely aligned vertically. For example, the text baselines in the following screenshot are slightly misaligned: !\[Screenshot with show baselines enabled]\(/assets/images/docs/tools/devtools/debug-toggle-guidelines-baseline.png) The \[Baseline]\[] widget can be used to adjust baselines. \[Baseline]: https\://api.flutter.dev/flutter/widgets/Baseline-class.html A line is drawn on any \[render box]\[] that has a baseline set; alphabetic baselines are shown as green and ideographic as yellow. You can also enable this in code:

<!--?code-excerpt "lib/show_baselines.dart"?-->

\`\`\`dart import 'package:flutter/rendering.dart'; void showBaselines() { debugPaintBaselinesEnabled = true; } \`\`\` ### Highlight repaints This option draws a border around all \[render boxes]\[] that changes color every time that box repaints. \[render boxes]: https\://api.flutter.dev/flutter/rendering/RenderBox-class.html This rotating rainbow of colors is useful for finding parts of your app that are repainting too often and potentially harming performance. For example, one small animation could be causing an entire page to repaint on every frame. Wrapping the animation in a \[RepaintBoundary widget]\[] limits the repainting to just the animation. \[RepaintBoundary widget]: https\://api.flutter.dev/flutter/widgets/RepaintBoundary-class.html Here the progress indicator causes its container to repaint:

<!--?code-excerpt "lib/highlight_repaints.dart (everything-repaints)"?-->

\`\`\`dart class EverythingRepaintsPage extends StatelessWidget { const EverythingRepaintsPage({super.key}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('Repaint Example')), body: const Center(child: CircularProgressIndicator()), ); } } \`\`\` !\[Screen recording of a whole screen repainting]\(/assets/images/docs/tools/devtools/debug-toggle-guidelines-repaint-1.webp) Wrapping the progress indicator in a \`RepaintBoundary\` causes only that section of the screen to repaint:

<!--?code-excerpt "lib/highlight_repaints.dart (area-repaints)"?-->

\`\`\`dart class AreaRepaintsPage extends StatelessWidget { const AreaRepaintsPage({super.key}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('Repaint Example')), body: const Center( child: RepaintBoundary(child: CircularProgressIndicator()), ), ); } } \`\`\` !\[Screen recording of a just a progress indicator repainting]\(/assets/images/docs/tools/devtools/debug-toggle-guidelines-repaint-2.webp) \`RepaintBoundary\` widgets have tradeoffs. They can help with performance, but they also have an overhead of creating a new canvas, which uses additional memory. You can also enable this option in code:

<!--?code-excerpt "lib/highlight_repaints.dart (toggle)"?-->

\`\`\`dart import 'package:flutter/rendering.dart'; import 'package:material\_ui/material\_ui.dart'; void highlightRepaints() { debugRepaintRainbowEnabled = true; } \`\`\` ### Highlight oversized images This option highlights images that are too large by both inverting their colors and flipping them vertically: !\[A highlighted oversized image]\(/assets/images/docs/tools/devtools/debug-toggle-guidelines-oversized.png) The highlighted images use more memory than is required; for example, a large 5MB image displayed at 100 by 100 pixels. Such images can cause poor performance, especially on lower-end devices and when you have many images, as in a list view, this performance hit can add up. Information about each image is printed in the debug console: \`\`\`console dash.png has a display size of 213×392 but a decode size of 2130×392, which uses an additional 2542KB. \`\`\` Images are deemed too large if they use at least 128KB more than required. #### Fixing images Wherever possible, the best way to fix this problem is resizing the image asset file so it's smaller. If this isn't possible, you can use the \`cacheHeight\` and \`cacheWidth\` parameters on the \`Image\` constructor:

<!--?code-excerpt "lib/oversized_images.dart (resized-image)"?-->

\`\`\`dart class ResizedImage extends StatelessWidget { const ResizedImage({super.key}); @override Widget build(BuildContext context) { return Image.asset('dash.png', cacheHeight: 213, cacheWidth: 392); } } \`\`\` This makes the engine decode this image at the specified size, and reduces memory usage (decoding and storage is still more expensive than if the image asset itself was shrunk). The image is rendered to the constraints of the layout or width and height regardless of these parameters. This property can also be set in code:

<!--?code-excerpt "lib/oversized_images.dart (toggle)"?-->

\`\`\`dart void showOversizedImages() { debugInvertOversizedImages = true; } \`\`\` #### More information You can learn more at the following link: - \[Flutter documentation: debugInvertOversizedImages]\(https\://api.flutter.dev/flutter/rendering/debugInvertOversizedImages.html) \[render box]: https\://api.flutter.dev/flutter/rendering/RenderBox-class.html ## Track widget creation Part of the functionality of the Flutter inspector is based on instrumenting the application code in order to better understand the source locations where widgets are created. The source instrumentation allows the Flutter inspector to present the widget tree in a manner similar to how the UI was defined in your source code. Without it, the tree of nodes in the widget tree are much deeper, and it can be more difficult to understand how the runtime widget hierarchy corresponds to your application's UI. You can disable this feature by passing \`--no-track-widget-creation\` to the \`flutter run\` command. Here are examples of what your widget tree might look like with and without track widget creation enabled. Track widget creation enabled (default): !\[The widget tree with track widget creation enabled]\(/assets/images/docs/tools/devtools/track\_widget\_creation\_enabled.png){:width="100%"} Track widget creation disabled (not recommended): !\[The widget tree with track widget creation disabled]\(/assets/images/docs/tools/devtools/track\_widget\_creation\_disabled.png){:width="100%"} This feature prevents otherwise-identical \`const\` Widgets from being considered equal in debug builds. For more details, see the discussion on \[common problems when debugging]\[]. ## Inspector settings !\[The Flutter Inspector Settings dialog]\(/assets/images/docs/tools/devtools/flutter-inspector-settings.png){:width="100%"} ### Enable hover inspection Hovering over any widget displays its properties and values. Toggling this value enables or disables the hover inspection functionality. ### Enable widget tree auto-refreshing When enabled, the widget tree automatically refreshes after a hot-reload or a navigation event. ### Package directories By default, DevTools limits the widgets displayed in the widget tree to those created in the project's root directory. To see all widgets, including those created outside of a project's root directory, toggle on \[Show implementation widgets]\[] In order to include other widgets in the default widget tree, a parent directory of theirs must be added to the Package Directories. For example, consider the following directory structure: \`\`\`plaintext project\_foo pkgs project\_foo\_app widgets\_A widgets\_B \`\`\` Running your app from \`project\_foo\_app\` displays only widgets from \`project\_foo/pkgs/project\_foo\_app\` in the widget inspector tree. To show widgets from \`widgets\_A\` in the widget tree, add \`project\_foo/pkgs/widgets\_A\` to the package directories. To display \_all\_ widgets from your project root in the widget tree, add \`project\_foo\` to the package directories. Changes to your package directories persist the next time the widget inspector is opened for the app. \[Show implementation widgets]: #debugging-layout-issues-visually ## Other resources For a demonstration of what's generally possible with the inspector, see the \[DartConf 2018 talk]\[] demonstrating the IntelliJ version of the Flutter inspector. To learn how to visually debug layout issues using DevTools, check out a guided \[Flutter Inspector tutorial]\[inspector-tutorial]. \[\`Column\`]: https\://api.flutter.dev/flutter/widgets/Column-class.html \[common problems when debugging]: /testing/debugging \[\`crossAxisAlignment\`]: https\://api.flutter.dev/flutter/widgets/Flex/crossAxisAlignment.html \[DartConf 2018 talk]: https\://www\.youtube.com/watch?v=JIcmJNT9DNI \[debug mode]: /testing/build-modes#debug \[\`Flex\`]: https\://api.flutter.dev/flutter/widgets/Flex-class.html \[flex layouts]: https\://api.flutter.dev/flutter/widgets/Flex-class.html \[\`FlexFit\`]: https\://api.flutter.dev/flutter/rendering/FlexFit.html \[\`FlexParentData.fit\`]: https\://api.flutter.dev/flutter/rendering/FlexParentData/fit.html \[\`FlexParentData.flex\`]: https\://api.flutter.dev/flutter/rendering/FlexParentData/flex.html \[\`mainAxisAlignment\`]: https\://api.flutter.dev/flutter/widgets/Flex/mainAxisAlignment.html \[\`mainAxisSize\`]: https\://api.flutter.dev/flutter/widgets/Flex/mainAxisSize.html \[\`Row\`]: https\://api.flutter.dev/flutter/widgets/Row-class.html \[\`textDirection\`]: https\://api.flutter.dev/flutter/widgets/Flex/textDirection.html \[Understanding constraints]: /ui/layout/constraints \[inspector-tutorial]: https\://medium.com/@fluttergems/mastering-dart-flutter-devtools-flutter-inspector-part-2-of-8-bbff40692fc7

----
url: https://docs.flutter.dev/cookbook/networking/background-parsing.md
----

\# Parse JSON in the background > How to perform a task in the background.

<!--?code-excerpt path-base="cookbook/networking/background_parsing/"?-->

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 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, using a separate \[Isolate]\[]. This recipe uses the following steps: 1. Add the \`http\` package. 2. Make a network request using the \`http\` package. 3. Convert the response into a list of photos. 4. Move this work to a separate isolate. ## 1. Add the \`http\` package First, add the \[\`http\`]\[] package to your project. The \`http\` package makes it easier to perform network requests, such as fetching data from a JSON endpoint. To add the \`http\` package as a dependency, run \`flutter pub add\`: \`\`\`console $ flutter pub add http \`\`\` ## 2. Make a network request This example covers how to fetch a large JSON document that contains a list of 5000 photo objects from the \[JSONPlaceholder REST API]\[], using the \[\`http.get()\`]\[] method.

<!--?code-excerpt "lib/main_step2.dart (fetchPhotos)"?-->

\`\`\`dart Future fetchPhotos(http.Client client) async { return client.get(Uri.parse('https\://jsonplaceholder.typicode.com/photos')); } \`\`\` :::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. ::: ## 3. Parse and convert the JSON into a list of photos Next, following the guidance from the \[Fetch data from the internet]\[] recipe, convert the \`http.Response\` into a list of Dart objects. This makes the data easier to work with. ### Create a \`Photo\` class First, create a \`Photo\` class that contains data about a photo. Include a \`fromJson()\` factory method to make it easy to create a \`Photo\` starting with a JSON object.

<!--?code-excerpt "lib/main_step3.dart (Photo)"?-->

\`\`\`dart class Photo { final int albumId; final int id; final String title; final String url; final String thumbnailUrl; const Photo({ required this.albumId, required this.id, required this.title, required this.url, required this.thumbnailUrl, }); factory Photo.fromJson(Map json) { return Photo( albumId: json\['albumId'] as int, id: json\['id'] as int, title: json\['title'] as String, url: json\['url'] as String, thumbnailUrl: json\['thumbnailUrl'] as String, ); } } \`\`\` ### Convert the response into a list of photos Now, use the following instructions to update the \`fetchPhotos()\` function so that it returns a \`Future>\`: 1. Create a \`parsePhotos()\` function that converts the response body into a \`List\`. 2. Use the \`parsePhotos()\` function in the \`fetchPhotos()\` function.

<!--?code-excerpt "lib/main_step3.dart (parsePhotos)"?-->

\`\`\`dart // A function that converts a response body into a List. List parsePhotos(String responseBody) { final parsed = (jsonDecode(responseBody) as List) .cast>(); return parsed.map(Photo.fromJson).toList(); } Future> fetchPhotos(http.Client client) async { final response = await client.get( Uri.parse('https\://jsonplaceholder.typicode.com/photos'), ); // Synchronously run parsePhotos in the main isolate. return parsePhotos(response.body); } \`\`\` ## 4. Move this work to a separate isolate 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 get rid of it. You can remove the jank by moving the parsing and conversion to a background isolate using the \[\`compute()\`]\[] function provided by Flutter. The \`compute()\` function runs expensive functions in a background isolate and returns the result. In this case, run the \`parsePhotos()\` function in the background.

<!--?code-excerpt "lib/main.dart (fetchPhotos)"?-->

\`\`\`dart Future> fetchPhotos(http.Client client) async { final response = await client.get( Uri.parse('https\://jsonplaceholder.typicode.com/photos'), ); // Use the compute function to run parsePhotos in a separate isolate. return compute(parsePhotos, response.body); } \`\`\` ## Notes on working with isolates 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 might experience errors if you try to pass more complex objects, such as a \`Future\` or \`http.Response\` between isolates. As an alternate solution, check out the \[\`worker\_manager\`]\[] or \[\`workmanager\`]\[] packages for background processing. \[\`worker\_manager\`]: https\://pub.dev/packages/worker\_manager \[\`workmanager\`]: https\://pub.dev/packages/workmanager ## Complete example

<!--?code-excerpt "lib/main.dart"?-->

\`\`\`dart import 'dart:async'; import 'dart:convert'; import 'package:flutter/foundation.dart'; import 'package:http/http.dart' as http; import 'package:material\_ui/material\_ui.dart'; Future> fetchPhotos(http.Client client) async { final response = await client.get( Uri.parse('https\://jsonplaceholder.typicode.com/photos'), ); // 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. List parsePhotos(String responseBody) { final parsed = (jsonDecode(responseBody) as List) .cast>(); return parsed.map(Photo.fromJson).toList(); } class Photo { final int albumId; final int id; final String title; final String url; final String thumbnailUrl; const Photo({ required this.albumId, required this.id, required this.title, required this.url, required this.thumbnailUrl, }); factory Photo.fromJson(Map json) { return Photo( albumId: json\['albumId'] as int, id: json\['id'] as int, title: json\['title'] as String, url: json\['url'] as String, thumbnailUrl: json\['thumbnailUrl'] as String, ); } } void main() => runApp(const MyApp()); class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { const appTitle = 'Isolate Demo'; return const MaterialApp( title: appTitle, home: MyHomePage(title: appTitle), ); } } class MyHomePage extends StatefulWidget { const MyHomePage({super.key, required this.title}); final String title; @override State createState() => \_MyHomePageState(); } class \_MyHomePageState extends State { late Future> futurePhotos; @override void initState() { super.initState(); futurePhotos = fetchPhotos(http.Client()); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text(widget.title)), body: FutureBuilder>( future: futurePhotos, builder: (context, snapshot) { if (snapshot.hasError) { return const Center(child: Text('An error has occurred!')); } else if (snapshot.hasData) { return PhotosList(photos: snapshot.data!); } else { return const Center(child: CircularProgressIndicator()); } }, ), ); } } class PhotosList extends StatelessWidget { const PhotosList({super.key, required this.photos}); final List photos; @override Widget build(BuildContext context) { return GridView\.builder( gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 2, ), itemCount: photos.length, itemBuilder: (context, index) { return Image.network(photos\[index].thumbnailUrl); }, ); } } \`\`\` !\[Isolate demo]\(/assets/images/docs/cookbook/isolate.webp){:.site-mobile-screenshot} \[\`compute()\`]: https\://api.flutter.dev/flutter/foundation/compute.html \[Fetch data from the internet]: /cookbook/networking/fetch-data \[\`http\`]: https\://pub.dev/packages/http \[\`http.get()\`]: https\://pub.dev/documentation/http/latest/http/get.html \[Isolate]: https\://api.flutter.dev/flutter/dart-isolate/Isolate-class.html \[JSONPlaceholder REST API]: https\://jsonplaceholder.typicode.com

----
url: https://docs.flutter.dev/cookbook/lists/mixed-list.md
----

\# Create lists with different types of items > How to implement a list that contains different types of assets.

<!--?code-excerpt path-base="cookbook/lists/mixed_list/"?-->

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. Here's how you can create such a structure with Flutter: 1. Create a data source with different types of items. 2. Convert the data source into a list of widgets. ## 1. Create a data source with different types of items ### Types of items To represent different types of items in a list, define a class for each type of item. In this example, create an app that shows a header followed by five messages. Therefore, create three classes: \`ListItem\`, \`HeadingItem\`, and \`MessageItem\`.

<!--?code-excerpt "lib/main.dart (ListItem)"?-->

\`\`\`dart /// The base class for the different types of items the list can contain. abstract class ListItem { /// The title line to show in a list item. Widget buildTitle(BuildContext context); /// The subtitle line, if any, to show in a list item. Widget buildSubtitle(BuildContext context); } /// A ListItem that contains data to display a heading. class HeadingItem implements ListItem { final String heading; HeadingItem(this.heading); @override Widget buildTitle(BuildContext context) { return Text(heading, style: Theme.of(context).textTheme.headlineSmall); } @override Widget buildSubtitle(BuildContext context) => const SizedBox.shrink(); } /// A ListItem that contains data to display a message. class MessageItem implements ListItem { final String sender; final String body; MessageItem(this.sender, this.body); @override Widget buildTitle(BuildContext context) => Text(sender); @override Widget buildSubtitle(BuildContext context) => Text(body); } \`\`\` ### Create 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, generate a list of items to work with. The list contains a header followed by five messages. Each message has one of 3 types: \`ListItem\`, \`HeadingItem\`, or \`MessageItem\`.

<!--?code-excerpt "lib/main.dart (Items)" replace="/^items:/final items =/g;/^\),$/);/g"?-->

\`\`\`dart final items = List.generate( 1000, (i) => i % 6 == 0 ? HeadingItem('Heading $i') : MessageItem('Sender $i', 'Message body $i'), ); \`\`\` ## 2. Convert the data source into a list of widgets To convert each item into a widget, use the \[\`ListView\.builder()\`]\[] constructor. 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.

<!--?code-excerpt "lib/main.dart (builder)" replace="/^body: //g;/^\),$/)/g"?-->

\`\`\`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. // Convert each item into a widget based on the type of item it is. itemBuilder: (context, index) { final item = items\[index]; return ListTile( title: item.buildTitle(context), subtitle: item.buildSubtitle(context), ); }, ) \`\`\` ## Interactive example

<!--?code-excerpt "lib/main.dart"?-->

\`\`\`dartpad title="Flutter create mixed lists hands-on example in DartPad" run="true" import 'package:flutter/material.dart'; void main() { runApp( MyApp( items: List.generate( 1000, (i) => i % 6 == 0 ? HeadingItem('Heading $i') : MessageItem('Sender $i', 'Message body $i'), ), ), ); } class MyApp extends StatelessWidget { final List items; const MyApp({super.key, required this.items}); @override Widget build(BuildContext context) { const title = 'Mixed List'; return MaterialApp( title: title, home: Scaffold( appBar: AppBar(title: const Text(title)), body: 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. // Convert each item into a widget based on the type of item it is. itemBuilder: (context, index) { final item = items\[index]; return ListTile( title: item.buildTitle(context), subtitle: item.buildSubtitle(context), ); }, ), ), ); } } /// The base class for the different types of items the list can contain. abstract class ListItem { /// The title line to show in a list item. Widget buildTitle(BuildContext context); /// The subtitle line, if any, to show in a list item. Widget buildSubtitle(BuildContext context); } /// A ListItem that contains data to display a heading. class HeadingItem implements ListItem { final String heading; HeadingItem(this.heading); @override Widget buildTitle(BuildContext context) { return Text(heading, style: Theme.of(context).textTheme.headlineSmall); } @override Widget buildSubtitle(BuildContext context) => const SizedBox.shrink(); } /// A ListItem that contains data to display a message. class MessageItem implements ListItem { final String sender; final String body; MessageItem(this.sender, this.body); @override Widget buildTitle(BuildContext context) => Text(sender); @override Widget buildSubtitle(BuildContext context) => Text(body); } \`\`\` \[\`ListView\.builder()\`]: https\://api.flutter.dev/flutter/widgets/ListView/ListView\.builder.html

----
url: https://docs.flutter.dev/app-architecture/case-study
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Architecture case study](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

The code examples in this guide are from the [Compass sample application](https://github.com/flutter/samples/tree/main/compass_app), an app that helps users build and book itineraries for trips. It's a robust sample application with many features, routes, and screens. The app communicates with an HTTP server, has development and production environments, includes brand-specific styling, and contains high test coverage. In these ways and more, it simulates a real-world, feature-rich Flutter application.

The Compass app's architecture most resembles the [MVVM architectural pattern](https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93viewmodel) as described in Flutter's [app architecture guidelines](/app-architecture/guide). This architecture case study demonstrates how to implement those guidelines by walking through the "Home" feature of the compass app. If you aren't familiar with MVVM, you should read those guidelines first.

The Home screen of the Compass app displays user account information and a list of the user's saved trips. From this screen you can log out, open detailed trip pages, delete saved trips, and navigate to the first page of the core app flow, which allows the user to build a new itinerary.

In this case study, you'll learn the following:

* How to implement Flutter's [app architecture guidelines](/app-architecture/guide) using repositories and services in the [data layer](/app-architecture/case-study/data-layer) and the MVVM architectural pattern in the [UI layer](/app-architecture/case-study/ui-layer)
* How to use the [Command pattern](/app-architecture/case-study/ui-layer#command-objects) to safely render UI as data changes
* How to use [`ChangeNotifier`](https://api.flutter.dev/flutter/foundation/ChangeNotifier-class.html) and [`Listenable`](https://api.flutter.dev/flutter/foundation/Listenable-class.html) objects to manage state
* How to implement [Dependency Injection](/app-architecture/case-study/dependency-injection) using `package:provider`
* How to [set up tests](/app-architecture/case-study/testing) when following the recommended architecture
* Effective [package structure](/app-architecture/case-study#package-structure) for large Flutter apps

This case-study was written to be read in order. Any given page might reference the previous pages.

The code examples in this case-study include all the details needed to understand the architecture, but they're not complete, runnable snippets. If you prefer to follow along with the full app, you can find it on [GitHub](https://github.com/flutter/samples/tree/main/compass_app).

## Package structure

[#](#package-structure)

Well-organized code is easier for multiple engineers to work on with minimal code conflicts and is easier for new engineers to navigate and understand. Code organization both benefits and benefits from well-defined architecture.

There are two popular means of organizing code:

1. By feature - The classes needed for each feature are grouped together. For example, you might have an `auth` directory, which would contain files like `auth_viewmodel.dart`, `login_usecase.dart`, `logout_usecase.dart`, `login_screen.dart`, `logout_button.dart`, etc.
2. By type - Each "type" of architecture is grouped together. For example, you might have directories such as `repositories`, `models`, `services`, and `viewmodels`.

The architecture recommended in this guide lends itself to a combination of the two. Data layer objects (repositories and services) aren't tied to a single feature, while UI layer objects (views and view models) are. The following is how the code is organized within the Compass application.

* lib/

  * ui/

    * core/

      * ui/

        * \<shared\_widgets>

      * themes/

    * \<feature\_name>/

      * view\_models/

        * \<view\_model\_class>.dart

      * widgets/

        * \<feature\_name>\_screen.dart
        * \<other\_widgets>

  * domain/

    * models/

      * \<model\_name>.dart

  * data/

    * repositories/

      * \<repository\_class>.dart

    * services/

      * \<service\_class>.dart

    * model/

      * \<api\_model\_class>.dart

  * config/

  * utils/

  * routing/

  * main\_staging.dart

  * main\_development.dart

  * main.dart

* test/// Contains unit and widget tests.

  * data/
  * domain/
  * ui/
  * utils/

* testing/// Contains mocks that other classes need to execute tests.

  * fakes/
  * models/

Most of the application code lives in the `data`, `domain`, and `ui` folders. The data folder organizes code by type, because repositories and services can be used across different features and by multiple view models. The ui folder organizes the code by feature, because each feature has exactly one view and exactly one view model.

Other notable features of this folder structure:

* The UI folder also contains a subdirectory named "core". Core contains widgets and theme logic that is shared by multiple views, such as buttons with your brand styling.
* The domain folder contains the application data types, because they're used by the data and ui layers.
* The app contains three "main" files, which act as different entry points to the application for development, staging, and production.
* There are two test-related directories at the same level as `lib`: `test/` has the test code, and its own structure matches `lib/`. `testing/` is a subpackage that contains mocks and other testing utilities which can be used in other packages' test code. The `testing/` folder could be described as a version of your app that you don't ship. It's the content that is tested.

There's additional code in the compass app that doesn't pertain to architecture. For the full package structure, [view it on GitHub](https://github.com/flutter/samples/tree/main/compass_app).

## Other architecture options

[#](#other-architecture-options)

The example in this case-study demonstrates how one application abides by our recommended architectural rules, but there are many other example apps that could've been written. The UI of this app leans heavily on view models and `ChangeNotifier`, but it could've easily been written with streams, or with other libraries such as [`riverpod`](https://pub.dev/packages/riverpod), [`flutter_bloc`](https://pub.dev/packages/flutter_bloc), and [`signals`](https://pub.dev/packages/signals). The communication between layers of this app handled everything with method calls, including polling for new data. It could've instead used streams to expose data from a repository to a view model and still abide by the rules covered in this guide.

Even if you do follow this guide exactly, and choose not to introduce additional libraries, you have decisions to make: Will you have a domain layer? If so, how will you manage data access? The answer depends so much on an individual team's needs that there isn't a single right answer. Regardless of how you answer these questions, the principles in this guide will help you write scalable Flutter apps.

And if you squint, aren't all architectures MVVM anyway?

## Feedback

[#](#feedback)

As this section of the website is evolving, we [welcome your feedback](https://google.qualtrics.com/jfe/form/SV_4T0XuR9Ts29acw6?page=%22case-study/index%22)!

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/app-architecture/case-study/index.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/app-architecture/case-study\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/app-architecture/case-study/index.md "Report an issue with this page").

----
url: https://docs.flutter.dev/ai/best-practices/mode-of-interaction.md
----

\# Mode of interaction > Learn to balance LLM capabilities with traditional code and implement guardrails to manage nondeterministic AI behavior. It's a mistake to think of a request to an LLM in the same way as calling a function. Given the same set of inputs in the same order, a function acts predictably. We can write tests and inject faults and harden a function for a wide variety of inputs. An LLM is not like that. A better way to think about it is as if the LLM were a user and to treat the data we get from them as such. Like a user, an LLM is nondeterministic, often wrong (partially or wholly) and sometimes plain random. To guard our apps under these conditions, we need to build the same guardrails around LLM input as we do around user input. If we can do that successfully, then we can bring extraordinary abilities to apps in the form of problem solving and creativity that can rival that of a human. ### Separation of concerns LLMs are good at some things and bad at others; the key is to bring them into your apps for the good while mitigating the bad. As an example, let's consider the task list in the Crossword Companion: The task list is the set of clues that need solving. The goal is to use colors and solutions in the task list to show progress during the solving process. The initial implementation provided the model with a tool for managing the task list, asking it to provide updates on progress as it went. Flash could not solve the puzzle this way, but Pro could. Unfortunately, it solved it in big chunks, only remembering to update the task list once or twice with a big delay in between. No amount of prompting could convince it to update the tasks as it went. You'll see the same behavior with modern AI agents managing their own task lists; that's just where we are in the evolution of LLMs at the moment. So how do we get consistent, deterministic updates of the task list? Take task management out of the LLM's hands and handle it in the code. To generalize, before applying an LLM solution to a problem you're facing, ask yourself whether an LLM is the best tool for the job. Is human-like problem solving and creativity worth the tradeoff in unpredictability? The answer to that question comes with experimentation. Here are some examples from the sample: | Task | LLM Suitability | Code Suitability | | ------------------------------------------------- | ------------------------------------------------------------ | --------------------------------------------------------------------------------- | | \*\*Parsing the grid for size, contents and clues\*\* | Great for an LLM by using vision and language understanding | Difficult to write the code to do this | | \*\*Validating grid contents\*\* | Possible to do with another LLM checking the work | Easier for a human to glance at and adjust | | \*\*Handling the task list\*\* | An LLM is unlikely to do this consistently | Easy to write the code to loop through a task list, updating as it goes | | \*\*Solving each clue\*\* | Great for an LLM using language understanding and generation | Difficult to do given real world clues that depend on word play, names, and slang | | \*\*Resolving conflicts\*\* | An LLM is inconsistent on this kind of looping | Easy for a human to glance at and adjust | It's a judgement call for sure, but if you can reasonably write the code to do it, your results will be predictable. However, if writing the code would be unreasonably difficult, then consider an LLM, knowing you'll have to build the guardrails like we did in the sample. ### Ask vs agent There's more than just one pivot to consider besides code vs. LLM. Models operate in roughly two modes: "ask" and "agent". A LLM is in "ask" mode when we prompt it without giving it tools to affect change in the world, for example, no tools at all or tools just for looking up data. Both the crossword interference model and clue solver models run in ask mode, using tools only for additional data. On the other hand, when we give an LLM a set of tools that allow it to operate on our behalf in the world – like reading and writing files, executing bash commands, loading web pages, calling web APIs, and so on – that LLM is in "agent" mode. ### Guardrails The difference between ask and agent mode is not the model you choose or the prompts you give it, but the tools you supply. The combination of the tools and the agentic loop described in the Tool calls section allow an LLM to call any number of those tools as often as it decides. Giving it that power puts the responsibility on you to make sure you treat it as unpredictable; more like a person than a program. You do that the same way that you validate user input, by building up a suite of tests to see how your app works against LLM responses. Give real LLMs a wide variety of prompts and mock the tools to evaluate how the LLM is using them. Like your first user testing experience, your first LLM testing results might surprise you. Use that data to build the guardrails you need to harden your app. In the sample, we didn't have to guard against harm, but we did have to guard against imperfect results. It was extensive testing against real-world data that led to the institution of human-in-the-loop guards against attempting to solve an invalid puzzle or conflicting solutions. In this way, Flutter and Firebase AI Logic make the perfect combination to harness the power of an LLM and bring unique capabilities to your apps.

----
url: https://docs.flutter.dev/ui/design/text
----

[Flutter's fonts and typography](/ui/design/text/typography)

[Learn about Flutter's support for typography.](/ui/design/text/typography)

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/ui/design/text/index.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/ui/design/text\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/ui/design/text/index.md "Report an issue with this page").

----
url: https://docs.flutter.dev/platform-integration/ios
----

[iOS debugging](/platform-integration/ios/ios-debugging)

[iOS-specific debugging techniques for Flutter apps](/platform-integration/ios/ios-debugging)

[Adding a launch screen to your iOS app](/platform-integration/ios/launch-screen)

[Learn how to add a launch screen to your iOS app.](/platform-integration/ios/launch-screen)

[Set up iOS development](/platform-integration/ios/setup)

[Configure your development environment to run, build, and deploy Flutter apps for iOS devices.](/platform-integration/ios/setup)

[Adding an iOS App Clip target](/platform-integration/ios/ios-app-clip)

[How to add an iOS App Clip target to your Flutter project.](/platform-integration/ios/ios-app-clip)

[Leveraging Apple's system APIs and frameworks](/platform-integration/ios/apple-frameworks)

[Learn about Flutter plugins that offer equivalent functionalities to Apple's frameworks.](/platform-integration/ios/apple-frameworks)

[Adding iOS app extensions](/platform-integration/ios/app-extensions)

[Learn how to add app extensions to your Flutter apps](/platform-integration/ios/app-extensions)

[Host native iOS views in your Flutter app with platform views](/platform-integration/ios/platform-views)

[Learn how to host native iOS views in your Flutter app with platform views.](/platform-integration/ios/platform-views)

[Restore state on iOS](/platform-integration/ios/restore-state-ios)

[How to restore the state of your iOS app after it's been killed by the OS.](/platform-integration/ios/restore-state-ios)

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/platform-integration/ios/index.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/platform-integration/ios\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/platform-integration/ios/index.md "Report an issue with this page").

----
url: https://docs.flutter.dev/tools/devtools/deep-links.md
----

\# Validate deep links > Learn how to validate deep links in your app. :::note As of release 3.27, the deep link validator tool works for \_both\_ Android and iOS. To see a demo of the deep link validator, check out the Google I/O 2024 video, \[No more broken links: Deep linking success in Flutter]\[]. ::: \[No more broken links: Deep linking success in Flutter]: https\://www\.youtube.com/watch?v=d7sZL6h1Elw The deep link view validates any deep links that are defined in your app. To use this feature, open DevTools, click into the \*\*Deep Links\*\* tab, and import a Flutter project that contains deep links. !\[Screenshot of the Deep Link Validator]\(/assets/images/docs/tools/devtools/deep-link-validator.png){:width="100%"} This tool helps you identify and troubleshoot any errors in your mobile deep link setup, from website configuration to manifest files. DevTools provides instructions on how to fix any issues, making the implementation process easier.

----
url: https://docs.flutter.dev/install/archive
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Flutter SDK archive](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

## Overview

[#](#overview)

The Flutter SDK archive is a collection of all previous versions of the Flutter SDK. This archive is useful for developers who need to use an older version of Flutter for compatibility reasons or to investigate bugs.

After you download a release, to set up Flutter, follow the [manual installation instructions](/install/manual).

The archive includes Flutter SDKs for Windows, macOS, and Linux on the following [channels](https://github.com/flutter/flutter/blob/main/docs/releases/Flutter-build-release-channels.md):

* **Stable channel**: This channel contains the most stable Flutter builds. Roughly every third beta version is promoted to the stable version. The stable channel is the recommended channel for new users and for production app releases.

* **Beta channel**: This channel is the most recent version of Flutter that is available, but it is not yet stable. The beta branch is usually released on the first Wednesday of the month. A fix will typically end up in the beta channel about two weeks after it lands in the main channel. Releases are distributed as [installation bundles](https://github.com/flutter/flutter/blob/main/docs/infra/Flutter-Installation-Bundles.md).

* **Main channel**: This channel has the newest features, but it hasn't been fully tested and might have some bugs. We don't recommend using it unless you're contributing to Flutter itself.

The following information is available for each Flutter release in the SDK archive:

* **Flutter version**: The version number of the Flutter SDK (for example, 3.35.0, 2.10.5) follows a modified [calendar versioning](https://calver.org/) scheme called *CalVer*. For more information, visit the [Flutter SDK versioning](https://github.com/flutter/flutter/blob/main/docs/releases/Release-versioning.md) page.
* **Architecture**: The processor architecture the SDK is built for (for example, x64, arm64). This specifies the type of processor the SDK is compatible with.
* **Ref**: The git commit hash that uniquely identifies the specific codebase used for that release.
* **Release Date**: The date when that particular Flutter version was officially released.
* **Dart version**: The corresponding version of the Dart SDK included in the Flutter SDK release.
* **Provenance**: Provides details about the build process and origin of the SDK, potentially including information about security attestations or build systems used. Results are returned as JSON.

## Public release windows

[#](#public-release-windows)

Predictability is key to landing complex features safely. We use public release windows to provide the community with the visibility needed to plan ahead. By explicitly calling out branch cutoff dates and release targets, we enable everyone to align their development cycles and coordinate feature landing effectively.

### What is a branch cutoff date?

[#](#what-is-a-branch-cutoff-date)

This date is the deadline for pull requests to land in the default branches (`main` for Dart and `master` for Flutter) to guarantee inclusion in the next stable release.

* **Before cutoff:** Your PR will ship in the next stable version.
* **After cutoff:** Your PR will wait for the following cycle.

### 2026 schedule

[#](#2026-schedule)

| Flutter version | Release target | Branch cutoff date |
| --------------- | -------------- | ------------------ |
| Flutter 3.41    | February, 2026 | 2026-01-06         |
| Flutter 3.44    | May, 2026      | 2026-04-07         |
| Flutter 3.47    | August, 2026   | 2026-07-07         |
| Flutter 3.50    | November, 2026 | 2026-10-06         |

***

## Stable channel

[#](#stable-channel)

* [Windows](#32-tab-panel)
* [macOS](#33-tab-panel)
* [Linux](#34-tab-panel)

Select from the following scrollable list:

| Flutter version | Architecture | Ref | Release date | Dart version | Provenance |
| --------------- | ------------ | --- | ------------ | ------------ | ---------- |
| Loading...      |              |     |              |              |            |

Select from the following scrollable list:

| Flutter version | Architecture | Ref | Release date | Dart version | Provenance |
| --------------- | ------------ | --- | ------------ | ------------ | ---------- |
| Loading...      |              |     |              |              |            |

Select from the following scrollable list:

| Flutter version | Architecture | Ref | Release date | Dart version | Provenance |
| --------------- | ------------ | --- | ------------ | ------------ | ---------- |
| Loading...      |              |     |              |              |            |

## Beta channel

[#](#beta-channel)

* [Windows](#35-tab-panel)
* [macOS](#36-tab-panel)
* [Linux](#37-tab-panel)

Select from the following scrollable list:

| Flutter version | Architecture | Ref | Release date | Dart version | Provenance |
| --------------- | ------------ | --- | ------------ | ------------ | ---------- |
| Loading...      |              |     |              |              |            |

Select from the following scrollable list:

| Flutter version | Architecture | Ref | Release date | Dart version | Provenance |
| --------------- | ------------ | --- | ------------ | ------------ | ---------- |
| Loading...      |              |     |              |              |            |

Select from the following scrollable list:

| Flutter version | Architecture | Ref | Release date | Dart version | Provenance |
| --------------- | ------------ | --- | ------------ | ------------ | ---------- |
| Loading...      |              |     |              |              |            |

[]()

## Main channel

[#](#main-channel)

[Installation bundles](https://github.com/flutter/flutter/blob/main/docs/infra/Flutter-Installation-Bundles.md) are not available for the `main` channel (which was previously known as the `master` channel). However, you can get the SDK directly from [GitHub repo](https://github.com/flutter/flutter) by cloning the `main` channel and then triggering a download of the SDK dependencies:

```
$ git clone -b main https://github.com/flutter/flutter.git
$ ./flutter/bin/flutter --version
```

[]()

## More information

[#](#more-information)

* For instructions on installing Flutter from a downloaded SDK, see [Install Flutter manually](/install/manual).

* To learn what's new in the major Flutter builds, check out the [release notes](/release/release-notes) page.

* For details on how our installation bundles are structured, see [Installation bundles](https://github.com/flutter/flutter/blob/main/docs/infra/Flutter-Installation-Bundles.md).

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-20. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/install/archive.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/install/archive\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/install/archive.md "Report an issue with this page").

----
url: https://docs.flutter.dev/ui/adaptive-responsive/safearea-mediaquery
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top SafeArea & MediaQuery](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

This page discusses how and when to use the `SafeArea` and `MediaQuery` widgets.

## SafeArea

[#](#safearea)

When running your app on the latest devices, you might encounter bits of the UI being blocked by cutouts on the device's screen. You can fix this with the [`SafeArea`](https://api.flutter.dev/flutter/widgets/SafeArea-class.html) widget, which insets its child widget to avoid intrusions (like notches and camera cutouts), as well as operating system UI (such as the status bar on Android), or by rounded corners of the physical display.

If you don't want this behavior, the `SafeArea` widget allows you to disable padding on any of its four sides. By default, all four sides are enabled.

It's generally recommended to wrap the body of a `Scaffold` widget in `SafeArea` as a good place to start, but you don't always need to put it this high in the `Widget` tree.

For example, if you purposefully want your app to stretch under the cutouts, you can move the `SafeArea` to wrap whatever content makes sense, and let the rest of the app take up the full screen.

Using `SafeArea` ensures that your app content won't be cut off by physical display features or operating system UI, and sets your app up for success even as new devices with different shapes and styles of cutouts enter the market.

How does `SafeArea` do so much in a small amount of code? Behind the scenes it uses the `MediaQuery` object.

## MediaQuery

[#](#mediaquery)

As discussed in the [SafeArea](#safearea) section, `MediaQuery` is a powerful widget for creating adaptive apps. Sometimes you'll use `MediaQuery` directly, and sometimes you'll use `SafeArea`, which uses `MediaQuery` behind the scenes.

`MediaQuery` provides lots of information, including the app's current window size. It exposes accessibility settings like high contrast mode and text scaling, or if the user is using an accessibility service like TalkBack or VoiceOver. `MediaQuery` also contains info about the features of your device's display, such as having a hinge or a fold.

`SafeArea` uses the data from `MediaQuery` to figure out how much to inset its child `Widget`. Specifically, it uses the `MediaQuery` padding property, which is basically the amount of the display that's partially obscured by system UI, display notches, or status bar.

So, why not use `MediaQuery` directly?

The answer is that `SafeArea` does one clever thing that makes it beneficial to use over just raw `MediaQueryData`. Specifically, it modifies the `MediaQuery` exposed to `SafeArea`'s children to make it appear as if the padding added to `SafeArea` doesn't exist. This means that you can nest `SafeArea`s, and only the topmost one will apply the padding needed to avoid the notches as system UI.

As your app grows and you move widgets around, you don't have to worry about having too much padding applied if you have multiple `SafeArea`s, whereas you would have issues if using `MediaQueryData.padding` directly.

You *can* wrap the body of a `Scaffold` widget with a `SafeArea`, but you don't *have* to put it this high in the widget tree. The `SafeArea` just needs to wrap the contents that would cause information loss if cut off by the hardware features mentioned earlier.

For example, if you purposefully want your app to stretch under the cutouts, you can move the `SafeArea` to wrap whatever content makes sense, and let the rest of the app take up the full screen. A side note is that this is what the `AppBar` widget does by default, which is how it goes underneath the system status bar. This is also why wrapping the body of a `Scaffold` in a `SafeArea` is recommended, instead of wrapping the whole `Scaffold` itself.

`SafeArea` ensures that your app content won't be cut off in a generic way and sets your app up for success even as new devices with different shapes and styles of cutouts enter the market.

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/ui/adaptive-responsive/safearea-mediaquery.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/ui/adaptive-responsive/safearea-mediaquery\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/ui/adaptive-responsive/safearea-mediaquery.md "Report an issue with this page").

----
url: https://docs.flutter.dev/cookbook/images/network-image
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Display images from the internet](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

Displaying images is fundamental for most mobile apps. Flutter provides the [`Image`](https://api.flutter.dev/flutter/widgets/Image-class.html) widget to display different types of images.

To work with images from a URL, use the [`Image.network()`](https://api.flutter.dev/flutter/widgets/Image/Image.network.html) constructor.

dart

```
Image.network('https://picsum.photos/250?image=9'),
```

## Bonus: animated gifs

[#](#bonus-animated-gifs)

One useful thing about the `Image` widget: It supports animated gifs.

dart

```
Image.network(
  'https://docs.flutter.dev/assets/images/dash/dash-fainting.gif',
);
```

## Image fade in with placeholders

[#](#image-fade-in-with-placeholders)

The default `Image.network` constructor doesn't handle more advanced functionality, such as fading images in after loading. To accomplish this task, check out [Fade in images with a placeholder](/cookbook/images/fading-in-images).

* [Fade in images with a placeholder](/cookbook/images/fading-in-images)

## Interactive example

[#](#interactive-example)

```
import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    var title = 'Web Images';

    return MaterialApp(
      title: title,
      home: Scaffold(
        appBar: AppBar(title: Text(title)),
        body: Image.network('https://picsum.photos/250?image=9'),
      ),
    );
  }
}
```

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/images/network-image.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/cookbook/images/network-image\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/images/network-image.md "Report an issue with this page").

----
url: https://docs.flutter.dev/ui/animations/overview
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Animations API overview](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

The animation system in Flutter is based on typed [`Animation`](https://api.flutter.dev/flutter/animation/Animation-class.html) objects. Widgets can either incorporate these animations in their build functions directly by reading their current value and listening to their state changes or they can use the animations as the basis of more elaborate animations that they pass along to other widgets.

## Animation

[#](#animation)

The primary building block of the animation system is the [`Animation`](https://api.flutter.dev/flutter/animation/Animation-class.html) class. An animation represents a value of a specific type that can change over the lifetime of the animation. Most widgets that perform an animation receive an `Animation` object as a parameter, from which they read the current value of the animation and to which they listen for changes to that value.

### `addListener`

[#](#addlistener)

Whenever the animation's value changes, the animation notifies all the listeners added with [`addListener`](https://api.flutter.dev/flutter/animation/Animation/addListener.html). Typically, a [`State`](https://api.flutter.dev/flutter/widgets/State-class.html) object that listens to an animation calls [`setState`](https://api.flutter.dev/flutter/widgets/State/setState.html) on itself in its listener callback to notify the widget system that it needs to rebuild with the new value of the animation.

This pattern is so common that Flutter provides widgets that rebuild when animations change value: [`AnimatedWidget`](https://api.flutter.dev/flutter/widgets/AnimatedWidget-class.html) and [`AnimatedBuilder`](https://api.flutter.dev/flutter/widgets/AnimatedBuilder-class.html). The first, `AnimatedWidget`, is most useful for stateless animated widgets. To use `AnimatedWidget`, subclass it and implement the [`build`](https://api.flutter.dev/flutter/widgets/AnimatedWidget/build.html) function. The second, `AnimatedBuilder`, is useful for more complex widgets that include an animation as part of a larger build function. To use `AnimatedBuilder`, construct the widget and pass it a `builder` function. To rebuild in response to other kinds of `Listenable` objects, use [`ListenableBuilder`](https://api.flutter.dev/flutter/widgets/ListenableBuilder-class.html).

For implicit animations, which manage their own controller, use an [`ImplicitlyAnimatedWidget`](https://api.flutter.dev/flutter/widgets/ImplicitlyAnimatedWidget-class.html) subclass, such as `AnimatedOpacity` or [`TweenAnimationBuilder`](https://api.flutter.dev/flutter/widgets/TweenAnimationBuilder-class.html).

### `addStatusListener`

[#](#addstatuslistener)

Animations also provide an [`AnimationStatus`](https://api.flutter.dev/flutter/animation/AnimationStatus.html), which indicates how the animation will evolve over time. Whenever the animation's status changes, the animation notifies all the listeners added with [`addStatusListener`](https://api.flutter.dev/flutter/animation/Animation/addStatusListener.html). Typically, animations start out in the `dismissed` status, which means they're at the beginning of their range. For example, animations that progress from 0.0 to 1.0 will be `dismissed` when their value is 0.0. An animation might then run `forward` (from 0.0 to 1.0) or perhaps in `reverse` (from 1.0 to 0.0). Eventually, if the animation reaches the end of its range (1.0), the animation reaches the `completed` status.

## AnimationController

[#](#animationcontroller)

To create an animation, first create an [`AnimationController`](https://api.flutter.dev/flutter/animation/AnimationController-class.html). As well as being an animation itself, an `AnimationController` lets you control the animation. For example, you can tell the controller to play the animation [`forward`](https://api.flutter.dev/flutter/animation/AnimationController/forward.html) or [`stop`](https://api.flutter.dev/flutter/animation/AnimationController/stop.html) the animation. You can also [`fling`](https://api.flutter.dev/flutter/animation/AnimationController/fling.html) animations, which uses a physical simulation, such as a spring, to drive the animation.

Once you've created an animation controller, you can start building other animations based on it. For example, you can create a [`ReverseAnimation`](https://api.flutter.dev/flutter/animation/ReverseAnimation-class.html) that mirrors the original animation but runs in the opposite direction (from 1.0 to 0.0). Similarly, you can create a [`CurvedAnimation`](https://api.flutter.dev/flutter/animation/CurvedAnimation-class.html) whose value is adjusted by a [`Curve`](https://api.flutter.dev/flutter/animation/Curve-class.html) (such as the predefined curves in [`Curves`](https://api.flutter.dev/flutter/animation/Curves-class.html)).

## Tweens

[#](#tweens)

To animate beyond the 0.0 to 1.0 interval, you can use a [`Tween<T>`](https://api.flutter.dev/flutter/animation/Tween-class.html), which interpolates between its [`begin`](https://api.flutter.dev/flutter/animation/Tween/begin.html) and [`end`](https://api.flutter.dev/flutter/animation/Tween/end.html) values. Many types have specific `Tween` subclasses that provide type-specific interpolation. For example, [`ColorTween`](https://api.flutter.dev/flutter/animation/ColorTween-class.html) interpolates between colors and [`RectTween`](https://api.flutter.dev/flutter/animation/RectTween-class.html) interpolates between rects. You can define your own interpolations by creating your own subclass of `Tween` and overriding its [`lerp`](https://api.flutter.dev/flutter/animation/Tween/lerp.html) function.

By itself, a tween only defines how to interpolate between two values. To get a concrete value for the current frame of an animation, you also need an animation to determine the current state. There are two ways to combine a tween with an animation to get a concrete value:

1. You can [`evaluate`](https://api.flutter.dev/flutter/animation/Animatable/evaluate.html) the tween at the current value of an animation. This approach is most useful for widgets that are already listening to the animation and hence rebuilding whenever the animation changes value.

2. You can [`animate`](https://api.flutter.dev/flutter/animation/Animatable/animate.html) the tween based on the animation. Rather than returning a single value, the animate function returns a new `Animation` that incorporates the tween. This approach is most useful when you want to give the newly created animation to another widget, which can then read the current value that incorporates the tween as well as listen for changes to the value.

## Architecture

[#](#architecture)

Animations are built from a number of core building blocks.

### Scheduler

[#](#scheduler)

The [`SchedulerBinding`](https://api.flutter.dev/flutter/scheduler/SchedulerBinding-mixin.html) is a singleton class that exposes the Flutter scheduling primitives.

For this discussion, the key primitive is the frame callbacks. Each time a frame needs to be shown on the screen, Flutter's engine triggers a "begin frame" callback that the scheduler multiplexes to all the listeners registered using [`scheduleFrameCallback()`](https://api.flutter.dev/flutter/scheduler/SchedulerBinding/scheduleFrameCallback.html). All these callbacks are given the official time stamp of the frame, in the form of a `Duration` from some arbitrary epoch. Since all the callbacks have the same time, any animations triggered from these callbacks will appear to be exactly synchronized even if they take a few milliseconds to be executed.

### Tickers

[#](#tickers)

The [`Ticker`](https://api.flutter.dev/flutter/scheduler/Ticker-class.html) class hooks into the scheduler's [`scheduleFrameCallback()`](https://api.flutter.dev/flutter/scheduler/SchedulerBinding/scheduleFrameCallback.html) mechanism to invoke a callback every tick.

A `Ticker` can be started and stopped. When started, it returns a `Future` that will resolve when it is stopped.

Each tick, the `Ticker` provides the callback with the duration since the first tick after it was started.

Because tickers always give their elapsed time relative to the first tick after they were started, tickers are all synchronized. If you start three tickers at different times between two ticks, they will all nonetheless be synchronized with the same starting time, and will subsequently tick in lockstep. Like people at a bus stop, all the tickers wait for a regularly occurring event (the tick) to begin moving (counting time).

### Simulations

[#](#simulations)

The [`Simulation`](https://api.flutter.dev/flutter/physics/Simulation-class.html) abstract class maps a relative time value (an elapsed time) to a double value, and has a notion of completion.

In principle simulations are stateless, but in practice some simulations (for example, [`BouncingScrollSimulation`](https://api.flutter.dev/flutter/widgets/BouncingScrollSimulation-class.html) and [`ClampingScrollSimulation`](https://api.flutter.dev/flutter/widgets/ClampingScrollSimulation-class.html)) change state irreversibly when queried.

There are [various concrete implementations](https://api.flutter.dev/flutter/physics/physics-library.html) of the `Simulation` class for different effects.

### Animatables

[#](#animatables)

The [`Animatable`](https://api.flutter.dev/flutter/animation/Animatable-class.html) abstract class maps a double to a value of a particular type.

An `Animatable` doesn't track animation progress, though some subclasses have mutable configuration.

#### Tweens

[#](#tweens-1)

The [`Tween<T>`](https://api.flutter.dev/flutter/animation/Tween-class.html) class maps a double value nominally in the range 0.0-1.0 to a typed value (for example, a `Color`, or another double). It is an `Animatable`.

It has a notion of an output type (`T`), a `begin` value and an `end` value of that type, and a way to interpolate (`lerp`) between the begin and end values for a given input value (the double nominally in the range 0.0-1.0).

A `Tween` doesn't track animation progress, but its `begin` and `end` values are mutable.

#### Composing animatables

[#](#composing-animatables)

Passing an `Animatable<double>` (the parent) to the `chain()` method of an `Animatable` object creates a new `Animatable` subclass that applies the parent's mapping then the child's mapping.

### Curves

[#](#curves)

The [`Curve`](https://api.flutter.dev/flutter/animation/Curve-class.html) abstract class maps doubles nominally in the range 0.0-1.0 to doubles nominally in the range 0.0-1.0.

`Curve` classes are stateless and immutable.

### Animations

[#](#animations)

The [`Animation`](https://api.flutter.dev/flutter/animation/Animation-class.html) abstract class provides a value of a given type, a concept of animation direction and animation status, and a listener interface to register callbacks that get invoked when the value or status change.

Some subclasses of `Animation` have values that never change ([`kAlwaysCompleteAnimation`](https://api.flutter.dev/flutter/animation/kAlwaysCompleteAnimation-constant.html), [`kAlwaysDismissedAnimation`](https://api.flutter.dev/flutter/animation/kAlwaysDismissedAnimation-constant.html), [`AlwaysStoppedAnimation`](https://api.flutter.dev/flutter/animation/AlwaysStoppedAnimation-class.html)); registering callbacks on these has no effect as the callbacks are never called.

The `Animation<double>` variant is special because it can be used to represent a double nominally in the range 0.0-1.0, which is the input expected by `Curve` and `Tween` classes, as well as some further subclasses of `Animation`.

Some `Animation` subclasses are stateless, merely forwarding listeners to their parents. Some are very stateful.

#### Composable animations

[#](#composable-animations)

Most `Animation` subclasses take an explicit "parent" `Animation<double>`. They are driven by that parent.

The `CurvedAnimation` subclass takes an `Animation<double>` object (the parent), a forward curve, and an optional reverse curve. It uses the parent's value as input to the curve for the current direction. If the parent reverses before reaching an endpoint, `CurvedAnimation` keeps using the same curve to avoid an abrupt jump in value.

The `ReverseAnimation` subclass takes an `Animation<double>` object as its parent and reverses all the values of the animation. It assumes the parent is using a value nominally in the range 0.0-1.0 and returns a value in the range 1.0-0.0. The status and direction of the parent animation are also reversed. `ReverseAnimation` is immutable and stateless.

The `ProxyAnimation` subclass takes an `Animation<double>` object as its parent and merely forwards the current state of that parent. However, the parent is mutable.

The `TrainHoppingAnimation` subclass takes two parents, and switches between them when their values cross.

#### Animation controllers

[#](#animation-controllers)

The [`AnimationController`](https://api.flutter.dev/flutter/animation/AnimationController-class.html) is a stateful `Animation<double>` that uses a `Ticker` to advance its progress each frame. It can be started and stopped. At each tick, it takes the time elapsed since it was started and passes it to a `Simulation` to obtain a value. That is then the value it reports. If the `Simulation` reports that at that time it has ended, then the controller stops itself.

The animation controller can be given a lower and upper bound to animate between, and a duration.

In the common case (using `forward()` or `reverse()`), the animation controller performs a linear interpolation from the lower bound to the upper bound (or vice versa, for the reverse direction) over the given duration.

When using `repeat()`, the animation controller uses a linear interpolation between the given bounds over the given duration. It repeats indefinitely unless you specify a `count` or stop it.

When using `animateTo()`, the animation controller interpolates from the current value to the given target, using a linear curve unless you specify a different `curve`. If you omit `duration`, it scales the controller's duration by the distance to the target as a fraction of the controller's range.

When using `fling()`, the animation controller uses a spring simulation with the given initial velocity and optional `SpringDescription`.

When using `animateWith()`, the given simulation is used to drive the controller.

These methods return a `TickerFuture` that completes when the animation finishes successfully. Canceling the animation, including by starting another animation or disposing the controller, leaves this future incomplete. Use its `orCancel` property to await a future that throws `TickerCanceled` on cancellation.

#### Attaching animatables to animations

[#](#attaching-animatables-to-animations)

Passing an `Animation<double>` (the new parent) to the `animate()` method of an `Animatable` object creates a new `Animation` subclass that acts like the `Animatable` but is driven from the given parent.

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-10-02. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/ui/animations/overview.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/ui/animations/overview\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/ui/animations/overview.md "Report an issue with this page").

----
url: https://docs.flutter.dev/perf/ui-performance
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Flutter performance profiling](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

## Overview

[#](#overview)

App performance encompasses various aspects, from raw speed and I/O throughput to the smoothness of the user interface. While this page primarily focuses on UI smoothness (lack of stutter or jank), the tools described here can often be used to diagnose other performance issues as well.

Flutter offers several tools for performance analysis. Here are a few of them:

* **The Performance Overlay**: Displays a simplified set of metrics directly within your running app. To learn more, see the sections in this topic.

* **The Performance View**: A web-based interface that connects to your app and displays detailed performance metrics. Part of the DevTools utility. To learn more, see [Use the Performance View](/tools/devtools/performance).

* **Performance tracing within Dart**: Add tracing directly into your app's Dart code, using the `dart:developer package`, and then track your app's performance in the DevTools utility. To learn more, see [Tracing Dart code](/testing/code-debugging#trace-dart-code-performance).

* **Benchmarking**: You can measure and track your app's performance by writing benchmark tests. The Flutter Driver library provides support for benchmarking. Using this integration test framework, you can generate metrics that track jank, download size, battery efficiency, and startup time. For more information, check out [Integration testing](/testing/integration-tests).

* **Widget rebuild profiler (IntelliJ for Android Studio)**: Jank often arises from unnecessary UI rebuilds. If you are using IntelliJ for Android Studio, the Widget Rebuild Profiler helps pinpoint and fix these issues by showing widget rebuild counts for the current screen and frame. For more information, see [Show performance data](/tools/android-studio#show-performance-data).

Flutter aims to provide 60 frames per second (fps) performance, or 120 fps on devices that support it. To achieve the 60fps, each frame must render approximately every 16ms to avoid jank. Jank occurs when frames take significantly longer to render and are dropped, resulting in a visible stutter in animations. For example, if a frame occasionally takes 10 times longer than usual to render, it will likely be dropped, causing the animation to appear jerky.

## Connect to a physical device

[#](#connect-to-a-physical-device)

Almost all performance debugging for Flutter applications should be conducted on a physical Android or iOS device, with your Flutter application running in [profile mode](/testing/build-modes#profile). Using debug mode, or running apps on simulators or emulators, is generally not indicative of the final behavior of release mode builds. *You should consider checking performance on the slowest device that your users might reasonably use.*

## Run in profile mode

[#](#run-in-profile-mode)

Flutter's profile mode compiles and launches your application almost identically to release mode, but with just enough additional functionality to allow debugging performance problems. For example, profile mode provides tracing information to the profiling tools.

Launch the app in profile mode as follows:

* In VS Code, open your `launch.json` file, and set the `flutterMode` property to `profile` (when done profiling, change it back to `release` or `debug`):

  json

  ```
  "configurations": [
    {
      "name": "Flutter",
      "request": "launch",
      "type": "dart",
      "flutterMode": "profile"
    }
  ]
  ```

* In Android Studio and IntelliJ, use the **Run > Flutter Run main.dart in Profile Mode** menu item.

* From the command line, use the `--profile` flag:

  ```
  $ flutter run --profile
  ```

For more information on the different modes, see [Flutter's build modes](/testing/build-modes).

You'll begin by opening DevTools and viewing the performance overlay, as discussed in the next section.

## Launch DevTools

[#](#launch-devtools)

DevTools provides features like profiling, examining the heap, displaying code coverage, enabling the performance overlay, and a step-by-step debugger. DevTools' [Timeline view](/tools/devtools/performance) allows you to investigate the UI performance of your application on a frame-by-frame basis.

Once your app is running in profile mode, [launch DevTools](/tools/devtools).

## Display the performance overlay

[#](#displaying-the-performance-overlay)

You can toggle the display of the performance overlay as follows:

* **DevTools Performance view**: The easiest way to enable the PerformanceOverlay widget is from the [Performance view](/tools/devtools/performance) in [DevTools](/tools/devtools). Simply click the **Performance Overlay** button to toggle the overlay on your running app.

* **command line**: Toggle the performance overlay using the **P** key from the command line.

* **programmatically**: To enable the overlay programmatically, see [Performance overlay](/testing/code-debugging#add-performance-overlay), a section in the [Debugging Flutter apps programmatically](/testing/code-debugging) page.

[]()

## Observe the performance overlay

[#](#performance-overlay)

The performance overlay displays statistics in two graphs that show where time is being spent in your app. If the UI is janky (skipping frames), these graphs help you figure out why. The graphs display on top of your running app, but they aren't drawn like a normal widget—the Flutter engine itself paints the overlay and only minimally impacts performance. Each graph represents the last 300 frames for that thread.

This section describes how to enable the performance overlay and use it to diagnose the cause of jank in your application. The following screenshot shows the performance overlay running on the Flutter Gallery example:

Performance overlay showing the raster thread (top), and UI thread (bottom).\
The vertical green bars represent the current frame.

### Review the graphs

[#](#interpreting-the-graphs)

The top graph (marked "GPU") shows the time spent by the raster thread, the bottom one graph shows the time spent by the UI thread. The white lines across the graphs show 16ms increments along the vertical axis; if the graph ever goes over one of these lines then you are running at less than 60Hz. The horizontal axis represents frames. The graph is only updated when your application paints, so if it's idle the graph stops moving.

The overlay should always be viewed in [profile mode](/testing/build-modes#profile), since [debug mode](/testing/build-modes#debug) performance is intentionally sacrificed in exchange for expensive asserts that are intended to aid development, and thus the results are misleading.

Each frame should be created and displayed within 1/60th of a second (approximately 16ms). A frame exceeding this limit (in either graph) fails to display, resulting in jank, and a vertical red bar appears in one or both of the graphs. If a red bar appears in the UI graph, the Dart code is too expensive. If a red vertical bar appears in the GPU graph, the scene is too complicated to render quickly.

The vertical red bars indicate that the current frame is expensive to both render and paint.\
When both graphs display red, start by diagnosing the UI thread.

### Review the threads

[#](#flutters-threads)

Flutter uses several threads to do its work, though only two of the threads are shown in the overlay. All of your Dart code runs on the UI thread. Although you have no direct access to any other thread, your actions on the UI thread have performance consequences on other threads.

* **Platform thread**

  The platform's main thread. Plugin code runs here. For more information, see the [UIKit](https://developer.apple.com/documentation/uikit) documentation for iOS, or the [MainThread](https://developer.android.com/reference/android/support/annotation/MainThread) documentation for Android. *This thread is not shown in the performance overlay.*

* **UI thread**

  The UI thread executes Dart code in the Dart VM. This thread includes code that you wrote, and code executed by Flutter's framework on your app's behalf. When your app creates and displays a scene, the UI thread creates a *layer tree*, a lightweight object containing device-agnostic painting commands, and sends the layer tree to the raster thread to be rendered on the device. *Don't block this thread!* Shown in the bottom row of the performance overlay.

* **Raster thread**

  The raster thread takes the layer tree and displays it by talking to the GPU (graphic processing unit). You cannot directly access the raster thread or its data but, if this thread is slow, it's a result of something you've done in the Dart code. Skia and Impeller, the graphics libraries, run on this thread. Shown in the top row of the performance overlay. Note that while the raster thread rasterizes for the GPU, the thread itself runs on the CPU.

* **I/O thread**

  Performs expensive tasks (mostly I/O) that would otherwise block either the UI or raster threads. *This thread is not shown in the performance overlay.*

For links to more information and videos, see [The Framework architecture](https://github.com/flutter/flutter/blob/main/docs/about/The-Framework-architecture.md) in the [Flutter wiki](https://github.com/flutter/flutter/tree/main/docs), and the community article, [The Layer Cake](https://medium.com/flutter-community/the-layer-cake-widgets-elements-renderobjects-7644c3142401).

## Identify problems

[#](#identify-problems)

### Review the UI graph

[#](#identifying-problems-in-the-ui-graph)

If the performance overlay shows red in the UI graph, start by profiling the Dart VM, even if the GPU graph also shows red.

### Review the GPU graph

[#](#identifying-problems-in-the-gpu-graph)

Sometimes a scene results in a layer tree that is easy to construct, but expensive to render on the raster thread. When this happens, the UI graph has no red, but the GPU graph shows red. In this case, you'll need to figure out what your code is doing that is causing rendering code to be slow. Specific kinds of workloads are more difficult for the GPU. They might involve unnecessary calls to [`saveLayer`](https://api.flutter.dev/flutter/dart-ui/Canvas/saveLayer.html), intersecting opacities with multiple objects, and clips or shadows in specific situations.

If you suspect that the source of the slowness is during an animation, click the **Slow Animations** button in the Flutter inspector to slow animations down by 5x. If you want more control on the speed, you can also do this [programmatically](/testing/code-debugging#debug-animation-issues).

Is the slowness on the first frame, or on the whole animation? If it's the whole animation, is clipping causing the slow down? Maybe there's an alternative way of drawing the scene that doesn't use clipping. For example, overlay opaque corners onto a square instead of clipping to a rounded rectangle. If it's a static scene that's being faded, rotated, or otherwise manipulated, a [`RepaintBoundary`](https://api.flutter.dev/flutter/widgets/RepaintBoundary-class.html) might help.

#### Checking for offscreen layers

[#](#checking-for-offscreen-layers)

The [`saveLayer`](https://api.flutter.dev/flutter/dart-ui/Canvas/saveLayer.html) method is one of the most expensive methods in the Flutter framework. It's useful when applying post-processing to the scene, but it can slow your app and should be avoided if you don't need it. Even if you don't call `saveLayer` explicitly, implicit calls might happen on your behalf, for example when specifying [`Clip.antiAliasWithSaveLayer`](https://api.flutter.dev/flutter/dart-ui/Clip.html) (typically as a `clipBehavior`).

For example, perhaps you have a group of objects with opacities that are rendered using `saveLayer`. In this case, it's probably more performant to apply an opacity to each individual widget, rather than a parent widget higher up in the widget tree. The same goes for other potentially expensive operations, such as clipping or shadows.

When you encounter calls to `saveLayer`, ask yourself these questions:

* Does the app need this effect?
* Can any of these calls be eliminated?
* Can I apply the same effect to an individual element instead of a group?

#### Checking for non-cached images

[#](#checking-for-non-cached-images)

Caching an image with [`RepaintBoundary`](https://api.flutter.dev/flutter/widgets/RepaintBoundary-class.html) is good, *when it makes sense*.

One of the most expensive operations, from a resource perspective, is rendering a texture using an image file. First, the compressed image is fetched from persistent storage. The image is decompressed into host memory (GPU memory), and transferred to device memory (RAM).

In other words, image I/O can be expensive. The cache provides snapshots of complex hierarchies so they are easier to render in subsequent frames. *Because raster cache entries are expensive to construct and take up loads of GPU memory, cache images only where absolutely necessary.*

## Other resources

[#](#other-resources)

The following resources provide more information on using Flutter's tools and debugging in Flutter:

* [Debugging](/testing/debugging)
* [Performance view](/tools/devtools/performance)
* [Flutter inspector](/tools/devtools/inspector)
* [Flutter inspector talk](https://www.youtube.com/watch?v=JIcmJNT9DNI), presented at DartConf 2018
* [Why Flutter Uses Dart](https://hackernoon.com/why-flutter-uses-dart-dd635a054ebf), an article on Hackernoon
* [Why Flutter uses Dart](https://www.youtube.com/watch?v=5F-6n_2XWR8), a video on the Flutter channel
* [DevTools](/tools/devtools): performance tooling for Dart and Flutter apps
* [Flutter API](https://api.flutter.dev) docs, particularly the [`PerformanceOverlay`](https://api.flutter.dev/flutter/widgets/PerformanceOverlay-class.html) class, and the [`dart:developer`](https://api.flutter.dev/flutter/dart-developer/dart-developer-library.html) package

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-07-31. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/perf/ui-performance.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/perf/ui-performance\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/perf/ui-performance.md "Report an issue with this page").

----
url: https://docs.flutter.dev/reference/user-surveys
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top User surveys](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

## Why do I see a survey announcement?

[#](#why-do-i-see-a-survey-announcement)

If you have not opted-out of Flutter's [analytics and crash reporting](/reference/crash-reporting), you may receive a survey announcement in your IDE.

We run two types of surveys:

1. **Each quarter.** We give all active Flutter and Dart users the option to take this survey.
2. **Ad-hoc.** We design and deploy experimental surveys when we want to learn more about specific topics. If your telemetry data meets the survey criteria, you might see a survey announcement.

## How will my responses be used?

[#](#how-will-my-responses-be-used)

We use the responses you submit via survey to improve Flutter and Dart. We store this information independent of the information sent to Google via analytics. To see how we used prior surveys to improve Flutter and Dart, check out the survey posts on the [Flutter blog](https://blog.flutter.dev/search?q=survey).

## How can I disable it?

[#](#how-can-i-disable-it)

To mute survey announcements, you might do one of the following:

1. Click the button on the message.
2. Opt-out of analytics and crash reporting per the steps given in [Disabling analytics reporting](/reference/crash-reporting#disabling-analytics-reporting).

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-07-29. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/reference/user-surveys.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/reference/user-surveys\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/reference/user-surveys.md "Report an issue with this page").

----
url: https://docs.flutter.dev/testing/debugging.md
----

\# Debug Flutter apps > How to debug your Flutter app.

<!--?code-excerpt path-base="testing/debugging"?-->

There's a wide variety of tools and features to help debug Flutter applications. Here are some of the available tools: \* \[VS Code]\[] (recommended) and \[Android Studio/IntelliJ]\[], (enabled with the Flutter and Dart plugins) support a built-in source-level debugger with the ability to set breakpoints, step through code, and examine values. \* \[DevTools]\[], a suite of performance and profiling tools that run in a browser. \* \[Flutter inspector]\[], a widget inspector available in DevTools, and also directly from Android Studio and IntelliJ (enabled with the Flutter plugin). The inspector allows you to examine a visual representation of the widget tree, inspect individual widgets and their property values, enable the performance overlay, and more. ## Other resources You might find the following docs useful: \* \[Performance best practices]\[] \* \[Flutter performance profiling]\[] \* \[Use a native debugger]\[] \* \[Flutter's modes]\[] \* \[Debugging Flutter apps programmatically]\[] \[Debugging Flutter apps programmatically]: /testing/code-debugging \[Flutter's modes]: /testing/build-modes \[Flutter performance profiling]: /perf/ui-performance \[Performance best practices]: /perf/best-practices \[Use a native debugger]: /testing/native-debugging \[Android Studio/IntelliJ]: /tools/android-studio#run-app-with-breakpoints \[VS Code]: /tools/vs-code#run-app-with-breakpoints \[DevTools]: /tools/devtools \[Flutter inspector]: /tools/devtools/inspector

----
url: https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.29.0.md
----

\# DevTools 2.29.0 release notes > Release notes for Dart and Flutter DevTools version 2.29.0. The 2.29.0 release of the Dart and Flutter DevTools includes the following changes among other general improvements. To learn more about DevTools, check out the \[DevTools overview]\(https\://docs.flutter.dev/tools/devtools). ## General updates \* Fix a bug with service extension states not being cleared on app disconnect. - \[#6547]\(https\://github.com/flutter/devtools/pull/6547) \* Improved styling of bottom status bar when connected to an app. - \[#6525]\(https\://github.com/flutter/devtools/pull/6525) \* Added a workaround to fix copy button functionality in VSCode. - \[#6598]\(https\://github.com/flutter/devtools/pull/6598) ## Performance updates \* Added an option in the "Enhance Tracing" menu for tracking platform channel activity. This is useful for apps with plugins. - \[#6515]\(https\://github.com/flutter/devtools/pull/6515) !\[Track platform channels setting]\(/assets/images/docs/tools/devtools/release-notes/images-2.29.0/track\_platform\_channels.png "Track platform channels setting") \* Made the Performance screen available when there is no connected app. Performance data that was previously saved from DevTools can be reloaded for viewing from this screen. - \[#6567]\(https\://github.com/flutter/devtools/pull/6567) \* Added an "Open" button to the Performance controls for loading data that was previously saved from DevTools. - \[#6567]\(https\://github.com/flutter/devtools/pull/6567) !\[Open file button on the performance screen]\(/assets/images/docs/tools/devtools/release-notes/images-2.29.0/open\_file\_performance\_screen.png "Open file button on the performance screen") ## CPU profiler updates \* Tree guidelines are now always enabled for the "Bottom Up" and "Call Tree" tabs. - \[#6534]\(https\://github.com/flutter/devtools/pull/6534) \* Made the CPU profiler screen available when there is no connected app. CPU profiles that were previously saved from DevTools can be reloaded for viewing from this screen. - \[#6567]\(https\://github.com/flutter/devtools/pull/6567) \* Added an "Open" button to the CPU profiler controls for loading data that was previously saved from DevTools. - \[#6567]\(https\://github.com/flutter/devtools/pull/6567) ## Network profiler updates \* Network statuses now show with an error color when the request failed. - \[#6527]\(https\://github.com/flutter/devtools/pull/6527) ## Full commit history To find a complete list of changes in this release, check out the \[DevTools git log]\(https\://github.com/flutter/devtools/tree/v2.29.0).

----
url: https://docs.flutter.dev/deployment/linux.md
----

\# Build and release a Linux app to the Snap Store > How to prepare for and release a Linux app to the Snap store. During a typical development cycle, you test an app using \`flutter run\` at the command line, or by using the \*\*Run\*\* and \*\*Debug\*\* options in your IDE. By default, Flutter builds a \_debug\_ version of your app. When you're ready to prepare a \_release\_ version of your app, for example to \[publish to the Snap Store]\[snap] or an \[alternative channel]\(#additional-deployment-resources), this page can help. ## Prerequisites To build and publish to the Snap Store, you need the following components: \* \[Ubuntu]\[] OS, 18.04 LTS (or higher) \* \[Snapcraft]\[] command line tool \* \[LXD container manager]\[] ## Set up the build environment Use the following instructions to set up your build environment. ### Install snapcraft At the command line, run the following: \`\`\`console $ sudo snap install snapcraft --classic \`\`\` ### Install LXD To install LXD, use the following command: \`\`\`console $ sudo snap install lxd \`\`\` LXD is required during the snap build process. Once installed, LXD needs to be configured for use. The default answers are suitable for most use cases. \`\`\`console $ sudo lxd init Would you like to use LXD clustering? (yes/no) \[default=no]: Do you want to configure a new storage pool? (yes/no) \[default=yes]: Name of the new storage pool \[default=default]: Name of the storage backend to use (btrfs, dir, lvm, zfs, ceph) \[default=zfs]: Create a new ZFS pool? (yes/no) \[default=yes]: Would you like to use an existing empty disk or partition? (yes/no) \[default=no]: Size in GB of the new loop device (1GB minimum) \[default=5GB]: Would you like to connect to a MAAS server? (yes/no) \[default=no]: Would you like to create a new local network bridge? (yes/no) \[default=yes]: What should the new bridge be called? \[default=lxdbr0]: What IPv4 address should be used? (CIDR subnet notation, "auto" or "none") \[default=auto]: What IPv6 address should be used? (CIDR subnet notation, "auto" or "none") \[default=auto]: Would you like LXD to be available over the network? (yes/no) \[default=no]: Would you like stale cached images to be updated automatically? (yes/no) \[default=yes] Would you like a YAML "lxd init" preseed to be printed? (yes/no) \[default=no]: \`\`\` On the first run, LXD might not be able to connect to its socket: \`\`\`console An error occurred when trying to communicate with the 'LXD' provider: cannot connect to the LXD socket ('/var/snap/lxd/common/lxd/unix.socket'). \`\`\` This means you need to add your username to the LXD (lxd) group, so log out of your session and then log back in: \`\`\`console $ sudo usermod -a -G lxd \`\`\` ## Overview of snapcraft The \`snapcraft\` tool builds snaps based on the instructions listed in a \`snapcraft.yaml\` file. To get a basic understanding of snapcraft and its core concepts, take a look at the \[Snap documentation]\[] and the \[Introduction to snapcraft]\[]. Additional links and information are listed at the bottom of this page. ## Flutter snapcraft.yaml example Place the YAML file in your Flutter project under \`/snap/snapcraft.yaml\`. (And remember that YAML files are sensitive to white space!) For example: \`\`\`yaml name: super-cool-app version: 0.1.0 summary: Super Cool App description: Super Cool App that does everything! confinement: strict base: core22 grade: stable slots: dbus-super-cool-app: # adjust accordingly to your app name interface: dbus bus: session name: org.bar.super\_cool\_app # adjust accordingly to your app name and apps: super-cool-app: command: super\_cool\_app extensions: \[gnome] # gnome includes the libraries required by flutter plugs: - network slots: - dbus-super-cool-app parts: super-cool-app: source: . plugin: flutter flutter-target: lib/main.dart # The main entry-point file of the application \`\`\` The following sections explain the various pieces of the YAML file. ### Metadata This section of the \`snapcraft.yaml\` file defines and describes the application. The snap version is derived (adopted) from the build section. \`\`\`yaml name: super-cool-app version: 0.1.0 summary: Super Cool App description: Super Cool App that does everything! \`\`\` ### Grade, confinement, and base This section defines how the snap is built. \`\`\`yaml confinement: strict base: core22 grade: stable \`\`\` \*\*Grade\*\* : Specifies the quality of the snap; this is relevant for the publication step later. \*\*Confinement\*\* : Specifies what level of system resource access the snap will have once installed on the end-user system. Strict confinement limits the application access to specific resources (defined by plugs in the \`app\` section). \*\*Base\*\* : Snaps are designed to be self-contained applications, and therefore, they require their own private core root filesystem known as \`base\`. The \`base\` keyword specifies the version used to provide the minimal set of common libraries, and mounted as the root filesystem for the application at runtime. ### Apps This section defines the application(s) that exist inside the snap. There can be one or more applications per snap. This example has a single application—super\_cool\_app. \`\`\`yaml apps: super-cool-app: command: super\_cool\_app extensions: \[gnome] \`\`\` \*\*Command\*\* : Points to the binary, relative to the snap's root, and runs when the snap is invoked. \*\*Extensions\*\* : A list of one or more extensions. Snapcraft extensions are reusable components that can expose sets of libraries and tools to a snap at build and runtime, without the developer needing to have specific knowledge of included frameworks. The \`gnome\` extension exposes the GTK 3 libraries to the Flutter snap. This ensures a smaller footprint and better integration with the system. \*\*Plugs\*\* : A list of one or more plugs for system interfaces. These are required to provide necessary functionality when snaps are strictly confined. This Flutter snap needs access to the network. \*\*DBus interface\*\* : The \[DBus interface]\[] provides a way for snaps to communicate over DBus. The snap providing the DBus service declares a slot with the well-known DBus name and which bus it uses. Snaps wanting to communicate with the providing snap's service declare a plug for the providing snap. Note that a snap declaration is needed for your snap to be delivered via the snap store and claim this well-known DBus name (simply upload the snap to the store and request a manual review and a reviewer will take a look). When a providing snap is installed, snapd will generate security policy that will allow it to listen on the well-known DBus name on the specified bus. If the system bus is specified, snapd will also generate DBus bus policy that allows 'root' to own the name and any user to communicate with the service. Non-snap processes are allowed to communicate with the providing snap following traditional permissions checks. Other (consuming) snaps might only communicate with the providing snap by connecting the snaps' interface. \`\`\`plaintext dbus-super-cool-app: # adjust accordingly to your app name interface: dbus bus: session name: dev.site.super\_cool\_app \`\`\` ### Parts This section defines the sources required to assemble the snap. Parts can be downloaded and built automatically using plugins. Similar to extensions, snapcraft can use various plugins (such as Python, C, Java, and Ruby) to assist in the building process. Snapcraft also has some special plugins. \*\*nil\*\* plugin : Performs no action and the actual build process is handled using a manual override. \*\*flutter\*\* plugin : Provides the necessary Flutter SDK tools so you can use it without having to manually download and set up the build tools. \`\`\`yaml parts: super-cool-app: source: . plugin: flutter flutter-target: lib/main.dart # The main entry-point file of the application \`\`\` ## Desktop file and icon Desktop entry files are used to add an application to the desktop menu. These files specify the name and icon of your application, the categories it belongs to, related search keywords and more. These files have the extension .desktop and follow the XDG Desktop Entry Specification version 1.1. ### Flutter super-cool-app.desktop example Place the .desktop file in your Flutter project under \`/snap/gui/super-cool-app.desktop\`. \*\*Notice\*\*: icon and .desktop file name must be the same as your app name in yaml file! For example: \`\`\`yaml \[Desktop Entry] Name=Super Cool App Comment=Super Cool App that does everything Exec=super-cool-app Icon=${SNAP}/meta/gui/super-cool-app.png # Replace name with your app name. Terminal=false Type=Application Categories=Education; # Adjust accordingly your snap category. \`\`\` Place your icon with .png extension in your Flutter project under \`/snap/gui/super-cool-app.png\`. ## Build the snap Once the \`snapcraft.yaml\` file is complete, run \`snapcraft\` as follows from the root directory of the project. To use the Multipass VM backend: \`\`\`console $ snapcraft \`\`\` To use the LXD container backend: \`\`\`console $ snapcraft --use-lxd \`\`\` ## Test the snap Once the snap is built, you'll have a \`.snap\` file in your root project directory. $ sudo snap install ./super-cool-app\_0.1.0\_amd64.snap --dangerous ## Publish You can now publish the snap. The process consists of the following: 1. Create a developer account at \[snapcraft.io]\[], if you haven't already done so. 1. Register the app's name. Registration can be done either using the Snap Store Web UI portal, or from the command line, as follows: \`\`\`console $ snapcraft login $ snapcraft register \`\`\` 1. Release the app. After reading the next section to learn about selecting a Snap Store channel, push the snap to the store: \`\`\`console $ snapcraft upload --release= .snap \`\`\` ### Snap Store channels The Snap Store uses channels to differentiate among different versions of snaps. The \`snapcraft upload\` command uploads the snap file to the store. However, before you run this command, you need to learn about the different release channels. Each channel consists of three components: \*\*Track\*\* : All snaps must have a default track called latest. This is the implied track unless specified otherwise. \*\*Risk\*\* : Defines the readiness of the application. The risk levels used in the snap store are: \`stable\`, \`candidate\`, \`beta\`, and \`edge\`. \*\*Branch\*\* : Allows creation of short-lived snap sequences to test bug-fixes. ### Snap Store automatic review The Snap Store runs several automated checks against your snap. There might also be a manual review, depending on how the snap was built, and if there are any specific security concerns. If the checks pass without errors, the snap becomes available in the store. ## Additional snapcraft resources You can learn more from the following links on the \[snapcraft.io]\[] site: \* \[Channels]\[] \* \[Environment variables]\[] \* \[Interface management]\[] \* \[Parts environment variables]\[] \* \[Releasing to the Snap Store]\[] \* \[Snapcraft extensions]\[] \* \[Supported plugins]\[] ## Additional deployment resources ### \[fastforge]\[] > An all-in-one Flutter application packaging and distribution tool, providing you with a one-stop solution to meet various distribution needs. Supports popular packaging formats like, appimage, deb, pacman, rpm, and more. ### \[flatpak-flutter]\[] > Flatpak manifest tooling for the offline build of Flutter apps. Supports Flatpak preparation for publishing on \[Flathub]\[]. \[Environment variables]: https\://snapcraft.io/docs/environment-variables \[Flutter wiki]: https\://github.com/flutter/flutter/tree/main/docs \[Interface management]: https\://snapcraft.io/docs/interface-management \[DBus interface]: https\://snapcraft.io/docs/dbus-interface \[Introduction to snapcraft]: https\://snapcraft.io/blog/introduction-to-snapcraft \[LXD container manager]: https\://linuxcontainers.org/lxd/downloads/ \[Multipass virtualization manager]: https\://multipass.run/ \[Parts environment variables]: https\://snapcraft.io/docs/parts-environment-variables \[Releasing to the Snap Store]: https\://snapcraft.io/docs/releasing-to-the-snap-store \[Channels]: https\://docs.snapcraft.io/channels \[snap]: https\://snapcraft.io/store \[Snap documentation]: https\://snapcraft.io/docs \[Snapcraft]: https\://snapcraft.io/snapcraft \[snapcraft.io]: https\://snapcraft.io/ \[Snapcraft extensions]: https\://snapcraft.io/docs/snapcraft-extensions \[Supported plugins]: https\://snapcraft.io/docs/supported-plugins \[Ubuntu]: https\://ubuntu.com/download/desktop \[fastforge]: https\://github.com/fastforgedev/fastforge \[flatpak-flutter]: https\://github.com/TheAppgineer/flatpak-flutter \[Flathub]: https\://flathub.org

----
url: https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.59.0
----

The 2.59.0 release of the Dart and Flutter DevTools includes the following changes among other general improvements. To learn more about DevTools, check out the [DevTools overview](/tools/devtools).

## General updates

[#](#general-updates)

* Fixed a `RangeError` thrown by `SplitPane` when the parent rebuilt the widget with a different number of children. For example, when toggling a panel in or out of the layout. - [#9822](https://github.com/flutter/devtools/pull/9822)

## Inspector updates

[#](#inspector-updates)

* Fixed an issue where hover tooltips in the widget tree were being clipped by the window boundaries. - [#9823](https://github.com/flutter/devtools/pull/9823)

## Full commit history

[#](#full-commit-history)

To find a complete list of changes in this release, check out the [DevTools git log](https://github.com/flutter/devtools/tree/v2.59.0).

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-06-03. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/release-notes/release-notes-2.59.0.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.59.0\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/release-notes/release-notes-2.59.0.md "Report an issue with this page").

----
url: https://docs.flutter.dev/deployment/web
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Build and release a web app](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

During a typical development cycle, you test an app using `flutter run -d chrome` (for example) at the command line. This builds a *debug* version of your app.

This page helps you prepare a *release* version of your app and covers the following topics:

* [Building the app for release](#building-the-app-for-release)
* [Deploying to the web](#deploying-to-the-web)
* [Deploying to Firebase Hosting](#deploying-to-firebase-hosting)
* [Handling images on the web](#handling-images-on-the-web)
* [Opting-in to WebAssembly](#opting-in-to-webassembly)
* [Minification](#minification)

## Building the app for release

[#](#building-the-app-for-release)

Build the app for deployment using the `flutter build web` command.

```
flutter build web
```

To build your application with WebAssembly (Wasm), pass the `--wasm` flag:

```
flutter build web --wasm
```

This generates the app, including the assets, and places the files into the `/build/web` directory of the project.

To validate the release build of your app, launch a web server (for example, `python -m http.server 8000`, or by using the [dhttpd](https://pub.dev/packages/dhttpd) package), and open the /build/web directory. Navigate to `localhost:8000` in your browser (given the python SimpleHTTPServer example) to view the release version of your app.

## Additional build flags

[#](#additional-build-flags)

You might need to deploy a profile or debug build for testing. To do this, pass the `--profile` or `--debug` flag to the `flutter build web` command. Profile builds are specialized for performance profiling using Chrome DevTools, and debug builds can be used to configure dart2js to respect assertions and change the optimization level (using the `-O` flag.)

### Source maps

[#](#source-maps)

To generate source maps for symbolication in crash reporting services, pass the `--source-maps` flag:

```
flutter build web --source-maps
```

Generating source maps creates separate `.js.map` or `.wasm.map` files (or both, depending on your build target) in the `build/web` directory without adding binary overhead to the compiled output.

## Opting-in to WebAssembly

[#](#opting-in-to-webassembly)

To compile and optimize your app for WebAssembly, use the `--wasm` flag when running or building.

For more information, see [Support for WebAssembly (Wasm)](/platform-integration/web/wasm).

## Deploying to the web

[#](#deploying-to-the-web)

When you are ready to deploy your app, upload the release bundle to Firebase, the cloud, or a similar service. Here are a few possibilities, but there are many others:

* [Firebase Hosting](https://firebase.google.com/docs/hosting/frameworks/flutter)
* [GitHub Pages](https://pages.github.com/)
* [Google Cloud Hosting](https://cloud.google.com/solutions/web-hosting)

## Deploying to Firebase Hosting

[#](#deploying-to-firebase-hosting)

You can use the Firebase CLI to build and release your Flutter app with Firebase Hosting.

### Before you begin

[#](#before-you-begin)

To get started, [install or update](https://firebase.google.com/docs/cli#install_the_firebase_cli) the Firebase CLI:

```
npm install -g firebase-tools
```

### Initialize Firebase

[#](#initialize-firebase)

1. Enable the web frameworks preview to the [Firebase framework-aware CLI](https://firebase.google.com/docs/hosting/frameworks/frameworks-overview):

   ```
   firebase experiments:enable webframeworks
   ```

2. In an empty directory or an existing Flutter project, run the initialization command:

   ```
   firebase init hosting
   ```

3. Answer `yes` when asked if you want to use a web framework.

4. If you're in an empty directory, you'll be asked to choose your web framework. Choose `Flutter Web`.

5. Choose your hosting source directory; this could be an existing flutter app.

6. Select a region to host your files.

7. Choose whether to set up automatic builds and deploys with GitHub.

8. Deploy the app to Firebase Hosting:

   ```
   firebase deploy
   ```

   Running this command automatically runs `flutter build web --release`, so you don't have to build your app in a separate step.

To learn more, visit the official [Firebase Hosting](https://firebase.google.com/docs/hosting/frameworks/flutter) documentation for Flutter on the web.

## Handling images on the web

[#](#handling-images-on-the-web)

The web supports the standard `Image` widget to display images. By design, web browsers run untrusted code without harming the host computer. This limits what you can do with images compared to mobile and desktop platforms.

For more information, see [Displaying images on the web](/platform-integration/web/web-images).

## Minification

[#](#minification)

To improve app start-up the compiler reduces the size of the compiled code by removing unused code (known as *tree shaking*), and by renaming code symbols to shorter strings (e.g. by renaming `AlignmentGeometryTween` to something like `ab`). Which of these two optimizations are applied depends on the build mode:

| Type of web app build | Code minified? | Tree shaking performed? |
| --------------------- | -------------- | ----------------------- |
| debug                 | No             | No                      |
| profile               | No             | Yes                     |
| release               | Yes            | Yes                     |

## Embedding a Flutter app into an HTML page

[#](#embedding-a-flutter-app-into-an-html-page)

See [Embedding Flutter web](/platform-integration/web/embedding-flutter-web).

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-07-31. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/deployment/web.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/deployment/web\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/deployment/web.md "Report an issue with this page").

----
url: https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.51.0
----

The 2.51.0 release of the Dart and Flutter DevTools includes the following changes among other general improvements. To learn more about DevTools, check out the [DevTools overview](/tools/devtools/overview).

## General updates

[#](#general-updates)

* Flutter beta channel users were opted into the DevTools-on-Wasm experiment. All other users can still enable the Wasm-compiled DevTools from the settings dialog. - [#9455](https://github.com/flutter/devtools/pull/9455)

## Inspector updates

[#](#inspector-updates)

* Fixed an issue where selecting a widget with the Inspector would open the widget definition file instead of the user's project file. - [#176530](https://github.com/flutter/flutter/pull/176530)

## Full commit history

[#](#full-commit-history)

To find a complete list of changes in this release, check out the [DevTools git log](https://github.com/flutter/devtools/tree/v2.51.0).

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/release-notes/release-notes-2.51.0.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.51.0\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/release-notes/release-notes-2.51.0.md "Report an issue with this page").

----
url: https://docs.flutter.dev/tools
----

[Flutter Property Editor](/tools/property-editor)

[Learn how to use the Flutter Property Editor to view and modify the properties of your widgets.](/tools/property-editor)

[Flutter and Dart DevTools](/tools/devtools)

[How to use Flutter DevTools with Flutter.](/tools/devtools)

[Flutter Widget Previewer](/tools/widget-previewer)

[Learn how to use the Flutter Widget Previewer to see your widgets render in real-time, separate from your full app.](/tools/widget-previewer)

[Code formatting](/tools/formatting)

[Flutter's code formatter formats your code following recommended style guidelines.](/tools/formatting)

[Visual Studio Code](/tools/vs-code)

[How to develop Flutter apps in Visual Studio Code.](/tools/vs-code)

[Android Studio and IntelliJ](/tools/android-studio)

[Learn how to develop Flutter apps in Android Studio and other IntelliJ products.](/tools/android-studio)

[Hot reload](/tools/hot-reload)

[Speed up development using Flutter's hot reload feature.](/tools/hot-reload)

[Flutter editor support](/tools/editors)

[Editor support for Dart and Flutter.](/tools/editors)

[Flutter fix](/tools/flutter-fix)

[Keep your code up to date with the help of the Flutter Fix feature.](/tools/flutter-fix)

[Flutter pubspec options](/tools/pubspec)

[Describes the Flutter-only fields in the pubspec file.](/tools/pubspec)

[Flutter SDK overview](/tools/sdk)

[Flutter libraries and command-line tools.](/tools/sdk)

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/index.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/tools\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/index.md "Report an issue with this page").

----
url: https://docs.flutter.dev/data-and-backend/state-mgmt/ephemeral-vs-app.md
----

\# Differentiate between ephemeral state and app state > How to tell the difference between ephemeral and app state. \_This doc introduces app state, ephemeral state, and how you might manage each in a Flutter app.\_ In the broadest possible sense, the state of an app is everything that exists in memory when the app is running. This includes the app's assets, all the variables that the Flutter framework keeps about the UI, animation state, textures, fonts, and so on. While this broadest possible definition of state is valid, it's not very useful for architecting an app. First, you don't even manage some state (like textures). The framework handles those for you. So a more useful definition of state is "whatever data you need in order to rebuild your UI at any moment in time". Second, the state that you \_do\_ manage yourself can be separated into two conceptual types: ephemeral state and app state. ## Ephemeral state Ephemeral state (sometimes called \_UI state\_ or \_local state\_) is the state you can neatly contain in a single widget. This is, intentionally, a vague definition, so here are a few examples. \* current page in a \[\`PageView\`]\[] \* current progress of a complex animation \* current selected tab in a \`BottomNavigationBar\` Other parts of the widget tree seldom need to access this kind of state. There is no need to serialize it, and it doesn't change in complex ways. In other words, there is no need to use state management techniques (ScopedModel, Redux, etc.) on this kind of state. All you need is a \`StatefulWidget\`. Below, you see how the currently selected item in a bottom navigation bar is held in the \`\_index\` field of the \`\_MyHomepageState\` class. In this example, \`\_index\` is ephemeral state.

<!--?code-excerpt "state_mgmt/simple/lib/src/set_state.dart (ephemeral)" plaster="// ... items ..."?-->

\`\`\`dart class MyHomepage extends StatefulWidget { const MyHomepage({super.key}); @override State createState() => \_MyHomepageState(); } class \_MyHomepageState extends State { int \_index = 0; @override Widget build(BuildContext context) { return BottomNavigationBar( currentIndex: \_index, onTap: (newIndex) { setState(() { \_index = newIndex; }); }, // ... items ... ); } } \`\`\` Here, using \`setState()\` and a field inside the StatefulWidget's State class is completely natural. No other part of your app needs to access \`\_index\`. The variable only changes inside the \`MyHomepage\` widget. And, if the user closes and restarts the app, you don't mind that \`\_index\` resets to zero. ## App state State that is not ephemeral, that you want to share across many parts of your app, and that you want to keep between user sessions, is what we call application state (sometimes also called shared state). Examples of application state: \* User preferences \* Login info \* Notifications in a social networking app \* The shopping cart in an e-commerce app \* Read/unread state of articles in a news app For managing app state, you'll want to research your options. Your choice depends on the complexity and nature of your app, your team's previous experience, and many other aspects. Read on. ## There is no clear-cut rule To be clear, you \_can\_ use \`State\` and \`setState()\` to manage all of the state in your app. In fact, the Flutter team does this in many simple app samples (including the starter app that you get with every \`flutter create\`). It goes the other way, too. For example, you might decide that—in the context of your particular app—the selected tab in a bottom navigation bar is \_not\_ ephemeral state. You might need to change it from outside the class, keep it between sessions, and so on. In that case, the \`\_index\` variable is app state. There is no clear-cut, universal rule to distinguish whether a particular variable is ephemeral or app state. Sometimes, you'll have to refactor one into another. For example, you'll start with some clearly ephemeral state, but as your application grows in features, it might need to be moved to app state. For that reason, take the following diagram with a large grain of salt: When asked about React's setState versus Redux's store, the author of Redux, Dan Abramov, replied: > "The rule of thumb is: \[Do whatever is less awkward]\[]." In summary, there are two conceptual types of state in any Flutter app. Ephemeral state can be implemented using \`State\` and \`setState()\`, and is often local to a single widget. The rest is your app state. Both types have their place in any Flutter app, and the split between the two depends on your own preference and the complexity of the app. \[Do whatever is less awkward]: https\://github.com/reduxjs/redux/issues/1287#issuecomment-175351978 \[\`PageView\`]: https\://api.flutter.dev/flutter/widgets/PageView-class.html

----
url: https://docs.flutter.dev/packages-and-plugins/favorites
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Flutter Favorite program](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

The aim of the **Flutter Favorite** program is to identify packages and plugins that you should first consider when building your app. This is not a guarantee of quality or suitability to your particular situation—you should always perform your own evaluation of packages and plugins for your project.

You can see the complete list of [Flutter Favorite packages](https://pub.dev/flutter/favorites) on pub.dev.

## Metrics

[#](#metrics)

Flutter Favorite packages have passed high quality standards using the following metrics:

* [Overall package score](https://pub.dev/help)
* **Permissive license**, including (but not limited to) Apache, Artistic, BSD, CC BY, MIT, MS-PL and W3C
* GitHub **version tag** matches the current version from pub.dev, so you can see exactly what source is in the package
* Feature **completeness**—and not marked as incomplete (for example, with labels like "beta" or "under construction")
* [Verified publisher](https://dart.dev/tools/pub/verified-publishers)
* General **usability** when it comes to the overview, docs, sample/example code, and API quality
* Good **runtime behavior** in terms of CPU and memory usage
* High quality **dependencies**

## Flutter Ecosystem Committee

[#](#flutter-ecosystem-committee)

The Flutter Ecosystem Committee is comprised of Flutter team members and Flutter community members spread across its ecosystem. One of their jobs is to decide when a package has met the quality bar to become a Flutter Favorite.

The current committee members (ordered alphabetically by last name) are as follows:

* Abdallah Shaban
* Pooja Bhaumik
* Hillel Coren
* Majid Hajian
* Simon Lightfoot
* John Ryan
* Diego Velasquez

If you'd like to nominate a package or plugin as a potential future Flutter Favorite, or would like to bring any other issues to the attention of the committee, [send the committee](mailto:flutter-committee@googlegroups.com) an email.

## Flutter Favorite usage guidelines

[#](#flutter-favorite-usage-guidelines)

Flutter Favorite packages are labeled as such on pub.dev by the Flutter team. If you own a package that has been designated as a Flutter Favorite, you must adhere to the following guidelines:

* Flutter Favorite package authors can place the Flutter Favorite logo in the package's GitHub README, on the package's pub.dev **Overview** tab, and on social media as related to posts about that package.
* We encourage you to use the **#FlutterFavorite** hashtag in social media.
* When using the Flutter Favorite logo, the author must link to (this) Flutter Favorite landing page, to provide context for the designation.
* If a Flutter Favorite package loses its Flutter Favorite status, the author will be notified, at which point the author must immediately remove all uses of "Flutter Favorite" and the Flutter Favorite logo from the affected package.
* Don't alter, distort, or modify the Flutter Favorite logo in any way, including displaying the logo with color variations or unapproved visual elements.
* Don't display the Flutter Favorite logo in a manner that is misleading, unfair, defamatory, infringing, libelous, disparaging, obscene, or otherwise objectionable to Google.

## What's next

[#](#whats-next)

You should expect the list of Flutter Favorite packages to grow and change as the ecosystem continues to thrive. The committee will continue working with package authors to increase quality, as well as consider other areas of the ecosystem that could benefit from the Flutter Favorite program, such as tools, consulting firms, and prolific Flutter contributors.

As the Flutter ecosystem grows, we'll be looking at expanding the set of metrics, which might include the following:

* Use of the [pubspec.yaml format](/packages-and-plugins/developing-packages#plugin-platforms) that clearly indicates which platforms a plugin supports.
* Support for the latest stable version of Flutter.
* Support for AndroidX.
* Support for multiple platforms, such as web, macOS, Windows, Linux, etc.
* Integration as well as unit test coverage.

## Flutter Favorites

[#](#flutter-favorites)

You can see the complete list of [Flutter Favorite packages](https://pub.dev/flutter/favorites) on pub.dev.

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-11. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/packages-and-plugins/favorites.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/packages-and-plugins/favorites\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/packages-and-plugins/favorites.md "Report an issue with this page").

----
url: https://docs.flutter.dev/flutter-for/swiftui-devs
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Flutter for SwiftUI Developers](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

SwiftUI developers who want to write mobile apps using Flutter should review this guide. It explains how to apply existing SwiftUI knowledge to Flutter.

Flutter is a framework for building cross-platform applications that uses the Dart programming language. To understand some differences between programming with Dart and programming with Swift, see [Learning Dart as a Swift Developer](https://dart.dev/guides/language/coming-from/swift-to-dart) and [Flutter concurrency for Swift developers](/flutter-for/dart-swift-concurrency).

Your SwiftUI knowledge and experience are highly valuable when building with Flutter.

Flutter also makes a number of adaptations to app behavior when running on iOS and macOS. To learn how, see [Platform adaptations](/platform-integration/platform-adaptations).

This document can be used as a cookbook by jumping around and finding questions that are most relevant to your needs. This guide embeds sample code.

## Overview

[#](#overview)

As an introduction, watch the following video. It outlines how Flutter works on iOS and how to use Flutter to build iOS apps.

[Watch on YouTube in a new tab: "Flutter for iOS developers"](https://www.youtube.com/watch/ceMsPBbcEGg)

Flutter and SwiftUI code describes how the UI looks and works. Developers call this type of code a *declarative framework*.

### Views vs. Widgets

[#](#views-vs-widgets)

**SwiftUI** represents UI components as *views*. You configure views using *modifiers*.

swift

```
Text("Hello, World!") // <-- This is a View
  .padding(10)        // <-- This is a modifier of that View
```

**Flutter** represents UI components as *widgets*.

Both views and widgets only exist until they need to be changed. These languages call this property *immutability*. SwiftUI represents a UI component property as a View modifier. By contrast, Flutter uses widgets for both UI components and their properties.

dart

```
Padding(                         // <-- This is a Widget
  padding: EdgeInsets.all(10.0), // <-- So is this
  child: Text("Hello, World!"),  // <-- This, too
)
```

To compose layouts, both SwiftUI and Flutter nest UI components within one another. SwiftUI nests Views while Flutter nests Widgets.

### Layout process

[#](#layout-process)

**SwiftUI** lays out views using the following process:

1. The parent view proposes a size to its child view.

2. All subsequent child views:

   * propose a size to *their* child's view
   * ask that child what size it wants

3. Each parent view renders its child view at the returned size.

**Flutter** differs somewhat with its process:

1. The parent widget passes constraints down to its children. Constraints include minimum and maximum values for height and width.

2. The child tries to decide its size. It repeats the same process with its own list of children:

   * It informs its child of the child's constraints.
   * It asks its child what size it wishes to be.

3. The parent lays out the child.

   * If the requested size fits in the constraints, the parent uses that size.
   * If the requested size doesn't fit in the constraints, the parent limits the height, width, or both to fit in its constraints.

Flutter differs from SwiftUI because the parent component can override the child's desired size. The widget cannot have any size it wants. It also cannot know or decide its position on screen as its parent makes that decision.

To force a child widget to render at a specific size, the parent must set tight constraints. A constraint becomes tight when its constraint's minimum size value equals its maximum size value.

In **SwiftUI**, views might expand to the available space or limit their size to that of its content. **Flutter** widgets behave in similar manner.

However, in Flutter parent widgets can offer unbounded constraints. Unbounded constraints set their maximum values to infinity.

dart

```
UnboundedBox(
  child: Container(
      width: double.infinity, height: double.infinity, color: red),
)
```

If the child expands and it has unbounded constraints, Flutter returns an overflow warning:

dart

```
UnconstrainedBox(
  child: Container(color: red, width: 4000, height: 50),
)
```

To learn how constraints work in Flutter, see [Understanding constraints](/ui/layout/constraints).

### Design system

[#](#design-system)

Because Flutter targets multiple platforms, your app doesn't need to conform to any design system. Though this guide features [Material](https://m3.material.io/develop/flutter/) widgets, your Flutter app can use many different design systems:

* Custom Material widgets
* Community built widgets
* Your own custom widgets
* [Cupertino widgets](/ui/widgets/cupertino) that follow Apple's Human Interface Guidelines

[Watch on YouTube in a new tab: "Flutter's cupertino library for iOS developers"](https://www.youtube.com/watch/3PdUaidHc-E)

If you're looking for a great reference app that features a custom design system, check out [Wonderous](https://flutter.gskinner.com/wonderous/?utm_source=flutterdocs\&utm_medium=docs\&utm_campaign=iosdevs).

## UI Basics

[#](#ui-basics)

This section covers the basics of UI development in Flutter and how it compares to SwiftUI. This includes how to start developing your app, display static text, create buttons, react to on-press events, display lists, grids, and more.

### Getting started

[#](#getting-started)

In **SwiftUI**, you use `App` to start your app.

swift

```
@main
struct MyApp: App {
  var body: some Scene {
    WindowGroup {
      HomePage()
    }
  }
}
```

Another common SwiftUI practice places the app body within a `struct` that conforms to the `View` protocol as follows:

swift

```
struct HomePage: View {
  var body: some View {
    Text("Hello, World!")
  }
}
```

To start your **Flutter** app, pass in an instance of your app to the `runApp` function.

dart

```
void main() {
  runApp(const MyApp());
}
```

`App` is a widget. The build method describes the part of the user interface it represents. It's common to begin your app with a [`WidgetApp`](https://api.flutter.dev/flutter/widgets/WidgetsApp-class.html) class, like [`CupertinoApp`](https://pub.dev/documentation/cupertino_ui/latest/cupertino_ui/CupertinoApp-class.html).

dart

```
class MyApp extends StatelessWidget {
  const MyApp({super.key});
​
  @override
  Widget build(BuildContext context) {
    // Returns a CupertinoApp that, by default,
    // has the look and feel of an iOS app.
    return const CupertinoApp(home: HomePage());
  }
}
```

The widget used in `HomePage` might begin with the `Scaffold` class. `Scaffold` implements a basic layout structure for an app.

dart

```
class HomePage extends StatelessWidget {
  const HomePage({super.key});
​
  @override
  Widget build(BuildContext context) {
    return const Scaffold(body: Center(child: Text('Hello, World!')));
  }
}
```

Note how Flutter uses the [`Center`](https://api.flutter.dev/flutter/widgets/Center-class.html) widget. SwiftUI renders a view's contents in its center by default. That's not always the case with Flutter. `Scaffold` doesn't render its `body` widget at the center of the screen. To center the text, wrap it in a `Center` widget. To learn about different widgets and their default behaviors, check out the [Widget catalog](/ui/widgets/layout).

### Adding Buttons

[#](#adding-buttons)

In **SwiftUI**, you use the `Button` struct to create a button.

swift

```
Button("Do something") {
  // this closure gets called when your
  // button is tapped
}
```

To achieve the same result in **Flutter**, use the `CupertinoButton` class:

dart

```
CupertinoButton(
  onPressed: () {
    // This closure is called when your button is tapped.
  },
  const Text('Do something'),
),
```

**Flutter** gives you access to a variety of buttons with predefined styles. The [`CupertinoButton`](https://pub.dev/documentation/cupertino_ui/latest/cupertino_ui/CupertinoButton-class.html) class comes from the Cupertino library. Widgets in the Cupertino library use Apple's design system.

### Aligning components horizontally

[#](#aligning-components-horizontally)

In **SwiftUI**, stack views play a big part in designing your layouts. Two separate structures allow you to create stacks:

1. `HStack` for horizontal stack views

2. `VStack` for vertical stack views

The following SwiftUI view adds a globe image and text to a horizontal stack view:

swift

```
HStack {
  Image(systemName: "globe")
  Text("Hello, world!")
}
```

**Flutter** uses [`Row`](https://api.flutter.dev/flutter/widgets/Row-class.html) rather than `HStack`:

dart

```
Row(
  mainAxisAlignment: MainAxisAlignment.center,
  children: [Icon(CupertinoIcons.globe), Text('Hello, world!')],
),
```

The `Row` widget requires a `List<Widget>` in the `children` parameter. The `mainAxisAlignment` property tells Flutter how to position children with extra space. `MainAxisAlignment.center` positions children in the center of the main axis. For `Row`, the main axis is the horizontal axis.

### Aligning components vertically

[#](#aligning-components-vertically)

The following examples build on those in the previous section.

In **SwiftUI**, you use `VStack` to arrange the components into a vertical pillar.

swift

```
VStack {
  Image(systemName: "globe")
  Text("Hello, world!")
}
```

**Flutter** uses the same Dart code from the previous example, except it swaps [`Column`](https://api.flutter.dev/flutter/widgets/Column-class.html) for `Row`:

dart

```
Column(
  mainAxisAlignment: MainAxisAlignment.center,
  children: [Icon(CupertinoIcons.globe), Text('Hello, world!')],
),
```

### Displaying a list view

[#](#displaying-a-list-view)

In **SwiftUI**, you use the `List` base component to display sequences of items. To display a sequence of model objects, make sure that the user can identify your model objects. To make an object identifiable, use the `Identifiable` protocol.

swift

```
struct Person: Identifiable {
  var name: String
}
​
var persons = [
  Person(name: "Person 1"),
  Person(name: "Person 2"),
  Person(name: "Person 3"),
]
​
struct ListWithPersons: View {
  let persons: [Person]
  var body: some View {
    List {
      ForEach(persons) { person in
        Text(person.name)
      }
    }
  }
}
```

This resembles how **Flutter** prefers to build its list widgets. Flutter doesn't need the list items to be identifiable. You set the number of items to display then build a widget for each item.

dart

```
class Person {
  String name;
  Person(this.name);
}
​
final List<Person> items = [
  Person('Person 1'),
  Person('Person 2'),
  Person('Person 3'),
];
​
class HomePage extends StatelessWidget {
  const HomePage({super.key});
​
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: ListView.builder(
        itemCount: items.length,
        itemBuilder: (context, index) {
          return ListTile(title: Text(items[index].name));
        },
      ),
    );
  }
}
```

Flutter has some caveats for lists:

* The [`ListView`](https://api.flutter.dev/flutter/widgets/ListView-class.html) widget has a builder method. This works like the `ForEach` within SwiftUI's `List` struct.

* The `itemCount` parameter of the `ListView` sets how many items the `ListView` displays.

* The `itemBuilder` has an index parameter that will be between zero and one less than itemCount.

The previous example returned a [`ListTile`](https://api.flutter.dev/flutter/widgets/ListTitle-class.html) widget for each item. The `ListTile` widget includes properties like `height` and `font-size`. These properties help build a list. However, Flutter allows you to return almost any widget that represents your data.

### Displaying a grid

[#](#displaying-a-grid)

When constructing non-conditional grids in **SwiftUI**, you use `Grid` with `GridRow`.

swift

```
Grid {
  GridRow {
    Text("Row 1")
    Image(systemName: "square.and.arrow.down")
    Image(systemName: "square.and.arrow.up")
  }
  GridRow {
    Text("Row 2")
    Image(systemName: "square.and.arrow.down")
    Image(systemName: "square.and.arrow.up")
  }
}
```

To display grids in **Flutter**, use the [`GridView`](https://api.flutter.dev/flutter/widgets/GridView-class.html) widget. This widget has various constructors. Each constructor has a similar goal, but uses different input parameters. The following example uses the `.builder()` initializer:

dart

```
const widgets = <Widget>[
  Text('Row 1'),
  Icon(CupertinoIcons.arrow_down_square),
  Icon(CupertinoIcons.arrow_up_square),
  Text('Row 2'),
  Icon(CupertinoIcons.arrow_down_square),
  Icon(CupertinoIcons.arrow_up_square),
];
​
class HomePage extends StatelessWidget {
  const HomePage({super.key});
​
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: GridView.builder(
        gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
          crossAxisCount: 3,
          mainAxisExtent: 40,
        ),
        itemCount: widgets.length,
        itemBuilder: (context, index) => widgets[index],
      ),
    );
  }
}
```

The `SliverGridDelegateWithFixedCrossAxisCount` delegate determines various parameters that the grid uses to lay out its components. This includes `crossAxisCount` that dictates the number of items displayed on each row.

How SwiftUI's `Grid` and Flutter's `GridView` differ in that `Grid` requires `GridRow`. `GridView` uses the delegate to decide how the grid should lay out its components.

### Creating a scroll view

[#](#creating-a-scroll-view)

In **SwiftUI**, you use `ScrollView` to create custom scrolling components. The following example displays a series of `PersonView` instances in a scrollable fashion.

swift

```
ScrollView {
  VStack(alignment: .leading) {
    ForEach(persons) { person in
      PersonView(person: person)
    }
  }
}
```

To create a scrolling view, **Flutter** uses [`SingleChildScrollView`](https://api.flutter.dev/flutter/widgets/SingleChildScrollView-class.html). In the following example, the function `mockPerson` mocks instances of the `Person` class to create the custom `PersonView` widget.

dart

```
SingleChildScrollView(
  child: Column(
    children: mockPersons
        .map((person) => PersonView(person: person))
        .toList(),
  ),
),
```

### Responsive and adaptive design

[#](#responsive-and-adaptive-design)

In **SwiftUI**, you use `GeometryReader` to create relative view sizes.

For example, you could:

* Multiply `geometry.size.width` by some factor to set the *width*.
* Use `GeometryReader` as a breakpoint to change the design of your app.

You can also see if the size class has `.regular` or `.compact` using `horizontalSizeClass`.

To create relative views in **Flutter**, you can use one of two options:

* Get the `BoxConstraints` object in the [`LayoutBuilder`](https://api.flutter.dev/flutter/widgets/LayoutBuilder-class.html) class.
* Use the [`MediaQuery.of()`](https://api.flutter.dev/flutter/widgets/MediaQuery-class.html) in your build functions to get the size and orientation of your current app.

To learn more, check out [Creating responsive and adaptive apps](/ui/adaptive-responsive).

### Managing state

[#](#managing-state)

In **SwiftUI**, you use the `@State` property wrapper to represent the internal state of a SwiftUI view.

swift

```
struct ContentView: View {
  @State private var counter = 0;
  var body: some View {
    VStack{
      Button("+") { counter+=1 }
      Text(String(counter))
    }
  }}
```

**SwiftUI** also includes several options for more complex state management such as the `ObservableObject` protocol.

**Flutter** manages local state using a [`StatefulWidget`](https://api.flutter.dev/flutter/widgets/StatefulWidget-class.html). Implement a stateful widget with the following two classes:

* a subclass of `StatefulWidget`
* a subclass of `State`

The `State` object stores the widget's state. To change a widget's state, call `setState()` from the `State` subclass to tell the framework to redraw the widget.

The following example shows a part of a counter app:

dart

```
class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key});
  @override
  State<MyHomePage> createState() => _MyHomePageState();
}
​
class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;
​
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text('$_counter'),
            TextButton(
              onPressed: () => setState(() {
                _counter++;
              }),
              child: const Text('+'),
            ),
          ],
        ),
      ),
    );
  }
}
```

To learn more ways to manage state, check out [State management](/data-and-backend/state-mgmt).

### Animations

[#](#animations)

Two main types of UI animations exist.

* Implicit that animated from a current value to a new target.
* Explicit that animates when asked.

#### Implicit Animation

[#](#implicit-animation)

SwiftUI and Flutter take a similar approach to animation. In both frameworks, you specify parameters like `duration`, and `curve`.

In **SwiftUI**, you use the `animate()` modifier to handle implicit animation.

swift

```
Button("Tap me!"){
   angle += 45
}
.rotationEffect(.degrees(angle))
.animation(.easeIn(duration: 1))
```

**Flutter** includes widgets for implicit animation. This simplifies animating common widgets. Flutter names these widgets with the following format: `AnimatedFoo`.

For example: To rotate a button, use the [`AnimatedRotation`](https://api.flutter.dev/flutter/widgets/AnimatedRotation-class.html) class. This animates the `Transform.rotate` widget.

dart

```
AnimatedRotation(
  duration: const Duration(seconds: 1),
  turns: turns,
  curve: Curves.easeIn,
  TextButton(
    onPressed: () {
      setState(() {
        turns += .125;
      });
    },
    const Text('Tap me!'),
  ),
),
```

Flutter allows you to create custom implicit animations. To compose a new animated widget, use the [`TweenAnimationBuilder`](https://api.flutter.dev/flutter/widgets/TweenAnimationBuilder-class.html).

#### Explicit Animation

[#](#explicit-animation)

For explicit animations, **SwiftUI** uses the `withAnimation()` function.

**Flutter** includes explicitly animated widgets with names formatted like `FooTransition`. One example would be the [`RotationTransition`](https://api.flutter.dev/flutter/widgets/RotationTransition-class.html) class.

Flutter also allows you to create a custom explicit animation using `AnimatedWidget` or `AnimatedBuilder`.

To learn more about animations in Flutter, see [Animations overview](/ui/animations).

### Drawing on the Screen

[#](#drawing-on-the-screen)

In **SwiftUI**, you use `CoreGraphics` to draw lines and shapes to the screen.

**Flutter** has an API based on the `Canvas` class, with two classes that help you draw:

1. [`CustomPaint`](https://api.flutter.dev/flutter/widgets/CustomPaint-class.html) that requires a painter:

   dart

   ```
   CustomPaint(
     painter: SignaturePainter(_points),
     size: Size.infinite,
   ),
   ```

2. [`CustomPainter`](https://api.flutter.dev/flutter/rendering/CustomPainter-class.html) that implements your algorithm to draw to the canvas.

   dart

   ```
   class SignaturePainter extends CustomPainter {
     SignaturePainter(this.points);
   ​
     final List<Offset?> points;
   ​
     @override
     void paint(Canvas canvas, Size size) {
       final Paint paint = Paint()
         ..color = Colors.black
         ..strokeCap = StrokeCap.round
         ..strokeWidth = 5;
       for (int i = 0; i < points.length - 1; i++) {
         if (points[i] != null && points[i + 1] != null) {
           canvas.drawLine(points[i]!, points[i + 1]!, paint);
         }
       }
     }
   ​
     @override
     bool shouldRepaint(SignaturePainter oldDelegate) =>
         oldDelegate.points != points;
   }
   ```

[#](#navigation)

This section explains how to navigate between pages of an app, the push and pop mechanism, and more.

### Navigating between pages

[#](#navigating-between-pages)

Developers build iOS and macOS apps with different pages called *navigation routes*.

In **SwiftUI**, the `NavigationStack` represents this stack of pages.

The following example creates an app that displays a list of persons. To display a person's details in a new navigation link, tap on that person.

swift

```
NavigationStack(path: $path) {
  List {
    ForEach(persons) { person in
      NavigationLink(
        person.name,
        value: person
      )
    }
  }
  .navigationDestination(for: Person.self) { person in
    PersonView(person: person)
  }
}
```

If you have a small **Flutter** app without complex linking, use [`Navigator`](https://api.flutter.dev/flutter/widgets/Navigator-class.html) with named routes. After defining your navigation routes, call your navigation routes using their names.

1. Name each route in the class passed to the `runApp()` function. The following example uses `App`:

   dart

   ```
   // Defines the route name as a constant
   // so that it's reusable.
   const detailsPageRouteName = '/details';
   ​
   class App extends StatelessWidget {
     const App({super.key});
   ​
     @override
     Widget build(BuildContext context) {
       return CupertinoApp(
         home: const HomePage(),
         // The [routes] property defines the available named routes
         // and the widgets to build when navigating to those routes.
         routes: {detailsPageRouteName: (context) => const DetailsPage()},
       );
     }
   }
   ```

   The following sample generates a list of persons using `mockPersons()`. Tapping a person pushes the person's detail page to the `Navigator` using `pushNamed()`.

   dart

   ```
   ListView.builder(
     itemCount: mockPersons.length,
     itemBuilder: (context, index) {
       final person = mockPersons.elementAt(index);
       final age = '${person.age} years old';
       return ListTile(
         title: Text(person.name),
         subtitle: Text(age),
         trailing: const Icon(Icons.arrow_forward_ios),
         onTap: () {
           // When a [ListTile] that represents a person is
           // tapped, push the detailsPageRouteName route
           // to the Navigator and pass the person's instance
           // to the route.
           Navigator.of(context)
               .pushNamed(detailsPageRouteName, arguments: person);
         },
       );
     },
   ),
   ```

2. Define the `DetailsPage` widget that displays the details of each person. In Flutter, you can pass arguments into the widget when navigating to the new route. Extract the arguments using `ModalRoute.of()`:

   dart

   ```
   class DetailsPage extends StatelessWidget {
     const DetailsPage({super.key});
   ​
     @override
     Widget build(BuildContext context) {
       // Read the person instance from the arguments.
       final Person person = ModalRoute.of(context)?.settings.arguments as Person;
       // Extract the age.
       final age = '${person.age} years old';
       return Scaffold(
         // Display name and age.
         body: Column(children: [Text(person.name), Text(age)]),
       );
     }
   }
   ```

To create more advanced navigation and routing requirements, use a routing package such as [go\_router](https://pub.dev/packages/go_router).

To learn more, check out [Navigation and routing](/ui/navigation).

### Manually pop back

[#](#manually-pop-back)

In **SwiftUI**, you use the `dismiss` environment value to pop-back to the previous screen.

swift

```
Button("Pop back") {
  dismiss()
}
```

In **Flutter**, use the `pop()` function of the `Navigator` class:

dart

```
TextButton(
  onPressed: () {
    // This code allows the
    // view to pop back to its presenter.
    Navigator.of(context).pop();
  },
  child: const Text('Pop back'),
),
```

### Navigating to another app

[#](#navigating-to-another-app)

In **SwiftUI**, you use the `openURL` environment variable to open a URL to another application.

swift

```
@Environment(\.openURL) private var openUrl
​
// View code goes here
​
Button("Open website") {
  openUrl(
    URL(
      string: "https://google.com"
    )!
  )
}
```

In **Flutter**, use the [`url_launcher`](https://pub.dev/packages/url_launcher) plugin.

dart

```
CupertinoButton(
  onPressed: () async {
    await launchUrl(Uri.parse('https://google.com'));
  },
  const Text('Open website'),
),
```

## Themes, styles, and media

[#](#themes-styles-and-media)

You can style Flutter apps with little effort. Styling includes switching between light and dark themes, changing the design of your text and UI components, and more. This section covers how to style your apps.

### Using dark mode

[#](#using-dark-mode)

In **SwiftUI**, you call the `preferredColorScheme()` function on a `View` to use dark mode.

In **Flutter**, you can control light and dark mode at the app-level. To control the brightness mode, use the `theme` property of the `App` class:

dart

```
const CupertinoApp(
  theme: CupertinoThemeData(brightness: Brightness.dark),
  home: HomePage(),
);
```

### Styling text

[#](#styling-text)

In **SwiftUI**, you use modifier functions to style text. For example, to change the font of a `Text` string, use the `font()` modifier:

swift

```
Text("Hello, world!")
  .font(.system(size: 30, weight: .heavy))
  .foregroundColor(.yellow)
```

To style text in **Flutter**, add a `TextStyle` widget as the value of the `style` parameter of the `Text` widget.

dart

```
Text(
  'Hello, world!',
  style: TextStyle(
    fontSize: 30,
    fontWeight: FontWeight.bold,
    color: CupertinoColors.systemYellow,
  ),
),
```

### Styling buttons

[#](#styling-buttons)

In **SwiftUI**, you use modifier functions to style buttons.

swift

```
Button("Do something") {
  // Do something when the button is tapped.
}
.font(.system(size: 30, weight: .bold))
.background(Color.yellow)
.foregroundColor(Color.blue)
```

To style button widgets in **Flutter**, set the style of its child, or modify properties on the button itself.

In the following example:

* The `color` property of `CupertinoButton` sets its `color`.
* The `color` property of the child `Text` widget sets the button text color.

dart

```
child: CupertinoButton(
  color: CupertinoColors.systemYellow,
  onPressed: () {},
  child: const Text(
    'Do something',
    style: TextStyle(
      color: CupertinoColors.systemBlue,
      fontSize: 30,
      fontWeight: FontWeight.bold,
    ),
  ),
),
```

### Using custom fonts

[#](#using-custom-fonts)

In **SwiftUI**, you can use a custom font in your app in two steps. First, add the font file to your SwiftUI project. After adding the file, use the `.font()` modifier to apply it to your UI components.

swift

```
Text("Hello")
  .font(
    Font.custom(
      "BungeeSpice-Regular",
      size: 40
    )
  )
```

In **Flutter**, you control your resources with a file named `pubspec.yaml`. This file is platform agnostic. To add a custom font to your project, follow these steps:

1. Create a folder called `fonts` in the project's root directory. This optional step helps to organize your fonts.

2. Add your `.ttf`, `.otf`, or `.ttc` font file into the `fonts` folder.

3. Open the `pubspec.yaml` file within the project.

4. Find the `flutter` section.

5. Add your custom font(s) under the `fonts` section.

   yaml

   ```
   flutter:
     fonts:
       - family: BungeeSpice
         fonts:
           - asset: fonts/BungeeSpice-Regular.ttf
   ```

After you add the font to your project, you can use it as in the following example:

dart

```
Text(
  'Cupertino',
  style: TextStyle(fontSize: 40, fontFamily: 'BungeeSpice'),
),
```

### Bundling images in apps

[#](#bundling-images-in-apps)

In **SwiftUI**, you first add the image files to `Assets.xcassets`, then use the `Image` view to display the images.

To add images in **Flutter**, follow a method similar to how you added custom fonts.

1. Add an `images` folder to the root directory.

2. Add this asset to the `pubspec.yaml` file.

   yaml

   ```
   flutter:
     assets:
       - images/Blueberries.jpg
   ```

After adding your image, display it using the `Image` widget's `.asset()` constructor. This constructor:

1. Instantiates the given image using the provided path.
2. Reads the image from the assets bundled with your app.
3. Displays the image on the screen.

To review a complete example, check out the [`Image`](https://api.flutter.dev/flutter/widgets/Image-class.html) docs.

### Bundling videos in apps

[#](#bundling-videos-in-apps)

In **SwiftUI**, you bundle a local video file with your app in two steps. First, you import the `AVKit` framework, then you instantiate a `VideoPlayer` view.

In **Flutter**, add the [video\_player](https://pub.dev/packages/video_player) plugin to your project. This plugin allows you to create a video player that works on Android, iOS, and on the web from the same codebase.

1. Add the plugin to your app and add the video file to your project.
2. Add the asset to your `pubspec.yaml` file.
3. Use the `VideoPlayerController` class to load and play your video file.

To review a complete walkthrough, check out the [video\_player example](https://pub.dev/packages/video_player/example).

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-09-29. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/flutter-for/swiftui-devs.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/flutter-for/swiftui-devs\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/flutter-for/swiftui-devs.md "Report an issue with this page").

----
url: https://docs.flutter.dev/deployment/flavors-ios.md
----

\# Set up Flutter flavors for iOS and macOS > How to create Flutter flavors for an iOS or macOS app. This guide shows you how to create Flutter flavors for an iOS or macOS app. ## Overview A Flutter flavor is basically a collection of settings that define how a specific version of your app should be built and run. For example, a flavor could determine which icon, app name, API key, feature flag, and logging level is associated with a specific version of your app. If you want to create Flutter flavors for an iOS app, you'll need to do so in Xcode. Xcode does not have a concept called "flavor". Instead, you'll need to set up something called a scheme and attach custom configurations to it. The following illustrates an example of two Flutter flavors (staging, production) as Xcode schemes with custom Xcode configurations assigned to them: | Scheme | Configurations for the scheme| | --- | --- | | staging | Debug-staging\
Profile-staging\
Release-staging | | production | Debug-production\
Profile-production\
Releae-production | {:.table .table-striped} ## Configure Xcode schemes The following steps show how to configure two Xcode schemes called \`staging\` and \`production\` for your Flutter iOS project. You can also use these steps to set up a macOS project by replacing any reference to \`iOS\` with \`macOS\`. For a seamless workflow, we've started with a new Flutter project called \`flavors\_example\`, but you can always start with an existing project. 1. Create a new Flutter project called \`flavors\_example\`: \`\`\`console title="console" $ flutter create flavors\_example \`\`\` 1. Open the default Xcode workspace for the iOS version of the \`flavors\_example\` project: \`\`\`console title="console" $ cd flavors\_example && open ios/Runner.xcworkspace \`\`\` 1. Open the \`flavors\_example\` project in the Xcode project navigator: \* Open the \*\*project navigator\*\* (\*\*View\*\* > \*\*Navigators\*\* > \*\*Project\*\*). \* In the \*\*project navigator\*\*, at the top, select \*\*Runner\*\*. 1. Create schemes in Xcode: \* Open the \*\*New Scheme\*\* window (\*\*Product > Scheme > New Scheme\*\*). \* In the \*\*Target\*\* field, select \*\*Runner\*\*. \* In the \*\*Name\*\* box, enter \`staging\`. \* Click \*\*Okay\*\* to add the new scheme. \* Repeat the previous steps for a scheme called \`production\`. \* When finished, check to make sure that you have the following schemes: !\[Schemes for Flutter flavors]\(/assets/images/docs/flavors/flavors-ios-schemes.png){:width="100%"} :::note By default the new schemes are shared. The schemes must be shared for Flutter flavors to work properly. To double-check that sharing is enabled, open the \*\*Manage Schemes\*\* window (\*\*Product > Scheme > Manage Schemes\*\*) and make sure that the \*\*Shared\*\* checkbox to the right of your new scheme is checked. ::: 1. Create configurations for the schemes in Xcode: \* In the \*\*project navigator\*\*, select \*\*Runner\*\*. \* In the main window under \*\*PROJECT\*\*, select \*\*Runner\*\*. \* Open the \*\*Info tab\*\* if it isn’t open. \* Go to the \*\*Configurations\*\* section and add new \`Debug\` configurations. Always use lowercase for the appended flavor name (such as \`staging\` or \`production\`) so the Flutter CLI recognizes it: \* Click \*\*+\*\*, select \*\*Duplicate "Debug" configuration\*\*, and name the new configuration \`Debug-staging\`. \* Click \*\*+\*\*, select \*\*Duplicate "Debug" configuration\*\*, and name the new configuration \`Debug-production\`. \* Repeat the previous step for the \`Release\` configurations and the \`Profile\` configurations. \* When finished, check to make sure that you have the following configurations: !\[Scheme configurations for Flutter flavors]\(/assets/images/docs/flavors/flavors-ios-scheme-configurations.png){:width="100%"} Your configurations should be based on \`Debug.xcconfig\` and \`Release.xcconfig\` (not \`Pods-Runner.xcconfig\`). By default, both Profile and Release configurations use \`Release.xcconfig\`. You can check this by expanding the configuration names in Xcode. 1. Assign the configurations to the schemes in Xcode: \* Open the \*\*Manage Schemes\*\* window (\*\*Product > Scheme > Manage Schemes\*\*). \* Select the \`staging\` scheme and edit it. \* In the following tabs, update the \*\*Build Configuration\*\* field as follows: \* \*\*Run\*\*: \`Debug-staging\` \* \*\*Test\*\*: \`Debug-staging\` \* \*\*Profile\*\*: \`Profile-staging\` \* \*\*Analyze\*\*: \`Debug-staging\` \* \*\*Archive\*\*: \`Release-staging\` \* Click \*\*Close\*\*. \* Repeat the previous steps for the \`production\` scheme. 1. If you are working with a pre-existing Flutter project that has at least one Podfile, update it. For details, refer to \[Update Podfiles]\[]. 1. To make sure that you've set up everything correctly, run your app on the new schemes in Xcode. The app won't have any visual differences yet because the configuration settings haven't changed, but you should verify that it runs without errors. \* Select the \`staging\` scheme (\*\*Product > Schemes > staging\*\*). \* To the right of \`staging\` in the toolbar, select the iOS device you want to test against. In the following example, the device is \`iPhone 16 Pro\`. !\[Run a Flutter flavor]\(/assets/images/docs/flavors/flavors-ios-test-scheme.png){:width="100%"} \* Run the app scheme (\*\*Product > Run\*\*). \* Repeat the previous steps for the \`production\` scheme. 1. If everything runs, you're ready to customize your configurations. For details, refer to \[Customize configurations]\[]. \[Update Podfiles]: #update-podfiles \[Customize configurations]: #customize-configurations ## Launch an Xcode scheme After you've created the schemes for an iOS app in Xcode, you can launch a specific scheme through Xcode or Flutter. You can also use these steps to launch a macOS project by replacing any reference to \`iOS\` with \`macOS\`. ### Use the flavor flag (Flutter CLI) You can launch an Xcode scheme in \`Debug\` mode with the Flutter CLI using the following steps: 1. In your IDE, start the iOS simulator. 1. In the console, navigate to the \`flavors\_example\` directory and enter the following command: \`\`\`console title="console" $ flutter run --flavor \`\`\` \* \`\`: Replace this with the name of your Xcode scheme (for example, \`staging\` or \`production\`). Example: \`\`\`console title="console" $ flutter run --flavor staging \`\`\` ### Access the current flavor 1. \*\*Import the services library.\*\* To access the \`appFlavor\` constant, add the following import to your Dart file: \`\`\`dart import 'package:flutter/services.dart'; \`\`\` 1. \*\*Check the flavor value.\*\* Use the \`appFlavor\` constant in your application logic (often in \`main()\`) to handle flavor-specific configurations: \`\`\`dart void main() { // appFlavor will match the name of the Xcode scheme if (appFlavor == 'production') { // Logic for production environment Config.apiUrl = 'https\://api.flavors\_example.com'; } else if (appFlavor == 'staging') { // Logic for staging environment Config.apiUrl = 'https\://staging.api.flavors\_example.com'; } runApp(const MyApp()); } \`\`\` :::note The value of \`appFlavor\` matches the name of the Xcode scheme you defined (for example, \`staging\` or \`production\`). If no flavor is specified during the build, \`appFlavor\` returns \`null\`. ::: ### Use the run command (Xcode) You can launch a specific scheme in Xcode using the following steps: 1. Select the scheme you want to test (\*\*Product > Schemes > Choose scheme\*\*). 1. Next to the scheme name in the toolbar, select the device you want to test against. 1. Run the scheme for your app (\*\*Product > Run\*\*). ## Customize configurations After you've added Xcode schemes, you can customize them for your iOS app. You can also use these steps to configure a macOS project by replacing any reference to \`iOS\` with \`macOS\`. ### Create distinct app display names {: #create\_a\_distinct\_app\_display\_name } If you have multiple schemes, a distinct app name can quickly identify which scheme your deployed app is using. The following steps show how to add distinct app display names in Xcode for two schemes called \`staging\` and \`production\` in a project called \`flavors\_example\`. 1. Create user-defined settings in Xcode: \* Open the \*\*project navigator\*\* (\*\*View > Navigators > Project\*\*). \* In the \*\*project navigator\*\*, at the top, select \*\*Runner\*\*. \* In the main window under \*\*TARGETS\*\*, select \*\*Runner\*\*. \* Open the \*\*Build Settings\*\* tab. \* To the left of the Basic tab, click \*\*+\*\* and select \*\*Add User-Defined Setting\*\*. \* Create a setting named \`APP\_DISPLAY\_NAME\`. \* Expand the \*\*APP\_DISPLAY\_NAME\*\* setting. \* Assign the following values to the following keys: \* \*\*Debug-production\*\*: \`Flavors prod\` \* \*\*Debug-staging\*\*: \`Flavors staging\` \* \*\*Profile-production\*\*: \`Flavors prod\` \* \*\*Profile-staging\*\*: \`Flavors staging\` \* \*\*Release-production\*\*: \`Flavors prod\` \* \*\*Release-staging\*\*: \`Flavors staging\` 1. Update \`Info.plist\` in Xcode: \* In the project navigator, select \*\*Runner > Runner > Info\*\* to open \`flavor\_test/ios/Runner/Info.plist\`. \* Under \*\*Information Property List\*\*, find the following key and update the value for it: \* \*\*Key\*\*: \`CFBundleDisplayName\` \* \*\*Value\*\*: \`$(APP\_DISPLAY\_NAME)\` 1. Launch the app for each scheme (\`staging\`, \`production\`) and check to make sure that the app display name has changed for each. To learn how to launch a scheme, refer to \[Launch an Xcode scheme]\[]. \[Launch an Xcode scheme]: #launch-an-xcode-scheme ### Create distinct icons If you have multiple schemes, a distinct icon for each configuration can help you quickly identify which scheme your deployed app is using. The following steps show how to add a distinct icon in Xcode for two schemes called \`staging\` and \`production\` in an iOS project called \`flavors\_example\`. 1. Prepare your icons: \* Design your staging icon and production icon in the design tool of your choice. \* Generate versions of the staging icon and production icon in the sizes that you need. Save them in PNG format. :::note You can use a tool like \[App Icon Generator]\[] to generate the versions of your icons. ::: 1. Add the icons to your Xcode project: \* Open the \*\*project navigator\*\* (\*\*View > Navigators > Project\*\*). \* In the \*\*project navigator\*\*, select \*\*Runner > Runner > Assets\*\* to open the \*\*Assets\*\* window. \* Complete the following steps for the staging icon: \* Click \*\*+ > iOS > iOS App icon\*\*. \* Name the icon \`AppIcon-staging\`. \* Drag your staging icons into the \*\*AppIcon-staging\*\* window and make sure the icons are assigned to the correct sizes. \* Repeat the previous step for the production icon. 1. Connect the icons to your schemes: \* Open the \*\*project navigator\*\*. \* In the main window under \*\*TARGETS\*\*, select \*\*Runner\*\*. \* Open the \*\*General\*\* tab if it's not already open. \* Go to the \*\*Apps Icons and Launch Screen\*\* section and expand it. \* To the right of the \*\*App icon\*\* field, click \*\*+\*\* and update the fields as follows: \* \*\*Debug-staging\*\*: \`AppIcon-staging\` \* \*\*Profile-staging\*\*: \`AppIcon-staging\` \* \*\*Release-staging\*\*: \`AppIcon-staging\` \* \*\*Debug-production\*\*: \`AppIcon-production\` \* \*\*Profile-production\*\*: \`AppIcon-production\` \* \*\*Release-production\*\*: \`AppIcon-production\` 1. Launch the app for each scheme (\`staging\`, \`production\`) and check to make sure that the app icon has changed for each. To learn how to launch a scheme, refer to \[Launch an Xcode scheme]\[]. \[Launch an Xcode scheme]: #launch-an-xcode-scheme \[App Icon Generator]: https\://www\.appicon.co/ ### Add distinct bundle identifiers A bundle identifier is a unique identifier for your application on Apple's platforms. If you are using multiple Xcode schemes as Flutter flavors, you can have Apple treat each scheme as a separate application. To do this, you need to assign a different bundle identifier to each scheme. This allows you to test new features or bug fixes in one version of the app (for example \`staging\`) without affecting another version of the app (for example, \`production\`). The following steps show how to set a unique bundle identifier for two Xcode schemes called \`staging\` and \`production\` in an iOS project called \`flavors\_example\`. 1. In Xcode, open the \*\*project navigator\*\* (\*\*View > Navigators > Project\*\*). 1. In the main window under \*\*TARGETS\*\*, select \*\*Runner\*\*. 1. Open the \*\*Build Settings\*\* tab. 1. Navigate to the \*\*Packaging\*\* section. 1. Expand the \*\*Product Bundle Identifier\*\* setting to view the different build configurations. 1. For each scheme's build configuration, set the desired bundle identifier. For example: \* Debug-staging, Profile-staging, Release-staging: \`com.example.flavorsExample.staging\` \* Debug, Profile, Release, Debug-production, Profile-production, Release-production: \`com.example.flavorsExample\` 1. Ensure that these bundle identifiers are included in your App ID and your App ID is \[registered in your Apple Developer account]\[]. \[registered in your Apple Developer account]: https\://developer.apple.com/help/account/identifiers/register-an-app-id/ ### Bundle assets If you have assets that are only used in a specific flavor in your app, you can configure them to only be bundled into your app when launching that flavor. This prevents your app bundle size from being bloated by unused assets. To bundle assets for each flavor, add the \`flavors\` subfield to the \`assets\` field in your project's pubspec. For details, refer to the \[\`assets\` field]\[] in \[Flutter pubspec options]\[]. \[\`assets\` field]: /tools/pubspec#assets \[Flutter pubspec options]: /tools/pubspec ### Update Podfiles If you are creating new Xcode schemes for a Flutter iOS project and you have an iOS Podfile in an existing Flutter project, you must update the Flutter iOS Podfile to match the changes you made in Xcode. The following steps show how to update your iOS Podfile to include two new Xcode schemes called \`staging\` and \`production\` in a Flutter project called \`flavors\_example\`. You can also use these steps to update a macOS project by replacing any reference to \`iOS\` with \`macOS\`. 1. In your IDE, open the \`ios/Podfile\` file. 1. Make the following updates and save your changes: \`\`\`ruby title="flavors\_example/ios/Podfile" project 'Runner', { ... 'Debug' => :debug, 'Debug-staging' => :debug, 'Debug-production' => :debug, 'Profile' => :release, 'Profile-staging' => :release, 'Profile-production' => :release, 'Release' => :release, 'Release-staging' => :release, 'Release-production' => :release, ... \`\`\` ### Add unique build settings You can use \[build settings]\[] to govern your iOS build process from compilation and linking to debugging and distribution. One way that you can use build settings with Flutter flavors is to assign those build settings to Xcode build configurations. For example, you might want to assign different API URLs to \`Debug-staging\` and \`Debug-production\`. The following configurations show example build settings: \`\`\`plaintext title="debug-staging-settings.xcconfig" # Debug-staging build settings API\_BASE\_URL = staging.flavors.com/api \`\`\` \`\`\`plaintext title="debug-production-settings.xcconfig" # Debug-production build settings API\_BASE\_URL = flavors.com/api \`\`\` To learn how to add additional build settings for a specific build configuration, consult Apple's \[Adding a build configuration file to your project]\[]. \[build settings]: https\://developer.apple.com/documentation/xcode/build-settings-reference/ \[Adding a build configuration file to your project]: https\://developer.apple.com/documentation/xcode/adding-a-build-configuration-file-to-your-project ### Add additional customizations This document contains a few common Xcode scheme configurations, but there are many more that you can apply. To learn about them, consult \[Customizing the build schemes for a project]\[]. \[Customizing the build schemes for a project]: https\://developer.apple.com/documentation/xcode/customizing-the-build-schemes-for-a-project ## More information To learn about creating and using flavors, consult the following resources: \* \[Set up Flutter flavors for Android]\[] \* \[Set up Flutter flavors for Linux]\[] \* \[Set up Flutter flavors for Windows]\[] \* \[How to set up Flutter & Firebase with Multiple Flavors using the FlutterFire CLI]\[flutterfire-cli] \* \[Build flavors in Flutter (Android and iOS) with different Firebase projects per flavor Flutter Ready to Go]\[flavors-firebase] \[Set up Flutter flavors for Android]: /deployment/flavors \[Set up Flutter flavors for Linux]: /deployment/flavors-linux \[Set up Flutter flavors for Windows]: /deployment/flavors-windows \[flutterfire-cli]: https\://codewithandrea.com/articles/flutter-firebase-multiple-flavors-flutterfire-cli/ \[flavors-firebase]: https\://medium.com/@animeshjain/build-flavors-in-flutter-android-and-ios-with-different-firebase-projects-per-flavor-27c5c5dac10b

----
url: https://docs.flutter.dev/ai/genui.md
----

\# GenUI SDK for Flutter > Learn how to use GenUI SDK for Flutter to build more interactive experiences for applications and chatbots. ## What is GenUI? At its core, the GenUI SDK for Flutter is an orchestration layer. This suite of packages coordinates the flow of information between your user, your Flutter widgets, and an AI agent, transforming text-based conversations into rich, interactive experiences. Imagine that, instead of presenting your user with a wall of text, they are presented with a graphical UI consisting of (for example), a row of labeled buttons and a date picker. The GenUI SDK for Flutter uses a JSON-based format to compose a UI from your existing widget catalog. As a user interacts with the UI, state changes are fed back to the agent, creating a high-bandwidth loop and turning an agent interaction into a rich, intuitive experience. The GenUI SDK for Flutter is designed to easily integrate into your Flutter application. ## When would you use it? Use GenUI SDK for Flutter to incorporate graphical UI into your app. For example: \* Instead of describing a list of products in text, use it to render a clickable carousel of product widgets. \* When a user asks to plan a trip, use it to generate a complete form with sliders, date pickers, and text fields. For more context about GenUI SDK for Flutter, check out the \[Getting started with GenUI video]\[]: Also, check out the Flutter + A2UI = GenUI video from Google I/O 2026! :::experimental The \`genui\` package is in alpha and is likely to change. ::: \[Getting started with GenUI video]: https\://www\.youtube.com/watch?v=nWr6eZKM6no

----
url: https://docs.flutter.dev/ui/layout/constraints
----

When someone learning Flutter asks you why some widget with `width: 100` isn't 100 pixels wide, the default answer is to tell them to put that widget inside of a `Center`, right?

**Don't do that.**

If you do, they'll come back again and again, asking why some `FittedBox` isn't working, why that `Column` is overflowing, or what `IntrinsicWidth` is supposed to be doing.

Instead, first tell them that Flutter layout is very different from HTML layout (which is probably where they're coming from), and then make them memorize the following rule:

**Constraints go down. Sizes go up. Parent sets position.**

Flutter layout can't really be understood without knowing this rule, so Flutter developers should learn it early on.

In more detail:

* A widget gets its own **constraints** from its **parent**. A *constraint* is just a set of 4 doubles: a minimum and maximum width, and a minimum and maximum height.
* Then the widget goes through its own list of **children**. One by one, the widget tells its children what their **constraints** are (which can be different for each child), and then asks each child what size it wants to be.
* Then, the widget positions its **children** (horizontally in the `x` axis, and vertically in the `y` axis), one by one.
* And, finally, the widget tells its parent about its own **size** (within the original constraints, of course).

For example, if a composed widget contains a column with some padding, and wants to lay out its two children as follows:

The negotiation goes something like this:

**Widget**: "Hey parent, what are my constraints?"

**Parent**: "You must be from `0` to `300` pixels wide, and `0` to `85` tall."

**Widget**: "Hmmm, since I want to have `5` pixels of padding, then my children can have at most `290` pixels of width and `75` pixels of height."

**Widget**: "Hey first child, You must be from `0` to `290` pixels wide, and `0` to `75` tall."

**First child**: "OK, then I wish to be `290` pixels wide, and `20` pixels tall."

**Widget**: "Hmmm, since I want to put my second child below the first one, this leaves only `55` pixels of height for my second child."

**Widget**: "Hey second child, You must be from `0` to `290` wide, and `0` to `55` tall."

**Second child**: "OK, I wish to be `140` pixels wide, and `30` pixels tall."

**Widget**: "Very well. My first child has position `x: 5` and `y: 5`, and my second child has `x: 80` and `y: 25`."

**Widget**: "Hey parent, I've decided that my size is going to be `300` pixels wide, and `60` pixels tall."

## Limitations

[#](#limitations)

Flutter's layout engine is designed to be a one-pass process. This means that Flutter lays out its widgets very efficiently, but does result in a few limitations:

* A widget can decide its own size only within the constraints given to it by its parent. This means a widget usually **can't have any size it wants**.

* A widget **can't know and doesn't decide its own position in the screen**, since it's the widget's parent who decides the position of the widget.

* Since the parent's size and position, in its turn, also depends on its own parent, it's impossible to precisely define the size and position of any widget without taking into consideration the tree as a whole.

* If a child wants a different size from its parent and the parent doesn't have enough information to align it, then the child's size might be ignored. **Be specific when defining alignment.**

In Flutter, widgets are rendered by their underlying [`RenderBox`](https://api.flutter.dev/flutter/rendering/RenderBox-class.html) objects. Many boxes in Flutter, especially those that just take a single child, pass their constraint on to their children.

Generally, there are three kinds of boxes, in terms of how they handle their constraints:

* Those that try to be as big as possible. For example, the boxes used by [`Center`](https://api.flutter.dev/flutter/widgets/Center-class.html) and [`ListView`](https://api.flutter.dev/flutter/widgets/ListView-class.html).
* Those that try to be the same size as their children. For example, the boxes used by [`Transform`](https://api.flutter.dev/flutter/widgets/Transform-class.html) and [`Opacity`](https://api.flutter.dev/flutter/widgets/Opacity-class.html).
* Those that try to be a particular size. For example, the boxes used by [`Image`](https://api.flutter.dev/flutter/dart-ui/Image-class.html) and [`Text`](https://api.flutter.dev/flutter/widgets/Text-class.html).

Some widgets, for example [`Container`](https://api.flutter.dev/flutter/widgets/Container-class.html), vary from type to type based on their constructor arguments. The [`Container`](https://api.flutter.dev/flutter/widgets/Container-class.html) constructor defaults to trying to be as big as possible, but if you give it a `width`, for instance, it tries to honor that and be that particular size.

Others, for example [`Row`](https://api.flutter.dev/flutter/widgets/Row-class.html) and [`Column`](https://api.flutter.dev/flutter/widgets/Column-class.html) (flex boxes) vary based on the constraints they are given, as described in the [Flex](#flex) section.

## Examples

[#](#examples)

For an interactive experience, use the following DartPad. Use the numbered horizontal scrolling bar to switch between 29 different examples.

```
import 'package:flutter/material.dart';

void main() => runApp(const HomePage());

const Color red = Colors.red;
const Color green = Colors.green;
const Color blue = Colors.blue;
const TextStyle big = TextStyle(fontSize: 30);

//////////////////////////////////////////////////

class HomePage extends StatelessWidget {
  const HomePage({super.key});

  @override
  Widget build(BuildContext context) {
    return const FlutterLayoutArticle([
      Example1(),
      Example2(),
      Example3(),
      Example4(),
      Example5(),
      Example6(),
      Example7(),
      Example8(),
      Example9(),
      Example10(),
      Example11(),
      Example12(),
      Example13(),
      Example14(),
      Example15(),
      Example16(),
      Example17(),
      Example18(),
      Example19(),
      Example20(),
      Example21(),
      Example22(),
      Example23(),
      Example24(),
      Example25(),
      Example26(),
      Example27(),
      Example28(),
      Example29(),
    ]);
  }
}

//////////////////////////////////////////////////

abstract class Example extends StatelessWidget {
  const Example({super.key});

  String get code;

  String get explanation;
}

//////////////////////////////////////////////////

class FlutterLayoutArticle extends StatefulWidget {
  const FlutterLayoutArticle(this.examples, {super.key});

  final List<Example> examples;

  @override
  State<FlutterLayoutArticle> createState() => _FlutterLayoutArticleState();
}

//////////////////////////////////////////////////

class _FlutterLayoutArticleState extends State<FlutterLayoutArticle> {
  late int count;
  late Widget example;
  late String code;
  late String explanation;

  @override
  void initState() {
    count = 1;
    code = const Example1().code;
    explanation = const Example1().explanation;

    super.initState();
  }

  @override
  void didUpdateWidget(FlutterLayoutArticle oldWidget) {
    super.didUpdateWidget(oldWidget);
    var example = widget.examples[count - 1];
    code = example.code;
    explanation = example.explanation;
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'Flutter Layout Article',
      home: SafeArea(
        child: Material(
          color: Colors.black,
          child: FittedBox(
            child: Container(
              width: 400,
              height: 670,
              color: const Color(0xFFCCCCCC),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.center,
                children: [
                  Expanded(
                    child: ConstrainedBox(
                      constraints: const BoxConstraints.tightFor(
                        width: double.infinity,
                        height: double.infinity,
                      ),
                      child: widget.examples[count - 1],
                    ),
                  ),
                  Container(
                    height: 50,
                    width: double.infinity,
                    color: Colors.black,
                    child: SingleChildScrollView(
                      scrollDirection: Axis.horizontal,
                      child: Row(
                        mainAxisSize: MainAxisSize.min,
                        children: [
                          for (int i = 0; i < widget.examples.length; i++)
                            Container(
                              width: 58,
                              padding: const EdgeInsets.only(left: 4, right: 4),
                              child: button(i + 1),
                            ),
                        ],
                      ),
                    ),
                  ),
                  Container(
                    height: 273,
                    color: Colors.grey[50],
                    child: Scrollbar(
                      child: SingleChildScrollView(
                        key: ValueKey(count),
                        child: Padding(
                          padding: const EdgeInsets.all(10),
                          child: Column(
                            children: [
                              Center(child: Text(code)),
                              const SizedBox(height: 15),
                              Text(
                                explanation,
                                style: TextStyle(
                                  color: Colors.blue[900],
                                  fontStyle: FontStyle.italic,
                                ),
                              ),
                            ],
                          ),
                        ),
                      ),
                    ),
                  ),
                ],
              ),
            ),
          ),
        ),
      ),
    );
  }

  Widget button(int exampleNumber) {
    return Button(
      key: ValueKey('button$exampleNumber'),
      isSelected: count == exampleNumber,
      exampleNumber: exampleNumber,
      onPressed: () {
        showExample(
          exampleNumber,
          widget.examples[exampleNumber - 1].code,
          widget.examples[exampleNumber - 1].explanation,
        );
      },
    );
  }

  void showExample(int exampleNumber, String code, String explanation) {
    setState(() {
      count = exampleNumber;
      this.code = code;
      this.explanation = explanation;
    });
  }
}

//////////////////////////////////////////////////

class Button extends StatelessWidget {
  final bool isSelected;
  final int exampleNumber;
  final VoidCallback onPressed;

  const Button({
    super.key,
    required this.isSelected,
    required this.exampleNumber,
    required this.onPressed,
  });

  @override
  Widget build(BuildContext context) {
    return TextButton(
      style: TextButton.styleFrom(
        foregroundColor: Colors.white,
        backgroundColor: isSelected ? Colors.grey : Colors.grey[800],
      ),
      child: Text(exampleNumber.toString()),
      onPressed: () {
        Scrollable.ensureVisible(
          context,
          duration: const Duration(milliseconds: 350),
          curve: Curves.easeOut,
          alignment: 0.5,
        );
        onPressed();
      },
    );
  }
}
//////////////////////////////////////////////////

class Example1 extends Example {
  const Example1({super.key});

  @override
  final code = 'Container(color: red)';

  @override
  final explanation =
      'The screen is the parent of the Container, '
      'and it forces the Container to be exactly the same size as the screen.'
      '\n\n'
      'So the Container fills the screen and paints it red.';

  @override
  Widget build(BuildContext context) {
    return Container(color: red);
  }
}

//////////////////////////////////////////////////

class Example2 extends Example {
  const Example2({super.key});

  @override
  final code = 'Container(width: 100, height: 100, color: red)';
  @override
  final String explanation =
      'The red Container wants to be 100x100, but it can\'t, '
      'because the screen forces it to be exactly the same size as the screen.'
      '\n\n'
      'So the Container fills the screen.';

  @override
  Widget build(BuildContext context) {
    return Container(width: 100, height: 100, color: red);
  }
}

//////////////////////////////////////////////////

class Example3 extends Example {
  const Example3({super.key});

  @override
  final code =
      'Center(\n'
      '   child: Container(width: 100, height: 100, color: red))';
  @override
  final String explanation =
      'The screen forces the Center to be exactly the same size as the screen, '
      'so the Center fills the screen.'
      '\n\n'
      'The Center tells the Container that it can be any size it wants, but not bigger than the screen.'
      'Now the Container can indeed be 100x100.';

  @override
  Widget build(BuildContext context) {
    return Center(child: Container(width: 100, height: 100, color: red));
  }
}

//////////////////////////////////////////////////

class Example4 extends Example {
  const Example4({super.key});

  @override
  final code =
      'Align(\n'
      '   alignment: Alignment.bottomRight,\n'
      '   child: Container(width: 100, height: 100, color: red))';
  @override
  final String explanation =
      'This is different from the previous example in that it uses Align instead of Center.'
      '\n\n'
      'Align also tells the Container that it can be any size it wants, but if there is empty space it won\'t center the Container. '
      'Instead, it aligns the Container to the bottom-right of the available space.';

  @override
  Widget build(BuildContext context) {
    return Align(
      alignment: Alignment.bottomRight,
      child: Container(width: 100, height: 100, color: red),
    );
  }
}

//////////////////////////////////////////////////

class Example5 extends Example {
  const Example5({super.key});

  @override
  final code =
      'Center(\n'
      '   child: Container(\n'
      '              color: red,\n'
      '              width: double.infinity,\n'
      '              height: double.infinity))';
  @override
  final String explanation =
      'The screen forces the Center to be exactly the same size as the screen, '
      'so the Center fills the screen.'
      '\n\n'
      'The Center tells the Container that it can be any size it wants, but not bigger than the screen.'
      'The Container wants to be of infinite size, but since it can\'t be bigger than the screen, it just fills the screen.';

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Container(
        width: double.infinity,
        height: double.infinity,
        color: red,
      ),
    );
  }
}

//////////////////////////////////////////////////

class Example6 extends Example {
  const Example6({super.key});

  @override
  final code = 'Center(child: Container(color: red))';
  @override
  final String explanation =
      'The screen forces the Center to be exactly the same size as the screen, '
      'so the Center fills the screen.'
      '\n\n'
      'The Center tells the Container that it can be any size it wants, but not bigger than the screen.'
      '\n\n'
      'Since the Container has no child and no fixed size, it decides it wants to be as big as possible, so it fills the whole screen.'
      '\n\n'
      'But why does the Container decide that? '
      'Simply because that\'s a design decision by those who created the Container widget. '
      'It could have been created differently, and you have to read the Container documentation to understand how it behaves, depending on the circumstances. ';

  @override
  Widget build(BuildContext context) {
    return Center(child: Container(color: red));
  }
}

//////////////////////////////////////////////////

class Example7 extends Example {
  const Example7({super.key});

  @override
  final code =
      'Center(\n'
      '   child: Container(color: red\n'
      '      child: Container(color: green, width: 30, height: 30)))';
  @override
  final String explanation =
      'The screen forces the Center to be exactly the same size as the screen, '
      'so the Center fills the screen.'
      '\n\n'
      'The Center tells the red Container that it can be any size it wants, but not bigger than the screen.'
      'Since the red Container has no size but has a child, it decides it wants to be the same size as its child.'
      '\n\n'
      'The red Container tells its child that it can be any size it wants, but not bigger than the screen.'
      '\n\n'
      'The child is a green Container that wants to be 30x30.'
      '\n\n'
      'Since the red `Container` has no size but has a child, it decides it wants to be the same size as its child. '
      'The red color isn\'t visible, since the green Container entirely covers all of the red Container.';

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Container(
        color: red,
        child: Container(color: green, width: 30, height: 30),
      ),
    );
  }
}

//////////////////////////////////////////////////

class Example8 extends Example {
  const Example8({super.key});

  @override
  final code =
      'Center(\n'
      '   child: Container(color: red\n'
      '      padding: const EdgeInsets.all(20),\n'
      '      child: Container(color: green, width: 30, height: 30)))';
  @override
  final String explanation =
      'The red Container sizes itself to its children size, but it takes its own padding into consideration. '
      'So it is also 30x30 plus padding. '
      'The red color is visible because of the padding, and the green Container has the same size as in the previous example.';

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Container(
        padding: const EdgeInsets.all(20),
        color: red,
        child: Container(color: green, width: 30, height: 30),
      ),
    );
  }
}

//////////////////////////////////////////////////

class Example9 extends Example {
  const Example9({super.key});

  @override
  final code =
      'ConstrainedBox(\n'
      '   constraints: BoxConstraints(\n'
      '              minWidth: 70, minHeight: 70,\n'
      '              maxWidth: 150, maxHeight: 150),\n'
      '      child: Container(color: red, width: 10, height: 10)))';
  @override
  final String explanation =
      'You might guess that the Container has to be between 70 and 150 pixels, but you would be wrong. '
      'The ConstrainedBox only imposes ADDITIONAL constraints from those it receives from its parent.'
      '\n\n'
      'Here, the screen forces the ConstrainedBox to be exactly the same size as the screen, '
      'so it tells its child Container to also assume the size of the screen, '
      'thus ignoring its \'constraints\' parameter.';

  @override
  Widget build(BuildContext context) {
    return ConstrainedBox(
      constraints: const BoxConstraints(
        minWidth: 70,
        minHeight: 70,
        maxWidth: 150,
        maxHeight: 150,
      ),
      child: Container(color: red, width: 10, height: 10),
    );
  }
}

//////////////////////////////////////////////////

class Example10 extends Example {
  const Example10({super.key});

  @override
  final code =
      'Center(\n'
      '   child: ConstrainedBox(\n'
      '      constraints: BoxConstraints(\n'
      '                 minWidth: 70, minHeight: 70,\n'
      '                 maxWidth: 150, maxHeight: 150),\n'
      '        child: Container(color: red, width: 10, height: 10))))';
  @override
  final String explanation =
      'Now, Center allows ConstrainedBox to be any size up to the screen size.'
      '\n\n'
      'The ConstrainedBox imposes ADDITIONAL constraints from its \'constraints\' parameter onto its child.'
      '\n\n'
      'The Container must be between 70 and 150 pixels. It wants to have 10 pixels, so it will end up having 70 (the MINIMUM).';

  @override
  Widget build(BuildContext context) {
    return Center(
      child: ConstrainedBox(
        constraints: const BoxConstraints(
          minWidth: 70,
          minHeight: 70,
          maxWidth: 150,
          maxHeight: 150,
        ),
        child: Container(color: red, width: 10, height: 10),
      ),
    );
  }
}

//////////////////////////////////////////////////

class Example11 extends Example {
  const Example11({super.key});

  @override
  final code =
      'Center(\n'
      '   child: ConstrainedBox(\n'
      '      constraints: BoxConstraints(\n'
      '                 minWidth: 70, minHeight: 70,\n'
      '                 maxWidth: 150, maxHeight: 150),\n'
      '        child: Container(color: red, width: 1000, height: 1000))))';
  @override
  final String explanation =
      'Center allows ConstrainedBox to be any size up to the screen size.'
      'The ConstrainedBox imposes ADDITIONAL constraints from its \'constraints\' parameter onto its child'
      '\n\n'
      'The Container must be between 70 and 150 pixels. It wants to have 1000 pixels, so it ends up having 150 (the MAXIMUM).';

  @override
  Widget build(BuildContext context) {
    return Center(
      child: ConstrainedBox(
        constraints: const BoxConstraints(
          minWidth: 70,
          minHeight: 70,
          maxWidth: 150,
          maxHeight: 150,
        ),
        child: Container(color: red, width: 1000, height: 1000),
      ),
    );
  }
}

//////////////////////////////////////////////////

class Example12 extends Example {
  const Example12({super.key});

  @override
  final code =
      'Center(\n'
      '   child: ConstrainedBox(\n'
      '      constraints: BoxConstraints(\n'
      '                 minWidth: 70, minHeight: 70,\n'
      '                 maxWidth: 150, maxHeight: 150),\n'
      '        child: Container(color: red, width: 100, height: 100))))';
  @override
  final String explanation =
      'Center allows ConstrainedBox to be any size up to the screen size.'
      'ConstrainedBox imposes ADDITIONAL constraints from its \'constraints\' parameter onto its child.'
      '\n\n'
      'The Container must be between 70 and 150 pixels. It wants to have 100 pixels, and that\'s the size it has, since that\'s between 70 and 150.';

  @override
  Widget build(BuildContext context) {
    return Center(
      child: ConstrainedBox(
        constraints: const BoxConstraints(
          minWidth: 70,
          minHeight: 70,
          maxWidth: 150,
          maxHeight: 150,
        ),
        child: Container(color: red, width: 100, height: 100),
      ),
    );
  }
}

//////////////////////////////////////////////////

class Example13 extends Example {
  const Example13({super.key});

  @override
  final code =
      'UnconstrainedBox(\n'
      '   child: Container(color: red, width: 20, height: 50));';
  @override
  final String explanation =
      'The screen forces the UnconstrainedBox to be exactly the same size as the screen.'
      'However, the UnconstrainedBox lets its child Container be any size it wants.';

  @override
  Widget build(BuildContext context) {
    return UnconstrainedBox(
      child: Container(color: red, width: 20, height: 50),
    );
  }
}

//////////////////////////////////////////////////

class Example14 extends Example {
  const Example14({super.key});

  @override
  final code =
      'UnconstrainedBox(\n'
      '   child: Container(color: red, width: 4000, height: 50));';
  @override
  final String explanation =
      'The screen forces the UnconstrainedBox to be exactly the same size as the screen, '
      'and UnconstrainedBox lets its child Container be any size it wants.'
      '\n\n'
      'Unfortunately, in this case the Container has 4000 pixels of width and is too big to fit in the UnconstrainedBox, '
      'so the UnconstrainedBox displays the much dreaded "overflow warning".';

  @override
  Widget build(BuildContext context) {
    return UnconstrainedBox(
      child: Container(color: red, width: 4000, height: 50),
    );
  }
}

//////////////////////////////////////////////////

class Example15 extends Example {
  const Example15({super.key});

  @override
  final code =
      'OverflowBox(\n'
      '   minWidth: 0,'
      '   minHeight: 0,'
      '   maxWidth: double.infinity,'
      '   maxHeight: double.infinity,'
      '   child: Container(color: red, width: 4000, height: 50));';
  @override
  final String explanation =
      'The screen forces the OverflowBox to be exactly the same size as the screen, '
      'and OverflowBox lets its child Container be any size it wants.'
      '\n\n'
      'OverflowBox is similar to UnconstrainedBox, and the difference is that it won\'t display any warnings if the child doesn\'t fit the space.'
      '\n\n'
      'In this case the Container is 4000 pixels wide, and is too big to fit in the OverflowBox, '
      'but the OverflowBox simply shows as much as it can, with no warnings given.';

  @override
  Widget build(BuildContext context) {
    return OverflowBox(
      minWidth: 0,
      minHeight: 0,
      maxWidth: double.infinity,
      maxHeight: double.infinity,
      child: Container(color: red, width: 4000, height: 50),
    );
  }
}

//////////////////////////////////////////////////

class Example16 extends Example {
  const Example16({super.key});

  @override
  final code =
      'UnconstrainedBox(\n'
      '   child: Container(color: Colors.red, width: double.infinity, height: 100));';
  @override
  final String explanation =
      'This won\'t render anything, and you\'ll see an error in the console.'
      '\n\n'
      'The UnconstrainedBox lets its child be any size it wants, '
      'however its child is a Container with infinite size.'
      '\n\n'
      'Flutter can\'t render infinite sizes, so it throws an error with the following message: '
      '"BoxConstraints forces an infinite width."';

  @override
  Widget build(BuildContext context) {
    return UnconstrainedBox(
      child: Container(color: Colors.red, width: double.infinity, height: 100),
    );
  }
}

//////////////////////////////////////////////////

class Example17 extends Example {
  const Example17({super.key});

  @override
  final code =
      'UnconstrainedBox(\n'
      '   child: LimitedBox(maxWidth: 100,\n'
      '      child: Container(color: Colors.red,\n'
      '                       width: double.infinity, height: 100));';
  @override
  final String explanation =
      'Here you won\'t get an error anymore, '
      'because when the LimitedBox is given an infinite size by the UnconstrainedBox, '
      'it passes a maximum width of 100 down to its child.'
      '\n\n'
      'If you swap the UnconstrainedBox for a Center widget, '
      'the LimitedBox won\'t apply its limit anymore (since its limit is only applied when it gets infinite constraints), '
      'and the width of the Container is allowed to grow past 100.'
      '\n\n'
      'This explains the difference between a LimitedBox and a ConstrainedBox.';

  @override
  Widget build(BuildContext context) {
    return UnconstrainedBox(
      child: LimitedBox(
        maxWidth: 100,
        child: Container(
          color: Colors.red,
          width: double.infinity,
          height: 100,
        ),
      ),
    );
  }
}

//////////////////////////////////////////////////

class Example18 extends Example {
  const Example18({super.key});

  @override
  final code =
      'FittedBox(\n'
      '   child: Text(\'Some Example Text.\'));';
  @override
  final String explanation =
      'The screen forces the FittedBox to be exactly the same size as the screen.'
      'The Text has some natural width (also called its intrinsic width) that depends on the amount of text, its font size, and so on.'
      '\n\n'
      'The FittedBox lets the Text be any size it wants, '
      'but after the Text tells its size to the FittedBox, '
      'the FittedBox scales the Text until it fills all of the available width.';

  @override
  Widget build(BuildContext context) {
    return const FittedBox(child: Text('Some Example Text.'));
  }
}

//////////////////////////////////////////////////

class Example19 extends Example {
  const Example19({super.key});

  @override
  final code =
      'Center(\n'
      '   child: FittedBox(\n'
      '      child: Text(\'Some Example Text.\')));';
  @override
  final String explanation =
      'But what happens if you put the FittedBox inside of a Center widget? '
      'The Center lets the FittedBox be any size it wants, up to the screen size.'
      '\n\n'
      'The FittedBox then sizes itself to the Text, and lets the Text be any size it wants.'
      '\n\n'
      'Since both FittedBox and the Text have the same size, no scaling happens.';

  @override
  Widget build(BuildContext context) {
    return const Center(child: FittedBox(child: Text('Some Example Text.')));
  }
}

////////////////////////////////////////////////////

class Example20 extends Example {
  const Example20({super.key});

  @override
  final code =
      'Center(\n'
      '   child: FittedBox(\n'
      '      child: Text(\'…\')));';
  @override
  final String explanation =
      'However, what happens if FittedBox is inside of a Center widget, but the Text is too large to fit the screen?'
      '\n\n'
      'FittedBox tries to size itself to the Text, but it can\'t be bigger than the screen. '
      'It then assumes the screen size, and resizes Text so that it fits the screen, too.';

  @override
  Widget build(BuildContext context) {
    return const Center(
      child: FittedBox(
        child: Text(
          'This is some very very very large text that is too big to fit a regular screen in a single line.',
        ),
      ),
    );
  }
}

//////////////////////////////////////////////////

class Example21 extends Example {
  const Example21({super.key});

  @override
  final code =
      'Center(\n'
      '   child: Text(\'…\'));';
  @override
  final String explanation =
      'If, however, you remove the FittedBox, '
      'the Text gets its maximum width from the screen, '
      'and breaks the line so that it fits the screen.';

  @override
  Widget build(BuildContext context) {
    return const Center(
      child: Text(
        'This is some very very very large text that is too big to fit a regular screen in a single line.',
      ),
    );
  }
}

//////////////////////////////////////////////////

class Example22 extends Example {
  const Example22({super.key});

  @override
  final code =
      'FittedBox(\n'
      '   child: Container(\n'
      '      height: 20, width: double.infinity));';
  @override
  final String explanation =
      'FittedBox can only scale a widget that is BOUNDED (has non-infinite width and height).'
      'Otherwise, it won\'t render anything, and you\'ll see an error in the console.';

  @override
  Widget build(BuildContext context) {
    return FittedBox(
      child: Container(height: 20, width: double.infinity, color: Colors.red),
    );
  }
}

//////////////////////////////////////////////////

class Example23 extends Example {
  const Example23({super.key});

  @override
  final code =
      'Row(children:[\n'
      '   Container(color: red, child: Text(\'Hello!\'))\n'
      '   Container(color: green, child: Text(\'Goodbye!\'))]';
  @override
  final String explanation =
      'The screen forces the Row to be exactly the same size as the screen.'
      '\n\n'
      'Just like an UnconstrainedBox, the Row won\'t impose any constraints onto its children, '
      'and instead lets them be any size they want.'
      '\n\n'
      'The Row then puts them side-by-side, and any extra space remains empty.';

  @override
  Widget build(BuildContext context) {
    return Row(
      children: [
        Container(
          color: red,
          child: const Text('Hello!', style: big),
        ),
        Container(
          color: green,
          child: const Text('Goodbye!', style: big),
        ),
      ],
    );
  }
}

//////////////////////////////////////////////////

class Example24 extends Example {
  const Example24({super.key});

  @override
  final code =
      'Row(children:[\n'
      '   Container(color: red, child: Text(\'…\'))\n'
      '   Container(color: green, child: Text(\'Goodbye!\'))]';
  @override
  final String explanation =
      'Since the Row won\'t impose any constraints onto its children, '
      'it\'s quite possible that the children might be too big to fit the available width of the Row.'
      'In this case, just like an UnconstrainedBox, the Row displays the "overflow warning".';

  @override
  Widget build(BuildContext context) {
    return Row(
      children: [
        Container(
          color: red,
          child: const Text(
            'This is a very long text that '
            'won\'t fit the line.',
            style: big,
          ),
        ),
        Container(
          color: green,
          child: const Text('Goodbye!', style: big),
        ),
      ],
    );
  }
}

//////////////////////////////////////////////////

class Example25 extends Example {
  const Example25({super.key});

  @override
  final code =
      'Row(children:[\n'
      '   Expanded(\n'
      '       child: Container(color: red, child: Text(\'…\')))\n'
      '   Container(color: green, child: Text(\'Goodbye!\'))]';
  @override
  final String explanation =
      'When a Row\'s child is wrapped in an Expanded widget, the Row won\'t let this child define its own width anymore.'
      '\n\n'
      'Instead, it defines the Expanded width according to the other children, and only then the Expanded widget forces the original child to have the Expanded\'s width.'
      '\n\n'
      'In other words, once you use Expanded, the original child\'s width becomes irrelevant, and is ignored.';

  @override
  Widget build(BuildContext context) {
    return Row(
      children: [
        Expanded(
          child: Center(
            child: Container(
              color: red,
              child: const Text(
                'This is a very long text that won\'t fit the line.',
                style: big,
              ),
            ),
          ),
        ),
        Container(
          color: green,
          child: const Text('Goodbye!', style: big),
        ),
      ],
    );
  }
}

//////////////////////////////////////////////////

class Example26 extends Example {
  const Example26({super.key});

  @override
  final code =
      'Row(children:[\n'
      '   Expanded(\n'
      '       child: Container(color: red, child: Text(\'…\')))\n'
      '   Expanded(\n'
      '       child: Container(color: green, child: Text(\'Goodbye!\'))]';
  @override
  final String explanation =
      'If all of Row\'s children are wrapped in Expanded widgets, each Expanded has a size proportional to its flex parameter, '
      'and only then each Expanded widget forces its child to have the Expanded\'s width.'
      '\n\n'
      'In other words, Expanded ignores the preferred width of its children.';

  @override
  Widget build(BuildContext context) {
    return Row(
      children: [
        Expanded(
          child: Container(
            color: red,
            child: const Text(
              'This is a very long text that won\'t fit the line.',
              style: big,
            ),
          ),
        ),
        Expanded(
          child: Container(
            color: green,
            child: const Text('Goodbye!', style: big),
          ),
        ),
      ],
    );
  }
}

//////////////////////////////////////////////////

class Example27 extends Example {
  const Example27({super.key});

  @override
  final code =
      'Row(children:[\n'
      '   Flexible(\n'
      '       child: Container(color: red, child: Text(\'…\')))\n'
      '   Flexible(\n'
      '       child: Container(color: green, child: Text(\'Goodbye!\'))]';
  @override
  final String explanation =
      'The only difference if you use Flexible instead of Expanded, '
      'is that Flexible lets its child be SMALLER than the Flexible width, '
      'while Expanded forces its child to have the same width of the Expanded.'
      '\n\n'
      'But both Expanded and Flexible ignore their children\'s width when sizing themselves.'
      '\n\n'
      'This means that it\'s IMPOSSIBLE to expand Row children proportionally to their sizes. '
      'The Row either uses the exact child\'s width, or ignores it completely when you use Expanded or Flexible.';

  @override
  Widget build(BuildContext context) {
    return Row(
      children: [
        Flexible(
          child: Container(
            color: red,
            child: const Text(
              'This is a very long text that won\'t fit the line.',
              style: big,
            ),
          ),
        ),
        Flexible(
          child: Container(
            color: green,
            child: const Text('Goodbye!', style: big),
          ),
        ),
      ],
    );
  }
}

//////////////////////////////////////////////////

class Example28 extends Example {
  const Example28({super.key});

  @override
  final code =
      'Scaffold(\n'
      '   body: Container(color: blue,\n'
      '   child: Column(\n'
      '      children: [\n'
      '         Text(\'Hello!\'),\n'
      '         Text(\'Goodbye!\')])))';

  @override
  final String explanation =
      'The screen forces the Scaffold to be exactly the same size as the screen, '
      'so the Scaffold fills the screen.'
      '\n\n'
      'The Scaffold tells the Container that it can be any size it wants, but not bigger than the screen.'
      '\n\n'
      'When a widget tells its child that it can be smaller than a certain size, '
      'we say the widget supplies "loose" constraints to its child. More on that later.';

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Container(
        color: blue,
        child: const Column(children: [Text('Hello!'), Text('Goodbye!')]),
      ),
    );
  }
}

//////////////////////////////////////////////////

class Example29 extends Example {
  const Example29({super.key});

  @override
  final code =
      'Scaffold(\n'
      '   body: Container(color: blue,\n'
      '   child: SizedBox.expand(\n'
      '      child: Column(\n'
      '         children: [\n'
      '            Text(\'Hello!\'),\n'
      '            Text(\'Goodbye!\')]))))';

  @override
  final String explanation =
      'If you want the Scaffold\'s child to be exactly the same size as the Scaffold itself, '
      'you can wrap its child with SizedBox.expand.'
      '\n\n'
      'When a widget tells its child that it must be of a certain size, '
      'we say the widget supplies "tight" constraints to its child. More on that later.';

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: SizedBox.expand(
        child: Container(
          color: blue,
          child: const Column(children: [Text('Hello!'), Text('Goodbye!')]),
        ),
      ),
    );
  }
}

//////////////////////////////////////////////////
```

If you prefer, you can grab the code from [this GitHub repo](https://github.com/marcglasberg/flutter_layout_article).

The examples are explained in the following sections.

### Example 1

[#](#example-1)

dart

```
Container(color: red)
```

The screen is the parent of the `Container`, and it forces the `Container` to be exactly the same size as the screen.

So the `Container` fills the screen and paints it red.

### Example 2

[#](#example-2)

dart

```
Container(width: 100, height: 100, color: red)
```

The red `Container` wants to be 100 × 100, but it can't, because the screen forces it to be exactly the same size as the screen.

So the `Container` fills the screen.

### Example 3

[#](#example-3)

dart

```
Center(child: Container(width: 100, height: 100, color: red))
```

The screen forces the `Center` to be exactly the same size as the screen, so the `Center` fills the screen.

The `Center` tells the `Container` that it can be any size it wants, but not bigger than the screen. Now the `Container` can indeed be 100 × 100.

### Example 4

[#](#example-4)

dart

```
Align(
  alignment: Alignment.bottomRight,
  child: Container(width: 100, height: 100, color: red),
)
```

This is different from the previous example in that it uses `Align` instead of `Center`.

`Align` also tells the `Container` that it can be any size it wants, but if there is empty space it won't center the `Container`. Instead, it aligns the container to the bottom-right of the available space.

### Example 5

[#](#example-5)

dart

```
Center(
  child: Container(
    width: double.infinity,
    height: double.infinity,
    color: red,
  ),
)
```

The screen forces the `Center` to be exactly the same size as the screen, so the `Center` fills the screen.

The `Center` tells the `Container` that it can be any size it wants, but not bigger than the screen. The `Container` wants to be of infinite size, but since it can't be bigger than the screen, it just fills the screen.

### Example 6

[#](#example-6)

dart

```
Center(child: Container(color: red))
```

The screen forces the `Center` to be exactly the same size as the screen, so the `Center` fills the screen.

The `Center` tells the `Container` that it can be any size it wants, but not bigger than the screen. Since the `Container` has no child and no fixed size, it decides it wants to be as big as possible, so it fills the whole screen.

But why does the `Container` decide that? Simply because that's a design decision by those who created the `Container` widget. It could have been created differently, and you have to read the [`Container`](https://api.flutter.dev/flutter/widgets/Container-class.html) API documentation to understand how it behaves, depending on the circumstances.

### Example 7

[#](#example-7)

dart

```
Center(
  child: Container(
    color: red,
    child: Container(color: green, width: 30, height: 30),
  ),
)
```

The screen forces the `Center` to be exactly the same size as the screen, so the `Center` fills the screen.

The `Center` tells the red `Container` that it can be any size it wants, but not bigger than the screen. Since the red `Container` has no size but has a child, it decides it wants to be the same size as its child.

The red `Container` tells its child that it can be any size it wants, but not bigger than the screen.

The child is a green `Container` that wants to be 30 × 30. Given that the red `Container` sizes itself to the size of its child, it is also 30 × 30. The red color isn't visible because the green `Container` entirely covers the red `Container`.

### Example 8

[#](#example-8)

dart

```
Center(
  child: Container(
    padding: const EdgeInsets.all(20),
    color: red,
    child: Container(color: green, width: 30, height: 30),
  ),
)
```

The red `Container` sizes itself to its children's size, but it takes its own padding into consideration. So it is also 30 × 30 plus padding. The red color is visible because of the padding, and the green `Container` has the same size as in the previous example.

### Example 9

[#](#example-9)

dart

```
ConstrainedBox(
  constraints: const BoxConstraints(
    minWidth: 70,
    minHeight: 70,
    maxWidth: 150,
    maxHeight: 150,
  ),
  child: Container(color: red, width: 10, height: 10),
)
```

You might guess that the `Container` has to be between 70 and 150 pixels, but you would be wrong. The `ConstrainedBox` only imposes **additional** constraints from those it receives from its parent.

Here, the screen forces the `ConstrainedBox` to be exactly the same size as the screen, so it tells its child `Container` to also assume the size of the screen, thus ignoring its `constraints` parameter.

### Example 10

[#](#example-10)

dart

```
Center(
  child: ConstrainedBox(
    constraints: const BoxConstraints(
      minWidth: 70,
      minHeight: 70,
      maxWidth: 150,
      maxHeight: 150,
    ),
    child: Container(color: red, width: 10, height: 10),
  ),
)
```

Now, `Center` allows `ConstrainedBox` to be any size up to the screen size. The `ConstrainedBox` imposes **additional** constraints from its `constraints` parameter onto its child.

The Container must be between 70 and 150 pixels. It wants to have 10 pixels, so it ends up having 70 (the minimum).

### Example 11

[#](#example-11)

dart

```
Center(
  child: ConstrainedBox(
    constraints: const BoxConstraints(
      minWidth: 70,
      minHeight: 70,
      maxWidth: 150,
      maxHeight: 150,
    ),
    child: Container(color: red, width: 1000, height: 1000),
  ),
)
```

`Center` allows `ConstrainedBox` to be any size up to the screen size. The `ConstrainedBox` imposes **additional** constraints from its `constraints` parameter onto its child.

The `Container` must be between 70 and 150 pixels. It wants to have 1000 pixels, so it ends up having 150 (the maximum).

### Example 12

[#](#example-12)

dart

```
Center(
  child: ConstrainedBox(
    constraints: const BoxConstraints(
      minWidth: 70,
      minHeight: 70,
      maxWidth: 150,
      maxHeight: 150,
    ),
    child: Container(color: red, width: 100, height: 100),
  ),
)
```

`Center` allows `ConstrainedBox` to be any size up to the screen size. The `ConstrainedBox` imposes **additional** constraints from its `constraints` parameter onto its child.

The `Container` must be between 70 and 150 pixels. It wants to have 100 pixels, and that's the size it has, since that's between 70 and 150.

### Example 13

[#](#example-13)

dart

```
UnconstrainedBox(
  child: Container(color: red, width: 20, height: 50),
)
```

The screen forces the `UnconstrainedBox` to be exactly the same size as the screen. However, the `UnconstrainedBox` lets its child `Container` be any size it wants.

### Example 14

[#](#example-14)

dart

```
UnconstrainedBox(
  child: Container(color: red, width: 4000, height: 50),
)
```

The screen forces the `UnconstrainedBox` to be exactly the same size as the screen, and `UnconstrainedBox` lets its child `Container` be any size it wants.

Unfortunately, in this case the `Container` is 4000 pixels wide and is too big to fit in the `UnconstrainedBox`, so the `UnconstrainedBox` displays the much dreaded "overflow warning".

### Example 15

[#](#example-15)

dart

```
OverflowBox(
  minWidth: 0,
  minHeight: 0,
  maxWidth: double.infinity,
  maxHeight: double.infinity,
  child: Container(color: red, width: 4000, height: 50),
)
```

The screen forces the `OverflowBox` to be exactly the same size as the screen, and `OverflowBox` lets its child `Container` be any size it wants.

`OverflowBox` is similar to `UnconstrainedBox`; the difference is that it won't display any warnings if the child doesn't fit the space.

In this case, the `Container` has 4000 pixels of width, and is too big to fit in the `OverflowBox`, but the `OverflowBox` simply shows as much as it can, with no warnings given.

### Example 16

[#](#example-16)

dart

```
UnconstrainedBox(
  child: Container(color: Colors.red, width: double.infinity, height: 100),
)
```

This won't render anything, and you'll see an error in the console.

The `UnconstrainedBox` lets its child be any size it wants, however its child is a `Container` with infinite size.

Flutter can't render infinite sizes, so it throws an error with the following message: `BoxConstraints forces an infinite width.`

### Example 17

[#](#example-17)

dart

```
UnconstrainedBox(
  child: LimitedBox(
    maxWidth: 100,
    child: Container(
      color: Colors.red,
      width: double.infinity,
      height: 100,
    ),
  ),
)
```

Here you won't get an error anymore, because when the `LimitedBox` is given an infinite size by the `UnconstrainedBox`; it passes a maximum width of 100 down to its child.

If you swap the `UnconstrainedBox` for a `Center` widget, the `LimitedBox` won't apply its limit anymore (since its limit is only applied when it gets infinite constraints), and the width of the `Container` is allowed to grow past 100.

This explains the difference between a `LimitedBox` and a `ConstrainedBox`.

### Example 18

[#](#example-18)

dart

```
const FittedBox(child: Text('Some Example Text.'))
```

The screen forces the `FittedBox` to be exactly the same size as the screen. The `Text` has some natural width (also called its intrinsic width) that depends on the amount of text, its font size, and so on.

The `FittedBox` lets the `Text` be any size it wants, but after the `Text` tells its size to the `FittedBox`, the `FittedBox` scales the Text until it fills all of the available width.

### Example 19

[#](#example-19)

dart

```
const Center(child: FittedBox(child: Text('Some Example Text.')))
```

But what happens if you put the `FittedBox` inside of a `Center` widget? The `Center` lets the `FittedBox` be any size it wants, up to the screen size.

The `FittedBox` then sizes itself to the `Text`, and lets the `Text` be any size it wants. Since both `FittedBox` and the `Text` have the same size, no scaling happens.

### Example 20

[#](#example-20)

dart

```
const Center(
  child: FittedBox(
    child: Text(
      'This is some very very very large text that is too big to fit a regular screen in a single line.',
    ),
  ),
)
```

However, what happens if `FittedBox` is inside of a `Center` widget, but the `Text` is too large to fit the screen?

`FittedBox` tries to size itself to the `Text`, but it can't be bigger than the screen. It then assumes the screen size, and resizes `Text` so that it fits the screen, too.

### Example 21

[#](#example-21)

dart

```
const Center(
  child: Text(
    'This is some very very very large text that is too big to fit a regular screen in a single line.',
  ),
)
```

If, however, you remove the `FittedBox`, the `Text` gets its maximum width from the screen, and breaks the line so that it fits the screen.

### Example 22

[#](#example-22)

dart

```
FittedBox(
  child: Container(height: 20, width: double.infinity, color: Colors.red),
)
```

`FittedBox` can only scale a widget that is bounded (has non-infinite width and height). Otherwise, it won't render anything, and you'll see an error in the console.

### Example 23

[#](#example-23)

dart

```
Row(
  children: [
    Container(
      color: red,
      child: const Text('Hello!', style: big),
    ),
    Container(
      color: green,
      child: const Text('Goodbye!', style: big),
    ),
  ],
)
```

The screen forces the `Row` to be exactly the same size as the screen.

Just like an `UnconstrainedBox`, the `Row` won't impose any constraints onto its children, and instead lets them be any size they want. The `Row` then puts them side-by-side, and any extra space remains empty.

### Example 24

[#](#example-24)

dart

```
Row(
  children: [
    Container(
      color: red,
      child: const Text(
        'This is a very long text that '
        'won\'t fit the line.',
        style: big,
      ),
    ),
    Container(
      color: green,
      child: const Text('Goodbye!', style: big),
    ),
  ],
)
```

Since `Row` won't impose any constraints onto its children, it's quite possible that the children might be too big to fit the available width of the `Row`. In this case, just like an `UnconstrainedBox`, the `Row` displays the "overflow warning".

### Example 25

[#](#example-25)

dart

```
Row(
  children: [
    Expanded(
      child: Center(
        child: Container(
          color: red,
          child: const Text(
            'This is a very long text that won\'t fit the line.',
            style: big,
          ),
        ),
      ),
    ),
    Container(
      color: green,
      child: const Text('Goodbye!', style: big),
    ),
  ],
)
```

When a `Row`'s child is wrapped in an `Expanded` widget, the `Row` won't let this child define its own width anymore.

Instead, it defines the `Expanded` width according to the other children, and only then the `Expanded` widget forces the original child to have the `Expanded`'s width.

In other words, once you use `Expanded`, the original child's width becomes irrelevant, and is ignored.

### Example 26

[#](#example-26)

dart

```
Row(
  children: [
    Expanded(
      child: Container(
        color: red,
        child: const Text(
          'This is a very long text that won\'t fit the line.',
          style: big,
        ),
      ),
    ),
    Expanded(
      child: Container(
        color: green,
        child: const Text('Goodbye!', style: big),
      ),
    ),
  ],
)
```

If all of `Row`'s children are wrapped in `Expanded` widgets, each `Expanded` has a size proportional to its flex parameter, and only then each `Expanded` widget forces its child to have the `Expanded`'s width.

In other words, `Expanded` ignores the preferred width of its children.

### Example 27

[#](#example-27)

dart

```
Row(
  children: [
    Flexible(
      child: Container(
        color: red,
        child: const Text(
          'This is a very long text that won\'t fit the line.',
          style: big,
        ),
      ),
    ),
    Flexible(
      child: Container(
        color: green,
        child: const Text('Goodbye!', style: big),
      ),
    ),
  ],
)
```

The only difference if you use `Flexible` instead of `Expanded`, is that `Flexible` lets its child have the same or smaller width than the `Flexible` itself, while `Expanded` forces its child to have the exact same width of the `Expanded`. But both `Expanded` and `Flexible` ignore their children's width when sizing themselves.

### Example 28

[#](#example-28)

dart

```
Scaffold(
  body: Container(
    color: blue,
    child: const Column(children: [Text('Hello!'), Text('Goodbye!')]),
  ),
)
```

The screen forces the `Scaffold` to be exactly the same size as the screen, so the `Scaffold` fills the screen. The `Scaffold` tells the `Container` that it can be any size it wants, but not bigger than the screen.

### Example 29

[#](#example-29)

dart

```
Scaffold(
  body: SizedBox.expand(
    child: Container(
      color: blue,
      child: const Column(children: [Text('Hello!'), Text('Goodbye!')]),
    ),
  ),
)
```

If you want the `Scaffold`'s child to be exactly the same size as the `Scaffold` itself, you can wrap its child with `SizedBox.expand`.

## Tight vs loose constraints

[#](#tight-vs-loose-constraints)

It's very common to hear that some constraint is "tight" or "loose", so what does that mean?

### Tight constraints

[#](#tight-constraints)

A *tight* constraint offers a single possibility, an exact size. In other words, a tight constraint has its maximum width equal to its minimum width; and has its maximum height equal to its minimum height.

An example of this is the `App` widget, which is contained by the [`RenderView`](https://api.flutter.dev/flutter/rendering/RenderView-class.html) class: the box used by the child returned by the application's [`build`](https://api.flutter.dev/flutter/widgets/State/build.html) function is given a constraint that forces it to exactly fill the application's content area (typically, the entire screen).

Another example: if you nest a bunch of boxes inside each other at the root of your application's render tree, they'll all exactly fit in each other, forced by the box's tight constraints.

If you go to Flutter's `box.dart` file and search for the `BoxConstraints` constructors, you'll find the following:

dart

```
BoxConstraints.tight(Size size)
   : minWidth = size.width,
     maxWidth = size.width,
     minHeight = size.height,
     maxHeight = size.height;
```

If you revisit [Example 2](#example-2), the screen forces the red `Container` to be exactly the same size as the screen. The screen achieves that, of course, by passing tight constraints to the `Container`.

### Loose constraints

[#](#loose-constraints)

A *loose* constraint is one that has a minimum of zero and a maximum non-zero.

Some boxes *loosen* the incoming constraints, meaning the maximum is maintained but the minimum is removed, so the widget can have a **minimum** width and height both equal to **zero**.

Ultimately, `Center`'s purpose is to transform the tight constraints it received from its parent (the screen) to loose constraints for its child (the `Container`).

If you revisit [Example 3](#example-3), the `Center` allows the red `Container` to be smaller, but not bigger than the screen.

[]()

## Unbounded constraints

[#](#unbounded-constraints)

In certain situations, a box's constraint is *unbounded*, or infinite. This means that either the maximum width or the maximum height is set to [`double.infinity`](https://api.flutter.dev/flutter/dart-core/double/infinity-constant.html).

A box that tries to be as big as possible won't function usefully when given an unbounded constraint and, in debug mode, throws an exception.

The most common case where a render box ends up with an unbounded constraint is within a flex box ([`Row`](https://api.flutter.dev/flutter/widgets/Row-class.html) or [`Column`](https://api.flutter.dev/flutter/widgets/Column-class.html)), and **within a scrollable region** (such as [`ListView`](https://api.flutter.dev/flutter/widgets/ListView-class.html) and other [`ScrollView`](https://api.flutter.dev/flutter/widgets/ScrollView-class.html) subclasses).

[`ListView`](https://api.flutter.dev/flutter/widgets/ListView-class.html), for example, tries to expand to fit the space available in its cross-direction (perhaps it's a vertically-scrolling block and tries to be as wide as its parent). If you nest a vertically scrolling [`ListView`](https://api.flutter.dev/flutter/widgets/ListView-class.html) inside a horizontally scrolling `ListView`, the inner list tries to be as wide as possible, which is infinitely wide, since the outer one is scrollable in that direction.

The next section describes the error you might encounter with unbounded constraints in a `Flex` widget.

## Flex

[#](#flex)

A flex box ([`Row`](https://api.flutter.dev/flutter/widgets/Row-class.html) and [`Column`](https://api.flutter.dev/flutter/widgets/Column-class.html)) behaves differently depending on whether its constraint is bounded or unbounded in its primary direction.

A flex box with a bounded constraint in its primary direction tries to be as big as possible.

A flex box with an unbounded constraint in its primary direction tries to fit its children in that space. Each child's `flex` value must be set to zero, meaning that you can't use [`Expanded`](https://api.flutter.dev/flutter/widgets/Expanded-class.html) when the flex box is inside another flex box or a scrollable; otherwise it throws an exception.

The *cross* direction (width for [`Column`](https://api.flutter.dev/flutter/widgets/Column-class.html) or height for [`Row`](https://api.flutter.dev/flutter/widgets/Row-class.html)), must *never* be unbounded, or it can't reasonably align its children.

## Learning the layout rules for specific widgets

[#](#learning-the-layout-rules-for-specific-widgets)

Knowing the general layout rule is necessary, but it's not enough.

Each widget has a lot of freedom when applying the general rule, so there is no way of knowing how it behaves by just reading the widget's name.

If you try to guess, you'll probably guess wrong. You can't know exactly how a widget behaves unless you've read its documentation, or studied its source-code.

The layout source-code is usually complex, so it's probably better to just read the documentation. However, if you decide to study the layout source-code, you can easily find it by using the navigating capabilities of your IDE.

Here's an example:

* Find a `Column` in your code and navigate to its source code. To do this, use `command+B` (macOS) or `control+B` (Windows/Linux) in Android Studio or IntelliJ. You'll be taken to the `basic.dart` file. Since `Column` extends `Flex`, navigate to the `Flex` source code (also in `basic.dart`).

* Scroll down until you find a method called `createRenderObject()`. As you can see, this method returns a `RenderFlex`. This is the render-object for the `Column`. Now navigate to the source-code of `RenderFlex`, which takes you to the `flex.dart` file.

* Scroll down until you find a method called `performLayout()`. This is the method that does the layout for the `Column`.

***

Original article by Marcelo Glasberg

Marcelo originally published this content as [Flutter: The Advanced Layout Rule Even Beginners Must Know](https://medium.com/flutter-community/flutter-the-advanced-layout-rule-even-beginners-must-know-edc9516d1a2) on Medium. We loved it and asked that he allow us to publish in on docs.flutter.dev, to which he graciously agreed. Thanks, Marcelo! You can find Marcelo on [GitHub](https://github.com/marcglasberg) and [pub.dev](https://pub.dev/publishers/glasberg.dev/packages).

Also, thanks to [Simon Lightfoot](https://github.com/slightfoot) for creating the header image at the top of the article.

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-07-31. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/ui/layout/constraints.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/ui/layout/constraints\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/ui/layout/constraints.md "Report an issue with this page").

----
url: https://docs.flutter.dev/tools/widget-previewer
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Flutter Widget Previewer](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

In this guide, you will learn how to use the Flutter Widget Previewer.

## Overview

[#](#overview)

With the Flutter Widget Previewer, you can instantly render, inspect, and iterate on individual UI components in real-time, separate from a full app, in your IDE or browser.

The following sections explain how to open the previewer, preview your widgets, search and filter previews, and customize preview configurations.

## Opening the previewer

[#](#opening-the-previewer)

### IDEs

[#](#ides)

Android Studio, IntelliJ, and Visual Studio Code automatically start the Flutter Widget Previewer on launch.

#### Android Studio and IntelliJ

[#](#android-studio-and-intellij)

To open the Widget Previewer in Android Studio or IntelliJ, open the "Flutter Widget Preview" tab in the sidebar:

#### Visual Studio Code

[#](#visual-studio-code)

To open the Widget Previewer in Visual Studio Code, open the "Flutter Widget Preview" tab in the sidebar:

### Command line

[#](#command-line)

To start the Flutter Widget Previewer from your terminal, navigate to your Flutter project's root directory and run the following command:

shell

```
flutter widget-preview start
```

This launches a local server and opens a real-time preview environment in your browser.

To optimize startup times, the previewer automatically caches project builds in a `.widget_preview/` folder in your project root.

## Preview a widget

[#](#preview-a-widget)

After you've started the previewer, to look at a widget, you must use the [`@Preview`](https://api.flutter.dev/flutter/widget_previews/Preview-class.html) annotation defined in `package:flutter/widget_previews.dart`. This annotation can be applied to:

* **Top-level functions** that return a `Widget` or `WidgetBuilder`.
* **Static methods** within a class that return a `Widget` or `WidgetBuilder`.
* **Public Widget constructors and factories** with no required arguments.

Here is a basic example of how to use the `@Preview` annotation to preview a `Text` widget:

dart

```
import 'package:flutter/widget_previews.dart';
import 'package:material_ui/material_ui.dart'; // For Material widgets
​
@Preview(name: 'My Sample Text')
Widget mySampleText() {
  return const Text('Hello, World!');
}
```

Each preview instance provides various controls for interacting with the previewed widget. From left to right:

* **Zoom in:** Magnifies the widget in the preview.

* **Zoom out:** Reduces the magnification of the widget in the preview.

* **Reset zoom:** Returns the widget preview to its default zoom level.

* **Toggle between light and dark mode:** Switches the preview's theme between a light and dark color scheme.

* **Perform a hot restart for the individual preview:** Restarts only the specific widget preview, allowing changes to be applied quickly without restarting the entire application.

For the case where global state has been modified (for example, a static initializer has been changed), you can hot restart the entire widget previewer using the button at the bottom right of the environment.

## Search and filter previews

[#](#search-and-filter-previews)

The Widget Previewer environment includes search and filtering capabilities to help navigate projects with many annotated previews.

### Search bar and filtering criteria

[#](#search-bar-and-filtering-criteria)

Use the search bar at the top of the environment to filter previews in real-time. Click the filter dropdown next to the search field to choose which criteria to match against:

* **Preview name**: Filters by the descriptive name set in the [`@Preview`](https://api.flutter.dev/flutter/widget_previews/Preview-class.html) annotation.
* **Group name**: Filters by the group name specified in the `@Preview(group: ...)` parameter.
* **Containing script**: Filters by the URI of the Dart file containing the preview.
* **Containing package**: Filters by the package name containing the preview.

For example, the parameters in the following preview annotation match against the **Preview name** (`'Submit Button'`) and **Group name** (`'Form Controls'`) filters:

dart

```
@Preview(name: 'Submit Button', group: 'Form Controls')
Widget submitButtonPreview() => const SubmitButton();
```

### Filter previews by selected file in IDEs

[#](#filter-previews-by-selected-file-in-ides)

When viewing previews within an IDE, the widget previewer can also filter previews based on the currently selected file:

To toggle this behavior, select the **Filter previews by selected file** option at the bottom of the environment.

## Customize a preview

[#](#customize-a-preview)

The [`@Preview`](https://api.flutter.dev/flutter/widget_previews/Preview-class.html) annotation has several parameters you can use to customize the preview:

* **`name`**: A descriptive name for the preview.

* **`group`**: A name used to group related previews together in the widget previewer.

* **`size`**: Artificial size constraints using a `Size` object.

* **`textScaleFactor`**: A custom font scale.

* **`wrapper`**: A function that wraps your previewed widget in a specific widget tree (for example, to inject application state into the widget tree with an `InheritedWidget`).

* **`theme`**: A function returning a `PreviewThemeData` instance to provide custom and generic theming data with sequential theme layering support for complex matrix testing.

* **`brightness`**: The initial theme brightness.

* **`localizations`**: A function to apply a localization configuration.

## Create custom preview annotations

[#](#create-custom-preview-annotations)

To reduce the amount of boilerplate needed to define previews with a common set of properties, the [`Preview`](https://api.flutter.dev/flutter/widget_previews/Preview-class.html) annotation class can be extended to create custom preview annotations tailored for your project.

Here's an example of a custom preview annotation that provides custom theming data to material widgets:

dart

```
final class const MyCustomPreview({
  super.name,
  super.group,
  super.size,
  super.textScaleFactor,
  super.wrapper,
  super.brightness,
  super.localizations,
}) extends Preview {
  this : super(theme: PreviewMaterialThemeData.new);
}
​
final class const PreviewMaterialThemeData() extends PreviewThemeData {
  @override
  Widget apply(BuildContext context, Widget child) => Theme(
    data: .from(colorScheme: .fromSeed(seedColor: Colors.lime)),
    child: child,
  );
}
```

Extending the [`Preview`](https://api.flutter.dev/flutter/widget_previews/Preview-class.html) annotation class also allows for overriding the [`Preview.transform()`](https://api.flutter.dev/flutter/widget_previews/Preview/transform.html) method. This method is invoked by the widget previewer and can be used to modify the preview at runtime, allowing for preview configurations that would not otherwise be possible in a `const` context:

dart

```
final class const TransformativePreview({
  super.name,
  super.group,
  super.size,
  super.textScaleFactor,
  super.wrapper,
  super.brightness,
  super.localizations,
}) extends Preview {
  PreviewThemeData _themeBuilder() =>
      PreviewMaterialThemeData(brightness: brightness);
​
  @override
  Preview transform() {
    final originalPreview = super.transform();
​
    final themeVariant = switch (brightness) {
      null => 'Responsive',
      final b => b.name,
    };
​
    // Creates a PreviewBuilder that can be used to modify
    // the preview contents.
    final builder = originalPreview.toBuilder();
    builder
      ..name = 'Transformed - ${originalPreview.name}[$themeVariant]'
      ..theme = _themeBuilder;
​
    // Returns the updated Preview instance.
    return builder.build();
  }
}
​
final class const PreviewMaterialThemeData({final Brightness? _brightness})
    extends PreviewThemeData {
  @override
  Widget apply(BuildContext context, Widget child) {
    return Theme(
      data: .from(
        colorScheme: .fromSeed(
          seedColor: Colors.lime,
          brightness: _brightness ?? MediaQuery.platformBrightnessOf(context),
        ),
      ),
      child: child,
    );
  }
}
```

## Creating multiple preview configurations

[#](#creating-multiple-preview-configurations)

Creating multiple previews with different configurations can be as simple as applying multiple [`@Preview`](https://api.flutter.dev/flutter/widget_previews/Preview-class.html) annotations to a single function or constructor:

dart

```
@Preview(
  group: 'Brightness',
  name: 'Example - light',
  brightness: Brightness.light,
)
@Preview(
  group: 'Brightness',
  name: 'Example - dark',
  brightness: Brightness.dark,
)
Widget buttonPreview() => const ButtonShowcase();
```

To simplify creating multiple previews with common configurations, you can extend the [`MultiPreview`](https://api.flutter.dev/flutter/widget_previews/MultiPreview-class.html) to define a custom annotation that creates multiple previews. The following [`MultiPreview`](https://api.flutter.dev/flutter/widget_previews/MultiPreview-class.html) creates the same two previews as the previous example:

dart

```
/// Creates light and dark mode previews.
final class MultiBrightnessPreview extends MultiPreview {
  const MultiBrightnessPreview();
​
  @override
  List<Preview> get previews => const [
        Preview(
          group: 'Brightness',
          name: 'Example - light',
          brightness: Brightness.light,
        ),
        Preview(
          group: 'Brightness',
          name: 'Example - dark',
          brightness: Brightness.dark,
        ),
      ];
}
​
@MultiBrightnessPreview()
Widget buttonPreview() => const ButtonShowcase();
```

Like [`Preview`](https://api.flutter.dev/flutter/widget_previews/Preview-class.html), [`MultiPreview`](https://api.flutter.dev/flutter/widget_previews/MultiPreview-class.html) also provides a [`MultiPreview.transform()`](https://api.flutter.dev/flutter/widget_previews/MultiPreview/transform.html) method to perform transformations on each preview at runtime:

dart

```
/// Creates light and dark mode previews.
final class MultiBrightnessPreview extends MultiPreview {
  const MultiBrightnessPreview({required this.name});
​
  final String name;
​
  @override
  List<Preview> get previews => const [
        Preview(brightness: Brightness.light),
        Preview(brightness: Brightness.dark),
      ];
​
  @override
  List<Preview> transform() {
    final previews = super.transform();
    return previews.map((preview) {
      final builder = preview.toBuilder()
        ..group = 'Brightness'
        // Building names based on values provided to the annotation
        // isn't possible within a constant constructor. However,
        // there's no such restriction when building a Preview at
        // runtime.
        ..name = '$name - ${preview.brightness!.name}';
      return builder.toPreview();
    }).toList();
  }
}
​
@MultiBrightnessPreview(name: 'Example')
Widget buttonPreview() => const ButtonShowcase();
```

## Restrictions and limitations

[#](#restrictions-and-limitations)

The Flutter Widget Previewer has certain restrictions you should be aware of:

* **Public callback names**: All callback arguments provided to preview annotations must be public and constant. This is required for the previewer's code generation implementation to work correctly.

* **Unsupported APIs**: Native plugins and any APIs from the `dart:io` or `dart:ffi` libraries are not supported. This is because the widget previewer is built with Flutter Web, which doesn't have access to the underlying native platform APIs. While web plugins might work in your browser, there is no guarantee that they will work in your IDE.

  Widgets with transitive dependencies on `dart:io` or `dart:ffi` will load correctly, but all APIs from these libraries will throw an exception when invoked.

  See the [Dart documentation on conditional imports](https://dart.dev/tools/pub/create-packages#conditionally-importing-and-exporting-library-files) for details on how to structure your application to cleanly support platform-specific libraries when targeting multiple platforms.

* **Asset paths**: When using `fromAsset` APIs from `dart:ui` to load resources, you must use **package-based paths** instead of direct local paths. This ensures that the assets can be correctly located and loaded within the previewer's web environment. For example, use `'packages/my_package_name/assets/my_image.png'` instead of `'assets/my_image.png'`.

  When previewing web widgets, the previewer automatically synchronizes assets from your host project's `web/` directory, applying custom theming or `index.html` customizations.

* **Unconstrained widgets**: Unconstrained widgets are automatically constrained to approximately half the height and width of the widget previewer. This behavior is likely to change in the future, so constraints should be applied using the `size` parameter when possible.

* **Multi-project support in IDEs**: The widget previewer currently only supports displaying previews contained within a single project or Pub workspace. We’re actively investigating options to support IDE sessions with multiple Flutter projects ([#173550](https://github.com/flutter/flutter/issues/173550)).

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-09-29. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/widget-previewer.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/tools/widget-previewer\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/widget-previewer.md "Report an issue with this page").

----
url: https://docs.flutter.dev/dash
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Who is Dash?](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

This is Dash:

Dash is the mascot for the Dart language and the Flutter framework.

## How did it all start?

[#](#how-did-it-all-start)

As soon as Shams Zakhour started working as a Dart writer at Google in December 2013, she started advocating for a Dart mascot. After documenting Java for 14 years, she had observed how beloved the Java mascot, [Duke](https://www.oracle.com/java/duke.html), had become, and she wanted something similar for Dart.

But the idea didn't gain momentum until 2017, when one of the Flutter engineers, Nina Chen, suggested it on an internal mailing list. The Flutter VP at the time, Joshy Joseph, approved the idea and asked the organizer for the [2018 Dart Conference](https://events.dartlang.org/2018/dartconf/), Linda Rasmussen, to make it happen.

Once Shams heard about these plans, she rushed to Linda and asked to own and drive the project to produce the plushies for the conference. Linda had already elicited some design sketches, which she handed off. Starting with the sketches, Shams located a vendor who could work within an aggressive deadline (competing with Lunar New Year), and started the process of creating the specs for the plushy.

That's right, Dash was originally a **Dart** mascot, not a Flutter mascot.

Here are some early mockups and one of the first prototypes:

The first prototype had uneven eyes

## Why a hummingbird?

[#](#why-a-hummingbird)

Early on, a hummingbird image was created for the Dart team to use for presentations and the web. The hummingbird represents that Dart is a speedy language.

However, hummingbirds are pointed and angular and we wanted a cuddly plushy, so we chose a round hummingbird.

Shams specified which color would go where, the tail shape, the tuft of hair, the eyes...all the little details. The vendor sent the specs to two manufacturers who returned the prototypes some weeks later.

[Watch on YouTube in a new tab: "Introducing Dash at the January 2018 Dart Conference"](https://www.youtube.com/watch/R5vIUjRZaZA)

While the manufacturing process was proceeding, Shams chose a name for the plushy: Dash, because it was an early code name for the Dart project, it was gender neutral, and it seemed appropriate for a hummingbird.

Many boxes of Dash plushies arrived in southern California just in time for the conference. They were eagerly adopted by Dart *and* Flutter enthusiasts.

The people have spoken, so Dash is now the mascot for Flutter **and** Dart.

Dash 1.0

Conference swag

Since the creation of Dash 1.0, we've made two more versions. Marketing slightly changed the Dart and Flutter color scheme after Dash 1.0 was created, so Dash 2.0 reflects the updated scheme (which removed the green color). Dash 2.1 is a smaller size and has a few more color tweaks. The smaller size is easier to ship, and fits better in a claw machine!

Dash 2.0 and 2.1

## Dash facts

[#](#dash-facts)

* Dash is female, but she doesn't mind being called *they*, *their*, *he*, or *him*.

* Dash has an [Instagram account](https://www.instagram.com/dash_the_dartlang_plushy/).

* Dash has a **straight** beak. **Please, don't depict Dash with a curved beak.**

* We also have Mega-Dash, a life-sized mascot who is currently resting in a Google office.

  Mega-Dash made her first appearance at the [Flutter Interact](https://www.youtube.com/playlist?list=PLjxrf2q8roU0o0wKRJTjyN0pSUA6TI8lg) event in Brooklyn, New York, on December 11, 2019.

  [Watch on YouTube in a new tab: "Flutter Interact 2019 highlights"](https://www.youtube.com/watch/EgBMGDtHZhE)

* We also have a Dash puppet that Shams made from one of the first plushies.

  A number of our YouTube videos feature the Dash puppet, voiced by Emily Fortuna, one of the early (and much loved) Flutter Developer Advocates.

  [Watch on YouTube in a new tab: "Building DashCast, a Flutter-based podcast app"](https://www.youtube.com/watch/oyy_1CjNdBU)

  [Watch on YouTube in a new tab: "Revisiting DashCast, a Flutter-based podcast app"](https://www.youtube.com/watch/dsiLVNDJ3t0)

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/dash/index.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/dash\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/dash/index.md "Report an issue with this page").

----
url: https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.41.0
----

The 2.41.0 release of the Dart and Flutter DevTools includes the following changes among other general improvements. To learn more about DevTools, check out the [DevTools overview](/tools/devtools/overview).

## General updates

[#](#general-updates)

* Persist filter settings across sessions. - [#8447](https://github.com/flutter/devtools/pull/8447), [#8456](https://github.com/flutter/devtools/pull/8456) [#8470](https://github.com/flutter/devtools/pull/8470)

## Inspector updates

[#](#inspector-updates)

* Added an option to the new Inspector's settings to allow auto-refreshing the widget tree after a hot-reload. - [#8483](https://github.com/flutter/devtools/pull/8483)

## Network profiler updates

[#](#network-profiler-updates)

* Added a filter text field to the top-level Network profiler controls. - [#8469](https://github.com/flutter/devtools/pull/8469)

## Logging updates

[#](#logging-updates)

* Fetch log details immediately upon receiving logs so that log data is not lost due to lazy loading. - [#8421](https://github.com/flutter/devtools/pull/8421)
* Reduce initial page load time. - [#8500](https://github.com/flutter/devtools/pull/8500)
* Added support for displaying metadata, such as log severity, category, zone, and isolate - [#8419](https://github.com/flutter/devtools/pull/8419), [#8439](https://github.com/flutter/devtools/pull/8439), [#8441](https://github.com/flutter/devtools/pull/8441). It is now also possible to search and filter by these metadata values. - [#8473](https://github.com/flutter/devtools/pull/8473)
* Add a filter text field to the top-level Logging controls. - [#8427](https://github.com/flutter/devtools/pull/8427)
* Added support for filtering by log severity / levels. - [#8433](https://github.com/flutter/devtools/pull/8433)
* Added a setting to set the log retention limit. - [#8493](https://github.com/flutter/devtools/pull/8493)
* Added a button to toggle the log details display between raw text and JSON. - [#8445](https://github.com/flutter/devtools/pull/8445)
* Fixed a bug where logs would get out of order after midnight. - [#8420](https://github.com/flutter/devtools/pull/8420)
* Automatically scroll logs table to the bottom on the initial load. - [#8437](https://github.com/flutter/devtools/pull/8437)

## VS Code Sidebar updates

[#](#vs-code-sidebar-updates)

* The legacy `postMessage` version of the VS Code sidebar has been removed in favor of the DTD-powered version. Trying to access the legacy sidebar will show a message advising to update your Dart VS Code extension. The Dart VS Code extension was the only user of the legacy sidebar and migrated off in v3.96.

## Full commit history

[#](#full-commit-history)

To find a complete list of changes in this release, check out the [DevTools git log](https://github.com/flutter/devtools/tree/v2.41.0).

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/release-notes/release-notes-2.41.0.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.41.0\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/release-notes/release-notes-2.41.0.md "Report an issue with this page").

----
url: https://docs.flutter.dev/testing/integration-tests.md
----

\# Check app functionality with an integration test > Learn how to write integration tests

<!--?code-excerpt path-base="testing/integration_tests/how_to"?-->

\## Introduction This guide describes how to run integration tests with your Flutter app. With it, you'll learn how to do the following: \* Set up integration tests. \* Verify if an app displays specific text. \* Tap specific widgets. \* Run integration tests. The guide references the \`counter\_app\` project that comes with Flutter and the Flutter \[\`integration\_test\`]\[] package. The \`integration\_test\` package lets you: \* Use the \`flutter drive\` command to run tests on a physical device or emulator. \* Run on \[Firebase Test Lab]\[], to automate testing on a variety of devices. \* Use \[flutter\_test]\[] APIs to write tests in a style similar to \[widget tests]\[]. ## Create a new app to test Integration testing requires an app to test. This example uses the built-in \*\*Counter App\*\* example that Flutter produces when you run the \`flutter create\` command. The counter app allows a user to tap on a button to increase a counter. 1. To create an instance of the built-in Flutter app, run the following command in your terminal: \`\`\`console $ flutter create counter\_app \`\`\` 1. Change into the \`counter\_app\` directory. 1. Open \`lib/main.dart\` in your preferred IDE. 1. Add a \`key\` parameter to the \`floatingActionButton()\` widget with an instance of a \`Key\` class with a string value of \`increment\`. \`\`\`dart floatingActionButton: FloatingActionButton( \[!key: const ValueKey('increment'),!] onPressed: \_incrementCounter, tooltip: 'Increment', child: const Icon(Icons.add), ), \`\`\` 1. Save your \`lib/main.dart\` file. After these changes, the \`lib/main.dart\` file should resemble the following code.

<!--?code-excerpt "lib/main.dart"?-->

\`\`\`dart title="lib/main.dart" import 'package:material\_ui/material\_ui.dart'; void main() => runApp(const MyApp()); class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return const MaterialApp( title: 'Counter App', home: MyHomePage(title: 'Counter App Home Page'), ); } } class MyHomePage extends StatefulWidget { const MyHomePage({super.key, required this.title}); final String title; @override State createState() => \_MyHomePageState(); } class \_MyHomePageState extends State { int \_counter = 0; void \_incrementCounter() { setState(() { \_counter++; }); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text(widget.title)), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: \[ const Text('You have pushed the button this many times:'), Text( '$\_counter', style: Theme.of(context).textTheme.headlineMedium, ), ], ), ), floatingActionButton: FloatingActionButton( // Provide a Key to this button. This allows finding this // specific button inside the test suite, and tapping it. key: const Key('increment'), onPressed: \_incrementCounter, tooltip: 'Increment', child: const Icon(Icons.add), ), ); } } \`\`\` ## Add the \`integration\_test\` dependency You need to add the testing packages to your new app. To add \`integration\_test\` and \`flutter\_test\` packages as \`dev\_dependencies\` using \`sdk: flutter\`, run following command. \`\`\`console $ flutter pub add "dev:integration\_test:{sdk: flutter}" \`\`\` Output: \`\`\`console Building flutter tool... Resolving dependencies... Got dependencies. Resolving dependencies... + file 7.0.0 + flutter\_driver 0.0.0 from sdk flutter + fuchsia\_remote\_debug\_protocol 0.0.0 from sdk flutter + integration\_test 0.0.0 from sdk flutter ... test\_api 0.6.1 (0.7.1 available) vm\_service 13.0.0 (14.2.1 available) + webdriver 3.0.3 Changed 8 dependencies! 7 packages have newer versions incompatible with dependency constraints. Try \`flutter pub outdated\` for more information. \`\`\` Updated \`pubspec.yaml\` file: \`\`\`yaml title="pubspec.yaml" # ... dev\_dependencies: # ... added dependencies flutter\_test: sdk: flutter flutter\_lints: ^6.0.0 \[!integration\_test:!] \[!sdk: flutter!] # ... \`\`\` ## Create the integration test files Integration tests reside in a separate directory inside your Flutter project. 1. Create a new directory named \`integration\_test\`. 1. Add empty file named \`app\_test.dart\` in that directory. The resulting directory tree should resemble the following: \`\`\`plaintext counter\_app/ lib/ main.dart integration\_test/ app\_test.dart \`\`\` ## Write the integration test The integration test file consists of a Dart code file with dependencies on \`integration\_test\`, \`flutter\_test\`, and your app's Dart file. 1. Open your \`integration\_test/app\_test.dart\` file in your preferred IDE. 1. Copy the following code and paste it into your \`integration\_test/app\_test.dart\` file. The last import should point to the \`main.dart\` file of your \`counter\_app\`. (This \`import\` points to the example app called \`introduction\`.)

<!--?code-excerpt "integration_test/counter_test.dart (initial)" replace="/introduction/counter_app/g"?-->

\`\`\`dart title="integration\_test/counter\_test.dart" import 'package:flutter\_test/flutter\_test.dart'; import 'package:how\_to/main.dart'; import 'package:integration\_test/integration\_test.dart'; import 'package:material\_ui/material\_ui.dart'; void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); group('end-to-end test', () { testWidgets('tap on the floating action button, verify counter', ( tester, ) async { // Load app widget. await tester.pumpWidget(const MyApp()); // Verify the counter starts at 0. expect(find.text('0'), findsOneWidget); // Finds the floating action button to tap on. final fab = find.byKey(const ValueKey('increment')); // Emulate a tap on the floating action button. await tester.tap(fab); // Trigger a frame. await tester.pumpAndSettle(); // Verify the counter increments by 1. expect(find.text('1'), findsOneWidget); }); }); } \`\`\` This example goes through three steps: 1. Initialize \`IntegrationTestWidgetsFlutterBinding\`. This singleton service executes tests on a physical device. 2. Interact and test widgets using the \`WidgetTester\` class. 3. Test the important scenarios. ## Run integration tests The integration tests that run vary depending on the platform on which you test. \* To test a desktop platform, use the command line or a CI system. \* To test a mobile platform, use the command line or Firebase Test Lab. \* To test in a web browser, use the command line. --- ### Test on a desktop platform

Expand if you test Linux apps using a CI system

To test a Linux app, your CI system must invoke an X server first. In the GitHub Action, GitLab Runner, or similar configuration file, set the integration test to work \_with\_ the \`xvfb-run\` tool. Doing this invokes an X Window system into which Flutter can launch and test your Linux app. As an example using GitHub Actions, your \`jobs.setup.steps\` should include a step resembling the following: \`\`\`yaml - name: Run Integration Tests uses: username/xvfb-action\@v1.1.2 with: run: flutter test integration\_test -d linux -r github \`\`\` This starts the integration test within an X Window. If you don't configure your integration in this way, Flutter returns an error. \`\`\`console Building Linux application... Error waiting for a debug connection: The log reader stopped unexpectedly, or never started. \`\`\` To test on a macOS, Windows, or Linux platform, complete the following tasks. 1. Run the following command from the root of the project. \`\`\`console $ flutter test integration\_test/app\_test.dart \`\`\` 1. If offered a choice of platform to test, choose the desktop platform. Type \`1\` to choose the desktop platform. Based on platform, the command result should resemble the following output. \`\`\`ps PS C:\path\to\counter\_app> flutter test .\integration\_test\app\_test.dart Resolving dependencies... Downloading packages... flutter\_lints 3.0.2 (4.0.0 available) leak\_tracker 10.0.4 (10.0.5 available) leak\_tracker\_flutter\_testing 3.0.3 (3.0.5 available) lints 3.0.0 (4.0.0 available) material\_color\_utilities 0.8.0 (0.11.1 available) meta 1.12.0 (1.15.0 available) test\_api 0.7.0 (0.7.1 available) vm\_service 14.2.1 (14.2.2 available) Got dependencies! 8 packages have newer versions incompatible with dependency constraints. Try \`flutter pub outdated\` for more information. Connected devices: Windows (desktop) • windows • windows-x64 • Microsoft Windows \[Version 10.0.22631.3593] Chrome (web) • chrome • web-javascript • Google Chrome 124.0.6367.207 Edge (web) • edge • web-javascript • Microsoft Edge 124.0.2478.97 \[1]: Windows (windows) \[2]: Chrome (chrome) \[3]: Edge (edge) Please choose one (or "q" to quit): 1 00:00 +0: loading C:/path/to/counter\_app/integration\_test/app\_test.dart B 00:29 +0: loading C:/path/to/counter\_app/counter\_app/integration\_test/app\_test.dart 29.1s √ Built build\windows\x64\runner\Debug\counter\_app.exe 00:31 +1: All tests passed! \`\`\` \`\`\`console $ flutter test integration\_test Resolving dependencies... Downloading packages... flutter\_lints 3.0.2 (4.0.0 available) > leak\_tracker 10.0.4 (was 10.0.0) (10.0.5 available) > leak\_tracker\_flutter\_testing 3.0.3 (was 2.0.1) (3.0.5 available) > leak\_tracker\_testing 3.0.1 (was 2.0.1) lints 3.0.0 (4.0.0 available) material\_color\_utilities 0.8.0 (0.11.1 available) > meta 1.12.0 (was 1.11.0) (1.15.0 available) > test\_api 0.7.0 (was 0.6.1) (0.7.1 available) > vm\_service 14.2.1 (was 13.0.0) (14.2.2 available) Changed 6 dependencies! 8 packages have newer versions incompatible with dependency constraints. Try \`flutter pub outdated\` for more information. Connected devices: macOS (desktop) • macos • darwin-arm64 • macOS 14.4.1 23E224 darwin-arm64 Mac Designed for iPad (desktop) • mac-designed-for-ipad • darwin • macOS 14.4.1 23E224 darwin-arm64 Chrome (web) • chrome • web-javascript • Google Chrome 124.0.6367.208 No wireless devices were found. \[1]: macOS (macos) \[2]: Mac Designed for iPad (mac-designed-for-ipad) \[3]: Chrome (chrome) Please choose one (or "q" to quit): 1 00:01 +0: loading /path/to/counter\_app/integration\_test/app\_test.dart R 00:02 +0: loading /path/to/counter\_app/integration\_test/app\_test.dart 846ms 00:03 +0: loading /path/to/counter\_app/integration\_test/app\_test.dart B Building macOS application... ✓ Built build/macos/Build/Products/Debug/counter\_app.app 00:32 +1: All tests passed! \`\`\` \`\`\`console $ flutter test integration\_test/app\_test.dart Connected devices: Linux (desktop) • linux • linux-x64 • Ubuntu 22.04.4 LTS 6.5.0-35-generic Chrome (web) • chrome • web-javascript • Google Chrome 104.0.5112.101 \[1]: Linux (linux) \[2]: Chrome (chrome) Please choose one (or "q" to quit): 1 00:00 +0: /path/to/counter\_app/integration\_test/app\_test.dart B 00:16 +0: /path/to/counter\_app/integration\_test/app\_test.dart ✓ Built build/linux/x64/debug/bundle/counter\_app \`\`\` --- ### Test on an Android device To test on a real Android device, complete the following tasks. 1. Connect the Android device. 1. Run the following command from the root of the project. \`\`\`console $ flutter test integration\_test/app\_test.dart \`\`\` The result should resemble the following output. \`\`\`console $ flutter test integration\_test/app\_test.dart 00:04 +0: loading /path/to/counter\_app/integration\_test/app\_test.dart 00:15 +0: loading /path/to/counter\_app/integration\_test/app\_test.dart 00:18 +0: loading /path/to/counter\_app/integration\_test/app\_test.dart 2,387ms Installing build/app/outputs/flutter-apk/app.apk... 612ms 00:21 +1: All tests passed! \`\`\` 1. Verify that the test removed the Counter App when it finished. If not, subsequent tests fail. If needed, press on the app and choose \*\*Remove App\*\* from the context menu. --- ### Test on an iOS device To test on a real iOS device, complete the following tasks. 1. Connect the iOS device. 1. Run the following command from the root of the project. \`\`\`console $ flutter test integration\_test/app\_test.dart \`\`\` The result should resemble the following output. \`\`\`console $ flutter test integration\_test/app\_test.dart 00:04 +0: loading /path/to/counter\_app/integration\_test/app\_test.dart 00:15 +0: loading /path/to/counter\_app/integration\_test/app\_test.dart 00:18 +0: loading /path/to/counter\_app/integration\_test/app\_test.dart 2,387ms Xcode build done. 13.5s 00:21 +1: All tests passed! \`\`\` 1. Verify that the test removed the Counter App when it finished. If not, subsequent tests fail. If needed, press on the app and choose \*\*Remove App\*\* from the context menu. --- ### Test in a web browser To test in a web browser, perform the following steps. 1. Install \[ChromeDriver]\[] into the directory of your choice. \`\`\`console $ npx @puppeteer/browsers install chromedriver\@stable \`\`\` To simplify the install, this command uses the \[\`@puppeteer/browsers\`]\[puppeteer] Node library. \[puppeteer]: https\://www\.npmjs.com/package/@puppeteer/browsers 1. Add the path to ChromeDriver to your \`$PATH\` environment variable. 1. Verify the ChromeDriver install succeeded. \`\`\`console $ chromedriver --version ChromeDriver 124.0.6367.60 (8771130bd84f76d855ae42fbe02752b03e352f17-refs/branch-heads/6367@{#798}) \`\`\` 1. In your \`counter\_app\` project directory, create a new directory named \`test\_driver\`. \`\`\`console $ mkdir test\_driver \`\`\` 1. In this directory, create a new file named \`integration\_test.dart\`. 1. Copy the following code and paste it into your \`integration\_test.dart\` file.

<!--?code-excerpt "test_driver/integration_test.dart"?-->

\`\`\`dart title="test\_driver/integration\_test.dart" import 'package:integration\_test/integration\_test\_driver.dart'; Future main() => integrationDriver(); \`\`\` 1. Launch \`chromedriver\` as follows: \`\`\`console $ chromedriver --port=4444 \`\`\` 1. From the root of the project, run the following command: \`\`\`console $ flutter drive \ --driver=test\_driver/integration\_test.dart \ --target=integration\_test/app\_test.dart \ -d chrome \`\`\` The response should resemble the following output: \`\`\`console Resolving dependencies... leak\_tracker 10.0.0 (10.0.5 available) leak\_tracker\_flutter\_testing 2.0.1 (3.0.5 available) leak\_tracker\_testing 2.0.1 (3.0.1 available) material\_color\_utilities 0.8.0 (0.11.1 available) meta 1.11.0 (1.14.0 available) test\_api 0.6.1 (0.7.1 available) vm\_service 13.0.0 (14.2.1 available) Got dependencies! 7 packages have newer versions incompatible with dependency constraints. Try \`flutter pub outdated\` for more information. Launching integration\_test/app\_test.dart on Chrome in debug mode... Waiting for connection from debug service on Chrome... 10.9s This app is linked to the debug service: ws\://127.0.0.1:51523/3lofIjIdmbs=/ws Debug service listening on ws\://127.0.0.1:51523/3lofIjIdmbs=/ws 00:00 +0: end-to-end test tap on the floating action button, verify counter 00:01 +1: (tearDownAll) 00:01 +2: All tests passed! All tests passed. Application finished. \`\`\` To run this as a headless test, run \`flutter drive\` with \`-d web-server\` option: \`\`\`console $ flutter drive \ --driver=test\_driver/integration\_test.dart \ --target=integration\_test/app\_test.dart \ -d web-server \`\`\` To learn more, see the \[Running Flutter driver tests with web]\[] wiki page. --- ### Test in Firebase Test Lab (Android) You can use Firebase Test Lab to test Android targets. #### Android setup Follow the instructions in the \[Android Device Testing]\[] section of the README. #### Test Lab project setup 1. Launch your \[Firebase Console]\[]. 1. Create a new Firebase project if necessary. 1. Navigate to \*\*Quality > Test Lab\*\*. #### Upload an Android APK Complete the following steps to upload an Android APK. 1. Create an APK using Gradle. \`\`\`console // Go to the Android directory which contains the gradlew script $ pushd android // Build a debug APK for Flutter with gradlew // Note that a standard --release build will not include package:integration\_test $ flutter build apk --debug // Build an Android test APK $ ./gradlew app:assembleAndroidTest // Build a debug APK by passing in an integration test $ ./gradlew app:assembleDebug -Ptarget=integration\_test/\_test.dart \`\`\` \* \`\_test.dart\`: The file created in the \*\*Project Setup\*\* section. 1. If needed, pass parameters into the integration test as a comma-separated list. Encode all parameters as \`base64\`. \`\`\`console $ ./gradlew project:task -Pdart-defines="{base64 (key=value)}\[, ...]" \`\`\` \* \`(key=value)}\[, ...]\`: Replace this with a comma-separated list of key value pairs. 1. Return to your previous directory. \`\`\`console $ popd \`\`\` For additional instructions, see the \[Firebase Test Lab section of the README]\[]. #### Start Robo test To use Robo test to run integration tests, complete the following steps. 1. Drag the debug APK from \`/build/app/outputs/apk/debug\` into the \*\*Android Robo Test\*\* target on the web page. For example: 1. Click \*\*Run a test\*\*. 1. Select the \*\*Instrumentation\*\* test type. 1. Add the App APK to the \*\*App APK or AAB\*\* box. \`/build/app/outputs/apk/debug/.apk\` 1. Add the Test APK to the \*\*Test APK\*\* box. \`/build/app/outputs/apk/androidTest/debug/.apk\` 1. If a failure occurs, click the red icon to view the output: --- ### Test in Firebase Test Lab (iOS) You can use Firebase Test Lab to test iOS targets. #### iOS setup Follow the \[iOS Device Testing instructions]\[]. #### Test Lab project setup 1. Launch your \[Firebase Console]\[]. 1. Create a new Firebase project if necessary. 1. Navigate to \*\*Quality > Test Lab\*\*. #### Upload Xcode tests through the Firebase Console To learn how to upload tests from a ZIP file, using the Firebase Test Lab Console, consult the \[Firebase Test Lab iOS instructions]\[]. #### Upload Xcode tests to Firebase Console with the command line To learn how to upload tests from a ZIP file from the command line to the Firebase Test Lab Console, consult the \[iOS Device Testing instructions]\[]. \[\`integration\_test\`]: https\://github.com/flutter/flutter/tree/main/packages/integration\_test#integration\_test \[Android Device Testing]: https\://github.com/flutter/flutter/tree/main/packages/integration\_test#android-device-testing \[ChromeDriver]: https\://googlechromelabs.github.io/chrome-for-testing/ \[Download EdgeDriver]: https\://developer.microsoft.com/en-us/microsoft-edge/tools/webdriver/ \[Download GeckoDriver]: https\://github.com/mozilla/geckodriver/releases \[Firebase Console]: https\://console.firebase.google.com/ \[Firebase Test Lab section of the README]: https\://github.com/flutter/flutter/tree/main/packages/integration\_test#firebase-test-lab \[Firebase Test Lab]: https\://firebase.google.com/docs/test-lab \[Firebase Test Lab iOS instructions]: https\://firebase.google.com/docs/test-lab/ios/firebase-console \[flutter\_test]: https\://api.flutter.dev/flutter/flutter\_test/flutter\_test-library.html \[Integration testing]: /testing/integration-tests \[iOS Device Testing instructions]: https\://github.com/flutter/flutter/tree/main/packages/integration\_test#ios-device-testing \[Running Flutter driver tests with web]: https\://github.com/flutter/flutter/blob/main/docs/contributing/testing/Running-Flutter-Driver-tests-with-Web.md \[widget tests]: /testing/overview#widget-tests \[flutter\_driver]: https\://api.flutter.dev/flutter/flutter\_driver/flutter\_driver-library.html \[integration\_test usage]: https\://github.com/flutter/flutter/tree/main/packages/integration\_test#usage \[samples]: https\://github.com/flutter/samples \[testing\_app]: https\://github.com/flutter/samples/tree/main/testing\_app/integration\_test \[testWidgets]: https\://api.flutter.dev/flutter/flutter\_test/testWidgets.html

----
url: https://docs.flutter.dev/ai/ai-toolkit/user-experience.md
----

\# User experience > How the user will experience the AI Toolkit in your app. The \[\`LlmChatView\`]\[] widget is the entry point for the interactive chat experience that AI Toolkit provides. Hosting an instance of the \`LlmChatView\` enables a number of user experience features that don't require any additional code to use: \* \*\*Multiline text input\*\*: Allows users to paste long text input or insert new lines into their text as they enter it. \* \*\*Voice input\*\*: Allows users to input prompts using speech for ease of use. \* \*\*Multimedia input\*\*: Enables users to take pictures, send images and other file types and attach URLs as link to online resources. \* \*\*Image zoom\*\*: Enables users to zoom into image thumbnails. \* \*\*Copy to clipboard\*\*: Allows the user to copy the text of a message or a LLM response to the clipboard. \* \*\*Message editing\*\*: Allows the user to edit the most recent message for resubmission to the LLM. \* \*\*Material and Cupertino\*\*: Adapts to the best practices of both design languages. \[\`LlmChatView\`]: https\://pub.dev/documentation/flutter\_ai\_toolkit/latest/flutter\_ai\_toolkit/LlmChatView-class.html ## Multiline text input The user has options when it comes to submitting their prompt once they've finished composing it, which again differs depending on their platform: \* \*\*Mobile\*\*: Tap the \*\*Submit\*\* button \* \*\*Web\*\*: Press \*\*Enter\*\* or tap the \*\*Submit\*\* button \* \*\*Desktop\*\*: Press \*\*Enter\*\* or tap the \*\*Submit\*\* button In addition, the chat view supports text prompts with embedded newlines in them. If the user has existing text with newlines, they can paste them into the prompt text field as normal. If they'd like to embed newlines into their prompt manually as they enter it, they can do so. The gesture for that activity differs based on the platform they're using: \* \*\*Mobile\*\*: Tap Return key on the virtual keyboard \* \*\*Web\*\*: Press \`Shift+Enter\` \* \*\*Desktop\*\*: Press \`Shift+Enter\` These options look like the following: \*\*Web and Desktop\*\*: !\[Screenshot of entering text on desktop]\(/assets/images/docs/ai-toolkit/desktop-enter-text.png) \*\*Mobile\*\*: !\[Screenshot of entering text on mobile]\(/assets/images/docs/ai-toolkit/mobile-enter-text.png) ## Voice input In addition to text input the chat view can take an audio recording as input by tapping the Mic button, which is visible when no text has yet been entered. Tap the \*\*Mic\*\* button to start the recording: !\[Screenshot of entering text]\(/assets/images/docs/ai-toolkit/enter-textfield.png) Select the \*\*Stop\*\* button to translate the user's voice input into text: This text can then be edited, augmented and submitted as normal. !\[Screenshot of entered voice]\(/assets/images/docs/ai-toolkit/enter-voice-into-textfield.png) ## Multimedia input !\[Textfield containing "Testing, testing, one, two, three"]\(/assets/images/docs/ai-toolkit/multi-media-testing-testing.png) The chat view can also take images and files as input to pass along to the underlying LLM. The user can select the \*\*Plus\*\* button to the left of the text input and choose from the \*\*Take Photo\*\*, \*\*Image Gallery\*\*, \*\*Attach File\*\* and \*\*Attach Link\*\* icons: !\[Screenshot of the 4 icons]\(/assets/images/docs/ai-toolkit/multi-media-icons.png) The \*\*Take Photo\*\* button allows the user to use their device's camera to take a photo: !\[Selfie image]\(/assets/images/docs/ai-toolkit/selfie.png) Select the \*\*Image Gallery\*\* button to let the user upload from their device's image gallery: !\[Download image from gallery]\(/assets/images/docs/ai-toolkit/download-from-gallery.png) Select the \*\*Attach File\*\* button to let the user select a file of any type available on their device, like a PDF or TXT file. Select the \*\*Attach Link\*\* button to let the user enter a link to a web page or an online file. Once a photo, image, file, or link has been selected, it becomes an attachment and shows up as a thumbnail associated with the currently active prompt: !\[Thumbnails of images]\(/assets/images/docs/ai-toolkit/image-thumbnails.png) The user can remove an attachment by clicking the \*\*X\*\* button on the thumbnail. ## Image zoom The user can zoom into an image thumbnail by tapping it: !\[Zoomed image]\(/assets/images/docs/ai-toolkit/image-zoom.png) Pressing the \*\*Esc\*\* key or tapping anywhere outside the image dismisses the zoomed image. ## Copy to clipboard The user can copy any text prompt or LLM response in their current chat in a variety of ways. On the desktop or the web, the user can mouse to select the text on their screen and copy it to the clipboard as normal: !\[Copy to clipboard]\(/assets/images/docs/ai-toolkit/copy-to-clipboard.png) In addition, at the bottom of each prompt or response, the user can select the \*\*Copy\*\* button that pops up when they hover their mouse: !\[Select the copy button]\(/assets/images/docs/ai-toolkit/chatbot-prompt.png) On mobile platforms, the user can long-tap a prompt or response and choose the Copy option: !\[Long tap to see the copy button]\(/assets/images/docs/ai-toolkit/long-tap-choose-copy.png) ## Message editing If the user would like to edit their last prompt and cause the LLM to take another run at it, they can do so. On the desktop, the user can tap the \*\*Edit\*\* button alongside the \*\*Copy\*\* button for their most recent prompt: !\[How to edit prompt]\(/assets/images/docs/ai-toolkit/how-to-edit-prompt.png) On a mobile device, the user can long-tap and get access to the \*\*Edit\*\* option on their most recent prompt: !\[How to access edit menu]\(/assets/images/docs/ai-toolkit/accessing-edit-menu.png) Once the user taps the \*\*Edit\*\* button, they enter Editing mode, which removes both the user's last prompt and the LLM's last response from the chat history, puts the text of the prompt into the text field, and provides an Editing indicator: !\[How to exit editing mode]\(/assets/images/docs/ai-toolkit/how-to-exit-editing-mode.png) In Editing mode, the user can edit the prompt as they choose and submit it to have the LLM produce a response as normal. Or, if they change their mind, they can tap the \*\*X\*\* near the Editing indicator to cancel their edit and restore their previous LLM response. ## Material and Cupertino When the \`LlmChatView\` widget is hosted in a \[Material app]\[], it uses facilities provided by the Material design language, such as Material's \[\`TextField\`]\[]. Likewise, when hosted in a \[Cupertino app]\[], it uses those facilities, such as \[\`CupertinoTextField\`]\[]. !\[Cupertino example app]\(/assets/images/docs/ai-toolkit/cupertino-chat-app.png) However, while the chat view supports both the Material and Cupertino app types, it doesn't automatically adopt the associated themes. Instead, that's set by the \`style\` property of the \`LlmChatView\` as described in the \[Custom styling]\[] documentation. \[Cupertino app]: https\://pub.dev/documentation/cupertino\_ui/latest/cupertino\_ui/CupertinoApp-class.html \[\`CupertinoTextField\`]: https\://pub.dev/documentation/cupertino\_ui/latest/cupertino\_ui/CupertinoTextField-class.html \[Custom styling]: /ai/ai-toolkit/feature-integration#custom-styling \[Material app]: https\://pub.dev/documentation/material\_ui/latest/material\_ui/MaterialApp-class.html \[\`TextField\`]: https\://pub.dev/documentation/material\_ui/latest/material\_ui/TextField-class.html

----
url: https://docs.flutter.dev/tools/devtools/vscode
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Run DevTools from VS Code](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

## Add the VS Code extensions

[#](#add-the-vs-code-extensions)

To use the DevTools from VS Code, you need the [Dart extension](https://marketplace.visualstudio.com/items?itemName=Dart-Code.dart-code). If you're debugging Flutter applications, you should also install the [Flutter extension](https://marketplace.visualstudio.com/items?itemName=Dart-Code.flutter).

## Start an application to debug

[#](#run-and-debug)

Start a debug session for your application by opening the root folder of your project (the one containing `pubspec.yaml`) in VS Code and clicking **Run > Start Debugging** (`F5`).

## Launch DevTools

[#](#launch-devtools)

Once the debug session is active and the application has started, the **Open DevTools** commands become available in the VS Code command palette (`F1`):

The chosen tool will be opened embedded inside VS Code.

You can choose to have DevTools always opened in a browser with the `dart.embedDevTools` setting, and control whether it opens as a full window or in a new column next to your current editor with the `dart.devToolsLocation` setting.

A full list of Dart/Flutter settings are available on [dartcode.org](https://dartcode.org/docs/settings/) or in the [VS Code settings editor](https://code.visualstudio.com/docs/getstarted/settings#_settings-editor). Some recommendation settings for Dart/Flutter in VS Code can also be found on [dartcode.org](https://dartcode.org/docs/recommended-settings/).

You can also see whether DevTools is running and launch it in a browser from the language status area (the `{}` icon next to **Dart** in the status bar).

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/vscode.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/tools/devtools/vscode\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/vscode.md "Report an issue with this page").

----
url: https://docs.flutter.dev/app-architecture/design-patterns/command
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top The command pattern](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

[Model-View-ViewModel (MVVM)](/app-architecture/guide#view-models) is a design pattern that separates a feature of an application into three parts: the model, the view model, and the view. Views and view models make up the UI layer of an application. Repositories and services represent the data layer of an application, or the model layer of MVVM.

A command is a class that wraps a method and helps to handle the different states of that method, such as running, complete, and error.

[View models](/app-architecture/guide#view-models) can use commands to handle interaction and run actions. You can also use them to display different UI states, like loading indicators when an action is running, or display an error dialog when an action failed.

View models can become very complex as an application grows and features become bigger. Commands can help to simplify view models and reuse code.

In this guide, you will learn how to use the command pattern to improve your view models.

## Challenges when implementing view models

[#](#challenges-when-implementing-view-models)

View model classes in Flutter are typically implemented by extending the [`ChangeNotifier`](/learn/pathway/tutorial/change-notifier) class. This allows view models to call `notifyListeners()` to refresh views when data is updated.

dart

```
class HomeViewModel extends ChangeNotifier {
  // ···
}
```

View models contain a representation of the UI state, including the data being displayed. For example, this `HomeViewModel` exposes the `User` instance to the view.

dart

```
class HomeViewModel extends ChangeNotifier {
​
  User? get user => // ...
  // ···
}
```

View models also contain actions typically triggered by the view, such as a `load` action in charge of loading the `user`.

dart

```
class HomeViewModel extends ChangeNotifier {
​
  User? get user => // ...
  // ···
  void load() {
    // load user
  }
  // ···
}
```

### UI state in view models

[#](#ui-state-in-view-models)

A view model also contains UI state besides data, such as whether the view is running or has experienced an error. This allows the app to tell the user if the action has completed successfully.

dart

```
class HomeViewModel extends ChangeNotifier {
​
  User? get user => // ...
​
  bool get running => // ...
​
  Exception? get error => // ...
​
  void load() {
    // load user
  }
  // ···
}
```

You can use the running state to display a progress indicator in the view:

dart

```
ListenableBuilder(
  listenable: widget.viewModel,
  builder: (context, _) {
    if (widget.viewModel.running) {
      return const Center(child: CircularProgressIndicator());
    }
    // ···
  },
)
```

Or use the running state to avoid executing the action multiple times:

dart

```
void load() {
  if (running) {
    return;
  }
  // load user
}
```

Managing the state of an action can get complicated if the view model contains multiple actions. For example, adding an `edit()` action to the `HomeViewModel` can lead the following outcome:

dart

```
class HomeViewModel extends ChangeNotifier {
  User? get user => // ...
​
  bool get runningLoad => // ...
​
  Exception? get errorLoad => // ...
​
  bool get runningEdit => // ...
​
  Exception? get errorEdit => // ...
​
  void load() {
    // load user
  }
​
  void edit(String name) {
    // edit user
  }
}
```

Sharing the running state between the `load()` and `edit()` actions might not always work, because you might want to show a different UI component when the `load()` action runs than when the `edit()` action runs; you'll have the same problem with the `error` state.

### Triggering UI actions from view models

[#](#triggering-ui-actions-from-view-models)

View model classes can run into problems when executing UI actions and the view model's state changes.

For example, you might want to show a `SnackBar` when an error occurs, or navigate to a different screen when an action completes. To implement this, listen for changes in the view model, and perform the action depending on the state.

In the view:

dart

```
@override
void initState() {
  super.initState();
  widget.viewModel.addListener(_onViewModelChanged);
}
​
@override
void dispose() {
  widget.viewModel.removeListener(_onViewModelChanged);
  super.dispose();
}
```

dart

```
void _onViewModelChanged() {
  if (widget.viewModel.error != null) {
    // Show Snackbar
  }
}
```

You need to clear the error state each time you execute this action, otherwise this action happens each time `notifyListeners()` is called.

dart

```
void _onViewModelChanged() {
  if (widget.viewModel.error != null) {
    widget.viewModel.clearError();
    // Show Snackbar
  }
}
```

## Command pattern

[#](#command-pattern)

You might find yourself repeating the above code over and over, implementing a different running state for each action in every view model. At that point, it makes sense to extract this code into a reusable pattern called a *command*.

A command is a class that encapsulates a view model action, and exposes the different states that an action can have.

dart

```
class Command extends ChangeNotifier {
  Command(this._action);
​
  bool get running => // ...
​
  Exception? get error => // ...
​
  bool get completed => // ...
​
  void Function() _action;
​
  void execute() {
    // run _action
  }
​
  void clear() {
    // clear state
  }
}
```

In the view model, instead of defining an action directly with a method, you create a command object:

dart

```
class HomeViewModel extends ChangeNotifier {
  HomeViewModel() {
    load = Command(_load)..execute();
  }
​
  User? get user => // ...
​
  late final Command load;
​
  void _load() {
    // load user
  }
}
```

The previous `load()` method becomes `_load()`, and instead the command `load` gets exposed to the `View`. The previous `running` and `error` states can be removed, as they are now part of the command.

### Executing a command

[#](#executing-a-command)

Instead of calling `viewModel.load()` to run the load action, now you call `viewModel.load.execute()`.

The `execute()` method can also be called from within the view model. The following line of code runs the `load` command when the view model is created.

dart

```
HomeViewModel() {
  load = Command(_load)..execute();
}
```

The `execute()` method sets the running state to `true` and resets the `error` and `completed` states. When the action finishes, the `running` state changes to `false` and the `completed` state to `true`.

If the `running` state is `true`, the command cannot begin executing again. This prevents users from triggering a command multiple times by pressing a button rapidly.

The command’s `execute()` method captures any thrown `Exceptions` automatically and exposes them in the `error` state.

The following code shows a sample `Command` class that has been simplified for demo purposes. You can see a full implementation at the end of this page.

dart

```
class Command extends ChangeNotifier {
  Command(this._action);
​
  bool _running = false;
  bool get running => _running;
​
  Exception? _error;
  Exception? get error => _error;
​
  bool _completed = false;
  bool get completed => _completed;
​
  final Future<void> Function() _action;
​
  Future<void> execute() async {
    if (_running) {
      return;
    }
​
    _running = true;
    _completed = false;
    _error = null;
    notifyListeners();
​
    try {
      await _action();
      _completed = true;
    } on Exception catch (error) {
      _error = error;
    } finally {
      _running = false;
      notifyListeners();
    }
  }
​
  void clear() {
    _running = false;
    _error = null;
    _completed = false;
  }
}
```

### Listening to the command state

[#](#listening-to-the-command-state)

The `Command` class extends from `ChangeNotifier`, allowing Views to listen to its states.

In the `ListenableBuilder`, instead of passing the view model to `ListenableBuilder.listenable`, pass the command:

dart

```
ListenableBuilder(
  listenable: widget.viewModel.load,
  builder: (context, child) {
    if (widget.viewModel.load.running) {
      return const Center(child: CircularProgressIndicator());
    }
  // ···
)
```

And listen to changes in the command state in order to run UI actions:

dart

```
@override
void initState() {
  super.initState();
  widget.viewModel.addListener(_onViewModelChanged);
}
​
@override
void dispose() {
  widget.viewModel.removeListener(_onViewModelChanged);
  super.dispose();
}
```

dart

```
void _onViewModelChanged() {
  if (widget.viewModel.load.error != null) {
    widget.viewModel.load.clear();
    // Show Snackbar
  }
}
```

### Combining command and ViewModel

[#](#combining-command-and-viewmodel)

You can stack multiple `ListenableBuilder` widgets to listen to `running` and `error` states before showing the view model data.

dart

```
body: ListenableBuilder(
  listenable: widget.viewModel.load,
  builder: (context, child) {
    if (widget.viewModel.load.running) {
      return const Center(child: CircularProgressIndicator());
    }
​
    if (widget.viewModel.load.error != null) {
      return Center(
        child: Text('Error: ${widget.viewModel.load.error}'),
      );
    }
​
    return child!;
  },
  child: ListenableBuilder(
    listenable: widget.viewModel,
    builder: (context, _) {
      // ···
    },
  ),
),
```

You can define multiple commands classes in a single view model, simplifying its implementation and minimizing the amount of repeated code.

dart

```
class HomeViewModel2 extends ChangeNotifier {
  HomeViewModel2() {
    load = Command(_load)..execute();
    delete = Command(_delete);
  }
​
  User? get user => // ...
​
  late final Command load;
​
  late final Command delete;
​
  Future<void> _load() async {
    // load user
  }
​
  Future<void> _delete() async {
    // delete user
  }
}
```

### Extending the command pattern

[#](#extending-the-command-pattern)

The command pattern can be extended in multiple ways. For example, to support a different number of arguments.

dart

```
class HomeViewModel extends ChangeNotifier {
  HomeViewModel() {
    load = Command0(_load)..execute();
    edit = Command1<String>(_edit);
  }
​
  User? get user => // ...
​
  // Command0 accepts 0 arguments
  late final Command0 load;
​
  // Command1 accepts 1 argument
  late final Command1<String> edit;
​
  Future<void> _load() async {
    // load user
  }
​
  Future<void> _edit(String name) async {
    // edit user
  }
}
```

## Putting it all together

[#](#putting-it-all-together)

In this guide, you learned how to use the command design pattern to improve the implementation of view models when using the MVVM design pattern.

Below, you can find the full `Command` class as implemented in the [Compass App example](https://github.com/flutter/samples/tree/main/compass_app) for the Flutter architecture guidelines. It also uses the [`Result` class](/app-architecture/design-patterns/result) to determine if the action completed successfully or with an error.

This implementation also includes two types of commands, a `Command0`, for actions without parameters, and a `Command1`, for actions that take one parameter.

dart

```
// Copyright 2024 The Flutter team. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
​
import 'dart:async';
​
import 'package:flutter/foundation.dart';
​
import 'result.dart';
​
/// Defines a command action that returns a [Result] of type [T].
/// Used by [Command0] for actions without arguments.
typedef CommandAction0<T> = Future<Result<T>> Function();
​
/// Defines a command action that returns a [Result] of type [T].
/// Takes an argument of type [A].
/// Used by [Command1] for actions with one argument.
typedef CommandAction1<T, A> = Future<Result<T>> Function(A);
​
/// Facilitates interaction with a view model.
///
/// Encapsulates an action,
/// exposes its running and error states,
/// and ensures that it can't be launched again until it finishes.
///
/// Use [Command0] for actions without arguments.
/// Use [Command1] for actions with one argument.
///
/// Actions must return a [Result] of type [T].
///
/// Consume the action result by listening to changes,
/// then call to [clearResult] when the state is consumed.
abstract class Command<T> extends ChangeNotifier {
  bool _running = false;
​
  /// Whether the action is running.
  bool get running => _running;
​
  Result<T>? _result;
​
  /// Whether the action completed with an error.
  bool get error => _result is Error;
​
  /// Whether the action completed successfully.
  bool get completed => _result is Ok;
​
  /// The result of the most recent action.
  ///
  /// Returns `null` if the action is running or completed with an error.
  Result<T>? get result => _result;
​
  /// Clears the most recent action's result.
  void clearResult() {
    _result = null;
    notifyListeners();
  }
​
  /// Execute the provided [action], notifying listeners and
  /// setting the running and result states as necessary.
  Future<void> _execute(CommandAction0<T> action) async {
    // Ensure the action can't launch multiple times.
    // e.g. avoid multiple taps on button
    if (_running) return;
​
    // Notify listeners.
    // e.g. button shows loading state
    _running = true;
    _result = null;
    notifyListeners();
​
    try {
      _result = await action();
    } finally {
      _running = false;
      notifyListeners();
    }
  }
}
​
/// A [Command] that accepts no arguments.
final class Command0<T> extends Command<T> {
  /// Creates a [Command0] with the provided [CommandAction0].
  Command0(this._action);
​
  final CommandAction0<T> _action;
​
  /// Executes the action.
  Future<void> execute() async {
    await _execute(_action);
  }
}
​
/// A [Command] that accepts one argument.
final class Command1<T, A> extends Command<T> {
  /// Creates a [Command1] with the provided [CommandAction1].
  Command1(this._action);
​
  final CommandAction1<T, A> _action;
​
  /// Executes the action with the specified [argument].
  Future<void> execute(A argument) async {
    await _execute(() => _action(argument));
  }
}
```

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/app-architecture/design-patterns/command.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/app-architecture/design-patterns/command\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/app-architecture/design-patterns/command.md "Report an issue with this page").

----
url: https://docs.flutter.dev/deployment/flavors.md
----

\# Set up Flutter flavors for Android > How to create build flavors specific to different release types or development environments. This guide shows you how to create Flutter flavors for an Android app. ## Overview A Flutter flavor when used with Android represents a unified term for various platform-specific features. For example, a flavor could determine which icon, app name, API key, feature flag, and logging level is associated with a specific version of your app. If you want to create Flutter flavors for an Android app, you can do this in Flutter. In Android, a Flutter flavor is referred to as a \[\_product flavor\_]\[]. The following illustrates an example of the Android \[\_build variants\_] that are created when an Android app has two product flavors (\`staging\`, \`production\`) and two build types (\`debug\`, \`release\`): | Product flavors | Build types | Resulting build variants | | --- | --- | --- | | staging | debug | stagingDebug\
stagingRelease | | production | release | productionDebug\
productionRelease | {:.table .table-striped} \[\_product flavor\_]: https\://developer.android.com/build/build-variants#product-flavors \[\_build variants\_]: https\://developer.android.com/build/build-variants ## Configure your product flavors {: #using-flavors-in-android } Complete the following steps to add two Android product flavors called \`staging\` and \`production\` to a new Flutter project called \`flavors\_example\`, and then test your project to make sure that the flavors work as expected. 1. Create a new Flutter project called \`flavors\_example\` with Kotlin as the preferred Android language. By default, the project includes the \`debug\` and \`release\` Android build types. \`\`\`console title="console" $ flutter create --android-language kotlin flavors\_example \`\`\` 1. Add the product flavors called \`staging\` and \`production\` to the \`flavors\_example\` project. \* In the \`flavors\_example\` project, navigate to the \`android/app/\` directory and open \`build.gradle.kts\`. \* Add the \`flavorDimensions\` property and the \`productFlavors\` properties inside the \`android {}\` block. The default template explicitly defines the \`release\` build type, while the \`debug\` build type is implicit: \`\`\`kotlin title="build.gradle.kts" android { ... buildTypes { release { // TODO: Add your own signing config for the release build. // Signing with the debug keys for now, so \`flutter run --release\` works. signingConfig = signingConfigs.getByName("debug") } } ... flavorDimensions += "default" productFlavors { create("staging") { dimension = "default" applicationIdSuffix = ".staging" } create("production") { dimension = "default" applicationIdSuffix = ".production" } } } \`\`\` 1. To make sure that you've set up everything correctly, run your app on the Android product flavors. You won't see any differences because the configuration settings haven't changed, but you do want to make sure that the app can run. \* Start an Android emulator or connect a physical device with developer options enabled. \* In the console, navigate to the \`flavors\_example\` directory and enter the following command to test the \`staging\` flavor: \`\`\`console title="console" $ flutter run --flavor staging \`\`\` \* Repeat the previous step for the \`production\` flavor. 1. If everything runs, you're ready to customize your configurations. For more information, see \[Customize configurations]\[]. \[Customize configurations]: #customize-configurations ## Launch a flavor {: #launching-your-app-flavors } After you've created the product flavors for an Android app, you can launch a specific product flavor through Flutter. You can launch a product flavor with the Flutter CLI using the following steps: 1. Start an Android emulator or connect a physical device with developer options enabled. 1. In the console, navigate to the \`flavors\_example\` directory and enter the following command: \`\`\`console title="console" $ flutter (run | build ) --flavor \`\`\` \* \`(run | build )\`: Replace this with one of the following: \* \`run\`: Runs the app in debug mode. \* \`build\`: Builds either an APK or an appbundle. \* \`\`: Either \`apk\` or \`appbundle\`. \* \`\`: Replace this with the name of your Android product flavor (for example: \`staging\`, \`production\`). Example: \`\`\`console title="console" $ flutter build apk --flavor staging \`\`\` ## Use flavors in Flutter code After you've configured your product flavors, you can change your app's behavior—such as pointing to different API endpoints or changing the theme—based on the active flavor. The Flutter framework provides the \`appFlavor\` constant, which retrieves the name of the current flavor as a \`String\`. This value matches the flavor name passed to the \`--flavor\` flag during the \`flutter run\` or \`flutter build\` process. ### Access the current flavor 1. \*\*Import the services library:\*\* To access the \`appFlavor\` constant, add the following import to your Dart file: \`\`\`dart import 'package:flutter/services.dart'; \`\`\` 1. \*\*Check the flavor value:\*\* Use the \`appFlavor\` constant in your application logic (often in \`main()\`) to handle flavor-specific configurations: \`\`\`dart void main() { // appFlavor will match the flavor name from build.gradle.kts if (appFlavor == 'production') { // Logic for production environment Config.apiUrl = 'https\://api.flavors\_example.com'; } else if (appFlavor == 'staging') { // Logic for staging environment Config.apiUrl = 'https\://staging.api.flavors\_example.com'; } runApp(const MyApp()); } \`\`\` :::note The value of \`appFlavor\` matches the name of the product flavor you defined in your \`build.gradle.kts\` file (for example, \`staging\` or \`production\`). If no flavor is specified during the build, \`appFlavor\` returns \`null\`. ::: ## Customize configurations After you've added product flavors, you can customize them for your Android app. ### Create a distinct app display name If you have multiple product flavors, a distinct app name can quickly identify which flavor your deployed app is using. !\[Distinct app names in menu]\(/assets/images/docs/flavors/flavors-android-app-names-1.png){:width="40%"} The following steps show how to add distinct app display names for two product flavors called \`staging\` and \`production\` in a project called \`flavors\_example\`. 1. Create flavor-specific resource directories: \* Navigate to the \`android/app/src\` directory. \* Create a directory called \`staging/res/values\`. \* Navigate to the \`staging/res/values\` directory. \* Create the following \`strings.xml\` file: \`\`\`xml title="strings.xml"

<!--?xml version="1.0" encoding="utf-8"?-->

Flavors staging \`\`\` :::note If your project sets \`app\_name\` with \`resValue()\` in \`build.gradle.kts\`, builds with Android Gradle Plugin (AGP) 9.0 or later fail by default with an error such as \`Product Flavor contains custom resource values, but the feature is disabled.\` To fix this, remove those \`resValue()\` calls and define \`app\_name\` in \`strings.xml\` files as shown in this step. ::: \* Repeat the previous steps for the \`production\` flavor, and set the value of \`app\_name\` to \`Flavors production\`. 1. Update \`AndroidManifest.xml\` in your IDE: \* In the \`flavors\_example\` project, navigate to \`android/app/src/main\` and open \`AndroidManifest.xml\`. \* Replace the value for \`android:label\` with \`@string/app\_name\`. \`\`\`xml title="AndroidManifest.xml" /> \`\`\` 1. Launch the app for each product flavor (\`staging\`, \`production\`) and check to make sure that the app display name has changed for each. \* To launch a product flavor, see the steps in \[Launch a flavor]\[]. \* In the Android App Emulator, go to the list of apps. You should see one for \`Flavors p...\` and \`Flavors s...\`. \* To see more information for \`Flavors p...\` or \`Flavors s...\`, long-press the icon for one of them and select \`App info\`. \[Launch a flavor]: #launching-your-app-flavors ### Create distinct icons If you have multiple product flavors, a distinct icon for each configuration can help you quickly identify which flavor your deployed app is using. !\[Distinct icons]\(/assets/images/docs/flavors/flavors-android-icons.png){:width="40%"} The following steps show how to add a distinct icon for two product flavors called \`staging\` and \`production\` in a project called \`flavors\_example\`. 1. Prepare your icons: \* Design your \`staging\` icon and \`production\` icon in the design tool of your choice. \* Generate versions of the \`staging\` icon and \`production\` icon in the following sizes and them in \`PNG\` format: \* mipmap-mdpi (48x48 pixels) \* mipmap-hdpi (72x72 pixels) \* mipmap-xhdpi (96x96 pixels) \* mipmap-xxhdpi (144x144 pixels) \* mipmap-xxxhdpi (192x192 pixels) :::note You can use a tool like \[App Icon Generator]\[] to generate the versions of your icons. ::: 1. Create flavor-specific resource directories: \* Navigate to the \`android/app/src\` directory. \* Create a directory called \`staging/res\`. \* Navigate to the \`staging/res\` directory. \* Create the following \`mipmap\` directories and move the versions of the \`staging\` icon into them: \* \`mipmap-mdpi/48x48\_staging.png\` \* \`mipmap-hdpi/72x72\_staging.png\` \* \`mipmap-xhdpi/96x96\_staging.png\` \* \`mipmap-xxhdpi/144x144\_staging.png\` \* \`mipmap-xxxhdpi/192x192\_staging.png\` \* Repeat the previous steps for the \`production\` flavor directories and icons. \* Rename all of the icons to \`ic\_launcher.png\`. 1. Double-check the configurations in \`AndroidManifest.xml\` in your IDE: \* In the \`flavors\_example\` project, navigate to \`android/app/src/main\` and open \`AndroidManifest.xml\`. \* Make sure that the value for \`android:icon\` is \`@mipmap/ic\_launcher\`. 1. Launch the app for each product flavor (\`staging\`, \`production\`) and check to make sure that the app icon has changed for each. To launch a product flavor, see the steps in \[Launch a flavor]\[]. \[Launch a flavor]: #launching-your-app-flavors \[App Icon Generator]: https\://www\.appicon.co/ ### Bundle assets If you have assets that are only used in a specific flavor in your app, you can configure them to only be bundled into your app when launching that flavor. This prevents your app bundle size from being bloated by unused assets. To bundle assets for each flavor, add the \`flavors\` subfield to the \`assets\` field in your project's pubspec. To learn more, see the \[\`assets\` field]\[] in \[Flutter pubspec options]\[]. \[\`assets\` field]: /tools/pubspec#assets \[Flutter pubspec options]: /tools/pubspec ### Set a default flavor You can have your app use a specific flavor when you launch your app without specifying a flavor. To do this, you need to add the \`default-flavor\` field to your project's pubspec. To learn more, see the \[\`default-flavor\` field]\[] in \[Flutter pubspec options]\[]. \[\`default-flavor\` field]: /tools/pubspec#default-flavor ### Add unique build settings If you have additional build settings that you would like to configure for a specific Android product flavor, see Android's \[Configure build variants]\[]. While it is possible to set \`abiFilters\` in product flavors, it is not recommended. Prefer configuring \`abiFilters\` in \`defaultConfig\` when possible. When setting \`abiFilters\` in product flavors, pass \`-Pdisable-abi-filtering=true\` when running \`flutter build\` or \`flutter run\`. \[Configure build variants]: https\://developer.android.com/build/build-variants ## More information For more information on creating and using flavors, check out the following resources: \* \[Set up Flutter flavors for iOS and macOS]\[] \* \[Set up Flutter flavors for Linux]\[] \* \[Set up Flutter flavors for Windows]\[] \* \[Build flavors in Flutter (Android and iOS) with Firebase]\[] \* \[How to Setup Flutter & Firebase with Multiple Flavors using the FlutterFire CLI]\[flutterfireCLI] \[Set up Flutter flavors for iOS and macOS]: /deployment/flavors-ios \[Set up Flutter flavors for Linux]: /deployment/flavors-linux \[Set up Flutter flavors for Windows]: /deployment/flavors-windows \[Build flavors in Flutter (Android and iOS) with Firebase]: https\://medium.com/@animeshjain/build-flavors-in-flutter-android-and-ios-with-different-firebase-projects-per-flavor-27c5c5dac10b \[flutterfireCLI]: https\://codewithandrea.com/articles/flutter-firebase-multiple-flavors-flutterfire-cli/

----
url: https://docs.flutter.dev/ui/animations
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Introduction to animations](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

Well-designed animations make 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](/ui/widgets/material), come with the standard motion effects defined in their design spec, but it's also possible to customize these effects.

## Choosing an approach

[#](#choosing-an-approach)

There are different approaches you can take when creating animations in Flutter. Which approach is right for you? To help you decide, check out the video, [How to choose which Flutter Animation Widget is right for you?](https://www.youtube.com/watch?v=GXIJJkq_H8g) (Also published as a [*companion article*](https://blog.flutter.dev/how-to-choose-which-flutter-animation-widget-is-right-for-you-79ecfb7e72b5).)

[Watch on YouTube in a new tab: "How to choose which Flutter animation widget is right for your use case"](https://www.youtube.com/watch/GXIJJkq_H8g)

(To dive deeper into the decision process, watch the [Animations in Flutter done right](https://www.youtube.com/watch?v=wnARLByOtKA\&t=3s) video, presented at Flutter Europe.)

As shown in the video, the following decision tree helps you decide what approach to use when implementing a Flutter animation:

## Animation deep dive

[#](#animation-deep-dive)

For a deeper understanding of how animations work in Flutter, watch [Animation deep dive](https://www.youtube.com/watch?v=PbcILiN8rbo\&list=PLjxrf2q8roU2v6UqYlt_KPaXlnjbYySua\&index=5). (Also published as a [*companion article*](https://blog.flutter.dev/animation-deep-dive-39d3ffea111f).)

[Watch on YouTube in a new tab: "Take a deep dive into Flutter animation"](https://www.youtube.com/watch/PbcILiN8rbo)

## Implicit and explicit animations

[#](#implicit-and-explicit-animations)

### Pre-packaged implicit animations

[#](#pre-packaged-implicit-animations)

If a pre-packaged implicit animation (the easiest animation to implement) suits your needs, watch [Animation basics with implicit animations](https://www.youtube.com/watch?v=IVTjpW3W33s\&list=PLjxrf2q8roU2v6UqYlt_KPaXlnjbYySua\&index=1). (Also published as a [*companion article*](https://blog.flutter.dev/flutter-animation-basics-with-implicit-animations-95db481c5916).)

[Watch on YouTube in a new tab: "Flutter implicit animation basics"](https://www.youtube.com/watch/IVTjpW3W33s)

### Custom implicit animations

[#](#custom-implicit-animations)

To create a custom implicit animation, watch [Creating your own custom implicit animations with TweenAnimationBuilder](https://www.youtube.com/watch?v=6KiPEqzJIKQ\&feature=youtu.be). (Also published as a [*companion article*](https://blog.flutter.dev/custom-implicit-animations-in-flutter-with-tweenanimationbuilder-c76540b47185).)

[Watch on YouTube in a new tab: "Create custom implicit animations with TweenAnimationBuilder"](https://www.youtube.com/watch/6KiPEqzJIKQ)

### Built-in explicit animations

[#](#built-in-explicit-animations)

To create an explicit animation (where you control the animation, rather than letting the framework control it), consider using one of the built-in explicit animation classes. To learn more, watch [Making your first directional animations with built-in explicit animations](https://www.youtube.com/watch?v=CunyH6unILQ\&list=PLjxrf2q8roU2v6UqYlt_KPaXlnjbYySua\&index=3). (Also published as a [*companion article*](https://blog.flutter.dev/directional-animations-with-built-in-explicit-animations-3e7c5e6fbbd7).)

[Watch on YouTube in a new tab: "Making your first directional animations with built-in explicit animations"](https://www.youtube.com/watch/CunyH6unILQ)

### Explicit animations

[#](#explicit-animations)

If you need to build an explicit animation from scratch, watch [Creating custom explicit animations with AnimatedBuilder and AnimatedWidget](https://www.youtube.com/watch?v=fneC7t4R_B0\&list=PLjxrf2q8roU2v6UqYlt_KPaXlnjbYySua\&index=4). (Also published as a [*companion article*](https://blog.flutter.dev/when-should-i-useanimatedbuilder-or-animatedwidget-57ecae0959e8).)

[Watch on YouTube in a new tab: "Creating custom explicit animations with AnimatedBuilder and AnimatedWidget"](https://www.youtube.com/watch/fneC7t4R_B0)

## Animation types

[#](#animation-types)

Generally, animations are either tween- or physics-based. The following sections explain what these terms mean, and point you to resources where you can learn more.

### Tween animation

[#](#tween-animation)

Short for *in-betweening*. In a tween animation, the beginning and ending points are defined, as well as a timeline, and a curve that defines the timing and speed of the transition. The framework calculates how to transition from the beginning point to the end point.

* Refer to the [Animations tutorial](/ui/animations/tutorial), which uses tweens in the examples.

* Consult the API documentation for [`Tween`](https://api.flutter.dev/flutter/animation/Tween-class.html), [`CurveTween`](https://api.flutter.dev/flutter/animation/CurveTween-class.html), and [`TweenSequence`](https://api.flutter.dev/flutter/animation/TweenSequence-class.html).

### Physics-based animation

[#](#physics-based-animation)

In physics-based animation, motion is modeled to resemble real-world behavior. When you toss a ball, for example, where and when it lands depends on how fast it was tossed and how far it was from the ground. Similarly, dropping a ball attached to a spring falls (and bounces) differently than dropping a ball attached to a string.

* [Animate a widget using a physics simulation](/cookbook/animation/physics-simulation)\
  A recipe in the animations section of the Flutter cookbook.

* Consult the API documentation for [`AnimationController.animateWith`](https://api.flutter.dev/flutter/animation/AnimationController/animateWith.html) and [`SpringSimulation`](https://api.flutter.dev/flutter/physics/SpringSimulation-class.html).

## Common animation patterns

[#](#common-animation-patterns)

Most UX or motion designers find that certain animation patterns are used repeatedly when designing a UI. This section lists some of the commonly used animation patterns, and tells you where to learn more.

### Animated list or grid

[#](#animated-list-or-grid)

This pattern involves animating the addition or removal of elements from a list or grid.

* [`AnimatedList` example](https://github.com/flutter/samples/blob/main/animations)\
  This demo, from the [Sample app catalog](https://github.com/flutter/samples), shows how to animate adding an element to a list, or removing a selected element. The internal Dart list is synced as the user modifies the list using the plus (+) and minus (-) buttons.

### Shared element transition

[#](#shared-element-transition)

In this pattern, the user selects an element—often an image—from the page, and the UI animates the selected element to a new page with more detail. In Flutter, you can implement shared element transitions between routes (pages) using the `Hero` widget.

* [Hero animations](/ui/animations/hero-animations)\
  How to create two styles of Hero animations:

  * The hero flies from one page to another while changing position and size.
  * The hero's boundary changes shape, from a circle to a square, as it flies from one page to another.

* Consult the API documentation for the [`Hero`](https://api.flutter.dev/flutter/widgets/Hero-class.html), [`Navigator`](https://api.flutter.dev/flutter/widgets/Navigator-class.html), and [`PageRoute`](https://api.flutter.dev/flutter/widgets/PageRoute-class.html) classes.

### Staggered animation

[#](#staggered-animation)

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](/ui/animations/staggered-animations)

[]()

## Essential animation concepts and classes

[#](#essential-animation-concepts-and-classes)

The animation system in Flutter is based on typed [`Animation`](https://api.flutter.dev/flutter/animation/Animation-class.html) objects. Widgets can either incorporate these animations in their build functions directly by reading their current value and listening to their state changes or they can use the animations as the basis of more elaborate animations that they pass along to other widgets.

[]()

### Animation​\<double>

[#](#animationwbrdouble)

In Flutter, an `Animation` object is independent of what is rendered onscreen. An `Animation` is an abstract class that exposes its current value and its status: dismissed, forward, reverse, or completed. One of the more commonly used animation types is `Animation<double>`.

An `Animation` object sequentially generates interpolated numbers between two values over a certain duration. The output of an `Animation` object might be linear, a curve, a step function, or any other mapping you can create. Depending on how the `Animation` object is controlled, it could run in reverse, or even switch directions in the middle.

Animations can also interpolate types other than double, such as `Animation<Color>` or `Animation<Size>`.

An `Animation` object has state. Its current value is always available in the `.value` getter.

An `Animation` object doesn't handle rendering or `build()` functions.

### CurvedAnimation

[#](#curvedanimation)

A [`CurvedAnimation`](https://api.flutter.dev/flutter/animation/CurvedAnimation-class.html) defines the animation's progress as a non-linear curve.

dart

```
animation = CurvedAnimation(parent: controller, curve: Curves.easeIn);
```

`CurvedAnimation` and `AnimationController` (described in the next sections) are both of type `Animation<double>`, so you can pass them interchangeably. The `CurvedAnimation` wraps the object it's modifying—you don't subclass `AnimationController` to implement a curve.

You can use [`Curves`](https://api.flutter.dev/flutter/animation/Curves-class.html) with `CurvedAnimation`. The `Curves` class defines many commonly used curves, or you can create your own by overriding `transformInternal()`. For example:

dart

```
import 'dart:math';
​
class ShakeCurve extends Curve {
  @override
  double transformInternal(double t) => t + sin(t * pi * 4) * 0.2;
}
```

If you want to apply an animation curve to a `Tween`, consider using [`CurveTween`](https://api.flutter.dev/flutter/animation/CurveTween-class.html).

### AnimationController

[#](#animationcontroller)

[`AnimationController`](https://api.flutter.dev/flutter/animation/AnimationController-class.html) is a special `Animation` object that generates a new value whenever the hardware is ready for a new frame. By default, an `AnimationController` linearly produces the numbers from 0.0 to 1.0 during a given duration. For example, this code creates an `Animation` object, but does not start it running:

dart

```
controller = AnimationController(
  duration: const Duration(seconds: 2),
  vsync: this,
);
```

`AnimationController` derives from `Animation<double>`, so it can be used wherever an `Animation` object is needed. However, the `AnimationController` has additional methods to control the animation. For example, you start an animation with the `.forward()` method. The generation of numbers is tied to the screen refresh rate (typically 60 to 120 frames per second). After each number is generated, each `Animation` object calls the attached listener callbacks. To create a custom display list for each child, refer to [`RepaintBoundary`](https://api.flutter.dev/flutter/widgets/RepaintBoundary-class.html).

When creating an `AnimationController`, you pass it a `vsync` argument. The presence of `vsync` prevents offscreen animations from consuming unnecessary resources. You can use your stateful object as the vsync by adding `SingleTickerProviderStateMixin` to the class definition. You can find an example of this in [animate1](https://github.com/flutter/website/tree/main/examples/animation/animate1) on GitHub.

### Tween

[#](#tween)

By default, the `AnimationController` object ranges from 0.0 to 1.0. If you need a different range or a different data type, you can use a [`Tween`](https://api.flutter.dev/flutter/animation/Tween-class.html) to configure an animation to interpolate to a different range or data type. For example, the following `Tween` goes from -200.0 to 0.0:

dart

```
tween = Tween<double>(begin: -200, end: 0);
```

A `Tween` defines a mapping from an input range to an output range. The input range is commonly 0.0 to 1.0, but that's not a requirement.

A `Tween` inherits from `Animatable<T>`, not from `Animation<T>`. An `Animatable`, like `Animation`, doesn't have to output double. For example, `ColorTween` specifies a progression between two colors.

dart

```
colorTween = ColorTween(begin: Colors.transparent, end: Colors.black54);
```

A `Tween` stores mutable `begin` and `end` values, but doesn't track animation progress. Instead, it provides the [`evaluate(Animation<double> animation)`](https://api.flutter.dev/flutter/animation/Animatable/evaluate.html) method that uses the `transform` function to map the current value of the animation (between 0.0 and 1.0) to the actual animation value.

The current value of the `Animation` object is available from the `.value` getter. The tween returns `begin` and `end` when the animation values are 0.0 and 1.0, respectively.

#### Tween.animate

[#](#tween-animate)

To use a `Tween` object, call `animate()` on the `Tween`, passing in the controller object. For example, the following code generates the integer values from 0 to 255 over the course of 500 ms.

dart

```
final AnimationController controller = AnimationController(
  duration: const Duration(milliseconds: 500),
  vsync: this,
);
final Animation<int> alpha = IntTween(
  begin: 0,
  end: 255,
).animate(controller);
```

The following example shows a controller, a curve, and a `Tween`:

dart

```
final AnimationController controller = AnimationController(
  duration: const Duration(milliseconds: 500),
  vsync: this,
);
final Animation<double> curve = CurvedAnimation(
  parent: controller,
  curve: Curves.easeOut,
);
final Animation<int> alpha = IntTween(begin: 0, end: 255).animate(curve);
```

### Animation notifications

[#](#animation-notifications)

An [`Animation`](https://api.flutter.dev/flutter/animation/Animation-class.html) object can have listeners and status listeners, registered with `addListener()` and `addStatusListener()`. A listener is called whenever the value of the animation changes. The most common behavior of a listener is to call `setState()` to cause a rebuild. A status listener is called when an animation begins, ends, moves forward, or moves in reverse, as defined by `AnimationStatus`.

## Codelabs, tutorials, and articles

[#](#codelabs-tutorials-and-articles)

The following resources are a good place to start learning the Flutter animation framework. Each of these documents shows how to write animation code.

* [Animations in Flutter codelab](https://codelabs.developers.google.com/advanced-flutter-animations)\
  Learn about implicit and explicit animations while building a multiple-choice quiz game.

* [Animations tutorial](/ui/animations/tutorial)\
  Explains the fundamental classes in the Flutter animation package (controllers, `Animatable`, curves, listeners, builders), as it guides you through a progression of tween animations using different aspects of the animation APIs. This tutorial shows how to create your own custom explicit animations.

* [Zero to One with Flutter, part 1](https://flutter.dev/blog/zero-to-one-with-flutter) and [part 2](https://flutter.dev/blog/zero-to-one-with-flutter-part-two)\
  Flutter blog posts showing how to create an animated chart using tweening.

* [Casual games toolkit](/resources/games-toolkit/)\
  A toolkit with game templates that contain examples of how to use Flutter animations.

## Other resources

[#](#other-resources)

Learn more about Flutter animations at the following links:

* There are several [animations packages](https://pub.dev/packages?q=topic%3Aanimation) available on pub.dev that contain pre-built animations for commonly used patterns, including: `Container` transforms, shared axis transitions, fade through transitions, and fade transitions.

* [Animation samples](https://github.com/flutter/samples/tree/main/animations#animation-samples) from the [Sample app catalog](https://github.com/flutter/samples).

* [Animation recipes](/cookbook/animation) from the Flutter cookbook.

* [Animation videos](https://www.youtube.com/@flutterdev/search?query=animation) from the Flutter YouTube channel.

* [Animations: overview](/ui/animations/overview)\
  A look at some of the major classes in the animations library, and Flutter's animation architecture.

* [Animation and motion widgets](/ui/widgets/animation)\
  A catalog of some of the animation widgets provided in the Flutter APIs.

* The [animation library](https://api.flutter.dev/flutter/animation/animation-library.html) in the [Flutter API documentation](https://api.flutter.dev)\
  The animation API for the Flutter framework. This link takes you to a technical overview page for the library.

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-10-02. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/ui/animations/index.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/ui/animations\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/ui/animations/index.md "Report an issue with this page").

----
url: https://docs.flutter.dev/platform-integration/android/local-network-permission
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Request local network permissions on Android](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

Starting in Android 17 (API level 37), Android blocks local network access by default. Apps targeting Android 17 or higher that discover, scan, or connect to devices on the local area network must declare and request the `ACCESS_LOCAL_NETWORK` runtime permission.

## Understand the impact on Dart sockets

[#](#understand-the-impact-on-dart-sockets)

The standard `dart:io` library provides low-level socket networking, including [`Socket`](https://api.dart.dev/dart-io/Socket-class.html), [`RawSocket`](https://api.dart.dev/dart-io/RawSocket-class.html), and [`ServerSocket`](https://api.dart.dev/dart-io/ServerSocket-class.html). These socket classes operate at the system level and do not interact with Android's application framework UI.

Because Dart sockets cannot display an Android permission prompt, any attempt to connect to a local IP address without the required permission fails and throws a `SocketException`. To prevent connection failures, request the runtime permission in Flutter before you initialize or connect your Dart socket.

## Declare the permission in Android

[#](#declare-the-permission-in-android)

Before you can request the permission at runtime, declare it in your Android manifest.

1. Open `android/app/src/main/AndroidManifest.xml`.
2. Add the `ACCESS_LOCAL_NETWORK` permission inside the `<manifest>` tag:

xml

```
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    <uses-permission android:name="android.permission.ACCESS_LOCAL_NETWORK" />
    <application
        ...
    </application>
</manifest>
```

## Add the permission\_handler package

[#](#add-the-permission-handler-package)

To request Android runtime permissions from Dart, use the [`permission_handler`](https://pub.dev/packages/permission_handler) plugin from [pub.dev](https://pub.dev).

Add `permission_handler` to your Flutter project dependencies:

bash

```
flutter pub add permission_handler
```

## Request permission before connecting a socket

[#](#request-permission-before-connecting-a-socket)

Call `Permission.accessLocalNetwork.request()` before opening a Dart socket connection.

The following example demonstrates how to check the platform, request local network permission, and open a socket connection:

dart

```
import 'dart:io';
import 'package:material_ui/material_ui.dart';
import 'package:permission_handler/permission_handler.dart';
​
Future<void> connectToLocalDevice({
  required String host,
  required int port,
}) async {
  // Check and request runtime permission on Android.
  if (Platform.isAndroid) {
    final status = await Permission.accessLocalNetwork.request();
​
    if (status.isPermanentlyDenied) {
      // The user opted not to grant permission and checked "Don't ask again".
      // In a real app, show an explanation dialog before calling openAppSettings().
      debugPrint('Permission permanently denied. Please enable it in settings.');
      return;
    }
​
    if (!status.isGranted) {
      // Handle the case where the user denied permission.
      debugPrint('Local network permission was denied.');
      return;
    }
  }
​
  // Connect the Dart socket after the permission is granted.
  try {
    final socket = await Socket.connect(
      host,
      port,
      timeout: const Duration(seconds: 5),
    );
​
    debugPrint(
      'Connected to ${socket.remoteAddress.address}:${socket.remotePort}',
    );
​
    // Use the socket...
​
    await socket.close();
  } on SocketException catch (e) {
    debugPrint('Failed to connect: $e');
  }
}
```

## Handle permission status

[#](#handle-permission-status)

The `Permission.accessLocalNetwork.request()` method returns a `PermissionStatus` enum that indicates the outcome:

* `PermissionStatus.granted`: The user granted local network access. You can proceed to connect your Dart sockets.
* `PermissionStatus.denied`: The user denied permission for this request. Prompt the user with an explanation before requesting permission again.
* `PermissionStatus.permanentlyDenied`: The user denied permission permanently. Call `openAppSettings()` to help the user enable permission in the system settings.

## More information

[#](#more-information)

To learn more about local network permissions and socket programming, consult the following resources:

* Android developer guide on [Local network permission](https://developer.android.com/privacy-and-security/local-network-permission#android-17-enforcement)
* [`permission_handler`](https://pub.dev/packages/permission_handler) package on pub.dev
* Dart [`Socket` class documentation](https://api.dart.dev/dart-io/Socket-class.html)
* [Issue #184859 on GitHub](https://github.com/flutter/flutter/issues/184859)

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-09-29. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/platform-integration/android/local-network-permission.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/platform-integration/android/local-network-permission\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/platform-integration/android/local-network-permission.md "Report an issue with this page").

----
url: https://docs.flutter.dev/platform-integration/macos/platform-views
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Hosting native macOS views in your Flutter app with Platform Views](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

Platform views allow you to embed native views in a Flutter app, so you can apply transforms, clips, and opacity to the native view from Dart.

This allows you, for example, to use the native web views directly inside your Flutter app.

macOS uses Hybrid composition, which means that the native `NSView` is appended to the view hierarchy.

To create a platform view on macOS, use the following instructions:

## On the Dart side

[#](#on-the-dart-side)

On the Dart side, create a `Widget` and add the build implementation, as shown in the following steps:

In the Dart widget file, make changes similar to those shown in `native_view_example.dart`:

1. Add the following imports:

   dart

   ```
   import 'package:flutter/foundation.dart';
   import 'package:flutter/services.dart';
   ```

2. Implement a `build()` method:

   dart

   ```
   Widget build(BuildContext context) {
     // This is used in the platform side to register the view.
     const String viewType = '<platform-view-type>';
     // Pass parameters to the platform side.
     final Map<String, dynamic> creationParams = <String, dynamic>{};
   ​
     return AppKitView(
       viewType: viewType,
       layoutDirection: TextDirection.ltr,
       creationParams: creationParams,
       creationParamsCodec: const StandardMessageCodec(),
     );
   }
   ```

For more information, check out the [`AppKitView`](https://api.flutter.dev/flutter/widgets/AppKitView-class.html) API docs.

## On the platform side

[#](#on-the-platform-side)

Implement the factory and the platform view. The `NativeViewFactory` creates the platform view, and the platform view provides a reference to the `NSView`. For example, `NativeView.swift`:

NativeView\.swift

swift

```
import Cocoa
import FlutterMacOS
​
class NativeViewFactory: NSObject, FlutterPlatformViewFactory {
  private var messenger: FlutterBinaryMessenger
​
  init(messenger: FlutterBinaryMessenger) {
    self.messenger = messenger
    super.init()
  }
​
  func create(
    withViewIdentifier viewId: Int64,
    arguments args: Any?
  ) -> NSView {
    return NativeView(
      viewIdentifier: viewId,
      arguments: args,
      binaryMessenger: messenger)
  }
​
  /// Implementing this method is only necessary when
  /// the `arguments` in `createWithFrame` is not `nil`.
  public func createArgsCodec() -> (FlutterMessageCodec & NSObjectProtocol)? {
    return FlutterStandardMessageCodec.sharedInstance()
  }
}
​
class NativeView: NSView {
​
  init(
    viewIdentifier viewId: Int64,
    arguments args: Any?,
    binaryMessenger messenger: FlutterBinaryMessenger?
  ) {
    super.init(frame: CGRect(x: 0, y: 0, width: 200, height: 200))
    wantsLayer = true
    layer?.backgroundColor = NSColor.systemBlue.cgColor
    // macOS views can be created here
    createNativeView(view: self)
  }
​
    required init?(coder nsCoder: NSCoder) {
        super.init(coder: nsCoder)
    }
​
  func createNativeView(view _view: NSView) {
    let nativeLabel = NSTextField()
    nativeLabel.frame = CGRect(x: 0, y: 0, width: 180, height: 48.0)
    nativeLabel.stringValue = "Native text from macOS"
    nativeLabel.textColor = NSColor.black
    nativeLabel.font = NSFont.systemFont(ofSize: 14)
    nativeLabel.isBezeled = false
    nativeLabel.focusRingType = .none
    nativeLabel.isEditable = true
    nativeLabel.sizeToFit()
    _view.addSubview(nativeLabel)
  }
}
```

Finally, register the platform view. This can be done in an app or a plugin.

For app registration, modify the App's `MainFlutterWindow.swift`:

MainFlutterWindow\.swift

swift

```
import Cocoa
import FlutterMacOS
​
class MainFlutterWindow: NSWindow {
  override func awakeFromNib() {
    // ...
​
    let registrar = flutterViewController.registrar(forPlugin: "plugin-name")
    let factory = NativeViewFactory(messenger: registrar.messenger)
    registrar.register(
      factory,
      withId: "<platform-view-type>")
  }
}
```

For plugin registration, modify the plugin's main file:

Plugin.swift

swift

```
import Cocoa
import FlutterMacOS
​
public class Plugin: NSObject, FlutterPlugin {
  public static func register(with registrar: FlutterPluginRegistrar) {
    let factory = NativeViewFactory(messenger: registrar.messenger)
    registrar.register(factory, withId: "<platform-view-type>")
  }
}
```

For more information, check out the API docs for:

```
Widget build(BuildContext context) {
  // This is used in the platform side to register the view.
  const String viewType = '<platform-view-type>';
  // Pass parameters to the platform side.
  final Map<String, dynamic> creationParams = <String, dynamic>{};
​
  switch (defaultTargetPlatform) {
    case TargetPlatform.android:
    // return widget on Android.
    case TargetPlatform.iOS:
    // return widget on iOS.
    case TargetPlatform.macOS:
    // return widget on macOS.
    default:
      throw UnsupportedError('Unsupported platform view');
  }
}
```

## Performance

[#](#performance)

Platform views in Flutter come with performance trade-offs.

For example, in a typical Flutter app, the Flutter UI is composed on a dedicated raster thread. This allows Flutter apps to be fast, as this thread is rarely blocked.

When a platform view is rendered with hybrid composition, the Flutter UI continues to be composed from the dedicated raster thread, but the platform view performs graphics operations on the platform thread. To rasterize the combined contents, Flutter performs synchronization between its raster thread and the platform thread. As such, any slow or blocking operations on the platform thread can negatively impact Flutter graphics performance.

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/platform-integration/macos/platform-views.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/platform-integration/macos/platform-views\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/platform-integration/macos/platform-views.md "Report an issue with this page").

----
url: https://docs.flutter.dev/ai/ai-toolkit/feature-integration.md
----

\# Feature integration > How to integrate with other Flutter features. In addition to the features that are provided automatically by the \[\`LlmChatView\`]\[], a number of integration points allow your app to blend seamlessly with other features to provide additional functionality: \* \*\*Welcome messages\*\*: Display an initial greeting to users. \* \*\*Suggested prompts\*\*: Offer users predefined prompts to guide interactions. \* \*\*System instructions\*\*: Provide the LLM with specific input to influence its responses. \* \*\*Disable attachments and audio input\*\*: Remove optional parts of the chat UI. \* \*\*Manage cancel or error behavior\*\*: Change the user cancellation or LLM error behavior. \* \*\*Manage history\*\*: Every LLM provider allows for managing chat history, which is useful for clearing it, changing it dynamically and storing it between sessions. \* \*\*Chat serialization/deserialization\*\*: Store and retrieve conversations between app sessions. \* \*\*Custom response widgets\*\*: Introduce specialized UI components to present LLM responses. \* \*\*Custom styling\*\*: Define unique visual styles to match the chat appearance to the overall app. \* \*\*Chat without UI\*\*: Interact directly with the LLM providers without affecting the user's current chat session. \* \*\*Custom LLM providers\*\*: Build your own LLM provider for integration of chat with your own model backend. \* \*\*Rerouting prompts\*\*: Debug, log, or reroute messages meant for the provider to track down issues or route prompts dynamically. \[\`LlmChatView\`]: https\://pub.dev/documentation/flutter\_ai\_toolkit/latest/flutter\_ai\_toolkit/LlmChatView-class.html ## Welcome messages The chat view allows you to provide a custom welcome message to set context for the user: !\[Example welcome message]\(/assets/images/docs/ai-toolkit/example-of-welcome-message.png) You can initialize the \`LlmChatView\` with a welcome message by setting the \`welcomeMessage\` parameter: \`\`\`dart class ChatPage extends StatelessWidget { const ChatPage({super.key}); @override Widget build(BuildContext context) => Scaffold( appBar: AppBar(title: const Text(App.title)), body: LlmChatView( welcomeMessage: 'Hello and welcome to the Flutter AI Toolkit!', provider: FirebaseProvider( model: FirebaseAI.geminiAI().generativeModel( model: 'gemini-2.5-flash', ), ), ), ); } \`\`\` To see a complete example of setting the welcome message, check out the \[welcome example]\[]. \[welcome example]: https\://github.com/flutter/ai/blob/main/example/lib/welcome/welcome.dart ## Suggested prompts You can provide a set of suggested prompts to give the user some idea of what the chat session has been optimized for: !\[Example suggested prompts]\(/assets/images/docs/ai-toolkit/example-of-suggested-prompts.png) The suggestions are only shown when there is no existing chat history. Clicking one sends it immediately as a request to the underlying LLM. To set the list of suggestions, construct the \`LlmChatView\` with the \`suggestions\` parameter: \`\`\`dart class ChatPage extends StatelessWidget { const ChatPage({super.key}); @override Widget build(BuildContext context) => Scaffold( appBar: AppBar(title: const Text(App.title)), body: LlmChatView( suggestions: \[ 'I\\'m a Star Wars fan. What should I wear for Halloween?', 'I\\'m allergic to peanuts. What candy should I avoid at Halloween?', 'What\\'s the difference between a pumpkin and a squash?', ], provider: FirebaseProvider( model: FirebaseAI.geminiAI().generativeModel( model: 'gemini-2.5-flash', ), ), ), ); } \`\`\` To see a complete example of setting up suggestions for the user, take a look at the \[suggestions example]\[]. \[suggestions example]: https\://github.com/flutter/ai/blob/main/example/lib/suggestions/suggestions.dart ## LLM instructions To optimize an LLM's responses based on the needs of your app, you'll want to give it instructions. For example, the \[recipes example app]\[] uses the \`systemInstructions\` parameter of the \`GenerativeModel\` class to tailor the LLM to focus on delivering recipes based on the user's instructions: \`\`\`dart class \_HomePageState extends State { ... // create a new provider with the given history and the current settings LlmProvider \_createProvider(\[List? history]) => FirebaseProvider( history: history, ..., model: FirebaseAI.geminiAI().generativeModel( model: 'gemini-2.5-flash', ..., systemInstruction: Content.system(''' You are a helpful assistant that generates recipes based on the ingredients and instructions provided as well as my food preferences, which are as follows: ${Settings.foodPreferences.isEmpty ? 'I don\\'t have any food preferences' : Settings.foodPreferences} You should keep things casual and friendly. You may generate multiple recipes in a single response, but only if asked. ... ''', ), ), ); ... } \`\`\` Setting system instructions is unique to each provider; the \`FirebaseProvider\` allows you to provide them through the \`systemInstruction\` parameter. Notice that, in this case, we're bringing in user preferences as part of the creation of the LLM provider passed to the \`LlmChatView\` constructor. We set the instructions as part of the creation process each time the user changes their preferences. The recipes app allows the user to change their food preferences using a drawer on the scaffold: !\[Example of refining prompt]\(/assets/images/docs/ai-toolkit/setting-food-preferences.png) Whenever the user changes their food preferences, the recipes app creates a new model to use the new preferences: \`\`\`dart class \_HomePageState extends State { ... void \_onSettingsSave() => setState(() { // move the history over from the old provider to the new one final history = \_provider.history.toList(); \_provider = \_createProvider(history); }); } \`\`\` ## Function calling To enable the LLM to perform actions on behalf of the user, you can provide a set of tools (functions) that the LLM can call. The \`FirebaseProvider\` supports function calling out of the box. It handles the loop of sending the user's prompt, receiving a function call request from the LLM, executing the function, and sending the result back to the LLM until a final text response is generated. To use function calling, you need to define your tools and pass them to the \`FirebaseProvider\`. Check out the \[function calling example]\[] for details. \[function calling example]: https\://github.com/flutter/ai/blob/main/example/lib/function\_calls/function\_calls.dart ## Disable attachments and audio input If you'd like to disable attachments (the \*\*+\*\* button) or audio input (the mic button), you can do so with the \`enableAttachments\` and \`enableVoiceNotes\` parameters to the \`LlmChatView\` constructor: \`\`\`dart class ChatPage extends StatelessWidget { const ChatPage({super.key}); @override Widget build(BuildContext context) { // ... return Scaffold( appBar: AppBar(title: const Text('Restricted Chat')), body: LlmChatView( // ... enableAttachments: false, enableVoiceNotes: false, ), ); } } \`\`\` Both of these flags default to \`true\`. ## Custom speech-to-text By default, the AI Toolkit uses the \`LlmProvider\` to pass to the \`LlmChatView\` to provide the speech-to-text implementation. If you'd like to provide your own implementation, for example to use a device-specific service, you can do so by implementing the \`SpeechToText\` interface and passing it to the \`LlmChatView\` constructor: \`\`\`dart LlmChatView( // ... speechToText: MyCustomSpeechToText(), ) \`\`\` Check out the \[custom STT example]\[] for details. \[custom STT example]: https\://github.com/flutter/ai/tree/main/example/lib/custom\_stt ## Manage cancel or error behavior By default, when the user cancels an LLM request, the LLM's response will be appended with the string "CANCEL" and a message will pop up that the user has canceled the request. Likewise, in the event of an LLM error, like a dropped network connection, the LLM's response will be appended with the string "ERROR" and an alert dialog will pop up with the details of the error. You can override the cancel and error behavior with the \`cancelMessage\`, \`errorMessage\`, \`onCancelCallback\` and \`onErrorCallback\` parameters of the \`LlmChatView\`. For example, the following code replaces the default cancellation handling behavior: \`\`\`dart class ChatPage extends StatelessWidget { // ... void \_onCancel(BuildContext context) { ScaffoldMessenger.of( context, ).showSnackBar(const SnackBar(content: Text('Chat cancelled'))); } @override Widget build(BuildContext context) => Scaffold( appBar: AppBar(title: const Text(App.title)), body: LlmChatView( // ... onCancelCallback: \_onCancel, cancelMessage: 'Request cancelled', ), ); } \`\`\` You can override any or all of these parameters and the \`LlmChatView\` will use its defaults for anything you don't override. ## Manage history The \[standard interface that defines all LLM providers]\[providerIF] that can plug into the chat view includes the ability to get and set history for the provider: \`\`\`dart abstract class LlmProvider implements Listenable { Stream generateStream( String prompt, { Iterable attachments, }); Stream sendMessageStream( String prompt, { Iterable attachments, }); Iterable get history; set history(Iterable history); } \`\`\` \[providerIF]: https\://pub.dev/documentation/flutter\_ai\_toolkit/latest/flutter\_ai\_toolkit/LlmProvider-class.html When the history for a provider changes, it calls the \`notifyListener\` method exposed by the \`Listenable\` base class. This means that you manually subscribe/unsubscribe with the \`add\` and \`remove\` methods or use it to construct an instance of the \`ListenableBuilder\` class. The \`generateStream\` method calls into the underlying LLM without affecting the history. Calling the \`sendMessageStream\` method changes the history by adding two new messages to the provider's history—one for the user message and one for the LLM response—when the response is completed. The chat view uses \`sendMessageStream\` when it processes a user's chat prompt and \`generateStream\` when it's processing the user's voice input. To see or set the history, you can access the \`history\` property: \`\`\`dart void \_clearHistory() => \_provider.history = \[]; \`\`\` The ability to access a provider's history is also useful when it comes to recreating a provider while maintaining the history: \`\`\`dart class \_HomePageState extends State { ... void \_onSettingsSave() => setState(() { // move the history over from the old provider to the new one final history = \_provider.history.toList(); \_provider = \_createProvider(history); }); } \`\`\` The \`\_createProvider\` method creates a new provider with the history from the previous provider \_and\_ the new user preferences. It's seamless for the user; they can keep chatting away but now the LLM gives them responses taking their new food preferences into account. For example: \`\`\`dart class \_HomePageState extends State { ... // create a new provider with the given history and the current settings LlmProvider \_createProvider(\[List? history]) => FirebaseProvider( history: history, ... ); ... } \`\`\` To see history in action, check out the \[recipes example app]\[] and the \[history example app]\[]. \[history example app]: https\://github.com/flutter/ai/blob/main/example/lib/history/history.dart \[recipes example app]: https\://github.com/flutter/ai/tree/main/example/lib/recipes ## Chat serialization/deserialization To save and restore chat history between sessions of an app requires the ability to serialize and deserialize each user prompt, including the attachments, and each LLM response. Both kinds of messages (the user prompts and LLM responses), are exposed in the \`ChatMessage\` class. Serialization can be accomplished by using the \`toJson\` method of each \`ChatMessage\` instance. \`\`\`dart Future \_saveHistory() async { // get the latest history final history = \_provider.history.toList(); // write the new messages for (var i = 0; i != history.length; ++i) { // skip if the file already exists final file = await \_messageFile(i); if (file.existsSync()) continue; // write the new message to disk final map = history\[i].toJson(); final json = JsonEncoder.withIndent(' ').convert(map); await file.writeAsString(json); } } \`\`\` Likewise, to deserialize, use the static \`fromJson\` method of the \`ChatMessage\` class: \`\`\`dart Future \_loadHistory() async { // read the history from disk final history = \[]; for (var i = 0;; ++i) { final file = await \_messageFile(i); if (!file.existsSync()) break; final map = jsonDecode(await file.readAsString()); history.add(ChatMessage.fromJson(map)); } // set the history on the controller \_provider.history = history; } \`\`\` To ensure fast turnaround when serializing, we recommend only writing each user message once. Otherwise, the user must wait for your app to write every message every time and, in the face of binary attachments, that could take a while. To see this in action, check out the \[history example app]\[]. \[history example app]: https\://github.com/flutter/ai/blob/main/example/lib/history/history.dart ## Custom response widgets By default, the LLM response shown by the chat view is formatted Markdown. However, in some cases, you want to create a custom widget to show the LLM response that's specific to and integrated with your app. For example, when the user requests a recipe in the \[recipes example app]\[], the LLM response is used to create a widget that's specific to showing recipes just like the rest of the app does and to provide for an \*\*Add\*\* button in case the user would like to add the recipe to their database: !\[Add recipe button]\(/assets/images/docs/ai-toolkit/add-recipe-button.png) This is accomplished by setting the \`responseBuilder\` parameter of the \`LlmChatView\` constructor: \`\`\`dart LlmChatView( provider: \_provider, welcomeMessage: \_welcomeMessage, responseBuilder: (context, response) => RecipeResponseView( response, ), ), \`\`\` In this particular example, the \`RecipeResponseView\` widget is constructed with the LLM provider's response text and uses that to implement its \`build\` method: \`\`\`dart class RecipeResponseView extends StatelessWidget { const RecipeResponseView(this.response, {super.key}); final String response; @override Widget build(BuildContext context) { final children = \[]; String? finalText; // created with the response from the LLM as the response streams in, so // many not be a complete response yet try { final map = jsonDecode(response); final recipesWithText = map\['recipes'] as List; finalText = map\['text'] as String?; for (final recipeWithText in recipesWithText) { // extract the text before the recipe final text = recipeWithText\['text'] as String?; if (text != null && text.isNotEmpty) { children.add(MarkdownBody(data: text)); } // extract the recipe final json = recipeWithText\['recipe'] as Map; final recipe = Recipe.fromJson(json); children.add(const Gap(16)); children.add(Column( crossAxisAlignment: CrossAxisAlignment.start, children: \[ Text(recipe.title, style: Theme.of(context).textTheme.titleLarge), Text(recipe.description), RecipeContentView(recipe: recipe), ], )); // add a button to add the recipe to the list children.add(const Gap(16)); children.add(OutlinedButton( onPressed: () => RecipeRepository.addNewRecipe(recipe), child: const Text('Add Recipe'), )); children.add(const Gap(16)); } } catch (e) { debugPrint('Error parsing response: $e'); } ... return Column( crossAxisAlignment: CrossAxisAlignment.start, children: children, ); } } \`\`\` This code parses the text to extract introductory text and the recipe from the LLM, bundling them together with an \*\*Add Recipe\*\* button to show in place of the Markdown. Notice that we're parsing the LLM response as JSON. It's common to set the provider into JSON mode and to provide a schema to restrict the format of its responses to ensure that we've got something we can parse. Each provider exposes this functionality in its own way, but the \`FirebaseProvider\` class enables this with a \`GenerationConfig\` object that the recipes example uses as follows: \`\`\`dart class \_HomePageState extends State { ... // create a new provider with the given history and the current settings LlmProvider \_createProvider(\[List? history]) => FirebaseProvider( ... model: FirebaseAI.geminiAI().generativeModel( ... generationConfig: GenerationConfig( responseMimeType: 'application/json', responseSchema: Schema(...), systemInstruction: Content.system(''' ... Generate each response in JSON format with the following schema, including one or more "text" and "recipe" pairs as well as any trailing text commentary you care to provide: { "recipes": \[ { "text": "Any commentary you care to provide about the recipe.", "recipe": { "title": "Recipe Title", "description": "Recipe Description", "ingredients": \["Ingredient 1", "Ingredient 2", "Ingredient 3"], "instructions": \["Instruction 1", "Instruction 2", "Instruction 3"] } } ], "text": "any final commentary you care to provide", } ''', ), ), ); ... } \`\`\` This code initializes the \`GenerationConfig\` object by setting the \`responseMimeType\` parameter to \`'application/json'\` and the \`responseSchema\` parameter to an instance of the \`Schema\` class that defines the structure of the JSON that you're prepared to parse. In addition, it's good practice to also ask for JSON and to provide a description of that JSON schema in the system instructions, which we've done here. To see this in action, check out the \[recipes example app]\[]. ## Custom styling The chat view comes out of the box with a set of default styles for the background, the text field, the buttons, the icons, the suggestions, and so on. You can fully customize those styles by setting your own by using the \`style\` parameter to the \`LlmChatView\` constructor: \`\`\`dart LlmChatView( provider: FirebaseProvider(...), style: LlmChatViewStyle(...), ), \`\`\` For example, the \[custom styles example app]\[custom-ex] uses this feature to implement an app with a Halloween theme: !\[Halloween-themed demo app]\(/assets/images/docs/ai-toolkit/demo-app.png) For a complete list of the styles available in the \`LlmChatViewStyle\` class, check out the \[reference documentation]\[]. You can also customize the appearance of the voice recorder using the \`voiceNoteRecorderStyle\` parameter of the \`LlmChatViewStyle\` class, which is demonstrated in the \[styles example]\[styles-ex]. To see custom styles in action, in addition to the \[custom styles example]\[custom-ex] and the \[styles example]\[styles-ex], check out the \[dark mode example]\[] and the \[demo app]\[]. \[custom-ex]: https\://github.com/flutter/ai/blob/main/example/lib/custom\_styles/custom\_styles.dart \[styles-ex]: https\://github.com/flutter/ai/blob/main/example/lib/styles/styles.dart \[dark mode example]: https\://github.com/flutter/ai/blob/main/example/lib/dark\_mode/dark\_mode.dart \[demo app]: https\://github.com/flutter/ai#online-demo \[reference documentation]: https\://pub.dev/documentation/flutter\_ai\_toolkit/latest/flutter\_ai\_toolkit/LlmChatViewStyle-class.html ## Chat without UI You don't have to use the chat view to access the functionality of the underlying provider. In addition to being able to simply call it with whatever proprietary interface it provides, you can also use it with the \[LlmProvider interface]\[]. \[LlmProvider interface]: https\://pub.dev/documentation/flutter\_ai\_toolkit/latest/flutter\_ai\_toolkit/LlmProvider-class.html As an example, the recipes example app provides a Magic button on the page for editing recipes. The purpose of that button is to update an existing recipe in your database with your current food preferences. Pressing the button allows you to preview the recommended changes and decide whether you'd like to apply them or not: !\[User decides whether to update recipe in database]\(/assets/images/docs/ai-toolkit/apply-changes-decision.png) Instead of using the same provider that the chat portion of the app uses, which would insert spurious user messages and LLM responses into the user's chat history, the Edit Recipe page instead creates its own provider and uses it directly: \`\`\`dart class \_EditRecipePageState extends State { ... final \_provider = FirebaseProvider(...); ... Future \_onMagic() async { final stream = \_provider.sendMessageStream( 'Generate a modified version of this recipe based on my food preferences: ' '${\_ingredientsController.text}\n\n${\_instructionsController.text}', ); var response = await stream.join(); final json = jsonDecode(response); try { final modifications = json\['modifications']; final recipe = Recipe.fromJson(json\['recipe']); if (!context.mounted) return; final accept = await showDialog( context: context, builder: (context) => AlertDialog( title: Text(recipe.title), content: Column( crossAxisAlignment: CrossAxisAlignment.start, children: \[ const Text('Modifications:'), const Gap(16), Text(\_wrapText(modifications)), ], ), actions: \[ TextButton( onPressed: () => context.pop(true), child: const Text('Accept'), ), TextButton( onPressed: () => context.pop(false), child: const Text('Reject'), ), ], ), ); ... } catch (ex) { ... } } } } \`\`\` The call to \`sendMessageStream\` creates entries in the provider's history, but since it's not associated with a chat view, they won't be shown. If it's convenient, you can also accomplish the same thing by calling \`generateStream\`, which allows you to reuse an existing provider without affecting the chat history. To see this in action, check out the \[Edit Recipe page]\[] of the recipes example. \[Edit Recipe page]: https\://github.com/flutter/ai/blob/main/example/lib/recipes/pages/edit\_recipe\_page.dart ## Rerouting prompts If you'd like to debug, log, or manipulate the connection between the chat view and the underlying provider, you can do so with an implementation of an \[\`LlmStreamGenerator\`]\[] function. You then pass that function to the \`LlmChatView\` in the \`messageSender\` parameter: \[\`LlmStreamGenerator\`]: https\://pub.dev/documentation/flutter\_ai\_toolkit/latest/flutter\_ai\_toolkit/LlmStreamGenerator.html \`\`\`dart class ChatPage extends StatelessWidget { final \_provider = FirebaseProvider(...); @override Widget build(BuildContext context) => Scaffold( appBar: AppBar(title: const Text(App.title)), body: LlmChatView( provider: \_provider, messageSender: \_logMessage, ), ); Stream \_logMessage( String prompt, { required Iterable attachments, }) async\* { // log the message and attachments debugPrint('# Sending Message'); debugPrint('## Prompt\n$prompt'); debugPrint('## Attachments\n${attachments.map((a) => a.toString())}'); // forward the message on to the provider final response = \_provider.sendMessageStream( prompt, attachments: attachments, ); // log the response final text = await response.join(); debugPrint('## Response\n$text'); // return it yield text; } } \`\`\` This example logs the user prompts and LLM responses as they go back and forth. When providing a function as a \`messageSender\`, it's your responsibility to call the underlying provider. If you don't, it won't get the message. This capability allows you to do advanced things like routing to a provider dynamically or Retrieval Augmented Generation (RAG). To see this in action, check out the \[logging example app]\[]. \[logging example app]: https\://github.com/flutter/ai/blob/main/example/lib/logging/logging.dart

----
url: https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.42.3.md
----

\# DevTools 2.42.3 release notes > Release notes for Dart and Flutter DevTools version 2.42.3. The 2.42.3 release of the Dart and Flutter DevTools includes the following changes among other general improvements. To learn more about DevTools, check out the \[DevTools overview]\(/tools/devtools/overview). ## General updates \* Added "View licenses" shortcut to the About dialog. - \[#8610]\(https\://github.com/flutter/devtools/pull/8610) \* Lower the wasm optimization level to resolve crashes on the dart2wasm build. - \[#8814]\(https\://github.com/flutter/devtools/pull/8814) ## Inspector updates \* Enabled the new inspector by default. This can be disabled in the inspector settings. - \[#8650]\(https\://github.com/flutter/devtools/pull/8650) !\[Legacy inspector setting]\(/assets/images/docs/tools/devtools/release-notes/images-2.42.3/legacy\_inspector\_setting.png "Legacy inspector setting") \* Fixed an issue where selecting an implementation widget on the device while implementation widgets were hidden in the new inspector showed an error. - \[#8625]\(https\://github.com/flutter/devtools/pull/8625) \* Enabled auto-refreshes of the widget tree on hot-reloads and navigation events by default. This can be disabled in the inspector settings. - \[#8646]\(https\://github.com/flutter/devtools/pull/8646) !\[Auto-refresh setting]\(/assets/images/docs/tools/devtools/release-notes/images-2.42.3/inspector\_auto\_refresh\_setting.png "Inspector auto-refresh setting") ## Network profiler updates \* Fixed an issue where the HTTP requests would sometimes not be displayed properly, particularly when DevTools is communicating with an application over a slow network connection. - \[#8860]\(https\://github.com/flutter/devtools/pull/8860) ## Full commit history To find a complete list of changes in this release, check out the \[DevTools git log]\(https\://github.com/flutter/devtools/tree/v2.42.3).

----
url: https://docs.flutter.dev/cookbook/images/network-image.md
----

\# Display images from the internet > How to display images from the internet.

<!--?code-excerpt path-base="cookbook/images/network_image"?-->

Displaying images is fundamental for most mobile apps. Flutter provides the \[\`Image\`]\[] widget to display different types of images. To work with images from a URL, use the \[\`Image.network()\`]\[] constructor.

<!--?code-excerpt "lib/main.dart (ImageNetwork)" replace="/^body\: //g"?-->

\`\`\`dart Image.network('https\://picsum.photos/250?image=9'), \`\`\` ## Bonus: animated gifs One useful thing about the \`Image\` widget: It supports animated gifs.

<!--?code-excerpt "lib/gif.dart (Gif)" replace="/^return\ //g"?-->

\`\`\`dart Image.network( 'https\://docs.flutter.dev/assets/images/dash/dash-fainting.gif', ); \`\`\` ## Image fade in with placeholders The default \`Image.network\` constructor doesn't handle more advanced functionality, such as fading images in after loading. To accomplish this task, check out \[Fade in images with a placeholder]\[]. \* \[Fade in images with a placeholder]\[] ## Interactive example

<!--?code-excerpt "lib/main.dart"?-->

\`\`\`dartpad title="Flutter network images hands-on example in DartPad" run="true" import 'package:flutter/material.dart'; void main() => runApp(const MyApp()); class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { var title = 'Web Images'; return MaterialApp( title: title, home: Scaffold( appBar: AppBar(title: Text(title)), body: Image.network('https\://picsum.photos/250?image=9'), ), ); } } \`\`\` \[Fade in images with a placeholder]: /cookbook/images/fading-in-images \[\`Image\`]: https\://api.flutter.dev/flutter/widgets/Image-class.html \[\`Image.network()\`]: https\://api.flutter.dev/flutter/widgets/Image/Image.network.html

----
url: https://docs.flutter.dev/cookbook/navigation/passing-data
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Send data to a new screen](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

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. This recipe uses the following steps:

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.

## 1. Define a todo class

[#](#1-define-a-todo-class)

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.

dart

```
class Todo {
  final String title;
  final String description;
​
  const Todo(this.title, this.description);
}
```

## 2. Create a list of todos

[#](#2-create-a-list-of-todos)

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 [Use lists](/cookbook/lists/basic-list) recipe.

### Generate the list of todos

[#](#generate-the-list-of-todos)

dart

```
final todos = List.generate(
  20,
  (i) => Todo(
    'Todo $i',
    'A description of what needs to be done for Todo $i',
  ),
);
```

### Display the list of todos using a ListView

[#](#display-the-list-of-todos-using-a-listview)

dart

```
ListView.builder(
  itemCount: todos.length,
  itemBuilder: (context, index) {
    return ListTile(title: Text(todos[index].title));
  },
)
```

So far, so good. This generates 20 todos and displays them in a ListView.

## 3. Create a Todo screen to display the list

[#](#3-create-a-todo-screen-to-display-the-list)

For this, we create a `StatelessWidget`. We call it `TodosScreen`. Since the contents of this page won't change during runtime, we'll have to require the list of todos within the scope of this widget.

We pass in our `ListView.builder` as body of the widget we're returning to `build()`. This'll render the list on to the screen for you to get going!

dart

```
class TodosScreen extends StatelessWidget {
  // Requiring the list of todos.
  const TodosScreen({super.key, required this.todos});
​
  final List<Todo> todos;
​
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Todos')),
      //passing in the ListView.builder
      body: ListView.builder(
        itemCount: todos.length,
        itemBuilder: (context, index) {
          return ListTile(title: Text(todos[index].title));
        },
      ),
    );
  }
}
```

With Flutter's default styling, you're good to go without sweating about things that you'd like to do later on!

## 4. Create a detail screen to display information about a todo

[#](#4-create-a-detail-screen-to-display-information-about-a-todo)

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 the detail screen is a normal `StatelessWidget`, require the user to enter a `Todo` in the UI. Then, build the UI using the given todo.

dart

```
class DetailScreen extends StatelessWidget {
  // In the constructor, require a Todo.
  const DetailScreen({super.key, required this.todo});
​
  // Declare a field that holds the Todo.
  final Todo todo;
​
  @override
  Widget build(BuildContext context) {
    // Use the Todo to create the UI.
    return Scaffold(
      appBar: AppBar(title: Text(todo.title)),
      body: Padding(
        padding: const EdgeInsets.all(16),
        child: Text(todo.description),
      ),
    );
  }
}
```

## 5. Navigate and pass data to the detail screen

[#](#5-navigate-and-pass-data-to-the-detail-screen)

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. Pass the todo to the `DetailScreen`.

To capture the user's tap in the `TodosScreen`, write an [`onTap()`](https://pub.dev/documentation/material_ui/latest/material_ui/ListTile/onTap.html) callback for the `ListTile` widget. Within the `onTap()` callback, use the [`Navigator.push()`](https://api.flutter.dev/flutter/widgets/Navigator/push.html) method.

dart

```
body: ListView.builder(
  itemCount: todos.length,
  itemBuilder: (context, index) {
    return ListTile(
      title: Text(todos[index].title),
      // 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,
          MaterialPageRoute<void>(
            builder: (context) => DetailScreen(todo: todos[index]),
          ),
        );
      },
    );
  },
),
```

### Interactive example

[#](#interactive-example)

```
import 'package:flutter/material.dart';

class Todo {
  final String title;
  final String description;

  const Todo(this.title, this.description);
}

void main() {
  runApp(
    MaterialApp(
      title: 'Passing Data',
      home: TodosScreen(
        todos: List.generate(
          20,
          (i) => Todo(
            'Todo $i',
            'A description of what needs to be done for Todo $i',
          ),
        ),
      ),
    ),
  );
}

class TodosScreen extends StatelessWidget {
  const TodosScreen({super.key, required this.todos});

  final List<Todo> todos;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Todos')),
      body: ListView.builder(
        itemCount: todos.length,
        itemBuilder: (context, index) {
          return ListTile(
            title: Text(todos[index].title),
            // 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,
                MaterialPageRoute<void>(
                  builder: (context) => DetailScreen(todo: todos[index]),
                ),
              );
            },
          );
        },
      ),
    );
  }
}

class DetailScreen extends StatelessWidget {
  // In the constructor, require a Todo.
  const DetailScreen({super.key, required this.todo});

  // Declare a field that holds the Todo.
  final Todo todo;

  @override
  Widget build(BuildContext context) {
    // Use the Todo to create the UI.
    return Scaffold(
      appBar: AppBar(title: Text(todo.title)),
      body: Padding(
        padding: const EdgeInsets.all(16),
        child: Text(todo.description),
      ),
    );
  }
}
```

## Alternatively, pass the arguments using RouteSettings

[#](#alternatively-pass-the-arguments-using-routesettings)

Repeat the first two steps.

### Create a detail screen to extract the arguments

[#](#create-a-detail-screen-to-extract-the-arguments)

Next, create a detail screen that extracts and displays the title and description from the `Todo`. To access the `Todo`, use the [`ModalRoute.of()`](https://api.flutter.dev/flutter/widgets/ModalRoute/of.html) method. This method returns the current route with the arguments.

dart

```
class DetailScreen extends StatelessWidget {
  const DetailScreen({super.key});
​
  @override
  Widget build(BuildContext context) {
    final todo = ModalRoute.of(context)!.settings.arguments as Todo;
​
    // Use the Todo to create the UI.
    return Scaffold(
      appBar: AppBar(title: Text(todo.title)),
      body: Padding(
        padding: const EdgeInsets.all(16),
        child: Text(todo.description),
      ),
    );
  }
}
```

### Navigate and pass the arguments to the detail screen

[#](#navigate-and-pass-the-arguments-to-the-detail-screen)

Finally, navigate to the `DetailScreen` when a user taps a `ListTile` widget using `Navigator.push()`. Pass the arguments as part of the [`RouteSettings`](https://api.flutter.dev/flutter/widgets/RouteSettings-class.html). The `DetailScreen` extracts these arguments.

dart

```
ListView.builder(
  itemCount: todos.length,
  itemBuilder: (context, index) {
    return ListTile(
      title: Text(todos[index].title),
      // 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,
          MaterialPageRoute<void>(
            builder: (context) => const DetailScreen(),
            // Pass the arguments as part of the RouteSettings. The
            // DetailScreen reads the arguments from these settings.
            settings: RouteSettings(arguments: todos[index]),
          ),
        );
      },
    );
  },
)
```

### Complete example

[#](#complete-example)

dart

```
import 'package:flutter/material.dart';
​
class Todo {
  final String title;
  final String description;
​
  const Todo(this.title, this.description);
}
​
void main() {
  runApp(
    MaterialApp(
      title: 'Passing Data',
      home: TodosScreen(
        todos: List.generate(
          20,
          (i) => Todo(
            'Todo $i',
            'A description of what needs to be done for Todo $i',
          ),
        ),
      ),
    ),
  );
}
​
class TodosScreen extends StatelessWidget {
  const TodosScreen({super.key, required this.todos});
​
  final List<Todo> todos;
​
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Todos')),
      body: ListView.builder(
        itemCount: todos.length,
        itemBuilder: (context, index) {
          return ListTile(
            title: Text(todos[index].title),
            // 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,
                MaterialPageRoute<void>(
                  builder: (context) => const DetailScreen(),
                  // Pass the arguments as part of the RouteSettings. The
                  // DetailScreen reads the arguments from these settings.
                  settings: RouteSettings(arguments: todos[index]),
                ),
              );
            },
          );
        },
      ),
    );
  }
}
​
class DetailScreen extends StatelessWidget {
  const DetailScreen({super.key});
​
  @override
  Widget build(BuildContext context) {
    final todo = ModalRoute.of(context)!.settings.arguments as Todo;
​
    // Use the Todo to create the UI.
    return Scaffold(
      appBar: AppBar(title: Text(todo.title)),
      body: Padding(
        padding: const EdgeInsets.all(16),
        child: Text(todo.description),
      ),
    );
  }
}
```

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-09-29. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/navigation/passing-data.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/cookbook/navigation/passing-data\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/navigation/passing-data.md "Report an issue with this page").

----
url: https://docs.flutter.dev/contribute.md
----

\# Contribute to Flutter > Learn about contributing to the Flutter project and its surrounding ecosystem. !\[Dash and her friends excited for your contribution]\(/assets/images/dash/dash-contribute.png){:height="160px" style="float: right;"} If you would like to contribute to the Flutter project and its surrounding ecosystem, we're happy to have your help! Flutter is an open-source project that thrives on community contributions. No matter whether you're fixing a bug, proposing a new feature, improving documentation, or helping others in the community, your efforts are valuable and appreciated. This page provides a non-exhaustive overview of how you can get involved. If you need help contributing or would like more suggestions on getting started, consider reaching out on the \[Flutter contributors Discord]\[]. :::important Before beginning your journey of contributing to Flutter, please make sure to read and follow Flutter's \[Code of conduct]\[]. Also, learn more about Flutter's \[culture of inclusivity]\[] and \[core values]\[]. ::: \[Flutter contributors Discord]: https\://flutter.dev/chat \[Code of conduct]: https\://github.com/flutter/flutter/blob/main/CODE\_OF\_CONDUCT.md \[culture of inclusivity]: https\://flutter.dev/culture \[core values]: https\://github.com/flutter/flutter/blob/main/docs/about/Values.md

\## Develop with Flutter Even just using Flutter and providing feedback is a valuable contribution! ### Provide feedback Sharing your feedback and experiences helps the Flutter team understand and prioritize developer needs and pain points. You can provide valuable feedback through many avenues, including: - Upvoting existing issues If you're experiencing an issue that has already been reported, or want to indicate interest in an issue tracking a new feature, consider upvoting it to help the Flutter team understand its importance. You can upvote an issue by locating it in the \[GitHub issue tracker]\[flutter-issues], and pressing the "thumbs up" reaction on the issue's topmost comment. Avoid otherwise empty thumbs up, +1, or similar comments. However, if you have additional information, such as reproduction steps or additional version information, do consider providing those details in a new comment. - Reporting new bugs If you experience a bug with Flutter that hasn't yet been reported, do \[open a new issue]\[] with reproduction information. - Requesting features If there's a feature you think Flutter should add or implement but hasn't yet been suggested, do \[open a new issue]\[] with all relevant information as well as your use case. - Partaking in surveys Occasionally, the Flutter team will run developer surveys and studies. To help understand pain points and improve the Flutter developer experience, consider responding with as much feedback and details as possible. To sign up for future UX research studies, visit \[flutter.dev/research-signup]\[uxr-signup]. - Discussing proposals Architectural proposals and major changes to Flutter are authored, reviewed, and preserved as Requests for Comments (RFCs) in the \[\`flutter/rfc\` repository]\[flutter-rfc]. Consider reading and providing feedback on proposals that affect you or your apps. To find active proposals and join the discussion, browse \[open RFC pull requests]\[rfc-prs] in \`flutter/rfc\` or track \[issues with the \`design doc\` label]\[design-doc-issues]. If you want to author a proposal yourself, consult \[Architectural proposals (RFCs)]\(#architectural-proposals-rfcs). - Reviewing pull requests If you're familiar with a particular area of Flutter or a solution to a particular issue is important to you, consider reviewing open pull requests, trying them with your app, and providing any relevant feedback. \[open a new issue]: https\://github.com/flutter/flutter/issues/new \[uxr-signup]: https\://flutter.dev/research-signup \[flutter-rfc]: https\://github.com/flutter/rfc \[rfc-prs]: https\://github.com/flutter/rfc/pulls \[design-doc-issues]: https\://github.com/flutter/flutter/issues?q=is%3Aopen+is%3Aissue+label%3A%22design+doc%22 ### Try out the beta channel To help ensure Flutter's stability and improve upcoming features, help test upcoming releases before they reach the stable channel. Consider testing releases on the \`beta\` channel, both for general development and for testing compatibility with your apps. Any feedback you have or regressions you encounter, make sure to \[report them]\[report-bugs] to the Flutter team. To get started, \[switch]\[switch-channels] to the \[\`beta\` channel]\[beta-channel] today and account for any \[necessary migrations]\[]. \[switch-channels]: /install/upgrade#change-channels \[beta-channel]: /install/upgrade#the-beta-channel \[report-bugs]: https\://github.com/flutter/flutter/issues/new/choose \[necessary migrations]: /release/breaking-changes ## Contribute code Directly improve Flutter's codebase and related tools. ### Architectural proposals (RFCs) Planning a major architectural change, cross-subsystem feature, new primitive, or breaking change? Before writing implementation code, propose and align on the design through a Request for Comments (RFC) in the \[\`flutter/rfc\` repository]\[flutter-rfc]. Routine bug fixes and localized changes don't require an RFC; open an issue or pull request directly instead. To learn when an RFC is required and how to submit one, consult the \[Flutter RFCs & Design Documents guide]\[design-docs-guide] and \[RFC 000.0002 (RFC Review & Decision Process)]\[rfc-0002]. \[design-docs-guide]: https\://github.com/flutter/flutter/blob/main/docs/contributing/Design-Documents.md \[rfc-0002]: https\://github.com/flutter/rfc/blob/main/rfc/000.0002-flutter-rfc-review-process.md ### Flutter framework Found a bug in a built-in widget, have an idea for a new one, love adding tests, or just interested in the internals of Flutter? Consider contributing to the Flutter framework itself and improving the core of Flutter for everyone. To learn about contributing to the Flutter framework, check out the Flutter \[contribution guide]\[framework-contribute]. \[framework-contribute]: https\://github.com/flutter/flutter/blob/main/CONTRIBUTING.md ### Flutter engine Interested in implementing the primitives and platform integrations that underlay Flutter or have a knack for graphics programming? Consider contributing to the Flutter engine and making Flutter even more portable, performant, and powerful. To learn about contributing to the Flutter engine, check out the Flutter \[contribution guide]\[framework-contribute] and how to \[Set up the engine development environment]\[engine-setup]. \[framework-contribute]: https\://github.com/flutter/flutter/blob/main/CONTRIBUTING.md \[engine-setup]: https\://github.com/flutter/flutter/blob/main/docs/engine/contributing/Setting-up-the-Engine-development-environment.md ### Flutter packages Contribute to first-party packages that are maintained by the Flutter team. The first-party packages provide essential functionality for apps as well as encapsulate various platform-specific functionality. To learn about contributing to the first-party packages, check out the Flutter \[contribution guide]\[framework-contribute] as well as the packages-specific \[contribution guide]\[packages-contribute]. \[framework-contribute]: https\://github.com/flutter/flutter/blob/main/CONTRIBUTING.md \[packages-contribute]: https\://github.com/flutter/packages/blob/main/CONTRIBUTING.md ### DevTools Contributing to the \[Dart and Flutter DevTools]\[] is a great place to begin contributing due to its relatively low-cost setup. Enhance and fixes can greatly impact the developer experience for Flutter developers and perhaps help you develop your own apps. To get started, check out the \[DevTools \`CONTRIBUTING.md\` guide]\[devtools-contribute]. \[Dart and Flutter DevTools]: /tools/devtools \[devtools-contribute]: https\://github.com/flutter/devtools/blob/master/CONTRIBUTING.md ### Site infrastructure Fix bugs, improve accessibility, or add features to the Dart and Flutter websites. If you're familiar with web development or site generation, contributing to the Dart and Flutter websites can be a great avenue to improve the learning experience of Flutter developers. Depending on your interests, you might want to contribute to: - The pub.dev site - \*\*Live site:\*\* \[\`pub.dev\`]\(https\://pub.dev) - \*\*Repository:\*\* \[\`dart-lang/pub-dev\`]\(https\://github.com/dart-lang/pub-dev) - \*\*Contribution guide:\*\* \[\`CONTRIBUTING.md\`]\(https\://github.com/dart-lang/pub-dev/blob/master/CONTRIBUTING.md) - The Flutter documentation site - \*\*Live site:\*\* \[\`docs.flutter.dev\`]\(https\://docs.flutter.dev) - \*\*Repository:\*\* \[\`flutter/website\`]\(https\://github.com/flutter/website) - \*\*Contribution guide:\*\* \[\`CONTRIBUTING.md\`]\(https\://github.com/flutter/website/blob/main/CONTRIBUTING.md) - The Dart documentation site - \*\*Live site:\*\* \[\`dart.dev\`]\(https\://dart.dev) - \*\*Repository:\*\* \[\`dart-lang/site-www\`]\(https\://github.com/dart-lang/site-www) - \*\*Contribution guide:\*\* \[\`CONTRIBUTING.md\`]\(https\://github.com/dart-lang/site-www/blob/main/CONTRIBUTING.md) - DartPad - \*\*Live site:\*\* \[\`dartpad.dev\`]\(https\://dartpad.dev) - \*\*Repository:\*\* \[\`dart-lang/dart-pad\`]\(https\://github.com/dart-lang/dart-pad) - \*\*Contribution guide:\*\* \[\`CONTRIBUTING.md\`]\(https\://github.com/dart-lang/dart-pad/blob/main/CONTRIBUTING.md) - The \`dartdoc\` tool - \*\*Live site:\*\* \[\`api.flutter.dev\`]\(https\://api.flutter.dev) - \*\*Repository:\*\* \[\`dart-lang/dartdoc\`]\(https\://github.com/dart-lang/dartdoc) - \*\*Contribution guide:\*\* \[\`CONTRIBUTING.md\`]\(https\://github.com/dart-lang/dartdoc/blob/main/CONTRIBUTING.md) ### Dart SDK Contribute to the Dart language and surrounding tooling, improving the client-optimized language that forms the foundation of Flutter's excellent developer experience. Dart's contribution workflow is slightly different, so if you're interested, make sure to check out its \[contribution]\[dart-contribute] and \[building]\[dart-build] guides. \[dart-contribute]: https\://github.com/dart-lang/sdk/blob/main/CONTRIBUTING.md \[dart-build]: https\://github.com/dart-lang/sdk/blob/main/docs/Building.md ### Code samples Improve or add samples demonstrating Flutter features, helping developers that prefer to learn by example. You can always share your own samples or templates, or you can contribute to Flutter-maintained samples: - Full project samples - \*\*Location:\*\* \[\`flutter/samples\`]\(https\://github.com/flutter/samples) - \*\*Contribution guide:\*\* \[\`CONTRIBUTING.md\`]\(https\://github.com/flutter/samples/blob/main/CONTRIBUTING.md) - API doc samples - \*\*Location:\*\* \[\`flutter/flutter/packages/flutter\`]\(https\://github.com/flutter/flutter/tree/main/packages/flutter) - \*\*Contribution guide:\*\* \[\`README.md\`]\(https\://github.com/flutter/flutter/tree/main/dev/snippets) - Website code snippets - \*\*Location:\*\* \[\`flutter/website/examples\`]\(https\://github.com/flutter/website/tree/main/examples) - \*\*Contribution guide:\*\* \[\`CONTRIBUTING.md\`]\(https\://github.com/flutter/website/blob/main/CONTRIBUTING.md) - Flutter repo samples - \*\*Location:\*\* \[\`flutter/flutter/examples\`]\(https\://github.com/flutter/flutter/tree/main/examples) - \*\*Contribution guide:\*\* \[\`CONTRIBUTING.md\`]\(https\://github.com/flutter/flutter/blob/main/CONTRIBUTING.md) ## Write documentation Contributing to Flutter documentation, no matter the form, is one of the most impactful ways you can help Flutter. ### Flutter API docs The API docs are heavily relied on by many Flutter developers, both online and in their code editors. Whether you're interested in writing new docs, updating existing ones, adding relevant code snippets, or even creating new visuals like diagrams, your contribution to the API docs will be appreciated by every Flutter developer. To get started, check out the \[Flutter SDK contribution guide]\[flutter-contribute], particularly its section on \[API documentation]\[flutter-api-contribute] \[flutter-contribute]: https\://github.com/flutter/flutter/blob/main/CONTRIBUTING.md \[flutter-api-contribute]: https\://github.com/flutter/flutter/blob/main/CONTRIBUTING.md#api-documentation ### Documentation website Consider contributing to this very site, guiding developers as they learn and explore Flutter. To learn about contributing to the Flutter documentation website, check out the website's \[contribution documentation]\[website-contribute]. You can also contribute to the \[Dart website]\[], enhancing the documentation for the client-optimized language that forms the foundation of Flutter. To learn how to contribute, check out the \[\`dart-lang/site-www\` contribution docs]\[dart-dev-contribute]. \[website-contribute]: https\://github.com/flutter/website/blob/main/CONTRIBUTING.md \[Dart website]: https\://dart.dev \[dart-dev-contribute]: https\://github.com/dart-lang/site-www/tree/main?tab=readme-ov-file#getting-started ## Triage issues Help the Flutter team by triaging incoming bug reports and feature requests. There are plenty of ways to help in \[Flutter's issue database]\[flutter-issues], including but not limited to: - Determining issue validity - Ensuring actionability - Documenting affected versions - Adding reproduction steps - Identifying duplicate or resolved issues - Solving or redirecting support queries To get started helping with issues, read about \[helping out in the issue database]\[issue-contribute] and learn about Flutter's approach to \[issue triage]\[] and \[issue hygiene]\[issue hygiene]. \[flutter-issues]: https\://github.com/flutter/flutter/issues \[issue-contribute]: https\://github.com/flutter/flutter/blob/main/CONTRIBUTING.md#helping-out-in-the-issue-database \[issue triage]: https\://github.com/flutter/flutter/blob/main/docs/triage/README.md \[issue hygiene]: https\://github.com/flutter/flutter/tree/main/docs/contributing/issue\_hygiene ## Strengthen the package ecosystem Help grow and support the collection of available Dart and Flutter packages on \[pub.dev]\(https\://pub.dev/). ### Contribute to packages you use To give back to packages you depend on and potentially even help your own apps, find packages you rely on and contribute back to them. To contribute to a package, navigate to its page on the \[pub.dev site]\[] and find the repository linked in the page's sidenav. Before contributing, do make sure to follow each package's contribution guide, discuss your contribution with the maintainers, and keep in mind Flutter's \[Code of conduct]\[]. \[pub.dev site]: https\://pub.dev \[Code of conduct]: https\://github.com/flutter/flutter/blob/main/CODE\_OF\_CONDUCT.md ### Open source reusable functionality from your app If you've built a cool, generic widget or utility in your app, consider extracting it into a package and publishing it to pub.dev. To get started, learn about \[Creating Dart packages]\[] and \[Developing Flutter packages]\[]. Then, when you're ready to publish your package to the \[pub.dev site]\[], follow the guide and best practices on \[Publishing packages]\[]. \[Creating Dart packages]: https\://dart.dev/tools/pub/create-packages \[Developing Flutter packages]: /packages-and-plugins/developing-packages \[pub.dev site]: https\://pub.dev \[Publishing packages]: https\://dart.dev/tools/pub/publishing ### Add Dart or Flutter support to popular SDKs Create or contribute to packages that wrap native SDKs or web APIs. Before creating a new package, first try to find any existing wrappers that you could use or contribute to on the \[pub.dev site]\[]. Depending on the SDK and platform, you might need to \[Write platform-specific code]\[], use \[JS interop]\[], wrap a REST API using \[\`package:http\`]\[], or reimplement the required functionality in Dart. If you're planning to create a new package, learn about \[Creating Dart packages]\[] and \[Developing Flutter packages]\[]. Then, when you're ready to publish your package to the \[pub.dev site]\[], follow the guide and best practices on \[Publishing packages]\[]. \[pub.dev site]: https\://pub.dev \[Write platform-specific code]: /platform-integration/platform-channels \[JS interop]: https\://dart.dev/interop/js-interop \[\`package:http\`]: https\://pub.dev/packages/http ## Support the community Help other developers learn Flutter and succeed as they build their own apps. ### Help other developers Share your Flutter knowledge and expertise to help your fellow Flutter developers succeed. This can take many forms from starting a Flutter help channel in your company to answering questions on public forums. Some common locations Flutter developers look for help include: - \[Stack Overflow]\(https\://stackoverflow\.com/questions/tagged/flutter) - \[Flutter Dev Discord]\(https\://discord.com/invite/rflutterdev) - \[Dart Community Discord]\(https\://discord.com/invite/Qt6DgfAWWx) - \[r/FlutterDev on Reddit]\(https\://www\.reddit.com/r/FlutterDev) - \[GitHub issues]\(https\://github.com/flutter/flutter/issues) - \[Flutter Forum]\(https\://forum.itsallwidgets.com/) ### Host events Connect with other Flutter enthusiasts and organize local, national, and even virtual events. Events can be anything, from study groups and simple meetups, to workshops and hackathons. For inspiration and support, check out existing \[Flutter events]\[], learn more about the \[Flutter community]\[], and explore the \[Flutter Meetup Network]\[]. \[Flutter events]: https\://flutter.dev/events \[Flutter community]: https\://flutter.dev/community \[Flutter Meetup Network]: https\://www\.meetup.com/pro/flutter/ ### Post about Flutter Share your insights and projects with the wider Flutter community. There are endless options for sharing about Flutter and connecting with the developer community. Some common outlets include: - Blog posts - Video tutorials - Short-form posts - Forum threads - GitHub discussions - Link aggregation boards Post or share about whatever you're passionate about, but if you're not sure what to post, consider posting about topics that developers often ask about. If the platform you're posting on supports tagging posts, consider adding the \`#Flutter\` and \`#FlutterDev\` hashtags to help other developers find your content.

----
url: https://docs.flutter.dev/ui/interactivity/gestures.md
----

\# Taps, drags, and other gestures > How gestures, such as taps and drags, work in Flutter. This document explains how to listen for, and respond to, \_gestures\_ in Flutter. Examples of gestures include taps, drags, and scaling. The gesture system in Flutter has two separate layers. The first layer has raw pointer events that describe the location and movement of pointers (for example, touches, mice, and styli) across the screen. The second layer has \_gestures\_ that describe semantic actions that consist of one or more pointer movements. ## Pointers Pointers represent raw data about the user's interaction with the device's screen. There are four types of pointer events: \[\`PointerDownEvent\`]\[] : The pointer has contacted the screen at a particular location. \[\`PointerMoveEvent\`]\[] : The pointer has moved from one location on the screen to another. \[\`PointerUpEvent\`]\[] : The pointer has stopped contacting the screen. \[\`PointerCancelEvent\`]\[] : Input from this pointer is no longer directed towards this app. On pointer down, the framework does a \_hit test\_ on your app to determine which widget exists at the location where the pointer contacted the screen. The pointer down event (and subsequent events for that pointer) are then dispatched to the innermost widget found by the hit test. From there, the events bubble up the tree and are dispatched to all the widgets on the path from the innermost widget to the root of the tree. There is no mechanism for canceling or stopping pointer events from being dispatched further. To listen to pointer events directly from the widgets layer, use a \[\`Listener\`]\[] widget. However, generally, consider using gestures (as discussed below) instead. \[\`Listener\`]: https\://api.flutter.dev/flutter/widgets/Listener-class.html \[\`PointerCancelEvent\`]: https\://api.flutter.dev/flutter/gestures/PointerCancelEvent-class.html \[\`PointerDownEvent\`]: https\://api.flutter.dev/flutter/gestures/PointerDownEvent-class.html \[\`PointerMoveEvent\`]: https\://api.flutter.dev/flutter/gestures/PointerMoveEvent-class.html \[\`PointerUpEvent\`]: https\://api.flutter.dev/flutter/gestures/PointerUpEvent-class.html ## Gestures Gestures represent semantic actions (for example, tap, drag, and scale) that are recognized from multiple individual pointer events, potentially even multiple individual pointers. Gestures can dispatch multiple events, corresponding to the lifecycle of the gesture (for example, drag start, drag update, and drag end): \*\*Tap\*\* \`onTapDown\` : A pointer that might cause a tap has contacted the screen at a particular location. \`onTapUp\` : A pointer that triggers a tap has stopped contacting the screen at a particular location. \`onTap\` : The pointer that previously triggered the \`onTapDown\` has also triggered \`onTapUp\` which ends up causing a tap. \`onTapCancel\` : The pointer that previously triggered the \`onTapDown\` won't end up causing a tap. \*\*Double tap\*\* \`onDoubleTap\` : The user has tapped the screen at the same location twice in quick succession. \*\*Long press\*\* \`onLongPress\` : A pointer has remained in contact with the screen at the same location for a long period of time. \*\*Vertical drag\*\* \`onVerticalDragStart\` : A pointer has contacted the screen and might begin to move vertically. \`onVerticalDragUpdate\` : A pointer that is in contact with the screen and moving vertically has moved in the vertical direction. \`onVerticalDragEnd\` : A pointer that was previously in contact with the screen and moving vertically is no longer in contact with the screen and was moving at a specific velocity when it stopped contacting the screen. \*\*Horizontal drag\*\* \`onHorizontalDragStart\` : A pointer has contacted the screen and might begin to move horizontally. \`onHorizontalDragUpdate\` : A pointer that is in contact with the screen and moving horizontally has moved in the horizontal direction. \`onHorizontalDragEnd\` : A pointer that was previously in contact with the screen and moving horizontally is no longer in contact with the screen and was moving at a specific velocity when it stopped contacting the screen. \*\*Pan\*\* \`onPanStart\` : A pointer has contacted the screen and might begin to move horizontally or vertically. This callback crashes if \`onHorizontalDragStart\` or \`onVerticalDragStart\` is set. \`onPanUpdate\` : A pointer that is in contact with the screen and is moving in the vertical or horizontal direction. This callback crashes if \`onHorizontalDragUpdate\` or \`onVerticalDragUpdate\` is set. \`onPanEnd\` : A pointer that was previously in contact with screen is no longer in contact with the screen and is moving at a specific velocity when it stopped contacting the screen. This callback crashes if \`onHorizontalDragEnd\` or \`onVerticalDragEnd\` is set. ### Adding gesture detection to widgets To listen to gestures from the widgets layer, use a \[\`GestureDetector\`]\[]. :::note To learn more, watch this short Widget of the Week video on the \`GestureDetector\` widget: ::: If you're using Material Components, many of those widgets already respond to taps or gestures. For example, \[\`IconButton\`]\[] and \[\`TextButton\`]\[] respond to presses (taps), and \[\`ListView\`]\[] responds to swipes to trigger scrolling. If you aren't using those widgets, but you want the "ink splash" effect on a tap, you can use \[\`InkWell\`]\[]. \[\`GestureDetector\`]: https\://api.flutter.dev/flutter/widgets/GestureDetector-class.html \[\`IconButton\`]: https\://pub.dev/documentation/material\_ui/latest/material\_ui/IconButton-class.html \[\`InkWell\`]: https\://pub.dev/documentation/material\_ui/latest/material\_ui/InkWell-class.html \[\`ListView\`]: https\://api.flutter.dev/flutter/widgets/ListView-class.html \[\`TextButton\`]: https\://pub.dev/documentation/material\_ui/latest/material\_ui/TextButton-class.html ### Gesture disambiguation At a given location on screen, there might be multiple gesture detectors. For example: \* A \`ListTile\` has a tap recognizer that responds to the entire \`ListTile\`, and a nested one around a trailing icon button. The screen rect of the trailing icon is now covered by two gesture recognizers that need to negotiate for who handles the gesture if it turns out to be a tap. \* A single \`GestureDetector\` covers a screen area configured to handle multiple gestures, such as a long press and a tap. The \`tap\` recognizer must now negotiate with the \`long press\` recognizer when the user touches that part of the screen. Depending on what happens next with that pointer, one of the two recognizers receives the gesture, or neither receives the gesture if the user performs something that's neither a tap nor a long press. All of these gesture detectors listen to the stream of pointer events as they flow past and attempt to recognize specific gestures. The \[\`GestureDetector\`] widget decides which gestures to attempt to recognize based on which of its callbacks are non-null. When there is more than one gesture recognizer for a given pointer on the screen, the framework disambiguates which gesture the user intends by having each recognizer join the \_gesture arena\_. The gesture arena determines which gesture wins using the following rules: \* At any time, a recognizer can eliminate itself and leave the arena. If there's only one recognizer left in the arena, that recognizer wins. \* At any time, a recognizer can declare itself the winner, causing all of the remaining recognizers to lose. For example, when disambiguating horizontal and vertical dragging, both recognizers enter the arena when they receive the pointer down event. The recognizers observe the pointer move events. If the user moves the pointer more than a certain number of logical pixels horizontally, the horizontal recognizer declares the win and the gesture is interpreted as a horizontal drag. Similarly, if the user moves more than a certain number of logical pixels vertically, the vertical recognizer declares itself the winner. The gesture arena is beneficial when there is only a horizontal (or vertical) drag recognizer. In that case, there is only one recognizer in the arena and the horizontal drag is recognized immediately, which means the first pixel of horizontal movement can be treated as a drag and the user won't need to wait for further gesture disambiguation.

----
url: https://docs.flutter.dev/cookbook/testing/widget/scrolling.md
----

\# Handle scrolling > How to handle scrolling in a widget test.

<!--?code-excerpt path-base="cookbook/testing/widget/scrolling/"?-->

Many apps feature lists of content, from email clients to music apps and beyond. To verify that lists contain the expected content using widget tests, you need a way to scroll through lists to search for particular items. To scroll through lists using the widget tests, use the methods provided by the \[\`WidgetTester\`]\[] class, which is included in the \[\`flutter\_test\`]\[] package: In this recipe, learn how to scroll through a list of items to verify a specific widget is being displayed, and the pros and cons of different approaches. This recipe uses the following steps: 1. Create an app with a list of items. 2. Write a test that scrolls through the list. 3. Run the test. ## 1. Create an app with a list of items 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 \[Work with long lists]\[] recipe. If you're unsure of how to work with long lists, see that recipe for an introduction. Add keys to the widgets you want to interact with inside the widget tests.

<!--?code-excerpt "lib/main.dart"?-->

\`\`\`dart import 'package:material\_ui/material\_ui.dart'; void main() { runApp(MyApp(items: List.generate(10000, (i) => 'Item $i'))); } class MyApp extends StatelessWidget { final List items; const MyApp({super.key, required this.items}); @override Widget build(BuildContext context) { const title = 'Long List'; return MaterialApp( title: title, home: Scaffold( appBar: AppBar(title: const Text(title)), body: ListView\.builder( // Add a key to the ListView. This makes it possible to // find the list and scroll through it in the tests. key: const 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 makes // it possible to look for a particular item in the list // and verify that the text is correct key: Key('item\_${index}\_text'), ), ); }, ), ), ); } } \`\`\` ## 2. Write a test that scrolls 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 \[\`WidgetTester\`]\[] class provides the \[\`scrollUntilVisible()\`]\[] method, which scrolls through a list until a specific widget is visible. This is useful because the height of the items in the list can change depending on the device. 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, the \`scrollUntilVisible()\` method repeatedly scrolls through a list of items until it finds what it's 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/widget\_test.dart\`.

<!--?code-excerpt "test/widget_test.dart (ScrollWidgetTest)"?-->

\`\`\`dart // This is a basic Flutter widget test. // // To perform an interaction with a widget in your test, use the WidgetTester // utility that Flutter provides. For example, you can send tap and scroll // gestures. You can also use WidgetTester to find child widgets in the widget // tree, read text, and verify that the values of widget properties are correct. import 'package:flutter\_test/flutter\_test.dart'; import 'package:material\_ui/material\_ui.dart'; import 'package:scrolling/main.dart'; void main() { testWidgets('finds a deep item in a long list', (tester) async { // Build our app and trigger a frame. await tester.pumpWidget( MyApp(items: List.generate(10000, (i) => 'Item $i')), ); final listFinder = find.byType(Scrollable); final itemFinder = find.byKey(const ValueKey('item\_50\_text')); // Scroll until the item to be found appears. await tester.scrollUntilVisible(itemFinder, 500.0, scrollable: listFinder); // Verify that the item contains the correct text. expect(itemFinder, findsOneWidget); }); } \`\`\` ## 3. Run the test Run the test using the following command from the root of the project: \`\`\`console flutter test test/widget\_test.dart \`\`\` \[\`flutter\_test\`]: https\://api.flutter.dev/flutter/flutter\_test/flutter\_test-library.html \[\`WidgetTester\`]: https\://api.flutter.dev/flutter/flutter\_test/WidgetTester-class.html \[\`ListView\.builder\`]: https\://api.flutter.dev/flutter/widgets/ListView/ListView\.builder.html \[\`scrollUntilVisible()\`]: https\://api.flutter.dev/flutter/flutter\_test/WidgetController/scrollUntilVisible.html \[Work with long lists]: /cookbook/lists/long-lists

----
url: https://docs.flutter.dev/platform-integration/ios/apple-frameworks.md
----

\# Leveraging Apple's system APIs and frameworks > Learn about Flutter plugins that offer equivalent functionalities to Apple's frameworks. When you come from iOS development, you might need to find Flutter plugins that offer the same abilities as Apple's system libraries. This might include accessing device hardware or interacting with specific frameworks like \`HealthKit\`. For an overview of how the SwiftUI framework compares to Flutter, see \[Flutter for SwiftUI developers]\[]. ## Introducing Flutter plugins Dart calls libraries that contain platform-specific code \_plugins\_, short for "plugin package". When developing an app with Flutter, you use \_plugins\_ to interact with system libraries. In your Dart code, you use the plugin's Dart API to call the native code from the system library being used. This means that you can write the code to call the Dart API. The API then makes it work for all platforms that the plugin supports. To learn more about plugins, see \[Using packages]\[]. Though this page links to some popular plugins, you can find thousands more, along with examples, on \[pub.dev]\[]. The following table does not endorse any particular plugin. If you can't find a package that meets your needs, you can create your own or use platform channels directly in your project. To learn more, check out \[Writing platform-specific code]\[]. ## Adding a plugin to your project To use an Apple framework within your native project, import it into your Swift or Objective-C file. To add a Flutter plugin, run \`flutter pub add package\_name\` from the root of your project. This adds the dependency to your \[\`pubspec.yaml\`]\[] file. After you add the dependency, add an \`import\` statement for the package in your Dart file. You might need to change app settings or initialization logic. If that's needed, the package's "Readme" page on \[pub.dev]\[] should provide details. ### Flutter plugins and Apple frameworks | Use Case | Apple Framework or Class | Flutter integration | |------------------------------------------------|----------------------------------------------------------------------------------------|------------------------------| | Access the photo library | \`PhotoKit\` using the \`Photos\` and \`PhotosUI \` frameworks and \`UIImagePickerController\` | \[\`image\_picker\`]\[] | | Access the camera | \`UIImagePickerController\` using the \`.camera\` \`sourceType\` | \[\`image\_picker\`]\[] | | Use advanced camera features | \`AVFoundation\` | \[\`camera\`]\[] | | Offer In-app purchases | \`StoreKit\` | \[\`in\_app\_purchase\`]\[]\[^1] | | Process payments | \`PassKit\` | \[\`pay\`]\[]\[^2] | | Send push notifications | \`UserNotifications\` | \[\`firebase\_messaging\`]\[]\[^3] | | Access GPS coordinates | \`CoreLocation\` | \[\`geolocator\`]\[] | | Access sensor data\[^4] | \`CoreMotion\` | \[\`sensors\_plus\`]\[] | | Make network requests | \`URLSession\` | \[\`http\`]\[] | | Store key-values | \`@AppStorage\` property wrapper and \`NSUserDefaults\` | \[\`shared\_preferences\`]\[] | | Persist to a database | \`CoreData\` or SQLite | \[\`sqflite\`]\[] | | Access health data | \`HealthKit\` | \[\`health\`]\[] | | Use machine learning | \`CoreML\` | \[\`google\_ml\_kit\`]\[]\[^5] | | Recognize text | \`VisionKit\` | \[\`google\_ml\_kit\`]\[]\[^5] | | Recognize speech | \`Speech\` | \[\`speech\_to\_text\`]\[] | | Use augmented reality | \`ARKit\` | \[\`arkit\_plugin\`]\[] | | Access weather data | \`WeatherKit\` | \[\`weather\`]\[]\[^6] | | Access and manage contacts | \`Contacts\` | \[\`flutter\_contacts\`]\[] | | Expose quick actions on the home screen | \`UIApplicationShortcutItem\` | \[\`quick\_actions\`]\[] | | Configure, update and communicate with Widgets | \`WidgetKit\` | \[\`home\_widget\`]\[] | | Automate app actions with Siri/Shortcuts | \`AppIntents\` | \[\`intelligence\`]\[] | {:.table .table-striped .nowrap} \[^1]: Supports both Google Play Store on Android and Apple App Store on iOS. \[^2]: Adds Google Pay payments on Android and Apple Pay payments on iOS. \[^3]: Uses Firebase Cloud Messaging and integrates with APNs. \[^4]: Includes sensors like accelerometer, gyroscope, etc. \[^5]: Uses Google's ML Kit and supports various features like text recognition, face detection, image labeling, landmark recognition, and barcode scanning. You can also create a custom model with Firebase. To learn more, see \[Use a custom TensorFlow Lite model with Flutter]\[]. \[^6]: Uses the \[OpenWeatherMap API]\[]. Other packages exist that can pull from different weather APIs. \[Flutter for SwiftUI developers]: /flutter-for/swiftui-devs \[Using packages]: /packages-and-plugins/using-packages \[pub.dev]: https\://pub.dev/packages \[\`shared\_preferences\`]: https\://pub.dev/packages/shared\_preferences \[\`http\`]: https\://pub.dev/packages/http \[\`sensors\_plus\`]: https\://pub.dev/packages/sensors\_plus \[\`geolocator\`]: https\://pub.dev/packages/geolocator \[\`image\_picker\`]: https\://pub.dev/packages/image\_picker \[\`pubspec.yaml\`]: /tools/pubspec \[\`quick\_actions\`]: https\://pub.dev/packages/quick\_actions \[\`in\_app\_purchase\`]: https\://pub.dev/packages/in\_app\_purchase \[\`pay\`]: https\://pub.dev/packages/pay \[\`firebase\_messaging\`]: https\://pub.dev/packages/firebase\_messaging \[\`google\_ml\_kit\`]: https\://pub.dev/packages/google\_ml\_kit \[Use a custom TensorFlow Lite model with Flutter]: https\://firebase.google.com/docs/ml/flutter/use-custom-models \[\`speech\_to\_text\`]: https\://pub.dev/packages/speech\_to\_text \[\`arkit\_plugin\`]: https\://pub.dev/packages/arkit\_plugin \[\`weather\`]: https\://pub.dev/packages/weather \[\`flutter\_contacts\`]: https\://pub.dev/packages/flutter\_contacts \[\`health\`]: https\://pub.dev/packages/health \[OpenWeatherMap API]: https\://openweathermap.org/api \[\`sqflite\`]: https\://pub.dev/packages/sqflite \[Writing platform-specific code]: /platform-integration/platform-channels \[\`camera\`]: https\://pub.dev/packages/camera \[\`home\_widget\`]: https\://pub.dev/packages/home\_widget \[\`intelligence\`]: https\://pub.dev/packages/intelligence

----
url: https://docs.flutter.dev/data-and-backend/networking
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Networking](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

## Cross-platform http networking

[#](#cross-platform-http-networking)

The [`http`](https://pub.dev/packages/http) package provides the simplest way to issue http requests. This package is supported on Android, iOS, macOS, Windows, Linux and the web.

## Platform notes

[#](#platform-notes)

Some platforms require additional steps, as detailed below.

### Android

[#](#android)

Android apps must [declare their use of the internet](https://developer.android.com/training/basics/network-ops/connecting) in the Android manifest (`AndroidManifest.xml`):

xml

```
<manifest xmlns:android...>
 ...
 <uses-permission android:name="android.permission.INTERNET" />
 <application ...
</manifest>
```

### macOS

[#](#macos)

macOS apps must allow network access in the relevant `*.entitlements` files.

xml

```
<key>com.apple.security.network.client</key>
<true/>
```

Learn more about [setting up entitlements](/platform-integration/macos/building#setting-up-entitlements).

## Samples

[#](#samples)

For a practical sample of various networking tasks (incl. fetching data, WebSockets, and parsing data in the background) see the [networking cookbook recipes](/cookbook/networking).

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/data-and-backend/networking.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/data-and-backend/networking\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/data-and-backend/networking.md "Report an issue with this page").

----
url: https://docs.flutter.dev/add-to-app/android/add-flutter-screen
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Add a Flutter screen to an Android app](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

This guide describes how to add a single Flutter screen to an existing Android app. A Flutter screen can be added as a normal, opaque screen, or as a see-through, translucent screen. Both options are described in this guide.

## Add a normal Flutter screen

[#](#add-a-normal-flutter-screen)

### Step 1: Add FlutterActivity to AndroidManifest.xml

[#](#step-1-add-flutteractivity-to-androidmanifest-xml)

Flutter provides [`FlutterActivity`](https://api.flutter.dev/javadoc/io/flutter/embedding/android/FlutterActivity.html) to display a Flutter experience within an Android app. Like any other [`Activity`](https://developer.android.com/reference/android/app/Activity), `FlutterActivity` must be registered in your `AndroidManifest.xml`. Add the following XML to your `AndroidManifest.xml` file under your `application` tag:

xml

```
<activity
  android:name="io.flutter.embedding.android.FlutterActivity"
  android:theme="@style/LaunchTheme"
  android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
  android:hardwareAccelerated="true"
  android:windowSoftInputMode="adjustResize"
  />
```

The reference to `@style/LaunchTheme` can be replaced by any Android theme that want to apply to your `FlutterActivity`. The choice of theme dictates the colors applied to Android's system chrome, like Android's navigation bar, and to the background color of the `FlutterActivity` just before the Flutter UI renders itself for the first time.

### Step 2: Launch FlutterActivity

[#](#step-2-launch-flutteractivity)

With `FlutterActivity` registered in your manifest file, add code to launch `FlutterActivity` from whatever point in your app that you'd like. The following example shows `FlutterActivity` being launched from an `OnClickListener`.

* [Jetpack Compose](#99-tab-panel)
* [Kotlin](#100-tab-panel)
* [Java](#101-tab-panel)

ExistingActivity.kt

kotlin

```
MyButton(onClick = {
    startActivity(
        FlutterActivity.createDefaultIntent(this)
    )
})
​
@Composable
fun MyButton(onClick: () -> Unit) {
    Button(onClick = onClick) {
        Text("Launch Flutter!")
    }
}
```

ExistingActivity.kt

kotlin

```
myButton.setOnClickListener {
  startActivity(
    FlutterActivity.createDefaultIntent(this)
  )
}
```

ExistingActivity.java

java

```
myButton.setOnClickListener(new OnClickListener() {
  @Override
  public void onClick(View v) {
    startActivity(
      FlutterActivity.createDefaultIntent(currentActivity)
    );
  }
});
```

The previous example assumes that your Dart entrypoint is called `main()`, and your initial Flutter route is '/'. The Dart entrypoint can't be changed using `Intent`, but the initial route can be changed using `Intent`. The following example demonstrates how to launch a `FlutterActivity` that initially renders a custom route in Flutter.

* [Jetpack Compose](#102-tab-panel)
* [Kotlin](#103-tab-panel)
* [Java](#104-tab-panel)

ExistingActivity.kt

kotlin

```
MyButton(onClick = {
  startActivity(
    FlutterActivity
      .withNewEngine()
      .initialRoute("/my_route")
      .build(this)
  )
})
​
@Composable
fun MyButton(onClick: () -> Unit) {
    Button(onClick = onClick) {
        Text("Launch Flutter!")
    }
}
```

ExistingActivity.kt

kotlin

```
myButton.setOnClickListener {
  startActivity(
    FlutterActivity
      .withNewEngine()
      .initialRoute("/my_route")
      .build(this)
  )
}
```

ExistingActivity.java

java

```
myButton.addOnClickListener(new OnClickListener() {
  @Override
  public void onClick(View v) {
    startActivity(
      FlutterActivity
        .withNewEngine()
        .initialRoute("/my_route")
        .build(currentActivity)
      );
  }
});
```

Replace `"/my_route"` with your desired initial route.

The use of the `withNewEngine()` factory method configures a `FlutterActivity` that internally create its own [`FlutterEngine`](https://api.flutter.dev/javadoc/io/flutter/embedding/engine/FlutterEngine.html) instance. This comes with a non-trivial initialization time. The alternative approach is to instruct `FlutterActivity` to use a pre-warmed, cached `FlutterEngine`, which minimizes Flutter's initialization time. That approach is discussed next.

### Step 3: (Optional) Use a cached FlutterEngine

[#](#step-3-optional-use-a-cached-flutterengine)

Every `FlutterActivity` creates its own `FlutterEngine` by default. Each `FlutterEngine` has a non-trivial warm-up time. This means that launching a standard `FlutterActivity` comes with a brief delay before your Flutter experience becomes visible. To minimize this delay, you can warm up a `FlutterEngine` before arriving at your `FlutterActivity`, and then you can use your pre-warmed `FlutterEngine` instead.

To pre-warm a `FlutterEngine`, find a reasonable location in your app to instantiate a `FlutterEngine`. The following example arbitrarily pre-warms a `FlutterEngine` in the `Application` class:

* [Kotlin](#105-tab-panel)
* [Java](#106-tab-panel)

MyApplication.kt

kotlin

```
class MyApplication : Application() {
  lateinit var flutterEngine : FlutterEngine
​
  override fun onCreate() {
    super.onCreate()
​
    // Instantiate a FlutterEngine.
    flutterEngine = FlutterEngine(this)
​
    // Start executing Dart code to pre-warm the FlutterEngine.
    flutterEngine.dartExecutor.executeDartEntrypoint(
      DartExecutor.DartEntrypoint.createDefault()
    )
​
    // Cache the FlutterEngine to be used by FlutterActivity.
    FlutterEngineCache
      .getInstance()
      .put("my_engine_id", flutterEngine)
  }
}
```

MyApplication.java

java

```
public class MyApplication extends Application {
  public FlutterEngine flutterEngine;
​
  @Override
  public void onCreate() {
    super.onCreate();
    // Instantiate a FlutterEngine.
    flutterEngine = new FlutterEngine(this);
​
    // Start executing Dart code to pre-warm the FlutterEngine.
    flutterEngine.getDartExecutor().executeDartEntrypoint(
      DartEntrypoint.createDefault()
    );
​
    // Cache the FlutterEngine to be used by FlutterActivity.
    FlutterEngineCache
      .getInstance()
      .put("my_engine_id", flutterEngine);
  }
}
```

The ID passed to the [`FlutterEngineCache`](https://api.flutter.dev/javadoc/io/flutter/embedding/engine/FlutterEngineCache.html) can be whatever you want. Make sure that you pass the same ID to any `FlutterActivity` or [`FlutterFragment`](https://api.flutter.dev/javadoc/io/flutter/embedding/android/FlutterFragment.html) that should use the cached `FlutterEngine`. Using `FlutterActivity` with a cached `FlutterEngine` is discussed next.

With a pre-warmed, cached `FlutterEngine`, you now need to instruct your `FlutterActivity` to use the cached `FlutterEngine` instead of creating a new one. To accomplish this, use `FlutterActivity`'s `withCachedEngine()` builder:

* [Kotlin](#107-tab-panel)
* [Java](#108-tab-panel)

ExistingActivity.kt

kotlin

```
myButton.setOnClickListener {
  startActivity(
    FlutterActivity
      .withCachedEngine("my_engine_id")
      .build(this)
  )
}
```

ExistingActivity.java

java

```
myButton.addOnClickListener(new OnClickListener() {
  @Override
  public void onClick(View v) {
    startActivity(
      FlutterActivity
        .withCachedEngine("my_engine_id")
        .build(currentActivity)
      );
  }
});
```

When using the `withCachedEngine()` factory method, pass the same ID that you used when caching the desired `FlutterEngine`.

Now, when you launch `FlutterActivity`, there is significantly less delay in the display of Flutter content.

#### Initial route with a cached engine

[#](#initial-route-with-a-cached-engine)

The concept of an initial route is available when configuring a `FlutterActivity` or a `FlutterFragment` with a new `FlutterEngine`. However, `FlutterActivity` and `FlutterFragment` don't offer the concept of an initial route when using a cached engine. This is because a cached engine is expected to already be running Dart code, which means it's too late to configure the initial route.

Developers that would like their cached engine to begin with a custom initial route can configure their cached `FlutterEngine` to use a custom initial route just before executing the Dart entrypoint. The following example demonstrates the use of an initial route with a cached engine:

* [Kotlin](#109-tab-panel)
* [Java](#110-tab-panel)

MyApplication.kt

kotlin

```
class MyApplication : Application() {
  lateinit var flutterEngine : FlutterEngine
  override fun onCreate() {
    super.onCreate()
    // Instantiate a FlutterEngine.
    flutterEngine = FlutterEngine(this)
    // Configure an initial route.
    flutterEngine.navigationChannel.setInitialRoute("your/route/here");
    // Start executing Dart code to pre-warm the FlutterEngine.
    flutterEngine.dartExecutor.executeDartEntrypoint(
      DartExecutor.DartEntrypoint.createDefault()
    )
    // Cache the FlutterEngine to be used by FlutterActivity or FlutterFragment.
    FlutterEngineCache
      .getInstance()
      .put("my_engine_id", flutterEngine)
  }
}
```

MyApplication.java

java

```
public class MyApplication extends Application {
  @Override
  public void onCreate() {
    super.onCreate();
    // Instantiate a FlutterEngine.
    flutterEngine = new FlutterEngine(this);
    // Configure an initial route.
    flutterEngine.getNavigationChannel().setInitialRoute("your/route/here");
    // Start executing Dart code to pre-warm the FlutterEngine.
    flutterEngine.getDartExecutor().executeDartEntrypoint(
      DartEntrypoint.createDefault()
    );
    // Cache the FlutterEngine to be used by FlutterActivity or FlutterFragment.
    FlutterEngineCache
      .getInstance()
      .put("my_engine_id", flutterEngine);
  }
}
```

By setting the initial route of the navigation channel, the associated `FlutterEngine` displays the desired route upon initial execution of the `runApp()` Dart function.

Changing the initial route property of the navigation channel after the initial execution of `runApp()` has no effect. Developers who would like to use the same `FlutterEngine` between different `Activity`s and `Fragment`s and switch the route between those displays need to set up a method channel and explicitly instruct their Dart code to change `Navigator` routes.

## Add a translucent Flutter screen

[#](#add-a-translucent-flutter-screen)

Most full-screen Flutter experiences are opaque. However, some apps would like to deploy a Flutter screen that looks like a modal, for example, a dialog or bottom sheet. Flutter supports translucent `FlutterActivity`s out of the box.

To make your `FlutterActivity` translucent, make the following changes to the regular process of creating and launching a `FlutterActivity`.

### Step 1: Use a theme with translucency

[#](#step-1-use-a-theme-with-translucency)

Android requires a special theme property for `Activity`s that render with a translucent background. Create or update an Android theme with the following property:

xml

```
<style name="MyTheme" parent="@style/MyParentTheme">
  <item name="android:windowIsTranslucent">true</item>
</style>
```

Then, apply the translucent theme to your `FlutterActivity`.

xml

```
<activity
  android:name="io.flutter.embedding.android.FlutterActivity"
  android:theme="@style/MyTheme"
  android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
  android:hardwareAccelerated="true"
  android:windowSoftInputMode="adjustResize"
  />
```

Your `FlutterActivity` now supports translucency. Next, you need to launch your `FlutterActivity` with explicit transparency support.

### Step 2: Start FlutterActivity with transparency

[#](#step-2-start-flutteractivity-with-transparency)

To launch your `FlutterActivity` with a transparent background, pass the appropriate `BackgroundMode` to the `IntentBuilder`:

* [Kotlin](#111-tab-panel)
* [Java](#112-tab-panel)

ExistingActivity.kt

kotlin

```
// Using a new FlutterEngine.
startActivity(
  FlutterActivity
    .withNewEngine()
    .backgroundMode(FlutterActivityLaunchConfigs.BackgroundMode.transparent)
    .build(this)
);
​
// Using a cached FlutterEngine.
startActivity(
  FlutterActivity
    .withCachedEngine("my_engine_id")
    .backgroundMode(FlutterActivityLaunchConfigs.BackgroundMode.transparent)
    .build(this)
);
```

ExistingActivity.java

java

```
// Using a new FlutterEngine.
startActivity(
  FlutterActivity
    .withNewEngine()
    .backgroundMode(FlutterActivityLaunchConfigs.BackgroundMode.transparent)
    .build(context)
);
​
// Using a cached FlutterEngine.
startActivity(
  FlutterActivity
    .withCachedEngine("my_engine_id")
    .backgroundMode(FlutterActivityLaunchConfigs.BackgroundMode.transparent)
    .build(context)
);
```

You now have a `FlutterActivity` with a transparent background.

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/add-to-app/android/add-flutter-screen.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/add-to-app/android/add-flutter-screen\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/add-to-app/android/add-flutter-screen.md "Report an issue with this page").

----
url: https://docs.flutter.dev/ui/internationalization.md
----

\# Internationalizing Flutter apps > How to internationalize your Flutter app.

<!--?code-excerpt path-base="internationalization"?-->

:::secondary What you'll learn \* How to track the device's locale (the user's preferred language). \* How to enable locale-specific Material or Cupertino widgets. \* How to manage locale-specific app values. \* How to define the locales an app supports. ::: If your app might be deployed to users who speak another language then you'll need to internationalize it. That means you need to write the app in a way that makes it possible to localize values like text and layouts for each language or locale that the app supports. Flutter provides widgets and classes that help with internationalization and the Flutter libraries themselves are internationalized. This page covers concepts and workflows necessary to localize a Flutter application using the \`MaterialApp\` and \`CupertinoApp\` classes, as most apps are written that way. However, applications written using the lower level \`WidgetsApp\` class can also be internationalized using the same classes and logic. ## Introduction to localizations in Flutter This section provides a tutorial on how to create and internationalize a new Flutter application, along with any additional setup that a target platform might require. You can find the source code for this example in \[\`gen\_l10n\_example\`]\[]. \[\`gen\_l10n\_example\`]: https\://github.com/flutter/website/tree/main/examples/internationalization/gen\_l10n\_example ### Set up an internationalized app with \`flutter\_localizations\` {:#setting-up} By default, Flutter only provides US English localizations. To add support for other languages, an application must specify additional \`MaterialApp\` (or \`CupertinoApp\`) properties, and include a package called \`flutter\_localizations\`. To begin, start by creating a new Flutter application in a directory of your choice with the \`flutter create\` command. \`\`\`console $ flutter create \`\`\` Flutter projects will soon be encouraged to use the \`material\_ui\` and \`cupertino\_ui\` packages, but the \`flutter create\` command hasn't yet been updated to do this by default. You can get ahead of things and migrate now by running the following command: \`\`\`console $ dart fix --apply --code=migrate\_design\_widgets \`\`\` To use \`flutter\_localizations\`, add it and the \`intl\` package as dependencies: \`\`\`console $ flutter pub add flutter\_localizations:"{sdk: flutter}" intl:any \`\`\` This updates your \`pubspec.yaml\` file with the following entries:

<!--?code-excerpt "gen_l10n_example/pubspec.yaml (flutter-localizations)"?-->

\`\`\`yaml title="pubspec.yaml" highlightLines=4-6 dependencies: flutter: sdk: flutter flutter\_localizations: sdk: flutter intl: any \`\`\` Import the package with the \`flutter\_localizations\` prefix: \`\`\`dart import 'package:flutter\_localizations/flutter\_localizations.dart' as flutter\_localizations; \`\`\` Then specify \`localizationsDelegates\` and \`supportedLocales\` for your \`MaterialApp\`, \`CupertinoApp\`, or \`WidgetsApp\`:

<!--?code-excerpt "gen_l10n_example/lib/no_app_localizations.dart (material-app)"?-->

\`\`\`dart highlightLines=3-7 return const MaterialApp( title: 'Localizations Sample App', localizationsDelegates: GlobalMaterialLocalizations.delegates, supportedLocales: \[ Locale('en'), // English Locale('es'), // Spanish ], home: MyHomePage(), ); \`\`\` :::note If your app is based on \`CupertinoApp\` instead of \`MaterialApp\`, use \`GlobalCupertinoLocalizations.delegates\`. If your app is based on \`WidgetsApp\` instead of \`MaterialApp\`, use \`flutter\_localizations.GlobalWidgetsLocalizations.delegate\`. ::: After depending on \`flutter\_localizations\` and adding the previous code, Flutter localizes the Material and Cupertino libraries in the selected supported locale. Widgets should be adapted to the localized messages and use the appropriate left-to-right or right-to-left layout. Try switching the target platform's locale to Spanish (\`es\`) and the messages should be localized. The full \`Locale.fromSubtags\` constructor is preferred as it supports \[\`scriptCode\`]\[], though the \`Locale\` default constructor is still fully valid. \[\`scriptCode\`]: https\://api.flutter.dev/flutter/package-intl\_locale/Locale/scriptCode.html The elements of the \`localizationsDelegates\` list are factories that produce collections of localized values. \`GlobalMaterialLocalizations.delegate\` provides localized strings and other values for the Material Components library. \`flutter\_localizations.GlobalWidgetsLocalizations.delegate\` defines the default text direction, either left-to-right or right-to-left, for the \`widgets.dart\` library. More information about these app properties, the types they depend on, and how internationalized Flutter apps are typically structured, is covered on this page. \[language-count]: https\://api.flutter.dev/flutter/flutter\_localizations/GlobalMaterialLocalizations-class.html []()[]()### Override the locale \`Localizations.override\` is a factory constructor for the \`Localizations\` widget that allows for (the typically rare) situation where a section of your application needs to be localized to a different locale than the locale configured for your device. To observe this behavior, add a call to \`Localizations.override\` and a simple \`CalendarDatePicker\`:

<!--?code-excerpt "gen_l10n_example/lib/examples.dart (date-picker)"?-->

\`\`\`dart Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text(widget.title)), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: \[ // Add the following code Localizations.override( context: context, locale: const Locale('es'), // Using a Builder to get the correct BuildContext. // Alternatively, you can create a new widget and Localizations.override // will pass the updated BuildContext to the new widget. child: Builder( builder: (context) { // A toy example for an internationalized Material widget. return CalendarDatePicker( initialDate: DateTime.now(), firstDate: DateTime(1900), lastDate: DateTime(2100), onDateChanged: (value) {}, ); }, ), ), ], ), ), ); } \`\`\` Hot reload the app and the \`CalendarDatePicker\` widget should re-render in Spanish. []()[]()### Add your own localized messages To add localized text to your application, complete the following instructions: 1. Add the \`flutter\_localizations\` and \`intl\` packages as dependencies: \`\`\`console $ flutter pub add flutter\_localizations:"{sdk: flutter}" intl:any \`\`\` This adds the following entries in the \`pubspec.yaml\` file:

<!--?code-excerpt "gen_l10n_example/pubspec.yaml (flutter-localizations)"?-->

\`\`\`yaml title="pubspec.yaml" highlightLines=4-6 dependencies: flutter: sdk: flutter flutter\_localizations: sdk: flutter intl: any \`\`\` 2. Open the \`pubspec.yaml\` file and enable the \`generate\` flag. This flag is found in the \`flutter\` section in the pubspec file.

<!--?code-excerpt "gen_l10n_example/pubspec.yaml (generate)"?-->

\`\`\`yaml title="pubspec.yaml" highlightLines=3 # The following section is specific to Flutter. flutter: generate: true # Add this line \`\`\` 3. Add a new YAML file to the root directory of the Flutter project. Name this file \`l10n.yaml\` and include the following content:

<!--?code-excerpt "gen_l10n_example/l10n.yaml"?-->

\`\`\`yaml title="l10n.yaml" arb-dir: lib/l10n template-arb-file: app\_en.arb output-localization-file: app\_localizations.dart \`\`\` This file configures the localization tool. In this example, you've done the following: \* Put the \[App Resource Bundle]\[] (\`.arb\`) input files in \`${FLUTTER\_PROJECT}/lib/l10n\`. The \`.arb\` provide localization resources for your app. \* Set the English template as \`app\_en.arb\`. \* Told Flutter to generate localizations in the \`app\_localizations.dart\` file. 4. In \`${FLUTTER\_PROJECT}/lib/l10n\`, add the \`app\_en.arb\` template file. For example:

<!--?code-excerpt "gen_l10n_example/lib/l10n/app_en.arb" take="5" replace="/},/}\n}/g"?-->

\`\`\`json title="app\_en.arb" { "helloWorld": "Hello World!", "@helloWorld": { "description": "The conventional newborn programmer greeting" } } \`\`\` 5. Add another bundle file called \`app\_es.arb\` in the same directory. In this file, add the Spanish translation of the same message.

<!--?code-excerpt "gen_l10n_example/lib/l10n/app_es.arb"?-->

\`\`\`json title="app\_es.arb" { "helloWorld": "¡Hola Mundo!" } \`\`\` 6. Now, run \`flutter pub get\` or \`flutter run\` and code generation takes place automatically. You should find generated files in the directory at the path you specified with the \`arb-dir\` or \`output-dir\` options Alternatively, you can also run \`flutter gen-l10n\` to generate the same files without running the app. 7. Import \`app\_localizations.dart\`, then add \`AppLocalizations.delegate\` to the \`localizationsDelegates\` list in the \`MaterialApp\` constructor:

<!--?code-excerpt "gen_l10n_example/lib/main.dart (app-localizations-import)"?-->

\`\`\`dart import 'l10n/app\_localizations.dart'; \`\`\`

<!--?code-excerpt "gen_l10n_example/lib/main.dart (material-app)"?-->

\`\`\`dart highlightLines=4 return const MaterialApp( title: 'Localizations Sample App', localizationsDelegates: \[ AppLocalizations.delegate, // Add this line ...GlobalMaterialLocalizations.delegates, ], supportedLocales: \[ Locale('en'), // English Locale('es'), // Spanish ], home: MyHomePage(), ); \`\`\` The \`AppLocalizations\` class also provides an auto-generated \`supportedLocales\` list. You can use it instead of providing the locales manually.

<!--?code-excerpt "gen_l10n_example/lib/examples.dart (material-app)"?-->

\`\`\`dart highlightLines=7 const MaterialApp( title: 'Localizations Sample App', localizationsDelegates: \[ AppLocalizations.delegate, ...GlobalMaterialLocalizations.delegates, ], supportedLocales: AppLocalizations.supportedLocales, ); \`\`\` 8. Once the Material app has started, you can use \`AppLocalizations\` anywhere in your app:

<!--?code-excerpt "gen_l10n_example/lib/main.dart (internationalized-title)"?-->

\`\`\`dart appBar: AppBar( // The \[AppBar] title text should update its message // according to the system locale of the target platform. // Switching between English and Spanish locales should // cause this text to update. title: Text(AppLocalizations.of(context)!.helloWorld), ), \`\`\` :::note The Material app has to actually be started to initialize \`AppLocalizations\`. If the app hasn't yet started, \`AppLocalizations.of(context)!.helloWorld\` causes a null exception. ::: This code generates a \`Text\` widget that displays "Hello World!" if the target device's locale is set to English, and "¡Hola Mundo!" if the target device's locale is set to Spanish. In the \`arb\` files, the key of each entry is used as the method name of the getter, while the value of that entry contains the localized message. The \[\`gen\_l10n\_example\`]\[] uses this tool. To localize your device app description, pass the localized string to \[\`MaterialApp.onGenerateTitle\`]\[]:

<!--?code-excerpt "intl_example/lib/main.dart (app-title)"?-->

\`\`\`dart return MaterialApp( onGenerateTitle: (context) => DemoLocalizations.of(context).title, \`\`\` \[App Resource Bundle]: https\://github.com/google/app-resource-bundle \[\`gen\_l10n\_example\`]: https\://github.com/flutter/website/tree/main/examples/internationalization/gen\_l10n\_example \[\`MaterialApp.onGenerateTitle\`]: https\://pub.dev/documentation/material\_ui/latest/material\_ui/MaterialApp/onGenerateTitle.html ### Placeholders, plurals, and selects :::tip When using VS Code, add the \[arb-editor extension]\[]. This extension adds syntax highlighting, snippets, diagnostics, and quick fixes to help edit \`.arb\` template files. ::: \[arb-editor extension]: https\://marketplace.visualstudio.com/items?itemName=Google.arb-editor You can also include application values in a message with special syntax that uses a \_placeholder\_ to generate a method instead of a getter. A placeholder, which must be a valid Dart identifier name, becomes a positional parameter in the generated method in the \`AppLocalizations\` code. Define a placeholder name by wrapping it in curly braces as follows: \`\`\`json "{placeholderName}" \`\`\` Define each placeholder in the \`placeholders\` object in the app's \`.arb\` file. For example, to define a hello message with a \`userName\` parameter, add the following to \`lib/l10n/app\_en.arb\`:

<!--?code-excerpt "gen_l10n_example/lib/l10n/app_en.arb" skip="5" take="10" replace="/},$/}/g"?-->

\`\`\`json "hello": "Hello {userName}", "@hello": { "description": "A message with a single parameter", "placeholders": { "userName": { "type": "String", "example": "Bob" } } } \`\`\` This code snippet adds a \`hello\` method call to the \`AppLocalizations.of(context)\` object, and the method accepts a parameter of type \`String\`; the \`hello\` method returns a string. Regenerate the \`AppLocalizations\` file. Replace the code passed into \`Builder\` with the following:

<!--?code-excerpt "gen_l10n_example/lib/main.dart (placeholder)" remove="/wombat|Wombats|he'|they|pronoun/"?-->

\`\`\`dart // Examples of internationalized strings. return Column( children: \[ // Returns 'Hello John' Text(AppLocalizations.of(context)!.hello('John')), ], ); \`\`\` You can also use numerical placeholders to specify multiple values. Different languages have different ways to pluralize words. The syntax also supports specifying \_how\_ a word should be pluralized. A \_pluralized\_ message must include a \`num\` parameter indicating how to pluralize the word in different situations. English, for example, pluralizes "person" to "people", but that doesn't go far enough. The \`message0\` plural might be "no people" or "zero people". The \`messageFew\` plural might be "several people", "some people", or "a few people". The \`messageMany\` plural might be "most people" or "many people", or "a crowd". Only the more general \`messageOther\` field is required. The following example shows what options are available: \`\`\`json "{countPlaceholder, plural, =0{message0} =1{message1} =2{message2} few{messageFew} many{messageMany} other{messageOther}}" \`\`\` The previous expression is replaced by the message variation (\`message0\`, \`message1\`, ...) corresponding to the value of the \`countPlaceholder\`. Only the \`messageOther\` field is required. The following example defines a message that pluralizes the word, "wombat":

<!--?code-excerpt "gen_l10n_example/lib/l10n/app_en.arb" skip="15" take="10" replace="/},$/}/g"?-->

\`\`\`json "nWombats": "{count, plural, =0{no wombats} =1{1 wombat} other{{count} wombats}}", "@nWombats": { "description": "A plural message", "placeholders": { "count": { "type": "num", "format": "compact" } } } \`\`\` Use a plural method by passing in the \`count\` parameter:

<!--?code-excerpt "gen_l10n_example/lib/main.dart (placeholder)" remove="/John|he|she|they|pronoun/" replace="/\[/[\n    .../g"?-->

\`\`\`dart // Examples of internationalized strings. return Column( children: \[ ... // Returns 'no wombats' Text(AppLocalizations.of(context)!.nWombats(0)), // Returns '1 wombat' Text(AppLocalizations.of(context)!.nWombats(1)), // Returns '5 wombats' Text(AppLocalizations.of(context)!.nWombats(5)), ], ); \`\`\` Similar to plurals, you can also choose a value based on a \`String\` placeholder. This is most often used to support gendered languages. The syntax is as follows: \`\`\`json "{selectPlaceholder, select, case{message} ... other{messageOther}}" \`\`\` The next example defines a message that selects a pronoun based on gender:

<!--?code-excerpt "gen_l10n_example/lib/l10n/app_en.arb" skip="25" take="9" replace="/},$/}/g"?-->

\`\`\`json "pronoun": "{gender, select, male{he} female{she} other{they}}", "@pronoun": { "description": "A gendered message", "placeholders": { "gender": { "type": "String" } } } \`\`\` Use this feature by passing the gender string as a parameter:

<!--?code-excerpt "gen_l10n_example/lib/main.dart (placeholder)" remove="/'He|hello|ombat/" replace="/\[/[\n    .../g"?-->

\`\`\`dart // Examples of internationalized strings. return Column( children: \[ ... // Returns 'he' Text(AppLocalizations.of(context)!.pronoun('male')), // Returns 'she' Text(AppLocalizations.of(context)!.pronoun('female')), // Returns 'they' Text(AppLocalizations.of(context)!.pronoun('other')), ], ); \`\`\` Keep in mind that when using \`select\` statements, comparison between the parameter and the actual value is case-sensitive. That is, \`AppLocalizations.of(context)!.pronoun("Male")\` defaults to the "other" case, and returns "they". ### Escaping syntax Sometimes, you have to use tokens, such as \`{\` and \`}\`, as normal characters. To ignore such tokens from being parsed, enable the \`use-escaping\` flag by adding the following to \`l10n.yaml\`: \`\`\`yaml use-escaping: true \`\`\` The parser ignores any string of characters wrapped with a pair of single quotes. To use a normal single quote character, use a pair of consecutive single quotes. For example, the following text is converted to a Dart \`String\`: \`\`\`json { "helloWorld": "Hello! '{Isn''t}' this a wonderful day?" } \`\`\` The resulting string is as follows: \`\`\`dart "Hello! {Isn't} this a wonderful day?" \`\`\` ### Messages with numbers and currencies Numbers, including those that represent currency values, are displayed very differently in different locales. The localizations generation tool in \`flutter\_localizations\` uses the \[\`NumberFormat\`]\(https\://api.flutter.dev/flutter/package-intl\_intl/NumberFormat-class.html) class in the \`intl\` package to format numbers based on the locale and the desired format. The \`int\`, \`double\`, and \`num\` types can use any of the following \`NumberFormat\` constructors: | Message "format" value | Output for 1200000 | |--------------------------|--------------------| | \`compact\` | "1.2M" | | \`compactCurrency\`\* | "$1.2M" | | \`compactSimpleCurrency\`\* | "$1.2M" | | \`compactLong\` | "1.2 million" | | \`currency\`\* | "USD1,200,000.00" | | \`decimalPattern\` | "1,200,000" | | \`decimalPatternDigits\`\* | "1,200,000" | | \`decimalPercentPattern\`\* | "120,000,000%" | | \`percentPattern\` | "120,000,000%" | | \`scientificPattern\` | "1E6" | | \`simpleCurrency\`\* | "$1,200,000" | {:.table .table-striped} The starred \`NumberFormat\` constructors in the table offer optional, named parameters. Those parameters can be specified as the value of the placeholder's \`optionalParameters\` object. For example, to specify the optional \`decimalDigits\` parameter for \`compactCurrency\`, make the following changes to the \`lib/l10n/app\_en.arb\` file:

<!--?code-excerpt "gen_l10n_example/lib/l10n/app_en.arb" skip="34" take="13" replace="/},$/}/g"?-->

\`\`\`json "numberOfDataPoints": "Number of data points: {value}", "@numberOfDataPoints": { "description": "A message with a formatted int parameter", "placeholders": { "value": { "type": "int", "format": "compactCurrency", "optionalParameters": { "decimalDigits": 2 } } } } \`\`\` ### Messages with dates Dates strings are formatted in many different ways depending both the locale and the app's needs. Placeholder values with type \`DateTime\` are formatted with \[\`DateFormat\`]\[] in the \`intl\` package. There are 41 format variations, identified by the names of their \`DateFormat\` factory constructors. In the following example, the \`DateTime\` value that appears in the \`helloWorldOn\` message is formatted with \`DateFormat.yMd\`: \`\`\`json "helloWorldOn": "Hello World on {date}", "@helloWorldOn": { "description": "A message with a date parameter", "placeholders": { "date": { "type": "DateTime", "format": "yMd" } } } \`\`\` In an app where the locale is US English, the following expression would produce "7/9/1959". In a Russian locale, it would produce "9.07.1959". \`\`\`dart AppLocalizations.of(context).helloWorldOn(DateTime.utc(1959, 7, 9)) \`\`\` \[\`DateFormat\`]: https\://api.flutter.dev/flutter/package-intl\_intl/DateFormat-class.html []()### Localizing for iOS: Updating the iOS app bundle Although the localizations are handled by Flutter, you need to add the supported languages in the Xcode project. This ensures your entry in the App Store correctly displays the supported languages. To configure the locales supported by your app, use the following instructions: 1. Open your project's \`ios/Runner.xcodeproj\` Xcode file. 2. In the \*\*Project Navigator\*\*, select the \`Runner\` project file under \*\*Projects\*\*. 4. Select the \`Info\` tab in the project editor. 5. In the \*\*Localizations\*\* section, click the \`Add\` button (\`+\`) to add the supported languages and regions to your project. When asked to choose files and reference language, simply select \`Finish\`. 7. Xcode automatically creates empty \`.strings\` files and updates the \`ios/Runner.xcodeproj/project.pbxproj\` file. These files are used by the App Store to determine which languages and regions your app supports. []()## Advanced topics for further customization This section covers additional ways to customize a localized Flutter application. []()### Advanced locale definition Some languages with multiple variants require more than just a language code to properly differentiate. For example, fully differentiating all variants of Chinese requires specifying the language code, script code, and country code. This is due to the existence of simplified and traditional script, as well as regional differences in the way characters are written within the same script type. In order to fully express every variant of Chinese for the country codes \`CN\`, \`TW\`, and \`HK\`, the list of supported locales should include:

<!--?code-excerpt "gen_l10n_example/lib/examples.dart (supported-locales)"?-->

\`\`\`dart supportedLocales: \[ Locale.fromSubtags(languageCode: 'zh'), // generic Chinese 'zh' Locale.fromSubtags( languageCode: 'zh', scriptCode: 'Hans', ), // generic simplified Chinese 'zh\_Hans' Locale.fromSubtags( languageCode: 'zh', scriptCode: 'Hant', ), // generic traditional Chinese 'zh\_Hant' Locale.fromSubtags( languageCode: 'zh', scriptCode: 'Hans', countryCode: 'CN', ), // 'zh\_Hans\_CN' Locale.fromSubtags( languageCode: 'zh', scriptCode: 'Hant', countryCode: 'TW', ), // 'zh\_Hant\_TW' Locale.fromSubtags( languageCode: 'zh', scriptCode: 'Hant', countryCode: 'HK', ), // 'zh\_Hant\_HK' ], \`\`\` This explicit full definition ensures that your app can distinguish between and provide the fully nuanced localized content to all combinations of these country codes. If a user's preferred locale isn't specified, Flutter selects the closest match, which likely contains differences to what the user expects. Flutter only resolves to locales defined in \`supportedLocales\` and provides scriptCode-differentiated localized content for commonly used languages. See \[\`Localizations\`]\[] for information on how the supported locales and the preferred locales are resolved. Although Chinese is a primary example, other languages like French (\`fr\_FR\`, \`fr\_CA\`) should also be fully differentiated for more nuanced localization. \[\`Localizations\`]: https\://api.flutter.dev/flutter/widgets/WidgetsApp/supportedLocales.html []()### Tracking the locale: The Locale class and the Localizations widget The \[\`Locale\`]\[] class identifies the user's language. Mobile devices support setting the locale for all applications, usually using a system settings menu. Internationalized apps respond by displaying values that are locale-specific. For example, if the user switches the device's locale from English to French, then a \`Text\` widget that originally displayed "Hello World" would be rebuilt with "Bonjour le monde". The \[\`Localizations\`]\[widgets-global] widget defines the locale for its child and the localized resources that the child depends on. The \[\`WidgetsApp\`]\[] widget creates a \`Localizations\` widget and rebuilds it if the system's locale changes. You can always look up an app's current locale with \`Localizations.localeOf()\`:

<!--?code-excerpt "gen_l10n_example/lib/examples.dart (my-locale)"?-->

\`\`\`dart Locale myLocale = Localizations.localeOf(context); \`\`\` \[\`Locale\`]: https\://api.flutter.dev/flutter/dart-ui/Locale-class.html \[\`WidgetsApp\`]: https\://api.flutter.dev/flutter/widgets/WidgetsApp-class.html \[widgets-global]: https\://api.flutter.dev/flutter/flutter\_localizations/GlobalWidgetsLocalizations-class.html []()### Specifying the app's supported­Locales parameter Although the \`flutter\_localizations\` library supports many languages and language variants, only English language translations are available by default. It's up to the developer to decide exactly which languages to support. The \`MaterialApp\` \[\`supportedLocales\`]\[] parameter limits locale changes. When the user changes the locale setting on their device, the app's \`Localizations\` widget only follows suit if the new locale is a member of this list. If an exact match for the device locale isn't found, then the first supported locale with a matching \[\`languageCode\`]\[] is used. If that fails, then the first element of the \`supportedLocales\` list is used. An app that wants to use a different "locale resolution" method can provide a \[\`localeResolutionCallback\`]\[]. For example, to have your app unconditionally accept whatever locale the user selects:

<!--?code-excerpt "gen_l10n_example/lib/examples.dart (locale-resolution)"?-->

\`\`\`dart MaterialApp( localeResolutionCallback: (locale, supportedLocales) { return locale; }, ); \`\`\` \[\`languageCode\`]: https\://api.flutter.dev/flutter/dart-ui/Locale/languageCode.html \[\`localeResolutionCallback\`]: https\://api.flutter.dev/flutter/widgets/LocaleResolutionCallback.html \[\`supportedLocales\`]: https\://pub.dev/documentation/material\_ui/latest/material\_ui/MaterialApp/supportedLocales.html ### Configuring the l10n.yaml file The \`l10n.yaml\` file allows you to configure the \`gen-l10n\` tool to specify the following: \* where all the input files are located \* where all the output files should be created \* what Dart class name to give your localizations delegate For a full list of options, either run \`flutter gen-l10n --help\` at the command line or refer to the following table: | Option | Description | | ------------------------------------| ------------------ | | \`arb-dir\` | The directory where the template and translated arb files are located. The default is \`lib/l10n\`. | | \`output-dir\` | The directory where the generated localization classes are written. This option is only relevant if you want to generate the localizations code somewhere else in the Flutter project. You also need to set the \`synthetic-package\` flag to false.\
\
The app must import the file specified in the \`output-localization-file\` option from this directory. If unspecified, this defaults to the same directory as the input directory specified in \`arb-dir\`. | | \`template-arb-file\` | The template arb file that is used as the basis for generating the Dart localization and messages files. The default is \`app\_en.arb\`. | | \`output-localization-file\` | The filename for the output localization and localizations delegate classes. The default is \`app\_localizations.dart\`. | | \`untranslated-messages-file\` | The location of a file that describes the localization messages haven't been translated yet. Using this option creates a JSON file at the target location, in the following format:\
\
\`"locale": \["message\_1", "message\_2" ... "message\_n"]\`\
\
If this option is not specified, a summary of the messages that haven't been translated are printed on the command line. | | \`output-class\` | The Dart class name to use for the output localization and localizations delegate classes. The default is \`AppLocalizations\`. | | \`preferred-supported-locales\` | The list of preferred supported locales for the application. By default, the tool generates the supported locales list in alphabetical order. Use this flag to default to a different locale.\
\
For example, pass in \`\[ en\_US ]\` to default to American English if a device supports it. | | \`header\` | The header to prepend to the generated Dart localizations files. This option takes in a string.\
\
For example, pass in \`"/// All localized files."\` to prepend this string to the generated Dart file.\
\
Alternatively, check out the \`header-file\` option to pass in a text file for longer headers. | | \`header-file\` | The header to prepend to the generated Dart localizations files. The value of this option is the name of the file that contains the header text that is inserted at the top of each generated Dart file.\
\
Alternatively, check out the \`header\` option to pass in a string for a simpler header.\
\
This file should be placed in the directory specified in \`arb-dir\`. | | \`\[no-]use-deferred-loading\` | Specifies whether to generate the Dart localization file with locales imported as deferred, allowing for lazy loading of each locale in Flutter web.\
\
This can reduce a web app's initial startup time by decreasing the size of the JavaScript bundle. When this flag is set to true, the messages for a particular locale are only downloaded and loaded by the Flutter app as they are needed. For projects with a lot of different locales and many localization strings, it can improve performance to defer loading. For projects with a small number of locales, the difference is negligible, and might slow down the start up compared to bundling the localizations with the rest of the application.\
\
Note that this flag doesn't affect other platforms such as mobile or desktop. | | \`gen-inputs-and-outputs-list\` | When specified, the tool generates a JSON file containing the tool's inputs and outputs, named \`gen\_l10n\_inputs\_and\_outputs.json\`.\
\
This can be useful for keeping track of which files of the Flutter project were used when generating the latest set of localizations. For example, the Flutter tool's build system uses this file to keep track of when to call gen\_l10n during hot reload.\
\
The value of this option is the directory where the JSON file is generated. When null, the JSON file won't be generated. | | \`synthetic-package\` | Determines whether the generated output files are generated as a synthetic package or at a specified directory in the Flutter project. This flag is \`true\` by default. When \`synthetic-package\` is set to \`false\`, it generates the localizations files in the directory specified by \`arb-dir\` by default. If \`output-dir\` is specified, files are generated there. | | \`project-dir\` | When specified, the tool uses the path passed into this option as the directory of the root Flutter project.\
\
When null, the relative path to the present working directory is used. | | \`\[no-]required-resource-attributes\` | Requires all resource ids to contain a corresponding resource attribute.\
\
By default, simple messages won't require metadata, but it's highly recommended as this provides context for the meaning of a message to readers.\
\
Resource attributes are still required for plural messages. | | \`\[no-]nullable-getter\` | Specifies whether the localizations class getter is nullable.\
\
By default, this value is true so that \`Localizations.of(context)\` returns a nullable value for backwards compatibility. If this value is false, then a null check is performed on the returned value of \`Localizations.of(context)\`, removing the need for null checking in user code. | | \`\[no-]format\` | When specified, the \`dart format\` command is run after generating the localization files. | | \`use-escaping\` | Specifies whether to enable the use of single quotes as escaping syntax. | | \`\[no-]suppress-warnings\` | When specified, all warnings are suppressed. | | \`\[no-]relax-syntax\` | When specified, the syntax is relaxed so that the special character "{" is treated as a string if not followed by a valid placeholder and "}" is treated as a string if it doesn't close any previous "{" that is treated as a special character. | | \`\[no-]use-named-parameters\` | Whether to use named parameters for the generated localization methods. | {:.table .table-striped} ## How internationalization in Flutter works This section covers the technical details of how localizations work in Flutter. If you're planning on supporting your own set of localized messages, the following content would be helpful. Otherwise, you can skip this section. []()### Loading and retrieving localized values The \`Localizations\` widget is used to load and look up objects that contain collections of localized values. Apps refer to these objects with \[\`Localizations.of(context,type)\`]\[]. If the device's locale changes, the \`Localizations\` widget automatically loads values for the new locale and then rebuilds widgets that used it. This happens because \`Localizations\` works like an \[\`InheritedWidget\`]\[]. When a build function refers to an inherited widget, an implicit dependency on the inherited widget is created. When an inherited widget changes (when the \`Localizations\` widget's locale changes), its dependent contexts are rebuilt. Localized values are loaded by the \`Localizations\` widget's list of \[\`LocalizationsDelegate\`]\[]s. Each delegate must define an asynchronous \[\`load()\`]\[] method that produces an object that encapsulates a collection of localized values. Typically these objects define one method per localized value. In a large app, different modules or packages might be bundled with their own localizations. That's why the \`Localizations\` widget manages a table of objects, one per \`LocalizationsDelegate\`. To retrieve the object produced by one of the \`LocalizationsDelegate\`'s \`load\` methods, specify a \`BuildContext\` and the object's type. For example, the localized strings for the Material Components widgets are defined by the \[\`MaterialLocalizations\`]\[] class. Instances of this class are created by a \`LocalizationDelegate\` provided by the \[\`MaterialApp\`]\[] class. They can be retrieved with \`Localizations.of()\`: \`\`\`dart Localizations.of(context, MaterialLocalizations); \`\`\` This particular \`Localizations.of()\` expression is used frequently, so the \`MaterialLocalizations\` class provides a convenient shorthand: \`\`\`dart static MaterialLocalizations of(BuildContext context) { return Localizations.of(context, MaterialLocalizations); } /// References to the localized values defined by MaterialLocalizations /// are typically written like this: tooltip: MaterialLocalizations.of(context).backButtonTooltip, \`\`\` \[\`InheritedWidget\`]: https\://api.flutter.dev/flutter/widgets/InheritedWidget-class.html \[\`load()\`]: https\://api.flutter.dev/flutter/widgets/LocalizationsDelegate/load.html \[\`LocalizationsDelegate\`]: https\://api.flutter.dev/flutter/widgets/LocalizationsDelegate-class.html \[\`Localizations.of(context,type)\`]: https\://api.flutter.dev/flutter/widgets/Localizations/of.html \[\`MaterialApp\`]: https\://pub.dev/documentation/material\_ui/latest/material\_ui/MaterialApp-class.html \[\`MaterialLocalizations\`]: https\://pub.dev/documentation/material\_ui/latest/material\_ui/MaterialLocalizations-class.html []()### Defining a class for the app's localized resources Putting together an internationalized Flutter app usually starts with the class that encapsulates the app's localized values. The example that follows is typical of such classes. Complete source code for the \[\`intl\_example\`]\[] for this app. This example is based on the APIs and tools provided by the \[\`intl\`]\[] package. The \[An alternative class for the app's localized resources]\(#alternative-class) section describes \[an example]\[] that doesn't depend on the \`intl\` package. The \`DemoLocalizations\` class (defined in the following code snippet) contains the app's strings (just one for the example) translated into the locales that the app supports. It uses the \`initializeMessages()\` function generated by Dart's \[\`intl\`]\[] package, \[\`Intl.message()\`]\[], to look them up.

<!--?code-excerpt "intl_example/lib/main.dart (demo-localizations)"?-->

\`\`\`dart class DemoLocalizations { DemoLocalizations(this.localeName); static Future load(Locale locale) { final String name = locale.countryCode == null || locale.countryCode!.isEmpty ? locale.languageCode : locale.toString(); final String localeName = Intl.canonicalizedLocale(name); return initializeMessages(localeName).then((\_) { return DemoLocalizations(localeName); }); } static DemoLocalizations of(BuildContext context) { return Localizations.of(context, DemoLocalizations)!; } final String localeName; String get title { return Intl.message( 'Hello World', name: 'title', desc: 'Title for the Demo application', locale: localeName, ); } } \`\`\` A class based on the \`intl\` package imports a generated message catalog that provides the \`initializeMessages()\` function and the per-locale backing store for \`Intl.message()\`. The message catalog is produced by an \[\`intl\` tool]\(#dart-tools) that analyzes the source code for classes that contain \`Intl.message()\` calls. In this case that would just be the \`DemoLocalizations\` class. \[an example]: https\://github.com/flutter/website/tree/main/examples/internationalization/minimal \[\`intl\`]: https\://pub.dev/packages/intl \[\`Intl.message()\`]: https\://pub.dev/documentation/intl/latest/intl/Intl/message.html []()### Adding support for a new language An app that needs to support a language that's not included in \[\`GlobalMaterialLocalizations\`]\[] has to do some extra work: it must provide about 70 translations ("localizations") for words or phrases and the date patterns and symbols for the locale. See the following for an example of how to add support for the Norwegian Nynorsk language. A new \`GlobalMaterialLocalizations\` subclass defines the localizations that the Material library depends on. A new \`LocalizationsDelegate\` subclass, which serves as factory for the \`GlobalMaterialLocalizations\` subclass, must also be defined. Here's the source code for the complete \[\`add\_language\`]\[] example, minus the actual Nynorsk translations. The locale-specific \`GlobalMaterialLocalizations\` subclass is called \`NnMaterialLocalizations\`, and the \`LocalizationsDelegate\` subclass is \`\_NnMaterialLocalizationsDelegate\`. The value of \`NnMaterialLocalizations.delegate\` is an instance of the delegate, and is all that's needed by an app that uses these localizations. The delegate class includes basic date and number format localizations. All of the other localizations are defined by \`String\` valued property getters in \`NnMaterialLocalizations\`, like this:

<!--?code-excerpt "add_language/lib/nn_intl.dart (getters)"?-->

\`\`\`dart @override String get moreButtonTooltip => r'More'; @override String get aboutListTileTitleRaw => r'About $applicationName'; @override String get alertDialogLabel => r'Alert'; \`\`\` These are the English translations, of course. To complete the job you need to change the return value of each getter to an appropriate Nynorsk string. The getters return "raw" Dart strings that have an \`r\` prefix, such as \`r'About $applicationName'\`, because sometimes the strings contain variables with a \`$\` prefix. The variables are expanded by parameterized localization methods:

<!--?code-excerpt "add_language/lib/nn_intl.dart (raw)"?-->

\`\`\`dart @override String get pageRowsInfoTitleRaw => r'$firstRow–$lastRow of $rowCount'; @override String get pageRowsInfoTitleApproximateRaw => r'$firstRow–$lastRow of about $rowCount'; \`\`\` The date patterns and symbols of the locale also need to be specified, which are defined in the source code as follows:

<!--?code-excerpt "add_language/lib/nn_intl.dart (date-patterns)" replace="/  'LLL': 'LLL',/  'LLL': 'LLL',\n  \/\/ ...\n}/g"?-->

\`\`\`dart const nnLocaleDatePatterns = { 'd': 'd.', 'E': 'ccc', 'EEEE': 'cccc', 'LLL': 'LLL', // ... } \`\`\`

<!--?code-excerpt "add_language/lib/nn_intl.dart (date-symbols)" replace="/  ],/  ],\n  \/\/ ...\n}/g"?-->

\`\`\`dart const Map nnDateSymbols = { 'NAME': 'nn', 'ERAS': \['f.Kr.', 'e.Kr.'], \`\`\` These values need to be modified for the locale to use the correct date formatting. Unfortunately, since the \`intl\` library doesn't share the same flexibility for number formatting, the formatting for an existing locale must be used as a substitute in \`\_NnMaterialLocalizationsDelegate\`:

<!--?code-excerpt "add_language/lib/nn_intl.dart (delegate)"?-->

\`\`\`dart class \_NnMaterialLocalizationsDelegate extends LocalizationsDelegate { const \_NnMaterialLocalizationsDelegate(); @override bool isSupported(Locale locale) => locale.languageCode == 'nn'; @override Future load(Locale locale) { final String localeName = intl.Intl.canonicalizedLocale(locale.toString()); // The locale (in this case \`nn\`) needs to be initialized into the custom // date symbols and patterns setup that Flutter uses. date\_symbol\_data\_custom.initializeDateFormattingCustom( locale: localeName, patterns: nnLocaleDatePatterns, symbols: intl.DateSymbols.deserializeFromMap(nnDateSymbols), ); return SynchronousFuture( NnMaterialLocalizations( localeName: localeName, // The \`intl\` library's NumberFormat class is generated from CLDR data // (see https\://github.com/dart-lang/i18n/blob/main/pkgs/intl/lib/number\_symbols\_data.dart). // Unfortunately, there is no way to use a locale that isn't defined in // this map and the only way to work around this is to use a listed // locale's NumberFormat symbols. So, here we use the number formats // for 'en\_US' instead. decimalFormat: intl.NumberFormat('#,##0.###', 'en\_US'), twoDigitZeroPaddedFormat: intl.NumberFormat('00', 'en\_US'), // DateFormat here will use the symbols and patterns provided in the // \`date\_symbol\_data\_custom.initializeDateFormattingCustom\` call above. // However, an alternative is to simply use a supported locale's // DateFormat symbols, similar to NumberFormat above. fullYearFormat: intl.DateFormat('y', localeName), compactDateFormat: intl.DateFormat('yMd', localeName), shortDateFormat: intl.DateFormat('yMMMd', localeName), mediumDateFormat: intl.DateFormat('EEE, MMM d', localeName), longDateFormat: intl.DateFormat('EEEE, MMMM d, y', localeName), yearMonthFormat: intl.DateFormat('MMMM y', localeName), shortMonthDayFormat: intl.DateFormat('MMM d'), ), ); } @override bool shouldReload(\_NnMaterialLocalizationsDelegate old) => false; } \`\`\` For more information about localization strings, check out the \[flutter\_localizations README]\[]. Once you've implemented your language-specific subclasses of \`GlobalMaterialLocalizations\` and \`LocalizationsDelegate\`, you need to add the language and a delegate instance to your app. The following code sets the app's language to Nynorsk and adds the \`NnMaterialLocalizations\` delegate instance to the app's \`localizationsDelegates\` list:

<!--?code-excerpt "add_language/lib/main.dart (material-app)"?-->

\`\`\`dart const MaterialApp( localizationsDelegates: \[ GlobalWidgetsLocalizations.delegate, GlobalMaterialLocalizations.delegate, NnMaterialLocalizations.delegate, // Add the newly created delegate ], supportedLocales: \[Locale('en', 'US'), Locale('nn')], home: Home(), ), \`\`\` \[\`add\_language\`]: https\://github.com/flutter/website/tree/main/examples/internationalization/add\_language/lib/main.dart \[flutter\_localizations README]: https\://github.com/flutter/flutter/blob/main/packages/flutter\_localizations/lib/src/l10n/README.md \[\`GlobalMaterialLocalizations\`]: https\://api.flutter.dev/flutter/flutter\_localizations/GlobalMaterialLocalizations-class.html []()## Alternative internationalization workflows This section describes different approaches to internationalize your Flutter application. []()### An alternative class for the app's localized resources The previous example was defined in terms of the Dart \`intl\` package. You can choose your own approach for managing localized values for the sake of simplicity or perhaps to integrate with a different i18n framework. Complete source code for the \[\`minimal\`]\[] app. In the following example, the \`DemoLocalizations\` class includes all of its translations directly in per language Maps:

<!--?code-excerpt "minimal/lib/main.dart (demo)"?-->

\`\`\`dart class DemoLocalizations { DemoLocalizations(this.locale); final Locale locale; static DemoLocalizations of(BuildContext context) { return Localizations.of(context, DemoLocalizations)!; } static const \_localizedValues = >{ 'en': {'title': 'Hello World'}, 'es': {'title': 'Hola Mundo'}, }; static List languages() => \_localizedValues.keys.toList(); String get title { return \_localizedValues\[locale.languageCode]!\['title']!; } } \`\`\` In the minimal app the \`DemoLocalizationsDelegate\` is slightly different. Its \`load\` method returns a \[\`SynchronousFuture\`]\[] because no asynchronous loading needs to take place.

<!--?code-excerpt "minimal/lib/main.dart (delegate)"?-->

\`\`\`dart class DemoLocalizationsDelegate extends LocalizationsDelegate { const DemoLocalizationsDelegate(); @override bool isSupported(Locale locale) => DemoLocalizations.languages().contains(locale.languageCode); @override Future load(Locale locale) { // Returning a SynchronousFuture here because an async "load" operation // isn't needed to produce an instance of DemoLocalizations. return SynchronousFuture(DemoLocalizations(locale)); } @override bool shouldReload(DemoLocalizationsDelegate old) => false; } \`\`\` \[\`SynchronousFuture\`]: https\://api.flutter.dev/flutter/foundation/SynchronousFuture-class.html []()### Using the Dart intl tools Before building an API using the Dart \[\`intl\`]\[] package, review the \`intl\` package's documentation. The following list summarizes the process for localizing an app that depends on the \`intl\` package: The demo app depends on a generated source file called \`l10n/messages\_all.dart\`, which defines all of the localizable strings used by the app. Rebuilding \`l10n/messages\_all.dart\` requires two steps. 1. With the app's root directory as the current directory, generate \`l10n/intl\_messages.arb\` from \`lib/main.dart\`: \`\`\`console $ dart run intl\_translation:extract\_to\_arb --output-dir=lib/l10n lib/main.dart \`\`\` The \`intl\_messages.arb\` file is a JSON format map with one entry for each \`Intl.message()\` function defined in \`main.dart\`. This file serves as a template for the English and Spanish translations, \`intl\_en.arb\` and \`intl\_es.arb\`. These translations are created by you, the developer. 2. With the app's root directory as the current directory, generate \`intl\_messages\_.dart\` for each \`intl\_.arb\` file and \`intl\_messages\_all.dart\`, which imports all the message files: \`\`\`console $ dart run intl\_translation:generate\_from\_arb \ --output-dir=lib/l10n --no-use-deferred-loading \ lib/main.dart lib/l10n/intl\_\*.arb \`\`\` \*\*\*Windows doesn't support file name wildcarding.\*\*\* Instead, list the .arb files that were generated by the \`intl\_translation:extract\_to\_arb\` command. \`\`\`console $ dart run intl\_translation:generate\_from\_arb \ --output-dir=lib/l10n --no-use-deferred-loading \ lib/main.dart \ lib/l10n/intl\_en.arb lib/l10n/intl\_fr.arb lib/l10n/intl\_messages.arb \`\`\` The \`DemoLocalizations\` class uses the generated \`initializeMessages()\` function (defined in \`intl\_messages\_all.dart\`) to load the localized messages and \`Intl.message()\` to look them up. ## More information If you learn best by reading code, check out the following examples. \* \[\`minimal\`]\[]\
The \`minimal\` example is designed to be as simple as possible. \* \[\`intl\_example\`]\[]\
uses APIs and tools provided by the \[\`intl\`]\[] package. If Dart's \`intl\` package is new to you, check out \[Using the Dart intl tools]\(#dart-tools). \[\`intl\_example\`]: https\://github.com/flutter/website/tree/main/examples/internationalization/intl\_example \[\`minimal\`]: https\://github.com/flutter/website/tree/main/examples/internationalization/minimal

----
url: https://docs.flutter.dev/ai/best-practices/prompting.md
----

\# Prompting > Learn how to build and optimize generative AI prompts in Flutter using system instructions, dynamic parameters, and versioning techniques. Assuming you've configured your Flutter app with the Firebase project and configuration you need to use the Firebase AI Logic SDK (which you can learn about in \[the README]\[crossword-readme]), you're all set to start using generative AI. Generative AI is the branch of Machine Learning (ML) that uses a neural network trained on a large set of human language to produce a Large Language Model (LLM). At this point, the best models (like Google Gemini) are trained on what is essentially the entire internet. At that scale, a model trained with that much data has created models that can interpret human language and produce useful human language outputs. By now I'm sure you've used \[the Gemini chat app]\[gemini-app] (or ChatGPT or Claude or other chat apps), so you know that if you talk to an LLM using vague language, you're likely to get vague, often incorrect, results. If you want to get good results, you'll have to use good prompts. ### Prompt construction A prompt is the input you provide to an LLM to get the output you want. It will include text as well zero or more files, like images or PDF files. If you're building chat into your app, then the user will be entering the prompts (and \[the Flutter AI Toolkit]\[ai-toolkit] is useful for building the chat UI). If you're using an LLM to implement the features of your app, like parsing an image for crossword puzzle data, then you're going to be building the prompts yourself. How you build them matters. As an example, in building the Crossword Companion, the original clue solving prompt looked like this: \`\`\`dart You are a crossword puzzle solver. Your goal is to solve the puzzle by filling in the grid with the correct answers. Given the current state of the crossword grid and a single clue, provide the answer for that clue. The answer should be a single word, returned in a JSON object that matches the following schema: '{"type": "object", "properties": {"answer": {"type": "string"}}}'. # Puzzle Information ## Grid Layout The grid is (${grid.width}x${grid.height}): ${\_getGridStateAsString(grid)} ## Clue ${clue.number} ${clue.direction == ClueDirection.across ? 'Across' : 'Down'}: ${clue.text} \`\`\` This prompt isn't all bad – it has some useful pieces: - \*\*Persona:\*\* the phrase "You are a crossword puzzle solver" narrows the model's focus - \*\*Context:\*\* the current state of the puzzle - \*\*Query:\*\* asking for a solution to a clue - \*\*Format:\*\* provide the output in JSON so the result could be parsed programmatically However, because of the two-dimensional nature of the data, this is a hard prompt for some models to solve. The results from Gemini 2.5 Flash (the more efficient of the models available at the time) were inconsistent. The quality of the results from Gemini 2.5 Pro were excellent, but they were slower and more expensive to obtain. Debugging revealed that Pro was essentially solving the entire puzzle every time it was called, responding with just the solution to a single clue. What was needed was the efficiency of Flash with the quality of Pro. To do that required some work on the prompt: \`\`\`markdown Your task is to solve the following crossword clue. \*\*Clue:\*\* "${clue.text}" \*\*Constraints:\*\* - The answer is a \*\*$length-letter\*\* word. - The current letter pattern is \`$pattern\`, where \`\_\` represents an unknown letter. Return your answer and confidence score in the required JSON format. \`\`\` This prompt asks to solve the clue, provides the important context, and specifies the output format. Instead of handing in the entire state of the two-dimensional grid, the input was narrowed to the length requirement and a pattern, such as "\_ R \_ Y". These simplifications produce high quality results from Flash that come back quickly enough to make it \[fun to watch]\[crossword-demo]. ### Layering your prompts The prompt used to solve the clues is not the only prompt the model sees. It also has the system instruction (also known as the system message or the system prompt) which is set as part of model instance creation. Think of the system instruction as "this is what you do" while the individual prompts are "now do this." Here is the partial system instruction for the clue solver model (you'll see the rest later): \`\`\`dart final clueSolverSystemInstruction = ''' You are an expert crossword puzzle solver. \*\*Follow these rules at all times:\*\* 1. \*\*Prefer Common Words:\*\* Prioritize common English words and proper nouns. Avoid obscure, archaic, or highly technical terms unless the clue strongly implies them. 2. \*\*Match the Clue:\*\* Ensure your answer strictly matches the clue's tense, plurality (singular vs. plural), and part of speech. 3. \*\*Verify Grammatically:\*\* If a clue implies a specific part of speech (e.g., it's a verb, adverb, or plural), it's a good idea to use the \`getWordMetadata\` tool to verify your candidate answer matches. However, avoid using it for every clue. 4. \*\*Be Confident:\*\* Provide a confidence score from 0.0 to 1.0 indicating your certainty. 5. \*\*Trust the Clue Over the Pattern:\*\* The provided letter pattern is only a suggestion based on other potentially incorrect answers. Your primary goal is to find the best word that fits the \*\*clue text\*\*. If you are confident in an answer that contradicts the provided pattern, you should use that answer. 6. \*\*Format Correctly:\*\* You must return your answer in the specified JSON format. ... '''; \`\`\` Given the model we want to use and the system instruction, we now have everything we need to create an instance: \`\`\`dart // The model for solving clues. \_clueSolverModel = FirebaseAI.googleAI().generativeModel( model: 'gemini-2.5-flash', systemInstruction: Content.text(clueSolverSystemInstruction), ... ); \`\`\` While the system instruction is often static, the individual prompts are usually created dynamically based on data. ### Parameterizing your prompts Each clue solver prompt is created using the text from the clue, the target length of the answer and the pattern so far given previously solved clues, such as "\_R\_Y": \`\`\`dart String getSolverPrompt(Clue clue, int length, String pattern) => ''' Your task is to solve the following crossword clue. \*\*Clue:\*\* "${clue.text}" \*\*Constraints:\*\* - The answer is a \*\*$length-letter\*\* word. - The current letter pattern is \`$pattern\`, where \`\_\` represents an unknown letter. Return your answer and confidence score in the required JSON format. '''; \`\`\` With the prompt in hand, we can pass it along to the model for our clue answer: \`\`\`dart final result = await \_clueSolverModel.generateContent( prompt: getSolverPrompt(clue, length, pattern), ); \`\`\` ### Prompt versioning This basic app keeps the prompt strings in code. This makes them hard to track down and update. For production apps, it's better to keep your prompts separated from the code, perhaps bundled as Flutter assets. One way to arrange prompt files is to use \[the Google dotprompt format]\[dotprompt], which allows you to write \`.prompt\` files that look like this: \`\`\`markdown --- model: googleai/gemini-2.5-flash input: schema: text: string output: format: json schema: title?: string, the title of the article if it has one summary: string, a 3-sentence summary of the text tags?(array, a list of string tag category for the text): string --- Extract the requested information from the given text. If a piece of information is not present, omit that field from the output. Text: \`\`\` To expand a \`.prompt\` file for use in your Dart and Flutter projects, you can use \[the dotprompt\_dart package]\[dotprompt-dart]. \[crossword-readme]: https\://github.com/flutter/demos/tree/main/crossword\_companion \[gemini-app]: https\://gemini.google.com/app \[ai-toolkit]: /ai-toolkit \[crossword-demo]: https\://github.com/flutter/demos/raw/refs/heads/main/crossword\_companion/readme/screen-recording.mov \[dotprompt]: https\://google.github.io/dotprompt/getting-started/ \[dotprompt-dart]: https\://pub.dev/packages/dotprompt\_dart

----
url: https://docs.flutter.dev/cookbook/testing/unit/introduction.md
----

\# An introduction to unit testing > How to write unit tests.

<!--?code-excerpt path-base="cookbook/testing/unit/counter_app"?-->

How can you ensure that your app continues to work as you add more features or change existing functionality? By writing tests. Unit tests are handy for verifying the behavior of a single function, method, or class. The \[\`test\`]\[] package provides the core framework for writing unit tests, and the \[\`flutter\_test\`]\[] package provides additional utilities for testing widgets. This recipe demonstrates the core features provided by the \`test\` package using the following steps: 1. Add the \`test\` or \`flutter\_test\` dependency. 2. Create a test file. 3. Create a class to test. 4. Write a \`test\` for our class. 5. Combine multiple tests in a \`group\`. 6. Run the tests. For more information about the test package, see the \[test package documentation]\[]. ## 1. Add the test dependency 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. To add the \`test\` package as a dev dependency, run \`flutter pub add\`: \`\`\`console $ flutter pub add dev:test \`\`\` ## 2. Create a test file In this example, create two files: \`counter.dart\` and \`counter\_test.dart\`. 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 of your Flutter application or package. Test files should always end with \`\_test.dart\`, this is the convention used by the test runner when searching for tests. When you're finished, the folder structure should look like this: \`\`\`plaintext counter\_app/ lib/ counter.dart test/ counter\_test.dart \`\`\` ## 3. Create a class to test Next, you need a "unit" to test. Remember: "unit" is another name for a 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\`.

<!--?code-excerpt "lib/counter.dart"?-->

\`\`\`dart class Counter { int value = 0; void increment() => value++; void decrement() => value--; } \`\`\` \*\*Note:\*\* For simplicity, this tutorial does not follow the "Test Driven Development" approach. If you're more comfortable with that style of development, you can always go that route. ## 4. Write a test for our class Inside the \`counter\_test.dart\` file, write the first unit test. Tests are defined using the top-level \`test\` function, and you can check if the results are correct by using the top-level \`expect\` function. Both of these functions come from the \`test\` package.

<!--?code-excerpt "test/counter_test.dart"?-->

\`\`\`dart // Import the test package and Counter class import 'package:counter\_app/counter.dart'; import 'package:test/test.dart'; void main() { test('Counter value should be incremented', () { final counter = Counter(); counter.increment(); expect(counter.value, 1); }); } \`\`\` ## 5. Combine multiple tests in a \`group\` If you want to run a series of related tests, use the \`flutter\_test\` package \[\`group\`]\[] function to categorize the tests. Once put into a group, you can call \`flutter test\` on all tests in that group with one command.

<!--?code-excerpt "test/group.dart"?-->

\`\`\`dart import 'package:counter\_app/counter.dart'; import 'package:test/test.dart'; void main() { group('Test start, increment, decrement', () { test('value should start at 0', () { expect(Counter().value, 0); }); test('value should be incremented', () { final counter = Counter(); counter.increment(); expect(counter.value, 1); }); test('value should be decremented', () { final counter = Counter(); counter.decrement(); expect(counter.value, -1); }); }); } \`\`\` ## 6. Run the tests Now that you have a \`Counter\` class with tests in place, you can run the tests. ### Run tests using IntelliJ or VSCode The Flutter plugins for IntelliJ and VSCode support running tests. This is often the best option while writing tests because it provides the fastest feedback loop as well as the ability to set breakpoints. - \*\*IntelliJ\*\* 1. Open the \`counter\_test.dart\` file 1. Go to \*\*Run\*\* > \*\*Run 'tests in counter\_test.dart'\*\*. You can also press the appropriate keyboard shortcut for your platform. - \*\*VSCode\*\* 1. Open the \`counter\_test.dart\` file 1. Go to \*\*Run\*\* > \*\*Start Debugging\*\*. You can also press the appropriate keyboard shortcut for your platform. ### Run tests in a terminal To run the all tests from the terminal, run the following command from the root of the project: \`\`\`console flutter test test/counter\_test.dart \`\`\` To run all tests you put into one \`group\`, run the following command from the root of the project: \`\`\`console flutter test --plain-name "Test start, increment, decrement" \`\`\` This example uses the \`group\` created in \*\*section 5\*\*. To learn more about unit tests, you can execute this command: \`\`\`console flutter test --help \`\`\` \[\`group\`]: https\://api.flutter.dev/flutter/flutter\_test/group.html \[\`flutter\_test\`]: https\://api.flutter.dev/flutter/flutter\_test/flutter\_test-library.html \[\`test\`]: https\://pub.dev/packages/test \[test package documentation]: https\://pub.dev/packages/test

----
url: https://docs.flutter.dev/platform-integration/android/sensitive-content
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Protect your app's sensitive content](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

The [`SensitiveContent`](https://api.flutter.dev/flutter/widgets/SensitiveContent-class.html) widget allows you to prevent screens that contain sensitive content (such as passwords) from being projected. To learn more, check out the following two-minute Widget of the Week video:

[Watch on YouTube in a new tab: "SensitiveContent | Flutter widget of the week"](https://www.youtube.com/watch/WnzHARTIZww)

## About the `SensitiveContent` widget

[#](#about-the-sensitivecontent-widget)

You can use the `SensitiveContent` widget in your app to set the content sensitivity of a child `Widget` to one of the following [`ContentSensitivity`](https://api.flutter.dev/flutter/services/ContentSensitivity.html) values: `notSensitive`, `sensitive`, or `autoSensitive`. Your chosen mode determines if the device screen should be obscured (blacked out) during media projection to protect sensitive data.

You can have as many `SensitiveContent` widgets in your app as you wish, but if *any* one of those widgets has a `sensitive` content value, then the entire screen is obscured during media projection. Thus, for most use cases, using multiple `SensitiveContent` widgets provides no advantage over having one `SensitiveContent` widget in your app’s widget tree.

This feature is available on Android API 35+ and has no effect on lower API versions or other platforms.

## Using the `SensitiveContent` widget

[#](#using-the-sensitivecontent-widget)

Given some content that you want to protect from media screen share (for example, a `MySensitiveContent()` widget), you can wrap it with the `SensitiveContent` widget as shown in the following example:

dart

```
class MyWidget extends StatelessWidget {
  ...
  Widget build(BuildContext context) {
    return SensitiveContent(
      sensitivity: ContentSensitivity.sensitive,
      child: MySensitiveContent(),
    );
  }
}
```

When running on Android API 34 and below, the screen won't be obscured during media projection. The widget will exist in the tree but has no other effect, and you don't need to avoid usages of `SensitiveContent` on platforms that don't support this feature.

## For more information

[#](#for-more-information)

For more information, visit the [`SensitiveContent`](https://api.flutter.dev/flutter/widgets/SensitiveContent-class.html) and [`ContentSensitivity`](https://api.flutter.dev/flutter/services/ContentSensitivity.html) API docs.

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-07-31. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/platform-integration/android/sensitive-content.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/platform-integration/android/sensitive-content\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/platform-integration/android/sensitive-content.md "Report an issue with this page").

----
url: https://docs.flutter.dev/tools/formatting
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Code formatting](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

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 might be better spent on code behavior rather than code style.

## Automatically formatting code in VS Code

[#](#automatically-formatting-code-in-vs-code)

Install the `Flutter` extension (see [VS Code setup](/tools/vs-code#setup)) to get automatic formatting of code in VS Code.

To automatically format the code in the current source code window, right-click in the code window and select `Format Document`. You can add a keyboard shortcut to this VS Code **Preferences**.

To automatically format code whenever you save a file, set the `editor.formatOnSave` setting to `true`.

## Automatically formatting code in Android Studio and IntelliJ

[#](#automatically-formatting-code-in-android-studio-and-intellij)

Install the `Dart` plugin (see [Android Studio and IntelliJ setup](/tools/android-studio#setup)) to get automatic formatting of code in Android Studio and IntelliJ. To format your code in the current source code window:

* On macOS, press `Cmd` + `Option` + `L`.
* On Windows and Linux, press `Ctrl` + `Alt` + `L`.

Android Studio and IntelliJ also provide a checkbox named **Format code on save** on the Flutter page in **Preferences** on macOS or **Settings** on Windows and Linux. This option corrects formatting in the current file when you save it.

## Automatically formatting code with the `dart` command

[#](#automatically-formatting-code-with-the-dart-command)

To correct code formatting in the command line interface (CLI), run the `dart format` command:

```
$ dart format path1 path2 [...]
```

To learn more about the Dart formatter, check out the dart.dev docs on [`dart format`](https://dart.dev/tools/dart-format).

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/formatting.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/tools/formatting\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/formatting.md "Report an issue with this page").

----
url: https://docs.flutter.dev/add-to-app/android/add-flutter-view.md
----

\# Add a Flutter View to an Android app > Learn how to perform advanced integrations via Flutter Views. :::warning Integrating via a \[FlutterView]\(https\://api.flutter.dev/javadoc/io/flutter/embedding/android/FlutterView\.html) is advanced usage and requires manually creating custom, application specific bindings. ::: Integrating via a \[FlutterView]\(https\://api.flutter.dev/javadoc/io/flutter/embedding/android/FlutterView\.html) requires a bit more work than via FlutterActivity and FlutterFragment previously described. Fundamentally, the Flutter framework on the Dart side requires access to various activity-level events and lifecycles to function. Since the FlutterView (which is an \[android.view\.View]\(https\://developer.android.com/reference/android/view/View\.html)) can be added to any activity which is owned by the developer's application and since the FlutterView doesn't have access to activity level events, the developer must bridge those connections manually to the \[FlutterEngine]\(https\://api.flutter.dev/javadoc/io/flutter/embedding/engine/FlutterEngine.html). How you choose to feed your application's activities' events to the FlutterView will be specific to your application. ## A sample Unlike the guides for FlutterActivity and FlutterFragment, the FlutterView integration could be better demonstrated with a sample project. A sample project is at \[https\://github.com/flutter/samples/tree/main/add\_to\_app/android\_view]\(https\://github.com/flutter/samples/tree/main/add\_to\_app/android\_view) to document a simple FlutterView integration where FlutterViews are used for some of the cells in a RecycleView list of cards as seen in the gif above. ## General approach The general gist of the FlutterView-level integration is that you must recreate the various interactions between your Activity, the \[\`FlutterView\`]\(https\://api.flutter.dev/javadoc/io/flutter/embedding/android/FlutterView\.html) and the \[\`FlutterEngine\`]\(https\://api.flutter.dev/javadoc/io/flutter/embedding/engine/FlutterEngine.html) present in the \[\`FlutterActivityAndFragmentDelegate\`]\(https\://cs.opensource.google/flutter/engine/+/main:shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java) in your own application's code. The connections made in the \[\`FlutterActivityAndFragmentDelegate\`]\(https\://cs.opensource.google/flutter/engine/+/main:shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java) are done automatically when using a \[\`FlutterActivity\`]\(https\://api.flutter.dev/javadoc/io/flutter/embedding/android/FlutterActivity.html) or a \[\`FlutterFragment\`]\(https\://api.flutter.dev/javadoc/io/flutter/embedding/android/FlutterFragment.html), but since the \[\`FlutterView\`]\(https\://api.flutter.dev/javadoc/io/flutter/embedding/android/FlutterView\.html) in this case is being added to an \`Activity\` or \`Fragment\` in your application, you must recreate the connections manually. Otherwise, the \[\`FlutterView\`]\(https\://api.flutter.dev/javadoc/io/flutter/embedding/android/FlutterView\.html) won't render anything or have other missing functionalities. A sample \[\`FlutterViewEngine\`]\(https\://github.com/flutter/samples/blob/main/add\_to\_app/android\_view/android\_view/app/src/main/java/dev/flutter/example/androidView/FlutterViewEngine.kt) class shows one such possible implementation of an application-specific connection between an \`Activity\`, a \[\`FlutterView\`]\(https\://api.flutter.dev/javadoc/io/flutter/embedding/android/FlutterView\.html) and a \[FlutterEngine]\(https\://api.flutter.dev/javadoc/io/flutter/embedding/engine/FlutterEngine.html). ### APIs to implement The absolute minimum implementation needed for Flutter to draw anything at all is to: \* Call \[\`attachToFlutterEngine\`]\(https\://api.flutter.dev/javadoc/io/flutter/embedding/android/FlutterView\.html#attachToFlutterEngine-io.flutter.embedding.engine.FlutterEngine-) when the \[\`FlutterView\`]\(https\://api.flutter.dev/javadoc/io/flutter/embedding/android/FlutterView\.html) is added to a resumed \`Activity\`'s view hierarchy and is visible; and \* Call \[\`appIsResumed\`]\(https\://api.flutter.dev/javadoc/io/flutter/embedding/engine/systemchannels/LifecycleChannel.html#appIsResumed--) on the \[\`FlutterEngine\`]\(https\://api.flutter.dev/javadoc/io/flutter/embedding/engine/FlutterEngine.html)'s \`lifecycleChannel\` field when the \`Activity\` hosting the \[\`FlutterView\`]\(https\://api.flutter.dev/javadoc/io/flutter/embedding/android/FlutterView\.html) is visible. The reverse \[\`detachFromFlutterEngine\`]\(https\://api.flutter.dev/javadoc/io/flutter/embedding/android/FlutterView\.html#detachFromFlutterEngine--) and other lifecycle methods on the \[\`LifecycleChannel\`]\(https\://api.flutter.dev/javadoc/io/flutter/embedding/engine/systemchannels/LifecycleChannel.html) class must also be called to not leak resources when the \`FlutterView\` or \`Activity\` is no longer visible. In addition, see the remaining implementation in the \[\`FlutterViewEngine\`]\(https\://github.com/flutter/samples/blob/main/add\_to\_app/android\_view/android\_view/app/src/main/java/dev/flutter/example/androidView/FlutterViewEngine.kt) demo class or in the \[\`FlutterActivityAndFragmentDelegate\`]\(https\://cs.opensource.google/flutter/engine/+/main:shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java) to ensure a correct functioning of other features such as clipboards, system UI overlay, plugins, and so on. ## Content-sized views Usually, a \[\`FlutterView\`]\(https\://api.flutter.dev/javadoc/io/flutter/embedding/android/FlutterView\.html) needs fixed dimensions either through its own dimensions or by matching a parent's dimensions. This can be seen in the \[sample project]\(https\://github.com/flutter/samples/tree/main/add\_to\_app/android\_view/android\_view). However, it's now possible to allow \`FlutterView\` to size itself based on its content. By using, \`content\_wrap\` for either the height or the width a \`FlutterView\` can size itself, as shown in the \[content sized sample project]\(https\://github.com/flutter/samples/tree/main/add\_to\_app/android\_view/content\_sizing\_android\_view). \* To \_enable\_ Content-sized view when deploying your app, add the following setting to your project's \`AndroidManifest.xml\` file under the \`\` tag: \`\`\`xml \`\`\` ### Restrictions Since content-sized Flutter views require your Flutter app to be able to size itself, some widgets are not supported. \* A widget with unbounded size, like a \`ListView\`. \* A widget that defers to its child for the size, like \`LayoutBuilder\`. In practice, this means that quite a few common widgets are not supported, such as \`ScaffoldBuilder\`, \`CupertinoTimerPicker\`, or any widget that internally relies on a \`LayoutBuilder\`. When in doubt, you can use an \`UnconstrainedBox\` to test the usability of a widget for a content-sized view, as shown in the following example: \`\`\`dart import 'package:material\_ui/material\_ui.dart'; void main() => runApp(MyApp()); class MyApp extends StatelessWidget { @override Widget build(BuildContext context) => MaterialApp(home: MyPage()); } class MyPage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( body: UnconstrainedBox( // TODO: Edit this line to check if a widget // can cause problems with content-sized views. child: Text('This works!'), // child: Column(children: \[Column(children: \[Expanded(child: Text('This blows up!'))])]), // child: ListView(children: \[Text('This blows up!')]), ) ); } } \`\`\`

----
url: https://docs.flutter.dev/perf/rendering-performance
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Improving rendering performance](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

Rendering animations in your app is one of the most cited topics of interest when it comes to measuring performance. Thanks in part to Flutter's Skia engine and its ability to quickly create and dispose of widgets, Flutter applications are performant by default, so you only need to avoid common pitfalls to achieve excellent performance.

## General advice

[#](#general-advice)

If you see janky (non-smooth) animations, make **sure** that you are profiling performance with an app built in *profile* mode. The default Flutter build creates an app in *debug* mode, which is not indicative of release performance. For information, see [Flutter's build modes](/testing/build-modes).

A couple common pitfalls:

* Rebuilding far more of the UI than expected each frame. To track widget rebuilds, see [Show performance data](/tools/android-studio#show-performance-data).
* Building a large list of children directly, rather than using a ListView.

For more information on evaluating performance including information on common pitfalls, see the following docs:

* [Performance best practices](/perf/best-practices)
* [Flutter performance profiling](/perf/ui-performance)

## Mobile-only advice

[#](#mobile-only-advice)

Do you see noticeable jank on your mobile app, but only on the first run of an animation? To avoid this, make sure you're using Flutter's default graphic renderer, [Impeller](/perf/impeller).

## Web-only advice

[#](#web-only-advice)

The following series of articles cover what the Flutter Material team learned when improving performance of the Flutter Gallery app on the web:

* [Optimizing performance in Flutter web apps with tree shaking and deferred loading](https://blog.flutter.dev/optimizing-performance-in-flutter-web-apps-with-tree-shaking-and-deferred-loading-535fbe3cd674)
* [Improving perceived performance with image placeholders, precaching, and disabled navigation transitions](https://blog.flutter.dev/improving-perceived-performance-with-image-placeholders-precaching-and-disabled-navigation-6b3601087a2b)
* [Building performant Flutter widgets](https://blog.flutter.dev/building-performant-flutter-widgets-3b2558aa08fa)

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/perf/rendering-performance.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/perf/rendering-performance\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/perf/rendering-performance.md "Report an issue with this page").

----
url: https://docs.flutter.dev/ui/animations/staggered-animations
----

Staggered animations are a straightforward concept: visual changes happen as a series of operations, rather than all at once. The animation might be purely sequential, with one change occurring after the next, or it might partially or completely overlap. It might also have gaps, where no changes occur.

This guide shows how to build a staggered animation in Flutter that applies a series of sequential and overlapping animations to a single widget. Tapping the screen begins an animation that changes opacity, size, shape, color, and padding.

The following video demonstrates this staggered animation:

[Watch on YouTube in a new tab: "Staggered animation example"](https://www.youtube.com/watch/0fFvnZemmh8)

In the video, you see the following animation of a single widget, which begins as a bordered blue square with slightly rounded corners. The square runs through changes in the following order:

1. Fades in
2. Widens
3. Becomes taller while moving upwards
4. Transforms into a bordered circle
5. Changes color to orange

After running forward, the animation runs in reverse.

## Basic structure of a staggered animation

[#](#basic-structure-of-a-staggered-animation)

The following diagram shows the `Interval` objects used in the staggered animation example. You might notice the following characteristics:

* The opacity changes during the first 10% of the timeline.
* A tiny gap occurs between the change in opacity and the change in width.
* Nothing animates during the last 25% of the timeline.
* Increasing the padding makes the widget appear to rise upward.
* Increasing the border radius to `75.0` transforms the `150.0` x `150.0` square with rounded corners into a circle.
* The padding and height changes occur during the exact same interval, but they don't have to.

To set up the animation:

* Create an `AnimationController` that manages all of the `Animation` objects.

* Create a `Tween` for each property being animated.

  * The `Tween` defines a range of values.
  * The `animate()` method of `Tween` takes a parent animation and produces an `Animation` for that property.

* Specify the interval with the `curve` property of `CurvedAnimation`.

When the controlling animation's value changes, the new animation's value changes, triggering the UI to update.

The following code creates a tween for the `width` property. It builds a [`CurvedAnimation`](https://api.flutter.dev/flutter/animation/CurvedAnimation-class.html), specifying an eased curve. For other available predefined animation curves, consult [`Curves`](https://api.flutter.dev/flutter/animation/Curves-class.html).

dart

```
width = Tween<double>(begin: 50, end: 150).animate(
  CurvedAnimation(
    parent: controller,
    curve: const Interval(0.125, 0.250, curve: Curves.ease),
  ),
),
```

The `begin` and `end` values don't have to be doubles. The following code builds the tween for the `borderRadius` property (which controls the roundness of the square's corners), using `BorderRadius.circular()`.

dart

```
borderRadius =
    BorderRadiusTween(
      begin: BorderRadius.circular(4),
      end: BorderRadius.circular(75),
    ).animate(
      CurvedAnimation(
        parent: controller,
        curve: const Interval(0.375, 0.500, curve: Curves.ease),
      ),
    ),
```

### Complete staggered animation

[#](#complete-staggered-animation)

Like all interactive widgets, the complete animation consists of a widget pair: a stateless and a stateful widget.

The stateless widget specifies the `Tween` objects, defines the `Animation` objects, and provides a `build()` function responsible for building the animating portion of the widget tree.

The stateful widget creates the controller, plays the animation, and builds the non-animating portion of the widget tree. The animation begins when a tap is detected anywhere on the screen.

**App source:** [staggered animation example](https://github.com/flutter/website/tree/main/examples/animation/staggered_animation)

### Stateless widget: StaggerAnimation

[#](#stateless-widget-staggeranimation)

In the stateless widget, `StaggerAnimation`, the `build()` function instantiates an [`AnimatedBuilder`](https://api.flutter.dev/flutter/widgets/AnimatedBuilder-class.html)—a general-purpose widget for building animations. The `AnimatedBuilder` builds a widget and configures it using the current values of the `Animation` objects. The example creates a function named `_buildAnimation()` (which performs the actual UI updates), and assigns it to its `builder` property. `AnimatedBuilder` listens to notifications from the animation controller, marking the widget tree dirty as values change. For each tick of the animation, the values are updated, resulting in a call to `_buildAnimation()`.

dart

```
import 'dart:async';
​
import 'package:flutter/scheduler.dart' show timeDilation;
import 'package:material_ui/material_ui.dart';
​
class StaggerAnimation extends StatelessWidget {
  StaggerAnimation({super.key, required this.controller})
    : // Each animation defined here transforms its value during the subset
      // of the controller's duration defined by the animation's interval.
      // For example, the opacity animation transforms its value during
      // the first 10% of the controller's duration.
      opacity = Tween<double>(begin: 0, end: 1).animate(
        CurvedAnimation(
          parent: controller,
          curve: const Interval(0, 0.100, curve: Curves.ease),
        ),
      ),
​
      width = Tween<double>(begin: 50, end: 150).animate(
        CurvedAnimation(
          parent: controller,
          curve: const Interval(0.125, 0.250, curve: Curves.ease),
        ),
      ),
​
      height = Tween<double>(begin: 50, end: 150).animate(
        CurvedAnimation(
          parent: controller,
          curve: const Interval(0.250, 0.375, curve: Curves.ease),
        ),
      ),
​
      padding =
          EdgeInsetsTween(
            begin: const EdgeInsets.only(bottom: 16),
            end: const EdgeInsets.only(bottom: 75),
          ).animate(
            CurvedAnimation(
              parent: controller,
              curve: const Interval(0.250, 0.375, curve: Curves.ease),
            ),
          ),
​
      borderRadius =
          BorderRadiusTween(
            begin: BorderRadius.circular(4),
            end: BorderRadius.circular(75),
          ).animate(
            CurvedAnimation(
              parent: controller,
              curve: const Interval(0.375, 0.500, curve: Curves.ease),
            ),
          ),
​
      color = ColorTween(begin: Colors.indigo[100], end: Colors.orange[400])
          .animate(
            CurvedAnimation(
              parent: controller,
              curve: const Interval(0.500, 0.750, curve: Curves.ease),
            ),
          );
​
  final Animation<double> controller;
  final Animation<double> opacity;
  final Animation<double> width;
  final Animation<double> height;
  final Animation<EdgeInsets> padding;
  final Animation<BorderRadius?> borderRadius;
  final Animation<Color?> color;
​
  // This function is called each time the controller "ticks" a new frame.
  // When it runs, all of the animation's values will have been
  // updated to reflect the controller's current value.
  Widget _buildAnimation(BuildContext context, Widget? child) {
    return Container(
      padding: padding.value,
      alignment: Alignment.bottomCenter,
      child: Opacity(
        opacity: opacity.value,
        child: Container(
          width: width.value,
          height: height.value,
          decoration: BoxDecoration(
            color: color.value,
            border: Border.all(color: Colors.indigo[300]!, width: 3),
            borderRadius: borderRadius.value,
          ),
        ),
      ),
    );
  }
​
  @override
  Widget build(BuildContext context) {
    return AnimatedBuilder(builder: _buildAnimation, animation: controller);
  }
}
```

### Stateful widget: StaggerDemo

[#](#stateful-widget-staggerdemo)

The stateful widget, `StaggerDemo`, creates the `AnimationController`, specifying a 2000 ms duration. It plays the animation, and builds the non-animating portion of the widget tree. The animation begins when a tap is detected on the screen. The animation runs forward, then backward. Taps during playback are ignored so the sequence can finish.

dart

```
class StaggerDemo extends StatefulWidget {
  const StaggerDemo({super.key});
​
  @override
  State<StaggerDemo> createState() => _StaggerDemoState();
}
​
class _StaggerDemoState extends State<StaggerDemo>
    with SingleTickerProviderStateMixin {
  late final AnimationController _controller;
​
  @override
  void initState() {
    super.initState();
​
    _controller = AnimationController(
      duration: const Duration(milliseconds: 2000),
      vsync: this,
    );
  }
​
  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }
​
  Future<void> _playAnimation() async {
    if (_controller.isAnimating) return;
​
    try {
      await _controller.forward().orCancel;
      await _controller.reverse().orCancel;
    } on TickerCanceled {
      // The animation got canceled, probably because it was disposed of.
    }
  }
​
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Staggered Animation')),
      body: GestureDetector(
        behavior: HitTestBehavior.opaque,
        onTap: _playAnimation,
        child: Center(
          child: Container(
            width: 300,
            height: 300,
            decoration: BoxDecoration(
              color: Colors.black.withValues(alpha: 0.1),
              border: Border.all(color: Colors.black.withValues(alpha: 0.5)),
            ),
            child: StaggerAnimation(controller: _controller.view),
          ),
        ),
      ),
    );
  }
}
​
void main() {
  timeDilation = 10; // 1 is normal animation speed.
  runApp(const MaterialApp(home: StaggerDemo()));
}
```

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-10-02. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/ui/animations/staggered-animations.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/ui/animations/staggered-animations\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/ui/animations/staggered-animations.md "Report an issue with this page").

----
url: https://docs.flutter.dev/platform-integration/platform-channels.md
----

\# Writing custom platform-specific code > Learn how to write custom platform-specific code in your app.

<!--?code-excerpt path-base="platform_integration"?-->

This guide describes how to use custom platform-specific code with Flutter. ## Overview You can use platform-specific code in your Flutter app. A few common ways to do this include: \* Use Flutter's platform channel APIs to pass messages between Flutter and your desired platforms. For more information, see \[Call platform-specific code using platform channels]\(#example). \* Use the \`Pigeon\` package to generate type-safe platform-specific code. For more information, see \[Call platform-specific code using the Pigeon package]\(#pigeon). Flutter supports the following platforms and platform-specific languages: \* \*\*Android\*\*: Kotlin, Java \* \*\*iOS\*\*: Swift, Objective-C \* \*\*Windows\*\*: C++ \* \*\*macOS\*\*: Objective-C \* \*\*Linux\*\*: C :::note \* The information in this page is valid for most platforms, but platform-specific code for the web generally uses \[JS interoperability]\[] instead. \* This guide addresses using the platform channel mechanism if you need to use the platform's APIs in a non-Dart language. However, you can also write platform-specific Dart code in your Flutter app by inspecting the \[\`defaultTargetPlatform\`]\[] property. \[Platform adaptations]\[] lists some platform-specific adaptations that Flutter automatically performs for you in the framework. ::: \[\`defaultTargetPlatform\`]: https\://api.flutter.dev/flutter/foundation/defaultTargetPlatform.html ## Architectural overview of platform channels {:#architecture} Messages are passed between the client (UI) and host (platform) using platform channels as illustrated in this diagram: !\[Platform channels architecture]\(/assets/images/docs/PlatformChannels.png){:width="100%"} In the preceding diagram, messages and responses are passed asynchronously through channels to ensure the user interface remains responsive. On the client side, \[\`MethodChannel\` for Flutter]\[] enables sending messages that correspond to method calls. On the platform side, \[\`MethodChannel\` for Android]\[] and \[\`FlutterMethodChannel\` for iOS]\[] enable receiving method calls and sending back a result. These classes allow you to develop a platform plugin with very little \_boilerplate\_ code. :::note \* Even though Flutter sends messages to and from Dart asynchronously, whenever you invoke a channel method, you must invoke that method on the platform's main thread. See the \[section on threading]\[] for more information. \* If desired, method calls can also be sent in the reverse direction, with the platform acting as client to methods implemented in Dart. For a concrete example, check out the \[\`quick\_actions\`]\[] plugin. ::: ## Data types support {:#codec} The standard platform channel APIs and the Pigeon package use a standard message codec called \[\`StandardMessageCodec\`]\[] that supports efficient binary serialization of simple JSON-like values, such as booleans, numbers, Strings, byte buffers, Lists, and Maps. The serialization and deserialization of these values to and from messages happens automatically when you send and receive values. The following table shows how Dart values are received on the platform side and vice versa: | Dart | Kotlin | | ----------------- | ------------- | | \`null\` | \`null\` | | \`bool\` | \`Boolean\` | | \`int\` (<=32 bits) | \`Int\` | | \`int\` (>32 bits) | \`Long\` | | \`double\` | \`Double\` | | \`String\` | \`String\` | | \`Uint8List\` | \`ByteArray\` | | \`Int32List\` | \`IntArray\` | | \`Int64List\` | \`LongArray\` | | \`Float32List\` | \`FloatArray\` | | \`Float64List\` | \`DoubleArray\` | | \`List\` | \`List\` | | \`Map\` | \`HashMap\` | {:.table .table-striped} | Dart | Java | | ----------------- | --------------------- | | \`null\` | \`null\` | | \`bool\` | \`java.lang.Boolean\` | | \`int\` (<=32 bits) | \`java.lang.Integer\` | | \`int\` (>32 bits) | \`java.lang.Long\` | | \`double\` | \`java.lang.Double\` | | \`String\` | \`java.lang.String\` | | \`Uint8List\` | \`byte\[]\` | | \`Int32List\` | \`int\[]\` | | \`Int64List\` | \`long\[]\` | | \`Float32List\` | \`float\[]\` | | \`Float64List\` | \`double\[]\` | | \`List\` | \`java.util.ArrayList\` | | \`Map\` | \`java.util.HashMap\` | {:.table .table-striped} | Dart | Swift | | ----------------- | ----------------------------------------- | | \`null\` | \`nil\` (\`NSNull\` when nested) | | \`bool\` | \`NSNumber(value: Bool)\` | | \`int\` (<=32 bits) | \`NSNumber(value: Int32)\` | | \`int\` (>32 bits) | \`NSNumber(value: Int)\` | | \`double\` | \`NSNumber(value: Double)\` | | \`String\` | \`String\` | | \`Uint8List\` | \`FlutterStandardTypedData(bytes: Data)\` | | \`Int32List\` | \`FlutterStandardTypedData(int32: Data)\` | | \`Int64List\` | \`FlutterStandardTypedData(int64: Data)\` | | \`Float32List\` | \`FlutterStandardTypedData(float32: Data)\` | | \`Float64List\` | \`FlutterStandardTypedData(float64: Data)\` | | \`List\` | \`Array\` | | \`Map\` | \`Dictionary\` | {:.table .table-striped} | Dart | Objective-C | | ----------------- | ------------------------------------------------ | | \`null\` | \`nil\` (\`NSNull\` when nested) | | \`bool\` | \`NSNumber numberWithBool:\` | | \`int\` (<=32 bits) | \`NSNumber numberWithInt:\` | | \`int\` (>32 bits) | \`NSNumber numberWithLong:\` | | \`double\` | \`NSNumber numberWithDouble:\` | | \`String\` | \`NSString\` | | \`Uint8List\` | \`FlutterStandardTypedData typedDataWithBytes:\` | | \`Int32List\` | \`FlutterStandardTypedData typedDataWithInt32:\` | | \`Int64List\` | \`FlutterStandardTypedData typedDataWithInt64:\` | | \`Float32List\` | \`FlutterStandardTypedData typedDataWithFloat32:\` | | \`Float64List\` | \`FlutterStandardTypedData typedDataWithFloat64:\` | | \`List\` | \`NSArray\` | | \`Map\` | \`NSDictionary\` | {:.table .table-striped} | Dart | C++ | | ------------------ | ---------------------------------------------------------- | | \`null\` | \`EncodableValue()\` | | \`bool\` | \`EncodableValue(bool)\` | | \`int\` (<=32 bits) | \`EncodableValue(int32\_t)\` | | \`int\` (>32 bits) | \`EncodableValue(int64\_t)\` | | \`double\` | \`EncodableValue(double)\` | | \`String\` | \`EncodableValue(std::string)\` | | \`Uint8List\` | \`EncodableValue(std::vector)\` | | \`Int32List\` | \`EncodableValue(std::vector)\` | | \`Int64List\` | \`EncodableValue(std::vector)\` | | \`Float32List\` | \`EncodableValue(std::vector)\` | | \`Float64List\` | \`EncodableValue(std::vector)\` | | \`List\` | \`EncodableValue(std::vector)\` | | \`Map\` | \`EncodableValue(std::map)\` | {:.table .table-striped} | Dart | C (GObject) | | ------------------ | --------------------------- | | \`null\` | \`FlValue()\` | | \`bool\` | \`FlValue(bool)\` | | \`int\` | \`FlValue(int64\_t)\` | | \`double\` | \`FlValue(double)\` | | \`String\` | \`FlValue(gchar\*)\` | | \`Uint8List\` | \`FlValue(uint8\_t\*)\` | | \`Int32List\` | \`FlValue(int32\_t\*)\` | | \`Int64List\` | \`FlValue(int64\_t\*)\` | | \`Float32List\` | \`FlValue(float\*)\` | | \`Float64List\` | \`FlValue(double\*)\` | | \`List\` | \`FlValue(FlValue)\` | | \`Map\` | \`FlValue(FlValue, FlValue)\` | {:.table .table-striped} \[MessageCodec]: https\://api.flutter.dev/flutter/services/MessageCodec-class.html ## Call platform-specific code using platform channels {:#example} The following code demonstrates how to call a platform-specific API to retrieve and display the current battery level. It uses the Android \`BatteryManager\` API, the iOS \`device.batteryLevel\` API, the Windows \`GetSystemPowerStatus\` API, and the Linux \`UPower\` API with a single platform message, \`getBatteryLevel()\`. The example adds the platform-specific code inside the main app itself. If you want to reuse the platform-specific code for multiple apps, the project creation step is slightly different (see \[developing packages]\[plugins]), but the platform channel code is still written in the same way. :::note The full, runnable source-code for this example is available in \[\`/examples/platform\_channel/\`]\[] for Android with Java, iOS with Objective-C, Windows with C++, and Linux with C. For iOS with Swift, see \[\`/examples/platform\_channel\_swift/\`]\[]. ::: ### Step 1: Create a new app project {:#example-project} Start by creating a new app: \* In a terminal run: \`flutter create batterylevel\` By default, our template supports writing Android code using Kotlin, or iOS code using Swift. To use Java or Objective-C, use the \`-i\` and/or \`-a\` flags: \* In a terminal run: \`flutter create -i objc -a java batterylevel\` ### Step 2: Create the Flutter platform client {:#example-client} The app's \`State\` class holds the current app state. Extend that to hold the current battery state. First, construct the channel. Use a \`MethodChannel\` with a single platform method that returns the battery level. The client and host sides of a channel are connected through a channel name passed in the channel constructor. All channel names used in a single app must be unique; prefix the channel name with a unique 'domain prefix', for example: \`samples.flutter.dev/battery\`.

<!--?code-excerpt "platform_channels/lib/platform_channels.dart (import)"?-->

\`\`\`dart import 'dart:async'; import 'package:flutter/services.dart'; import 'package:material\_ui/material\_ui.dart'; \`\`\`

<!--?code-excerpt "platform_channels/lib/platform_channels.dart (my-home-page-state)"?-->

\`\`\`dart class \_MyHomePageState extends State { static const platform = MethodChannel('samples.flutter.dev/battery'); // Get battery level. \`\`\` Next, invoke a method on the method channel, specifying the concrete method to call using the \`String\` identifier \`getBatteryLevel\`. The call might fail—for example, if the platform doesn't support the platform API (such as when running in a simulator), so wrap the \`invokeMethod\` call in a try-catch statement. Use the returned result to update the user interface state in \`\_batteryLevel\` inside \`setState\`.

<!--?code-excerpt "platform_channels/lib/platform_channels.dart (get-battery)"?-->

\`\`\`dart // Get battery level. String \_batteryLevel = 'Unknown battery level.'; Future \_getBatteryLevel() async { String batteryLevel; try { final result = await platform.invokeMethod('getBatteryLevel'); batteryLevel = 'Battery level at $result % .'; } on PlatformException catch (e) { batteryLevel = "Failed to get battery level: '${e.message}'."; } setState(() { \_batteryLevel = batteryLevel; }); } \`\`\` Finally, replace the \`build\` method from the template to contain a small user interface that displays the battery state in a string, and a button for refreshing the value.

<!--?code-excerpt "platform_channels/lib/platform_channels.dart (build)"?-->

\`\`\`dart @override Widget build(BuildContext context) { return Material( child: Center( child: Column( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: \[ ElevatedButton( onPressed: \_getBatteryLevel, child: const Text('Get Battery Level'), ), Text(\_batteryLevel), ], ), ), ); } \`\`\` ### Step 3: Add an Android platform-specific implementation Start by opening the Android host portion of your Flutter app in Android Studio: 1. Start Android Studio 1. Select the menu item \*\*File > Open...\*\* 1. Navigate to the directory holding your Flutter app, and select the \*\*android\*\* folder inside it. Click \*\*OK\*\*. 1. Open the file \`MainActivity.kt\` located in the \*\*kotlin\*\* folder in the Project view. Inside the \`configureFlutterEngine()\` method, create a \`MethodChannel\` and call \`setMethodCallHandler()\`. Make sure to use the same channel name as was used on the Flutter client side. \`\`\`kotlin title="MainActivity.kt" import androidx.annotation.NonNull import io.flutter.embedding.android.FlutterActivity import io.flutter.embedding.engine.FlutterEngine import io.flutter.plugin.common.MethodChannel class MainActivity: FlutterActivity() { private val CHANNEL = "samples.flutter.dev/battery" override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) { super.configureFlutterEngine(flutterEngine) MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler { call, result -> // This method is invoked on the main thread. // TODO } } } \`\`\` Add the Android Kotlin code that uses the Android battery APIs to retrieve the battery level. This code is exactly the same as you would write in a native Android app. First, add the needed imports at the top of the file: \`\`\`kotlin title="MainActivity.kt" import android.content.Context import android.content.ContextWrapper import android.content.Intent import android.content.IntentFilter import android.os.BatteryManager import android.os.Build.VERSION import android.os.Build.VERSION\_CODES \`\`\` Next, add the following method in the \`MainActivity\` class, below the \`configureFlutterEngine()\` method: \`\`\`kotlin title="MainActivity.kt" private fun getBatteryLevel(): Int { val batteryLevel: Int if (VERSION.SDK\_INT >= VERSION\_CODES.LOLLIPOP) { val batteryManager = getSystemService(Context.BATTERY\_SERVICE) as BatteryManager batteryLevel = batteryManager.getIntProperty(BatteryManager.BATTERY\_PROPERTY\_CAPACITY) } else { val intent = ContextWrapper(applicationContext).registerReceiver(null, IntentFilter(Intent.ACTION\_BATTERY\_CHANGED)) batteryLevel = intent!!.getIntExtra(BatteryManager.EXTRA\_LEVEL, -1) \* 100 / intent.getIntExtra(BatteryManager.EXTRA\_SCALE, -1) } return batteryLevel } \`\`\` Finally, complete the \`setMethodCallHandler()\` method added earlier. You need to handle a single platform method, \`getBatteryLevel()\`, so test for that in the \`call\` argument. The implementation of this platform method calls the Android code written in the previous step, and returns a response for both the success and error cases using the \`result\` argument. If an unknown method is called, report that instead. Remove the following code: \`\`\`kotlin title="MainActivity.kt" MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler { call, result -> // This method is invoked on the main thread. // TODO } \`\`\` And replace with the following: \`\`\`kotlin title="MainActivity.kt" MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler { // This method is invoked on the main thread. call, result -> if (call.method == "getBatteryLevel") { val batteryLevel = getBatteryLevel() if (batteryLevel != -1) { result.success(batteryLevel) } else { result.error("UNAVAILABLE", "Battery level not available.", null) } } else { result.notImplemented() } } \`\`\` Start by opening the Android host portion of your Flutter app in Android Studio: 1. Start Android Studio 1. Select the menu item \*\*File > Open...\*\* 1. Navigate to the directory holding your Flutter app, and select the \*\*android\*\* folder inside it. Click \*\*OK\*\*. 1. Open the \`MainActivity.java\` file located in the \*\*java\*\* folder in the Project view. Next, create a \`MethodChannel\` and set a \`MethodCallHandler\` inside the \`configureFlutterEngine()\` method. Make sure to use the same channel name as was used on the Flutter client side. \`\`\`java title="MainActivity.java" import androidx.annotation.NonNull; import io.flutter.embedding.android.FlutterActivity; import io.flutter.embedding.engine.FlutterEngine; import io.flutter.plugin.common.MethodChannel; public class MainActivity extends FlutterActivity { private static final String CHANNEL = "samples.flutter.dev/battery"; @Override public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) { super.configureFlutterEngine(flutterEngine); new MethodChannel(flutterEngine.getDartExecutor().getBinaryMessenger(), CHANNEL) .setMethodCallHandler( (call, result) -> { // This method is invoked on the main thread. // TODO } ); } } \`\`\` Add the Android Java code that uses the Android battery APIs to retrieve the battery level. This code is exactly the same as you would write in a native Android app. First, add the needed imports at the top of the file: \`\`\`java title="MainActivity.java" import android.content.ContextWrapper; import android.content.Intent; import android.content.IntentFilter; import android.os.BatteryManager; import android.os.Build.VERSION; import android.os.Build.VERSION\_CODES; import android.os.Bundle; \`\`\` Then add the following as a new method in the activity class, below the \`configureFlutterEngine()\` method: \`\`\`java title="MainActivity.java" private int getBatteryLevel() { int batteryLevel = -1; if (VERSION.SDK\_INT >= VERSION\_CODES.LOLLIPOP) { BatteryManager batteryManager = (BatteryManager) getSystemService(BATTERY\_SERVICE); batteryLevel = batteryManager.getIntProperty(BatteryManager.BATTERY\_PROPERTY\_CAPACITY); } else { Intent intent = new ContextWrapper(getApplicationContext()). registerReceiver(null, new IntentFilter(Intent.ACTION\_BATTERY\_CHANGED)); batteryLevel = (intent.getIntExtra(BatteryManager.EXTRA\_LEVEL, -1) \* 100) / intent.getIntExtra(BatteryManager.EXTRA\_SCALE, -1); } return batteryLevel; } \`\`\` Finally, complete the \`setMethodCallHandler()\` method added earlier. You need to handle a single platform method, \`getBatteryLevel()\`, so test for that in the \`call\` argument. The implementation of this platform method calls the Android code written in the previous step, and returns a response for both the success and error cases using the \`result\` argument. If an unknown method is called, report that instead. Remove the following code: \`\`\`java title="MainActivity.java" new MethodChannel(flutterEngine.getDartExecutor().getBinaryMessenger(), CHANNEL) .setMethodCallHandler( (call, result) -> { // This method is invoked on the main thread. // TODO } ); \`\`\` And replace with the following: \`\`\`java title="MainActivity.java" new MethodChannel(flutterEngine.getDartExecutor().getBinaryMessenger(), CHANNEL) .setMethodCallHandler( (call, result) -> { // This method is invoked on the main thread. if (call.method.equals("getBatteryLevel")) { int batteryLevel = getBatteryLevel(); if (batteryLevel != -1) { result.success(batteryLevel); } else { result.error("UNAVAILABLE", "Battery level not available.", null); } } else { result.notImplemented(); } } ); \`\`\` You should now be able to run the app on Android. If using the Android Emulator, set the battery level in the Extended Controls panel accessible from the \*\*...\*\* button in the toolbar. ### Step 4: Add an iOS platform-specific implementation Start by opening the iOS host portion of your Flutter app in Xcode: 1. Start Xcode. 1. Select the menu item \*\*File > Open...\*\*. 1. Navigate to the directory holding your Flutter app, and select the \*\*ios\*\* folder inside it. Click \*\*OK\*\*. Add support for Swift in the standard template setup that uses Objective-C: 1. \*\*Expand Runner > Runner\*\* in the Project navigator. 1. Open the file \`AppDelegate.swift\` located under \*\*Runner > Runner\*\* in the Project navigator. Override the \`application:didFinishLaunchingWithOptions:\` function and create a \`FlutterMethodChannel\` tied to the channel name \`samples.flutter.dev/battery\`: :::note If your app adopts the \`UISceneDelegate\` lifecycle (default in Flutter 3.41+), \`window\` will be \`nil\` during \`application(\_:didFinishLaunchingWithOptions:)\`. To avoid a crash, use the \`FlutterImplicitEngineDelegate\` protocol and create your \`FlutterMethodChannel\` in the \`didInitializeImplicitFlutterEngine\` method. ::: \`\`\`swift title="AppDelegate.swift" @main @objc class AppDelegate: FlutterAppDelegate, FlutterImplicitEngineDelegate { func didInitializeImplicitFlutterEngine(\_ engineBridge: FlutterImplicitEngineBridge) { GeneratedPluginRegistrant.register(with: engineBridge.pluginRegistry) let batteryChannel = FlutterMethodChannel( name: "samples.flutter.dev/battery", binaryMessenger: engineBridge.applicationRegistrar.messenger() ) batteryChannel.setMethodCallHandler({ \[weak self] (call: FlutterMethodCall, result: FlutterResult) -> Void in // This method is invoked on the UI thread. // Handle battery messages. }) } } \`\`\` Next, add the iOS Swift code that uses the iOS battery APIs to retrieve the battery level. This code is exactly the same as you would write in a native iOS app. Add the following as a new method at the bottom of \`AppDelegate.swift\`: \`\`\`swift title="AppDelegate.swift" private func receiveBatteryLevel(result: FlutterResult) { let device = UIDevice.current device.isBatteryMonitoringEnabled = true if device.batteryState == UIDevice.BatteryState.unknown { result(FlutterError(code: "UNAVAILABLE", message: "Battery level not available.", details: nil)) } else { result(Int(device.batteryLevel \* 100)) } } \`\`\` Finally, complete the \`setMethodCallHandler()\` method added earlier. You need to handle a single platform method, \`getBatteryLevel()\`, so test for that in the \`call\` argument. The implementation of this platform method calls the iOS code written in the previous step. If an unknown method is called, report that instead. \`\`\`swift title="AppDelegate.swift" batteryChannel.setMethodCallHandler({ \[weak self] (call: FlutterMethodCall, result: FlutterResult) -> Void in // This method is invoked on the UI thread. guard call.method == "getBatteryLevel" else { result(FlutterMethodNotImplemented) return } self?.receiveBatteryLevel(result: result) }) \`\`\` Start by opening the iOS host portion of the Flutter app in Xcode: 1. Start Xcode. 1. Select the menu item \*\*File > Open...\*\*. 1. Navigate to the directory holding your Flutter app, and select the \*\*ios\*\* folder inside it. Click \*\*OK\*\*. 1. Make sure the Xcode projects builds without errors. 1. Open the file \`AppDelegate.m\`, located under \*\*Runner > Runner\*\* in the Project navigator. Create a \`FlutterMethodChannel\` and add a handler inside the \`application didFinishLaunchingWithOptions:\` method. Make sure to use the same channel name as was used on the Flutter client side. :::note If your app adopts the \`UISceneDelegate\` lifecycle (default in Flutter 3.41+), \`window\` will be \`nil\` during \`application:didFinishLaunchingWithOptions:\`. To avoid a crash, use the \`FlutterImplicitEngineDelegate\` protocol and create your \`FlutterMethodChannel\` in the \`didInitializeImplicitFlutterEngine\` method. ::: \`\`\`objc title="AppDelegate.m" #import #import "GeneratedPluginRegistrant.h" @implementation AppDelegate - (void)didInitializeImplicitFlutterEngine:(NSObject\*)engineBridge { \[GeneratedPluginRegistrant registerWithRegistry:engineBridge.pluginRegistry]; FlutterMethodChannel\* batteryChannel = \[FlutterMethodChannel methodChannelWithName:@"samples.flutter.dev/battery" binaryMessenger:engineBridge.applicationRegistrar.messenger]; \[batteryChannel setMethodCallHandler:^(FlutterMethodCall\* call, FlutterResult result) { // This method is invoked on the UI thread. // TODO }]; } @end \`\`\` Next, add the iOS ObjectiveC code that uses the iOS battery APIs to retrieve the battery level. This code is exactly the same as you would write in a native iOS app. Add the following method in the \`AppDelegate\` class, just before \`@end\`: \`\`\`objc title="AppDelegate.m" - (int)getBatteryLevel { UIDevice\* device = UIDevice.currentDevice; device.batteryMonitoringEnabled = YES; if (device.batteryState == UIDeviceBatteryStateUnknown) { return -1; } else { return (int)(device.batteryLevel \* 100); } } \`\`\` Finally, complete the \`setMethodCallHandler()\` method added earlier. You need to handle a single platform method, \`getBatteryLevel()\`, so test for that in the \`call\` argument. The implementation of this platform method calls the iOS code written in the previous step, and returns a response for both the success and error cases using the \`result\` argument. If an unknown method is called, report that instead. \`\`\`objc title="AppDelegate.m" \_\_weak typeof(self) weakSelf = self; \[batteryChannel setMethodCallHandler:^(FlutterMethodCall\* call, FlutterResult result) { // This method is invoked on the UI thread. if (\[@"getBatteryLevel" isEqualToString:call.method]) { int batteryLevel = \[weakSelf getBatteryLevel]; if (batteryLevel == -1) { result(\[FlutterError errorWithCode:@"UNAVAILABLE" message:@"Battery level not available." details:nil]); } else { result(@(batteryLevel)); } } else { result(FlutterMethodNotImplemented); } }]; \`\`\` You should now be able to run the app on iOS. If using the iOS Simulator, note that it doesn't support battery APIs, and the app displays 'Battery level not available'. ### Step 5: Add a Windows platform-specific implementation Start by opening the Windows host portion of your Flutter app in Visual Studio: 1. Run \`flutter build windows\` in your project directory once to generate the Visual Studio solution file. 1. Start Visual Studio. 1. Select \*\*Open a project or solution\*\*. 1. Navigate to the directory holding your Flutter app, then into the \*\*build\*\* folder, then the \*\*windows\*\* folder, then select the \`batterylevel.sln\` file. Click \*\*Open\*\*. Add the C++ implementation of the platform channel method: 1. Expand \*\*batterylevel > Source Files\*\* in the Solution Explorer. 1. Open the file \`flutter\_window\.cpp\`. First, add the necessary includes to the top of the file, just after \`#include "flutter\_window\.h"\`: \`\`\`cpp title="flutter\_window\.cpp" #include #include #include #include #include #include #include \`\`\` Edit the \`FlutterWindow::OnCreate\` method and create a \`flutter::MethodChannel\` tied to the channel name \`samples.flutter.dev/battery\`: \`\`\`cpp title="flutter\_window\.cpp" bool FlutterWindow::OnCreate() { // ... RegisterPlugins(flutter\_controller\_->engine()); flutter::MethodChannel<> channel( flutter\_controller\_->engine()->messenger(), "samples.flutter.dev/battery", \&flutter::StandardMethodCodec::GetInstance()); channel.SetMethodCallHandler( \[]\(const flutter::MethodCall<>& call, std::unique\_ptr> result) { // TODO }); SetChildContent(flutter\_controller\_->view()->GetNativeWindow()); return true; } \`\`\` Next, add the C++ code that uses the Windows battery APIs to retrieve the battery level. This code is exactly the same as you would write in a native Windows application. Add the following as a new function at the top of \`flutter\_window\.cpp\` just after the \`#include\` section: \`\`\`cpp title="flutter\_window\.cpp" static int GetBatteryLevel() { SYSTEM\_POWER\_STATUS status; if (GetSystemPowerStatus(\&status) == 0 || status.BatteryLifePercent == 255) { return -1; } return status.BatteryLifePercent; } \`\`\` Finally, complete the \`setMethodCallHandler()\` method added earlier. You need to handle a single platform method, \`getBatteryLevel()\`, so test for that in the \`call\` argument. The implementation of this platform method calls the Windows code written in the previous step. If an unknown method is called, report that instead. Remove the following code: \`\`\`cpp title="flutter\_window\.cpp" channel.SetMethodCallHandler( \[]\(const flutter::MethodCall<>& call, std::unique\_ptr> result) { // TODO }); \`\`\` And replace with the following: \`\`\`cpp title="flutter\_window\.cpp" channel.SetMethodCallHandler( \[]\(const flutter::MethodCall<>& call, std::unique\_ptr> result) { if (call.method\_name() == "getBatteryLevel") { int battery\_level = GetBatteryLevel(); if (battery\_level != -1) { result->Success(battery\_level); } else { result->Error("UNAVAILABLE", "Battery level not available."); } } else { result->NotImplemented(); } }); \`\`\` You should now be able to run the application on Windows. If your device doesn't have a battery, it displays 'Battery level not available'. ### Step 6: Add a macOS platform-specific implementation Start by opening the macOS host portion of your Flutter app in Xcode: 1. Start Xcode. 1. Select the menu item \*\*File > Open...\*\*. 1. Navigate to the directory holding your Flutter app, and select the \*\*macos\*\* folder inside it. Click \*\*OK\*\*. Add the Swift implementation of the platform channel method: 1. \*\*Expand Runner > Runner\*\* in the Project navigator. 1. Open the file \`MainFlutterWindow\.swift\` located under \*\*Runner > Runner\*\* in the Project navigator. First, add the necessary import to the top of the file, just after \`import FlutterMacOS\`: \`\`\`swift title="MainFlutterWindow\.swift" import IOKit.ps \`\`\` Create a \`FlutterMethodChannel\` tied to the channel name \`samples.flutter.dev/battery\` in the \`awakeFromNib\` method: \`\`\`swift title="MainFlutterWindow\.swift" override func awakeFromNib() { // ... self.setFrame(windowFrame, display: true) let batteryChannel = FlutterMethodChannel( name: "samples.flutter.dev/battery", binaryMessenger: flutterViewController.engine.binaryMessenger) batteryChannel.setMethodCallHandler { (call, result) in // This method is invoked on the UI thread. // Handle battery messages. } RegisterGeneratedPlugins(registry: flutterViewController) super.awakeFromNib() } } \`\`\` Next, add the macOS Swift code that uses the IOKit battery APIs to retrieve the battery level. This code is exactly the same as you would write in a native macOS app. Add the following as a new method at the bottom of \`MainFlutterWindow\.swift\`: \`\`\`swift title="MainFlutterWindow\.swift" private func getBatteryLevel() -> Int? { let info = IOPSCopyPowerSourcesInfo().takeRetainedValue() let sources: Array = IOPSCopyPowerSourcesList(info).takeRetainedValue() as Array if let source = sources.first { let description = IOPSGetPowerSourceDescription(info, source).takeUnretainedValue() as! \[String: AnyObject] if let level = description\[kIOPSCurrentCapacityKey] as? Int { return level } } return nil } \`\`\` Finally, complete the \`setMethodCallHandler\` method added earlier. You need to handle a single platform method, \`getBatteryLevel()\`, so test for that in the \`call\` argument. The implementation of this platform method calls the macOS code written in the previous step. If an unknown method is called, report that instead. \`\`\`swift title="MainFlutterWindow\.swift" batteryChannel.setMethodCallHandler { (call, result) in switch call.method { case "getBatteryLevel": guard let level = getBatteryLevel() else { result( FlutterError( code: "UNAVAILABLE", message: "Battery level not available", details: nil)) return } result(level) default: result(FlutterMethodNotImplemented) } } \`\`\` You should now be able to run the application on macOS. If your device doesn't have a battery, it displays 'Battery level not available'. ### Step 7: Add a Linux platform-specific implementation For this example you need to install the \`upower\` developer headers. This is likely available from your distribution, for example with: \`\`\`console sudo apt install libupower-glib-dev \`\`\` Start by opening the Linux host portion of your Flutter app in the editor of your choice. The instructions below are for Visual Studio Code with the "C/C++" and "CMake" extensions installed, but can be adjusted for other IDEs. 1. Launch Visual Studio Code. 1. Open the \*\*linux\*\* directory inside your project. 1. Choose \*\*Yes\*\* in the prompt asking: \`Would you like to configure project "linux"?\`. This enables C++ autocomplete. 1. Open the file \`runner/my\_application.cc\`. First, add the necessary includes to the top of the file, just after \`#include \`: \`\`\`c title="runner/my\_application.cc" #include

----
url: https://docs.flutter.dev/install/troubleshoot
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Troubleshooting installation](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

This page describes some common installation issues that new Flutter users have encountered and offers suggestions on how to resolve them.

If you are still experiencing problems after using this page, consider reaching out to any of the resources listed under [community support channels](#community-support). To add a topic to this page or make a correction, you can [file an issue](https://github.com/flutter/website/issues/new) or submit a [pull request](https://github.com/flutter/website/pulls) on GitHub.

## Get the Flutter SDK

[#](#get-the-flutter-sdk)

### Unable to find the `flutter` command

[#](#unable-to-find-the-flutter-command)

**What does this issue look like?**

When you try to run the `flutter` command, the console fails to find it. The error usually looks as follows:

```
'flutter' is not recognized as an internal or external command operable program or batch file
```

Error messages on macOS and Linux could look slightly different from the one on Windows.

**Explanation and suggestions**

Did you add Flutter to the `PATH` environment variable for your platform? On Windows, follow these [instructions for adding a command to your path](https://www.wikihow.com/Change-the-PATH-Environment-Variable-on-Windows).

If you've already [set up VS Code](/tools/vs-code#setup) for Flutter development, you can use the Flutter extension's **Locate SDK** prompt to identify the location of your `flutter` folder.

See also: [Configuring PATH and Environment Variables - Dart Code](https://dartcode.org/docs/configuring-path-and-environment-variables/)

### Flutter in special folders

[#](#flutter-in-special-folders)

**What does this issue look like?**

Running your Flutter project produces an error like the following:

```
The Flutter SDK is installed in a protected folder and may not function correctly.
Please move the SDK to a location that is user-writable without Administration permissions and restart.
```

**Explanation and suggestions**

On Windows, this usually happens when Flutter is installed in a directory like `C:\Program Files\` that requires elevated privileges. Try relocating Flutter to a different folder, such as `C:\src\flutter`.

### Invoke-Expression: You cannot call a method on a null-valued expression

[#](#invoke-expression-you-cannot-call-a-method-on-a-null-valued-expression)

**What does this issue look like?**

When running `flutter doctor` on Windows, you might see an error like:

```
Invoke-Expression : You cannot call a method on a null-valued expression.
At ...\update_engine_version.ps1:60 char:20
```

**Explanation and suggestions**

This error typically occurs when the `SystemRoot` environment variable is missing or when the PowerShell execution policy prevents the script from running correctly.

To resolve this:

1. **Run as Administrator**: Open your PowerShell terminal as an Administrator.

2. **Check Environment Variables**: Ensure the `SystemRoot` environment variable is set (usually to `C:\Windows`). You can check its value by running `echo $env:SystemRoot` in your PowerShell terminal.

3. **Check Execution Policy**: If the issue persists, you might need to adjust your execution policy. Run the following command in an Administrator PowerShell window:

   powershell

   ```
   Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
   ```

## Android setup

[#](#android-setup)

### Having multiple versions of Java installed

[#](#having-multiple-versions-of-java-installed)

**What does this issue look like?**

The command `flutter doctor --android-licenses` fails. Running `flutter doctor --verbose` gives an error message like the following:

```
java.lang.UnsupportedClassVersionError: com/android/prefs/AndroidLocationsProvider
has been compiled by a more recent version of the Java Runtime (class file version 55.0),
this version of the Java Runtime only recognizes class file versions up to 52.0
```

**Explanation and suggestions**

The error occurs when an older version of the Java Development Kit (JDK) is installed on your computer.

If you don't need multiple versions of Java, uninstall existing JDKs from your computer. Flutter automatically uses the JDK included in Android Studio.

If you do need another version of Java, try the workaround described in [this GitHub issue](https://github.com/flutter/flutter/issues/106416#issuecomment-1522198064) until a long-term solution is implemented. For more information, check out the [Android Java Gradle migration guide](/release/breaking-changes/android-java-gradle-migration-guide) or [flutter doctor --android-licenses not working due to java.lang.UnsupportedClassVersionError - Stack Overflow](https://stackoverflow.com/questions/75328050/).

### `cmdline-tools` component is missing

[#](#cmdline-tools-component-is-missing)

**What does this issue look like?**

The `flutter doctor` command complains that the `cmdline-tools` are missing from the Android toolchain. For example:

```
[!] Android toolchain - develop for Android devices (Android SDK version 33.0.2)
    • Android SDK at C:\Users\My PC\AppData\Local\Android\sdk
    X cmdline-tools component is missing
```

**Explanation and suggestions**

The easiest way to get the cmdline-tools is through the SDK Manager in Android Studio. To do this, use the following instructions:

1. Open the SDK Manager from Android Studio by selecting **Tools > SDK Manager** from the menu bar.
2. Select the latest Android SDK (or a specific version that your app requires), Android SDK Command-line Tools, and Android SDK Build-Tools.
3. Click **Apply** to install the selected artifacts.

If you're not using Android Studio, you can download the tools using the [sdkmanager](https://developer.android.com/studio/command-line/sdkmanager) command-line tool.

### Android license status unknown

[#](#android-license-status-unknown)

**What does this issue look like?**

When running `flutter doctor`, you see an error indicating that the Android license status is unknown:

```
[!] Android toolchain - develop for Android devices
    ! Android license status unknown.
      Run `flutter doctor --android-licenses` to accept the SDK licenses.
      See https://flutter.dev/to/macos-android-setup for more details.
```

**Explanation and suggestions**

This issue occurs when you haven't accepted the Android SDK licenses yet, or when `flutter doctor` can't locate the necessary Android SDK command-line tools to check license status.

To resolve this issue:

1. Ensure that the **Android SDK Command-line Tools** component is installed:

   1. Open Android Studio.
   2. Open the SDK Manager (**Tools > SDK Manager** or **More Actions > SDK Manager**).
   3. Switch to the **SDK Tools** tab.
   4. Select **Android SDK Command-line Tools (latest)**.
   5. Click **Apply**, then **OK** to install the component.

2. Run the license agreement command in your terminal:

   ```
   $ flutter doctor --android-licenses
   ```

3. Review and accept each license by entering `y` when prompted.

4. Run `flutter doctor` again to verify that all licenses are accepted:

   ```
   $ flutter doctor
   ```

## macOS setup

[#](#macos-setup)

### SocketException: Send failed, OS Error: No route to host, errno = 65

[#](#socketexception-send-failed-os-error-no-route-to-host-errno-65)

**What does this issue look like?**

On macOS, the `flutter run` command produces an error like:

```
$ flutter run
Launching lib/main.dart in debug mode...
...
Installing and launching...
Oops; flutter has exited unexpectedly: "SocketException: Send failed (OS Error: No route to host, errno = 65), address = 0.0.0.0, port = 5353".
```

**Explanation and suggestions**

This issue is related to macOS permissions.

To fix this:

1. Upgrade your Flutter SDK to the latest version.

2. Open **System Settings** > **Privacy & Security** > **Local Network**. Toggle on the permission for all the code editors and terminals you use to launch Flutter apps. You might need to restart your code editor, terminal, and physical device.

## Other problems

[#](#other-problems)

### Exit code 69

[#](#exit-code-69)

**What does this issue look like?**

Running a `flutter` command produces an "exit code: 69" error, as shown in the following example:

```
Running "flutter pub get" in flutter_tools...
Resolving dependencies in .../flutter/packages/flutter_tools... (28.0s)
Got TLS error trying to find package test at https://pub.dev/.
pub get failed
command:
".../flutter/bin/cache/dart-sdk/bin/
dart __deprecated_pub --color --directory
.../flutter/packages/flutter_tools get --example"
pub env: {
  "FLUTTER_ROOT": ".../flutter",
  "PUB_ENVIRONMENT": "flutter_cli:get",
  "PUB_CACHE": ".../.pub-cache",
}
exit code: 69
```

**Explanation and suggestions**

This issue is related to networking. Try the following instructions to troubleshoot:

* Check your internet connection. Make sure that you're connected to the internet and that your connection is stable.
* Restart your devices, including your computer and networking equipment.
* Use a VPN to help to bypass any restrictions that might prevent you from connecting to the network.
* If you have tried all of these steps and are still getting the error, print out verbose logs with the `flutter doctor -v` command and ask for help in one of the [community support channels](#community-support).

## Community support

[#](#community-support)

The Flutter community is helpful and welcoming. If none of the above suggestions solves your installation issue, consider asking for support from one of the following channels:

* [/r/flutterhelp](https://www.reddit.com/r/flutterhelp/) on Reddit
* [/r/flutterdev](https://discord.gg/rflutterdev) on Discord, particularly the `install-and-setup` channel on this server.
* [StackOverflow](https://stackoverflow.com), in particular, questions tagged with [#flutter](https://stackoverflow.com/questions/tagged/flutter) or [#dart](https://stackoverflow.com/questions/tagged/dart).

To be respectful of everyone's time, search the archive for a similar issue before posting a new one.

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-08-19. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/install/troubleshoot.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/install/troubleshoot\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/install/troubleshoot.md "Report an issue with this page").

----
url: https://docs.flutter.dev/install/add-to-path
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Add Flutter to your PATH](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

Learn how to add Flutter to your `PATH` environment variable after downloading the SDK. Adding Flutter to your `PATH` allows you to use the `flutter` and `dart` command-line tools in terminals and IDEs.

## Windows

[#](#windows)

To run `flutter` and `dart` commands in a terminal on Windows, add the Flutter SDK's `bin` directory to the `Path` environment variable.

1. ### Determine your Flutter SDK installation location

   Copy the absolute path to the directory that you downloaded and extracted the Flutter SDK into.

2. ### Navigate to the environment variables settings

   1. Press `Windows` + `Pause`.

      If your keyboard lacks a `Pause` key, try `Windows` + `Fn` + `B`.

      The **System > About** dialog opens.

   2. Click **Advanced System Settings** > **Advanced** > **Environment Variables...**.

      The **Environment Variables** dialog opens.

3. ### Add the Flutter SDK bin to your path

   1. In the **User variables for (username)** section of the **Environment Variables** dialog, look for the **Path** entry.

   2. If the **Path** entry exists, double-click it.

      The **Edit Environment Variable** dialog should open.

      1. Double-click inside an empty row.

      2. Type the path to the `bin` directory of your Flutter installation.

         For example, if you downloaded Flutter into a `develop\flutter` folder inside your user directory, you'd type the following:

         ```
         %USERPROFILE%\develop\flutter\bin
         ```

      3. Click the Flutter entry you added to select it.

      4. Click **Move Up** until the Flutter entry sits at the top of the list.

      5. To confirm your changes, click **OK** three times.

   3. If the entry doesn't exist, click **New\...**.

      The **Edit Environment Variable** dialog should open.

      1. In the **Variable Name** box, type `Path`.

      2. In the **Variable Value** box, type the path to the `bin` directory of your Flutter installation.

         For example, if you downloaded Flutter into a `develop\flutter` folder inside your user directory, you'd type the following:

         ```
         %USERPROFILE%\develop\flutter\bin
         ```

      3. To confirm your changes, click **OK** three times.

4. ### Apply your changes

   To apply this change and get access to the `flutter` tool, close and reopen all open command prompts, sessions in your terminal apps, and IDEs.

5. ### Validate your setup

   To ensure you successfully added the SDK to your `PATH`, open command prompt or your preferred terminal app, then try running the `flutter` and `dart` tools.

   ```
   $ flutter --version
   $ dart --version
   ```

   If either command isn't found, check out [Flutter installation troubleshooting](/install/troubleshoot).

## macOS

[#](#macos)

To run `flutter` and `dart` commands in a terminal on macOS, add the Flutter SDK's `bin` directory to the `PATH` environment variable.

1. ### Determine your Flutter SDK installation location

   Copy the absolute path to the directory that you downloaded and extracted the Flutter SDK into.

2. ### Open or create the Zsh environment variable file

   If it exists, open the [Zsh environment variable file](https://zsh.sourceforge.io/Intro/intro_3.html) `~/.zprofile` in your preferred text editor. If it doesn't exist, create the `~/.zprofile` file.

3. ### Add the Flutter SDK bin to your path

   At the end of your `~/.zprofile` file, use the built-in `export` command to update the `PATH` variable to include the `bin` directory of your Flutter installation.

   Replace `<path-to-sdk>` with the path to your Flutter SDK installation.

   bash

   ```
   export PATH="<path-to-sdk>/bin:$PATH"
   ```

   For example, if you downloaded Flutter into a `develop/flutter` folder inside your user directory, you'd add the following to the file:

   bash

   ```
   export PATH="$HOME/develop/flutter/bin:$PATH"
   ```

4. ### Save your changes

   Save, then close, the `~/.zprofile` file you edited.

5. ### Apply your changes

   To apply this change and get access to the `flutter` tool, close and reopen all open Zsh sessions in your terminal apps and IDEs.

6. ### Validate your setup

   To ensure you successfully added the SDK to your `PATH`, open a Zsh session in your preferred terminal, then try running the `flutter` and `dart` tools.

   ```
   $ flutter --version
   $ dart --version
   ```

   If either command isn't found, check out [Flutter installation troubleshooting](/install/troubleshoot).

## Linux

[#](#linux)

To run `flutter` and `dart` commands in a terminal on Linux, add the Flutter SDK's `bin` directory to the `PATH` environment variable.

1. ### Determine your Flutter SDK installation location

   Copy the absolute path to the directory that you downloaded and extracted the Flutter SDK into.

2. ### Determine your default shell

   If you don't know what shell you use, check which shell starts when you open a new console window.

   ```
   $ echo $SHELL
   ```

3. ### Add the Flutter SDK bin to your path

   To add the `bin` directory of your Flutter installation to your `PATH`:

   1. Expand the instructions for your default shell.
   2. Copy the provided command.
   3. Replace `<path-to-sdk>` with the path to your Flutter SDK install.
   4. Run the edited command in your preferred terminal with that shell.

   ***

   Expand for `bash` instructions

   ```
   $ echo 'export PATH="<path-to-sdk>/bin:$PATH"' >> ~/.bashrc
   ```

   For example, if you downloaded Flutter into a `develop/flutter` folder inside your user directory, you'd run the following:

   ```
   $ echo 'export PATH="$HOME/develop/flutter/bin:$PATH"' >> ~/.bashrc
   ```

   Expand for `zsh` instructions

   ```
   $ echo 'export PATH="<path-to-sdk>/bin:$PATH"' >> ~/.zshenv
   ```

   For example, if you downloaded Flutter into a `develop/flutter` folder inside your user directory, you'd run the following:

   ```
   $ echo 'export PATH="$HOME/develop/flutter/bin:$PATH"' >> ~/.zshenv
   ```

   Expand for `fish` instructions

   ```
   $ fish_add_path -U -p <path-to-sdk>/bin
   ```

   For example, if you downloaded Flutter into a `develop/flutter` folder inside your user directory, you'd run the following:

   ```
   $ fish_add_path -U -p ~/develop/flutter/bin
   ```

   Expand for `csh` instructions

   ```
   $ echo 'setenv PATH "<path-to-sdk>/bin:$PATH"' >> ~/.cshrc
   ```

   For example, if you downloaded Flutter into a `develop/flutter` folder inside your user directory, you'd run the following:

   ```
   $ echo 'setenv PATH "$HOME/develop/flutter/bin:$PATH"' >> ~/.cshrc
   ```

   Expand for `tcsh` instructions

   ```
   $ echo 'setenv PATH "<path-to-sdk>/bin:$PATH"' >> ~/.tcshrc
   ```

   For example, if you downloaded Flutter into a `develop/flutter` folder inside your user directory, you'd run the following:

   ```
   $ echo 'setenv PATH "$HOME/develop/flutter/bin:$PATH"' >> ~/.tcshrc
   ```

   Expand for `ksh` instructions

   ```
   $ echo 'export PATH="<path-to-sdk>/bin:$PATH"' >> ~/.profile
   ```

   For example, if you downloaded Flutter into a `develop/flutter` folder inside your user directory, you'd run the following:

   ```
   $ echo 'export PATH="$HOME/develop/flutter/bin:$PATH"' >> ~/.profile
   ```

   Expand for `sh` instructions

   ```
   $ echo 'export PATH="<path-to-sdk>/bin:$PATH"' >> ~/.profile
   ```

   For example, if you downloaded Flutter into a `develop/flutter` folder inside your user directory, you'd run the following:

   ```
   $ echo 'export PATH="$HOME/develop/flutter/bin:$PATH"' >> ~/.profile
   ```

4. ### Apply your changes

   To apply this change and get access to the `flutter` tool, close and reopen all open shell sessions in your terminal apps and IDEs.

5. ### Validate your setup

   To ensure you successfully added the SDK to your `PATH`, open your preferred terminal with your default shell, then try running the `flutter` and `dart` tools.

   ```
   $ flutter --version
   $ dart --version
   ```

   If either command isn't found, check out [Flutter installation troubleshooting](/install/troubleshoot).

## ChromeOS

[#](#chromeos)

To run `flutter` and `dart` commands in a terminal on chromeOS, add the Flutter SDK's `bin` directory to the `PATH` environment variable.

1. ### Determine your Flutter SDK installation location

   Copy the absolute path to the directory that you downloaded and extracted the Flutter SDK into.

2. ### Add the Flutter SDK bin to your path

   To add the `bin` directory of your Flutter installation to your `PATH`:

   1. Copy the following command.
   2. Replace `<path-to-sdk>` with the path to your Flutter SDK install.
   3. Run the edited command in your preferred terminal.

   ```
   $ echo 'export PATH="<path-to-sdk>:$PATH"' >> ~/.bash_profile
   ```

   For example, if you downloaded Flutter into a `develop/flutter` folder inside your user directory, you'd run the following:

   ```
   $ echo 'export PATH="$HOME/develop/flutter/bin:$PATH"' >> ~/.bash_profile
   ```

3. ### Apply your changes

   To apply this change and get access to the `flutter` tool, close and reopen all open Zsh sessions in your terminal apps and IDEs.

4. ### Validate your setup

   To ensure you successfully added the SDK to your `PATH`, open a Zsh session in your preferred terminal, then try running the `flutter` and `dart` tools.

   ```
   $ flutter --version
   $ dart --version
   ```

   If either command isn't found, check out [Flutter installation troubleshooting](/install/troubleshoot).

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/install/add-to-path.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/install/add-to-path\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/install/add-to-path.md "Report an issue with this page").

----
url: https://docs.flutter.dev/ui/adaptive-responsive/idioms
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Platform idioms](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

The final area to consider for adaptive apps is platform standards. Each platform has its own idioms and norms; these nominal or de facto standards inform user expectations of how an application should behave. Thanks, in part to the web, users are accustomed to more customized experiences, but reflecting these platform standards can still provide significant benefits:

* \* **Reduce cognitive load**

  By matching the user's existing mental model, accomplishing tasks becomes intuitive, which requires less thinking, boosts productivity, and reduces frustrations.

* \* **Build trust**

  Users can become wary or suspicious when applications don't adhere to their expectations. Conversely, a UI that feels familiar can build user trust and can help improve the perception of quality. This often has the added benefit of better app store ratings—something we can all appreciate!

## Consider expected behavior on each platform

[#](#consider-expected-behavior-on-each-platform)

The first step is to spend some time considering what the expected appearance, presentation, or behavior is on this platform. Try to forget any limitations of your current implementation, and just envision the ideal user experience. Work backwards from there.

Another way to think about this is to ask, "How would a user of this platform expect to achieve this goal?" Then, try to envision how that would work in your app without any compromises.

This can be difficult if you aren't a regular user of the platform. You might be unaware of the specific idioms and can easily miss them completely. For example, a lifetime Android user is likely unaware of platform conventions on iOS, and the same holds true for macOS, Linux, and Windows. These differences might be subtle to you, but be painfully obvious to an experienced user.

### Find a platform advocate

[#](#find-a-platform-advocate)

If possible, assign someone as an advocate for each platform. Ideally, your advocate uses the platform as their primary device, and can offer the perspective of a highly opinionated user. To reduce the number of people, combine roles. Have one advocate for Windows and Android, one for Linux and the web, and one for Mac and iOS.

The goal is to have constant, informed feedback so the app feels great on each platform. Advocates should be encouraged to be quite picky, calling out anything they feel differs from typical applications on their device. A simple example is how the default button in a dialog is typically on the left on Mac and Linux, but is on the right on Windows. Details like that are easy to miss if you aren't using a platform on a regular basis.

### Stay unique

[#](#stay-unique)

Conforming to expected behaviors doesn't mean that your app needs to use default components or styling. Many of the most popular multiplatform apps have very distinct and opinionated UIs including custom buttons, context menus, and title bars.

The more you can consolidate styling and behavior across platforms, the easier development and testing will be. The trick is to balance creating a unique experience with a strong identity, while respecting the norms of each platform.

## Common idioms and norms to consider

[#](#common-idioms-and-norms-to-consider)

Take a quick look at a few specific norms and idioms you might want to consider, and how you could approach them in Flutter.

### Scrollbar appearance and behavior

[#](#scrollbar-appearance-and-behavior)

Desktop and mobile users expect scrollbars, but they expect them to behave differently on different platforms. Mobile users expect smaller scrollbars that only appear while scrolling, whereas desktop users generally expect omnipresent, larger scrollbars that they can click or drag.

Flutter comes with a built-in `Scrollbar` widget that already has support for adaptive colors and sizes according to the current platform. The one tweak you might want to make is to toggle `alwaysShown` when on a desktop platform:

dart

```
return Scrollbar(
  thumbVisibility: DeviceType.isDesktop,
  controller: _scrollController,
  child: GridView.count(
    controller: _scrollController,
    padding: const EdgeInsets.all(Insets.extraLarge),
    childAspectRatio: 1,
    crossAxisCount: colCount,
    children: listChildren,
  ),
);
```

This subtle attention to detail can make your app feel more comfortable on a given platform.

### Multi-select

[#](#multi-select)

Dealing with multi-select within a list is another area with subtle differences across platforms:

dart

```
static bool get isSpanSelectModifierDown =>
    isKeyDown({LogicalKeyboardKey.shiftLeft, LogicalKeyboardKey.shiftRight});
```

To perform a platform-aware check for control or command, you can write something like this:

dart

```
static bool get isMultiSelectModifierDown {
  bool isDown = false;
  if (Platform.isMacOS) {
    isDown = isKeyDown({
      LogicalKeyboardKey.metaLeft,
      LogicalKeyboardKey.metaRight,
    });
  } else {
    isDown = isKeyDown({
      LogicalKeyboardKey.controlLeft,
      LogicalKeyboardKey.controlRight,
    });
  }
  return isDown;
}
```

A final consideration for keyboard users is the **Select All** action. If you have a large list of items of selectable items, many of your keyboard users will expect that they can use `Control+A` to select all the items.

#### Touch devices

[#](#touch-devices)

On touch devices, multi-selection is typically simplified, with the expected behavior being similar to having the `isMultiSelectModifier` down on the desktop. You can select or deselect items using a single tap, and will usually have a button to **Select All** or **Clear** the current selection.

How you handle multi-selection on different devices depends on your specific use cases, but the important thing is to make sure that you're offering each platform the best interaction model possible.

### Selectable text

[#](#selectable-text)

A common expectation on the web (and to a lesser extent desktop) is that most visible text can be selected with the mouse cursor. When text is not selectable, users on the web tend to have an adverse reaction.

Luckily, this is easy to support with the [`SelectableText`](https://pub.dev/documentation/material_ui/latest/material_ui/SelectableText-class.html) widget:

dart

```
return const SelectableText('Select me!');
```

To support rich text, then use `TextSpan`:

dart

```
return const SelectableText.rich(
  TextSpan(
    children: [
      TextSpan(text: 'Hello'),
      TextSpan(
        text: 'Bold',
        style: TextStyle(fontWeight: FontWeight.bold),
      ),
    ],
  ),
);
```

### Title bars

[#](#title-bars)

On modern desktop applications, it's common to customize the title bar of your app window, adding a logo for stronger branding or contextual controls to help save vertical space in your main UI.

This isn't supported directly in Flutter, but you can use the [`bits_dojo`](https://github.com/bitsdojo/bitsdojo_window) package to disable the native title bars, and replace them with your own.

This package lets you add whatever widgets you want to the `TitleBar` because it uses pure Flutter widgets under the hood. This makes it easy to adapt the title bar as you navigate to different sections of the app.

### Context menus and tooltips

[#](#context-menus-and-tooltips)

On desktop, there are several interactions that manifest as a widget shown in an overlay, but with differences in how they're triggered, dismissed, and positioned:

* \* **Context menu**

  Typically triggered by a right-click, a context menu is positioned close to the mouse, and is dismissed by clicking anywhere, selecting an option from the menu, or clicking outside it.

* \* **Tooltip**

  Typically triggered by hovering for 200-400ms over an interactive element, a tooltip is usually anchored to a widget (as opposed to the mouse position) and is dismissed when the mouse cursor leaves that widget.

* \* **Popup panel (also known as flyout)**

  Similar to a tooltip, a popup panel is usually anchored to a widget. The main difference is that panels are most often shown on a tap event, and they usually don't hide themselves when the cursor leaves. Instead, panels are typically dismissed by clicking outside the panel or by pressing a **Close** or **Submit** button.

To show basic tooltips in Flutter, use the built-in [`Tooltip`](https://pub.dev/documentation/material_ui/latest/material_ui/Tooltip-class.html) widget:

dart

```
return const Tooltip(
  message: 'I am a Tooltip',
  child: Text('Hover over the text to show a tooltip.'),
);
```

Flutter also provides built-in context menus when editing or selecting text.

To show more advanced tooltips, popup panels, or create custom context menus, you either use one of the available packages, or build it yourself using a `Stack` or `Overlay`.

Some available packages include:

* [`context_menus`](https://pub.dev/packages/context_menus)
* [`anchored_popups`](https://pub.dev/packages/anchored_popups)
* [`flutter_portal`](https://pub.dev/packages/flutter_portal)
* [`super_tooltip`](https://pub.dev/packages/super_tooltip)
* [`custom_pop_up_menu`](https://pub.dev/packages/custom_pop_up_menu)

While these controls can be valuable for touch users as accelerators, they are essential for mouse users. These users expect to right-click things, edit content in place, and hover for more information. Failing to meet those expectations can lead to disappointed users, or at least, a feeling that something isn't quite right.

### Horizontal button order

[#](#horizontal-button-order)

On Windows, when presenting a row of buttons, the confirmation button is placed at the start of the row (left side). On all other platforms, it's the opposite. The confirmation button is placed at the end of the row (right side).

This can be easily handled in Flutter using the `TextDirection` property on `Row`:

dart

```
TextDirection btnDirection = DeviceType.isWindows
    ? TextDirection.rtl
    : TextDirection.ltr;
return Row(
  children: [
    const Spacer(),
    Row(
      textDirection: btnDirection,
      children: [
        DialogButton(
          label: 'Cancel',
          onPressed: () => Navigator.pop(context, false),
        ),
        DialogButton(
          label: 'Ok',
          onPressed: () => Navigator.pop(context, true),
        ),
      ],
    ),
  ],
);
```

### Menu bar

[#](#menu-bar)

Another common pattern on desktop apps is the menu bar. On Windows and Linux, this menu lives as part of the Chrome title bar, whereas on macOS, it's located along the top of the primary screen.

Currently, you can specify custom menu bar entries using a prototype plugin, but it's expected that this functionality will eventually be integrated into the main SDK.

It's worth mentioning that on Windows and Linux, you can't combine a custom title bar with a menu bar. When you create a custom title bar, you're replacing the native one completely, which means you also lose the integrated native menu bar.

If you need both a custom title bar and a menu bar, you can achieve that by implementing it in Flutter, similar to a custom context menu.

### Drag and drop

[#](#drag-and-drop)

One of the core interactions for both touch-based and pointer-based inputs is drag and drop. Although this interaction is expected for both types of input, there are important differences to think about when it comes to scrolling lists of draggable items.

Generally speaking, touch users expect to see drag handles to differentiate draggable areas from scrollable ones, or alternatively, to initiate a drag by using a long press gesture. This is because scrolling and dragging are both sharing a single finger for input.

Mouse users have more input options. They can use a wheel or scrollbar to scroll, which generally eliminates the need for dedicated drag handles. If you look at the macOS Finder or Windows Explorer, you'll see that they work this way: you just select an item and start dragging.

In Flutter, you can implement drag and drop in many ways. Discussing specific implementations is outside the scope of this article, but some high level options include the following:

* Use the [`Draggable`](https://api.flutter.dev/flutter/widgets/Draggable-class.html) and [`DragTarget`](https://api.flutter.dev/flutter/widgets/DragTarget-class.html) APIs directly for a custom look and feel.

* Hook into `onPan` gesture events, and move an object yourself within a parent `Stack`.

* Use one of the [pre-made list packages](https://pub.dev/packages?q=reorderable+list) on pub.dev.

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-09-29. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/ui/adaptive-responsive/idioms.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/ui/adaptive-responsive/idioms\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/ui/adaptive-responsive/idioms.md "Report an issue with this page").

----
url: https://docs.flutter.dev/cookbook/navigation/navigation-basics
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Navigate to a new screen and back](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

Most apps contain several screens for displaying different types of information. For example, an app might have a screen that displays products. When the user taps the image of a product, a new screen displays details about the product.

In Android, a route is equivalent to an `Activity`. In iOS, a route is equivalent to a `ViewController`. In Flutter, a route is just a widget.

This recipe uses the [`Navigator`](https://api.flutter.dev/flutter/widgets/Navigator-class.html) to navigate to a new route.

The next few sections show how to navigate between two routes, using these steps:

1. Create two routes.
2. Navigate to the second route using `Navigator.push()`.
3. Return to the first route using `Navigator.pop()`.

## 1. Create two routes

[#](#1-create-two-routes)

First, create two routes to work with. Since this is a basic example, each route contains only a single button. Tapping the button on the first route navigates to the second route. Tapping the button on the second route returns to the first route.

First, set up the visual structure:

* [Android](#152-tab-panel)
* [iOS](#153-tab-panel)

dart

```
class FirstRoute extends StatelessWidget {
  const FirstRoute({super.key});
​
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('First Route')),
      body: Center(
        child: ElevatedButton(
          child: const Text('Open route'),
          onPressed: () {
            // Navigate to second route when tapped.
          },
        ),
      ),
    );
  }
}
​
class SecondRoute extends StatelessWidget {
  const SecondRoute({super.key});
​
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Second Route')),
      body: Center(
        child: ElevatedButton(
          onPressed: () {
            // Navigate back to first route when tapped.
          },
          child: const Text('Go back!'),
        ),
      ),
    );
  }
}
```

dart

```
class FirstRoute extends StatelessWidget {
  const FirstRoute({super.key});
​
  @override
  Widget build(BuildContext context) {
    return CupertinoPageScaffold(
      navigationBar: const CupertinoNavigationBar(middle: Text('First Route')),
      child: Center(
        child: CupertinoButton(
          child: const Text('Open route'),
          onPressed: () {
            // Navigate to second route when tapped.
          },
        ),
      ),
    );
  }
}
​
class SecondRoute extends StatelessWidget {
  const SecondRoute({super.key});
​
  @override
  Widget build(BuildContext context) {
    return CupertinoPageScaffold(
      navigationBar: const CupertinoNavigationBar(middle: Text('Second Route')),
      child: Center(
        child: CupertinoButton(
          onPressed: () {
            // Navigate back to first route when tapped.
          },
          child: const Text('Go back!'),
        ),
      ),
    );
  }
}
```

## 2. Navigate to the second route using Navigator.push()

[#](#2-navigate-to-the-second-route-using-navigator-push)

To switch to a new route, use the [`Navigator.push()`](https://api.flutter.dev/flutter/widgets/Navigator/push.html) 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 platform-specific route such as [`MaterialPageRoute`](https://pub.dev/documentation/material_ui/latest/material_ui/MaterialPageRoute-class.html) or [`CupertinoPageRoute`](https://pub.dev/documentation/cupertino_ui/latest/cupertino_ui/CupertinoPageRoute-class.html). A platform-specific route 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:

* [Android](#154-tab-panel)
* [iOS](#155-tab-panel)

dart

```
// Within the `FirstRoute` widget:
onPressed: () {
  Navigator.push(
    context,
    MaterialPageRoute<void>(
      builder: (context) => const SecondRoute(),
    ),
  );
}
```

dart

```
// Within the `FirstRoute` widget:
onPressed: () {
  Navigator.push(
    context,
    CupertinoPageRoute<void>(
      builder: (context) => const SecondRoute(),
    ),
  );
}
```

## 3. Return to the first route using Navigator.pop()

[#](#3-return-to-the-first-route-using-navigator-pop)

How do you close the second route and return to the first? By using the [`Navigator.pop()`](https://api.flutter.dev/flutter/widgets/Navigator/pop.html) method. The `pop()` method removes the current `Route` from the stack of routes managed by the `Navigator`.

To implement a return to the original route, update the `onPressed()` callback in the `SecondRoute` widget:

dart

```
// Within the SecondRoute widget
onPressed: () {
  Navigator.pop(context);
}
```

## Interactive example

[#](#interactive-example)

* [Android](#156-tab-panel)
* [iOS](#157-tab-panel)

```
import 'package:flutter/material.dart';

void main() {
  runApp(const MaterialApp(title: 'Navigation Basics', home: FirstRoute()));
}

class FirstRoute extends StatelessWidget {
  const FirstRoute({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('First Route')),
      body: Center(
        child: ElevatedButton(
          child: const Text('Open route'),
          onPressed: () {
            Navigator.push(
              context,
              MaterialPageRoute<void>(
                builder: (context) => const SecondRoute(),
              ),
            );
          },
        ),
      ),
    );
  }
}

class SecondRoute extends StatelessWidget {
  const SecondRoute({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Second Route')),
      body: Center(
        child: ElevatedButton(
          onPressed: () {
            Navigator.pop(context);
          },
          child: const Text('Go back!'),
        ),
      ),
    );
  }
}
```

```
import 'package:flutter/cupertino.dart';

void main() {
  runApp(const CupertinoApp(title: 'Navigation Basics', home: FirstRoute()));
}

class FirstRoute extends StatelessWidget {
  const FirstRoute({super.key});

  @override
  Widget build(BuildContext context) {
    return CupertinoPageScaffold(
      navigationBar: const CupertinoNavigationBar(middle: Text('First Route')),
      child: Center(
        child: CupertinoButton(
          child: const Text('Open route'),
          onPressed: () {
            Navigator.push(
              context,
              CupertinoPageRoute<void>(
                builder: (context) => const SecondRoute(),
              ),
            );
          },
        ),
      ),
    );
  }
}

class SecondRoute extends StatelessWidget {
  const SecondRoute({super.key});

  @override
  Widget build(BuildContext context) {
    return CupertinoPageScaffold(
      navigationBar: const CupertinoNavigationBar(middle: Text('Second Route')),
      child: Center(
        child: CupertinoButton(
          onPressed: () {
            Navigator.pop(context);
          },
          child: const Text('Go back!'),
        ),
      ),
    );
  }
}
```

## Additional navigation methods

[#](#additional-navigation-methods)

The recipe in this topic shows you one way to navigate to a new screen and back to the previous scene, using the [`push`](https://api.flutter.dev/flutter/widgets/Navigator/push.html) and [`pop`](https://api.flutter.dev/flutter/widgets/Navigator/pop.html) methods in the [`Navigator`](https://api.flutter.dev/flutter/widgets/Navigator-class.html) class, but there are several other `Navigator` static methods that you can use. Here are a few of them:

* [`pushAndRemoveUntil`](https://api.flutter.dev/flutter/widgets/Navigator/pushAndRemoveUntil.html): Adds a navigation route to the stack and then removes the most recent routes from the stack until a condition is met.
* [`pushReplacement`](https://api.flutter.dev/flutter/widgets/Navigator/pushReplacement.html): Replaces the current route on the top of the stack with a new one.
* [`replace`](https://api.flutter.dev/flutter/widgets/Navigator/replace.html): Replace a route on the stack with another route.
* [`replaceRouteBelow`](https://api.flutter.dev/flutter/widgets/Navigator/replaceRouteBelow.html): Replace the route below a specific route on the stack.
* [`popUntil`](https://api.flutter.dev/flutter/widgets/Navigator/popUntil.html): Removes the most recent routes that were added to the stack of navigation routes until a condition is met.
* [`removeRoute`](https://api.flutter.dev/flutter/widgets/Navigator/removeRoute.html): Remove a specific route from the stack.
* [`removeRouteBelow`](https://api.flutter.dev/flutter/widgets/Navigator/removeRouteBelow.html): Remove the route below a specific route on the stack.
* [`restorablePush`](https://api.flutter.dev/flutter/widgets/Navigator/restorablePush.html): Restore a route that was removed from the stack.

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-09-29. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/navigation/navigation-basics.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/cookbook/navigation/navigation-basics\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/navigation/navigation-basics.md "Report an issue with this page").

----
url: https://docs.flutter.dev/ui/accessibility/accessibility-testing.md
----

\# Accessibility testing > Information on Flutter's accessibility testing. ## Accessibility regulations To ensure your app is accessible, check it against public standards like the \[Web Content Accessibility Guidelines (WCAG) 2]\[], the \[EN 301 549]\[], and use resources like the \[Voluntary Product Accessibility Template (VPAT)]\[] to self-assess your product. For more details on these regulations, check out the main \[accessibility page]\(/ui/accessibility). \[Web Content Accessibility Guidelines (WCAG) 2]: https\://www\.w3.org/WAI/standards-guidelines/wcag/ \[EN 301 549]: https\://www\.etsi.org/deliver/etsi\_en/301500\_301599/301549/03.02.01\_60/en\_301549v030201p.pdf \[Voluntary Product Accessibility Template (VPAT)]: https\://www\.itic.org/policy/accessibility/vpat ## Inspecting accessibility support We recommend using automated accessibility scanners to test the following: \* For Android: 1. Install the \[Accessibility Scanner]\[] for Android 1. Enable the Accessibility Scanner from \*\*Android Settings > Accessibility > Accessibility Scanner > On\*\*. 1. Navigate to the Accessibility Scanner 'checkbox' icon button to initiate a scan. \* For iOS: 1. Open the \`iOS\` folder of your Flutter app in Xcode. 1. Select a Simulator as the target, and click the \*\*Run\*\* button. 1. In Xcode, select \*\*Xcode > Open Developer Tools > Accessibility Inspector\*\*. 1. In the Accessibility Inspector, select \*\*Inspection > Enable Point to Inspect\*\*, and then select the various user interface elements in your running Flutter app to inspect their accessibility attributes. 1. In the Accessibility Inspector, select \*\*Audit\*\* in the toolbar, and then select \*\*Run Audit\*\* to get a report of potential issues. \* For web: 1. Open Chrome DevTools (or similar tools in other browsers). 2. Inspect the HTML tree under semantics host, containing the ARIA attributes generated by Flutter. 3. In Chrome, the "Elements" tab has an "Accessibility" sub-tab that can be used to inspect the data exported to semantics tree. ## Testing accessibility on mobile Test your app using Flutter's \[Accessibility Guideline API]\[]. This API checks if your app's UI meets Flutter's accessibility recommendations. These cover recommendations for text contrast, target size, and target labels. The following snippet shows how to use the Guideline API on a sample widget named \`AccessibleApp\`:

<!--?code-excerpt "accessibility/test/a11y_test.dart"?-->

\`\`\`dart title="test/a11y\_test.dart" import 'package:flutter\_test/flutter\_test.dart'; import 'package:your\_accessible\_app/main.dart'; void main() { testWidgets('Follows a11y guidelines', (tester) async { final SemanticsHandle handle = tester.ensureSemantics(); await tester.pumpWidget(const AccessibleApp()); // Checks that tappable nodes have a minimum size of 48 by 48 pixels // for Android. await expectLater(tester, meetsGuideline(androidTapTargetGuideline)); // Checks that tappable nodes have a minimum size of 44 by 44 pixels // for iOS. await expectLater(tester, meetsGuideline(iOSTapTargetGuideline)); // Checks that touch targets with a tap or long press action are labeled. await expectLater(tester, meetsGuideline(labeledTapTargetGuideline)); // Checks whether semantic nodes meet the minimum text contrast levels. // The recommended text contrast is 3:1 for larger text // (18 point and above regular). await expectLater(tester, meetsGuideline(textContrastGuideline)); handle.dispose(); }); } \`\`\` To try these tests out, run them on a \[new app created with \`flutter create\`]\[create-new-app]. Each button on that app's main screen serves as a tappable target with text rendered in an 18-point font. You can add Guideline API tests alongside other \[widget tests]\[], or in a separate file, such as \`test/a11y\_test.dart\` in this example. \[Accessibility Guideline API]: https\://api.flutter.dev/flutter/flutter\_test/AccessibilityGuideline-class.html \[create-new-app]: /reference/create-new-app \[widget tests]: /testing/overview#widget-tests ## Testing accessibility on the web You can debug accessibility by visualizing the semantic nodes created for your web app using the following command line flag in profile and release modes: \`\`\`console flutter run -d chrome --profile --dart-define=FLUTTER\_WEB\_DEBUG\_SHOW\_SEMANTICS=true \`\`\` With the flag activated, the semantic nodes appear on top of the widgets; you can verify that the semantic elements are placed where they should be. If the semantic nodes are incorrectly placed, please \[file a bug report]\[]. \[Accessibility Scanner]: https\://play.google.com/store/apps/details?id=com.google.android.apps.accessibility.auditor\&hl=en \[file a bug report]: https\://goo.gle/flutter\_web\_issue

----
url: https://docs.flutter.dev/deployment/cd.md
----

\# Continuous delivery with Flutter > How to automate continuous building and releasing of your Flutter app. Follow continuous delivery best practices with Flutter to make sure your application is delivered to your beta testers and validated on a frequent basis without resorting to manual workflows. ## CI/CD Options There are a number of continuous integration (CI) and continuous delivery (CD) options available to help automate the delivery of your application. ### All-in-one options with built-in Flutter functionality \* \[Codemagic]\[] \* \[Bitrise]\[] \* \[Appcircle]\[] ### Integrating fastlane with existing workflows You can use fastlane with the following tooling: \* \[GitHub Actions]\[] \* Example: \[GitHub Action in Flutter Project]\[] \* \[Cirrus]\[] \* \[Travis]\[] \* \[GitLab]\[] \* \[CircleCI]\[] \* \[Building and deploying Flutter apps with Fastlane]\[] This guide shows how to set up fastlane and then integrate it with your existing testing and continuous integration (CI) workflows. For more information, see "Integrating fastlane with existing workflow". ## fastlane \[fastlane]\[] is an open-source tool suite to automate releases and deployments for your app. ### Local setup It's recommended that you test the build and deployment process locally before migrating to a cloud-based system. You could also choose to perform continuous delivery from a local machine. 1. Install fastlane \`gem install fastlane\` or \`brew install fastlane\`. Visit the \[fastlane docs]\[fastlane] for more info. 1. Create an environment variable named \`FLUTTER\_ROOT\`, and set it to the root directory of your Flutter SDK. (This is required for the scripts that deploy for iOS.) 1. Create your Flutter project, and when ready, make sure that your project builds via \* !\[Android]\(/assets/images/docs/cd/android.png) \`flutter build appbundle\`; and \* !\[iOS]\(/assets/images/docs/cd/ios.png) \`flutter build ipa\`. 1. Initialize the fastlane projects for each platform. \* !\[Android]\(/assets/images/docs/cd/android.png) In your \`\[project]/android\` directory, run \`fastlane init\`. \* !\[iOS]\(/assets/images/docs/cd/ios.png) In your \`\[project]/ios\` directory, run \`fastlane init\`. 1. Edit the \`Appfile\`s to ensure they have adequate metadata for your app. \* !\[Android]\(/assets/images/docs/cd/android.png) Check that \`package\_name\` in \`\[project]/android/fastlane/Appfile\` matches your package name in AndroidManifest.xml. \* !\[iOS]\(/assets/images/docs/cd/ios.png) Check that \`app\_identifier\` in \`\[project]/ios/fastlane/Appfile\` also matches Info.plist's bundle identifier. Fill in \`apple\_id\`, \`itc\_team\_id\`, \`team\_id\` with your respective account info. 1. Set up your local login credentials for the stores. \* !\[Android]\(/assets/images/docs/cd/android.png) Follow the \[Supply setup steps]\[] and ensure that \`fastlane supply init\` successfully syncs data from your Play Store console. \_Treat the .json file like your password and do not check it into any public source control repositories.\_ \* !\[iOS]\(/assets/images/docs/cd/ios.png) Your iTunes Connect username is already in your \`Appfile\`'s \`apple\_id\` field. Set the \`FASTLANE\_PASSWORD\` shell environment variable with your iTunes Connect password. Otherwise, you'll be prompted when uploading to iTunes/TestFlight. 1. Set up code signing. \* !\[Android]\(/assets/images/docs/cd/android.png) Follow the \[Android app signing steps]\[]. \* !\[iOS]\(/assets/images/docs/cd/ios.png) On iOS, create and sign using a distribution certificate instead of a development certificate when you're ready to test and deploy using TestFlight or App Store. \* Create and download a distribution certificate in your \[Apple Developer Account console]\[]. \* \`open \[project]/ios/Runner.xcworkspace/\` and select the distribution certificate in your target's settings pane. 1. Create a \`Fastfile\` script for each platform. \* !\[Android]\(/assets/images/docs/cd/android.png) On Android, follow the \[fastlane Android beta deployment guide]\[]. Your edit could be as simple as adding a \`lane\` that calls \`upload\_to\_play\_store\`. Set the \`aab\` argument to \`../build/app/outputs/bundle/release/app-release.aab\` to use the app bundle \`flutter build\` already built. \* !\[iOS]\(/assets/images/docs/cd/ios.png) On iOS, follow the \[fastlane iOS beta deployment guide]\[]. You can specify the archive path to avoid rebuilding the project. For example: \`\`\`ruby build\_app( skip\_build\_archive: true, archive\_path: "../build/ios/archive/Runner.xcarchive", ) upload\_to\_testflight \`\`\` You're now ready to perform deployments locally or migrate the deployment process to a continuous integration (CI) system. ### Running deployment locally 1. Build the release mode app. \* !\[Android]\(/assets/images/docs/cd/android.png) \`flutter build appbundle\`. \* !\[iOS]\(/assets/images/docs/cd/ios.png) \`flutter build ipa\`. 1. Run the Fastfile script on each platform. \* !\[Android]\(/assets/images/docs/cd/android.png) \`cd android\` then \`fastlane \[name of the lane you created]\`. \* !\[iOS]\(/assets/images/docs/cd/ios.png) \`cd ios\` then \`fastlane \[name of the lane you created]\`. ### Cloud build and deploy setup First, follow the local setup section described in 'Local setup' to make sure the process works before migrating onto a cloud system like Travis. The main thing to consider is that since cloud instances are ephemeral and untrusted, you won't be leaving your credentials like your Play Store service account JSON or your iTunes distribution certificate on the server. Continuous Integration (CI) systems generally support encrypted environment variables to store private data. You can pass these environment variables using \`--dart-define MY\_VAR=MY\_VALUE\` while building the app. \*\*Take precaution not to re-echo those variable values back onto the console in your test scripts\*\*. Those variables are also not available in pull requests until they're merged to ensure that malicious actors cannot create a pull request that prints these secrets out. Be careful with interactions with these secrets in pull requests that you accept and merge. 1. Make login credentials ephemeral. \* !\[Android]\(/assets/images/docs/cd/android.png) On Android: \* Remove the \`json\_key\_file\` field from \`Appfile\` and store the string content of the JSON in your CI system's encrypted variable. Read the environment variable directly in your \`Fastfile\`. \`\`\`plaintext upload\_to\_play\_store( ... json\_key\_data: ENV\[''] ) \`\`\` \* Serialize your upload key (for example, using base64) and save it as an encrypted environment variable. You can deserialize it on your CI system during the install phase with \`\`\`bash echo "$PLAY\_STORE\_UPLOAD\_KEY" | base64 --decode > \[path to your upload keystore] \`\`\` \* !\[iOS]\(/assets/images/docs/cd/ios.png) On iOS: \* Move the local environment variable \`FASTLANE\_PASSWORD\` to use encrypted environment variables on the CI system. \* The CI system needs access to your distribution certificate. fastlane's \[Match]\[] system is recommended to synchronize your certificates across machines. 2. It's recommended to use a Gemfile instead of using an indeterministic \`gem install fastlane\` on the CI system each time to ensure the fastlane dependencies are stable and reproducible between local and cloud machines. However, this step is optional. \* In both your \`\[project]/android\` and \`\[project]/ios\` folders, create a \`Gemfile\` containing the following content: \`\`\`plaintext source "https\://rubygems.org" gem "fastlane" \`\`\` \* In both directories, run \`bundle update\` and check both \`Gemfile\` and \`Gemfile.lock\` into source control. \* When running locally, use \`bundle exec fastlane\` instead of \`fastlane\`. 3. Create the CI test script such as \`.travis.yml\` or \`.cirrus.yml\` in your repository root. \* See \[fastlane CI documentation]\[] for CI specific setup. \* Shard your script to run on both Linux and macOS platforms. \* During the setup phase of the CI task, do the following: \* Ensure Bundler is available using \`gem install bundler\`. \* Run \`bundle install\` in \`\[project]/android\` or \`\[project]/ios\`. \* Make sure the Flutter SDK is available and set in \`PATH\`. \* For Android, ensure the Android SDK is available and the \`ANDROID\_SDK\_ROOT\` path is set. \* For iOS, you might have to specify a dependency on Xcode (for example, \`osx\_image: xcode9.2\`). \* In the script phase of the CI task: \* Run \`flutter build appbundle\` or \`flutter build ios --release --no-codesign --config-only\`, depending on the platform. \* \`cd android\` or \`cd ios\` \* \`bundle exec fastlane \[name of the lane]\` ## Xcode Cloud \[Xcode Cloud]\[] is a continuous integration and delivery service for building, testing, and distributing apps and frameworks for Apple platforms. ### Requirements \* Xcode 13.4.1 or higher. \* Be enrolled in the \[Apple Developer Program]\[]. ### Custom build script Xcode Cloud recognizes \[custom build scripts]\[] that can be used to perform additional tasks at a designated time. It also includes a set of \[predefined environment variables]\[], such as \`$CI\_WORKSPACE\`, which is the location of your cloned repository. :::note The temporary build environment that Xcode Cloud uses includes tools that are part of macOS and Xcode—for example, Python—and additionally Homebrew to support installing third-party dependencies and tools. ::: #### Post-clone script Leverage the post-clone custom build script that runs after Xcode Cloud clones your Git repository using the following instructions: Create a file at \`ios/ci\_scripts/ci\_post\_clone.sh\` and add the content below.

<!--?code-excerpt "deployment/xcode_cloud/ci_post_clone.sh"?-->

\`\`\`sh #!/bin/sh # Fail this script if any subcommand fails. set -e # The default execution directory of this script is the ci\_scripts directory. cd $CI\_PRIMARY\_REPOSITORY\_PATH # change working directory to the root of your cloned repo. # Install Flutter using git. git clone https\://github.com/flutter/flutter.git --depth 1 -b stable $HOME/flutter export PATH="$PATH:$HOME/flutter/bin" # Install Flutter artifacts for iOS (--ios), or macOS (--macos) platforms. flutter precache --ios # Install Flutter dependencies. flutter pub get # Install CocoaPods using Homebrew. HOMEBREW\_NO\_AUTO\_UPDATE=1 # disable homebrew's automatic updates. brew install cocoapods # Install CocoaPods dependencies. cd ios && pod install # run \`pod install\` in the \`ios\` directory. exit 0 \`\`\` This file should be added to your git repository and marked as executable. \`\`\`console $ git add --chmod=+x ios/ci\_scripts/ci\_post\_clone.sh \`\`\` ### Workflow configuration An \[Xcode Cloud workflow]\[] defines the steps performed in the CI/CD process when your workflow is triggered. :::note This requires that your project is already initialized with Git and linked to a remote repository. ::: To create a new workflow in Xcode, use the following instructions: 1. Choose \*\*Product > Xcode Cloud > Create Workflow\*\* to open the \*\*Create Workflow\*\* sheet. 2. Select the product (app) that the workflow should be attached to, then click the \*\*Next\*\* button. 3. The next sheet displays an overview of the default workflow provided by Xcode, and can be customized by clicking the \*\*Edit Workflow\*\* button. #### Branch changes By default Xcode suggests the Branch Changes condition that starts a new build for every change to your Git repository's default branch. For your app's iOS variant, it's reasonable that you would want Xcode Cloud to trigger your workflow after you've made changes to your flutter packages, or modified either the Dart or iOS source files within the \`lib\\\` and \`ios\\\` directories. This can be achieved by using the following Files and Folders conditions: !\[Xcode Workflow Branch Changes]\(/assets/images/docs/releaseguide/xcode\_workflow\_branch\_changes.png){:width="100%"} ### Next build number Xcode Cloud defaults the build number for new workflows to \`1\` and increments it per successful build. If you're using an existing app with a higher build number, you'll need to configure Xcode Cloud to use the correct build number for its builds by simply specifying the \`Next Build Number\` in your iteration. Check out \[Setting the next build number for Xcode Cloud builds]\[] for more information. \[Android app signing steps]: /deployment/android#signing-the-app \[Appcircle]: https\://appcircle.io/blog/guide-to-automated-mobile-ci-cd-for-flutter-projects-with-appcircle/ \[Apple Developer Account console]: https\://developer.apple.com/account/ios/certificate/ \[Bitrise]: https\://devcenter.bitrise.io/en/getting-started/quick-start-guides/getting-started-with-flutter-apps \[CI Options and Examples]: #reference-and-examples \[Cirrus]: https\://cirrus-ci.org \[Codemagic]: https\://blog.codemagic.io/getting-started-with-codemagic/ \[fastlane]: https\://docs.fastlane.tools \[fastlane Android beta deployment guide]: https\://docs.fastlane.tools/getting-started/android/beta-deployment/ \[fastlane CI documentation]: https\://docs.fastlane.tools/best-practices/continuous-integration \[fastlane iOS beta deployment guide]: https\://docs.fastlane.tools/getting-started/ios/beta-deployment/ \[GitHub Action in Flutter Project]: https\://github.com/nabilnalakath/flutter-githubaction \[GitHub Actions]: https\://github.com/features/actions \[GitLab]: https\://docs.gitlab.com/ee/ci/ \[CircleCI]: https\://circleci.com \[Building and deploying Flutter apps with Fastlane]: https\://circleci.com/blog/deploy-flutter-android \[Match]: https\://docs.fastlane.tools/actions/match/ \[Supply setup steps]: https\://docs.fastlane.tools/getting-started/android/setup/#setting-up-supply \[Travis]: https\://travis-ci.org/ \[Apple Developer Program]: https\://developer.apple.com/programs \[Xcode Cloud]: https\://developer.apple.com/xcode-cloud \[Xcode Cloud workflow]: https\://developer.apple.com/documentation/xcode/xcode-cloud-workflow-reference \[custom build scripts]: https\://developer.apple.com/documentation/xcode/writing-custom-build-scripts \[predefined environment variables]: https\://developer.apple.com/documentation/xcode/environment-variable-reference \[Setting the next build number for Xcode Cloud builds]: https\://developer.apple.com/documentation/xcode/setting-the-next-build-number-for-xcode-cloud-builds#Set-the-next-build-number-to-a-custom-value

----
url: https://docs.flutter.dev/install/upgrade
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Upgrade Flutter](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

No matter which one of the Flutter release channels you follow, you can use the `flutter` command to upgrade your Flutter SDK or the packages that your app depends on.

[]()

## Upgrade the Flutter SDK

[#](#upgrade-the-flutter-sdk)

To update the Flutter SDK use the `flutter upgrade` command:

```
$ flutter upgrade
```

This command gets the most recent version of the Flutter SDK that's available on your current Flutter channel.

If you are using the **stable** channel and want an even more recent version of the Flutter SDK, switch to the **beta** channel using `flutter channel beta`, and then run `flutter upgrade`.

[]()

### Keep informed

[#](#keep-informed)

We publish [migration guides](/release/breaking-changes) for known breaking changes.

We send announcements regarding these changes to the [Flutter announcements mailing list](https://groups.google.com/forum/#!forum/flutter-announce).

To avoid being broken by future versions of Flutter, consider submitting your tests to our [test registry](https://github.com/flutter/tests).

## Switching Flutter channels

[#](#switching-flutter-channels)

Flutter has two release channels: **stable** and **beta**.

### The **stable** channel

[#](#the-stable-channel)

We recommend the **stable** channel for new users and for production app releases. The team updates this channel about every three months. The channel might receive occasional hot fixes for high-severity or high-impact issues.

The continuous integration for the Flutter team's plugins and packages includes testing against the latest **stable** release.

The latest documentation for the **stable** branch is at: <https://api.flutter.dev>

### The **beta** channel

[#](#the-beta-channel)

The **beta** channel has the latest stable release. This is the most recent version of Flutter that we have heavily tested. This channel has passed all our public testing, has been verified against test suites for Google products that use Flutter, and has been vetted against [contributed private test suites](https://github.com/flutter/tests). The **beta** channel receives regular hot fixes to address newly discovered important issues.

The **beta** channel is essentially the same as the **stable** channel but updated monthly instead of quarterly. Indeed, when the **stable** channel is updated, it is updated to the latest **beta** release.

### Other channels

[#](#other-channels)

We currently have one other channel, **main** (previously known as **master**). People who [contribute to Flutter](https://github.com/flutter/flutter/blob/main/CONTRIBUTING.md) use this channel.

This channel is not as thoroughly tested as the **beta** and **stable** channels.

We do not recommend using this channel as it is more likely to contain serious regressions.

The latest documentation for the **main** branch is at: <https://main-api.flutter.dev>

[]()

### Change channels

[#](#change-channels)

To view your current channel, use the following command:

```
$ flutter channel
```

To change to another channel, use `flutter channel <channel-name>`. Once you've changed your channel, use `flutter upgrade` to download the latest Flutter SDK and dependent packages for that channel. For example:

```
$ flutter channel beta
$ flutter upgrade
```

[]()

## Switch to a specific Flutter version

[#](#switch-to-a-specific-flutter-version)

To switch to a specific Flutter version:

1. Find your desired **Flutter version** on the [Flutter SDK archive](/install/archive).

2. Navigate to the Flutter SDK:

   ```
   $ cd /path/to/flutter
   ```

3. Use `git checkout` to switch to your desired **Flutter version**:

   ```
   $ git checkout <Flutter version>
   ```

[]()

## Upgrade packages

[#](#upgrade-packages)

If you've modified your `pubspec.yaml` file, or you want to update only the packages that your app depends upon (instead of both the packages and Flutter itself), then use one of the `flutter pub` commands.

To update to the *latest compatible versions* of all the dependencies listed in the `pubspec.yaml` file, use the `upgrade` command:

```
$ flutter pub upgrade
```

To update to the *latest possible version* of all the dependencies listed in the `pubspec.yaml` file, use the `upgrade --major-versions` command:

```
$ flutter pub upgrade --major-versions
```

This also automatically update the constraints in the `pubspec.yaml` file.

To identify out-of-date package dependencies and get advice on how to update them, use the `outdated` command. For details, see the Dart [`pub outdated` documentation](https://dart.dev/tools/pub/cmd/pub-outdated).

```
$ flutter pub outdated
```

## Troubleshooting

[#](#troubleshooting)

### Windows: "Filename too long" error

[#](#windows-filename-too-long-error)

When running `flutter upgrade` on Windows, you might encounter an error like the following:

```
error: unable to create file ...: Filename too long
```

This occurs because the path to a file in the Flutter SDK exceeds the default maximum path length limit on Windows.

To resolve this issue, consider installing the Flutter SDK at a shorter path. For example, install Flutter at `C:\Flutter` instead of something longer like `C:\Users\<user name>\Documents\flutter`.

Otherwise, do the following:

1. Enable long paths support in Git:

   ```
   $ git config --system core.longpaths true
   ```

   If the command fails with a permission error, try running your terminal as an administrator.

2. Enable long paths in Windows:

   ```
   New-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\FileSystem" -Name "LongPathsEnabled" -Value 1 -PropertyType DWORD -Force
   ```

   This command requires administrator privileges.

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-07-31. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/install/upgrade.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/install/upgrade\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/install/upgrade.md "Report an issue with this page").

----
url: https://docs.flutter.dev/cookbook/lists/horizontal-list.md
----

\# Create a horizontal list > How to implement a horizontal list.

<!--?code-excerpt path-base="cookbook/lists/horizontal_list"?-->

You might want to create a list that scrolls horizontally rather than vertically. The \[\`ListView\`]\[] widget supports horizontal lists. Use the standard \`ListView\` constructor, passing in a horizontal \`scrollDirection\`, which overrides the default vertical direction.

<!--?code-excerpt "lib/main.dart (list-view)" replace="/^child\: //g"?-->

\`\`\`dart highlightLines=2 ListView( scrollDirection: Axis.horizontal, children: \[ for (final color in Colors.primaries) Container(width: 160, color: color), ], ), \`\`\` \[\`ListView\`]: https\://api.flutter.dev/flutter/widgets/ListView-class.html ## Interactive example

<!--?code-excerpt "lib/main.dart"?-->

\`\`\`dartpad title="Flutter horizontal list hands-on example in DartPad" run="true" import 'dart:ui'; import 'package:flutter/material.dart'; void main() => runApp(const MyApp()); class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { const title = 'Horizontal list'; return MaterialApp( title: title, home: Scaffold( appBar: AppBar(title: const Text(title)), body: Container( margin: const EdgeInsets.symmetric(vertical: 20), height: 200, child: ScrollConfiguration( // Add a custom scroll behavior that // allows all devices to drag the list. behavior: const MaterialScrollBehavior().copyWith( dragDevices: {...PointerDeviceKind.values}, ), child: ListView( scrollDirection: Axis.horizontal, children: \[ for (final color in Colors.primaries) Container(width: 160, color: color), ], ), ), ), ), ); } } \`\`\`

----
url: https://docs.flutter.dev/cookbook/lists/spaced-items
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top List with spaced items](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

Perhaps you want to create a list where all list items are spaced evenly, so that the items take up the visible space. For example, the four items in the following image are spaced evenly, with "Item 0" at the top, and "Item 3" at the bottom.

At the same time, you might want to allow users to scroll through the list when the list of items won't fit, maybe because a device is too small, a user resized a window, or the number of items exceeds the screen size.

Typically, you use [`Spacer`](https://api.flutter.dev/flutter/widgets/Spacer-class.html) to tune the spacing between widgets, or [`Expanded`](https://api.flutter.dev/flutter/widgets/Expanded-class.html) to expand a widget to fill the available space. However, these solutions are not possible inside scrollable widgets, because they need a finite height constraint.

This recipe demonstrates how to use [`LayoutBuilder`](https://api.flutter.dev/flutter/widgets/LayoutBuilder-class.html) and [`ConstrainedBox`](https://api.flutter.dev/flutter/widgets/ConstrainedBox-class.html) to space out list items evenly when there is enough space, and to allow users to scroll when there is not enough space, using the following steps:

1. Add a [`LayoutBuilder`](https://api.flutter.dev/flutter/widgets/LayoutBuilder-class.html) with a [`SingleChildScrollView`](https://api.flutter.dev/flutter/widgets/SingleChildScrollView-class.html).
2. Add a [`ConstrainedBox`](https://api.flutter.dev/flutter/widgets/ConstrainedBox-class.html) inside the [`SingleChildScrollView`](https://api.flutter.dev/flutter/widgets/SingleChildScrollView-class.html).
3. Create a [`Column`](https://api.flutter.dev/flutter/widgets/Column-class.html) with spaced items.

## 1. Add a `LayoutBuilder` with a `SingleChildScrollView`

[#](#1-add-a-layoutbuilder-with-a-singlechildscrollview)

Start by creating a [`LayoutBuilder`](https://api.flutter.dev/flutter/widgets/LayoutBuilder-class.html). You need to provide a `builder` callback function with two parameters:

1. The [`BuildContext`](https://api.flutter.dev/flutter/widgets/BuildContext-class.html) provided by the [`LayoutBuilder`](https://api.flutter.dev/flutter/widgets/LayoutBuilder-class.html).
2. The [`BoxConstraints`](https://api.flutter.dev/flutter/rendering/BoxConstraints-class.html) of the parent widget.

In this recipe, you won't be using the [`BuildContext`](https://api.flutter.dev/flutter/widgets/BuildContext-class.html), but you will need the [`BoxConstraints`](https://api.flutter.dev/flutter/rendering/BoxConstraints-class.html) in the next step.

Inside the `builder` function, return a [`SingleChildScrollView`](https://api.flutter.dev/flutter/widgets/SingleChildScrollView-class.html). This widget ensures that the child widget can be scrolled, even when the parent container is too small.

dart

```
LayoutBuilder(
  builder: (context, constraints) {
    return SingleChildScrollView(child: Placeholder());
  },
);
```

## 2. Add a `ConstrainedBox` inside the `SingleChildScrollView`

[#](#2-add-a-constrainedbox-inside-the-singlechildscrollview)

In this step, add a [`ConstrainedBox`](https://api.flutter.dev/flutter/widgets/ConstrainedBox-class.html) as the child of the [`SingleChildScrollView`](https://api.flutter.dev/flutter/widgets/SingleChildScrollView-class.html).

The [`ConstrainedBox`](https://api.flutter.dev/flutter/widgets/ConstrainedBox-class.html) widget imposes additional constraints to its child.

Configure the constraint by setting the `minHeight` parameter to be the `maxHeight` of the [`LayoutBuilder`](https://api.flutter.dev/flutter/widgets/LayoutBuilder-class.html) constraints.

This ensures that the child widget is constrained to have a minimum height equal to the available space provided by the [`LayoutBuilder`](https://api.flutter.dev/flutter/widgets/LayoutBuilder-class.html) constraints, namely the maximum height of the [`BoxConstraints`](https://api.flutter.dev/flutter/rendering/BoxConstraints-class.html).

dart

```
LayoutBuilder(
  builder: (context, constraints) {
    return SingleChildScrollView(
      child: ConstrainedBox(
        constraints: BoxConstraints(minHeight: constraints.maxHeight),
        child: Placeholder(),
      ),
    );
  },
);
```

However, you don't set the `maxHeight` parameter, because you need to allow the child to be larger than the [`LayoutBuilder`](https://api.flutter.dev/flutter/widgets/LayoutBuilder-class.html) size, in case the items don't fit the screen.

## 3. Create a `Column` with spaced items

[#](#3-create-a-column-with-spaced-items)

Finally, add a [`Column`](https://api.flutter.dev/flutter/widgets/Column-class.html) as the child of the [`ConstrainedBox`](https://api.flutter.dev/flutter/widgets/ConstrainedBox-class.html).

To space the items evenly, set the `mainAxisAlignment` to `MainAxisAlignment.spaceBetween`.

dart

```
LayoutBuilder(
  builder: (context, constraints) {
    return SingleChildScrollView(
      child: ConstrainedBox(
        constraints: BoxConstraints(minHeight: constraints.maxHeight),
        child: Column(
          mainAxisAlignment: MainAxisAlignment.spaceBetween,
          children: [
            ItemWidget(text: 'Item 1'),
            ItemWidget(text: 'Item 2'),
            ItemWidget(text: 'Item 3'),
          ],
        ),
      ),
    );
  },
);
```

Alternatively, you can use the [`Spacer`](https://api.flutter.dev/flutter/widgets/Spacer-class.html) widget to tune the spacing between the items, or the [`Expanded`](https://api.flutter.dev/flutter/widgets/Expanded-class.html) widget, if you want one widget to take more space than others.

For that, you have to wrap the [`Column`](https://api.flutter.dev/flutter/widgets/Column-class.html) with an [`IntrinsicHeight`](https://api.flutter.dev/flutter/widgets/IntrinsicHeight-class.html) widget, which forces the [`Column`](https://api.flutter.dev/flutter/widgets/Column-class.html) widget to size itself to a minimum height, instead of expanding infinitely.

dart

```
LayoutBuilder(
  builder: (context, constraints) {
    return SingleChildScrollView(
      child: ConstrainedBox(
        constraints: BoxConstraints(minHeight: constraints.maxHeight),
        child: IntrinsicHeight(
          child: Column(
            children: [
              ItemWidget(text: 'Item 1'),
              Spacer(),
              ItemWidget(text: 'Item 2'),
              Expanded(child: ItemWidget(text: 'Item 3')),
            ],
          ),
        ),
      ),
    );
  },
);
```

## Interactive example

[#](#interactive-example)

This example shows a list of items that are spaced evenly within a column. The list can be scrolled up and down when the items don't fit the screen. The number of items is defined by the variable `items`, change this value to see what happens when the items won't fit the screen.

```
import 'package:flutter/material.dart';

void main() => runApp(const SpacedItemsList());

class SpacedItemsList extends StatelessWidget {
  const SpacedItemsList({super.key});

  @override
  Widget build(BuildContext context) {
    const items = 4;

    return MaterialApp(
      title: 'Flutter Demo',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        cardTheme: CardThemeData(color: Colors.blue.shade50),
      ),
      home: Scaffold(
        body: LayoutBuilder(
          builder: (context, constraints) {
            return SingleChildScrollView(
              child: ConstrainedBox(
                constraints: BoxConstraints(minHeight: constraints.maxHeight),
                child: Column(
                  mainAxisAlignment: MainAxisAlignment.spaceBetween,
                  crossAxisAlignment: CrossAxisAlignment.stretch,
                  children: List.generate(
                    items,
                    (index) => ItemWidget(text: 'Item $index'),
                  ),
                ),
              ),
            );
          },
        ),
      ),
    );
  }
}

class ItemWidget extends StatelessWidget {
  const ItemWidget({super.key, required this.text});

  final String text;

  @override
  Widget build(BuildContext context) {
    return Card(
      child: SizedBox(height: 100, child: Center(child: Text(text))),
    );
  }
}
```

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/lists/spaced-items.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/cookbook/lists/spaced-items\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/lists/spaced-items.md "Report an issue with this page").

----
url: https://docs.flutter.dev/ui/layout/scrolling/slivers
----

A sliver is a portion of a scrollable area that you can define to behave in a special way. You can use slivers to achieve custom scrolling effects, such as elastic scrolling.

For a free, instructor-led video workshop that uses DartPad, check out the following video about using slivers.

[Watch on YouTube in a new tab: "Building scrolling experiences in Flutter"](https://www.youtube.com/watch/YY-_yrZdjGc)

## Resources

[#](#resources)

For more information on implementing fancy scrolling effects in Flutter, see the following resources:

* **[Slivers, Demystified](https://blog.flutter.dev/slivers-demystified-6ff68ab0296f)**

  A Flutter blog post that explains how to implement custom scrolling using the sliver classes.

* **[SliverAppBar](https://www.youtube.com/watch?v=R9C5KMJKluE)**

  A one-minute Widget-of-the-week video that gives an overview of the `SliverAppBar` widget.

  [Watch on YouTube in a new tab: "SliverAppBar | Flutter widget of the week"](https://www.youtube.com/watch/R9C5KMJKluE)

* **[SliverList and SliverGrid](https://www.youtube.com/watch?v=ORiTTaVY6mM)**

  A one-minute Widget-of-the-week video that gives an overview of the `SliverList` and `SliverGrid` widgets.

  [Watch on YouTube in a new tab: "SliverList & SliverGrid | Flutter widget of the week"](https://www.youtube.com/watch/ORiTTaVY6mM)

* **[Slivers explained - Making dynamic layouts](https://www.youtube.com/watch?v=Mz3kHQxBjGg)**

  A 50-minute episode of [The Boring Show](https://www.youtube.com/playlist?list=PLOU2XLYxmsIK0r_D-zWcmJ1plIcDNnRkK) where Ian Hickson, Flutter's Tech Lead, and Filip Hracek discuss the power of slivers.

  [Watch on YouTube in a new tab: "Slivers explained - Making dynamic layouts"](https://www.youtube.com/watch/Mz3kHQxBjGg)

## API docs

[#](#api-docs)

To learn more about the available sliver APIs, check out these related API docs:

* [`CustomScrollView`](https://api.flutter.dev/flutter/widgets/CustomScrollView-class.html)
* [`SliverAppBar`](https://pub.dev/documentation/material_ui/latest/material_ui/SliverAppBar-class.html)
* [`SliverGrid`](https://api.flutter.dev/flutter/widgets/SliverGrid-class.html)
* [`SliverList`](https://api.flutter.dev/flutter/widgets/SliverList-class.html)

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-09-29. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/ui/layout/scrolling/slivers.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/ui/layout/scrolling/slivers\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/ui/layout/scrolling/slivers.md "Report an issue with this page").

----
url: https://docs.flutter.dev/add-to-app/ios/project-setup-legacy
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Integrate a Flutter module into your iOS project (Legacy)](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

Flutter UI components can be incrementally added into your existing iOS application as embedded frameworks. To embed Flutter in your existing application, consider one of the following three methods.

| Embedding Method                                                                                                                               | Methodology                                                                                                                          | Benefit                                                                                             |
| ---------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------- |
| Use CocoaPods *(Recommended)*                                                                                                                  | Install and use the Flutter SDK and CocoaPods. Flutter compiles the `flutter_module` from source each time Xcode builds the iOS app. | Least complicated method to embed Flutter into your app.                                            |
| Use [iOS frameworks](https://developer.apple.com/library/archive/documentation/MacOSX/Conceptual/BPFrameworks/Concepts/WhatAreFrameworks.html) | Create iOS frameworks for Flutter components, embed them into your iOS, and update your existing app's build settings.               | Doesn't require every developer to install the Flutter SDK and CocoaPods on their local machines.   |
| Use iOS frameworks and CocoaPods                                                                                                               | Embed the frameworks for your iOS app and the plugins in Xcode, but distribute the Flutter engine as a CocoaPods podspec.            | Provides an alternative to distributing the large Flutter engine ( `Flutter.xcframework` ) library. |

When you add Flutter to your existing iOS app, it [increases the size of your iOS app](/resources/faq#how-big-is-the-flutter-engine).

For examples using an app built with UIKit, see the iOS directories in the [add\_to\_app code samples](https://github.com/flutter/samples/tree/main/add_to_app). For an example using SwiftUI, consult the iOS directory in [News Feed App](https://github.com/flutter/put-flutter-to-work/tree/022208184ec2623af2d113d13d90e8e1ce722365).

## Development system requirements

[#](#development-system-requirements)

Flutter requires the latest version of Xcode and [CocoaPods](https://cocoapods.org/).

## Create a Flutter module

[#](#create-a-flutter-module)

To embed Flutter into your existing application with any method, create a Flutter module first. Use the following command to create a Flutter module.

```
$ cd /path/to/my_flutter
$ flutter create --template module my_flutter
```

Flutter creates module project under `/path/to/my_flutter/`. If you use the [CocoaPods method](/add-to-app/ios/project-setup-legacy/?tab=embed-using-cocoapods), save the module in the same parent directory as your existing iOS app.

From the Flutter module directory, you can run the same `flutter` commands you would in any other Flutter project, like `flutter run` or `flutter build ios`. You can also run the module in [VS Code](/tools/vs-code) or [Android Studio/IntelliJ](/tools/android-studio) with the Flutter and Dart plugins. This project contains a single-view example version of your module before embedding it in your existing iOS app. This helps when testing the Flutter-only parts of your code.

## Organize your module

[#](#organize-your-module)

The `my_flutter` module directory structure resembles a typical Flutter app.

* my\_flutter/

  * .ios/

    * Runner.xcworkspace

    * Flutter/

      * podhelper.rb

  * lib/

    * main.dart

  * test/

  * pubspec.yaml

Your Dart code should be added to the `lib/` directory. Your Flutter dependencies, packages, and plugins must be added to the `pubspec.yaml` file.

The `.ios/` hidden subfolder contains an Xcode workspace where you can run a standalone version of your module. This wrapper project bootstraps your Flutter code. It contains helper scripts to facilitate building frameworks or embedding the module into your existing application with CocoaPods.

## Embed a Flutter module in your iOS app

[#](#embed-a-flutter-module-in-your-ios-app)

After you have developed your Flutter module, you can embed it using the methods described in the table at the top of the page.

You can run in **Debug** mode on a simulator or a real device, and **Release** mode on a real device.

* [Use CocoaPods](#121-tab-panel)
* [Use frameworks](#122-tab-panel)
* [Use frameworks and CocoaPods](#123-tab-panel)

### Use CocoaPods and the Flutter SDK

[#](#method-a)

#### Approach

[#](#method-a-approach)

This first method uses CocoaPods to embed the Flutter modules. CocoaPods manages dependencies for Swift projects, including Flutter code and plugins. Each time Xcode builds the app, CocoaPods embeds the Flutter modules.

This allows rapid iteration with the most up-to-date version of your Flutter module without running additional commands outside of Xcode.

To learn more about CocoaPods, consult the [CocoaPods getting started guide](https://guides.cocoapods.org/using/using-cocoapods.html).

#### Requirements

[#](#method-a-reqs)

Every developer working on your project must have a local version of the Flutter SDK and CocoaPods installed.

#### Example project structure

[#](#method-a-structure)

This section assumes that your existing app and the Flutter module reside in sibling directories. If you have a different directory structure, adjust the relative paths. The example directory structure resembles the following:

* my\_flutter/

  * .ios/

  * Flutter/

    * podhelper.rb

* MyApp/

  * Podfile

#### Update your Podfile

[#](#update-your-podfile)

Add your Flutter modules to your Podfile configuration file. This section presumes you called your Swift app `MyApp`.

1. *(Optional)* If your existing app lacks a `Podfile` config file, navigate to the root of your app directory. Use the `pod init` command to create the `Podfile` file.

2. Update your `Podfile` config file.

   1. Add the following lines after the `platform` declaration.

      MyApp/Podfile

      ruby

      ```
      flutter_application_path = '../my_flutter'
      load File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb')
      ```

   2. For each [Podfile target](https://guides.cocoapods.org/syntax/podfile.html#target) that needs to embed Flutter, add a call to the `install_all_flutter_pods(flutter_application_path)` method. Add these calls after the settings in the previous step.

      MyApp/Podfile

      ruby

      ```
      target 'MyApp' do
        install_all_flutter_pods(flutter_application_path)
      end
      ```

   3. In the `Podfile`'s `post_install` block, add a call to `flutter_post_install(installer)`. This block should be the last block in the `Podfile` config file.

      MyApp/Podfile

      ruby

      ```
      post_install do |installer|
        flutter_post_install(installer) if defined?(flutter_post_install)
      end
      ```

To review an example `Podfile`, consult this [Flutter Podfile sample](https://github.com/flutter/samples/blob/main/add_to_app/plugin/ios_using_plugin/Podfile).

#### Embed your frameworks

[#](#embed-your-frameworks)

At build time, Xcode packages your Dart code, each Flutter plugin, and the Flutter engine into their own `*.xcframework` bundles. CocoaPod's `podhelper.rb` script then embeds these `*.xcframework` bundles into your project.

* `Flutter.xcframework` contains the Flutter engine.
* `App.xcframework` contains the compiled Dart code for this project.
* `<plugin>.xcframework` contains one Flutter plugin.

To embed the Flutter engine, your Dart code, and your Flutter plugins into your iOS app, complete the following procedure.

1. Refresh your Flutter plugins.

   If you change the Flutter dependencies in the `pubspec.yaml` file, run `flutter pub get` in your Flutter module directory. This refreshes the list of plugins that the `podhelper.rb` script reads.

   ```
   flutter pub get
   ```

2. Embed the plugins and frameworks with CocoaPods.

   1. Navigate to your iOS app project at `/path/to/MyApp/MyApp`.

   2. Use the `pod install` command.

      ```
      pod install
      ```

   Your iOS app's **Debug** and **Release** build configurations embed the corresponding [Flutter components for that build mode](/testing/build-modes).

3. Build the project.

   1. Open `MyApp.xcworkspace` in Xcode.

      Verify that you're opening `MyApp.xcworkspace` and not opening `MyApp.xcodeproj`. The `.xcworkspace` file has the CocoaPod dependencies, the `.xcodeproj` doesn't.

   2. Select **Product** > **Build** or press `Cmd` + `B`.

#### Set LLDB Init File

[#](#set-lldb-init-file)

1. Generate Flutter LLDB files.

   1. Within your flutter application, run the following:

   ```
   flutter build ios --config-only
   ```

   This will generate the LLDB files in the `.ios/Flutter/ephemeral` directory.

2. Set the LLDB Init File.

   1. Go to **Product > Scheme > Edit Scheme**.

   2. Select the **Run** section in the left side bar.

   3. Set the **LLDB Init File** using the same relative path to your Flutter application as you put in your Podfile in the **Update your Podfile** section.

      ```
      $(SRCROOT)/../my_flutter/.ios/Flutter/ephemeral/flutter_lldbinit
      ```

      If your scheme already has an **LLDB Init File**, you can add Flutter's LLDB file to it. The path to Flutter's LLDB Init File must be relative to the location of your project's LLDB Init File.

      For example, if your LLDB file is located at `/path/to/MyApp/.lldbinit`, you would add the following:

      ```
      command source --relative-to-command-file "../my_flutter/.ios/Flutter/ephemeral/flutter_lldbinit"
      ```

### Link and Embed frameworks in Xcode

[#](#method-b)

#### Approach

[#](#method-b-approach)

In this second method, edit your existing Xcode project, generate the necessary frameworks, and embed them in your app. Flutter generates iOS frameworks for Flutter itself, for your compiled Dart code, and for each of your Flutter plugins. Embed these frameworks and update your existing application's build settings.

#### Requirements

[#](#method-b-reqs)

No additional software or hardware requirements are needed for this method. Use this method in the following use cases:

* Members of your team can't install the Flutter SDK and CocoaPods
* You don't want to use CocoaPods as a dependency manager in existing iOS apps

#### Limitations

[#](#method-b-limits)

Flutter can't handle [common dependencies with xcframeworks](https://github.com/flutter/flutter/issues/130220). If both the host app and the Flutter module's plugin define the same pod dependency and you integrate Flutter module using this option, errors result. These errors include issues like `Multiple commands produce 'CommonDependency.framework'`.

To work around this issue, link every plugin source in its `podspec` file from the Flutter module to the host app's `Podfile`. Link the source instead of the plugins' `xcframework` framework. The next section explains how to [produce that framework](https://github.com/flutter/flutter/issues/114692).

To prevent the error that occurs when common dependencies exist, use `flutter build ios-framework` with the `--no-plugins` flag.

#### Example project structure

[#](#method-b-structure)

The following example assumes that you want to generate the frameworks to `/path/to/MyApp/Flutter/`.

```
$ flutter build ios-framework --output=/path/to/MyApp/Flutter/
```

Run this *every time* you change code in your Flutter module.

The resulting project structure should resemble this directory tree.

* /path/to/MyApp/

  * Flutter/

    * Debug/

      * Flutter.xcframework
      * App.xcframework
      * FlutterPluginRegistrant.xcframework(If you have plugins with iOS-platform code)
      * example\_plugin.xcframework(One framework file for each plugin)

    * Profile/

      * Flutter.xcframework
      * App.xcframework
      * FlutterPluginRegistrant.xcframework
      * example\_plugin.xcframework

    * Release/

      * Flutter.xcframework
      * App.xcframework
      * FlutterPluginRegistrant.xcframework
      * example\_plugin.xcframework

#### Procedures

[#](#procedures)

How you link, embed, or both the generated frameworks into your existing app in Xcode depends on the type of framework.

* Link and embed dynamic frameworks.
* Link static frameworks. [Never embed them](https://developer.apple.com/library/archive/technotes/tn2435/_index.html).

Flutter plugins might produce [static or dynamic frameworks](https://stackoverflow.com/questions/32591878/ios-is-it-a-static-or-a-dynamic-framework). Link static frameworks, [*never* embed them](https://developer.apple.com/library/archive/technotes/tn2435/_index.html).

If you embed a static framework into your iOS app, you can't publish that app to the App Store. Publishing fails with a `Found an unexpected Mach-O header code` archive error.

##### Link all frameworks

[#](#link-all-frameworks)

To link the necessary frameworks, follow this procedure.

1. Choose the frameworks to link.

   1. In the **Project Navigator**, click on your project.

   2. Click the **Build Phases** tab.

   3. Expand **Link Binary With Libraries**.

      Expand the **Link Binary With Libraries** build phase in Xcode

   4. Click **+** (plus sign).

   5. Click **Add Other...** then **Add Files...**.

   6. From the **Choose frameworks and libraries to add:** dialog box, navigate to the `/path/to/MyApp/Flutter/Release/` directory.

   7. Command-click the frameworks in that directory then click **Open**.

      Choose frameworks to link from the **Choose frameworks and libraries to add:** dialog box in Xcode

2. Update the paths to the libraries to account for build modes.

   1. Launch the Finder.

   2. Navigate to the `/path/to/MyApp/` directory.

   3. Right-click on `MyApp.xcodeproj` and select **Show Package Contents**.

   4. Open `project.pbxproj` with Xcode. The file opens in Xcode's text editor. This also locks **Project Navigator** until you close the text editor.

      The `project-pbxproj` file open in the Xcode text editor

   5. Find the lines that resemble the following text in the `/* Begin PBXFileReference section */`.

      ```
      312885572C1A441C009F74FF /* Flutter.xcframework */ = {
        isa = PBXFileReference;
        expectedSignature = "AppleDeveloperProgram:S8QB4VV633:FLUTTER.IO LLC";
        lastKnownFileType = wrapper.xcframework;
        name = Flutter.xcframework;
        path = Flutter/Release/Flutter.xcframework;
        sourceTree = "<group>";
      };
      312885582C1A441C009F74FF /* App.xcframework */ = {
        isa = PBXFileReference;
        lastKnownFileType = wrapper.xcframework;
        name = App.xcframework;
        path = Flutter/Release/App.xcframework;
        sourceTree = "<group>";
      };
      ```

   6. Change the `Release` text highlighted in the prior step and change it to `$(CONFIGURATION)`. Also wrap the path in quotation marks.

      ```
      312885572C1A441C009F74FF /* Flutter.xcframework */ = {
        isa = PBXFileReference;
        expectedSignature = "AppleDeveloperProgram:S8QB4VV633:FLUTTER.IO LLC";
        lastKnownFileType = wrapper.xcframework;
        name = Flutter.xcframework;
        path = "Flutter/$(CONFIGURATION)/Flutter.xcframework";
        sourceTree = "<group>";
      };
      312885582C1A441C009F74FF /* App.xcframework */ = {
        isa = PBXFileReference;
        lastKnownFileType = wrapper.xcframework;
        name = App.xcframework;
        path = "Flutter/$(CONFIGURATION)/App.xcframework";
        sourceTree = "<group>";
      };
      ```

3. Update the search paths.

   1. Click the **Build Settings** tab.

   2. Navigate to **Search Paths**

   3. Double-click to the right of **Framework Search Paths**.

   4. In the combo box, click **+** (plus sign).

   5. Type `$(inherited)`. and press `Enter`.

   6. Click **+** (plus sign).

   7. Type `$(PROJECT_DIR)/Flutter/$(CONFIGURATION)/` and press `Enter`.

      Update **Framework Search Paths** in Xcode

After linking the frameworks, they should display in the **Frameworks, Libraries, and Embedded Content** section of your target's **General** settings.

##### Embed the dynamic frameworks

[#](#embed-the-dynamic-frameworks)

To embed your dynamic frameworks, complete the following procedure.

1. Navigate to **General** > **Frameworks, Libraries, and Embedded Content**.

2. Click on each of your dynamic frameworks and select **Embed & Sign**.

   Select **Embed & Sign** for each of your frameworks in Xcode

   Don't include any static frameworks, including `FlutterPluginRegistrant.xcframework`.

3. Click the **Build Phases** tab.

4. Expand **Embed Frameworks**. Your dynamic frameworks should display in that section.

   The expanded **Embed Frameworks** build phase in Xcode

5. Build the project.

   1. Open `MyApp.xcworkspace` in Xcode.

      Verify that you're opening `MyApp.xcworkspace` and not opening `MyApp.xcodeproj`. The `.xcworkspace` file has the CocoaPod dependencies, the `.xcodeproj` doesn't.

   2. Select **Product** > **Build** or press `Cmd` + `B`.

#### Set LLDB Init File

[#](#set-lldb-init-file-1)

1. Generate Flutter LLDB files.

   1. Within your flutter application, re-run `flutter build ios-framework` if you haven't already:

   ```
   $ flutter build ios-framework --output=/path/to/MyApp/Flutter/
   ```

   This will generate the LLDB files in the `/path/to/MyApp/Flutter/` directory.

2. Set the LLDB Init File.

   1. Go to **Product > Scheme > Edit Scheme**.

   2. Select the **Run** section in the left side bar.

   3. Set the **LLDB Init File** to the following:

      ```
      $(PROJECT_DIR)/Flutter/flutter_lldbinit
      ```

      If your scheme already has an **LLDB Init File**, you can add Flutter's LLDB file to it. The path to Flutter's LLDB Init File must be relative to the location of your project's LLDB Init File.

      For example, if your LLDB file is located at `/path/to/MyApp/.lldbinit`, you would add the following:

      ```
      command source --relative-to-command-file "Flutter/flutter_lldbinit"
      ```

### Use frameworks in Xcode and Flutter framework as podspec

[#](#method-c)

#### Approach

[#](#method-c-approach)

This method generates Flutter as a CocoaPods podspec instead of distributing the large `Flutter.xcframework` to other developers, machines, or continuous integration systems. Flutter still generates iOS frameworks for your compiled Dart code, and for each of your Flutter plugins. Embed these frameworks and update your existing application's build settings.

#### Requirements

[#](#method-c-reqs)

No additional software or hardware requirements are needed for this method. Use this method in the following use cases:

* Members of your team can't install the Flutter SDK and CocoaPods
* You don't want to use CocoaPods as a dependency manager in existing iOS apps

#### Limitations

[#](#method-c-limits)

Flutter can't handle [common dependencies with xcframeworks](https://github.com/flutter/flutter/issues/130220). If both the host app and the Flutter module's plugin define the same pod dependency and you integrate Flutter module using this option, errors result. These errors include issues like `Multiple commands produce 'CommonDependency.framework'`.

To work around this issue, link every plugin source in its `podspec` file from the Flutter module to the host app's `Podfile`. Link the source instead of the plugins' `xcframework` framework. The next section explains how to [produce that framework](https://github.com/flutter/flutter/issues/114692).

To prevent the error that occurs when common dependencies exist, use `flutter build ios-framework` with the `--no-plugins` flag.

This method only works with the `beta` or `stable` [release channels](/install/upgrade#switching-flutter-channels).

#### Example project structure

[#](#method-c-structure)

The following example assumes that you want to generate the frameworks to `/path/to/MyApp/Flutter/`.

```
$ flutter build ios-framework --output=/path/to/MyApp/Flutter/
```

Run this *every time* you change code in your Flutter module.

The resulting project structure should resemble this directory tree.

* /path/to/MyApp/

  * Flutter/

    * Debug/

      * Flutter.xcframework
      * App.xcframework
      * FlutterPluginRegistrant.xcframework(If you have plugins with iOS-platform code)
      * example\_plugin.xcframework(One framework file for each plugin)

    * Profile/

      * Flutter.xcframework
      * App.xcframework
      * FlutterPluginRegistrant.xcframework
      * example\_plugin.xcframework

    * Release/

      * Flutter.xcframework
      * App.xcframework
      * FlutterPluginRegistrant.xcframework
      * example\_plugin.xcframework

#### Add Flutter engine to your Podfile

[#](#add-flutter-engine-to-your-podfile)

Host apps using CocoaPods can add the Flutter engine to their Podfile.

MyApp/Podfile

ruby

```
pod 'Flutter', :podspec => '/path/to/MyApp/Flutter/[build mode]/Flutter.podspec'
```

#### Link and embed app and plugin frameworks

[#](#link-and-embed-app-and-plugin-frameworks)

Flutter plugins might produce [static or dynamic frameworks](https://stackoverflow.com/questions/32591878/ios-is-it-a-static-or-a-dynamic-framework). Link static frameworks, [*never* embed them](https://developer.apple.com/library/archive/technotes/tn2435/_index.html).

If you embed a static framework into your iOS app, you can't publish that app to the App Store. Publishing fails with a `Found an unexpected Mach-O header code` archive error.

##### Link all frameworks

[#](#link-all-frameworks-1)

To link the necessary frameworks, follow this procedure.

1. Choose the frameworks to link.

   1. In the **Project Navigator**, click on your project.

   2. Click the **Build Phases** tab.

   3. Expand **Link Binary With Libraries**.

      Expand the **Link Binary With Libraries** build phase in Xcode

   4. Click **+** (plus sign).

   5. Click **Add Other...** then **Add Files...**.

   6. From the **Choose frameworks and libraries to add:** dialog box, navigate to the `/path/to/MyApp/Flutter/Release/` directory.

   7. Command-click the frameworks in that directory then click **Open**.

      Choose frameworks to link from the **Choose frameworks and libraries to add:** dialog box in Xcode

2. Update the paths to the libraries to account for build modes.

   1. Launch the Finder.

   2. Navigate to the `/path/to/MyApp/` directory.

   3. Right-click on `MyApp.xcodeproj` and select **Show Package Contents**.

   4. Open `project.pbxproj` with Xcode. The file opens in Xcode's text editor. This also locks **Project Navigator** until you close the text editor.

      The `project-pbxproj` file open in the Xcode text editor

   5. Find the lines that resemble the following text in the `/* Begin PBXFileReference section */`.

      ```
      312885572C1A441C009F74FF /* Flutter.xcframework */ = {
        isa = PBXFileReference;
        expectedSignature = "AppleDeveloperProgram:S8QB4VV633:FLUTTER.IO LLC";
        lastKnownFileType = wrapper.xcframework;
        name = Flutter.xcframework;
        path = Flutter/Release/Flutter.xcframework;
        sourceTree = "<group>";
      };
      312885582C1A441C009F74FF /* App.xcframework */ = {
        isa = PBXFileReference;
        lastKnownFileType = wrapper.xcframework;
        name = App.xcframework;
        path = Flutter/Release/App.xcframework;
        sourceTree = "<group>";
      };
      ```

   6. Change the `Release` text highlighted in the prior step and change it to `$(CONFIGURATION)`. Also wrap the path in quotation marks.

      ```
      312885572C1A441C009F74FF /* Flutter.xcframework */ = {
        isa = PBXFileReference;
        expectedSignature = "AppleDeveloperProgram:S8QB4VV633:FLUTTER.IO LLC";
        lastKnownFileType = wrapper.xcframework;
        name = Flutter.xcframework;
        path = "Flutter/$(CONFIGURATION)/Flutter.xcframework";
        sourceTree = "<group>";
      };
      312885582C1A441C009F74FF /* App.xcframework */ = {
        isa = PBXFileReference;
        lastKnownFileType = wrapper.xcframework;
        name = App.xcframework;
        path = "Flutter/$(CONFIGURATION)/App.xcframework";
        sourceTree = "<group>";
      };
      ```

3. Update the search paths.

   1. Click the **Build Settings** tab.

   2. Navigate to **Search Paths**

   3. Double-click to the right of **Framework Search Paths**.

   4. In the combo box, click **+** (plus sign).

   5. Type `$(inherited)`. and press `Enter`.

   6. Click **+** (plus sign).

   7. Type `$(PROJECT_DIR)/Flutter/$(CONFIGURATION)/` and press `Enter`.

      Update **Framework Search Paths** in Xcode

After linking the frameworks, they should display in the **Frameworks, Libraries, and Embedded Content** section of your target's **General** settings.

##### Embed the dynamic frameworks

[#](#embed-the-dynamic-frameworks-1)

To embed your dynamic frameworks, complete the following procedure.

1. Navigate to **General** > **Frameworks, Libraries, and Embedded Content**.

2. Click on each of your dynamic frameworks and select **Embed & Sign**.

   Select **Embed & Sign** for each of your frameworks in Xcode

   Don't include any static frameworks, including `FlutterPluginRegistrant.xcframework`.

3. Click the **Build Phases** tab.

4. Expand **Embed Frameworks**. Your dynamic frameworks should display in that section.

   The expanded **Embed Frameworks** build phase in Xcode

5. Build the project.

   1. Open `MyApp.xcworkspace` in Xcode.

      Verify that you're opening `MyApp.xcworkspace` and not opening `MyApp.xcodeproj`. The `.xcworkspace` file has the CocoaPod dependencies, the `.xcodeproj` doesn't.

   2. Select **Product** > **Build** or press `Cmd` + `B`.

#### Set LLDB Init File

[#](#set-lldb-init-file-2)

1. Generate Flutter LLDB files.

   1. Within your flutter application, re-run `flutter build ios-framework` if you haven't already:

   ```
   $ flutter build ios-framework --output=/path/to/MyApp/Flutter/
   ```

   This will generate the LLDB files in the `/path/to/MyApp/Flutter/` directory.

2. Set the LLDB Init File.

   1. Go to **Product > Scheme > Edit Scheme**.

   2. Select the **Run** section in the left side bar.

   3. Set the **LLDB Init File** to the following:

      ```
      $(PROJECT_DIR)/Flutter/flutter_lldbinit
      ```

      If your scheme already has an **LLDB Init File**, you can add Flutter's LLDB file to it. The path to Flutter's LLDB Init File must be relative to the location of your project's LLDB Init File.

      For example, if your LLDB file is located at `/path/to/MyApp/.lldbinit`, you would add the following:

      ```
      command source --relative-to-command-file "Flutter/flutter_lldbinit"
      ```

## Set local network privacy permissions

[#](#set-local-network-privacy-permissions)

      The `Info-Debug` property list with the **Bonjour Services** and **Privacy - Local Network Usage Description** keys added

      Updating the `Info.plist` build setting to use build mode-specific property lists

      This resolves to the path **Info-Debug.plist** in **Debug** and **Info-Release.plist** in **Release**.

      The updated **Info.plist File** build setting displaying the configuration variations

6. Remove the **Release** property list from the **Build Phases**.

   1. In the **Project Navigator**, click on your project.

   2. Click the **Build Phases** tab.

   3. Expand **Copy Bundle Resources**.

   4. If this list includes `Info-Release.plist`, click on it and then click the **-** (minus sign) under it to remove the property list from the resources list.

      The **Copy Bundle** build phase displaying the **Info-Release.plist** setting. Remove this setting.

7. The first Flutter screen your Debug app loads prompts for local network permission.

   Click **OK**.

   *(Optional)* To grant permission before the app loads, enable **Settings > Privacy > Local Network > Your App**.

## Mitigate known issue with Apple Silicon Macs

[#](#mitigate-known-issue-with-apple-silicon-macs)

On [Macs running Apple Silicon](https://support.apple.com/en-us/116943), the host app builds for an `arm64` simulator. While Flutter supports `arm64` simulators, some plugins might not. If you use one of these plugins, you might see a compilation error like **Undefined symbols for architecture arm64**. If this occurs, exclude `arm64` from the simulator architectures in your host app.

1. In the **Project Navigator**, click on your project.

2. Click the **Build Settings** tab.

3. Click **All** and **Combined** sub-tabs.

4. Under **Architectures**, click on **Excluded Architectures**.

5. Expand to see the available build configurations.

6. Click **Debug**.

7. Click the **+** (plus sign).

8. Select **iOS Simulator**.

9. Double-click in the value column for **Any iOS Simulator SDK**.

10. Click the **+** (plus sign).

11. Type `arm64` in the **Debug > Any iOS Simulator SDK** dialog box.

    Add `arm64` as an excluded architecture for your app

12. Press `Esc` to close this dialog box.

13. Repeat these steps for the **Release** build mode.

14. Repeat for any iOS unit test targets.

## Next steps

[#](#next-steps)

You can now [add a Flutter screen](/add-to-app/ios/add-flutter-screen) to your existing iOS app.

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-07-31. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/add-to-app/ios/project-setup-legacy.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/add-to-app/ios/project-setup-legacy\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/add-to-app/ios/project-setup-legacy.md "Report an issue with this page").

----
url: https://docs.flutter.dev/ui/adaptive-responsive/best-practices.md
----

\# Best practices for adaptive design > Summary of some of the best practices for adaptive design. Recommended best practices for adaptive design include: ## Design considerations ### Break down your widgets While designing your app, try to break down large, complex widgets into smaller, simpler ones. Refactoring widgets can reduce the complexity of adopting an adaptive UI by sharing core pieces of code. There are other benefits as well: \* On the performance side, having lots of small \`const\` widgets improves rebuild times over having large, complex widgets. \* Flutter can reuse \`const\` widget instances, while a larger complex widget has to be set up for every rebuild. \* From a code health perspective, organizing your UI into smaller bite sized pieces helps keep the complexity of each \`Widget\` down. A less-complex \`Widget\` is more readable, easier to refactor, and less likely to have surprising behavior. To learn more, check out the 3 steps of adaptive design in \[General approach]\[]. \[General approach]: /ui/adaptive-responsive/general ### Design to the strengths of each form factor Beyond screen size, you should also spend time considering the unique strengths and weaknesses of different form factors. It isn't always ideal for your multiplatform app to offer identical functionality everywhere. Consider whether it makes sense to focus on specific capabilities, or even remove certain features, on some device categories. For example, mobile devices are portable and have cameras, but they aren't well suited for detailed creative work. With this in mind, you might focus more on capturing content and tagging it with location data for a mobile UI, but focus on organizing or manipulating that content for a tablet or desktop UI. Another example is leveraging the web's extremely low barrier for sharing. If you're deploying a web app, decide which \[deep links]\[] to support, and design your navigation routes with those in mind. The key takeaway here is to think about what each platform does best and see if there are unique capabilities you can leverage. \[deep links]: /ui/navigation/deep-linking ### Solve touch first Building a great touch UI can often be more difficult than a traditional desktop UI due, in part, to the lack of input accelerators like right-click, scroll wheel, or keyboard shortcuts. One way to approach this challenge is to focus initially on a great touch-oriented UI. You can still do most of your testing using the desktop target for its iteration speed. But, remember to switch frequently to a mobile device to verify that everything feels right. After you have the touch interface polished, you can tweak the visual density for mouse users, and then layer on all the additional inputs. Approach these other inputs as accelerator—alternatives that make a task faster. The important thing to consider is what a user expects when using a particular input device, and work to reflect that in your app. ## Implementation details ### Don't lock the orientation of your app. An adaptive app should look good on windows of different sizes and shapes. While locking an app to portrait mode on phones can help narrow the scope of a minimum viable product, it can increase the effort required to make the app adaptive in the future. For example, the assumption that phones will only render your app in a full screen portrait mode is not a guarantee. Multi window app support is becoming common, and foldables have many use cases that work best with multiple apps running side by side. If you absolutely must lock your app in portrait mode (but don't), use the \`Display\` API instead of something like \`MediaQuery\` to get the physical dimensions of the screen. To summarize: \* Locked screens can be \[an accessibility issue]\[] for some users \* Android large format tiers require portrait and landscape support at the \[lowest level]\[]. \* Android devices can \[override a locked screen]\[] \* Apple guidelines say \[aim to support both orientations]\[] \[an accessibility issue]: https\://www\.w3.org/WAI/WCAG21/Understanding/orientation.html \[aim to support both orientations]: https\://www\.w3.org/WAI/WCAG21/Understanding/orientation.html \[lowest level]: https\://developer.android.com/docs/quality-guidelines/large-screen-app-quality#T3-8 \[override a locked screen]: https\://developer.android.com/guide/topics/large-screens/large-screen-compatibility-mode#per-app\_overrides ### Avoid device orientation-based layouts Avoid using \`MediaQuery\`'s orientation field or \`OrientationBuilder\` near the top of your widget tree to switch between different app layouts. This is similar to the guidance of not checking device types to determine screen size. The device's orientation also doesn't necessarily inform you of how much space your app window has. Instead, use \`MediaQuery\`'s \`sizeOf\` or \`LayoutBuilder\`, as discussed in the \[General approach]\[] page. Then use adaptive breakpoints like the ones that \[Material]\[] recommends. \[General approach]: /ui/adaptive-responsive/general# \[Material]: https\://m3.material.io/foundations/layout/applying-layout/window-size-classes ### Don't gobble up all of the horizontal space Apps that use the full width of the window to display boxes or text fields don't play well when these apps run on large screens. To learn how to avoid this, check out \[Layout with GridView]\[]. \[Layout with GridView]: /ui/adaptive-responsive/large-screens#layout-with-gridview ### Avoid checking for hardware types Avoid writing code that checks whether the device you're running on is a "phone" or a "tablet", or any other type of device when making layout decisions. What space your app is actually given to render in isn't always tied to the full screen size of the device. Flutter can run on many different platforms, and your app might be running in a resizeable window on ChromeOS, side by side with another app on tablets in a multi-window mode, or even in a picture-in-picture on phones. Therefore, device type and app window size aren't really strongly connected. Instead, use \`MediaQuery\` to get the size of the window your app is currently running in. This isn't only helpful for UI code. To learn how abstracting out device capabilities can help your business logic code, check out the 2022 Google I/O talk, \[Flutter lessons for federated plugin development]\[]. \[Flutter lessons for federated plugin development]: https\://www\.youtube.com/watch?v=GAnSNplNpCA ### Support a variety of input devices Apps should support basic mice, trackpads, and keyboard shortcuts. The most common user flows should support keyboard navigation to ensure accessibility. In particular, your app follow accessible best practices for keyboards on large devices. The Material library provides widgets with excellent default behavior for touch, mouse, and keyboard interaction. To learn how to add this support to custom widgets, check out \[User input & accessibility]\[]. \[User input & accessibility]: /ui/adaptive-responsive/input ### Restore List state To maintain the scroll position in a list that doesn't change its layout when the device's orientation changes, use the \[\`PageStorageKey\`]\[] class. \[\`PageStorageKey\`]\[] persists the widget state in storage after the widget is destroyed and restores state when recreated. You can see an example of this in the \[Wonderous app]\[], where it stores the list's state in the \`SingleChildScrollView\` widget. If the \`List\` widget changes its layout when the device's orientation changes, you might have to do a bit of math (\[example]\[]) to change the scroll position on screen rotation. \[example]: https\://github.com/gskinnerTeam/flutter-wonderous-app/blob/34e49a08084fbbe69ed67be948ab00ef23819313/lib/ui/screens/collection/widgets/\_collection\_list.dart#L39 \[\`PageStorageKey\`]: https\://api.flutter.dev/flutter/widgets/PageStorageKey-class.html \[Wonderous app]: https\://github.com/gskinnerTeam/flutter-wonderous-app/blob/8a29d6709668980340b1b59c3d3588f123edd4d8/lib/ui/screens/wonder\_events/widgets/\_events\_list.dart#L64 ## Save app state Apps should retain or restore \[app state]\[] as the device rotates, changes window size, or folds and unfolds. By default, an app should maintain state. If your app loses state during device configuration, verify that the plugins and native extensions that your app uses support the device type, such as a large screen. Some native extensions might lose state when the device changes position. For more information on a real-world case where this occurred, check out \[Problem: Folding/unfolding causes state loss]\[state-loss] in \[Developing Flutter apps for Large screens]\[article], a post on the Flutter blog. \[app state]: https\://developer.android.com/jetpack/compose/state#store-state \[article]: https\://blog.flutter.dev/developing-flutter-apps-for-large-screens-53b7b0e17f10 \[state-loss]: https\://blog.flutter.dev/developing-flutter-apps-for-large-screens-53b7b0e17f10#:\~:text=Problem%3A%20Folding/Unfolding%20causes%20state%2Dloss

----
url: https://docs.flutter.dev/cookbook/forms/focus
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Focus and text fields](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

When a text field is selected and accepting input, it is said to have "focus." 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 you have a search screen with a text field. When the user navigates to the search screen, you can set the focus to the text field for the search term. This allows the user to start typing as soon as the screen is visible, without needing to manually tap the text field.

In this recipe, learn how to give the focus to a text field as soon as it's visible, as well as how to give focus to a text field when a button is tapped.

## Focus a text field as soon as it's visible

[#](#focus-a-text-field-as-soon-as-its-visible)

To give focus to a text field as soon as it's visible, use the `autofocus` property.

dart

```
TextField(
  autofocus: true,
);
```

To learn more about handling input and creating text fields, consult the [Forms](/cookbook/forms) section of the cookbook.

## Focus a text field when a button is tapped

[#](#focus-a-text-field-when-a-button-is-tapped)

Rather than immediately shifting focus to a specific text field, you might need to give focus to a text field at a later point in time. In the real world, you might also need to give focus to a specific text field in response to an API call or a validation error. In this example, give focus to a text field after the user presses a button using the following steps:

1. Create a `FocusNode`.
2. Pass the `FocusNode` to a `TextField`.
3. Give focus to the `TextField` when a button is tapped.

### 1. Create a `FocusNode`

[#](#1-create-a-focusnode)

First, create a [`FocusNode`](https://api.flutter.dev/flutter/widgets/FocusNode-class.html). Use the `FocusNode` to identify a specific `TextField` in Flutter's "focus tree." This allows you to give focus to the `TextField` in the next steps.

Since focus nodes are long-lived objects, manage the lifecycle using a `State` object. Use the following instructions to create a `FocusNode` instance inside the `initState()` method of a `State` class, and clean it up in the `dispose()` method:

dart

```
// Define a custom Form widget.
class MyCustomForm extends StatefulWidget {
  const MyCustomForm({super.key});
​
  @override
  State<MyCustomForm> createState() => _MyCustomFormState();
}
​
// Define a corresponding State class.
// This class holds data related to the form.
class _MyCustomFormState extends State<MyCustomForm> {
  // Define the focus node. To manage the lifecycle, create the FocusNode in
  // the initState method, and clean it up in the dispose method.
  late final FocusNode myFocusNode;
​
  @override
  void initState() {
    super.initState();
​
    myFocusNode = FocusNode();
  }
​
  @override
  void dispose() {
    // Clean up the focus node when the Form is disposed.
    myFocusNode.dispose();
​
    super.dispose();
  }
​
  @override
  Widget build(BuildContext context) {
    // Fill this out in the next step.
  }
}
```

### 2. Pass the `FocusNode` to a `TextField`

[#](#2-pass-the-focusnode-to-a-textfield)

Now that you have a `FocusNode`, pass it to a specific `TextField` in the `build()` method.

dart

```
@override
Widget build(BuildContext context) {
  return TextField(focusNode: myFocusNode);
}
```

### 3. Give focus to the `TextField` when a button is tapped

[#](#3-give-focus-to-the-textfield-when-a-button-is-tapped)

Finally, focus the text field when the user taps a floating action button. Use the [`requestFocus()`](https://api.flutter.dev/flutter/widgets/FocusNode/requestFocus.html) method to perform this task.

dart

```
FloatingActionButton(
  // When the button is pressed,
  // give focus to the text field using myFocusNode.
  onPressed: () => myFocusNode.requestFocus(),
  tooltip: 'Focus Second Text Field',
  child: const Icon(Icons.edit),
),
```

## Interactive example

[#](#interactive-example)

```
import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(title: 'Text Field Focus', home: MyCustomForm());
  }
}

// Define a custom Form widget.
class MyCustomForm extends StatefulWidget {
  const MyCustomForm({super.key});

  @override
  State<MyCustomForm> createState() => _MyCustomFormState();
}

// Define a corresponding State class.
// This class holds data related to the form.
class _MyCustomFormState extends State<MyCustomForm> {
  // Define the focus node. To manage the lifecycle, create the FocusNode in
  // the initState method, and clean it up in the dispose method.
  late final FocusNode myFocusNode;

  @override
  void initState() {
    super.initState();

    myFocusNode = FocusNode();
  }

  @override
  void dispose() {
    // Clean up the focus node when the Form is disposed.
    myFocusNode.dispose();

    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Text Field Focus')),
      body: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          children: [
            // The first text field is focused on as soon as the app starts.
            const TextField(autofocus: true),
            // The second text field is focused on when a user taps the
            // FloatingActionButton.
            TextField(focusNode: myFocusNode),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        // When the button is pressed,
        // give focus to the text field using myFocusNode.
        onPressed: () => myFocusNode.requestFocus(),
        tooltip: 'Focus Second Text Field',
        child: const Icon(Icons.edit),
      ),
    );
  }
}
```

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-10-06. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/forms/focus.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/cookbook/forms/focus\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/forms/focus.md "Report an issue with this page").

----
url: https://docs.flutter.dev/add-to-app/ios/project-setup
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Integrate a Flutter app into your iOS project](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

Flutter UI components can be incrementally added into your existing iOS application using Swift packages.

## Prerequisites

[#](#prerequisites)

* Flutter 3.44 or later
* Xcode 15.0 or later

### Migrate from legacy integration (if applicable)

[#](#migrate-legacy-integration)

If you've already integrated Flutter into your iOS app using CocoaPods or embedded frameworks, you must first remove that integration before following the Swift Package Manager instructions below.

Expand to see instructions to migrate from CocoaPods integration

If your app was previously integrated using CocoaPods, you must first remove the Flutter installation code from your Podfile.

1. Remove Flutter installation code from your Podfile

   MyApp/Podfile

   ruby

   ```
   flutter_application_path = '../my_flutter'
   load File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb')
   ​
   install_all_flutter_pods(flutter_application_path)
   ​
   flutter_post_install(installer) if defined?(flutter_post_install)
   ```

2. Run `pod install`.

Expand to see instructions to migrate from embedded frameworks integration

If your app was previously integrated using frameworks generated by the `flutter build ios-framework` command, you must first remove the frameworks from your Xcode project.

1. Navigate to your target's General tab and remove all Flutter-related frameworks and libraries under **Frameworks, Libraries, and Embedded Content**.

   This includes the `App.xcframework`, `Flutter.xcframework`, `FlutterPluginRegistrant.xcframework`, and any Flutter plugins' `xcframework` files.

2. Remove the Flutter pod from your Podfile

   MyApp/Podfile

   ruby

   ```
   pod 'Flutter', :podspec => '/path/to/MyApp/Flutter/[build mode]/Flutter.podspec'
   ```

3. Run `pod install`.

The [legacy integration guide](/add-to-app/ios/project-setup-legacy) is preserved for reference, but will not receive ongoing maintenance.

### Organize your projects relative to each other

[#](#organize-projects-relatively)

This guide assumes that your existing iOS app and your Flutter app or module reside in sibling directories. If you have a different directory structure, you will need to adjust the example relative paths accordingly.

The example directory structure resembles the following:

* [Flutter App](#124-tab-panel)
* [Flutter Module](#125-tab-panel)

- my\_flutter\_app/

  * ios/

  * lib/

    * main.dart

- MyNativeApp/

  * MyNativeApp.xcodeproj/

* my\_flutter\_app/

  * .ios/

  * lib/

    * main.dart

* MyNativeApp/

  * MyNativeApp.xcodeproj/

## Integrate with Swift Package Manager

[#](#integrate-with-swiftpm)

1. ### Build the FlutterNativeIntegration Swift package

   Within your Flutter application or module, run the following command:

   ```
   flutter build swift-package --platform ios
   ```

   This will generate the following directories:

   * my\_flutter\_app/build/ios/SwiftPackages/

     * FlutterNativeIntegration/(A Swift package)
     * Scripts/(Directory of scripts and other files needed)

   You can optionally change the location of this output with the `--output` flag.

2. ### Add FlutterNativeIntegration to your Xcode project

   1. Open your existing iOS app in Xcode.

   2. In the Project navigator, right click on your project and select **Add Files to "MyNativeApp"...**

   3. Navigate to and select the generated `FlutterNativeIntegration` Swift package and click **Add**.

   4. Select **Reference files in place** and click **Finish**.

   5. In the File inspector, verify the **Location** is **Relative to Project**. If it is not, you'll need to move the Flutter output directory to be a sibling directory of your native app.

      Relative location of FlutterNativeIntegration shown in Xcode's File inspector.

   6. Navigate to your target's **General** tab and add `FlutterNativeIntegration` under **Frameworks, Libraries, and Embedded Content**.

      FlutterNativeIntegration under Frameworks, Libraries, and Embedded Content.

3. ### Add build settings

   1. In the **Build Settings** tab, set the location of the Flutter app's Swift package output directory:

      ```
      FLUTTER_SWIFT_PACKAGE_OUTPUT=$SRCROOT/../my_flutter_app/build/ios/SwiftPackages
      ```

   2. For custom configurations, set the Flutter build mode.

      Flutter supports three [build modes](/testing/build-modes): Debug, Profile, and Release. The build mode is determined using the `CONFIGURATION`. If your configuration does not match one of these, you can set the `FLUTTER_BUILD_MODE` build setting to one of these values.

      Setting `FLUTTER_BUILD_MODE` for custom configurations under **Build Settings**.

   3. (Optional) Allow Xcode to re-build your Flutter app.

      Add the below build settings to your target to allow Xcode to re-build your Flutter app as part of its build. This allows you to make changes to your Flutter application without needing to re-run `flutter build swift-package`. This requires Flutter to be installed on the machine.

      ```
      FLUTTER_APPLICATION_PATH=$SRCROOT/../my_flutter_app
      ENABLE_USER_SCRIPT_SANDBOXING=NO
      ```

4. ### Add Pre-action Run Script to Scheme

   1. Open **Product** > **Scheme** > **Edit Scheme...** > **Build** (in left side bar) > **Pre-action** > **+** > **New Run Script Action**

   2. Select your project in the **Provide build settings from** dropdown.

   3. Set the script to the following:

      ```
      /bin/sh $FLUTTER_SWIFT_PACKAGE_OUTPUT/Scripts/flutter_integration.sh prebuild
      ```

   Pre-action Run Script in scheme editor.

5. ### Add New Run Script Build Phase to Target

   1. Navigate to your target's **Build Phases** > **+** > **New Run Script Phase**

   2. Set the script to the following:

      ```
      /bin/sh $FLUTTER_SWIFT_PACKAGE_OUTPUT/Scripts/flutter_integration.sh assemble
      ```

   3. Uncheck **Based on dependency analysis**

   4. Add the following to **Input File Lists**:

      ```
      $(FLUTTER_SWIFT_PACKAGE_OUTPUT)/Scripts/FlutterAssembleInputs.xcfilelist
      ```

   New Run Script Build Phase under Build Phases.

6. ### (Optional) Set LLDB Init File

   Using Flutter's LLDB Init File improves performance when debugging on physical iOS 26+ devices.

   1. Open **Product** > **Scheme** > **Edit Scheme...** > **Run** (in left side bar).

   2. Set the **LLDB Init File** to the following path:

      ```
      $(FLUTTER_SWIFT_PACKAGE_OUTPUT)/Scripts/flutter_lldbinit
      ```

      Alternatively, if your scheme already has an LLDB Init File, you can add Flutter's LLDB file to it. The path to Flutter's LLDB Init File must be relative to the location of your project's LLDB Init File.

      ```
      command source --relative-to-command-file "../my_flutter_app/build/ios/SwiftPackages/Scripts/flutter_lldbinit"
      ```

## Set local network privacy permissions

[#](#local-network-permissions)

      The `Info-Debug` property list with the **Bonjour Services** and **Privacy - Local Network Usage Description** keys added

      Updating the `Info.plist` build setting to use build mode-specific property lists

      This resolves to the path **Info-Debug.plist** in **Debug** and **Info-Release.plist** in **Release**.

      The updated **Info.plist File** build setting displaying the configuration variations

6. Remove the **Release** property list from the **Build Phases**.

   1. In the **Project Navigator**, click on your project.

   2. Click the **Build Phases** tab.

   3. Expand **Copy Bundle Resources**.

   4. If this list includes `Info-Release.plist`, click on it and then click the **-** (minus sign) under it to remove the property list from the resources list.

      The **Copy Bundle** build phase displaying the **Info-Release.plist** setting. Remove this setting.

7. The first Flutter screen your Debug app loads prompts for local network permission.

   Click **OK**.

   *(Optional)* To grant permission before the app loads, enable **Settings > Privacy > Local Network > Your App**.

## Next steps

[#](#next-steps)

You can now [add a Flutter screen](/add-to-app/ios/add-flutter-screen) to your existing iOS app.

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-09-17. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/add-to-app/ios/project-setup.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/add-to-app/ios/project-setup\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/add-to-app/ios/project-setup.md "Report an issue with this page").

----
url: https://docs.flutter.dev/cookbook/navigation/set-up-universal-links.md
----

\# Set up universal links for iOS > Learn how to set up universal links for an iOS application built with Flutter. Deep linking allows an app user to launch an app with a URI. This URI contains scheme, host, and path, and opens the app to a specific screen. A \_universal link\_, a type of deep link exclusive to iOS devices, uses only the \`http\` or \`https\` protocols. To set up universal links, you need to own a web domain. As a temporary solution, consider using \[Firebase Hosting]\[] or \[GitHub Pages]\[]. Once you've set up your deep links, you can validate them. To learn more, see \[Validate deep links]\[]. ## Create or modify a Flutter app Write a Flutter app that can handle an incoming URL. This example uses the \[go\_router]\[] package to handle the routing. The Flutter team maintains the \`go\_router\` package. It provides a simple API to handle complex routing scenarios. 1. To create a new application, type \`flutter create \`. \`\`\`console $ flutter create deeplink\_cookbook \`\`\` 2. To include the \`go\_router\` package as a dependency, run \`flutter pub add\`: \`\`\`console $ flutter pub add go\_router \`\`\` 3. To handle the routing, create a \`GoRouter\` object in the \`main.dart\` file: \`\`\`dart title="main.dart" import 'package:material\_ui/material\_ui.dart'; import 'package:go\_router/go\_router.dart'; void main() => runApp(MaterialApp.router(routerConfig: router)); /// This handles '/' and '/details'. final router = GoRouter( routes: \[ GoRoute( path: '/', builder: (\_, \_) => Scaffold( appBar: AppBar(title: const Text('Home Screen')), ), routes: \[ GoRoute( path: 'details', builder: (\_, \_) => Scaffold( appBar: AppBar(title: const Text('Details Screen')), ), ), ], ), ], ); \`\`\` ## Adjust iOS build settings 1. Launch Xcode. 1. Open the \`ios/Runner.xcworkspace\` file inside the Flutter project's \`ios\` folder. :::version-note If you use a Flutter version earlier than 3.27, you need to manually opt in to deep linking by adding the key and value pair \`FlutterDeepLinkingEnabled\` and \`YES\` to \`info.Plist\`. ::: :::note If you're using third-party plugins to handle deep links, such as \[app\_links]\[], Flutter's default deeplink handler will break these plugins. If you use a third-party plugin, add the key and value pair \`FlutterDeepLinkingEnabled\` and \`NO\` to \`info.Plist\`. ::: ### Add associated domains :::warning Personal development teams don't support the Associated Domains capability. To add associated domains, choose the IDE tab. ::: 1. Launch Xcode if necessary. 1. Click the top-level \*\*Runner\*\*. 1. In the Editor, click the \*\*Runner\*\* target. 1. Click \*\*Signing & Capabilities\*\*. 1. To add a new domain, click \*\*+ Capability\*\* under \*\*Signing & Capabilities\*\*. 1. Click \*\*Associated Domains\*\*. 1. In the \*\*Associated Domains\*\* section, click \*\*+\*\*. 1. Enter \`applinks:\`. Replace \`\` with your own domain name. 1. Open the \`ios/Runner/Runner.entitlements\` XML file in your preferred editor. 1. Add an associated domain inside the \`\` tag. \`\`\`xml

<!--?xml version="1.0" encoding="UTF-8"?-->

\[!com.apple.developer.associated-domains!] \[!!] \[!applinks:example.com!] \[!!] \`\`\` 1. Save the \`ios/Runner/Runner.entitlements\` file. To check that the associated domains you created are available, perform the following steps: 1. Launch Xcode if necessary. 1. Click the top-level \*\*Runner\*\*. 1. In the Editor, click the \*\*Runner\*\* target. 1. Click \*\*Signing & Capabilities\*\*. The domains should appear in the \*\*Associated Domains\*\* section. You have finished configuring the application for deep linking. ## Associate your app with your web domain You need to host an \`apple-app-site-association\` file in the web domain. This file tells the mobile browser which iOS application to open instead of the browser. To create the file, find the \`appID\` of the Flutter app you created in the previous section. ### Locate components of the \`appID\` Apple formats the \`appID\` as \`.\`. \* Locate the bundle ID in the Xcode project. \* Locate the team ID in the \[developer account]\[]. \*\*For example:\*\* Given a team ID of \`S8QB4VV633\` and a bundle ID of \`com.example.deeplinkCookbook\`, you would enter an \`appID\` entry of \`S8QB4VV633.com.example.deeplinkCookbook\`. ### Create and host \`apple-app-site-association\` JSON file This file uses the JSON format. Don't include the \`.json\` file extension when you save this file. Per \[Apple's documentation]\[apple-app-site-assoc], this file should resemble the following content: \`\`\`json { "applinks": { "details": \[ { "appIDs": \[ "S8QB4VV633.com.example.deeplinkCookbook" ], "components": \[ { "/": "/\*" } ] } ] } } \`\`\` 1. Set one value in the \`appIDs\` array to \`.\`. 1. Set the \`components\` array to specify the allowed paths. In this example, the \`/\` key represents the URL path. The value \`/\*\` matches and redirects any path to the Flutter app. If needed, customize the \`components\` array to match only the paths appropriate for your app. 1. Host the file at a URL that resembles the following structure. \`/.well-known/apple-app-site-association\` 1. Verify that your browser can access this file. :::note If you have more than one scheme/flavor, you can add more than one \`appID\` into the \`appIDs\` field. ::: ## Test the universal link Test a universal link using a physical iOS device or the Simulator. :::note It might take up to 24 hours before Apple's \[Content Delivery Network]\[] (CDN) requests the \`apple-app-site-association\` (AASA) file from your web domain. Until the CDN requests the file, the universal link won't work. To bypass Apple's CDN, check out the \[alternate mode section]\[]. ::: 1. Before testing, install the Flutter app on the iOS device or Simulator, Use \`flutter run\` on the desired device. When complete, the Flutter app displays on the home screen of the iOS device or Simulator. 1. If you test using the Simulator, use the Xcode CLI: \`\`\`console $ xcrun simctl openurl booted https\:///details \`\`\` 1. If you test with a physical iOS device: 1. Launch the \*\*Note\*\* app. 1. Type the URL in the \*\*Note\*\* app. 1. Click the resulting link. If successful, the Flutter app launches and displays its details screen. \[Content Delivery Network]: https\://en.wikipedia.org/wiki/Content\_delivery\_network ## Find the source code You can find the source code for the \[deeplink\_cookbook]\[] recipe in the GitHub repo. \[apple-app-site-assoc]: https\://developer.apple.com/documentation/xcode/supporting-associated-domains \[alternate mode section]: https\://developer.apple.com/documentation/bundleresources/entitlements/com\_apple\_developer\_associated-domains?language=objc \[deeplink\_cookbook]: https\://github.com/flutter/codelabs/tree/main/deeplink\_cookbook \[developer account]: https\://developer.apple.com/account \[Firebase Hosting]: https\://firebase.google.com/docs/hosting \[go\_router]: https\://pub.dev/packages/go\_router \[GitHub Pages]: https\://pages.github.com \[app\_links]: https\://pub.dev/packages/app\_links \[Validate deep links]: /tools/devtools/deep-links

----
url: https://docs.flutter.dev/ui/interactivity
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Add interactivity to your Flutter app](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

How do you modify your app to make it react to user input? In this tutorial, you'll add interactivity to an app that contains only non-interactive widgets. Specifically, you'll modify an icon to make it tappable by creating a custom stateful widget that manages two stateless widgets.

The [building layouts tutorial](/ui/layout/tutorial) showed you how to create the layout for the following screenshot.

The layout tutorial app

When the app first launches, the star is solid red, indicating that this lake has previously been favorited. The number next to the star indicates that 41 people have favorited this lake. After completing this tutorial, tapping the star removes its favorited status, replacing the solid star with an outline and decreasing the count. Tapping again favorites the lake, drawing a solid star and increasing the count.

To accomplish this, you'll create a single custom widget that includes both the star and the count, which are themselves widgets. Tapping the star changes state for both widgets, so the same widget should manage both.

You can get right to touching the code in [Step 2: Subclass StatefulWidget](#step-2). If you want to try different ways of managing state, skip to [Managing state](#managing-state).

## Stateful and stateless widgets

[#](#stateful-and-stateless-widgets)

A widget is either stateful or stateless. If a widget can change—when a user interacts with it, for example—it's stateful.

A *stateless* widget never changes. [`Icon`](https://api.flutter.dev/flutter/widgets/Icon-class.html), [`IconButton`](https://pub.dev/documentation/material_ui/latest/material_ui/IconButton-class.html), and [`Text`](https://api.flutter.dev/flutter/widgets/Text-class.html) are examples of stateless widgets. Stateless widgets subclass [`StatelessWidget`](https://api.flutter.dev/flutter/widgets/StatelessWidget-class.html).

A *stateful* widget is dynamic: for example, it can change its appearance in response to events triggered by user interactions or when it receives data. [`Checkbox`](https://pub.dev/documentation/material_ui/latest/material_ui/Checkbox-class.html), [`Radio`](https://pub.dev/documentation/material_ui/latest/material_ui/Radio-class.html), [`Slider`](https://pub.dev/documentation/material_ui/latest/material_ui/Slider-class.html), [`InkWell`](https://pub.dev/documentation/material_ui/latest/material_ui/InkWell-class.html), [`Form`](https://api.flutter.dev/flutter/widgets/Form-class.html), and [`TextField`](https://pub.dev/documentation/material_ui/latest/material_ui/TextField-class.html) are examples of stateful widgets. Stateful widgets subclass [`StatefulWidget`](https://api.flutter.dev/flutter/widgets/StatefulWidget-class.html).

A widget's state is stored in a [`State`](https://api.flutter.dev/flutter/widgets/State-class.html) object, separating the widget's state from its appearance. The state consists of values that can change, like a slider's current value or whether a checkbox is checked. When the widget's state changes, the state object calls `setState()`, telling the framework to redraw the widget.

## Creating a stateful widget

[#](#creating-a-stateful-widget)

In this section, you'll create a custom stateful widget. You'll replace two stateless widgets—the solid red star and the numeric count next to it—with a single custom stateful widget that manages a row with two children widgets: an `IconButton` and `Text`.

Implementing a custom stateful widget requires creating two classes:

* A subclass of `StatefulWidget` that defines the widget.
* A subclass of `State` that contains the state for that widget and defines the widget's `build()` method.

This section shows you how to build a stateful widget, called `FavoriteWidget`, for the lakes app. After setting up, your first step is choosing how state is managed for `FavoriteWidget`.

### Step 0: Get ready

[#](#step-0-get-ready)

If you've already built the app in the [building layouts tutorial](/ui/layout/tutorial), skip to the next section.

1. Make sure you've [set up](/install) your environment.
2. [Create a new Flutter app](/reference/create-new-app).
3. Replace the `lib/main.dart` file with [`main.dart`](https://github.com/flutter/website/blob/main/examples/layout/lakes/step6/lib/main.dart).
4. Replace the `pubspec.yaml` file with [`pubspec.yaml`](https://github.com/flutter/website/blob/main/examples/layout/lakes/step6/pubspec.yaml).
5. Create an `images` directory in your project, and add [`lake.jpg`](https://github.com/flutter/website/blob/main/examples/layout/lakes/step6/images/lake.jpg).

Once you have a connected and enabled device, or you've launched the [iOS simulator](/platform-integration/ios/setup#set-up-devices) (part of the Flutter install) or the [Android emulator](/platform-integration/android/setup#set-up-devices) (part of the Android Studio install), you are good to go!

[]()

### Step 1: Decide which object manages the widget's state

[#](#step-1-decide-which-object-manages-the-widgets-state)

A widget's state can be managed in several ways, but in our example the widget itself, `FavoriteWidget`, will manage its own state. In this example, toggling the star is an isolated action that doesn't affect the parent widget or the rest of the UI, so the widget can handle its state internally.

Learn more about the separation of widget and state, and how state might be managed, in [Managing state](#managing-state).

[]()

### Step 2: Subclass StatefulWidget

[#](#step-2-subclass-statefulwidget)

The `FavoriteWidget` class manages its own state, so it overrides `createState()` to create a `State` object. The framework calls `createState()` when it wants to build the widget. In this example, `createState()` returns an instance of `_FavoriteWidgetState`, which you'll implement in the next step.

dart

```
class FavoriteWidget extends StatefulWidget {
  const FavoriteWidget({super.key});
​
  @override
  State<FavoriteWidget> createState() => _FavoriteWidgetState();
}
```

[]()

### Step 3: Subclass State

[#](#step-3-subclass-state)

The `_FavoriteWidgetState` class stores the mutable data that can change over the lifetime of the widget. When the app first launches, the UI displays a solid red star, indicating that the lake has "favorite" status, along with 41 likes. These values are stored in the `_isFavorited` and `_favoriteCount` fields:

dart

```
class _FavoriteWidgetState extends State<FavoriteWidget> {
  bool _isFavorited = true;
  int _favoriteCount = 41;
```

The class also defines a `build()` method, which creates a row containing a red `IconButton`, and `Text`. You use [`IconButton`](https://pub.dev/documentation/material_ui/latest/material_ui/IconButton-class.html) (instead of `Icon`) because it has an `onPressed` property that defines the callback function (`_toggleFavorite`) for handling a tap. You'll define the callback function next.

dart

```
class _FavoriteWidgetState extends State<FavoriteWidget> {
  // ···
  @override
  Widget build(BuildContext context) {
    return Row(
      mainAxisSize: MainAxisSize.min,
      children: [
        Container(
          padding: const EdgeInsets.all(0),
          child: IconButton(
            padding: const EdgeInsets.all(0),
            alignment: Alignment.center,
            icon: (_isFavorited
                ? const Icon(Icons.star)
                : const Icon(Icons.star_border)),
            color: Colors.red[500],
            onPressed: _toggleFavorite,
          ),
        ),
        SizedBox(width: 18, child: SizedBox(child: Text('$_favoriteCount'))),
      ],
    );
  }
​
  // ···
}
```

The `_toggleFavorite()` method, which is called when the `IconButton` is pressed, calls `setState()`. Calling `setState()` is critical, because this tells the framework that the widget's state has changed and that the widget should be redrawn. The function argument to `setState()` toggles the UI between these two states:

* A `star` icon and the number 41
* A `star_border` icon and the number 40

dart

```
void _toggleFavorite() {
  setState(() {
    if (_isFavorited) {
      _favoriteCount -= 1;
      _isFavorited = false;
    } else {
      _favoriteCount += 1;
      _isFavorited = true;
    }
  });
}
```

[]()

### Step 4: Plug the stateful widget into the widget tree

[#](#step-4-plug-the-stateful-widget-into-the-widget-tree)

Add your custom stateful widget to the widget tree in the app's `build()` method. First, locate the code that creates the `Icon` and `Text`, and delete it. In the same location, create the stateful widget:

dart

```
child: Row(
  children: [
    // ...
    Icon(
      Icons.star,
      color: Colors.red[500],
    ),
    const Text('41'),
    const FavoriteWidget(),
  ],
),
```

That's it! When you hot reload the app, the star icon should now respond to taps.

### Problems?

[#](#problems)

If you can't get your code to run, look in your IDE for possible errors. [Debugging Flutter apps](/testing/debugging) might help. If you still can't find the problem, check your code against the interactive lakes example on GitHub.

* [`lib/main.dart`](https://github.com/flutter/website/tree/main/examples/layout/lakes/interactive/lib/main.dart)
* [`pubspec.yaml`](https://github.com/flutter/website/tree/main/examples/layout/lakes/interactive/pubspec.yaml)
* [`lakes.jpg`](https://github.com/flutter/website/tree/main/examples/layout/lakes/interactive/images/lake.jpg)

If you still have questions, refer to any one of the developer [community](https://flutter.dev/community) channels.

***

The rest of this page covers several ways a widget's state can be managed, and lists other available interactive widgets.

## Managing state

[#](#managing-state)

Who manages the stateful widget's state? The widget itself? The parent widget? Both? Another object? The answer is... it depends. There are several valid ways to make your widget interactive. You, as the widget designer, make the decision based on how you expect your widget to be used. Here are the most common ways to manage state:

* [The widget manages its own state](#self-managed)
* [The parent manages the widget's state](#parent-managed)
* [A mix-and-match approach](#mix-and-match)

How do you decide which approach to use? The following principles should help you decide:

* If the state in question is user data, for example the checked or unchecked mode of a checkbox, or the position of a slider, then the state is best managed by the parent widget.

* If the state in question is aesthetic, for example an animation, then the state is best managed by the widget itself.

If in doubt, start by managing state in the parent widget.

We'll give examples of the different ways of managing state by creating three simple examples: TapboxA, TapboxB, and TapboxC. The examples all work similarly—each creates a container that, when tapped, toggles between a green or grey box. The `_active` boolean determines the color: green for active or grey for inactive.

These examples use [`GestureDetector`](https://api.flutter.dev/flutter/widgets/GestureDetector-class.html) to capture activity on the `Container`.

[]()

### The widget manages its own state

[#](#the-widget-manages-its-own-state)

Sometimes it makes the most sense for the widget to manage its state internally. For example, [`ListView`](https://api.flutter.dev/flutter/widgets/ListView-class.html) automatically scrolls when its content exceeds the render box. Most developers using `ListView` don't want to manage `ListView`'s scrolling behavior, so `ListView` itself manages its scroll offset.

The `_TapboxAState` class:

* Manages state for `TapboxA`.
* Defines the `_active` boolean which determines the box's current color.
* Defines the `_handleTap()` function, which updates `_active` when the box is tapped and calls the `setState()` function to update the UI.
* Implements all interactive behavior for the widget.

dart

```
import 'package:material_ui/material_ui.dart';
​
// TapboxA manages its own state.
​
//------------------------- TapboxA ----------------------------------
​
class TapboxA extends StatefulWidget {
  const TapboxA({super.key});
​
  @override
  State<TapboxA> createState() => _TapboxAState();
}
​
class _TapboxAState extends State<TapboxA> {
  bool _active = false;
​
  void _handleTap() {
    setState(() {
      _active = !_active;
    });
  }
​
  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: _handleTap,
      child: Container(
        width: 200,
        height: 200,
        decoration: BoxDecoration(
          color: _active ? Colors.lightGreen[700] : Colors.grey[600],
        ),
        child: Center(
          child: Text(
            _active ? 'Active' : 'Inactive',
            style: const TextStyle(fontSize: 32, color: Colors.white),
          ),
        ),
      ),
    );
  }
}
​
//------------------------- MyApp ----------------------------------
​
class MyApp extends StatelessWidget {
  const MyApp({super.key});
​
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      home: Scaffold(
        appBar: AppBar(title: const Text('Flutter Demo')),
        body: const Center(child: TapboxA()),
      ),
    );
  }
}
```

***

[]()

### The parent widget manages the widget's state

[#](#the-parent-widget-manages-the-widgets-state)

Often it makes the most sense for the parent widget to manage the state and tell its child widget when to update. For example, [`IconButton`](https://pub.dev/documentation/material_ui/latest/material_ui/IconButton-class.html) allows you to treat an icon as a tappable button. `IconButton` is a stateless widget because we decided that the parent widget needs to know whether the button has been tapped, so it can take appropriate action.

In the following example, TapboxB exports its state to its parent through a callback. Because TapboxB doesn't manage any state, it subclasses StatelessWidget.

The ParentWidgetState class:

* Manages the `_active` state for TapboxB.
* Implements `_handleTapboxChanged()`, the method called when the box is tapped.
* When the state changes, calls `setState()` to update the UI.

The TapboxB class:

* Extends StatelessWidget because all state is handled by its parent.
* When a tap is detected, it notifies the parent.

dart

```
import 'package:material_ui/material_ui.dart';
​
// ParentWidget manages the state for TapboxB.
​
//------------------------ ParentWidget --------------------------------
​
class ParentWidget extends StatefulWidget {
  const ParentWidget({super.key});
​
  @override
  State<ParentWidget> createState() => _ParentWidgetState();
}
​
class _ParentWidgetState extends State<ParentWidget> {
  bool _active = false;
​
  void _handleTapboxChanged(bool newValue) {
    setState(() {
      _active = newValue;
    });
  }
​
  @override
  Widget build(BuildContext context) {
    return SizedBox(
      child: TapboxB(active: _active, onChanged: _handleTapboxChanged),
    );
  }
}
​
//------------------------- TapboxB ----------------------------------
​
class TapboxB extends StatelessWidget {
  const TapboxB({super.key, this.active = false, required this.onChanged});
​
  final bool active;
  final ValueChanged<bool> onChanged;
​
  void _handleTap() {
    onChanged(!active);
  }
​
  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: _handleTap,
      child: Container(
        width: 200,
        height: 200,
        decoration: BoxDecoration(
          color: active ? Colors.lightGreen[700] : Colors.grey[600],
        ),
        child: Center(
          child: Text(
            active ? 'Active' : 'Inactive',
            style: const TextStyle(fontSize: 32, color: Colors.white),
          ),
        ),
      ),
    );
  }
}
```

***

[]()

### A mix-and-match approach

[#](#a-mix-and-match-approach)

For some widgets, a mix-and-match approach makes the most sense. In this scenario, the stateful widget manages some of the state, and the parent widget manages other aspects of the state.

In the `TapboxC` example, on tap down, a dark green border appears around the box. On tap up, the border disappears and the box's color changes. `TapboxC` exports its `_active` state to its parent but manages its `_highlight` state internally. This example has two `State` objects, `_ParentWidgetState` and `_TapboxCState`.

The `_ParentWidgetState` object:

* Manages the `_active` state.
* Implements `_handleTapboxChanged()`, the method called when the box is tapped.
* Calls `setState()` to update the UI when a tap occurs and the `_active` state changes.

The `_TapboxCState` object:

* Manages the `_highlight` state.
* The `GestureDetector` listens to all tap events. As the user taps down, it adds the highlight (implemented as a dark green border). As the user releases the tap, it removes the highlight.
* Calls `setState()` to update the UI on tap down, tap up, or tap cancel, and the `_highlight` state changes.
* On a tap event, passes that state change to the parent widget to take appropriate action using the [`widget`](https://api.flutter.dev/flutter/widgets/State/widget.html) property.

dart

```
import 'package:material_ui/material_ui.dart';
​
//---------------------------- ParentWidget ----------------------------
​
class ParentWidget extends StatefulWidget {
  const ParentWidget({super.key});
​
  @override
  State<ParentWidget> createState() => _ParentWidgetState();
}
​
class _ParentWidgetState extends State<ParentWidget> {
  bool _active = false;
​
  void _handleTapboxChanged(bool newValue) {
    setState(() {
      _active = newValue;
    });
  }
​
  @override
  Widget build(BuildContext context) {
    return SizedBox(
      child: TapboxC(active: _active, onChanged: _handleTapboxChanged),
    );
  }
}
​
//----------------------------- TapboxC ------------------------------
​
class TapboxC extends StatefulWidget {
  const TapboxC({super.key, this.active = false, required this.onChanged});
​
  final bool active;
  final ValueChanged<bool> onChanged;
​
  @override
  State<TapboxC> createState() => _TapboxCState();
}
​
class _TapboxCState extends State<TapboxC> {
  bool _highlight = false;
​
  void _handleTapDown(TapDownDetails details) {
    setState(() {
      _highlight = true;
    });
  }
​
  void _handleTapUp(TapUpDetails details) {
    setState(() {
      _highlight = false;
    });
  }
​
  void _handleTapCancel() {
    setState(() {
      _highlight = false;
    });
  }
​
  void _handleTap() {
    widget.onChanged(!widget.active);
  }
​
  @override
  Widget build(BuildContext context) {
    // This example adds a green border on tap down.
    // On tap up, the square changes to the opposite state.
    return GestureDetector(
      onTapDown: _handleTapDown, // Handle the tap events in the order that
      onTapUp: _handleTapUp, // they occur: down, up, tap, cancel
      onTap: _handleTap,
      onTapCancel: _handleTapCancel,
      child: Container(
        width: 200,
        height: 200,
        decoration: BoxDecoration(
          color: widget.active ? Colors.lightGreen[700] : Colors.grey[600],
          border: _highlight
              ? Border.all(color: Colors.teal[700]!, width: 10)
              : null,
        ),
        child: Center(
          child: Text(
            widget.active ? 'Active' : 'Inactive',
            style: const TextStyle(fontSize: 32, color: Colors.white),
          ),
        ),
      ),
    );
  }
}
```

An alternate implementation might have exported the highlight state to the parent while keeping the active state internal, but if you asked someone to use that tap box, they'd probably complain that it doesn't make much sense. The developer cares whether the box is active. The developer probably doesn't care how the highlighting is managed, and prefers that the tap box handles those details.

***

## Other interactive widgets

[#](#other-interactive-widgets)

Flutter offers a variety of buttons and similar interactive widgets. Most of these widgets implement the [Material Design guidelines](https://m3.material.io/styles), which define a set of components with an opinionated UI.

If you prefer, you can use [`GestureDetector`](https://api.flutter.dev/flutter/widgets/GestureDetector-class.html) to build interactivity into any custom widget. You can find examples of `GestureDetector` in [Managing state](#managing-state). Learn more about the `GestureDetector` in [Handle taps](/cookbook/gestures/handling-taps), a recipe in the Flutter cookbook.

When you need interactivity, it's easiest to use one of the prefabricated widgets. Here's a partial list:

### Standard widgets

[#](#standard-widgets)

* [`Form`](https://api.flutter.dev/flutter/widgets/Form-class.html)
* [`FormField`](https://api.flutter.dev/flutter/widgets/FormField-class.html)

### Material Components

[#](#material-components)

* [`Checkbox`](https://pub.dev/documentation/material_ui/latest/material_ui/Checkbox-class.html)
* [`DropdownButton`](https://pub.dev/documentation/material_ui/latest/material_ui/DropdownButton-class.html)
* [`TextButton`](https://pub.dev/documentation/material_ui/latest/material_ui/TextButton-class.html)
* [`FloatingActionButton`](https://pub.dev/documentation/material_ui/latest/material_ui/FloatingActionButton-class.html)
* [`IconButton`](https://pub.dev/documentation/material_ui/latest/material_ui/IconButton-class.html)
* [`Radio`](https://pub.dev/documentation/material_ui/latest/material_ui/Radio-class.html)
* [`ElevatedButton`](https://pub.dev/documentation/material_ui/latest/material_ui/ElevatedButton-class.html)
* [`Slider`](https://pub.dev/documentation/material_ui/latest/material_ui/Slider-class.html)
* [`Switch`](https://pub.dev/documentation/material_ui/latest/material_ui/Switch-class.html)
* [`TextField`](https://pub.dev/documentation/material_ui/latest/material_ui/TextField-class.html)

## Resources

[#](#resources)

The following resources might help when adding interactivity to your app.

[Gestures](/cookbook/gestures), a section in the Flutter cookbook.

* [Handling gestures](/ui#handling-gestures)

  How to create a button and make it respond to input.

* [Gestures in Flutter](/ui/interactivity/gestures)

  A description of Flutter's gesture mechanism.

* [Flutter API documentation](https://api.flutter.dev)

  Reference documentation for all of the Flutter libraries.

* Wonderous app [running app](https://wonderous.app//web) , [repo](https://github.com/gskinnerTeam/flutter-wonderous-app)

  Flutter showcase app with a custom design and engaging interactions.

* [Flutter's Layered Design](https://www.youtube.com/watch?v=dkyY9WCGMi0) (video)

  This video includes information about state and stateless widgets. Presented by Google engineer, Ian Hickson.

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-09-29. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/ui/interactivity/index.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/ui/interactivity\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/ui/interactivity/index.md "Report an issue with this page").

----
url: https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.11.2
----

The 2.11.2 release of the Dart and Flutter DevTools includes the following changes among other general improvements. To learn more about DevTools, check out the [DevTools overview](https://docs.flutter.dev/tools/devtools).

## General updates

[#](#general-updates)

* This release included a lot of cleanup and reduction in technical debt.

## CPU profiler updates

[#](#cpu-profiler-updates)

* Added the source line number to file uris in CPU profiles - [#3718](https://github.com/flutter/devtools/pull/3718)

## Debugger updates

[#](#debugger-updates)

* File opener UX improvements, including support for clicking the source file name to open the file search window - [#3612](https://github.com/flutter/devtools/pull/3612), [#3758](https://github.com/flutter/devtools/pull/3758)

* Added support for auto-scrolling the File Explorer to the selected file - [#3786](https://github.com/flutter/devtools/pull/3786), [#3794](https://github.com/flutter/devtools/pull/3794)

## Full commit history

[#](#full-commit-history)

To find a complete list of changes since the previous release, check out [the diff on GitHub](https://github.com/flutter/devtools/compare/v2.10.0...v2.11.2).

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/release-notes/release-notes-2.11.2.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.11.2\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/release-notes/release-notes-2.11.2.md "Report an issue with this page").

----
url: https://docs.flutter.dev/learn/pathway/tutorial/slivers
----

In this lesson, you'll learn about slivers, which are special widgets that can take advantage of Flutter's powerful and composable scrolling system. Slivers enable you to create sophisticated scroll effects, including collapsible headers, search integration, and custom scroll behaviors. By the end of this section, you'll understand how to use `CustomScrollView`, create navigation bars that collapse, and organize content in scrollable sections.

view\_day Understand slivers and how they differ from widgets

unfold\_more Build scrollable layouts with CustomScrollView

search Create collapsible navigation bars with search

sort\_by\_alpha Organize contacts in alphabetized sections

***

## Steps

1### Slivers and widgetskeyboard\_arrow\_up

Slivers are scrollable areas that can be composed together in a `CustomScrollView` or other scroll views. Think of slivers as building blocks that each contribute a portion of the overall scrollable content.

While slivers and widgets are both fundamental Flutter concepts, they serve different purposes and aren't interchangeable.

* **Widgets** are general UI building blocks that can be used anywhere in your widget tree.

* **Slivers** are specialized widgets designed specifically for scrollable layouts and have some constraints:

* Slivers can **only** be direct children of scroll views, such as `CustomScrollView` and `NestedScrollView`.

* Some scroll views **only** accept slivers as children. You can't pass regular widgets to `CustomScrollView.slivers`.

* To use regular widgets within a sliver context, wrap them in `SliverToBoxAdapter` or `SliverFillRemaining`.

This architectural separation allows Flutter to optimize scrolling performance while it maintains clear boundaries between different types of UI components.

2### Add a basic sliver structure to contact groupskeyboard\_arrow\_up

First, replace the placeholder content in your contact groups page. To avoid duplicating code between the phone layout and the tablet sidebar, you can create a private, reusable widget.

Update `lib/screens/contact_groups.dart` by adding `_ContactGroupsView` to the bottom of the file.

dart

```
import 'package:cupertino_ui/cupertino_ui.dart';
​
import '../data/contact_group.dart';
import '../main.dart';
​
class ContactGroupsPage extends StatelessWidget {
  const ContactGroupsPage({super.key});
​
  @override
  Widget build(BuildContext context) {
    return _ContactGroupsView(
      selectedListId: 0,
      onListSelected: (list) {
        debugPrint(list.toString());
      },
    );
  }
}
​
// ···
class _ContactGroupsView extends StatelessWidget {
  const _ContactGroupsView({required this.onListSelected, this.selectedListId});
​
  final int? selectedListId;
  final void Function(ContactGroup) onListSelected;
​
  @override
  Widget build(BuildContext context) {
    return CupertinoPageScaffold(
      backgroundColor: CupertinoColors.extraLightBackgroundGray,
      child: CustomScrollView(
        slivers: [
          const CupertinoSliverNavigationBar(largeTitle: Text('Lists')),
          SliverFillRemaining(
            child: ValueListenableBuilder<List<ContactGroup>>(
              valueListenable: contactGroupsModel.listsNotifier,
              builder: (context, contactLists, child) {
                return CupertinoListSection.insetGrouped(
                  header: const Text('iPhone'),
                  children: [
                    for (final ContactGroup contactList in contactLists)
                      CupertinoListTile(
                        title: Text(contactList.label),
                        onTap: () => onListSelected(contactList),
                      ),
                  ],
                );
              },
            ),
          ),
        ],
      ),
    );
  }
}
```

This private widget contains the shared UI for displaying the list of contact groups. On small screens, it will be used as a page, and on large screens it will be used to fill the left column.

This widget introduces several slivers:

* `CupertinoSliverNavigationBar`: An opinionated navigation bar that collapses as the page scrolls.
* `SliverList`: A scrollable list of items.
* `SliverFillRemaining`: A sliver that takes up the remaining space in the scroll area, and whose child is a non-sliver widget.

It accepts a callback function, `onListSelected`, to handle taps, which makes it adaptable for both navigation and sidebar selection.

Now, update `ContactGroupsPage` in `lib/screens/contact_groups.dart` to use your new `_ContactGroupsView` widget:

dart

```
class ContactGroupsPage extends StatelessWidget {
  const ContactGroupsPage({super.key});
​
  @override
  Widget build(BuildContext context) {
    return _ContactGroupsView(
      selectedListId: 0,
      onListSelected: (list) {
        debugPrint(list.toString());
      },
    );
  }
}
```

This structure keeps the `ContactGroupsPage` clean and focused on its primary responsibility: navigation, which you'll learn about in the next section of this tutorial.

3### Enhance the list with icons and visual elementskeyboard\_arrow\_up

Now, add icons and contact counts to make the list more informative. Add this `_buildTrailing` helper method to your `_ContactGroupsView` class in `lib/screens/contact_groups.dart`:

dart

```
Widget _buildTrailing(List<Contact> contacts, BuildContext context) {
  final TextStyle style = CupertinoTheme.of(context).textTheme.textStyle
      .copyWith(color: CupertinoColors.systemGrey);
​
  return Row(
    mainAxisSize: MainAxisSize.min,
    children: [
      Text(contacts.length.toString(), style: style),
      const Icon(
        CupertinoIcons.forward,
        color: CupertinoColors.systemGrey3,
        size: 18,
      ),
    ],
  );
}
```

This helper creates the trailing content for each list item. It shows the contact count and a forward arrow.

Now, update the `CupertinoListSection` in `_ContactGroupsView` to use icons and the trailing helper. Update the code within the `ValueListenableBuilder.builder` callback in the `build` method:

dart

```
child: ValueListenableBuilder<List<ContactGroup>>(
  valueListenable: contactGroupsModel.listsNotifier,
  builder: (context, contactLists, child) {
    const groupIcon = Icon(
      CupertinoIcons.group,
      weight: 900,
      size: 32,
    );
​
    const pairIcon = Icon(
      CupertinoIcons.person_2,
      weight: 900,
      size: 24,
    );
​
    return CupertinoListSection.insetGrouped(
      header: const Text('iPhone'),
      children: [
        for (final ContactGroup contactList in contactLists)
          CupertinoListTile(
            leading: contactList.id == 0 ? groupIcon : pairIcon,
            title: Text(contactList.label),
            trailing: _buildTrailing(contactList.contacts, context),
            onTap: () => onListSelected(contactList),
          ),
      ],
    );
  },
),
```

The updated code now shows icons that differentiate between the main "All iPhone" group and user-created groups, along with contact counts and navigation indicators.

4### Create advanced scrolling for contactskeyboard\_arrow\_up

Next, you'll implement the contacts list page.

In the next lesson, you'll implement navigation for small screens. To see your progress on the contacts list page in the meantime, first update `lib/screens/adaptive_layout.dart` to display the contacts list page:

dart

```
​
import 'package:cupertino_ui/cupertino_ui.dart';
​
import 'contacts.dart';
​
const largeScreenMinWidth = 600;
​
class AdaptiveLayout extends StatefulWidget {
  const AdaptiveLayout({super.key});
​
  @override
  State<AdaptiveLayout> createState() => _AdaptiveLayoutState();
}
​
class _AdaptiveLayoutState extends State<AdaptiveLayout> {
  int selectedListId = 0;
​
  void _onContactListSelected(int listId) {
    setState(() {
      selectedListId = listId;
    });
  }
​
  @override
  Widget build(BuildContext context) {
    return LayoutBuilder(
      builder: (context, constraints) {
        final isLargeScreen = constraints.maxWidth > largeScreenMinWidth;
​
        if (isLargeScreen) {
          return _buildLargeScreenLayout();
        } else {
          return const ContactListsPage(listId: 0); // New, temporary
        }
      },
    );
  }
​
  Widget _buildLargeScreenLayout() {
    return CupertinoPageScaffold(
      backgroundColor: CupertinoColors.extraLightBackgroundGray,
      child: SafeArea(
        child: Row(
          children: [
            const SizedBox(width: 320, child: Text('Sidebar placeholder')),
            Container(width: 1, color: CupertinoColors.separator),
            const Expanded(child: Text('Details placeholder')),
          ],
        ),
      ),
    );
  }
}
```

Update `lib/screens/contacts.dart` by adding `_ContactListView` to the bottom of the file:

dart

```
class _ContactListView extends StatelessWidget {
  const _ContactListView({
    required this.listId,
    this.automaticallyImplyLeading = true,
  });
​
  final int listId;
  final bool automaticallyImplyLeading;
​
  @override
  Widget build(BuildContext context) {
    return CupertinoPageScaffold(
      child: ValueListenableBuilder<List<ContactGroup>>(
        valueListenable: contactGroupsModel.listsNotifier,
        builder: (context, contactGroups, child) {
          final contactList = contactGroupsModel.findContactList(listId);
​
          return CustomScrollView(
            slivers: [
              CupertinoSliverNavigationBar(
                largeTitle: Text(contactList.title),
                automaticallyImplyLeading: automaticallyImplyLeading,
              ),
              SliverFillRemaining(
                child: Center(
                  child: Text(
                    '${contactList.contacts.length} contacts in ${contactList.label}',
                  ),
                ),
              ),
            ],
          );
        },
      ),
    );
  }
}
```

Now, update `ContactListsPage` to use this view:

dart

```
class ContactListsPage extends StatelessWidget {
  const ContactListsPage({super.key, required this.listId});
​
  final int listId;
​
  @override
  Widget build(BuildContext context) {
    return _ContactListView(listId: listId);
  }
}
```

This basic implementation demonstrates how to use slivers with dynamic data in a reusable component.

5### Add search integration with sliverskeyboard\_arrow\_up

Now, enhance the contacts page with integrated search functionality UI. Update the `CustomScrollView` in `_ContactListView` to use the `CupertinoSliverNavigationBar.search` constructor instead of the default `CupertinoSliverNavigationBar` constructor:

dart

```
class _ContactListView extends StatelessWidget {
  // ···
  @override
  Widget build(BuildContext context) {
    return CupertinoPageScaffold(
      child: ValueListenableBuilder<List<ContactGroup>>(
        valueListenable: contactGroupsModel.listsNotifier,
        builder: (context, contactGroups, child) {
          final contactList = contactGroupsModel.findContactList(listId);
​
          return CustomScrollView(
            slivers: [
              // Now using a search bar:
              CupertinoSliverNavigationBar.search(
                largeTitle: Text(contactList.title),
                searchField: const CupertinoSearchTextField(
                  suffixIcon: Icon(CupertinoIcons.mic_fill),
                  suffixMode: OverlayVisibilityMode.always,
                ),
              ),
              SliverFillRemaining(
                child: Center(
                  child: Text(
                    '${contactList.contacts.length} contacts in ${contactList.label}',
                  ),
                ),
              ),
            ],
          );
        },
      ),
    );
  }
}
```

The `CupertinoSliverNavigationBar.search` constructor provides integrated search functionality. As you scroll down, the search field smoothly transitions into the collapsed navigation bar.

6### Create alphabetized contact sectionskeyboard\_arrow\_up

Real-world contact apps organize contacts alphabetically. To do this, create sections for each letter. Add the following widget to the bottom of your `contacts.dart` file. This widget doesn't contain any slivers.

dart

```
class ContactListSection extends StatelessWidget {
  const ContactListSection({
    super.key,
    required this.lastInitial,
    required this.contacts,
  });
​
  final String lastInitial;
  final List<Contact> contacts;
​
  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsetsDirectional.fromSTEB(20, 0, 20, 0),
      child: Column(
        children: [
          const SizedBox(height: 15),
          Align(
            alignment: AlignmentDirectional.bottomStart,
            child: Text(
              lastInitial,
              style: const TextStyle(
                color: CupertinoColors.systemGrey,
                fontSize: 15,
                fontWeight: FontWeight.w700,
              ),
            ),
          ),
          CupertinoListSection(
            backgroundColor: CupertinoColors.systemBackground,
            dividerMargin: 0,
            additionalDividerMargin: 0,
            topMargin: 4,
            children: [
              for (final Contact contact in contacts)
                CupertinoListTile(
                  padding: const EdgeInsets.all(0),
                  title: Text('${contact.firstName} ${contact.lastName}'),
                ),
            ],
          ),
        ],
      ),
    );
  }
}
```

This widget creates the familiar alphabetized sections that you see in the iOS Contacts app.

7### Use `SliverList` for the alphabetized sectionskeyboard\_arrow\_up

Now, replace the placeholder content in `_ContactListView` with the alphabetized sections:

dart

```
class _ContactListView extends StatelessWidget {
  // ···
  @override
  Widget build(BuildContext context) {
    return CupertinoPageScaffold(
      child: ValueListenableBuilder<List<ContactGroup>>(
        valueListenable: contactGroupsModel.listsNotifier,
        builder: (context, contactGroups, child) {
          final contactList = contactGroupsModel.findContactList(listId);
​
          final contacts = contactList.alphabetizedContacts;
​
          return CustomScrollView(
            slivers: [
              CupertinoSliverNavigationBar.search(
                largeTitle: Text(contactList.title),
                automaticallyImplyLeading: automaticallyImplyLeading,
                searchField: const CupertinoSearchTextField(
                  suffixIcon: Icon(CupertinoIcons.mic_fill),
                  suffixMode: OverlayVisibilityMode.always,
                ),
              ),
              SliverList.list(
                children: [
                  const SizedBox(height: 20),
                  ...contacts.keys.map(
                    (initial) => ContactListSection(
                      lastInitial: initial,
                      contacts: contacts[initial]!,
                    ),
                  ),
                ],
              ),
            ],
          );
        },
      ),
    );
  }
}
```

`SliverList.list` allows you to provide a list of widgets that become part of the scrollable content. This is the simplest way to add a list of normal widgets to a scrollable sliver area.

In the next lesson, you'll learn about stack-based navigation and update the UI on small screens to navigate between the contacts list view and the contacts view.

8### Reviewkeyboard\_arrow\_up

view\_day Understood slivers and how they differ from widgets keyboard\_arrow\_up

Slivers are specialized widgets for scrollable layouts. They can only be direct children of scroll views like `CustomScrollView`. In `CustomScrollView` and other sliver contexts, regular widgets must be wrapped in `SliverToBoxAdapter` or `SliverFillRemaining`.

unfold\_more Built scrollable layouts with CustomScrollView keyboard\_arrow\_up

`CustomScrollView` lets you compose multiple slivers together. You used `CupertinoSliverNavigationBar`, `SliverFillRemaining`, and `SliverList` to create sophisticated scrollable interfaces.

search Created collapsible navigation bars with search keyboard\_arrow\_up

You used the `CupertinoSliverNavigationBar.search` constructor to create a collapsible navigation bar with integrated search functionality.

sort\_by\_alpha Organized contacts in alphabetized sections keyboard\_arrow\_up

You created `ContactListSection` widgets grouped by last name initial, then used `SliverList.list` to add them to the scrollable area. This mirrors the familiar iOS Contacts app experience.

9### Test yourselfkeyboard\_arrow\_up

### Slivers Quiz

1 / 2

**What is the key difference between slivers and regular widgets?**

1. Slivers are faster to render than regular widgets.

   Not quite.

   Both are optimized; the difference is their purpose and context.

2. Slivers are specialized widgets designed for scrollable layouts and can only be direct children of scroll views.

   That's right!

   Slivers work within scroll views like CustomScrollView; regular widgets can be used anywhere.

3. Slivers can have an unlimited number of children.

   Not quite.

   Some slivers like SliverList can have many children, but that's not what distinguishes them.

4. Slivers automatically handle user gestures.

   Not quite.

   Gesture handling is separate; slivers are about scrollable layout composition.

**How do you use a regular widget inside a CustomScrollView's slivers list?**

1. Just add it directly; CustomScrollView accepts any widget.

   Not quite.

   CustomScrollView only accepts slivers; regular widgets must be wrapped.

2. Wrap it in a SliverToBoxAdapter or SliverFillRemaining.

   That's right!

   These adapters convert regular widgets into slivers so they can be used in sliver contexts.

3. Convert the widget to a sliver by calling `.toSliver()` on it.

   Not quite.

   There's no `.toSliver()` method; you use adapter widgets like SliverToBoxAdapter.

4. Pass it to the `child` property instead of `slivers`.

   Not quite.

   CustomScrollView uses the slivers property; there's no child property for this purpose.

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-09-29. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/learn/pathway/tutorial/slivers.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/learn/pathway/tutorial/slivers\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/learn/pathway/tutorial/slivers.md "Report an issue with this page").

----
url: https://docs.flutter.dev/data-and-backend/state-mgmt/intro.md
----

\# State management > How to structure an app to manage the state of the data flowing through it. :::note If you have written a mobile app using Flutter and wonder why your app's state is lost on a restart, check out \[Restore state on Android]\[] or \[Restore state on iOS]\[]. ::: \[Restore state on Android]: /platform-integration/android/restore-state-android \[Restore state on iOS]: /platform-integration/ios/restore-state-ios \_If you are already familiar with state management in reactive apps, you can skip this section, though you might want to review the \[list of different approaches]\[].\_ As you explore Flutter, there comes a time when you need to share application state between screens, across your app. There are many approaches you can take, and many questions to think about. In the following pages, you will learn the basics of dealing with state in Flutter apps. \[list of different approaches]: /data-and-backend/state-mgmt/options

----
url: https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.51.1.md
----

\# DevTools 2.51.1 release notes The 2.51.1 release of the Dart and Flutter DevTools includes the following changes among other general improvements. To learn more about DevTools, check out the \[DevTools overview]\(/tools/devtools/overview). ## General updates - Flutter beta channel users were opted into the DevTools-on-Wasm experiment. All other users can still enable the Wasm-compiled DevTools from the settings dialog. - \[#9455]\(https\://github.com/flutter/devtools/pull/9455) - Added a horizontal scrollbar to data tables to help with navigation. - \[#9482]\(https\://github.com/flutter/devtools/pull/9482) - Made it possible to resize data table columns by dragging the header separators. - \[#9485]\(https\://github.com/flutter/devtools/pull/9485) ## Inspector updates - Fixed an issue where selecting a widget with the Inspector would open the widget definition file instead of the user's project file. - \[#176530]\(https\://github.com/flutter/flutter/pull/176530) ## Network profiler updates - Fixed layout of the "error count" badge in the tab name. - \[#9470]\(https\://github.com/flutter/devtools/pull/9470) - Fixed display of "Response Headers" and "Request Headers" when there are no headers. - \[#9492]\(https\://github.com/flutter/devtools/pull/9492) - Added a banner to clearly indicate when DevTools is not logging network requests. - \[#9495]\(https\://github.com/flutter/devtools/pull/9495) ## VS Code updates - Fixed issue preventing shortcuts like \`Cmd\`+\`C\` and \`Cmd\`+\`V\` from working when DevTools was embedded inside VS Code on macOS. - \[#9472]\(https\://github.com/flutter/devtools/pull/9472) ## Full commit history To find a complete list of changes in this release, check out the \[DevTools git log]\(https\://github.com/flutter/devtools/tree/v2.51.1).

----
url: https://docs.flutter.dev/cookbook/animation/opacity-animation.md
----

\# Fade a widget in and out > How to fade a widget in and out.

<!--?code-excerpt path-base="cookbook/animation/opacity_animation/"?-->

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, fade elements in and out with an opacity animation to create a smooth experience. The \[\`AnimatedOpacity\`]\[] widget makes it easy to perform opacity animations. This recipe uses the following steps: 1. Create a box to fade in and out. 1. Define a \`StatefulWidget\`. 1. Display a button that toggles the visibility. 1. Fade the box in and out. ## 1. Create a box to fade in and out First, create something to fade in and out. For this example, draw a green box on screen.

<!--?code-excerpt "lib/main.dart (Container)" replace="/^child: //g;/\),$/)/g"?-->

\`\`\`dart Container(width: 200, height: 200, color: Colors.green) \`\`\` ## 2. Define 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 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 have one piece of data: a boolean representing whether the green box is visible. To construct a \`StatefulWidget\`, create two classes: A \`StatefulWidget\` and a corresponding \`State\` class. Pro tip: The Flutter plugins for Android Studio and VS Code include the \`stful\` snippet to quickly generate this code.

<!--?code-excerpt "lib/starter.dart (Starter)" remove="return Container();"?-->

\`\`\`dart // 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 { const MyHomePage({super.key, required this.title}); final String title; @override State createState() => \_MyHomePageState(); } // 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. bool \_visible = true; @override Widget build(BuildContext context) { // The green box goes here with some other Widgets. } } \`\`\` ## 3. Display a button that toggles the visibility Now that you have some data to determine whether the green box should be visible, you need a way to update that data. In this example, if the box is visible, hide it. If the box is hidden, show it. To handle 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. To learn more about working with user input, consult the \[Gestures]\[] section of the cookbook.

<!--?code-excerpt "lib/main.dart (FAB)" replace="/^floatingActionButton: //g;/^\),$/)/g"?-->

\`\`\`dart FloatingActionButton( onPressed: () { // Call setState. This tells Flutter to rebuild the // UI with the changes. setState(() { \_visible = !\_visible; }); }, tooltip: 'Toggle Opacity', child: const Icon(Icons.flip), ) \`\`\` ## 4. Fade the box in and out You have a green box on screen and a button to toggle the visibility to \`true\` or \`false\`. How to fade the box in and out? With an \[\`AnimatedOpacity\`]\[] widget. 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 this case, the green box.

<!--?code-excerpt "lib/main.dart (AnimatedOpacity)" replace="/^child: //g;/^\),$/)/g"?-->

\`\`\`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). opacity: \_visible ? 1.0 : 0.0, duration: const Duration(milliseconds: 500), // The green box must be a child of the AnimatedOpacity widget. child: Container(width: 200, height: 200, color: Colors.green), ) \`\`\` ## Interactive example

<!--?code-excerpt "lib/main.dart"?-->

\`\`\`dartpad title="Implicit Animation Opacity DartPad hands-on example" run="true" import 'package:flutter/material.dart'; void main() => runApp(const MyApp()); class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { const appTitle = 'Opacity Demo'; return const MaterialApp( title: appTitle, home: MyHomePage(title: appTitle), ); } } // 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 { const MyHomePage({super.key, required this.title}); final String title; @override State createState() => \_MyHomePageState(); } // 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 bool \_visible = true; @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text(widget.title)), 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). opacity: \_visible ? 1.0 : 0.0, duration: const Duration(milliseconds: 500), // The green box must be a child of the AnimatedOpacity widget. child: Container(width: 200, height: 200, color: Colors.green), ), ), floatingActionButton: FloatingActionButton( onPressed: () { // Call setState. This tells Flutter to rebuild the // UI with the changes. setState(() { \_visible = !\_visible; }); }, tooltip: 'Toggle Opacity', child: const Icon(Icons.flip), ), ); } } \`\`\` \[\`AnimatedOpacity\`]: https\://api.flutter.dev/flutter/widgets/AnimatedOpacity-class.html \[Gestures]: /cookbook/gestures \[\`StatefulWidget\`]: https\://api.flutter.dev/flutter/widgets/StatefulWidget-class.html \[\`setState()\`]: https\://api.flutter.dev/flutter/widgets/State/setState.html

----
url: https://docs.flutter.dev/deployment/linux
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Build and release a Linux app to the Snap Store](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

During a typical development cycle, you test an app using `flutter run` at the command line, or by using the **Run** and **Debug** options in your IDE. By default, Flutter builds a *debug* version of your app.

When you're ready to prepare a *release* version of your app, for example to [publish to the Snap Store](https://snapcraft.io/store) or an [alternative channel](#additional-deployment-resources), this page can help.

## Prerequisites

[#](#prerequisites)

To build and publish to the Snap Store, you need the following components:

* [Ubuntu](https://ubuntu.com/download/desktop) OS, 18.04 LTS (or higher)
* [Snapcraft](https://snapcraft.io/snapcraft) command line tool
* [LXD container manager](https://linuxcontainers.org/lxd/downloads/)

## Set up the build environment

[#](#set-up-the-build-environment)

Use the following instructions to set up your build environment.

### Install snapcraft

[#](#install-snapcraft)

At the command line, run the following:

```
$ sudo snap install snapcraft --classic
```

### Install LXD

[#](#install-lxd)

To install LXD, use the following command:

```
$ sudo snap install lxd
```

LXD is required during the snap build process. Once installed, LXD needs to be configured for use. The default answers are suitable for most use cases.

```
$ sudo lxd init
Would you like to use LXD clustering? (yes/no) [default=no]:
Do you want to configure a new storage pool? (yes/no) [default=yes]:
Name of the new storage pool [default=default]:
Name of the storage backend to use (btrfs, dir, lvm, zfs, ceph) [default=zfs]:
Create a new ZFS pool? (yes/no) [default=yes]:
Would you like to use an existing empty disk or partition? (yes/no) [default=no]:
Size in GB of the new loop device (1GB minimum) [default=5GB]:
Would you like to connect to a MAAS server? (yes/no) [default=no]:
Would you like to create a new local network bridge? (yes/no) [default=yes]:
What should the new bridge be called? [default=lxdbr0]:
What IPv4 address should be used? (CIDR subnet notation, "auto" or "none") [default=auto]:
What IPv6 address should be used? (CIDR subnet notation, "auto" or "none") [default=auto]:
Would you like LXD to be available over the network? (yes/no) [default=no]:
Would you like stale cached images to be updated automatically? (yes/no) [default=yes]
Would you like a YAML "lxd init" preseed to be printed? (yes/no) [default=no]:
```

On the first run, LXD might not be able to connect to its socket:

```
An error occurred when trying to communicate with the 'LXD'
provider: cannot connect to the LXD socket
('/var/snap/lxd/common/lxd/unix.socket').
```

This means you need to add your username to the LXD (lxd) group, so log out of your session and then log back in:

```
$ sudo usermod -a -G lxd <your username>
```

## Overview of snapcraft

[#](#overview-of-snapcraft)

The `snapcraft` tool builds snaps based on the instructions listed in a `snapcraft.yaml` file. To get a basic understanding of snapcraft and its core concepts, take a look at the [Snap documentation](https://snapcraft.io/docs) and the [Introduction to snapcraft](https://snapcraft.io/blog/introduction-to-snapcraft). Additional links and information are listed at the bottom of this page.

## Flutter snapcraft.yaml example

[#](#flutter-snapcraft-yaml-example)

Place the YAML file in your Flutter project under `<project root>/snap/snapcraft.yaml`. (And remember that YAML files are sensitive to white space!) For example:

yaml

```
name: super-cool-app
version: 0.1.0
summary: Super Cool App
description: Super Cool App that does everything!
​
confinement: strict
base: core22
grade: stable
​
slots:
  dbus-super-cool-app: # adjust accordingly to your app name
    interface: dbus
    bus: session
    name: org.bar.super_cool_app # adjust accordingly to your app name and
​
apps:
  super-cool-app:
    command: super_cool_app
    extensions: [gnome] # gnome includes the libraries required by flutter
    plugs:
    - network
    slots:
      - dbus-super-cool-app
parts:
  super-cool-app:
    source: .
    plugin: flutter
    flutter-target: lib/main.dart # The main entry-point file of the application
```

The following sections explain the various pieces of the YAML file.

### Metadata

[#](#metadata)

This section of the `snapcraft.yaml` file defines and describes the application. The snap version is derived (adopted) from the build section.

yaml

```
name: super-cool-app
version: 0.1.0
summary: Super Cool App
description: Super Cool App that does everything!
```

### Grade, confinement, and base

[#](#grade-confinement-and-base)

This section defines how the snap is built.

yaml

```
confinement: strict
base: core22
grade: stable
```

* **Grade**

  Specifies the quality of the snap; this is relevant for the publication step later.

* **Confinement**

  Specifies what level of system resource access the snap will have once installed on the end-user system. Strict confinement limits the application access to specific resources (defined by plugs in the `app` section).

* **Base**

  Snaps are designed to be self-contained applications, and therefore, they require their own private core root filesystem known as `base`. The `base` keyword specifies the version used to provide the minimal set of common libraries, and mounted as the root filesystem for the application at runtime.

### Apps

[#](#apps)

This section defines the application(s) that exist inside the snap. There can be one or more applications per snap. This example has a single application—super\_cool\_app.

yaml

```
apps:
  super-cool-app:
    command: super_cool_app
    extensions: [gnome]
```

* **Command**

  Points to the binary, relative to the snap's root, and runs when the snap is invoked.

* **Extensions**

  A list of one or more extensions. Snapcraft extensions are reusable components that can expose sets of libraries and tools to a snap at build and runtime, without the developer needing to have specific knowledge of included frameworks. The `gnome` extension exposes the GTK 3 libraries to the Flutter snap. This ensures a smaller footprint and better integration with the system.

* **Plugs**

  A list of one or more plugs for system interfaces. These are required to provide necessary functionality when snaps are strictly confined. This Flutter snap needs access to the network.

* **DBus interface**

  The [DBus interface](https://snapcraft.io/docs/dbus-interface) provides a way for snaps to communicate over DBus. The snap providing the DBus service declares a slot with the well-known DBus name and which bus it uses. Snaps wanting to communicate with the providing snap's service declare a plug for the providing snap. Note that a snap declaration is needed for your snap to be delivered via the snap store and claim this well-known DBus name (simply upload the snap to the store and request a manual review and a reviewer will take a look).

  When a providing snap is installed, snapd will generate security policy that will allow it to listen on the well-known DBus name on the specified bus. If the system bus is specified, snapd will also generate DBus bus policy that allows 'root' to own the name and any user to communicate with the service. Non-snap processes are allowed to communicate with the providing snap following traditional permissions checks. Other (consuming) snaps might only communicate with the providing snap by connecting the snaps' interface.

```
dbus-super-cool-app: # adjust accordingly to your app name
  interface: dbus
  bus: session
  name: dev.site.super_cool_app
```

### Parts

[#](#parts)

This section defines the sources required to assemble the snap.

Parts can be downloaded and built automatically using plugins. Similar to extensions, snapcraft can use various plugins (such as Python, C, Java, and Ruby) to assist in the building process. Snapcraft also has some special plugins.

* **nil** plugin

  Performs no action and the actual build process is handled using a manual override.

* **flutter** plugin

  Provides the necessary Flutter SDK tools so you can use it without having to manually download and set up the build tools.

yaml

```
parts:
  super-cool-app:
    source: .
    plugin: flutter
    flutter-target: lib/main.dart # The main entry-point file of the application
```

## Desktop file and icon

[#](#desktop-file-and-icon)

Desktop entry files are used to add an application to the desktop menu. These files specify the name and icon of your application, the categories it belongs to, related search keywords and more. These files have the extension .desktop and follow the XDG Desktop Entry Specification version 1.1.

### Flutter super-cool-app.desktop example

[#](#flutter-super-cool-app-desktop-example)

Place the .desktop file in your Flutter project under `<project root>/snap/gui/super-cool-app.desktop`.

**Notice**: icon and .desktop file name must be the same as your app name in yaml file!

For example:

yaml

```
[Desktop Entry]
Name=Super Cool App
Comment=Super Cool App that does everything
Exec=super-cool-app
Icon=${SNAP}/meta/gui/super-cool-app.png # Replace name with your app name.
Terminal=false
Type=Application
Categories=Education; # Adjust accordingly your snap category.
```

Place your icon with .png extension in your Flutter project under `<project root>/snap/gui/super-cool-app.png`.

## Build the snap

[#](#build-the-snap)

Once the `snapcraft.yaml` file is complete, run `snapcraft` as follows from the root directory of the project.

To use the Multipass VM backend:

```
$ snapcraft
```

To use the LXD container backend:

```
$ snapcraft --use-lxd
```

## Test the snap

[#](#test-the-snap)

Once the snap is built, you'll have a `<name>.snap` file in your root project directory.

$ sudo snap install ./super-cool-app\_0.1.0\_amd64.snap --dangerous

## Publish

[#](#publish)

You can now publish the snap. The process consists of the following:

1. Create a developer account at [snapcraft.io](https://snapcraft.io/), if you haven't already done so.
2. Register the app's name. Registration can be done either using the Snap Store Web UI portal, or from the command line, as follows:
   ```
   $ snapcraft login
   $ snapcraft register
   ```
3. Release the app. After reading the next section to learn about selecting a Snap Store channel, push the snap to the store:
   ```
   $ snapcraft upload --release=<channel> <file>.snap
   ```

### Snap Store channels

[#](#snap-store-channels)

The Snap Store uses channels to differentiate among different versions of snaps.

The `snapcraft upload` command uploads the snap file to the store. However, before you run this command, you need to learn about the different release channels. Each channel consists of three components:

* **Track**

  All snaps must have a default track called latest. This is the implied track unless specified otherwise.

* **Risk**

  Defines the readiness of the application. The risk levels used in the snap store are: `stable`, `candidate`, `beta`, and `edge`.

* **Branch**

  Allows creation of short-lived snap sequences to test bug-fixes.

### Snap Store automatic review

[#](#snap-store-automatic-review)

The Snap Store runs several automated checks against your snap. There might also be a manual review, depending on how the snap was built, and if there are any specific security concerns. If the checks pass without errors, the snap becomes available in the store.

## Additional snapcraft resources

[#](#additional-snapcraft-resources)

You can learn more from the following links on the [snapcraft.io](https://snapcraft.io/) site:

* [Channels](https://docs.snapcraft.io/channels)
* [Environment variables](https://snapcraft.io/docs/environment-variables)
* [Interface management](https://snapcraft.io/docs/interface-management)
* [Parts environment variables](https://snapcraft.io/docs/parts-environment-variables)
* [Releasing to the Snap Store](https://snapcraft.io/docs/releasing-to-the-snap-store)
* [Snapcraft extensions](https://snapcraft.io/docs/snapcraft-extensions)
* [Supported plugins](https://snapcraft.io/docs/supported-plugins)

## Additional deployment resources

[#](#additional-deployment-resources)

### [fastforge](https://github.com/fastforgedev/fastforge)

[#](#fastforge)

> An all-in-one Flutter application packaging and distribution tool, providing you with a one-stop solution to meet various distribution needs.

Supports popular packaging formats like, appimage, deb, pacman, rpm, and more.

### [flatpak-flutter](https://github.com/TheAppgineer/flatpak-flutter)

[#](#flatpak-flutter)

> Flatpak manifest tooling for the offline build of Flutter apps.

Supports Flatpak preparation for publishing on [Flathub](https://flathub.org).

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-07-31. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/deployment/linux.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/deployment/linux\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/deployment/linux.md "Report an issue with this page").

----
url: https://docs.flutter.dev/deployment/windows.md
----

\# Build and release a Windows desktop app > How to release a Flutter app to the Microsoft Store. One convenient approach to distributing Windows apps is the \[Microsoft Store]\[microsoftstore]. This guide provides a step-by-step walkthrough of packaging and deploying a Flutter app in this way. :::note You are not required to publish Windows apps through the Microsoft Store, particularly if you prefer more control over the distribution experience or don't want to deal with the certification process. The Microsoft documentation includes more information about traditional installation approaches, including \[Windows Installer]\[msidocs]. ::: ## Preliminaries Before beginning the process of releasing a Flutter Windows desktop app to the Microsoft Store, first confirm that it satisfies \[Microsoft Store Policies]\[storepolicies]. Also, you must join the \[Microsoft Partner Network]\[microsoftpartner] to be able to submit apps. ## Set up your application in the Partner Center Manage an application's life cycle in the \[Microsoft Partner Center]\[microsoftpartner]. First, reserve the application name and ensure that the required rights to the name exist. Once the name is reserved, the application will be provisioned for services (such as push notifications), and you can start adding add-ons. Options such as pricing, availability, age ratings, and category have to be configured together with the first submission and are automatically retained for the subsequent submissions. ## Packaging and deployment In order to publish an application to Microsoft Store, you must first package it. The valid formats are \*\*.msix\*\*, \*\*.msixbundle\*\*, \*\*.msixupload\*\*, \*\*.appx\*\*, \*\*.appxbundle\*\*, \*\*.appxupload\*\*, and \*\*.xap\*\*. ### Manual packaging and deployment for the Microsoft Store Check out \[MSIX packaging]\[msix packaging] to learn about packaging Flutter Windows desktop applications. Note that each product has a unique identity, which the Store assigns. If the package is being built manually, you have to include its identity details manually during the packaging. The essential information can be retrieved from the Partner Center using the following instructions: 1. In the Partner Center, navigate to the application. 2. Select \*\*Product management\*\*. 3. Retrieve the package identity name, publisher, and publisher display name by clicking \*\*Product identity\*\*. After manually packaging the application, manually submit it to the \[Microsoft Partner Center]\[microsoftpartner]. You can do this by creating a new submission, navigating to \*\*Packages\*\*, and uploading the created application package. ### Continuous deployment In addition to manually creating and deploying the package, you can automate the build, package, versioning, and deployment process using CI/CD tooling after having submitted the application to the Microsoft Store for the first time. #### Codemagic CI/CD \[Codemagic CI/CD]\[codemagic] uses the \[\`msix\` pub package]\[msix package] to package Flutter Windows desktop applications. For Flutter applications, use either the \[Codemagic Workflow Editor]\[cmworkfloweditor] or \[codemagic.yaml]\[cmyaml] to package the application and deploy it to the Microsoft Partner Center. Additional options (such as the list of capabilities and language resources contained in the package) can be configured using this package. For publishing, Codemagic uses the \[Partner Center submission API]\[partnercenterapi]; so, Codemagic requires \[associating the Azure Active Directory and Partner Center accounts]\[azureadassociation]. #### GitHub Actions CI/CD GitHub Actions can use the \[Microsoft Dev Store CLI]\(https\://learn.microsoft.com/windows/apps/publish/msstore-dev-cli/overview) to package applications into an MSIX and publish them to the Microsoft Store. The \[setup-msstore-cli]\(https\://github.com/microsoft/setup-msstore-cli) GitHub Action installs the cli so that the Action can use it for packaging and publishing. As packaging the MSIX uses the \[\`msix\` pub package]\[msix package], the project's \`pubspec.yaml\` must contain an appropriate \`msix\_config\` node. You must create an Azure AD directory from the Dev Center with \[global administrator permission]\(https\://azure.microsoft.com/documentation/articles/active-directory-assign-admin-roles/). The GitHub Action requires environment secrets from the partner center. \`AZURE\_AD\_TENANT\_ID\`, \`AZURE\_AD\_ClIENT\_ID\`, and \`AZURE\_AD\_CLIENT\_SECRET\` are visible on the Dev Center following the instructions for the \[Windows Store Publish Action]\(https\://github.com/marketplace/actions/windows-store-publish#obtaining-your-credentials). You also need the \`SELLER\_ID\` secret, which can be found in the Dev Center under \*\*Account Settings\*\* > \*\*Organization Profile\*\* > \*\*Legal Info\*\*. The application must already be present in the Microsoft Dev Center with at least one complete submission, and \`msstore init\` must be run once within the repository before the Action can be performed. Once complete, running \[\`msstore package .\`]\(https\://learn.microsoft.com/windows/apps/publish/msstore-dev-cli/package-command) and \[\`msstore publish\`]\(https\://learn.microsoft.com/windows/apps/publish/msstore-dev-cli/publish-command) in a GitHub Action packages the application into an MSIX and uploads it to a new submission on the dev center. The steps necessary for MSIX publishing resemble the following \`\`\`yaml - uses: microsoft/setup-msstore-cli\@v1 - name: Configure the Microsoft Store CLI run: msstore reconfigure --tenantId $ --clientId $ --clientSecret $ --sellerId $ - name: Install Dart dependencies run: flutter pub get - name: Create MSIX package run: msstore package . - name: Publish MSIX to the Microsoft Store run: msstore publish -v \`\`\` ## Updating the app's version number For apps published to the Microsoft Store, the version number must be set during the packaging process. The default version number of the app is \`1.0.0.0\`. :::note Microsoft Store apps are not allowed to have a Version with a revision number other than zero. Therefore, the last number of the version must remain zero for all releases. Ensure that you follow Microsoft's \[versioning guidelines]\[windowspackageversioning]. ::: For apps not published to the Microsoft Store, you can set the app's executable's file and product versions. The executable's default file version is \`1.0.0.1\`, and its default product version is \`1.0.0+1\`. To update these, navigate to the \`pubspec.yaml\` file and update the following line: \`\`\`yaml version: 1.0.0+1 \`\`\` The build name is three numbers separated by dots, followed by an optional build number that is separated by a \`+\`. In the example above, the build name is \`1.0.0\` and the build number is \`1\`. The build name becomes the first three numbers of the file and product versions, while the build number becomes the fourth number of the file and product versions. Both the build name and number can be overridden in \`flutter build windows\` by specifying \`--build-name\` and \`--build-number\`, respectively. ## Add app icons To update the icon of a Flutter Windows desktop application before packaging use the following instructions: 1. In the Flutter project, navigate to \*\*windows\runner\resources\*\*. 2. Replace the \*\*app\_icon.ico\*\* with the desired icon. 3. If the name of the icon is other than \*\*app\_icon.ico\*\*, proceed to change the \*\*IDI\_APP\_ICON\*\* value in the \*\*windows\runner\Runner.rc\*\* file to point to the new path. When packaging with the \[\`msix\` pub package]\[msix package], the logo path can also be configured inside the \`pubspec.yaml\` file. To update the application image in the Store listing, navigate to the Store listing step of the submission and select Store logos. From there, you can upload the logo with the size of 300 x 300 pixels. All uploaded images are retained for subsequent submissions. ## Validating the application package Before publication to the Microsoft Store, first validate the application package locally. \[Windows App Certification Kit]\[windowsappcertification] is a tool included in the Windows Software Development Kit (SDK). To validate the application: 1. Launch Windows App Cert Kit. 2. Select the Flutter Windows desktop package (\*\*.msix\*\*, \*\*.msixbundle\*\*, etc.). 3. Choose a destination for the test report. The report might contain important warnings and information, even if the certification passes. \[azureadassociation]: https\://docs.microsoft.com/windows/uwp/publish/associate-azure-ad-with-partner-center \[cmworkfloweditor]: https\://docs.codemagic.io/flutter-publishing/publishing-to-microsoft-store/ \[cmyaml]: https\://docs.codemagic.io/yaml-publishing/microsoft-store/ \[codemagic]: https\://codemagic.io/start/ \[microsoftstore]: https\://www\.microsoft.com/store/apps/windows \[msidocs]: https\://docs.microsoft.com/en-us/windows/win32/msi/windows-installer-portal \[microsoftpartner]: https\://partner.microsoft.com/ \[msix package]: https\://pub.dev/packages/msix \[msix packaging]: /platform-integration/windows/building#msix-packaging \[partnercenterapi]: https\://docs.microsoft.com/azure/marketplace/azure-app-apis \[storepolicies]: https\://docs.microsoft.com/windows/uwp/publish/store-policies/ \[visualstudiopackaging]: https\://docs.microsoft.com/windows/msix/package/packaging-uwp-apps \[visualstudiosubmission]: https\://docs.microsoft.com/windows/msix/package/packaging-uwp-apps#automate-store-submissions \[windowspackageversioning]: https\://docs.microsoft.com/windows/uwp/publish/package-version-numbering \[windowsappcertification]: https\://docs.microsoft.com/windows/uwp/debug-test-perf/windows-app-certification-kit \[version migration guide]: /release/breaking-changes/windows-version-information

----
url: https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.31.0.md
----

\# DevTools 2.31.0 release notes > Release notes for Dart and Flutter DevTools version 2.31.0. The 2.31.0 release of the Dart and Flutter DevTools includes the following changes among other general improvements. To learn more about DevTools, check out the \[DevTools overview]\(https\://docs.flutter.dev/tools/devtools). ## General updates \* Added a new feature for deep link validation, supporting deep link web checks on Android. - \[#6935]\(https\://github.com/flutter/devtools/pull/6935) \* Added the basic plumbing to allow connections to a Dart Tooling Daemon. - \[#7009]\(https\://github.com/flutter/devtools/pull/7009) \* Made table text selectable \[#6919]\(https\://github.com/flutter/devtools/pull/6919) ## Inspector updates \* When done typing in the search field, the next selection is now automatically selected - \[#6677]\(https\://github.com/flutter/devtools/pull/6677) \* Added link to package directory documentation, from the inspect settings dialog - \[#6825]\(https\://github.com/flutter/devtools/pull/6825) !\[Link to documentation]\(/assets/images/docs/tools/devtools/release-notes/images-2.31.0/link-to-doc.png "Link to documentation") \* Fix bug where widgets owned by the Flutter framework were showing up in the widget tree view - \[#6857]\(https\://github.com/flutter/devtools/pull/6857) \* Only cache pub root directories added by the user - \[#6897]\(https\://github.com/flutter/devtools/pull/6897) \* Remove Flutter pub root if it was accidentally cached - \[#6911]\(https\://github.com/flutter/devtools/pull/6911) ## Performance updates \* Changed raster layer preview background to a checkerboard. - \[#6827]\(https\://github.com/flutter/devtools/pull/6827) ## CPU profiler updates \* Added hover cards to show sampling rate for the item in drop down. - \[#7010]\(https\://github.com/flutter/devtools/pull/7010) !\[Sampling rate for dropdown]\(/assets/images/docs/tools/devtools/release-notes/images-2.31.0/hover-for-dropdown.png "Sampling rate for dropdown") ## Debugger updates \* Highlight \`extension type\` as a declaration keyword, highlight the \`$\` in identifier interpolation as part of the interpolation, and properly highlight comments within type arguments. - \[6837]\(https\://github.com/flutter/devtools/pull/6837) ## Logging updates \* Added scrollbar to details pane. - \[#6917]\(https\://github.com/flutter/devtools/pull/6917) ## VS Code Sidebar updates \* Fixed an issue that prevented the VS code sidebar from loading in recent beta/master builds. - \[#6984]\(https\://github.com/flutter/devtools/pull/6984) ## DevTools Extension updates \* Fixed a couple bugs preventing Dart server apps from connecting to DevTools extensions. - \[#6982]\(https\://github.com/flutter/devtools/pull/6982), \[#6993]\(https\://github.com/flutter/devtools/pull/6993) ## Full commit history To find a complete list of changes in this release, check out the \[DevTools git log]\(https\://github.com/flutter/devtools/tree/v2.31.0).

----
url: https://docs.flutter.dev/platform-integration/linux
----

[Build Linux apps with Flutter](/platform-integration/linux/building)

[Platform-specific considerations when building for Linux with Flutter.](/platform-integration/linux/building)

[Set up Linux development](/platform-integration/linux/setup)

[Configure your development environment to run, build, and deploy Flutter apps for Linux desktop.](/platform-integration/linux/setup)

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/platform-integration/linux/index.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/platform-integration/linux\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/platform-integration/linux/index.md "Report an issue with this page").

----
url: https://docs.flutter.dev/cookbook/gestures/ripples.md
----

\# Add Material touch ripples > How to implement ripple animations.

<!--?code-excerpt path-base="cookbook/gestures/ripples/"?-->

Widgets that follow the Material Design guidelines display a ripple animation when tapped. Flutter provides the \[\`InkWell\`]\[] widget to perform this effect. Create a ripple effect using the following steps: 1. Create a widget that supports tap. 2. Wrap it in an \`InkWell\` widget to manage tap callbacks and ripple animations.

<!--?code-excerpt "lib/main.dart (InkWell)" replace="/return //g;/^\);$/)/g"?-->

\`\`\`dart // The InkWell wraps the custom flat button widget. InkWell( // When the user taps the button, show a snackbar. onTap: () { ScaffoldMessenger.of(context) .showSnackBar(const SnackBar(content: Text('Tap'))); }, child: const Padding( padding: EdgeInsets.all(12), child: Text('Flat Button'), ), ) \`\`\` ## Interactive example

<!--?code-excerpt "lib/main.dart"?-->

\`\`\`dartpad title="Flutter Material ripples hands-on example in DartPad" run="true" import 'package:flutter/material.dart'; void main() => runApp(const MyApp()); class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { const title = 'InkWell Demo'; return const MaterialApp( title: title, home: MyHomePage(title: title), ); } } class MyHomePage extends StatelessWidget { final String title; const MyHomePage({super.key, required this.title}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text(title)), body: const Center(child: MyButton()), ); } } class MyButton extends StatelessWidget { const MyButton({super.key}); @override Widget build(BuildContext context) { // The InkWell wraps the custom flat button widget. return InkWell( // When the user taps the button, show a snackbar. onTap: () { ScaffoldMessenger.of(context) .showSnackBar(const SnackBar(content: Text('Tap'))); }, child: const Padding( padding: EdgeInsets.all(12), child: Text('Flat Button'), ), ); } } \`\`\` \[\`InkWell\`]: https\://pub.dev/documentation/material\_ui/latest/material\_ui/InkWell-class.html

----
url: https://docs.flutter.dev/ui/navigation/url-strategies
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Configuring the URL strategy on the web](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

Flutter web apps support two ways of configuring URL-based navigation on the web:

* **Hash (default)**

  Paths are read and written to the [hash fragment](https://en.wikipedia.org/wiki/Uniform_Resource_Locator#Syntax).

For example, `flutterexample.dev/#/path/to/screen`.

* **Path**

  Paths are read and written without a hash. For example,

`flutterexample.dev/path/to/screen`.

## Configuring the URL strategy

[#](#configuring-the-url-strategy)

To configure Flutter to use the path instead, use the [usePathUrlStrategy](https://api.flutter.dev/flutter/flutter_web_plugins/usePathUrlStrategy.html) function provided by the [flutter\_web\_plugins](https://api.flutter.dev/flutter/flutter_web_plugins/flutter_web_plugins-library.html) library, which is part of the Flutter SDK.

You can't directly add `flutter_web_plugins` using `pub add`. Include it as a Flutter [SDK dependency](https://dart.dev/tools/pub/dependencies#sdk) in your `pubspec.yaml` file:

yaml

```
dependencies:
  flutter:
    sdk: flutter
  flutter_web_plugins:
    sdk: flutter
```

Then call the `usePathUrlStrategy` function before `runApp`:

dart

```
import 'package:flutter_web_plugins/url_strategy.dart';
​
void main() {
  usePathUrlStrategy();
  runApp(ExampleApp());
}
```

## Configuring your web server

[#](#configuring-your-web-server)

PathUrlStrategy uses the [History API](https://developer.mozilla.org/en-US/docs/Web/API/History_API), which requires additional configuration for web servers.

To configure your web server to support PathUrlStrategy, check your web server's documentation to rewrite requests to `index.html`. Check your web server's documentation for details on how to configure single-page apps.

If you are using Firebase Hosting, choose the "Configure as a single-page app" option when initializing your project. For more information see Firebase's [Configure rewrites](https://firebase.google.com/docs/hosting/full-config#rewrites) documentation.

The local dev server created by running `flutter run -d chrome` is configured to handle any path gracefully and fallback to your app's `index.html` file.

## Hosting a Flutter app at a non-root location

[#](#hosting-a-flutter-app-at-a-non-root-location)

Update the `<base href="/">` tag in `web/index.html` to the path where your app is hosted. For example, to host your Flutter app at `my_app.dev/flutter_app`, change this tag to `<base href="/flutter_app/">`.

Relative `base href` tags are supported for release builds but they must take into account the full URL where the page was served from. This means a relative `base href` for a request to `/flutter_app/`, `/flutter_app/nested/route`, and `/flutter_app/nested/route/` will be different (for example `"."`, `".."`, and `"../.."` respectively).

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-07-21. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/ui/navigation/url-strategies.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/ui/navigation/url-strategies\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/ui/navigation/url-strategies.md "Report an issue with this page").

----
url: https://docs.flutter.dev/data-and-backend/firebase.md
----

\# Firebase > How to use Firebase and Firestore with Flutter. Firebase is a Backend-as-a-Service (BaaS) app development platform that provides hosted backend services such as cloud databases (\[Cloud Firestore]\[] and \[Firebase SQL Connect]\[]), cloud storage, authentication, crash reporting, AI features, remote configuration, and hosting for your static files. ## Flutter and Firebase resources Firebase supports Flutter. To learn more, check out the following resources. ### Documentation \* \[Getting started with Firebase and Flutter]\[started] \* \[Choose a database for your Flutter app]\[choose-db] \* \[Firebase plugins]\[] ### Blog Posts \[Use Firebase to host your Flutter app on the web]\[article] ### Tutorials Get to know Firebase for Flutter \* \[tutorial]\[codelab1] \* \[video workshop]\[workshop] ## Flutter and Firebase community resources The Flutter community created the following useful resources. ### Blog Posts \[Building chat app with Flutter and Firebase]\[chat app] ### Videos \* \[Using Firestore as a backend to your Flutter app]\[video] \* \[Live Coding Firebase Authentication with Flutter]\[video2] \* \[Flutter & Firebase Auth 01]\[video3] \* \[Flutter: Firebase Tutorial Part 1 - Auth and Sign in]\[video4] \[article]: https\://blog.flutter.dev/must-try-use-firebase-to-host-your-flutter-app-on-the-web-852ee533a469 \[chat app]: https\://medium.com/flutter-community/building-a-chat-app-with-flutter-and-firebase-from-scratch-9eaa7f41782e \[choose-db]: /data-and-backend/persistence/choose-a-database \[Cloud Firestore]: https\://firebase.google.com/docs/firestore \[codelab1]: https\://codelabs.developers.google.com/codelabs/firebase-get-to-know-flutter \[Firebase plugins]: https\://firebase.google.com/docs/flutter/setup#available-plugins \[Firebase SQL Connect]: https\://firebase.google.com/docs/sql-connect \[started]: https\://firebase.google.com/docs/flutter/setup \[video]: https\://www\.youtube.com/watch/DqJ\_KjFzL9I\&t#38s \[video2]: https\://www\.youtube.com/watch/OlcYP6UXlm8 \[video3]: https\://www\.youtube.com/watch/u\_Lyx8KJWpg \[video4]: https\://www\.youtube.com/watch/13-jNF984C0 \[workshop]: https\://www\.youtube.com/watch/4wunbF29Kkg

----
url: https://docs.flutter.dev/reference/supported-platforms
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Supported deployment platforms](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

As of Flutter 3.47, Flutter supports deploying apps on the following combinations of hardware architectures and operating system versions. These combinations are called *platforms*.

Flutter categorizes platforms as follows:

* **Supported**: The platforms and versions that the Flutter team supports.
* **CI-tested**: The Flutter team tests these platforms on every commit.
* **Unsupported**: The Flutter team doesn't test or support these platforms.

Based on these categories, Flutter supports deploying to the following platforms.

## Mobile platforms

[#](#mobile-platforms)

mobile

### Android

[Deploy to Android](/deployment/android)

x64

Arm32

Arm64

Supported24 to 37CI tested24 to 36Unsupported23 and earlier

mobile

### iOS

[Deploy to iOS](/deployment/ios)

Arm64

Supported15 to 27CI tested18 and 26Unsupported14 and earlier

## Desktop platforms

[#](#desktop-platforms)

desktop\_windows

### Windows

[Deploy to Windows](/deployment/windows)

x64

Arm64

Supported10, 11CI tested10Unsupported8 and earlier

laptop\_mac

### macOS

[Deploy to macOS](/deployment/macos)

x64

Arm64

SupportedMonterey (12) to Golden Gate (27)CI testedSequoia (15)UnsupportedBig Sur (11) and earlier

computer

### Debian (Linux)

[Deploy to Linux](/deployment/linux)

x64

Arm64

Supported10 to 13CI tested12Unsupported9 and earlier

computer

### Ubuntu (Linux)

[Deploy to Linux](/deployment/linux)

x64

Arm64

Supported20.04 LTS to 24.04 LTSCI tested22.04 LTSUnsupported25.10 and earlier non-LTS

## Web platforms

[#](#web-platforms)

language

### Chrome

[Deploy to web](/deployment/web)

JavaScript

WebAssembly

Supported[Latest 2](https://chromereleases.googleblog.com/search/label/Stable%20updates)CI tested145Unsupported95 and earlier

language

### Firefox

[Deploy to web](/deployment/web)

JavaScript

Supported[Latest 2](https://www.mozilla.org/en-US/firefox/releases/)CI tested148Unsupported98 and earlier

language

### Safari

[Deploy to web](/deployment/web)

JavaScript

Supported15.6 and newerCI tested18.6Unsupported15.5 and earlier

language

### Edge

[Deploy to web](/deployment/web)

JavaScript

WebAssembly

Supported[Latest 2](https://learn.microsoft.com/en-us/deployedge/microsoft-edge-relnote-stable-channel)CI tested145Unsupported95 and earlier

## Previous Flutter versions

[#](#previous-flutter-versions)

To learn about support for previous versions of Flutter, see the following resources:

* [Flutter SDK release notes](/release/release-notes). Find breaking changes and support updates listed by release.
* [Supported platforms page history](https://github.com/flutter/website/commits/main/sites/docs/src/content/reference/supported-platforms.md). To see historical support matrices, view previous versions of this page.

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-09-22. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/reference/supported-platforms.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/reference/supported-platforms\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/reference/supported-platforms.md "Report an issue with this page").

----
url: https://docs.flutter.dev/tools/pubspec
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Flutter pubspec options](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

Every project written in the Dart language, including all Flutter apps and libraries, includes a `pubspec.yaml` file, referred to as *the pubspec*. Creating a new project generates a basic pubspec at the top of the project tree that contains metadata and lists project dependencies.

**Fields common to both Dart and Flutter projects are described in [The pubspec file](https://dart.dev/tools/pub/pubspec) on dart.dev.** This page lists fields that are only valid for a Flutter project.

## Example

[#](#example)

When you create a new project with the `flutter create` command (or by using the equivalent button in your IDE), it creates a pubspec for a basic Flutter app.

The first time you build your project, it also creates a `pubspec.lock` file that contains specific versions of the included packages. This ensures that you get the same version the next time the project is built. To learn more about how Flutter and Dart resolve package dependencies, check out [Package dependency management](/packages-and-plugins/dependency-management).

The following example of a pubspec file highlights fields used only in a Flutter app. Learn more about the non-highlighted fields on [dart.dev](https://dart.dev/tools/pub/pubspec).

pubspec.yaml

yaml

```
name: <project name>
description: A new Flutter project.
​
publish_to: 'none'  # Remove this line if you wish to publish to pub.dev
version: 1.0.0+1
​
environment:
  sdk: ^3.13.0
​
dependencies:
  flutter:       # Required for every Flutter project
    sdk: flutter # Required for every Flutter project
  flutter_localizations: # Required to enable localization
    sdk: flutter         # Required to enable localization
​
  material_ui    # Required for projects that use the Material components pkg
  cupertino_ui   # Required for projects that use the Cupertino components pkg
  cupertino_icons: ^1.0.8 # Only required if you use Cupertino (iOS style) icons
​
dev_dependencies:
  flutter_test:
    sdk: flutter # Required for a Flutter project that includes tests
  integration_test:
    sdk: flutter # Required for integration tests
​
  flutter_lints: ^6.0.0 # Contains a set of recommended lints for Flutter code
​
flutter:
​
  uses-material-design: true # Required if you use the Material icon font
​
  generate: true # Enables generation of localized strings from arb files
​
  config: # App-specific configuration flags that mirror `flutter config`
    enable-swift-package-manager: true
​
  assets:  # Lists assets, such as image files
    - images/a_dot_burr.jpeg
    - images/a_dot_ham.jpeg
​
  licenses: # Lists additional license files to be bundled with the app
    - assets/my_license.txt
​
  fonts:              # Required if your app uses custom fonts
    - family: Schyler
      fonts:
        - asset: fonts/Schyler-Regular.ttf
        - asset: fonts/Schyler-Italic.ttf
          style: italic
    - family: Trajan Pro
      fonts:
        - asset: fonts/TrajanPro.ttf
        - asset: fonts/TrajanPro_Bold.ttf
          weight: 700
```

## Fields

[#](#fields)

This page discusses Flutter-only pubspec fields. Learn more about general Dart fields in [Dart's pubspec supported fields](https://dart.dev/tools/pub/pubspec#supported-fields).

### assets field

[#](#assets)

A list of asset paths that your app uses. These assets are bundled with your application. Common types of assets include static data (for example, `JSON`), configuration files, icons, and images (`JPEG`, `WebP`, `GIF`, animated `WebP/GIF`, `PNG`, `BMP`, and `WBMP`).

Besides listing the images that are included in the app package, an image asset can also refer to one or more resolution-specific "variants". For more information, visit the [resolution aware](/ui/assets/assets-and-images#resolution-aware) section of the [Assets and images](/ui/assets/assets-and-images) page. For information on adding assets from package dependencies, visit the [asset images in package dependencies](/ui/assets/assets-and-images#from-packages) section in the same page.

The `assets` field has the following structure:

pubspec.yaml

yaml

```
flutter:
  assets:
    - [ path_to_file | path_to_directory ]
      [ flavor_path_field | platform_path_field | transformer_path_field ]
    [...]
```

yaml

```
# path_to_file structure
- path/to/directory/file
```

yaml

```
# path_to_directory structure
- path/to/directory/
```

yaml

```
# flavor_path_field structure
- path: path/to/directory
  flavors:
    - flavor_name
```

yaml

```
# platform_path_field structure
- path: path/to/file
  platforms:
    - platform_name
```

yaml

```
# transformer_path_field structure
- path: path/to/file
  transformers:
    - package: transformer_package_name
      args: ['arg1', 'arg2'] # Optional
```

Subfields of `assets`:

* `path_to_file`: A string that represents the path to a file.
* `path_to_directory`: A string that represents the path to a directory.
* `flavor_path_field`: A path field and its flavor subfields.
* `platform_path_field`: A path field and its platform subfields.
* `transformer_path_field`: A path field and its transformer subfields.
* `path`: The path to an asset file or directory.
* `flavors`: A list of flutter flavors to use with assets at a specific path. To learn more about flavors, consult the guides on setting up flavors for [Android](/deployment/flavors), [iOS and macOS](/deployment/flavors-ios), [Linux](/deployment/flavors-linux), and [Windows](/deployment/flavors-windows).
* `platforms`: A list of platforms to use with assets at a specific path. Valid values are `android`, `ios`, `web`, `linux`, `macos`, and `windows`.
* `transformers`: A list of transformer packages to run on the asset at build time. To learn more, see [Transforming assets at build time](/ui/assets/asset-transformation).

You can pass in a path to a file:

pubspec.yaml

yaml

```
flutter:
  assets:
    - assets/images/my_image_a.png
    - assets/images/my_image_b.png
```

You can pass in a path to a directory:

pubspec.yaml

yaml

```
flutter:
  assets:
    - assets/images/
    - assets/icons/
```

You can pass in a path to a directory for specific flavors:

pubspec.yaml

yaml

```
flutter:
  assets:
    - path: assets/flavor_a_and_b/images
      flavors:
        - flavor_a
        - flavor_b
    - path: assets/flavor_c/images
      flavors:
        - flavor_c
```

You can pass in a path to a file for specific platforms:

pubspec.yaml

yaml

```
flutter:
  assets:
    - path: assets/web_worker.js
      platforms:
        - web
    - path: assets/desktop_icon.png
      platforms:
        - windows
        - linux
        - macos
```

You can configure build-time asset transformers:

pubspec.yaml

yaml

```
flutter:
  assets:
    - path: assets/logo.svg
      transformers:
        - package: vector_graphics_compiler
          args: ['--tessellate']
```

### config field

[#](#config)

A map of keys to flags (`true` or `false`) that influences how the `flutter` CLI is executed.

The available keys mirror those available in `flutter config --list`.

pubspec.yaml

yaml

```
flutter:
  config:
    cli-animations: false
```

Use `flutter config --help` for a description of each flag.

Flags are only read from the current *application* package, and have no effect in the context of a package or dependency.

[]()

#### enable-swift-package-manager field

[#](#enable-swift-package-manager)

Control whether Flutter uses Swift Package Manager to manage native iOS and macOS dependencies for this project. To disable it for this project, set the field to `false`.

pubspec.yaml

yaml

```
flutter:
  config:
    enable-swift-package-manager: false
```

### default-flavor field

[#](#default-flavor)

Assign a default Flutter flavor for an app. When used, you don't need to include the name of this flavor in the Flutter launch command.

pubspec.yaml

yaml

```
flutter:
  default-flavor: flavor_name
```

In the following example, an Android Flutter app has a flavor called `staging` and `production`. The `production` flavor is the default flavor. When that flavor is run, you don't need to include it in the launch command.

pubspec.yaml

yaml

```
flutter:
  default-flavor: production
```

console

```
# Use this command to run the default flavor (production).
$ flutter run

# Use this command to run non-default flavors (staging).
$ flutter run --flavor staging
```

To learn how to create Flutter flavors, consult the guides on setting up flavors for [Android](/deployment/flavors), [iOS and macOS](/deployment/flavors-ios), [Linux](/deployment/flavors-linux), and [Windows](/deployment/flavors-windows).

### deferred-components field

[#](#deferred-components)

Defer downloading components to reduce the initial download size of an Android app. Most often used with large applications, modularized applications, and applications with on-demand features.

The `deferred-components` field has the following structure:

pubspec.yaml

yaml

```
flutter:
  deferred-components:
    - name: component_name
      libraries:
        - string_expression
        [...]
      assets:
        - string_expression
        [...]
    [...]
```

Deferred component subfields:

* `name`: The unique identifier for a specific deferred component.
* `libraries`: A list of Dart libraries that are part of the deferred component.
* `assets`: A list of asset paths that are associated with the deferred component.

Example:

pubspec.yaml

yaml

```
flutter:
  deferred-components:
    - name: box_component
      libraries:
        - package:testdeferredcomponents/box.dart
    - name: gallery_feature
      libraries:
        - package:testdeferredcomponents/gallery_feature.dart
      assets:
        - assets/gallery_images/gallery_feature.png
```

To learn more about how you can use deferred components with a Flutter Android app, see [Deferred components for Android](/perf/deferred-components).

### flutter field

[#](#flutter)

A field that contains Flutter-specific settings for your app.

pubspec.yaml

yaml

```
flutter:
  [flutter_field]
  [...]
```

### fonts field

[#](#fonts)

Configure and include custom fonts in your Flutter application.

For examples of using fonts visit the [Use a custom font](/cookbook/design/fonts) and [Export fonts from a package](/cookbook/design/package-fonts) recipes in the Flutter cookbook.

The `fonts` field has the following structure:

pubspec.yaml

yaml

```
flutter:
  fonts:
    -  { font_family_field | font_asset_field }
    [...]
```

yaml

```
# font_family_field structure
- family: font_name
  fonts:
    - font_asset_field
    [...]
```

yaml

```
# font_asset_field structure
- asset: path/to/directory/font_name
  weight: int_expression # Optional
  style: string_expression # Optional
```

Subfields of `fonts`:

* `family`: Optional. The font family name. Can have multiple font assets.
* `asset`: The font to use.
* `weight`: Optional. The weight of the font. This can be `100`, `200`, `300`, `400`, `500`, `600`, `700`, `800` or `900`.
* `style`: Optional. The style of the font. This can be `italic`.

Use a font that is not part of a font family:

pubspec.yaml

yaml

```
flutter:
  fonts:
    - asset: fonts/Roboto-Regular.ttf
      weight: 900 # Optional
      style: italic # Optional
```

Use a font family:

pubspec.yaml

yaml

```
flutter:
  fonts:
    - family: Roboto # Optional
      fonts:
        - asset: fonts/Roboto-Regular.ttf
        - asset: fonts/Roboto-Bold.ttf
          weight: 700 # Optional
          style: italic # Optional
```

Alternatively, if you have a font that requires no family, weight or style requirements, you can declare it as a simple asset:

pubspec.yaml

yaml

```
flutter:
  assets:
    - fonts/Roboto-Regular.ttf
```

### generate field

[#](#generate)

Enables generation of Dart files with localized strings from `.arb` files.

Enable general localization:

pubspec.yaml

yaml

```
flutter:
  generate: true
```

To learn more, visit [Internationalizing Flutter apps](/ui/accessibility-and-internationalization/internationalization).

### licenses field

[#](#licenses)

A list of additional license file paths that should be bundled with your application. These files are typically found within your project's `assets` directory.

The `licenses` field has the following structure:

pubspec.yaml

yaml

```
flutter:
  licenses:
    - [path_to_file]
```

### module field

[#](#module)

Configure settings for a Flutter module project when adding Flutter to an existing host Android or iOS application (Add-to-App).

The `module` field has the following structure:

pubspec.yaml

yaml

```
flutter:
  module:
    androidPackage: com.example.my_module # Optional
    iosBundleIdentifier: com.example.myModule # Optional
```

Subfields of `module`:

* `androidX`: Optional. Specifies whether the module uses AndroidX.
* `androidPackage`: Optional. The package name used for generated Android code.
* `iosBundleIdentifier`: Optional. The bundle identifier used for generated iOS code.

To learn more, visit [Add Flutter to existing apps](/add-to-app).

### plugin field

[#](#plugin)

Configure settings specifically for Flutter plugins.

The `plugin` field has the following structure:

pubspec.yaml

yaml

```
flutter:
  plugin:
    platforms:
      android: # Optional
        package: com.example.my_plugin
        pluginClass: MyPlugin
        dartPluginClass: MyPluginClassName
        ffiPlugin: true
        default_package: my_plugin_name
        fileName: my_file.dart
      ios: # Optional
        pluginClass: MyPlugin
        dartPluginClass: MyPluginClassName
        ffiPlugin: true
        default_package: my_plugin_name
        fileName: my_file.dart
        sharedDarwinSource: true
      macos: # Optional
        pluginClass: MyPlugin
        dartPluginClass: MyPluginClassName
        ffiPlugin: true
        default_package: my_plugin_name
        fileName: my_file.dart
        sharedDarwinSource: true
      windows: # Optional
        pluginClass: MyPlugin
        dartPluginClass: MyPluginClassName
        ffiPlugin: true
        default_package: my_plugin_name
        fileName: my_file.dart
      linux: # Optional
        pluginClass: MyPlugin
        dartPluginClass: MyPluginClassName
        ffiPlugin: true
        default_package: my_plugin_name
        fileName: my_file.dart
      web: # Optional
        ffiPlugin: true
        default_package: my_plugin_name
        fileName: my_file.dart
    implements: # Optional
      - example_platform_interface
```

Subfields of `plugin`:

* `platforms`: A list of platforms that will have configuration settings.
* `package`: The Android package name of the plugin. This can be used with the Android platform and is required.
* `pluginClass`: The name of the plugin class. Optional if `dartPluginClass` is used for the same platform. This can be used with the Android, iOS, Linux macOS, and Windows platforms.
* `default_package`: Optional. The package that should be used as the default implementation of a platform interface. Only applicable to federated plugins, where the plugin's implementation is split into multiple platform-specific packages.
* `dartPluginClass`: Optional. The Dart class that serves as the entry point for a Flutter plugin. This can be used with the Android, iOS, Linux macOS, and Windows platforms.
* `sharedDarwinSource`: Optional. Indicates that the plugin shares native code between iOS and macOS. This can be used with the iOS and macOS platforms.
* `fileName`: Optional. The file that contains the plugin class.
* `ffiPlugin`: Optional. True if the plugin uses a Foreign Function Interface (FFI).
* `implements`: Optional. The platform interfaces that a Flutter plugin implements.

To learn more about plugins, see [Developing packages & plugins](/packages-and-plugins/developing-packages).

### shaders field

[#](#shaders)

GLSL shaders with the `.frag` extension must be declared in the `shaders` section of your project's `pubspec.yaml` file. The Flutter command-line tool compiles the shader to its appropriate backend format and generates its necessary runtime metadata. The compiled shader is then included in the application just like an asset.

The `shaders` field accepts a list of individual file paths; directory paths aren't supported. The field has the following structure:

pubspec.yaml

yaml

```
flutter:
  shaders:
    - [path_to_file]
    [...]
```

For example, to add specific shaders to your project:

pubspec.yaml

yaml

```
flutter:
  shaders:
    - assets/shaders/shader_a.frag
    - assets/shaders/shader_b.frag
```

Don't list shader files under the `assets` field, either directly or through a directory that contains them. The Flutter tool reports an error if a shader is declared in both places.

Like other assets, shaders also support `flavors`, `platforms`, and `transformers`.

To learn more about using shaders in Flutter, check out [Writing and using fragment shaders](/ui/design/graphics/fragment-shaders).

### uses-material-design field

[#](#uses-material-design)

Use Material Design components and icon font in your Flutter app.

pubspec.yaml

yaml

```
flutter:
  uses-material-design: true
```

## Packages

[#](#packages)

The following Flutter-specific packages can be added to the pubspec. If you add a package, run `flutter pub get` in your terminal to install the package.

### flutter package

[#](#flutter-package)

A package that represents the Flutter SDK itself and can be added to the `dependencies` field. Use this if your project relies on the Flutter SDK, not a regular package from pub.dev.

pubspec.yaml

yaml

```
dependencies:
  flutter:
    sdk: flutter
```

### flutter\_localizations package

[#](#flutter-localizations)

A package provided by the Flutter SDK that enables localization of `ARB` files. Often used with the `intl` package.

pubspec.yaml

yaml

```
dependencies:
  flutter_localizations:
    sdk: flutter
  intl: any
```

### flutter\_test package

[#](#flutter_test)

A package provided by the Flutter SDK that contains testing utilities for unit and widget tests for your Flutter app.

pubspec.yaml

yaml

```
dev_dependencies:
  flutter_test:
    sdk: flutter
```

### integration\_test package

[#](#integration_test)

A package provided by the Flutter SDK for running integration tests on target devices or emulators.

pubspec.yaml

yaml

```
dev_dependencies:
  integration_test:
    sdk: flutter
```

To learn more, visit [Integration testing](/testing/integration-tests).

### flutter\_lints package

[#](#flutter_lints)

A package that provides a set of recommended lints for Flutter projects. This package can be added to the `dev_dependencies` field in the pubspec.

pubspec.yaml

yaml

```
dev_dependencies:
  flutter_lints: ^6.0.0
```

### cupertino\_icons package

[#](#cupertino_icons)

A package that provides a set of Apple's Cupertino icons for use in Flutter applications. This package can be added to the `dependencies` field in the pubspec.

pubspec.yaml

yaml

```
dependencies:
  cupertino_icons: ^1.0.8
```

## More information

[#](#more-information)

For more information on packages, plugins, and pubspec files, visit the following:

* [Add Flutter to existing apps](/add-to-app)
* [Creating packages](https://dart.dev/guides/libraries/create-library-packages) on dart.dev
* [Glossary of package terms](https://dart.dev/tools/pub/glossary) on dart.dev
* [Integration testing](/testing/integration-tests)
* [Package dependency management](/packages-and-plugins/dependency-management)
* [Package dependencies](https://dart.dev/tools/pub/dependencies) on dart.dev
* [Set up Flutter flavors for Android](/deployment/flavors)
* [Set up Flutter flavors for iOS and macOS](/deployment/flavors-ios)
* [Set up Flutter flavors for Linux](/deployment/flavors-linux)
* [Set up Flutter flavors for Windows](/deployment/flavors-windows)
* [Transforming assets at build time](/ui/assets/asset-transformation)
* [Using packages](/packages-and-plugins/using-packages)
* [What not to commit](https://dart.dev/guides/libraries/private-files#pubspeclock) on dart.dev

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-10-13. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/pubspec.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/tools/pubspec\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/pubspec.md "Report an issue with this page").

----
url: https://docs.flutter.dev/ai
----

[Flutter AI Toolkit](/ai/ai-toolkit)

[Learn how to add the AI Toolkit chatbot to your Flutter application.](/ai/ai-toolkit)

[How Flutter AI tools work](/ai/tools)

[Learn how agent skills, the Dart and Flutter MCP server, and package skills interact to assist your development workflow.](/ai/tools)

[FlutterBench agent evaluations](/ai/flutter-bench)

[How the Flutter team implements agent evals, and the results of those evals.](/ai/flutter-bench)

[Get started developing with AI](/ai/get-started)

[Learn how to set up and use AI agent plugins for Flutter and Dart using your preferred coding assistant.](/ai/get-started)

[GenUI SDK for Flutter](/ai/genui)

[Learn how to use GenUI SDK for Flutter to build more interactive experiences for applications and chatbots.](/ai/genui)

[Flutter AI best practices](/ai/best-practices)

[Learn best practices for building AI-powered Flutter apps using guardrails to verify and correct AI-generated data.](/ai/best-practices)

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-08-19. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/ai/index.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/ai\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/ai/index.md "Report an issue with this page").

----
url: https://docs.flutter.dev/ui/interactivity/focus.md
----

\# Understanding Flutter's keyboard focus system > How to use the focus system in your Flutter app. This article explains how to control where keyboard input is directed. If you are implementing an application that uses a physical keyboard, such as most desktop and web applications, this page is for you. If your app won't be used with a physical keyboard, you can skip this. ## Overview Flutter comes with a focus system that directs the keyboard input to a particular part of an application. In order to do this, users "focus" the input onto that part of an application by tapping or clicking the desired UI element. Once that happens, text entered with the keyboard flows to that part of the application until the focus moves to another part of the application. Focus can also be moved by pressing a particular keyboard shortcut, which is typically bound to `Tab`, so it is sometimes called "tab traversal". This page explores the APIs used to perform these operations on a Flutter application, and how the focus system works. We have noticed that there is some confusion among developers about how to define and use \[\`FocusNode\`]\[] objects. If that describes your experience, skip ahead to the \[best practices for creating \`FocusNode\` objects]\(#best-practices-for-creating-focusnode-objects). ### Focus use cases Some examples of situations where you might need to know how to use the focus system: - \[Receiving/handling key events]\(#key-events) - \[Implementing a custom component that needs to be focusable]\(#focus-widget) - \[Receiving notifications when the focus changes]\(#change-notifications) - \[Changing or defining the "tab order" of focus traversal in an application]\(#focustraversalpolicy) - \[Defining groups of controls that should be traversed together]\(#focustraversalgroup-widget) - \[Preventing some controls in an application from being focusable]\(#controlling-what-gets-focus) ## Glossary Below are terms, as Flutter uses them, for elements of the focus system. The various classes that implement some of these concepts are introduced below. - \*\*Focus tree\*\* - A tree of focus nodes that typically sparsely mirrors the widget tree, representing all the widgets that can receive focus. - \*\*Focus node\*\* - A single node in a focus tree. This node can receive the focus, and is said to "have focus" when it is part of the focus chain. It participates in handling key events only when it has focus. - \*\*Primary focus\*\* - The farthest focus node from the root of the focus tree that has focus. This is the focus node where key events start propagating to the primary focus node and its ancestors. - \*\*Focus chain\*\* - An ordered list of focus nodes that starts at the primary focus node and follows the branches of the focus tree to the root of the focus tree. - \*\*Focus scope\*\* - A special focus node whose job is to contain a group of other focus nodes, and allow only those nodes to receive focus. It contains information about which nodes were previously focused in its subtree. - \*\*Focus traversal\*\* - The process of moving from one focusable node to another in a predictable order. This is typically seen in applications when the user presses `Tab` to move to the next focusable control or field. ## FocusNode and FocusScopeNode The \`FocusNode\` and \[\`FocusScopeNode\`]\[] objects implement the mechanics of the focus system. They are long-lived objects (longer than widgets, similar to render objects) that hold the focus state and attributes so that they are persistent between builds of the widget tree. Together, they form the focus tree data structure. They were originally intended to be developer-facing objects used to control some aspects of the focus system, but over time they have evolved to mostly implement details of the focus system. In order to prevent breaking existing applications, they still contain public interfaces for their attributes. But, in general, the thing for which they are most useful is to act as a relatively opaque handle, passed to a descendant widget in order to call \`requestFocus()\` on an ancestor widget, which requests that a descendant widget obtain focus. Setting of the other attributes is best managed by a \[\`Focus\`]\[] or \[\`FocusScope\`]\[] widget, unless you are not using them, or implementing your own version of them. ### Best practices for creating FocusNode objects Some dos and don'ts around using these objects include: - Don't allocate a new \`FocusNode\` for each build. This can cause memory leaks, and occasionally causes a loss of focus when the widget rebuilds while the node has focus. - Do create \`FocusNode\` and \`FocusScopeNode\` objects in a stateful widget. \`FocusNode\` and \`FocusScopeNode\` need to be disposed of when you're done using them, so they should only be created inside of a stateful widget's state object, where you can override \`dispose\` to dispose of them. - Don't use the same \`FocusNode\` for multiple widgets. If you do, the widgets will fight over managing the attributes of the node, and you probably won't get what you expect. - Do set the \`debugLabel\` of a focus node widget to help with diagnosing focus issues. - Don't set the \`onKeyEvent\` callback on a \`FocusNode\` or \`FocusScopeNode\` if they are being managed by a \`Focus\` or \`FocusScope\` widget. If you want an \`onKeyEvent\` handler, then add a new \`Focus\` widget around the widget subtree you would like to listen to, and set the \`onKeyEvent\` attribute of the widget to your handler. Set \`canRequestFocus: false\` on the widget if you also don't want it to be able to take primary focus. This is because the \`onKeyEvent\` attribute on the \`Focus\` widget can be set to something else in a subsequent build, and if that happens, it overwrites the \`onKeyEvent\` handler you set on the node. - Do call \`requestFocus()\` on a node to request that it receives the primary focus, especially from an ancestor that has passed a node it owns to a descendant where you want to focus. - Do use \`focusNode.requestFocus()\`. It is not necessary to call \`FocusScope.of(context).requestFocus(focusNode)\`. The \`focusNode.requestFocus()\` method is equivalent and more performant. ### Unfocusing There is an API for telling a node to "give up the focus", named \`FocusNode.unfocus()\`. While it does remove focus from the node, it is important to realize that there really is no such thing as "unfocusing" all nodes. If a node is unfocused, then it must pass the focus somewhere else, since there is \_always\_ a primary focus. The node that receives the focus when a node calls \`unfocus()\` is either the nearest \`FocusScopeNode\`, or a previously focused node in that scope, depending upon the \`disposition\` argument given to \`unfocus()\`. If you would like more control over where the focus goes when you remove it from a node, explicitly focus another node instead of calling \`unfocus()\`, or use the focus traversal mechanism to find another node with the \`focusInDirection\`, \`nextFocus\`, or \`previousFocus\` methods on \`FocusNode\`. When calling \`unfocus()\`, the \`disposition\` argument allows two modes for unfocusing: \[\`UnfocusDisposition.scope\`]\[] and \`UnfocusDisposition.previouslyFocusedChild\`. The default is \`scope\`, which gives the focus to the nearest parent focus scope. This means that if the focus is thereafter moved to the next node with \`FocusNode.nextFocus\`, it starts with the "first" focusable item in the scope. The \`previouslyFocusedChild\` disposition will search the scope to find the previously focused child and request focus on it. If there is no previously focused child, it is equivalent to \`scope\`. :::secondary Beware If there is no other scope, then focus moves to the root scope node of the focus system, \`FocusManager.rootScope\`. This is generally not desirable, as the root scope doesn't have a \`context\` for the framework to determine which node should be focused next. If you find that your application suddenly loses the ability to navigate by using focus traversal, this is probably what has happened. To fix it, add a \`FocusScope\` as an ancestor to the focus node that is requesting the unfocus. The \`WidgetsApp\` (from which \`MaterialApp\` and \`CupertinoApp\` are derived) has its own \`FocusScope\`, so this should not be an issue if you are using those. ::: ## Focus widget The \`Focus\` widget owns and manages a focus node, and is the workhorse of the focus system. It manages the attaching and detaching of the focus node it owns from the focus tree, manages the attributes and callbacks of the focus node, and has static functions to enable discovery of focus nodes attached to the widget tree. In its simplest form, wrapping the \`Focus\` widget around a widget subtree allows that widget subtree to obtain focus as part of the focus traversal process, or whenever \`requestFocus\` is called on the \`FocusNode\` passed to it. When combined with a gesture detector that calls \`requestFocus\`, it can receive focus when tapped or clicked. You might pass a \`FocusNode\` object to the \`Focus\` widget to manage, but if you don't, it creates its own. The main reason to create your own \`FocusNode\` is to be able to call \`requestFocus()\` on the node to control the focus from a parent widget. Most of the other functionality of a \`FocusNode\` is best accessed by changing the attributes of the \`Focus\` widget itself. The \`Focus\` widget is used in most of Flutter's own controls to implement their focus functionality. Here is an example showing how to use the \`Focus\` widget to make a custom control focusable. It creates a container with text that reacts to receiving the focus.

<!--?code-excerpt "ui/focus/lib/custom_control_example.dart"?-->

\`\`\`dart import 'package:material\_ui/material\_ui.dart'; void main() => runApp(const MyApp()); class MyApp extends StatelessWidget { const MyApp({super.key}); static const String \_title = 'Focus Sample'; @override Widget build(BuildContext context) { return MaterialApp( title: \_title, home: Scaffold( appBar: AppBar(title: const Text(\_title)), body: const Column( mainAxisAlignment: MainAxisAlignment.center, children: \[MyCustomWidget(), MyCustomWidget()], ), ), ); } } class MyCustomWidget extends StatefulWidget { const MyCustomWidget({super.key}); @override State createState() => \_MyCustomWidgetState(); } class \_MyCustomWidgetState extends State { Color \_color = Colors.white; String \_label = 'Unfocused'; @override Widget build(BuildContext context) { return Focus( onFocusChange: (focused) { setState(() { \_color = focused ? Colors.black26 : Colors.white; \_label = focused ? 'Focused' : 'Unfocused'; }); }, child: Center( child: Container( width: 300, height: 50, alignment: Alignment.center, color: \_color, child: Text(\_label), ), ), ); } } \`\`\` ### Key events If you wish to listen for key events in a subtree, set the \`onKeyEvent\` attribute of the \`Focus\` widget to be a handler that either just listens to the key, or handles the key and stops its propagation to other widgets. Key events start at the focus node with primary focus. If that node doesn't return \`KeyEventResult.handled\` from its \`onKeyEvent\` handler, then its parent focus node is given the event. If the parent doesn't handle it, it goes to its parent, and so on, until it reaches the root of the focus tree. If the event reaches the root of the focus tree without being handled, then it is returned to the platform to give to the next native control in the application (in case the Flutter UI is part of a larger native application UI). Events that are handled are not propagated to other Flutter widgets, and they are also not propagated to native widgets. Here's an example of a \`Focus\` widget that absorbs every key that its subtree doesn't handle, without being able to be the primary focus:

<!--?code-excerpt "ui/focus/lib/samples.dart (absorb-keys)"?-->

\`\`\`dart @override Widget build(BuildContext context) { return Focus( onKeyEvent: (node, event) => KeyEventResult.handled, canRequestFocus: false, child: child, ); } \`\`\` Focus key events are processed before text entry events, so handling a key event when the focus widget surrounds a text field prevents that key from being entered into the text field. Here's an example of a widget that won't allow the letter "a" to be typed into the text field:

<!--?code-excerpt "ui/focus/lib/samples.dart (no-letter-a)"?-->

\`\`\`dart @override Widget build(BuildContext context) { return Focus( onKeyEvent: (node, event) { return (event.logicalKey == LogicalKeyboardKey.keyA) ? KeyEventResult.handled : KeyEventResult.ignored; }, child: const TextField(), ); } \`\`\` If the intent is input validation, this example's functionality would probably be better implemented using a \`TextInputFormatter\`, but the technique can still be useful: the \`Shortcuts\` widget uses this method to handle shortcuts before they become text input, for instance. ### Controlling what gets focus One of the main aspects of focus is controlling what can receive focus and how. The attributes \`canRequestFocus\`, \`skipTraversal,\` and \`descendantsAreFocusable\` control how this node and its descendants participate in the focus process. If the \`skipTraversal\` attribute true, then this focus node doesn't participate in focus traversal. It is still focusable if \`requestFocus\` is called on its focus node, but is otherwise skipped when the focus traversal system is looking for the next thing to focus on. The \`canRequestFocus\` attribute, unsurprisingly, controls whether or not the focus node that this \`Focus\` widget manages can be used to request focus. If this attribute is false, then calling \`requestFocus\` on the node has no effect. It also implies that this node is skipped for focus traversal, since it can't request focus. The \`descendantsAreFocusable\` attribute controls whether the descendants of this node can receive focus, but still allows this node to receive focus. This attribute can be used to turn off focusability for an entire widget subtree. This is how the \`ExcludeFocus\` widget works: it's just a \`Focus\` widget with this attribute set. ### Autofocus Setting the \`autofocus\` attribute of a \`Focus\` widget tells the widget to request the focus the first time the focus scope it belongs to is focused. If more than one widget has \`autofocus\` set, then it is arbitrary which one receives the focus, so try to only set it on one widget per focus scope. The \`autofocus\` attribute only takes effect if there isn't already a focus in the scope that the node belongs to. Setting the \`autofocus\` attribute on two nodes that belong to different focus scopes is well defined: each one becomes the focused widget when their corresponding scopes are focused. ### Change notifications The \`Focus.onFocusChanged\` callback can be used to get notifications that the focus state for a particular node has changed. It notifies if the node is added to or removed from the focus chain, which means it gets notifications even if it isn't the primary focus. If you only want to know if you have received the primary focus, check and see if \`hasPrimaryFocus\` is true on the focus node. ### Obtaining the FocusNode Sometimes, it is useful to obtain the focus node of a \`Focus\` widget to interrogate its attributes. To access the focus node from an ancestor of the \`Focus\` widget, create and pass in a \`FocusNode\` as the \`Focus\` widget's \`focusNode\` attribute. Because it needs to be disposed of, the focus node you pass needs to be owned by a stateful widget, so don't just create one each time it is built. If you need access to the focus node from the descendant of a \`Focus\` widget, you can call \`Focus.of(context)\` to obtain the focus node of the nearest \`Focus \`widget to the given context. If you need to obtain the \`FocusNode\` of a \`Focus\` widget within the same build function, use a \[\`Builder\`]\[] to make sure you have the correct context. This is shown in the following example:

<!--?code-excerpt "ui/focus/lib/samples.dart (builder)"?-->

\`\`\`dart @override Widget build(BuildContext context) { return Focus( child: Builder( builder: (context) { final bool hasPrimary = Focus.of(context).hasPrimaryFocus; print('Building with primary focus: $hasPrimary'); return const SizedBox(width: 100, height: 100); }, ), ); } \`\`\` ### Timing One of the details of the focus system is that when focus is requested, it only takes effect after the current build phase completes. This means that focus changes are always delayed by one frame, because changing focus can cause arbitrary parts of the widget tree to rebuild, including ancestors of the widget currently requesting focus. Because descendants cannot dirty their ancestors, it has to happen between frames, so that any needed changes can happen on the next frame. ## FocusScope widget The \`FocusScope\` widget is a special version of the \`Focus\` widget that manages a \`FocusScopeNode\` instead of a \`FocusNode\`. The \`FocusScopeNode\` is a special node in the focus tree that serves as a grouping mechanism for the focus nodes in a subtree. Focus traversal stays within a focus scope unless a node outside of the scope is explicitly focused. The focus scope also keeps track of the current focus and history of the nodes focused within its subtree. That way, if a node releases focus or is removed when it had focus, the focus can be returned to the node that had focus previously. Focus scopes also serve as a place to return focus to if none of the descendants have focus. This allows the focus traversal code to have a starting context for finding the next (or first) focusable control to move to. If you focus a focus scope node, it first attempts to focus the current, or most recently focused node in its subtree, or the node in its subtree that requested autofocus (if any). If there is no such node, it receives the focus itself. ## FocusableActionDetector widget The \[\`FocusableActionDetector\`]\[] is a widget that combines the functionality of \[\`Actions\`]\[], \[\`Shortcuts\`]\[], \[\`MouseRegion\`]\[] and a \`Focus\` widget to create a detector that defines actions and key bindings, and provides callbacks for handling focus and hover highlights. It is what Flutter controls use to implement all of these aspects of the controls. It is just implemented using the constituent widgets, so if you don't need all of its functionality, you can just use the ones you need, but it is a convenient way to build these behaviors into your custom controls. :::note To learn more, watch this short Widget of the Week video on the \`FocusableActionDetector\` widget: ::: ## Controlling focus traversal Once an application has the ability to focus, the next thing many apps want to do is to allow the user to control the focus using the keyboard or another input device. The most common example of this is "tab traversal" where the user presses `Tab` to go to the "next" control. Controlling what "next" means is the subject of this section. This kind of traversal is provided by Flutter by default. In a simple grid layout, it's fairly easy to decide which control is next. If you're not at the end of the row, then it's the one to the right (or left for right-to-left locales). If you are at the end of a row, it's the first control in the next row. Unfortunately, applications are rarely laid out in grids, so more guidance is often needed. The default algorithm in Flutter (\[\`ReadingOrderTraversalPolicy\`]\[]) for focus traversal is pretty good: It gives the right answer for most applications. However, there are always pathological cases, or cases where the context or design requires a different order than the one the default ordering algorithm arrives at. For those cases, there are other mechanisms for achieving the desired order. ### FocusTraversalGroup widget The \[\`FocusTraversalGroup\`]\[] widget should be placed in the tree around widget subtrees that should be fully traversed before moving on to another widget or group of widgets. Just grouping widgets into related groups is often enough to resolve many tab traversal ordering problems. If not, the group can also be given a \[\`FocusTraversalPolicy\`]\[] to determine the ordering within the group. The default \[\`ReadingOrderTraversalPolicy\`]\[] is usually sufficient, but in cases where more control over ordering is needed, an \[\`OrderedTraversalPolicy\`]\[] can be used. The \`order\` argument of the \[\`FocusTraversalOrder\`]\[] widget wrapped around the focusable components determines the order. The order can be any subclass of \[\`FocusOrder\`]\[], but \[\`NumericFocusOrder\`]\[] and \[\`LexicalFocusOrder\`]\[] are provided. If none of the provided focus traversal policies are sufficient for your application, you could also write your own policy and use it to determine any custom ordering you want. Here's an example of how to use the \`FocusTraversalOrder\` widget to traverse a row of buttons in the order TWO, ONE, THREE using \`NumericFocusOrder\`.

<!--?code-excerpt "ui/focus/lib/samples.dart (ordered-button-row)"?-->

\`\`\`dart class OrderedButtonRow extends StatelessWidget { const OrderedButtonRow({super.key}); @override Widget build(BuildContext context) { return FocusTraversalGroup( policy: OrderedTraversalPolicy(), child: Row( children: \[ const Spacer(), FocusTraversalOrder( order: const NumericFocusOrder(2), child: TextButton(child: const Text('ONE'), onPressed: () {}), ), const Spacer(), FocusTraversalOrder( order: const NumericFocusOrder(1), child: TextButton(child: const Text('TWO'), onPressed: () {}), ), const Spacer(), FocusTraversalOrder( order: const NumericFocusOrder(3), child: TextButton(child: const Text('THREE'), onPressed: () {}), ), const Spacer(), ], ), ); } } \`\`\` ### FocusTraversalPolicy The \`FocusTraversalPolicy\` is the object that determines which widget is next, given a request and the current focus node. The requests (member functions) are things like \`findFirstFocus\`, \`findLastFocus\`, \`next\`, \`previous\`, and \`inDirection\`. \`FocusTraversalPolicy\` is the abstract base class for concrete policies, like \`ReadingOrderTraversalPolicy\`, \`OrderedTraversalPolicy\` and the \[\`DirectionalFocusTraversalPolicyMixin\`]\[] classes. In order to use a \`FocusTraversalPolicy\`, you give one to a \`FocusTraversalGroup\`, which determines the widget subtree in which the policy will be effective. The member functions of the class are rarely called directly: they are meant to be used by the focus system. ## The focus manager The \[\`FocusManager\`]\[] maintains the current primary focus for the system. It only has a few pieces of API that are useful to users of the focus system. One is the \`FocusManager.instance.primaryFocus\` property, which contains the currently focused focus node and is also accessible from the global \`primaryFocus\` field. Other useful properties are \`FocusManager.instance.highlightMode\` and \`FocusManager.instance.highlightStrategy\`. These are used by widgets that need to switch between a "touch" mode and a "traditional" (mouse and keyboard) mode for their focus highlights. When a user is using touch to navigate, the focus highlight is usually hidden, and when they switch to a mouse or keyboard, the focus highlight needs to be shown again so they know what is focused. The \`highlightStrategy\` tells the focus manager how to interpret changes in the usage mode of the device: it can either automatically switch between the two based on the most recent input events, or it can be locked in touch or traditional modes. The provided widgets in Flutter already know how to use this information, so you only need it if you're writing your own controls from scratch. You can use \`addHighlightModeListener\` callback to listen for changes in the highlight mode. \[\`Actions\`]: https\://api.flutter.dev/flutter/widgets/Actions-class.html \[\`Builder\`]: https\://api.flutter.dev/flutter/widgets/Builder-class.html \[\`DirectionalFocusTraversalPolicyMixin\`]: https\://api.flutter.dev/flutter/widgets/DirectionalFocusTraversalPolicyMixin-mixin.html \[\`Focus\`]: https\://api.flutter.dev/flutter/widgets/Focus-class.html \[\`FocusableActionDetector\`]: https\://api.flutter.dev/flutter/widgets/FocusableActionDetector-class.html \[\`FocusManager\`]: https\://api.flutter.dev/flutter/widgets/FocusManager-class.html \[\`FocusNode\`]: https\://api.flutter.dev/flutter/widgets/FocusNode-class.html \[\`FocusOrder\`]: https\://api.flutter.dev/flutter/widgets/FocusOrder-class.html \[\`FocusScope\`]: https\://api.flutter.dev/flutter/widgets/FocusScope-class.html \[\`FocusScopeNode\`]: https\://api.flutter.dev/flutter/widgets/FocusScopeNode-class.html \[\`FocusTraversalGroup\`]: https\://api.flutter.dev/flutter/widgets/FocusTraversalGroup-class.html \[\`FocusTraversalOrder\`]: https\://api.flutter.dev/flutter/widgets/FocusTraversalOrder-class.html \[\`FocusTraversalPolicy\`]: https\://api.flutter.dev/flutter/widgets/FocusTraversalPolicy-class.html \[\`LexicalFocusOrder\`]: https\://api.flutter.dev/flutter/widgets/LexicalFocusOrder-class.html \[\`MouseRegion\`]: https\://api.flutter.dev/flutter/widgets/MouseRegion-class.html \[\`NumericFocusOrder\`]: https\://api.flutter.dev/flutter/widgets/NumericFocusOrder-class.html \[\`OrderedTraversalPolicy\`]: https\://api.flutter.dev/flutter/widgets/OrderedTraversalPolicy-class.html \[\`ReadingOrderTraversalPolicy\`]: https\://api.flutter.dev/flutter/widgets/ReadingOrderTraversalPolicy-class.html \[\`Shortcuts\`]: https\://api.flutter.dev/flutter/widgets/Shortcuts-class.html \[\`UnfocusDisposition.scope\`]: https\://api.flutter.dev/flutter/widgets/UnfocusDisposition.html

----
url: https://docs.flutter.dev/platform-integration/web/setup
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Set up web development](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

Learn how to set up your development environment to run, build, and deploy Flutter apps for the web platform.

## Install a web browser

[#](#install)

To run and debug your Flutter app on the web, [download and install Google Chrome](https://www.google.com/chrome/) or [install and use Microsoft Edge](https://www.microsoft.com/edge).

Expand for instructions for other browsers

If you want to debug your app in other web browsers, you can use the `flutter run -d web-server` command, and manually navigate to the specified URL in your preferred browser.

Note that debugging support in the `web-server` mode is limited.

## Validate your setup

[#](#validate-setup)

To ensure that you installed the browser successfully, and that Flutter can find it, run `flutter devices` in your preferred terminal.

You should at least see one connected device labeled **Chrome (web)** or **Edge (web)**, similar to the following:

```
$ flutter devices

Found 1 connected devices:
  Chrome (web)    • chrome • web-javascript • Google Chrome
```

If the command isn't found, or you don't see Chrome listed, check out [Set up troubleshooting](/install/troubleshoot).

## Start developing for the web

[#](#start-developing)

Now that you've set up web development for Flutter, you can continue your Flutter learning journey while testing on the web or begin expanding integration with the web.

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/platform-integration/web/setup.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/platform-integration/web/setup\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/platform-integration/web/setup.md "Report an issue with this page").

----
url: https://docs.flutter.dev/cookbook/games/firestore-multiplayer
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Add multiplayer support using Firestore](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

Multiplayer games need a way to synchronize game states between players. Broadly speaking, two types of multiplayer games exist:

1. **High tick rate**. These games need to synchronize game states many times per second with low latency. These would include action games, sports games, fighting games.

2. **Low tick rate**. These games only need to synchronize game states occasionally with latency having less impact. These would include card games, strategy games, puzzle games.

This resembles the differentiation between real-time versus turn-based games, though the analogy falls short. For example, real-time strategy games run—as the name suggests—in real-time, but that doesn't correlate to a high tick rate. These games can simulate much of what happens in between player interactions on local machines. Therefore, they don't need to synchronize game states that often.

If you can choose low tick rates as a developer, you should. Low tick lowers latency requirements and server costs. Sometimes, a game requires high tick rates of synchronization. For those cases, solutions such as Firestore *don't make a good fit*. Pick a dedicated multiplayer server solution such as [Nakama](https://heroiclabs.com/nakama/). Nakama has a [Dart package](https://pub.dev/packages/nakama).

If you expect that your game requires a low tick rate of synchronization, continue reading.

This recipe demonstrates how to use the [`cloud_firestore` package](https://pub.dev/packages/cloud_firestore) to implement multiplayer capabilities in your game. This recipe doesn't require a server. It uses two or more clients sharing game state using Cloud Firestore.

## 1. Prepare your game for multiplayer

[#](#1-prepare-your-game-for-multiplayer)

Write your game code to allow changing the game state in response to both local events and remote events. A local event could be a player action or some game logic. A remote event could be a world update coming from the server.

To simplify this cookbook recipe, start with the [`card`](https://github.com/flutter/games/tree/main/templates/card#readme) template that you'll find in the [`flutter/games` repository](https://github.com/flutter/games). Run the following command to clone that repository:

```
git clone https://github.com/flutter/games.git
```

Open the project in `templates/card`.

## 2. Install Firestore

[#](#2-install-firestore)

[Cloud Firestore](https://cloud.google.com/firestore/) is a horizontally scaling, NoSQL document database in the cloud. It includes built-in live synchronization. This is perfect for our needs. It keeps the game state updated in the cloud database, so every player sees the same state.

If you want a quick, 15-minute primer on Cloud Firestore, check out the following video:

[Watch on YouTube in a new tab: "What is a NoSQL Database? Learn about Cloud Firestore"](https://www.youtube.com/watch/v_hR4K4auoQ)

To add Firestore to your Flutter project, follow the first two steps of the [Get started with Cloud Firestore](https://firebase.google.com/docs/firestore/quickstart) guide:

* [Create a Cloud Firestore database](https://firebase.google.com/docs/firestore/quickstart#create)
* [Set up your development environment](https://firebase.google.com/docs/firestore/quickstart#set_up_your_development_environment)

The desired outcomes include:

* A Firestore database ready in the cloud, in **Test mode**
* A generated `firebase_options.dart` file
* The appropriate plugins added to your `pubspec.yaml`

You *don't* need to write any Dart code in this step. As soon as you understand the step of writing Dart code in that guide, return to this recipe.

## 3. Initialize Firestore

[#](#3-initialize-firestore)

1. Open `lib/main.dart` and import the plugins, as well as the `firebase_options.dart` file that was generated by `flutterfire configure` in the previous step.

   dart

   ```
   import 'package:cloud_firestore/cloud_firestore.dart';
   import 'package:firebase_core/firebase_core.dart';
   ​
   import 'firebase_options.dart';
   ```

2. Add the following code just above the call to `runApp()` in `lib/main.dart`:

   dart

   ```
   WidgetsFlutterBinding.ensureInitialized();
   ​
   await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
   ```

   This ensures that Firebase is initialized on game startup.

3. Add the Firestore instance to the app. That way, any widget can access this instance. Widgets can also react to the instance missing, if needed.

   To do this with the `card` template, you can use the `provider` package (which is already installed as a dependency).

   Replace the boilerplate `runApp(MyApp())` with the following:

   dart

   ```
   runApp(Provider.value(value: FirebaseFirestore.instance, child: MyApp()));
   ```

   Put the provider above `MyApp`, not inside it. This enables you to test the app without Firebase.

   :::note In case you are *not* working with the `card` template, you must either [install the `provider` package](https://pub.dev/packages/provider/install) or use your own method of accessing the `FirebaseFirestore` instance from various parts of your codebase. :::

## 4. Create a Firestore controller class

[#](#4-create-a-firestore-controller-class)

Though you can talk to Firestore directly, you should write a dedicated controller class to make the code more readable and maintainable.

How you implement the controller depends on your game and on the exact design of your multiplayer experience. For the case of the `card` template, you could synchronize the contents of the two circular playing areas. It's not enough for a full multiplayer experience, but it's a good start.

To create a controller, copy, then paste the following code into a new file called `lib/multiplayer/firestore_controller.dart`.

dart

```
import 'dart:async';
​
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/foundation.dart';
import 'package:logging/logging.dart';
​
import '../game_internals/board_state.dart';
import '../game_internals/playing_area.dart';
import '../game_internals/playing_card.dart';
​
class FirestoreController {
  static final _log = Logger('FirestoreController');
​
  final FirebaseFirestore instance;
​
  final BoardState boardState;
​
  /// For now, there is only one match. But in order to be ready
  /// for match-making, put it in a Firestore collection called matches.
  late final DocumentReference<Map<String, Object?>> _matchRef = instance
      .collection('matches')
      .doc('match_1');
​
  late final DocumentReference<List<PlayingCard>> _areaOneRef = _matchRef
      .collection('areas')
      .doc('area_one')
      .withConverter<List<PlayingCard>>(
        fromFirestore: _cardsFromFirestore,
        toFirestore: _cardsToFirestore,
      );
​
  late final DocumentReference<List<PlayingCard>> _areaTwoRef = _matchRef
      .collection('areas')
      .doc('area_two')
      .withConverter<List<PlayingCard>>(
        fromFirestore: _cardsFromFirestore,
        toFirestore: _cardsToFirestore,
      );
​
  late final StreamSubscription<void> _areaOneFirestoreSubscription;
  late final StreamSubscription<void> _areaTwoFirestoreSubscription;
​
  late final StreamSubscription<void> _areaOneLocalSubscription;
  late final StreamSubscription<void> _areaTwoLocalSubscription;
​
  FirestoreController({required this.instance, required this.boardState}) {
    // Subscribe to the remote changes (from Firestore).
    _areaOneFirestoreSubscription = _areaOneRef.snapshots().listen((snapshot) {
      _updateLocalFromFirestore(boardState.areaOne, snapshot);
    });
    _areaTwoFirestoreSubscription = _areaTwoRef.snapshots().listen((snapshot) {
      _updateLocalFromFirestore(boardState.areaTwo, snapshot);
    });
​
    // Subscribe to the local changes in game state.
    _areaOneLocalSubscription = boardState.areaOne.playerChanges.listen((_) {
      _updateFirestoreFromLocalAreaOne();
    });
    _areaTwoLocalSubscription = boardState.areaTwo.playerChanges.listen((_) {
      _updateFirestoreFromLocalAreaTwo();
    });
​
    _log.fine('Initialized');
  }
​
  void dispose() {
    _areaOneFirestoreSubscription.cancel();
    _areaTwoFirestoreSubscription.cancel();
    _areaOneLocalSubscription.cancel();
    _areaTwoLocalSubscription.cancel();
​
    _log.fine('Disposed');
  }
​
  /// Takes the raw JSON snapshot coming from Firestore and attempts to
  /// convert it into a list of [PlayingCard]s.
  List<PlayingCard> _cardsFromFirestore(
    DocumentSnapshot<Map<String, Object?>> snapshot,
    SnapshotOptions? options,
  ) {
    final data = snapshot.data()?['cards'] as List<Object?>?;
​
    if (data == null) {
      _log.info('No data found on Firestore, returning empty list');
      return [];
    }
​
    try {
      return data
          .cast<Map<String, Object?>>()
          .map(PlayingCard.fromJson)
          .toList();
    } catch (e) {
      throw FirebaseControllerException(
        'Failed to parse data from Firestore: $e',
      );
    }
  }
​
  /// Takes a list of [PlayingCard]s and converts it into a JSON object
  /// that can be saved into Firestore.
  Map<String, Object?> _cardsToFirestore(
    List<PlayingCard> cards,
    SetOptions? options,
  ) {
    return {'cards': cards.map((c) => c.toJson()).toList()};
  }
​
  /// Updates Firestore with the local state of [area].
  Future<void> _updateFirestoreFromLocal(
    PlayingArea area,
    DocumentReference<List<PlayingCard>> ref,
  ) async {
    try {
      _log.fine('Updating Firestore with local data (${area.cards}) ...');
      await ref.set(area.cards);
      _log.fine('... done updating.');
    } catch (e) {
      throw FirebaseControllerException(
        'Failed to update Firestore with local data (${area.cards}): $e',
      );
    }
  }
​
  /// Sends the local state of `boardState.areaOne` to Firestore.
  void _updateFirestoreFromLocalAreaOne() {
    _updateFirestoreFromLocal(boardState.areaOne, _areaOneRef);
  }
​
  /// Sends the local state of `boardState.areaTwo` to Firestore.
  void _updateFirestoreFromLocalAreaTwo() {
    _updateFirestoreFromLocal(boardState.areaTwo, _areaTwoRef);
  }
​
  /// Updates the local state of [area] with the data from Firestore.
  void _updateLocalFromFirestore(
    PlayingArea area,
    DocumentSnapshot<List<PlayingCard>> snapshot,
  ) {
    _log.fine('Received new data from Firestore (${snapshot.data()})');
​
    final cards = snapshot.data() ?? [];
​
    if (listEquals(cards, area.cards)) {
      _log.fine('No change');
    } else {
      _log.fine('Updating local data with Firestore data ($cards)');
      area.replaceWith(cards);
    }
  }
}
​
class FirebaseControllerException implements Exception {
  final String message;
​
  FirebaseControllerException(this.message);
​
  @override
  String toString() => 'FirebaseControllerException: $message';
}
```

Notice the following features of this code:

* The controller's constructor takes a `BoardState`. This enables the controller to manipulate the local state of the game.

* The controller subscribes to both local changes to update Firestore and to remote changes to update the local state and UI.

* The fields `_areaOneRef` and `_areaTwoRef` are Firebase document references. They describe where the data for each area resides, and how to convert between the local Dart objects (`List<PlayingCard>`) and remote JSON objects (`Map<String, dynamic>`). The Firestore API lets us subscribe to these references with `.snapshots()`, and write to them with `.set()`.

## 5. Use the Firestore controller

[#](#5-use-the-firestore-controller)

1. Open the file responsible for starting the play session: `lib/play_session/play_session_screen.dart` in the case of the `card` template. You instantiate the Firestore controller from this file.

2. Import Firebase and the controller:

   dart

   ```
   import 'package:cloud_firestore/cloud_firestore.dart';
   ​
   import '../multiplayer/firestore_controller.dart';
   ```

3. Add a nullable field to the `_PlaySessionScreenState` class to contain a controller instance:

   dart

   ```
   FirestoreController? _firestoreController;
   ```

4. In the `initState()` method of the same class, add code that tries to read the FirebaseFirestore instance and, if successful, constructs the controller. You added the `FirebaseFirestore` instance to `main.dart` in the *Initialize Firestore* step.

   dart

   ```
   final firestore = context.read<FirebaseFirestore?>();
   if (firestore == null) {
     _log.warning(
       "Firestore instance wasn't provided. "
       'Running without _firestoreController.',
     );
   } else {
     _firestoreController = FirestoreController(
       instance: firestore,
       boardState: _boardState,
     );
   }
   ```

5. Dispose of the controller using the `dispose()` method of the same class.

   dart

   ```
   _firestoreController?.dispose();
   ```

## 6. Test the game

[#](#6-test-the-game)

1. Run the game on two separate devices or in 2 different windows on the same device.

2. Watch how adding a card to an area on one device makes it appear on the other one.

3. Open the [Firebase web console](https://console.firebase.google.com/) and navigate to your project's Firestore Database.

4. Watch how it updates the data in real time. You can even edit the data in the console and see all running clients update.

### Troubleshooting

[#](#troubleshooting)

The most common issues you might encounter when testing Firebase integration include the following:

* **The game crashes when trying to reach Firebase.**

  * Firebase integration hasn't been properly set up. Revisit *Step 2* and make sure to run `flutterfire configure` as part of that step.

* **The game doesn't communicate with Firebase on macOS.**

  * By default, macOS apps don't have internet access. Enable [internet entitlement](/data-and-backend/networking#macos) first.

## 7. Next steps

[#](#7-next-steps)

At this point, the game has near-instant and dependable synchronization of state across clients. It lacks actual game rules: what cards can be played when, and with what results. This depends on the game itself and is left to you to try.

At this point, the shared state of the match only includes the two playing areas and the cards within them. You can save other data into `_matchRef`, too, like who the players are and whose turn it is. If you're unsure where to start, follow [a Firestore codelab or two](https://codelabs.developers.google.com/?product=flutter\&text=firestore) to familiarize yourself with the API.

At first, a single match should suffice for testing your multiplayer game with colleagues and friends. As you approach the release date, think about authentication and match-making. Thankfully, Firebase provides a [built-in way to authenticate users](https://firebase.google.com/docs/auth/flutter/start) and the Firestore database structure can handle multiple matches. Instead of a single `match_1`, you can populate the matches collection with as many records as needed.

An online match can start in a "waiting" state, with only the first player present. Other players can see the "waiting" matches in some kind of lobby. Once enough players join a match, it becomes "active". Once again, the exact implementation depends on the kind of online experience you want. The basics remain the same: a large collection of documents, each representing one active or potential match.

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-08-24. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/games/firestore-multiplayer.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/cookbook/games/firestore-multiplayer\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/games/firestore-multiplayer.md "Report an issue with this page").

----
url: https://docs.flutter.dev/resources
----

[Glossary](/resources/glossary)

[A glossary reference for terminology used across docs.flutter.dev.](/resources/glossary)

[In-app purchases overview](/resources/in-app-purchases-overview)

[Learn about the resources available for adding in-app purchases to your Flutter app.](/resources/in-app-purchases-overview)

[Flutter News Toolkit](/resources/news-toolkit)

[Learn more about creating a newsfeed app in Flutter.](/resources/news-toolkit)

[Flutter support](/resources/support)

[Where can you get support when developing with Flutter.](/resources/support)

[FAQ](/resources/faq)

[Frequently asked questions and answers about Flutter.](/resources/faq)

[Ads overview](/resources/ads-overview)

[Learn about the resources available for adding ads to your Flutter app.](/resources/ads-overview)

[Inside Flutter](/resources/inside-flutter)

[Learn about Flutter's inner workings from one of the founding engineers.](/resources/inside-flutter)

[Casual Games Toolkit](/resources/games-toolkit)

[Learn about free & open source multiplatform 2D game development in Flutter.](/resources/games-toolkit)

[Payments overview](/resources/payments-overview)

[Learn about the resources available for Google pay and other services to your Flutter app.](/resources/payments-overview)

[Videos](/resources/videos)

[Available videos on various aspects of developing in Flutter.](/resources/videos)

[Create useful bug reports](/resources/bug-reports)

[Where to file bug reports and enhancement requests for flutter and the website.](/resources/bug-reports)

[Flutter architectural overview](/resources/architectural-overview)

[A high-level overview of the architecture of Flutter, including the core principles and concepts that form its design.](/resources/architectural-overview)

[Online courses](/resources/courses)

[An index of online courses teaching Flutter development.](/resources/courses)

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/resources/index.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/resources\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/resources/index.md "Report an issue with this page").

----
url: https://docs.flutter.dev/app-architecture/concepts.md
----

\# Common architecture concepts > Learn about common architecture concepts in application design, and how they apply to Flutter. In this section, you'll find tried and true principles that guide architectural decisions in the larger world of app development, as well as information about how they fit into Flutter specifically. It's a gentle introduction to vocabulary and concepts related to the recommended architecture and best practices, so they can be explored in more detail throughout this guide. ## Separation of concerns \[Separation-of-concerns]\[] is a core principle in app development that promotes modularity and maintainability by dividing an application's functionality into distinct, self-contained units. From a high-level, this means separating your UI logic from your business logic. This is often described as \*layered\* architecture. Within each layer, you should further separate your application by feature or functionality. For example, your application's authentication logic should be in a different class than the search logic. In Flutter, this applies to \[widgets]\(/resources/glossary#widget) in the UI layer as well. You should write reusable, lean widgets that hold as little logic as possible. ## Layered architecture Flutter applications should be written in \*layers\*. Layered architecture is a software design pattern that organizes an application into distinct layers, each with specific roles and responsibilities. Typically, applications are separated into 2 to 3 layers, depending on complexity. \* \*\*UI layer\*\* - Displays data to the user that is exposed by the business logic layer, and handles user interaction. This is also commonly referred to as the "presentation layer". \* \*\*Logic layer\*\* - Implements core business logic, and facilitates interaction between the data layer and UI layer. Commonly known as the "domain layer". The logic layer is optional, and only needs to be implemented if your application has complex business logic that happens on the client. Many apps are only concerned with presenting data to a user and allowing the user to change that data (colloquially known as CRUD apps). These apps might not need this optional layer. \* \*\*Data layer\*\* - Manages interactions with data sources, such as databases or platform plugins. Exposes data and methods to the business logic layer. These are called "layers" because each layer can only communicate with the layers directly below or above it. The UI layer shouldn't know that the data layer exists, and vice versa. ## Single source of truth Every data type in your app should have a \[single source of truth]\[] (SSOT). The source of truth is responsible for representing local or remote state. If the data can be modified in the app, the SSOT class should be the only class that can do so. This can dramatically reduce the number of bugs in your application, and it can simplify code because you'll only ever have one copy of the same data. Generally, the source of truth for any given type of data in your application is held in a class called a \*\*Repository\*\*, which is part of the data layer. There is typically one repository class for each type of data in your app. This principle can be applied across layers and components in your application as well as within individual classes. For example, a Dart class might use \[getters]\[] to derive values from an SSOT field (instead of having multiple fields that need to be updated independently) or a list of \[records]\[] to group related values (instead of parallel lists whose indices might get out of sync). ## Unidirectional data flow \[Unidirectional data flow]\[] (UDF) refers to a design pattern that helps decouple state from the UI that displays that state. In the simplest terms, state flows from the data layer through the logic layer and eventually to the widgets in the UI layer. Events from user-interaction flow the opposite direction, from the presentation layer back through the logic layer and to the data layer. In UDF, the update loop from user interaction to re-rendering the UI looks like this: 1. \[UI layer] An event occurs due to user interaction, such as a button being clicked. The widget's event handler callback invokes a method exposed by a class in the logic layer. 2. \[Logic layer] The logic class calls methods exposed by a repository that know how to mutate the data. 3. \[Data layer] The repository updates data (if necessary) and then provides the new data to the logic class. 4. \[Logic layer] The logic class saves its new state, which it sends to the UI. 5. \[UI layer] The UI displays the new state of the view model. New data can also start at the data layer. For example, a repository might poll an HTTP server for new data. In this case, the data flow only makes the second half of the journey. The most important idea is that data changes always happen in the \[SSOT]\[], which is the data layer. This makes your code easier to understand, less error prone, and prevents malformed or unexpected data from being created. ## UI is a function of (immutable) state Flutter is declarative, meaning that it builds its UI to reflect the current state of your app. When state changes, your app should trigger a rebuild of the UI that depends on that state. In Flutter, you'll often hear this described as "UI is a function of state". It's crucial that your data drive your UI, and not the other way around. Data should be immutable and persistent, and views should contain as little logic as possible. This minimizes the possibility of data being lost when an app is closed, and makes your app more testable and resilient to bugs. ## Extensibility Each piece of architecture should have a well defined list of inputs and outputs. For example, a view model in the logic layer should only take in data sources as inputs, such as repositories, and should only expose commands and data formatted for views. Using clean interfaces in this way allows you to swap out concrete implementations of your classes without needing to change any of the code that consumes the interface. ## Testability The principles that make software extensible also make software easier to test. For example, you can test the self-contained logic of a view model by mocking a repository. The view model tests don't require you to mock other parts of your application, and you can test your UI logic separate from Flutter widgets themselves. Your app will also be more flexible. It will be straightforward and low risk to add new logic and new UI. For example, adding a new view model cannot break any logic from the data or business logic layers. The next section explains the idea of inputs and outputs for any given component in your application's architecture. \[Separation-of-concerns]: https\://en.wikipedia.org/wiki/Separation\_of\_concerns \[single source of truth]: https\://en.wikipedia.org/wiki/Single\_source\_of\_truth \[SSOT]: https\://en.wikipedia.org/wiki/Single\_source\_of\_truth \[getters]: https\://dart.dev/effective-dart/design#do-use-getters-for-operations-that-conceptually-access-properties \[records]: https\://dart.dev/language/records \[Unidirectional data flow]: https\://en.wikipedia.org/wiki/Unidirectional\_Data\_Flow\_(computer\_science) ## Feedback As this section of the website is evolving, we \[welcome your feedback]\[]! \[welcome your feedback]: https\://google.qualtrics.com/jfe/form/SV\_4T0XuR9Ts29acw6?page="concepts"

----
url: https://docs.flutter.dev/brand.md
----

\# Flutter Brand Guidelines > The guidelines governing the usage of the Flutter trademarks and assets. The "Flutter" name and logo are trademarks owned by Google. These Brand Guidelines describe the appropriate uses of the Flutter trademarks by members of the developer community who have obtained our consent to use the trademarks pursuant to the \[Flutter Terms of Service]\(/tos). These guidelines will ensure that the Flutter trademarks are used in a manner that promotes Google's mission to provide a free and open source SDK for crafting high-quality native interfaces on iOS and Android in record time, and are not associated with objectionable material, as determined by Google. Use of the Flutter trademarks that is not expressly permitted by these guidelines is prohibited absent written permission from Google. The official Flutter assets and further guidelines on representing the brand can be found at \[Representing the Flutter Brand]\(https\://flutter.dev/brand). ## General Rules That Govern the Use of the Flutter Trademarks You are free to use the Flutter trademarks: (i) in connection with your download and use of the Flutter SDK to build and develop apps, (ii) in training materials (e.g., video tutorials, online publications, etc.) that provide instructions or tips regarding how to use the Flutter SDK to build and develop apps, and (iii) to show your support for the use of the Flutter SDK by members of the developer community. These guidelines do not restrict your right to use the "Flutter" name in connection with descriptions of the Flutter SDK that would be considered "fair use." For example, you may use the "Flutter" name to make truthful factual statements (e.g., "built with the Flutter SDK") or to accurately describe a feature of the Flutter SDK. You may use the Flutter trademarks on your personal website, personal blog, or social media account to show your support for the Flutter SDK, provided you do not use the Flutter trademarks in a way that could confuse people into thinking that your site is an official Google site or that Google has sponsored or endorsed your site. In the case of websites or personal blogs, this means you should not use the Flutter trademarks as the primary element on the webpage (e.g., in the masthead of the webpage or the title of the blog). In the case of social media accounts, this means you should not use the Flutter trademarks in the background, in your profile image or in your social media username. ## Specific Rules for Proper Usage of the Flutter Trademarks In addition to the general rules discussed above, below are specific rules governing the proper use of the Flutter trademarks. \*\*DO:\*\* \* Use the "Flutter" name as an adjective, never as a noun or verb, and never in the plural or possessive form. \* Use a generic term following the "Flutter" name, for example, "the Flutter SDK" or "the Flutter UI toolkit." \* Distinguish the "Flutter" name from the surrounding text in some way. Capitalize the first letter, capitalize or italicize the entire mark, place the mark in quotes, use a different type style or font for the mark. \* Use the trademark symbol TM for the first or most prominent time the "Flutter" name appears in text on your website or blog. Make sure to always use the TM symbol, not the ® symbol. \* Include the following text near the first or most prominent use of the Flutter marks on your website or blog: "Flutter and the related logo are trademarks of Google LLC. We are not endorsed by or affiliated with Google LLC." \* Keep some distance between the Flutter trademarks and any other trademarks, logos, or icons that are displayed on the webpage. \*\*DON'T:\*\* \* Don't alter, distort, or modify the Flutter trademarks in any way. This includes varying the spelling of the "Flutter" name, or displaying the Flutter logo with color variations or unapproved visual elements. \* Don't combine the Google name with the "Flutter" name to form a unitary brand (e.g., don't use the phrases "Google Flutter" or "Google's Flutter"). You may use the Google name in full text to accurately describe the Flutter SDK (e.g., "The Flutter SDK by Google"). \* Don't register the Flutter trademarks or any trademarks, logos, or domain names that are confusingly similar to them. \* Don't incorporate the Flutter trademarks into your own product names, service names, trademarks, logos, or company names. \* Don't display the Flutter trademarks in a manner that is misleading, unfair, defamatory, infringing, libelous, disparaging, obscene or otherwise objectionable to Google. \* Don't use the Flutter trademarks on or in connection with the sale of any non-software goods or services (e.g., merchandise such as clothing, pens, and stickers). ## Community Use Exceptions To allow for the use of the Flutter trademarks by the Flutter community, below are specific exceptions to the rules described above: 1. Local Flutter user groups may: (i) use the "Flutter" name as part of their social media username in the following format: "Flutter + \[name of country/city]" (e.g., "Flutter France"); and (ii) use the Flutter logo in the national colors of the country where the user group is based (e.g., for a user group based in France, the colors blue, white and red), provided the Flutter logo is otherwise unaltered. Such social media accounts should include a disclaimer that clarifies that it is not an official Google account. 2. You may use the Flutter trademarks as part of the name of a newsletter or related community content (e.g., Flutter training courses, Flutter community forums) whose purpose is to promote the use of the Flutter SDK by members of the developer community. Where the Flutter trademarks are displayed on a website as part of a community site name, you should use the trademark symbol TM after the most prominent appearance of the "Flutter" name and include the following text: "Flutter and the related logo are trademarks of Google LLC. We are not endorsed by or affiliated with Google LLC." Where the Flutter trademarks are displayed on a social media account as part of a community site name, you should include a disclaimer that clarifies that it is not an official Google account. 3. **\[Unofficial Flutter Events]** You may use the Flutter trademarks as part of the name of a community event (e.g. conference), but please make sure to include the following disclaimer on the event website in a prominent and easy-to-see spot: "Flutter and the related logo are trademarks of Google LLC. \[Title of event] is not affiliated with or otherwise sponsored by Google LLC."

----
url: https://docs.flutter.dev/ui/navigation
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Navigation and routing](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

Flutter provides a complete system for navigating between screens and handling deep links. Small applications without complex deep linking can use [`Navigator`](https://api.flutter.dev/flutter/widgets/Navigator-class.html), while apps with specific deep linking and navigation requirements should also use the [`Router`](https://api.flutter.dev/flutter/widgets/Router-class.html) to correctly handle deep links on Android and iOS, and to stay in sync with the address bar when the app is running on the web.

To configure your Android or iOS application to handle deep links, see [Deep linking](/ui/navigation/deep-linking).

## Using the Navigator

[#](#using-the-navigator)

The `Navigator` widget displays screens as a stack using the correct transition animations for the target platform. To navigate to a new screen, access the `Navigator` through the route's `BuildContext` and call imperative methods such as `push()` `or pop()`:

dart

```
child: const Text('Open second screen'),
onPressed: () {
  Navigator.of(context).push(
    MaterialPageRoute<void>(
      builder: (context) => const SecondScreen(),
    ),
  );
},
```

Because `Navigator` keeps a stack of `Route` objects (representing the history stack), The `push()` method also takes a `Route` object. The `MaterialPageRoute` object is a subclass of `Route` that specifies the transition animations for Material Design. For more examples of how to use the `Navigator`, follow the [navigation recipes](/cookbook/navigation) from the Flutter Cookbook or visit the [Navigator API documentation](https://api.flutter.dev/flutter/widgets/Navigator-class.html).

## Using named routes

[#](#using-named-routes)

Applications with simple navigation and deep linking requirements can use the `Navigator` for navigation and the [`MaterialApp.routes`](https://pub.dev/documentation/material_ui/latest/material_ui/MaterialApp/routes.html) parameter for deep links:

dart

```
child: const Text('Open second screen'),
onPressed: () {
  Navigator.pushNamed(context, '/second');
},
```

`/second` represents a *named route* that was declared in the `MaterialApp.routes` list. For a complete example, follow the [Navigate with named routes](/cookbook/navigation/named-routes) recipe from the Flutter Cookbook.

### Limitations

[#](#limitations)

Although named routes can handle deep links, the behavior is always the same and can't be customized. When a new deep link is received by the platform, Flutter pushes a new `Route` onto the Navigator regardless of where the user currently is.

Flutter also doesn't support the browser forward button for applications using named routes. For these reasons, we don't recommend using named routes in most applications. Instead, use a routing package like [go\_router](https://pub.dev/packages/go_router) or use `Navigator` with [`MaterialPageRoute`](https://pub.dev/documentation/material_ui/latest/material_ui/MaterialPageRoute-class.html).

## Using the Router

[#](#using-the-router)

Flutter applications with advanced navigation and routing requirements (such as a web app that uses direct links to each screen, or an app with multiple `Navigator` widgets) should use a routing package such as [go\_router](https://pub.dev/packages/go_router) that can parse the route path and configure the `Navigator` whenever the app receives a new deep link.

To use the Router, switch to the `router` constructor on `MaterialApp` or `CupertinoApp` and provide it with a `Router` configuration. Routing packages, such as [go\_router](https://pub.dev/packages/go_router), typically provide route configuration and routes can be used as follows:

dart

```
child: const Text('Open second screen'),
onPressed: () => context.go('/second'),
```

Because packages like go\_router are *declarative*, they will always display the same screen(s) when a deep link is received.

## Using Router and Navigator together

[#](#using-router-and-navigator-together)

The `Router` and `Navigator` are designed to work together. You can navigate using the `Router` API through a declarative routing package, such as `go_router`, or by calling imperative methods such as `push()` and `pop()` on the `Navigator`.

When you navigate using the `Router` or a declarative routing package, each route on the Navigator is *page-backed*, meaning it was created from a [`Page`](https://api.flutter.dev/flutter/widgets/Page-class.html) using the [`pages`](https://api.flutter.dev/flutter/widgets/Navigator/pages.html) argument on the `Navigator` constructor. Conversely, any `Route` created by calling `Navigator.push` or `showDialog` will add a *pageless* route to the Navigator. If you are using a routing package, Routes that are *page-backed* are always deep-linkable, whereas *pageless* routes are not.

When a *page-backed* `Route` is removed from the `Navigator`, all of the *pageless* routes after it are also removed. For example, if a deep link navigates by removing a *page-backed* route from the Navigator, all *pageless* routes after (up until the next *page-backed* route) are removed too.

## Web support

[#](#web-support)

Apps using the `Router` class integrate with the browser History API to provide a consistent experience when using the browser's back and forward buttons. Whenever you navigate using the `Router`, a History API entry is added to the browser's history stack. Pressing the **back** button uses *[reverse chronological navigation](https://material.io/design/navigation/understanding-navigation.html#reverse-navigation)*, meaning that the user is taken to the previously visited location that was shown using the `Router`. This means that if the user pops a page from the `Navigator` and then presses the browser **back** button the previous page is pushed back onto the stack.

## More information

[#](#more-information)

For more information on navigation and routing, check out the following resources:

* The Flutter cookbook includes multiple [navigation recipes](/cookbook/navigation) that show how to use the `Navigator`.
* The [`Navigator`](https://api.flutter.dev/flutter/widgets/Navigator-class.html) and [`Router`](https://api.flutter.dev/flutter/widgets/Router-class.html) API documentation contain details on how to set up declarative navigation without a routing package.
* [Understanding navigation](https://material.io/design/navigation/understanding-navigation.html), a page from the Material Design documentation, outlines concepts for designing the navigation in your app, including explanations for forward, upward, and chronological navigation.
* [Learning Flutter's new navigation and routing system](https://flutter.dev/blog/learning-flutters-new-navigation-and-routing-system), a Flutter blog post, describes how to use the `Router` widget directly, without a routing package.
* The [Router design document](https://flutter.dev/go/navigator-with-router) contains the motivation and design of the `Router` API.

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-09-29. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/ui/navigation/index.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/ui/navigation\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/ui/navigation/index.md "Report an issue with this page").

----
url: https://docs.flutter.dev/cookbook/design/fonts.md
----

\# Use a custom font > How to use custom fonts.

<!--?code-excerpt path-base="cookbook/design/fonts/"?-->

:::secondary What you'll learn \* How to choose a font. \* How to import font files. \* How to set a font as a default. \* How to use a font in a given widget. ::: Although Android and iOS offer high quality system fonts, designers want support for custom fonts. You might have a custom-built font from a designer, or perhaps you downloaded a font from \[Google Fonts]\[]. A typeface is the collection of glyphs or shapes that comprise a given style of lettering. A font is one representation of that typeface at a given weight or variation. Roboto is a typeface and Roboto Bold is a font. Flutter lets you apply a custom font across an entire app or to individual widgets. This recipe creates an app that uses custom fonts with the following steps. 1. Choose your fonts. 1. Import the font files. 1. Declare the font in the pubspec. 1. Set a font as the default. 1. Use a font in a specific widget. You don't need to follow each step as you go. The guide offers completed example files at the end. :::note This guide makes the following presumptions: 1. You've \[set up your Flutter environment]\[]. 1. You've \[created a new Flutter app]\[new-flutter-app] named \`custom\_fonts\`. If you haven't completed these steps yet, do so before continuing with this guide. 1. You're performing the provided commands in a macOS or Linux shell and using \`vi\`. You can substitute any text editor for \`vi\`. Windows users should use the appropriate commands and paths when performing the steps. 1. You're adding the Raleway and RobotoMono fonts to your Flutter app. ::: \[set up your Flutter environment]: /install \[new-flutter-app]: /reference/create-new-app ## Choose a font Your choice of font should be more than a preference. Consider which file formats work with Flutter and how the font could affect design options and app performance. #### Pick a supported font format Flutter supports the following font formats: \* OpenType font collections: \`.ttc\` \* TrueType fonts: \`.ttf\` \* OpenType fonts: \`.otf\` Flutter does not support fonts in the Web Open Font Format, \`.woff\` and \`.woff2\`, on desktop platforms. #### Choose fonts for their specific benefits Few sources agree on what a font file type is or which uses less space. The key difference between font file types involves how the format encodes the glyphs in the file. Most TrueType and OpenType font files have similar capabilities as they borrowed from each other as the formats and fonts improved over time. Which font you should use depends on the following considerations. \* How much variation you need for fonts in your app? \* How much file size you can accept fonts using in your app? \* How many languages you need to support in your app? Research what options a given font offers, like more than one weight or style per font file, \[variable font capability]\[variable-fonts], the availability of multiple font files for a multiple font weights, or more than one width per font. Choose the typeface or font family that meets the design needs of your app. :::secondary To learn how to get direct access to over 1,000 open-sourced font families, check out the \[google\_fonts]\[] package. To learn about another approach to using custom fonts that allows you to re-use one font over multiple projects, check out \[Export fonts from a package]\[]. ::: ## Import the font files To work with a font, import its font files into your Flutter project. To import font files, perform the following steps. 1. If necessary, to match the remaining steps in this guide, change the name of your Flutter app to \`custom\_fonts\`. \`\`\`console $ mv /path/to/my\_app /path/to/custom\_fonts \`\`\` 1. Navigate to the root of your Flutter project. \`\`\`console $ cd /path/to/custom\_fonts \`\`\` 1. Create a \`fonts\` directory at the root of your Flutter project. \`\`\`console $ mkdir fonts \`\`\` 1. Move or copy the font files in a \`fonts\` or \`assets\` folder at the root of your Flutter project. \`\`\`console $ cp \~/Downloads/\*.ttf ./fonts \`\`\` The resulting folder structure should resemble the following: \`\`\`plaintext custom\_fonts/ |- fonts/ |- Raleway-Regular.ttf |- Raleway-Italic.ttf |- RobotoMono-Regular.ttf |- RobotoMono-Bold.ttf \`\`\` ## Declare the font in the pubspec.yaml file After you've downloaded a font, include a font definition in the \`pubspec.yaml\` file. This font definition also specifies which font file should be used to render a given weight or style in your app. ### Define fonts in the \`pubspec.yaml\` file To add font files to your Flutter app, complete the following steps. 1. Open the \`pubspec.yaml\` file at the root of your Flutter project. \`\`\`console $ vi pubspec.yaml \`\`\` 1. Paste the following YAML block after the \`flutter\` declaration. \`\`\`yaml fonts: - family: Raleway fonts: - asset: fonts/Raleway-Regular.ttf - asset: fonts/Raleway-Italic.ttf style: italic - family: RobotoMono fonts: - asset: fonts/RobotoMono-Regular.ttf - asset: fonts/RobotoMono-Bold.ttf weight: 700 \`\`\` This \`pubspec.yaml\` file defines the italic style for the \`Raleway\` font family as the \`Raleway-Italic.ttf\` font file. When you set \`style: TextStyle(fontStyle: FontStyle.italic)\`, Flutter swaps \`Raleway-Regular\` with \`Raleway-Italic\`. The \`family\` value sets the name of the typeface. You use this name in the \[\`fontFamily\`]\[] property of a \[\`TextStyle\`]\[] object. The value of an \`asset\` is a relative path from the \`pubspec.yaml\` file to the font file. These files contain the outlines for the glyphs in the font. When building the app, Flutter includes these files in the app's asset bundle. ### Include font files for each font Different typefaces implement font files in different ways. If you need a typeface with a variety of font weights and styles, choose and import font files that represent that variety. When you import a font file that doesn't include either multiple fonts within it or variable font capabilities, don't use the \`style\` or \`weight\` property to adjust how they display. If you use those properties on a regular font file, Flutter attempts to \_simulate\_ the look. The visual result will look quite different from using the correct font file. ### Declare styles and weights Flutter doesn't infer a font's weight or style from its file name. To use a specific weight or style, you must explicitly declare the \`weight\` or \`style\` property for that font file in your \`pubspec.yaml\`. #### Set font weight 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\`]\[] and can be used in the \[\`fontWeight\`]\[fontWeight property] property of a \[\`TextStyle\`]\[] object. In the \`pubspec.yaml\` shown in this guide, you defined \`RobotoMono-Bold\` as the \`700\` weight of the font family. To use the \`RobotoMono-Bold\` font that you added to your app, set \`fontWeight\` to \`FontWeight.w700\` in your \`TextStyle\` widget. If you hadn't added \`RobotoMono-Bold\` to your app and declared its weight, Flutter attempts to make the regular font \_look\_ bold. The text then might appear to be somewhat darker. You can't use the \`weight\` property to override the weight of the font. You can't set \`RobotoMono-Bold\` to any other weight than \`700\`. If you set \`TextStyle(fontFamily: 'RobotoMono', fontWeight: FontWeight.w900)\`, the displayed font would still render as however bold \`RobotoMono-Bold\` looks. #### Set font style The \`style\` property specifies whether the glyphs in the font file display as either \`italic\` or \`normal\`. These values correspond to the \[\`FontStyle\`]\[]. You can use these styles in the \[\`fontStyle\`]\[fontStyle property] property of a \[\`TextStyle\`]\[] object. In the \`pubspec.yaml\` shown in this guide, you defined \`Raleway-Italic\` as being in the \`italic\` style. To use the \`Raleway-Italic\` font that you added to your app, set \`style: TextStyle(fontStyle: FontStyle.italic)\`. Flutter swaps \`Raleway-Regular\` with \`Raleway-Italic\` when rendering. If you hadn't added \`Raleway-Italic\` to your app and declared its style, Flutter attempts to make the regular font \_look\_ italic. The text then might appear to be leaning to the right. You can't use the \`style\` property to override the glyphs of a font. If you set \`TextStyle(fontFamily: 'Raleway', fontStyle: FontStyle.normal)\`, the displayed font would still render as italic. The \`regular\` style of an italic font \_is\_ italic. ## Set a font as the default To apply a font to text, you can set the font as the app's default font in its \`theme\`. To set a default font, set the \`fontFamily\` property in the app's \`theme\`. Match the \`fontFamily\` value to the \`family\` name declared in the \`pubspec.yaml\` file. The result would resemble the following code.

<!--?code-excerpt "lib/main.dart (MaterialApp)"?-->

\`\`\`dart return MaterialApp( title: 'Custom Fonts', // Set Raleway as the default app font. theme: ThemeData(fontFamily: 'Raleway'), home: const MyHomePage(), ); \`\`\` To learn more about themes, check out the \[Using Themes to share colors and font styles]\[] recipe. ## Set the font in a specific widget To apply the font to a specific widget like a \`Text\` widget, provide a \[\`TextStyle\`]\[] to the widget. For this guide, try to apply the \`RobotoMono\` font to a single \`Text\` widget. Match the \`fontFamily\` value to the \`family\` name declared in the \`pubspec.yaml\` file. The result would resemble the following code.

<!--?code-excerpt "lib/main.dart (Text)"?-->

\`\`\`dart child: Text( 'Roboto Mono sample', style: TextStyle(fontFamily: 'RobotoMono'), ), \`\`\` :::important If a \[\`TextStyle\`]\[] object specifies a weight or style without a corresponding font file, the engine uses a generic file for the font and attempts to extrapolate outlines for the requested weight and style. Avoid relying on this capability. Import the proper font file instead. ::: ## Try the complete example ### Download fonts Download the Raleway and RobotoMono font files from \[Google Fonts]\[]. ### Update the \`pubspec.yaml\` file 1. Open the \`pubspec.yaml\` file at the root of your Flutter project. \`\`\`console $ vi pubspec.yaml \`\`\` 1. Replace its contents with the following YAML. \`\`\`yaml name: custom\_fonts description: An example of how to use custom fonts with Flutter dependencies: flutter: sdk: flutter dev\_dependencies: flutter\_test: sdk: flutter flutter: fonts: - family: Raleway fonts: - asset: fonts/Raleway-Regular.ttf - asset: fonts/Raleway-Italic.ttf style: italic - family: RobotoMono fonts: - asset: fonts/RobotoMono-Regular.ttf - asset: fonts/RobotoMono-Bold.ttf weight: 700 uses-material-design: true \`\`\` ### Use this \`main.dart\` file 1. Open the \`main.dart\` file in the \`lib/\` directory of your Flutter project. \`\`\`console $ vi lib/main.dart \`\`\` 1. Replace its contents with the following Dart code.

<!--?code-excerpt "lib/main.dart"?-->

\`\`\`dart import 'package:material\_ui/material\_ui.dart'; void main() => runApp(const MyApp()); class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( title: 'Custom Fonts', // Set Raleway as the default app font. theme: ThemeData(fontFamily: 'Raleway'), home: const MyHomePage(), ); } } class MyHomePage extends StatelessWidget { const MyHomePage({super.key}); @override Widget build(BuildContext context) { return Scaffold( // The AppBar uses the app-default Raleway font. appBar: AppBar(title: const Text('Custom Fonts')), body: const Center( // This Text widget uses the RobotoMono font. child: Text( 'Roboto Mono sample', style: TextStyle(fontFamily: 'RobotoMono'), ), ), ); } } \`\`\` The resulting Flutter app should display the following screen. !\[Custom Fonts Demo]\(/assets/images/docs/cookbook/fonts.png){:.site-mobile-screenshot} \[variable-fonts]: https\://fonts.google.com/knowledge/introducing\_type/introducing\_variable\_fonts \[Export fonts from a package]: /cookbook/design/package-fonts \[\`fontFamily\`]: https\://api.flutter.dev/flutter/painting/TextStyle/fontFamily.html \[fontStyle property]: https\://api.flutter.dev/flutter/painting/TextStyle/fontStyle.html \[\`FontStyle\`]: https\://api.flutter.dev/flutter/dart-ui/FontStyle.html \[fontWeight property]: https\://api.flutter.dev/flutter/painting/TextStyle/fontWeight.html \[\`FontWeight\`]: https\://api.flutter.dev/flutter/dart-ui/FontWeight-class.html \[Google Fonts]: https\://fonts.google.com \[google\_fonts]: https\://pub.dev/packages/google\_fonts \[\`TextStyle\`]: https\://api.flutter.dev/flutter/painting/TextStyle-class.html \[Using Themes to share colors and font styles]: /cookbook/design/themes

----
url: https://docs.flutter.dev/platform-integration/web/embedding-flutter-web
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Adding Flutter to any web application](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

Flutter views and web content can be composed to produce a web application in different ways. Choose one of the following depending on your use-case:

* A Flutter view controls the full page ([full page mode](#full-page-mode))
* Adding Flutter views to an existing web application ([embedded mode](#embedded-mode))

## Full page mode

[#](#full-page-mode)

In full page mode, the Flutter web application takes control of the whole browser window and covers its viewport completely when rendering.

This is the default embedding mode for new Flutter web projects, and no additional configuration is needed.

html

```
<!DOCTYPE html>
<html>
  <head>
  </head>
  <body>
    <script src="flutter_bootstrap.js" defer></script>
  </body>
</html>
```

When Flutter web is launched without referencing `multiViewEnabled` or a `hostElement`, it uses full page mode.

To learn more about the `flutter_bootstrap.js` file, check out [Customize app initialization](/platform-integration/web/initialization).

### `iframe` embedding

[#](#iframe-embedding)

Full page mode is recommended when embedding a Flutter web application through an `iframe`. The page that embeds the `iframe` can size and position it as needed, and Flutter will fill it completely.

html

```
<iframe src="https://url-to-your-flutter/index.html"></iframe>
```

To learn more about the pros and cons of an `iframe`, check out the [Inline Frame element](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe) docs on MDN.

### Compare `iframe` and direct DOM embedding

[#](#compare-iframe-and-dom-embedding)

When integrating Flutter into an existing web application, choose between `iframe` embedding and direct DOM embedding (`hostElement` or multi-view) based on your isolation, performance, and communication requirements:

| Feature                       | `iframe` embedding                                                                          | Direct DOM embedding (`hostElement` / multiview)                                                     |
| ----------------------------- | ------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- |
| **Isolation & sandboxing**    | Full browser sandboxing with separate JS global scope, DOM tree, and CSS styles             | Shares the same DOM tree and JavaScript context                                                      |
| **Memory & engine instances** | Each `iframe` initializes its own Flutter engine, WebAssembly/JS runtime, and memory heap   | A single Flutter engine instance and memory heap can manage one or more views (when using multiview) |
| **JavaScript interop**        | Communication requires asynchronous messaging (such as `postMessage`)                       | Direct synchronous communication using `package:web` and `dart:js_interop`                           |
| **Styling & layout**          | Independent frame isolated from host page CSS                                               | Direct integration into host page CSS layout (such as flexbox and grid)                              |
| **State sharing**             | State must be synchronized across window boundaries                                         | Direct state sharin in Dart across all attached views                                                |
| **Best for**                  | Third-party widgets, untrusted content, isolated micro-frontends, or simple drop-in embeds. | Embedded UI components in existing web apps, multi-view dashboards, and tight host-app integration   |

## Embedded mode

[#](#embedded-mode)

Flutter web applications can also render content into an arbitrary number of elements (commonly `div`s) of another web application; this is called "embedded mode" (or "multi-view").

In this mode:

* A Flutter web application can launch, but doesn't render until the first "view" is added, with `addView`.
* The host application can add or remove views from the embedded Flutter web application.
* The Flutter application is notified when views are added or removed, so it can adjust its widgets accordingly.

### Enable multi-view mode

[#](#enable-multi-view-mode)

Enable multi-view mode setting `multiViewEnabled: true` in the `initializeEngine` method as shown:

flutter\_bootstrap.js

js

```
{{flutter_js}}
{{flutter_build_config}}
​
_flutter.loader.load({
  onEntrypointLoaded: async function onEntrypointLoaded(engineInitializer) {
    let engine = await engineInitializer.initializeEngine({
      multiViewEnabled: true, // Enables embedded mode.
    });
    let app = await engine.runApp();
    // Make this `app` object available to your JS app.
  }
});
```

### Manage Flutter views from JS

[#](#manage-flutter-views-from-js)

To add or remove views, use the `app` object returned by the `runApp` method:

js

```
// Adding a view...
let viewId = app.addView({
  hostElement: document.querySelector('#some-element'),
});
​
// Removing viewId...
let viewConfig = app.removeView(viewId);
```

### Handling view changes from Dart

[#](#handling-view-changes-from-dart)

View additions and removals are surfaced to Flutter through the [`didChangeMetrics` method](https://api.flutter.dev/flutter/widgets/WidgetsBindingObserver/didChangeMetrics.html) of the `WidgetsBinding` class.

The complete list of views attached to your Flutter app is available through the `WidgetsBinding.instance.platformDispatcher.views` iterable. These views are of [type `FlutterView`](https://api.flutter.dev/flutter/dart-ui/FlutterView-class.html).

To render content into each `FlutterView`, your Flutter app needs to create a [`View` widget](https://api.flutter.dev/flutter/widgets/View-class.html). `View` widgets can be grouped together under a [`ViewCollection` widget](https://api.flutter.dev/flutter/widgets/ViewCollection-class.html).

The following example, from the *Multi View Playground*, encapsulates the above in a `MultiViewApp` widget that can be used as the root widget for your app. A [`WidgetBuilder` function](https://api.flutter.dev/flutter/widgets/WidgetBuilder.html) runs for each `FlutterView`:

multi\_view\_app.dart

dart

```
import 'dart:ui' show FlutterView;
import 'package:flutter/widgets.dart';
​
/// Calls [viewBuilder] for every view added to the app to obtain the widget to
/// render into that view. The current view can be looked up with [View.of].
class MultiViewApp extends StatefulWidget {
  const MultiViewApp({super.key, required this.viewBuilder});
​
  final WidgetBuilder viewBuilder;
​
  @override
  State<MultiViewApp> createState() => _MultiViewAppState();
}
​
class _MultiViewAppState extends State<MultiViewApp> with WidgetsBindingObserver {
  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance.addObserver(this);
    _updateViews();
  }
​
  @override
  void didUpdateWidget(MultiViewApp oldWidget) {
    super.didUpdateWidget(oldWidget);
    // Need to re-evaluate the viewBuilder callback for all views.
    _views.clear();
    _updateViews();
  }
​
  @override
  void didChangeMetrics() {
    _updateViews();
  }
​
  Map<Object, Widget> _views = <Object, Widget>{};
​
  void _updateViews() {
    final Map<Object, Widget> newViews = <Object, Widget>{};
    for (final FlutterView view in WidgetsBinding.instance.platformDispatcher.views) {
      final Widget viewWidget = _views[view.viewId] ?? _createViewWidget(view);
      newViews[view.viewId] = viewWidget;
    }
    setState(() {
      _views = newViews;
    });
  }
​
  Widget _createViewWidget(FlutterView view) {
    return View(
      view: view,
      child: Builder(
        builder: widget.viewBuilder,
      ),
    );
  }
​
  @override
  void dispose() {
    WidgetsBinding.instance.removeObserver(this);
    super.dispose();
  }
​
  @override
  Widget build(BuildContext context) {
    return ViewCollection(views: _views.values.toList(growable: false));
  }
}
```

For more information, check out the [`WidgetsBinding` mixin](https://api.flutter.dev/flutter/widgets/WidgetsBinding-mixin.html) in the API docs.

### Replace `runApp` by `runWidget` in Dart

[#](#replace-runapp-by-runwidget-in-dart)

Flutter's [`runApp` function](https://api.flutter.dev/flutter/widgets/runApp.html) assumes that there's at least one view available to render into (the `implicitView`), however in Flutter web's multi-view mode, the `implicitView` doesn't exist anymore, so `runApp` will start failing with `Unexpected null value` errors.

In multi-view mode, your `main.dart` must call the [`runWidget` function](https://api.flutter.dev/flutter/widgets/runWidget.html) instead. It doesn't require an `implicitView`, and will only render into the views that have been explicitly added into your app.

The following example uses the `MultiViewApp` described above to render copies of the `MyApp()` widget on every `FlutterView` available:

main.dart

dart

```
void main() {
  runWidget(
    MultiViewApp(
      viewBuilder: (BuildContext context) => const MyApp(),
    ),
  );
}
```

### Identifying views

[#](#identifying-views)

Each `FlutterView` has an identifier assigned by Flutter when attached. This `viewId` can be used to uniquely identify each view, retrieve its initial configuration, or decide what to render in it.

The `viewId` of the rendered `FlutterView` can be retrieved from its `BuildContext` like this:

dart

```
class SomeWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // Retrieve the `viewId` where this Widget is being built:
    final int viewId = View.of(context).viewId;
    // ...
```

Similarly, from the `viewBuilder` method of the `MultiViewApp`, the `viewId` can be retrieved like this:

dart

```
MultiViewApp(
  viewBuilder: (BuildContext context) {
    // Retrieve the `viewId` where this Widget is being built:
    final int viewId = View.of(context).viewId;
    // Decide what to render based on `viewId`...
  },
)
```

Read more about the [`View.of` constructor](https://api.flutter.dev/flutter/widgets/View/of.html).

### Initial view configuration

[#](#initial-view-configuration)

Flutter views can receive any initialization data from JS when starting up. The values are passed through the `initialData` property of the `addView` method, as shown:

js

```
// Adding a view with initial data...
let viewId = app.addView({
  hostElement: someElement,
  initialData: {
    greeting: 'Hello, world!',
    randomValue: Math.floor(Math.random() * 100),
  }
});
```

In Dart, the `initialData` is available as a `JSAny` object, accessible through the top-level `views` property in the `dart:ui_web` library. The data is accessed through the `viewId` of the current view, as shown:

dart

```
final initialData = ui_web.views.getInitialData(viewId) as YourJsInteropType;
```

To learn how to define the `YourJsInteropType` class to map the `initialData` object passed from JS so it's type-safe in your Dart program, check out: [JS Interoperability](https://dart.dev/interop/js-interop) on dart.dev.

### View constraints

[#](#view-constraints)

By default, an embedded Flutter web view considers the size of its `hostElement` as an immutable property, and tightly constrains its layout to the available space.

On the web, it's common for the intrinsic size of an element to affect the layout of the page (like `img` or `p` tags that can reflow content around them).

When adding a view to Flutter web, you might configure it with constraints that inform Flutter of how the view needs to be laid out:

js

```
// Adding a view with initial data...
let viewId = app.addView({
  hostElement: someElement,
  viewConstraints: {
    maxWidth: 320,
    minHeight: 0,
    maxHeight: Infinity,
  }
});
```

The view constraints passed from JS need to be compatible with the CSS styling of the `hostElement` where Flutter is being embedded. For example, Flutter won’t try to "fix" contradictory constants like passing `max-height: 100px` in CSS, but `maxHeight: Infinity` to Flutter.

To learn more, check out the [`ViewConstraints` class](https://api.flutter.dev/flutter/dart-ui/ViewConstraints-class.html), and [Understanding constraints](/ui/layout/constraints).

## Custom element (`hostElement`)

[#](#custom-element-hostelement)

You can embed a single-view Flutter web app into any HTML element of your web page.

To tell Flutter web which element to render into, pass an object with a `config` field to the `_flutter.loader.load` function that specifies a `HTMLElement` as the `hostElement`.

js

```
_flutter.loader.load({
  config: {
    hostElement: document.getElementById('flutter_host'),
  }
});
```

To learn more about other configuration options, check out [Customizing web app initialization](/platform-integration/web/initialization).

## Host element CSS requirements

[#](#host-element-css-requirements)

When embedding Flutter directly into a DOM element (using `hostElement` with single-view or `addView` in multi-view mode), Flutter sizes its rendering canvas based on the computed dimensions of the host container.

HTML container elements like `<div>` default to `height: auto` and `position: static`. Without explicit CSS styling, an empty host element has a computed height of `0px`. This causes Flutter to compute the view height as zero, which collapses the Flutter view and prevents content from rendering.

### Mandatory CSS properties

[#](#mandatory-css-properties)

To ensure Flutter renders correctly, apply the following CSS properties to your host element:

* **`width` and `height`**: Set explicit or relative dimensions (for example, `width: 100%; height: 500px;` or `height: 100%` within a sized parent). The element must evaluate to a non-zero width and height.
* **`position: relative`** (or `position: absolute` / `position: fixed`): Establishes a positioning context for the canvas and DOM overlay layers that Flutter creates inside the container.
* **`overflow: hidden`**: Prevents Flutter canvas and interaction elements from overflowing the container boundaries or causing unexpected scrollbars on the host page.

### Example CSS

[#](#example-css)

styles.css

css

```
#flutter_host {
  width: 100%;
  height: 600px;
  position: relative;
  overflow: hidden;
}
```

index.html

html

```
<div id="flutter_host"></div>
```

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-09-29. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/platform-integration/web/embedding-flutter-web.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/platform-integration/web/embedding-flutter-web\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/platform-integration/web/embedding-flutter-web.md "Report an issue with this page").

----
url: https://docs.flutter.dev/testing/integration-tests
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Check app functionality with an integration test](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

## Introduction

[#](#introduction)

This guide describes how to run integration tests with your Flutter app. With it, you'll learn how to do the following:

* Set up integration tests.
* Verify if an app displays specific text.
* Tap specific widgets.
* Run integration tests.

The guide references the `counter_app` project that comes with Flutter and the Flutter [`integration_test`](https://github.com/flutter/flutter/tree/main/packages/integration_test#integration_test) package. The `integration_test` package lets you:

* Use the `flutter drive` command to run tests on a physical device or emulator.
* Run on [Firebase Test Lab](https://firebase.google.com/docs/test-lab), to automate testing on a variety of devices.
* Use [flutter\_test](https://api.flutter.dev/flutter/flutter_test/flutter_test-library.html) APIs to write tests in a style similar to [widget tests](/testing/overview#widget-tests).

## Create a new app to test

[#](#create-a-new-app-to-test)

Integration testing requires an app to test. This example uses the built-in **Counter App** example that Flutter produces when you run the `flutter create` command. The counter app allows a user to tap on a button to increase a counter.

1. To create an instance of the built-in Flutter app, run the following command in your terminal:

   ```
   $ flutter create counter_app
   ```

2. Change into the `counter_app` directory.

3. Open `lib/main.dart` in your preferred IDE.

4. Add a `key` parameter to the `floatingActionButton()` widget with an instance of a `Key` class with a string value of `increment`.

   dart

   ```
    floatingActionButton: FloatingActionButton(
      key: const ValueKey('increment'),
      onPressed: _incrementCounter,
      tooltip: 'Increment',
      child: const Icon(Icons.add),
    ),
   ```

5. Save your `lib/main.dart` file.

After these changes, the `lib/main.dart` file should resemble the following code.

lib/main.dart

dart

```
import 'package:material_ui/material_ui.dart';
​
void main() => runApp(const MyApp());
​
class MyApp extends StatelessWidget {
  const MyApp({super.key});
​
  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      title: 'Counter App',
      home: MyHomePage(title: 'Counter App Home Page'),
    );
  }
}
​
class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});
​
  final String title;
​
  @override
  State<MyHomePage> createState() => _MyHomePageState();
}
​
class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;
​
  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }
​
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text(widget.title)),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            const Text('You have pushed the button this many times:'),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.headlineMedium,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        // Provide a Key to this button. This allows finding this
        // specific button inside the test suite, and tapping it.
        key: const Key('increment'),
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ),
    );
  }
}
```

## Add the `integration_test` dependency

[#](#add-the-integration-test-dependency)

You need to add the testing packages to your new app.

To add `integration_test` and `flutter_test` packages as `dev_dependencies` using `sdk: flutter`, run following command.

```
$ flutter pub add "dev:integration_test:{sdk: flutter}"
```

Output:

```
Building flutter tool...
Resolving dependencies...
Got dependencies.
Resolving dependencies...
+ file 7.0.0
+ flutter_driver 0.0.0 from sdk flutter
+ fuchsia_remote_debug_protocol 0.0.0 from sdk flutter
+ integration_test 0.0.0 from sdk flutter
...
  test_api 0.6.1 (0.7.1 available)
  vm_service 13.0.0 (14.2.1 available)
+ webdriver 3.0.3
Changed 8 dependencies!
7 packages have newer versions incompatible with dependency constraints.
Try `flutter pub outdated` for more information.
```

Updated `pubspec.yaml` file:

pubspec.yaml

yaml

```
# ...
dev_dependencies:
  # ... added dependencies
  flutter_test:
    sdk: flutter
  flutter_lints: ^6.0.0
  integration_test:
    sdk: flutter
# ...
```

## Create the integration test files

[#](#create-the-integration-test-files)

Integration tests reside in a separate directory inside your Flutter project.

1. Create a new directory named `integration_test`.
2. Add empty file named `app_test.dart` in that directory.

The resulting directory tree should resemble the following:

```
counter_app/
  lib/
    main.dart
  integration_test/
    app_test.dart
```

## Write the integration test

[#](#write-the-integration-test)

The integration test file consists of a Dart code file with dependencies on `integration_test`, `flutter_test`, and your app's Dart file.

1. Open your `integration_test/app_test.dart` file in your preferred IDE.

2. Copy the following code and paste it into your `integration_test/app_test.dart` file. The last import should point to the `main.dart` file of your `counter_app`. (This `import` points to the example app called `introduction`.)

   integration\_test/counter\_test.dart

   dart

   ```
   import 'package:flutter_test/flutter_test.dart';
   import 'package:how_to/main.dart';
   import 'package:integration_test/integration_test.dart';
   import 'package:material_ui/material_ui.dart';
   ​
   void main() {
     IntegrationTestWidgetsFlutterBinding.ensureInitialized();
   ​
     group('end-to-end test', () {
       testWidgets('tap on the floating action button, verify counter', (
         tester,
       ) async {
         // Load app widget.
         await tester.pumpWidget(const MyApp());
   ​
         // Verify the counter starts at 0.
         expect(find.text('0'), findsOneWidget);
   ​
         // Finds the floating action button to tap on.
         final fab = find.byKey(const ValueKey('increment'));
   ​
         // Emulate a tap on the floating action button.
         await tester.tap(fab);
   ​
         // Trigger a frame.
         await tester.pumpAndSettle();
   ​
         // Verify the counter increments by 1.
         expect(find.text('1'), findsOneWidget);
       });
     });
   }
   ```

This example goes through three steps:

1. Initialize `IntegrationTestWidgetsFlutterBinding`. This singleton service executes tests on a physical device.

2. Interact and test widgets using the `WidgetTester` class.

3. Test the important scenarios.

## Run integration tests

[#](#run-integration-tests)

The integration tests that run vary depending on the platform on which you test.

* To test a desktop platform, use the command line or a CI system.
* To test a mobile platform, use the command line or Firebase Test Lab.
* To test in a web browser, use the command line.

***

### Test on a desktop platform

[#](#test-on-a-desktop-platform)

Expand if you test Linux apps using a CI system

To test a Linux app, your CI system must invoke an X server first. In the GitHub Action, GitLab Runner, or similar configuration file, set the integration test to work *with* the `xvfb-run` tool.

Doing this invokes an X Window system into which Flutter can launch and test your Linux app.

As an example using GitHub Actions, your `jobs.setup.steps` should include a step resembling the following:

yaml

```
      - name: Run Integration Tests
        uses: username/xvfb-action@v1.1.2
        with:
          run: flutter test integration_test -d linux -r github
```

This starts the integration test within an X Window.

If you don't configure your integration in this way, Flutter returns an error.

```
Building Linux application...
Error waiting for a debug connection: The log reader stopped unexpectedly, or never started.
```

To test on a macOS, Windows, or Linux platform, complete the following tasks.

1. Run the following command from the root of the project.

   ```
   $ flutter test integration_test/app_test.dart
   ```

2. If offered a choice of platform to test, choose the desktop platform. Type `1` to choose the desktop platform.

Based on platform, the command result should resemble the following output.

* [Windows](#166-tab-panel)
* [macOS](#167-tab-panel)
* [Linux](#168-tab-panel)

```
PS C:\path\to\counter_app> flutter test .\integration_test\app_test.dart
Resolving dependencies...
Downloading packages...
  flutter_lints 3.0.2 (4.0.0 available)
  leak_tracker 10.0.4 (10.0.5 available)
  leak_tracker_flutter_testing 3.0.3 (3.0.5 available)
  lints 3.0.0 (4.0.0 available)
  material_color_utilities 0.8.0 (0.11.1 available)
  meta 1.12.0 (1.15.0 available)
  test_api 0.7.0 (0.7.1 available)
  vm_service 14.2.1 (14.2.2 available)
Got dependencies!
8 packages have newer versions incompatible with dependency constraints.
Try `flutter pub outdated` for more information.

Connected devices:

Windows (desktop) • windows • windows-x64    • Microsoft Windows [Version 10.0.22631.3593]
Chrome (web)      • chrome  • web-javascript • Google Chrome 124.0.6367.207
Edge (web)        • edge    • web-javascript • Microsoft Edge 124.0.2478.97

[1]: Windows (windows)
[2]: Chrome (chrome)
[3]: Edge (edge)

Please choose one (or "q" to quit): 1

00:00 +0: loading C:/path/to/counter_app/integration_test/app_test.dart               B
00:29 +0: loading C:/path/to/counter_app/counter_app/integration_test/app_test.dart   29.1s
√ Built build\windows\x64\runner\Debug\counter_app.exe
00:31 +1: All tests passed!
```

```
$ flutter test integration_test
Resolving dependencies...
Downloading packages...
  flutter_lints 3.0.2 (4.0.0 available)
> leak_tracker 10.0.4 (was 10.0.0) (10.0.5 available)
> leak_tracker_flutter_testing 3.0.3 (was 2.0.1) (3.0.5 available)
> leak_tracker_testing 3.0.1 (was 2.0.1)
  lints 3.0.0 (4.0.0 available)
  material_color_utilities 0.8.0 (0.11.1 available)
> meta 1.12.0 (was 1.11.0) (1.15.0 available)
> test_api 0.7.0 (was 0.6.1) (0.7.1 available)
> vm_service 14.2.1 (was 13.0.0) (14.2.2 available)
Changed 6 dependencies!
8 packages have newer versions incompatible with dependency constraints.
Try `flutter pub outdated` for more information.

Connected devices:

macOS (desktop)                 • macos                 • darwin-arm64   • macOS 14.4.1 23E224 darwin-arm64
Mac Designed for iPad (desktop) • mac-designed-for-ipad • darwin         • macOS 14.4.1 23E224 darwin-arm64
Chrome (web)                    • chrome                • web-javascript • Google Chrome 124.0.6367.208

No wireless devices were found.

[1]: macOS (macos)
[2]: Mac Designed for iPad (mac-designed-for-ipad)
[3]: Chrome (chrome)
Please choose one (or "q" to quit): 1

00:01 +0: loading /path/to/counter_app/integration_test/app_test.dart        R
00:02 +0: loading /path/to/counter_app/integration_test/app_test.dart    846ms
00:03 +0: loading /path/to/counter_app/integration_test/app_test.dart        B

Building macOS application...
✓ Built build/macos/Build/Products/Debug/counter_app.app
00:32 +1: All tests passed!
```

```
$ flutter test integration_test/app_test.dart

Connected devices:

Linux (desktop) • linux  • linux-x64      • Ubuntu 22.04.4 LTS 6.5.0-35-generic
Chrome (web)    • chrome • web-javascript • Google Chrome 104.0.5112.101

[1]: Linux (linux)
[2]: Chrome (chrome)

Please choose one (or "q" to quit): 1

00:00 +0: /path/to/counter_app/integration_test/app_test.dart     B
00:16 +0: /path/to/counter_app/integration_test/app_test.dart

✓ Built build/linux/x64/debug/bundle/counter_app
```

***

### Test on an Android device

[#](#test-on-an-android-device)

To test on a real Android device, complete the following tasks.

1. Connect the Android device.

2. Run the following command from the root of the project.

   ```
   $ flutter test integration_test/app_test.dart
   ```

   The result should resemble the following output.

   ```
   $ flutter test integration_test/app_test.dart
   00:04 +0: loading /path/to/counter_app/integration_test/app_test.dart
   00:15 +0: loading /path/to/counter_app/integration_test/app_test.dart
   00:18 +0: loading /path/to/counter_app/integration_test/app_test.dart   2,387ms
   Installing build/app/outputs/flutter-apk/app.apk...  612ms
   00:21 +1: All tests passed!
   ```

3. Verify that the test removed the Counter App when it finished. If not, subsequent tests fail. If needed, press on the app and choose **Remove App** from the context menu.

***

### Test on an iOS device

[#](#test-on-an-ios-device)

To test on a real iOS device, complete the following tasks.

1. Connect the iOS device.

2. Run the following command from the root of the project.

   ```
   $ flutter test integration_test/app_test.dart
   ```

   The result should resemble the following output.

   ```
   $ flutter test integration_test/app_test.dart
   00:04 +0: loading /path/to/counter_app/integration_test/app_test.dart
   00:15 +0: loading /path/to/counter_app/integration_test/app_test.dart
   00:18 +0: loading /path/to/counter_app/integration_test/app_test.dart   2,387ms
   Xcode build done.                                           13.5s
   00:21 +1: All tests passed!
   ```

3. Verify that the test removed the Counter App when it finished. If not, subsequent tests fail. If needed, press on the app and choose **Remove App** from the context menu.

***

### Test in a web browser

[#](#test-in-a-web-browser)

To test in a web browser, perform the following steps.

1. Install [ChromeDriver](https://googlechromelabs.github.io/chrome-for-testing/) into the directory of your choice.

   ```
   $ npx @puppeteer/browsers install chromedriver@stable
   ```

   To simplify the install, this command uses the [`@puppeteer/browsers`](https://www.npmjs.com/package/@puppeteer/browsers) Node library.

2. Add the path to ChromeDriver to your `$PATH` environment variable.

3. Verify the ChromeDriver install succeeded.

   ```
   $ chromedriver --version
   ChromeDriver 124.0.6367.60 (8771130bd84f76d855ae42fbe02752b03e352f17-refs/branch-heads/6367@{#798})
   ```

4. In your `counter_app` project directory, create a new directory named `test_driver`.

   ```
   $ mkdir test_driver
   ```

5. In this directory, create a new file named `integration_test.dart`.

6. Copy the following code and paste it into your `integration_test.dart` file.

   test\_driver/integration\_test.dart

   dart

   ```
   import 'package:integration_test/integration_test_driver.dart';
   ​
   Future<void> main() => integrationDriver();
   ```

7. Launch `chromedriver` as follows:

   ```
   $ chromedriver --port=4444
   ```

8. From the root of the project, run the following command:

   ```
   $ flutter drive \
     --driver=test_driver/integration_test.dart \
     --target=integration_test/app_test.dart \
     -d chrome
   ```

   The response should resemble the following output:

   ```
   Resolving dependencies...
     leak_tracker 10.0.0 (10.0.5 available)
     leak_tracker_flutter_testing 2.0.1 (3.0.5 available)
     leak_tracker_testing 2.0.1 (3.0.1 available)
     material_color_utilities 0.8.0 (0.11.1 available)
     meta 1.11.0 (1.14.0 available)
     test_api 0.6.1 (0.7.1 available)
     vm_service 13.0.0 (14.2.1 available)
   Got dependencies!
   7 packages have newer versions incompatible with dependency constraints.
   Try `flutter pub outdated` for more information.
   Launching integration_test/app_test.dart on Chrome in debug mode...
   Waiting for connection from debug service on Chrome...             10.9s
   This app is linked to the debug service: ws://127.0.0.1:51523/3lofIjIdmbs=/ws
   Debug service listening on ws://127.0.0.1:51523/3lofIjIdmbs=/ws
   00:00 +0: end-to-end test tap on the floating action button, verify counter
   00:01 +1: (tearDownAll)
   00:01 +2: All tests passed!
   All tests passed.
   Application finished.
   ```

   To run this as a headless test, run `flutter drive` with `-d web-server` option:

   ```
   $ flutter drive \
     --driver=test_driver/integration_test.dart \
     --target=integration_test/app_test.dart \
     -d web-server
   ```

To learn more, see the [Running Flutter driver tests with web](https://github.com/flutter/flutter/blob/main/docs/contributing/testing/Running-Flutter-Driver-tests-with-Web.md) wiki page.

***

### Test in Firebase Test Lab (Android)

[#](#test-in-firebase-test-lab-android)

You can use Firebase Test Lab to test Android targets.

#### Android setup

[#](#android-setup)

Follow the instructions in the [Android Device Testing](https://github.com/flutter/flutter/tree/main/packages/integration_test#android-device-testing) section of the README.

#### Test Lab project setup

[#](#test-lab-project-setup)

1. Launch your [Firebase Console](https://console.firebase.google.com/).

2. Create a new Firebase project if necessary.

3. Navigate to **Quality > Test Lab**.

#### Upload an Android APK

[#](#upload-an-android-apk)

Complete the following steps to upload an Android APK.

1. Create an APK using Gradle.

   ```
   // Go to the Android directory which contains the gradlew script
   $ pushd android

   // Build a debug APK for Flutter with gradlew
   // Note that a standard --release build will not include package:integration_test
   $ flutter build apk --debug

   // Build an Android test APK
   $ ./gradlew app:assembleAndroidTest

   // Build a debug APK by passing in an integration test
   $ ./gradlew app:assembleDebug -Ptarget=integration_test/<name>_test.dart
   ```

   * `<name>_test.dart`: The file created in the **Project Setup** section.

2. If needed, pass parameters into the integration test as a comma-separated list. Encode all parameters as `base64`.

   ```
   $ ./gradlew project:task -Pdart-defines="{base64 (key=value)}[, ...]"
   ```

   * `(key=value)}[, ...]`: Replace this with a comma-separated list of key value pairs.

3. Return to your previous directory.

   ```
   $ popd
   ```

For additional instructions, see the [Firebase Test Lab section of the README](https://github.com/flutter/flutter/tree/main/packages/integration_test#firebase-test-lab).

#### Start Robo test

[#](#start-robo-test)

To use Robo test to run integration tests, complete the following steps.

1. Drag the debug APK from `<flutter_project_directory>/build/app/outputs/apk/debug` into the **Android Robo Test** target on the web page. For example:

2. Click **Run a test**.

3. Select the **Instrumentation** test type.

4. Add the App APK to the **App APK or AAB** box.

   `<flutter_project_directory>/build/app/outputs/apk/debug/<file>.apk`

5. Add the Test APK to the **Test APK** box.

   `<flutter_project_directory>/build/app/outputs/apk/androidTest/debug/<file>.apk`

6. If a failure occurs, click the red icon to view the output:

***

### Test in Firebase Test Lab (iOS)

[#](#test-in-firebase-test-lab-ios)

You can use Firebase Test Lab to test iOS targets.

#### iOS setup

[#](#ios-setup)

Follow the [iOS Device Testing instructions](https://github.com/flutter/flutter/tree/main/packages/integration_test#ios-device-testing).

#### Test Lab project setup

[#](#test-lab-project-setup-1)

1. Launch your [Firebase Console](https://console.firebase.google.com/).

2. Create a new Firebase project if necessary.

3. Navigate to **Quality > Test Lab**.

#### Upload Xcode tests through the Firebase Console

[#](#upload-xcode-tests-through-the-firebase-console)

To learn how to upload tests from a ZIP file, using the Firebase Test Lab Console, consult the [Firebase Test Lab iOS instructions](https://firebase.google.com/docs/test-lab/ios/firebase-console).

#### Upload Xcode tests to Firebase Console with the command line

[#](#upload-xcode-tests-to-firebase-console-with-the-command-line)

To learn how to upload tests from a ZIP file from the command line to the Firebase Test Lab Console, consult the [iOS Device Testing instructions](https://github.com/flutter/flutter/tree/main/packages/integration_test#ios-device-testing).

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-09-29. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/testing/integration-tests/index.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/testing/integration-tests\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/testing/integration-tests/index.md "Report an issue with this page").

----
url: https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.44.0
----

The 2.44.0 release of the Dart and Flutter DevTools includes the following changes among other general improvements. To learn more about DevTools, check out the [DevTools overview](/tools/devtools/overview).

## General updates

[#](#general-updates)

* Fixed various memory leaks and lifecycle issues. - [#8901](https://github.com/flutter/devtools/pull/8901), [#8902](https://github.com/flutter/devtools/pull/8902), [#8907](https://github.com/flutter/devtools/pull/8907), [#8917](https://github.com/flutter/devtools/pull/8917), [#8932](https://github.com/flutter/devtools/pull/8932), [#8933](https://github.com/flutter/devtools/pull/8933), [#8934](https://github.com/flutter/devtools/pull/8934), [#8935](https://github.com/flutter/devtools/pull/8935), [#8937](https://github.com/flutter/devtools/pull/8937), [#8953](https://github.com/flutter/devtools/pull/8953), [#8969](https://github.com/flutter/devtools/pull/8969), [#8970](https://github.com/flutter/devtools/pull/8970), [#8975](https://github.com/flutter/devtools/pull/8975)

## CPU profiler updates

[#](#cpu-profiler-updates)

* Improved the load time and memory usage of CPU profiles.

  * [#8892](https://github.com/flutter/devtools/pull/8892)
  * [#8878](https://github.com/flutter/devtools/pull/8878)
  * [#8839](https://github.com/flutter/devtools/pull/8839)

* Fixed incorrect duration calculations when there is time during which no samples were taken - [#8941](https://github.com/flutter/devtools/pull/8941).

## Memory updates

[#](#memory-updates)

* Changed the memory heap snapshot tool so that references are included in snapshots by default. - [#8899](https://github.com/flutter/devtools/pull/8899)

## Debugger updates

[#](#debugger-updates)

* Added a tooltip to describe the exception mode drop-down. - [#8849](https://github.com/flutter/devtools/pull/8849)
* Updated syntax highlighting with support for digit separators and improved comment and string interpolation handling. - [#8861](https://github.com/flutter/devtools/pull/8861)
* Updated `string_scanner` dependency to avoid some syntax highlighting issues when source contains `\r\n` in certain positions on Windows. - [#8904](https://github.com/flutter/devtools/pull/8904)
* Added soft line wrapping in the debugger console. [#8855](https://github.com/flutter/devtools/pull/8855).

## Network profiler updates

[#](#network-profiler-updates)

* Added offline support for the network screen (thanks to @hrajwade96!) - [#8332](https://github.com/flutter/devtools/pull/8332)

* Changed the context menu style to be consistent with other screens [#8859](https://github.com/flutter/devtools/pull/8859).

## Full commit history

[#](#full-commit-history)

To find a complete list of changes in this release, check out the [DevTools git log](https://github.com/flutter/devtools/tree/v2.44.0).

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/release-notes/release-notes-2.44.0.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.44.0\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/release-notes/release-notes-2.44.0.md "Report an issue with this page").

----
url: https://docs.flutter.dev/cookbook/navigation/returning-data.md
----

\# Return data from a screen > How to return data from a new screen.

<!--?code-excerpt path-base="cookbook/navigation/returning_data/"?-->

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 that it can act on that information. You can do this with the \[\`Navigator.pop()\`]\[] method using the following steps: 1. Define the home screen 2. Add a button that launches the selection screen 3. Show the selection screen with two buttons 4. When a button is tapped, close the selection screen 5. Show a snackbar on the home screen with the selection ## 1. Define the home screen The home screen displays a button. When tapped, it launches the selection screen.

<!--?code-excerpt "lib/main_step2.dart (HomeScreen)"?-->

\`\`\`dart class HomeScreen extends StatelessWidget { const HomeScreen({super.key}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('Returning Data Demo')), // Create the SelectionButton widget in the next step. body: const Center(child: SelectionButton()), ); } } \`\`\` ## 2. Add a button that launches the selection screen Now, create the SelectionButton, which does the following: \* Launches the SelectionScreen when it's tapped. \* Waits for the SelectionScreen to return a result.

<!--?code-excerpt "lib/main_step2.dart (SelectionButton)"?-->

\`\`\`dart class SelectionButton extends StatefulWidget { const SelectionButton({super.key}); @override State createState() => \_SelectionButtonState(); } class \_SelectionButtonState extends State { @override Widget build(BuildContext context) { return ElevatedButton( onPressed: () { \_navigateAndDisplaySelection(context); }, child: const Text('Pick an option, any option!'), ); } Future \_navigateAndDisplaySelection(BuildContext context) async { // Navigator.push returns a Future that completes after calling // Navigator.pop on the Selection Screen. final result = await Navigator.push( context, // Create the SelectionScreen in the next step. MaterialPageRoute(builder: (context) => const SelectionScreen()), ); } } \`\`\` ## 3. Show the selection screen with two buttons Now, build a selection screen that contains two buttons. When a user taps a button, that app closes the selection screen and lets the home screen know which button was tapped. This step defines the UI. The next step adds code to return data.

<!--?code-excerpt "lib/main_step2.dart (SelectionScreen)"?-->

\`\`\`dart class SelectionScreen extends StatelessWidget { const SelectionScreen({super.key}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('Pick an option')), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: \[ Padding( padding: const EdgeInsets.all(8), child: ElevatedButton( onPressed: () { // Pop here with "Yep"... }, child: const Text('Yep!'), ), ), Padding( padding: const EdgeInsets.all(8), child: ElevatedButton( onPressed: () { // Pop here with "Nope"... }, child: const Text('Nope.'), ), ), ], ), ), ); } } \`\`\` ## 4. When a button is tapped, close the selection screen Now, update the \`onPressed()\` callback for both of the buttons. To return data to the first screen, use the \[\`Navigator.pop()\`]\[] method, which accepts an optional second argument called \`result\`. Any result is returned to the \`Future\` in the SelectionButton. ### Yep button

<!--?code-excerpt "lib/main.dart (Yep)" replace="/^child: //g;/^\),$/)/g"?-->

\`\`\`dart ElevatedButton( onPressed: () { // Close the screen and return "Yep!" as the result. Navigator.pop(context, 'Yep!'); }, child: const Text('Yep!'), ) \`\`\` ### Nope button

<!--?code-excerpt "lib/main.dart (Nope)" replace="/^child: //g;/^\),$/)/g"?-->

\`\`\`dart ElevatedButton( onPressed: () { // Close the screen and return "Nope." as the result. Navigator.pop(context, 'Nope.'); }, child: const Text('Nope.'), ) \`\`\` ## 5. Show a snackbar on the home screen with the selection 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, show a snackbar displaying the result by using the \`\_navigateAndDisplaySelection()\` method in \`SelectionButton\`:

<!--?code-excerpt "lib/main.dart (navigateAndDisplay)"?-->

\`\`\`dart // A method that launches the SelectionScreen and awaits the result from // Navigator.pop. Future \_navigateAndDisplaySelection(BuildContext context) async { // Navigator.push returns a Future that completes after calling // Navigator.pop on the Selection Screen. final result = await Navigator.push( context, MaterialPageRoute(builder: (context) => const SelectionScreen()), ); // When a BuildContext is used from a StatefulWidget, the mounted property // must be checked after an asynchronous gap. if (!context.mounted) return; // After the Selection Screen returns a result, hide any previous snackbars // and show the new result. ScaffoldMessenger.of(context) ..removeCurrentSnackBar() ..showSnackBar(SnackBar(content: Text('$result'))); } \`\`\` ## Interactive example

<!--?code-excerpt "lib/main.dart"?-->

\`\`\`dartpad title="Flutter Return from Data hands-on example in DartPad" run="true" import 'package:flutter/material.dart'; void main() { runApp(const MaterialApp(title: 'Returning Data', home: HomeScreen())); } class HomeScreen extends StatelessWidget { const HomeScreen({super.key}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('Returning Data Demo')), body: const Center(child: SelectionButton()), ); } } class SelectionButton extends StatefulWidget { const SelectionButton({super.key}); @override State createState() => \_SelectionButtonState(); } class \_SelectionButtonState extends State { @override Widget build(BuildContext context) { return ElevatedButton( onPressed: () { \_navigateAndDisplaySelection(context); }, child: const Text('Pick an option, any option!'), ); } // A method that launches the SelectionScreen and awaits the result from // Navigator.pop. Future \_navigateAndDisplaySelection(BuildContext context) async { // Navigator.push returns a Future that completes after calling // Navigator.pop on the Selection Screen. final result = await Navigator.push( context, MaterialPageRoute(builder: (context) => const SelectionScreen()), ); // When a BuildContext is used from a StatefulWidget, the mounted property // must be checked after an asynchronous gap. if (!context.mounted) return; // After the Selection Screen returns a result, hide any previous snackbars // and show the new result. ScaffoldMessenger.of(context) ..removeCurrentSnackBar() ..showSnackBar(SnackBar(content: Text('$result'))); } } class SelectionScreen extends StatelessWidget { const SelectionScreen({super.key}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('Pick an option')), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: \[ Padding( padding: const EdgeInsets.all(8), child: ElevatedButton( onPressed: () { // Close the screen and return "Yep!" as the result. Navigator.pop(context, 'Yep!'); }, child: const Text('Yep!'), ), ), Padding( padding: const EdgeInsets.all(8), child: ElevatedButton( onPressed: () { // Close the screen and return "Nope." as the result. Navigator.pop(context, 'Nope.'); }, child: const Text('Nope.'), ), ), ], ), ), ); } } \`\`\` \[\`Navigator.pop()\`]: https\://api.flutter.dev/flutter/widgets/Navigator/pop.html

----
url: https://docs.flutter.dev/ui/widgets/async
----

Widgets supporting async patterns in your Flutter apps.

[Future​Builder](https://api.flutter.dev/flutter/widgets/FutureBuilder-class.html)

[Widget that builds itself based on the latest snapshot of interaction with a Future.](https://api.flutter.dev/flutter/widgets/FutureBuilder-class.html)

[Stream​Builder](https://api.flutter.dev/flutter/widgets/StreamBuilder-class.html)

[Widget that builds itself based on the latest snapshot of interaction with a Stream.](https://api.flutter.dev/flutter/widgets/StreamBuilder-class.html)

Find more widgets in the [widget catalog](/ui/widgets).

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-09-29. [Report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/ui/widgets/async "Report an issue with this page").

----
url: https://docs.flutter.dev/cookbook/testing/unit
----

[An introduction to unit testing](/cookbook/testing/unit/introduction)

[How to write unit tests.](/cookbook/testing/unit/introduction)

[Mock dependencies using Mockito](/cookbook/testing/unit/mocking)

[Use the Mockito package to mimic the behavior of services for testing.](/cookbook/testing/unit/mocking)

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/testing/unit/index.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/cookbook/testing/unit\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/testing/unit/index.md "Report an issue with this page").

----
url: https://docs.flutter.dev/deployment/ios.md
----

\# Build and release an iOS app > How to release a Flutter app to the App Store. This guide provides a step-by-step walkthrough of releasing a Flutter app to the \[App Store]\[appstore] and \[TestFlight]\[]. ## Preliminaries Xcode is required to build and release your app. You must use a device running macOS to follow this guide. Before beginning the process of releasing your app, ensure that it meets Apple's \[App Review Guidelines]\[appreview]. To publish your app to the App Store, you must first enroll in the \[Apple Developer Program]\[devprogram]. You can read more about the various membership options in Apple's \[Choosing a Membership]\[devprogram\_membership] guide. ## Video overview For those who prefer video over text, the following video covers the same material as this guide. ## Register your app on App Store Connect Manage your app's life cycle on \[App Store Connect]\[appstoreconnect] (formerly iTunes Connect). You define your app name and description, add screenshots, set pricing, and manage releases to the App Store and TestFlight. Registering your app involves two steps: registering a unique Bundle ID, and creating an application record on App Store Connect. For a detailed overview of App Store Connect, see the \[App Store Connect]\[appstoreconnect\_guide] guide. ### Register a Bundle ID Every iOS application is associated with a Bundle ID, a unique identifier registered with Apple. To register a Bundle ID for your app, follow these steps: 1. Open the \[App IDs]\[devportal\_appids] page of your developer account. 1. Click \*\*+\*\* to create a new Bundle ID. 1. Enter an app name, select \*\*Explicit App ID\*\*, and enter an ID. 1. Select the services your app uses, then click \*\*Continue\*\*. 1. On the next page, confirm the details and click \*\*Register\*\* to register your Bundle ID. ### Create an application record on App Store Connect Register your app on App Store Connect: 1. Open \[App Store Connect]\[appstoreconnect\_login] in your browser. 1. On the App Store Connect landing page, click \*\*Apps\*\*. 1. Click \*\*+\*\* in the top-left corner, then select \*\*New App\*\*. 1. Fill in your app details in the form that appears. In the Platforms section, ensure that iOS is checked. Since Flutter does not currently support tvOS, leave that checkbox unchecked. Click \*\*Create\*\*. 1. Navigate to the application details for your app and select \*\*App Information\*\* from the sidebar. 1. In the General Information section, select the Bundle ID you registered in the preceding step. For a detailed overview, see \[Add an app to your account]\[appstoreconnect\_guide\_register]. ## Review Xcode project settings This step covers reviewing the most important settings in the Xcode workspace. For detailed procedures and descriptions, see \[Prepare for app distribution]\[distributionguide\_config]. Navigate to your target's settings in Xcode: 1. Open the default Xcode workspace in your project by running \`open ios/Runner.xcworkspace\` in a terminal window from your Flutter project directory. 1. To view your app's settings, select the \*\*Runner\*\* target in the Xcode navigator. Verify the most important settings. In the \*\*Identity\*\* section of the \*\*General\*\* tab: \`Display Name\` : The display name of your app. \`Bundle Identifier\` : The App ID you registered on App Store Connect. In the \*\*Signing & Capabilities\*\* tab: \`Automatically manage signing\` : Whether Xcode should automatically manage app signing and provisioning. This is set \`true\` by default, which should be sufficient for most apps. For more complex scenarios, see the \[Code Signing Guide]\[codesigning\_guide]. \`Team\` : Select the team associated with your registered Apple Developer account. If required, select \*\*Add Account...\*\*, then update this setting. In the \*\*Deployment\*\* section of the \*\*Build Settings\*\* tab: \`iOS Deployment Target\` : The minimum iOS version that your app supports. Flutter supports iOS 13 and later. If your app or plugins include Objective-C or Swift code that makes use of APIs newer than iOS 13, update this setting to the highest required version. The \*\*General\*\* tab of your project settings should resemble the following: !\[Xcode Project Settings]\(/assets/images/docs/releaseguide/xcode\_settings.png){:width="100%"} For a detailed overview of app signing, see \[Create, export, and delete signing certificates]\[appsigning]. ## Add an app icon When a new Flutter app is created, a placeholder icon set is created. This step covers replacing these placeholder icons with your app's icons: 1. Review the \[iOS App Icon]\[app-icon] guidelines and, in particular, the advice on \[creating light, dark, and tinted]\[icon-modes] icons for your app. 1. In the Xcode project navigator, select \`Assets.xcassets\` in the \`Runner\` folder. Update the placeholder icons with your own app icons. 1. Verify the icon has been replaced by running your app using \`flutter run\`. \[app-icon]: https\://developer.apple.com/design/human-interface-guidelines/app-icons/ \[icon-modes]: https\://developer.apple.com/design/human-interface-guidelines/app-icons#iOS-iPadOS ## Add a launch image Similar to the app icon, you can also replace the placeholder launch image: 1. In the Xcode project navigator, select \`Assets.xcassets\` in the \`Runner\` folder. Update the placeholder launch image with your own launch image. 1. Verify the new launch image by hot restarting your app. (Don't use \`hot reload\`.) ## Create a build archive and upload to App Store Connect During development, you've been building, debugging, and testing with \_debug\_ builds. When you're ready to ship your app to users on the App Store or TestFlight, you need to prepare a \_release\_ build. ### Update the app's build and version numbers The default version number of the app is \`1.0.0\`. To update it, navigate to the \`pubspec.yaml\` file and update the following line: \`\`\`yaml version: 1.0.0+1 \`\`\` The version number is three numbers separated by dots, such as \`1.0.0\` in the example above, followed by an optional build number such as \`1\` in the example above, separated by a \`+\`. Both the version and the build number can be overridden in \`flutter build ipa\` by specifying \`--build-name\` and \`--build-number\`, respectively. In iOS, \`build-name\` uses \`CFBundleShortVersionString\` while \`build-number\` uses \`CFBundleVersion\`. Read more about iOS versioning at \[Core Foundation Keys]\[] on the Apple Developer's site. You can also override the \`pubspec.yaml\` build name and number in Xcode: 1. Open \`Runner.xcworkspace\` in your app's \`ios\` folder. 1. Select \*\*Runner\*\* in the Xcode project navigator, then select the \*\*Runner\*\* target in the settings view sidebar. 1. In the Identity section, update the \*\*Version\*\* to the user-facing version number you wish to publish. 1. In the Identity section, update the \*\*Build\*\* identifier to a unique build number used to track this build on App Store Connect. Each upload requires a unique build number. ### Create an app bundle Run \`flutter build ipa\` to produce an Xcode build archive (\`.xcarchive\` file) in your project's \`build/ios/archive/\` directory and an App Store app bundle (\`.ipa\` file) in \`build/ios/ipa\`. Consider adding the \`--obfuscate\` and \`--split-debug-info\` flags to \[obfuscate your Dart code]\[] to make it more difficult to reverse engineer. If you are not distributing to the App Store, you can optionally choose a different \[export method]\[app\_bundle\_export\_method] by adding the option \`--export-method ad-hoc\`, \`--export-method development\` or \`--export-method enterprise\`. :::note On versions of Flutter where \`flutter build ipa --export-method\` is unavailable, open \`build/ios/archive/MyApp.xcarchive\` and follow the instructions below to validate and distribute the app from Xcode. ::: ### Upload the app bundle to App Store Connect Once the app bundle is created, upload it to \[App Store Connect]\[appstoreconnect\_login] by either:

1. Install and open the \[Apple Transport macOS app]\[apple\_transport\_app]. Drag and drop the \`build/ios/ipa/\*.ipa\` app bundle into the app.
2. Or upload the app bundle from the command line by running: \`\`\`bash xcrun altool --upload-app --type ios -f build/ios/ipa/\*.ipa --apiKey your\_api\_key --apiIssuer your\_issuer\_id \`\`\` Run \`man altool\` for details about how to authenticate with the App Store Connect API key.
3. Or open \`build/ios/archive/MyApp.xcarchive\` in Xcode. Click the \*\*Validate App\*\* button. If any issues are reported, address them and produce another build. You can reuse the same build ID until you upload an archive. After the archive has been successfully validated, click \*\*Distribute App\*\*. :::note When you export your app at the end of \*\*Distribute App\*\*, Xcode will create a directory containing an IPA of your app and an \`ExportOptions.plist\` file. You can create new IPAs with the same options without launching Xcode by running \`flutter build ipa --export-options-plist=path/to/ExportOptions.plist\`. See \`xcodebuild -h\` for details about the keys in this property list. :::

You can follow the status of your build in the Activities tab of your app's details page on \[App Store Connect]\[appstoreconnect\_login]. You should receive an email within 30 minutes notifying you that your build has been validated and is available to release to testers on TestFlight. At this point you can choose whether to release on TestFlight, or go ahead and release your app to the App Store. For more details, see \[Upload an app to App Store Connect]\[distributionguide\_upload]. ## Create a build archive with Codemagic CLI tools This step covers creating a build archive and uploading your build to App Store Connect using Flutter build commands and \[Codemagic CLI Tools]\[codemagic\_cli\_tools] executed in a terminal in the Flutter project directory. This allows you to create a build archive with full control of distribution certificates in a temporary keychain isolated from your login keychain.

1. Install the Codemagic CLI tools: \`\`\`bash pip3 install codemagic-cli-tools \`\`\`
2. You'll need to generate an \[App Store Connect API Key]\[appstoreconnect\_api\_key] with App Manager access to automate operations with App Store Connect. To make subsequent commands more concise, set the following environment variables from the new key: issuer id, key id, and API key file. \`\`\`bash export APP\_STORE\_CONNECT\_ISSUER\_ID=aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee export APP\_STORE\_CONNECT\_KEY\_IDENTIFIER=ABC1234567 export APP\_STORE\_CONNECT\_PRIVATE\_KEY=\`cat /path/to/api/key/AuthKey\_XXXYYYZZZ.p8\` \`\`\`
3. You need to export or create an iOS Distribution certificate to code sign and package a build archive. If you have existing \[certificates]\[devportal\_certificates], you can export the private keys by executing the following command for each certificate: \`\`\`bash openssl pkcs12 -in .p12 -nodes -nocerts | openssl rsa -out cert\_key \`\`\` Or you can create a new private key by executing the following command: \`\`\`bash ssh-keygen -t rsa -b 2048 -m PEM -f cert\_key -q -N "" \`\`\` Later, you can have CLI tools automatically create a new iOS Distribution from the private key.
4. Set up a new temporary keychain to be used for code signing: \`\`\`bash keychain initialize \`\`\` :::note Restore Login Keychain! After running \`keychain initialize\` you \*\*must\*\* run the following:\
   \`keychain use-login\` This sets your login keychain as the default to avoid potential authentication issues with apps on your machine. :::
5. Fetch the code signing files from App Store Connect: \`\`\`bash app-store-connect fetch-signing-files $(xcode-project detect-bundle-id) \ --platform IOS \ --type IOS\_APP\_STORE \ --certificate-key=@file:/path/to/cert\_key \ --create \`\`\` Where \`cert\_key\` is either your exported iOS Distribution certificate private key or a new private key which automatically generates a new certificate. The certificate will be created from the private key if it doesn't exist in App Store Connect.
6. Now add the fetched certificates to your keychain: \`\`\`bash keychain add-certificates \`\`\`
7. Update the Xcode project settings to use fetched code signing profiles: \`\`\`bash xcode-project use-profiles \`\`\`
8. Install Flutter dependencies: \`\`\`bash flutter packages pub get \`\`\`
9. Install CocoaPods dependencies: \`\`\`bash find . -name "Podfile" -execdir pod install \\; \`\`\`
10. Build the Flutter the iOS project: \`\`\`bash flutter build ipa --release \ --export-options-plist=$HOME/export\_options.plist \`\`\` Note that \`export\_options.plist\` is the output of the \`xcode-project use-profiles\` command.
11. Publish the app to App Store Connect: \`\`\`bash app-store-connect publish \ --path $(find $(pwd) -name "\*.ipa") \`\`\`
12. As mentioned earlier, don't forget to set your login keychain as the default to avoid authentication issues with apps on your machine: \`\`\`bash keychain use-login \`\`\`

You should receive an email within 30 minutes notifying you that your build has been validated and is available to release to testers on TestFlight. At this point you can choose whether to release on TestFlight, or go ahead and release your app to the App Store. ## Release your app on TestFlight \[TestFlight]\[] allows developers to push their apps to internal and external testers. This optional step covers releasing your build on TestFlight. 1. Navigate to the TestFlight tab of your app's application details page on \[App Store Connect]\[appstoreconnect\_login]. 1. Select \*\*Internal Testing\*\* in the sidebar. 1. Select the build to publish to testers, then click \*\*Save\*\*. 1. Add the email addresses of any internal testers. You can add additional internal users in the \*\*Users and Roles\*\* page of App Store Connect, available from the dropdown menu at the top of the page. For more details, see \[Distribute an app using TestFlight]\[distributionguide\_testflight]. ## Release your app to the App Store When you're ready to release your app to the world, follow these steps to submit your app for review and release to the App Store: 1. Select \*\*Pricing and Availability\*\* from the sidebar of your app's application details page on \[App Store Connect]\[appstoreconnect\_login] and complete the required information. 1. Select the status from the sidebar. If this is the first release of this app, its status is \*\*1.0 Prepare for Submission\*\*. Complete all required fields. 1. Click \*\*Submit for Review\*\*. Apple notifies you when their app review process is complete. Your app is released according to the instructions you specified in the \*\*Version Release\*\* section. For more details, see \[Distribute an app through the App Store]\[distributionguide\_submit]. ## Troubleshooting The \[Distribute your app]\[distributionguide] guide provides a detailed overview of the process of releasing an app to the App Store. \[appreview]: https\://developer.apple.com/app-store/review/ \[appsigning]: https\://help.apple.com/xcode/mac/current/#/dev154b28f09 \[appstore]: https\://developer.apple.com/app-store/submissions/ \[appstoreconnect]: https\://developer.apple.com/support/app-store-connect/ \[appstoreconnect\_api\_key]: https\://appstoreconnect.apple.com/access/api \[appstoreconnect\_guide]: https\://developer.apple.com/support/app-store-connect/ \[appstoreconnect\_guide\_register]: https\://help.apple.com/app-store-connect/#/dev2cd126805 \[appstoreconnect\_login]: https\://appstoreconnect.apple.com/ \[codemagic\_cli\_tools]: https\://github.com/codemagic-ci-cd/cli-tools \[codesigning\_guide]: https\://developer.apple.com/library/content/documentation/Security/Conceptual/CodeSigningGuide/Introduction/Introduction.html \[Core Foundation Keys]: https\://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html \[devportal\_appids]: https\://developer.apple.com/account/ios/identifier/bundle \[devportal\_certificates]: https\://developer.apple.com/account/resources/certificates \[devprogram]: https\://developer.apple.com/programs/ \[devprogram\_membership]: https\://developer.apple.com/support/compare-memberships/ \[distributionguide]: https\://help.apple.com/xcode/mac/current/#/devac02c5ab8 \[distributionguide\_config]: https\://help.apple.com/xcode/mac/current/#/dev91fe7130a \[distributionguide\_submit]: https\://help.apple.com/xcode/mac/current/#/dev067853c94 \[distributionguide\_testflight]: https\://help.apple.com/xcode/mac/current/#/dev2539d985f \[distributionguide\_upload]: https\://help.apple.com/xcode/mac/current/#/dev442d7f2ca \[obfuscate your Dart code]: /deployment/obfuscate \[TestFlight]: https\://developer.apple.com/testflight/ \[app\_bundle\_export\_method]: https\://help.apple.com/xcode/mac/current/#/dev31de635e5 \[apple\_transport\_app]: https\://apps.apple.com/us/app/transporter/id1450874784

----
url: https://docs.flutter.dev/platform-integration/web/web-content-in-flutter.md
----

\# Embedding web content into a Flutter web app > Learn how to embed web content, understand DOM slot layering, and handle pointer events in Flutter web apps. In some cases, Flutter web applications need to embed web content not rendered by Flutter. For example, embedding a \`google\_maps\_flutter\` view (which uses the Google Maps JavaScript SDK) or a \`video\_player\` (which uses a standard \`video\` element). Flutter web can render arbitrary web content within the boundaries of a \`Widget\`, and the primitives used to implement the example packages mentioned previously are available to all Flutter web applications. ## HtmlElementView The \`HtmlElementView\` Flutter widget reserves a space in the layout to be filled with any HTML element. It has two constructors: \* \`HtmlElementView\.fromTagName\` \* \`HtmlElementView\` and \`registerViewFactory\` ### \`HtmlElementView\.fromTagName\` The \[\`HtmlElementView\.fromTagName\` constructor]\[] creates an HTML element from its \`tagName\`, and provides an \`onElementCreated\` method to configure that element before it's injected into the DOM: \`\`\`dart // Create a \`video\` tag, and set its \`src\` and some \`style\` properties... HtmlElementView\.fromTagName( tagName: 'video', onElementCreated: (Object video) { video as web.HTMLVideoElement; video.src = 'https\://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4'; video.style.width = '100%'; video.style.height = '100%'; // other customizations to the element... }, ); \`\`\` To learn more about how to interact with DOM APIs, check out the \[\`HTMLVideoElement\` class] in \[\`package:web\`]\[]. To learn more about the video \`Object\` that is cast to \`web.HTMLVideoElement\`, check out Dart's \[JS Interoperability]\[] documentation. ### \`HtmlElementView\` and \`registerViewFactory\` If you need more control over generating the HTML code you inject, you can use the primitives that Flutter uses to implement the \`fromTagName\` constructor. In this scenario, register your own HTML element factory for each type of HTML content that you add to your app. The resulting code has two steps per platform view type: 1. Register the HTML element factory using \`platformViewRegistry.registerViewFactory\` provided by \`dart:ui\_web\`. 1. Place the widget with the desired \`viewType\` using \`HtmlElementView(viewType: 'viewType')\` in your app's widget tree. For more details about this approach, check out the \[\`HtmlElementView\` widget]\[] docs. ## Fixing hit testing issues Because of how Flutter web performs hit testing, underlying platform views might sometimes swallow pointer events before they can reach Flutter. To prevent losing pointer events intended for Flutter widgets, use the \[\`package:pointer\_interceptor\`]\[] package. The \[\`PointerInterceptor\`]\[] widget creates an empty, transparent platform view and places it directly behind its child widget in paint order. This transparent element catches browser pointer events before they reach the underlying \`HtmlElementView\`, allowing Flutter's gesture framework to handle the interaction as expected. ### Add the dependency Add \`pointer\_interceptor\` to your \`pubspec.yaml\` file: \`\`\`yaml dependencies: pointer\_interceptor: ^0.10.1+2 \`\`\` ### Wrap interactive widgets To make an individual interactive widget clickable over an \`HtmlElementView\` (such as a \[\`FloatingActionButton\`]\[] or \[\`ElevatedButton\`]\[]), wrap the widget with \`PointerInterceptor\`: \`\`\`dart PointerInterceptor( child: FloatingActionButton( onPressed: () { // Handle button press }, child: const Icon(Icons.add), ), ) \`\`\` ### Wrap layout and overlay containers When you use overlay components that cover a platform view, such as a \[\`Drawer\`]\[], dialog, or popup menu, wrap the container widget in a \`PointerInterceptor\`: \`\`\`dart Scaffold( drawer: PointerInterceptor( child: Drawer( child: ListView( children: \[ ListTile( title: const Text('Home'), onTap: () { // Handle navigation }, ), ], ), ), ), body: HtmlElementView\.fromTagName(tagName: 'iframe'), ) \`\`\` ### Conditionally intercept events The \`intercepting\` property lets you enable or disable pointer interception based on a boolean condition: \`\`\`dart PointerInterceptor( intercepting: isOverlayVisible, child: ElevatedButton( onPressed: () { // Handle button press }, child: const Text('Submit'), ), ) \`\`\` When \`intercepting\` is \`false\`, \`PointerInterceptor\` renders its \`child\` directly without creating an extra platform view in the DOM. ### Debug interceptor bounds The \`PointerInterceptor\` widget includes a \`debug\` property. Setting \`debug: true\` renders a visible colored overlay over the interceptor area, helping you verify the size and position of the intercepted region: \`\`\`dart PointerInterceptor( debug: true, child: ElevatedButton( onPressed: () {}, child: const Text('Click me'), ), ) \`\`\` ## \`package:webview\_flutter\` Embedding a full HTML page inside a Flutter app is a common requirement. The Flutter team offers a plugin for this purpose: \* \[\`package:webview\_flutter\`]\[] \[JS Interoperability]: https\://dart.dev/interop/js-interop \[\`Drawer\`]: https\://pub.dev/documentation/material\_ui/latest/material\_ui/Drawer-class.html \[\`ElevatedButton\`]: https\://pub.dev/documentation/material\_ui/latest/material\_ui/ElevatedButton-class.html \[\`FloatingActionButton\`]: https\://pub.dev/documentation/material\_ui/latest/material\_ui/FloatingActionButton-class.html \[\`HtmlElementView\.fromTagName\` constructor]: https\://api.flutter.dev/flutter/widgets/HtmlElementView/HtmlElementView\.fromTagName.html \[\`HtmlElementView\` widget]: https\://api.flutter.dev/flutter/widgets/HtmlElementView-class.html \[\`HTMLVideoElement\` class]: https\://pub.dev/documentation/web/latest/web/HTMLVideoElement-extension-type.html \[\`package:pointer\_interceptor\`]: https\://pub.dev/packages/pointer\_interceptor \[\`package:web\`]: https\://pub.dev/packages/web \[\`package:webview\_flutter\`]: https\://pub.dev/packages/webview\_flutter \[\`package:webview\_flutter\_web\`]: https\://pub.dev/packages/webview\_flutter\_web \[\`PointerInterceptor\`]: https\://pub.dev/documentation/pointer\_interceptor/latest/pointer\_interceptor/PointerInterceptor-class.html

----
url: https://docs.flutter.dev/ui/layout/scrolling
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Scrolling](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

Flutter has many built-in widgets that automatically scroll and also offers a variety of widgets that you can customize to create specific scrolling behavior.

## Basic scrolling

[#](#basic-scrolling)

Many Flutter widgets support scrolling out of the box and do most of the work for you. For example, [`SingleChildScrollView`](https://api.flutter.dev/flutter/widgets/SingleChildScrollView-class.html) automatically scrolls its child when necessary. Other useful widgets include [`ListView`](https://api.flutter.dev/flutter/widgets/ListView-class.html) and [`GridView`](https://api.flutter.dev/flutter/widgets/GridView-class.html). You can check out more of these widgets on the [scrolling page](/ui/widgets/scrolling) of the Widget catalog.

[Watch on YouTube in a new tab: "Scrollbar | Flutter widget of the week"](https://www.youtube.com/watch/DbkIQSvwnZc)

[Watch on YouTube in a new tab: "ListView | Flutter widget of the week"](https://www.youtube.com/watch/KJpkjHGiI5A)

### Infinite scrolling

[#](#infinite-scrolling)

When you have a long list of items in your `ListView` or `GridView` (including an *infinite* list), you can build the items on demand as they scroll into view. This provides a much more performant scrolling experience. For more information, check out [`ListView.builder`](https://api.flutter.dev/flutter/widgets/ListView/ListView.builder.html) or [`GridView.builder`](https://api.flutter.dev/flutter/widgets/GridView/GridView.builder.html).

### Specialized scrollable widgets

[#](#specialized-scrollable-widgets)

The following widgets provide more specific scrolling behavior.

A video on using [`DraggableScrollableSheet`](https://api.flutter.dev/flutter/widgets/DraggableScrollableSheet-class.html):

[Watch on YouTube in a new tab: "DraggableScrollableSheet | Flutter widget of the week"](https://www.youtube.com/watch/Hgw819mL_78)

Turn the scrollable area into a wheel with [`ListWheelScrollView`](https://api.flutter.dev/flutter/widgets/ListWheelScrollView-class.html)!

[Watch on YouTube in a new tab: "ListWheelScrollView | Flutter widget of the week"](https://www.youtube.com/watch/dUhmWAz4C7Y)

## Fancy scrolling

[#](#fancy-scrolling)

Perhaps you want to implement *elastic* scrolling, also called *scroll bouncing*. Or maybe you want to implement other dynamic scrolling effects, like parallax scrolling. Or perhaps you want a scrolling header with very specific behavior, such as shrinking or disappearing.

You can achieve all this and more using the Flutter `Sliver*` classes. A *sliver* refers to a piece of the scrollable area. You can define and insert a sliver into a [`CustomScrollView`](https://api.flutter.dev/flutter/widgets/CustomScrollView-class.html) to have finer-grained control over that area.

For more information, check out [Using slivers to achieve fancy scrolling](/ui/layout/scrolling/slivers) and the [Sliver classes](/ui/widgets/layout#sliver-widgets).

## Nested scrolling widgets

[#](#nested-scrolling-widgets)

How do you nest a scrolling widget inside another scrolling widget without hurting scrolling performance? Do you set the `ShrinkWrap` property to true, or do you use a sliver?

Check out the "ShrinkWrap vs Slivers" video:

[Watch on YouTube in a new tab: "ShrinkWrap vs Slivers | Decoding Flutter"](https://www.youtube.com/watch/LUqDNnv_dh0)

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/ui/layout/scrolling/index.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/ui/layout/scrolling\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/ui/layout/scrolling/index.md "Report an issue with this page").

----
url: https://docs.flutter.dev/app-architecture/design-patterns/sql.md
----

\# Persistent storage architecture: SQL > Save complex application data to a user's device with SQL.

<!--?code-excerpt path-base="app-architecture/todo_data_service"?-->

Most Flutter applications, no matter how small or big they are, might require storing data on the user’s device at some point. For example, API keys, user preferences or data that should be available offline. In this recipe, you will learn how to integrate persistent storage for complex data using SQL in a Flutter application following the Flutter Architecture design pattern. To learn how to store simpler key-value data, take a look at the Cookbook recipe: \[Persistent storage architecture: Key-value data]\[]. To read this recipe, you should be familiar with SQL and SQLite. If you need help, you can read the \[Persist data with SQLite]\[] recipe before reading this one. This example uses \[\`sqflite\`]\[] with the \[\`sqflite\_common\_ffi\`]\[] plugin, which combined support for mobile and desktop. Support for web is provided in the experimental plugin \[\`sqflite\_common\_ffi\_web\`]\[] but it's not included in this example. ## Example application: ToDo list application The example application consists of a single screen with an app bar at the top, a list of items, and a text field input at the bottom. The body of the application contains the \`TodoListScreen\`. This screen contains a \`ListView\` of \`ListTile\` items, each one representing a ToDo item. At the bottom, a \`TextField\` allows users to create new ToDo items by writing the task description and then tapping on the “Add” \`FilledButton\`. Users can tap on the delete \`IconButton\` to delete the ToDo item. The list of ToDo items is stored locally using a database service, and restored when the user starts the application. :::note The full, runnable source-code for this example is available in \[\`/examples/app-architecture/todo\_data\_service/\`]\[]. ::: ## Storing complex data with SQL This functionality follows the recommended \[Flutter Architecture design]\[], containing a UI layer and a data layer. Additionally, in the domain layer you will find the data model used. - UI layer with \`TodoListScreen\` and \`TodoListViewModel\` - Domain layer with \`Todo\` data class - Data layer with \`TodoRepository\` and \`DatabaseService\` ### ToDo list presentation layer The \`TodoListScreen\` is a Widget that contains the UI in charge of displaying and creating the ToDo items. It follows the \[MVVM pattern]\[] and is accompanied by the \`TodoListViewModel\`, which contains the list of ToDo items and three commands to load, add, and delete ToDo items. This screen is divided into two parts, one containing the list of ToDo items, implemented using a \`ListView\`, and the other is a \`TextField\` and a \`Button\`, used for creating new ToDo items. The \`ListView\` is wrapped by a \`ListenableBuilder\`, which listens to changes in the \`TodoListViewModel\`, and shows a \`ListTile\` for each ToDo item.

<!--?code-excerpt "lib/ui/todo_list/widgets/todo_list_screen.dart (ListenableBuilder)" replace="/child: //g;/^\),$/)/g"?-->

\`\`\`dart ListenableBuilder( listenable: widget.viewModel, builder: (context, child) { return ListView\.builder( itemCount: widget.viewModel.todos.length, itemBuilder: (context, index) { final todo = widget.viewModel.todos\[index]; return ListTile( title: Text(todo.task), trailing: IconButton( icon: const Icon(Icons.delete), onPressed: () => widget.viewModel.delete.execute(todo.id), ), ); }, ); }, ) \`\`\` The list of ToDo items is defined in the \`TodoListViewModel\`, and loaded by the \`load\` command. This method calls the \`TodoRepository\` and fetches the list of ToDo items.

<!--?code-excerpt "lib/ui/todo_list/viewmodel/todo_list_viewmodel.dart (TodoListViewModel)"?-->

\`\`\`dart List \_todos = \[]; List get todos => \_todos; Future> \_load() async { try { final result = await \_todoRepository.fetchTodos(); switch (result) { case Ok>(): \_todos = result.value; return Result.ok(null); case Error(): return Result.error(result.error); } } on Exception catch (e) { return Result.error(e); } finally { notifyListeners(); } } \`\`\` Pressing the \`FilledButton\`, executes the \`add\` command and passes in the text controller value.

<!--?code-excerpt "lib/ui/todo_list/widgets/todo_list_screen.dart (FilledButton)" replace="/^\),$/)/g"?-->

\`\`\`dart FilledButton.icon( onPressed: () => widget.viewModel.add.execute(\_controller.text), label: const Text('Add'), icon: const Icon(Icons.add), ) \`\`\` The \`add\` command then calls the \`TodoRepository.createTodo()\` method with the task description text and it creates a new ToDo item. The \`createTodo()\` method returns the newly created ToDo, which is then added to the \`\_todo\` list in the view model. ToDo items contain a unique identifier generated by the database. This is why the view model doesn’t create the ToDo item, but rather the \`TodoRepository\` does.

<!--?code-excerpt "lib/ui/todo_list/viewmodel/todo_list_viewmodel.dart (Add)"?-->

\`\`\`dart Future> \_add(String task) async { try { final result = await \_todoRepository.createTodo(task); switch (result) { case Ok(): \_todos.add(result.value); return Result.ok(null); case Error(): return Result.error(result.error); } } on Exception catch (e) { return Result.error(e); } finally { notifyListeners(); } } \`\`\` Finally, the \`TodoListScreen\` also listens to the result in the \`add\` command. When the action completes, the \`TextEditingController\` is cleared.

<!--?code-excerpt "lib/ui/todo_list/widgets/todo_list_screen.dart (Add)"?-->

\`\`\`dart void \_onAdd() { // Clear the text field when the add command completes. if (widget.viewModel.add.completed) { widget.viewModel.add.clearResult(); \_controller.clear(); } } \`\`\` When a user taps on the \`IconButton\` in the \`ListTile\`, the delete command is executed.

<!--?code-excerpt "lib/ui/todo_list/widgets/todo_list_screen.dart (Delete)" replace="/trailing: //g;/^\),$/)/g"?-->

\`\`\`dart IconButton( icon: const Icon(Icons.delete), onPressed: () => widget.viewModel.delete.execute(todo.id), ) \`\`\` Then, the view model calls the \`TodoRepository.deleteTodo()\` method, passing the unique ToDo item identifier. A correct result removes the ToDo item from the view model \*and\* the screen.

<!--?code-excerpt "lib/ui/todo_list/viewmodel/todo_list_viewmodel.dart (Delete)"?-->

\`\`\`dart Future> \_delete(int id) async { try { final result = await \_todoRepository.deleteTodo(id); switch (result) { case Ok(): \_todos.removeWhere((todo) => todo.id == id); return Result.ok(null); case Error(): return Result.error(result.error); } } on Exception catch (e) { return Result.error(e); } finally { notifyListeners(); } } \`\`\` ### Todo list domain layer The domain layer of this example application contains the \`Todo\` item data model. Items are presented by an immutable data class. In this case, the application uses the \`freezed\` package to generate the code. The class has two properties, an ID represented by an \`int\`, and a task description, represented by a \`String\`.

<!--?code-excerpt "lib/business/model/todo.dart (Todo)"?-->

\`\`\`dart @freezed abstract class Todo with \_$Todo { const factory Todo({ /// The unique identifier of the Todo item. required int id, /// The task description of the Todo item. required String task, }) = \_Todo; } \`\`\` ### Todo list data layer The data layer of this functionality is composed of two classes, the \`TodoRepository\` and the \`DatabaseService\`. The \`TodoRepository\` acts as the source of truth for all the ToDo items. View models must use this repository to access to the ToDo list, and it should not expose any implementation details on how they are stored. Internally, the \`TodoRepository\` uses the \`DatabaseService\`, which implements the access to the SQL database using the \`sqflite\` package. You can implement the same \`DatabaseService\` using other storage packages like \`sqlite3\`, \`drift\` or even cloud storage solutions like \`firebase\_database\`. The \`TodoRepository\` checks if the database is open before every request and opens it if necessary. It implements the \`fetchTodos()\`, \`createTodo()\`, and \`deleteTodo()\` methods.

<!--?code-excerpt "lib/data/repositories/todo_repository.dart (TodoRepository)"?-->

\`\`\`dart class TodoRepository { TodoRepository({required this.\_database}); final DatabaseService \_database; Future>> fetchTodos() async { if (!\_database.isOpen()) { await \_database.open(); } return \_database.getAll(); } Future> createTodo(String task) async { if (!\_database.isOpen()) { await \_database.open(); } return \_database.insert(task); } Future> deleteTodo(int id) async { if (!\_database.isOpen()) { await \_database.open(); } return \_database.delete(id); } } \`\`\` The \`DatabaseService\` implements the access to the SQLite database using the \`sqflite\` package. It’s a good idea to define the table and column names as constants to avoid typos when writing SQL code.

<!--?code-excerpt "lib/data/services/database_service.dart (Table)"?-->

\`\`\`dart static const String \_todoTableName = 'todo'; static const String \_idColumnName = '\_id'; static const String \_taskColumnName = 'task'; \`\`\` The \`open()\` method opens the existing database, or creates a new one if it doesn’t exist.

<!--?code-excerpt "lib/data/services/database_service.dart (Open)"?-->

\`\`\`dart Future open() async { \_database = await databaseFactory.openDatabase( join(await databaseFactory.getDatabasesPath(), 'app\_database.db'), options: OpenDatabaseOptions( onCreate: (db, version) { return db.execute( 'CREATE TABLE $\_todoTableName($\_idColumnName INTEGER PRIMARY KEY AUTOINCREMENT, $\_taskColumnName TEXT)', ); }, version: 1, ), ); } \`\`\` Note that the column \`id\` is set as \`primary key\` and \`autoincrement\`; this means that each newly inserted item is assigned a new value for the \`id\` column. The \`insert()\` method creates a new ToDo item in the database, and returns a newly created Todo instance. The \`id\` is generated as mentioned before.

<!--?code-excerpt "lib/data/services/database_service.dart (Insert)"?-->

\`\`\`dart Future> insert(String task) async { try { final id = await \_database!.insert(\_todoTableName, { \_taskColumnName: task, }); return Result.ok(Todo(id: id, task: task)); } on Exception catch (e) { return Result.error(e); } } \`\`\` All the \`DatabaseService\` operations use the \`Result\` class to return a value, as recommended by the \[Flutter architecture recommendations]\[]. This facilitates handling errors in further steps in the application code. The \`getAll()\` method performs a database query, obtaining all the values in the \`id\` and \`task\` columns. For each entry, it creates a \`Todo\` class instance.

<!--?code-excerpt "lib/data/services/database_service.dart (GetAll)"?-->

\`\`\`dart Future>> getAll() async { try { final entries = await \_database!.query( \_todoTableName, columns: \[\_idColumnName, \_taskColumnName], ); final list = entries .map( (element) => Todo( id: element\[\_idColumnName] as int, task: element\[\_taskColumnName] as String, ), ) .toList(); return Result.ok(list); } on Exception catch (e) { return Result.error(e); } } \`\`\` The \`delete()\` method performs a database delete operation based on the ToDo item \`id\`. In this case, if no items were deleted an error is returned, indicating that something went wrong.

<!--?code-excerpt "lib/data/services/database_service.dart (Delete)"?-->

\`\`\`dart Future> delete(int id) async { try { final rowsDeleted = await \_database!.delete( \_todoTableName, where: '$\_idColumnName = ?', whereArgs: \[id], ); if (rowsDeleted == 0) { return Result.error(Exception('No todo found with id $id')); } return Result.ok(null); } on Exception catch (e) { return Result.error(e); } } \`\`\` :::note In some cases, you might want to close the database when you are done with it. For example, when the user leaves the screen, or after a certain time has passed. This depends on the database implementation as well as your application requirements. It’s recommended that you check with the database package authors for recommendations. ::: ## Putting it all together In the \`main()\` method of your application, first initialize the \`DatabaseService\`, which requires different initialization code on different platforms. Then, pass the newly created \`DatabaseService\` into the \`TodoRepository\` which is itself passed into the \`MainApp\` as a constructor argument dependency.

<!--?code-excerpt "lib/main.dart (MainTodo)"?-->

\`\`\`dart void main() { final DatabaseService databaseService; if (kIsWeb) { throw UnsupportedError('Platform not supported'); } else if (Platform.isLinux || Platform.isWindows || Platform.isMacOS) { // Initialize the FFI-based SQLite. sqfliteFfiInit(); databaseService = DatabaseService(databaseFactory: databaseFactoryFfi); } else { // Use the default native SQLite. databaseService = DatabaseService(databaseFactory: databaseFactory); } runApp( MainApp( // ··· todoRepository: TodoRepository(database: databaseService), ), ); } \`\`\` Then, when the \`TodoListScreen\` is created, also create the \`TodoListViewModel\` and pass the \`TodoRepository\` to it as dependency.

<!--?code-excerpt "lib/main.dart (TodoListScreen)" replace="/body: //g;/^\),$/)/g"?-->

\`\`\`dart TodoListScreen( viewModel: TodoListViewModel(todoRepository: widget.todoRepository), ) \`\`\` \[Flutter Architecture design]:/app-architecture \[Flutter architecture recommendations]:/app-architecture \[MVVM pattern]:/app-architecture/guide#mvvm \[Persist data with SQLite]:/cookbook/persistence/sqlite \[Persistent storage architecture: Key-value data]:/app-architecture/design-patterns/key-value-data \[\`/examples/app-architecture/todo\_data\_service/\`]: https\://github.com/flutter/website/tree/main/examples/app-architecture/todo\_data\_service/ \[\`sqflite\_common\_ffi\_web\`]:https\://pub.dev/packages/sqflite\_common\_ffi\_web \[\`sqflite\_common\_ffi\`]:https\://pub.dev/packages/sqflite\_common\_ffi \[\`sqflite\`]:https\://pub.dev/packages/sqflite

----
url: https://docs.flutter.dev/cookbook/lists/floating-app-bar
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Place a floating app bar above a list](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

This guide describes how to place a floating app bar or navigation bar above a list in a Flutter app.

## Overview

[#](#overview)

To make it easier for users to view a list of items, you might want to minimize the app bar (navigation bar), as the user scrolls down the list.

Moving the app bar into a [`CustomScrollView`](https://api.flutter.dev/flutter/widgets/CustomScrollView-class.html) allows you to create an app bar that can be minimized or scroll 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 minimizes as the user scrolls down the list using the following steps:

1. Create a `CustomScrollView`.
2. Add a floating app bar to `CustomScrollView`.
3. Add a list of items to `CustomScrollView`.

## 1. Create a `CustomScrollView`

[#](#1-create-a-customscrollview)

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 provided to the `CustomScrollView` are known as *slivers*. There are several types of slivers, such as `SliverList`, `SliverGrid`, and `SliverAppBar`. In fact, the `ListView` and `GridView` widgets use the `SliverList` and `SliverGrid` widgets to implement scrolling.

For this example, create a `CustomScrollView` that contains a `SliverList`. Also, remove the app bar property from your code if it exists.

* [Material widgets](#158-tab-panel)
* [Cupertino widgets](#159-tab-panel)

dart

```
MaterialApp(
  title: 'Floating App Bar',
  home: Scaffold(
    // No app bar property provided yet.
    body: CustomScrollView(
      // Add the app bar and list of items as slivers in the next steps.
      slivers: <Widget>[],
    ),
  ),
);
```

dart

```
CupertinoApp(
  title: 'Floating Navigation Bar',
  home: CupertinoPageScaffold(
    // No navigation bar property provided yet.
    child: CustomScrollView(
      // Add the navigation bar and list of items as slivers in the next steps.
      slivers: <Widget>[],
    ),
  ),
);
```

## 2. Add a floating app bar

[#](#2-add-a-floating-app-bar)

Next, add an app bar to the [`CustomScrollView`](https://api.flutter.dev/flutter/widgets/CustomScrollView-class.html).

* [Material widgets](#160-tab-panel)
* [Cupertino widgets](#161-tab-panel)

Flutter provides the [`SliverAppBar`](https://pub.dev/documentation/material_ui/latest/material_ui/SliverAppBar-class.html) widget which, much like the normal `AppBar` widget, uses the `SliverAppBar` to display a title, tabs, images and more.

However, the `SliverAppBar` also gives you the ability to create a "floating" app bar that shrinks and floats when you're not at the top of the page.

To create this effect:

1. Start with an app bar that displays only a title.
2. Set the `pinned` property to `true`.
3. Add a `flexibleSpace` widget that fills the available `expandedHeight`.

dart

```
slivers: [
  // Add the app bar to the CustomScrollView.
  SliverAppBar(
    // Provide a standard title.
    title: Text('Floating App Bar'),
    // Pin the app bar when scrolling.
    pinned: true,
    // Display a placeholder widget to visualize the shrinking size.
    flexibleSpace: Placeholder(),
    // Make the initial height of the SliverAppBar larger than normal.
    expandedHeight: 200,
  ),
],
```

Flutter provides the [`CupertinoSliverNavigationBar`](https://pub.dev/documentation/cupertino_ui/latest/cupertino_ui/CupertinoSliverNavigationBar-class.html) widget, which lets you have a "floating" navigation bar that shrinks when you scroll down and floats when you're not at the top of the page.

To create this effect:

1. Add `CupertinoSliverNavigationBar` to `CustomScrollView`.
2. Start with an app bar that displays only a title.

dart

```
slivers: [
  // Add the navigation bar to the CustomScrollView.
  CupertinoSliverNavigationBar(
    // Provide a standard title.
    largeTitle: Text('Floating App Bar'),
  ),
],
```

## 3. Add a list of items

[#](#3-add-a-list-of-items)

Now that you have the app bar in place, add a list of items to the `CustomScrollView`. You have two options: a [`SliverList`](https://api.flutter.dev/flutter/widgets/SliverList-class.html) or a [`SliverGrid`](https://api.flutter.dev/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.

* [Material widgets](#162-tab-panel)
* [Cupertino widgets](#163-tab-panel)

dart

```
// Next, create a SliverList
SliverList.builder(
  // The builder function returns a ListTile with a title that
  // displays the index of the current item.
  itemBuilder: (context, index) =>
      ListTile(title: Text('Item #$index')),
  // Builds 50 ListTiles
  itemCount: 50,
)
```

dart

```
// Next, create a SliverList
SliverList.builder(
  // The builder function returns a CupertinoListTile with a title
  // that displays the index of the current item.
  itemBuilder: (context, index) =>
      CupertinoListTile(title: Text('Item #$index')),
  // Builds 50 CupertinoListTile
  itemCount: 50,
)
```

## Interactive example

[#](#interactive-example)

* [Material widgets](#164-tab-panel)
* [Cupertino widgets](#165-tab-panel)

```
import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    const title = 'Floating App Bar';

    return MaterialApp(
      title: title,
      home: Scaffold(
        // No app bar provided to Scaffold, only a body with a
        // CustomScrollView.
        body: CustomScrollView(
          slivers: [
            // Add the app bar to the CustomScrollView.
            const SliverAppBar(
              // Provide a standard title.
              title: Text(title),
              // Pin the app bar when scrolling
              pinned: true,
              // Display a placeholder widget to visualize the shrinking size.
              flexibleSpace: Placeholder(),
              // Make the initial height of the SliverAppBar larger than normal.
              expandedHeight: 200,
            ),
            // Next, create a SliverList
            SliverList.builder(
              // The builder function returns a ListTile with a title that
              // displays the index of the current item.
              itemBuilder: (context, index) =>
                  ListTile(title: Text('Item #$index')),
              // Builds 50 ListTiles
              itemCount: 50,
            ),
          ],
        ),
      ),
    );
  }
}
```

```
import 'package:flutter/cupertino.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    const title = 'Floating Navigation Bar';

    return CupertinoApp(
      title: title,
      home: CupertinoPageScaffold(
        // No navigation bar provided to CupertinoPageScaffold,
        // only a body with a CustomScrollView.
        child: CustomScrollView(
          slivers: [
            // Add the navigation bar to the CustomScrollView.
            const CupertinoSliverNavigationBar(
              // Provide a standard title.
              largeTitle: Text(title),
            ),
            // Next, create a SliverList
            SliverList.builder(
              // The builder function returns a CupertinoListTile with a title
              // that displays the index of the current item.
              itemBuilder: (context, index) =>
                  CupertinoListTile(title: Text('Item #$index')),
              // Builds 50 CupertinoListTile
              itemCount: 50,
            ),
          ],
        ),
      ),
    );
  }
}
```

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-09-29. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/lists/floating-app-bar.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/cookbook/lists/floating-app-bar\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/lists/floating-app-bar.md "Report an issue with this page").

----
url: https://docs.flutter.dev/data-and-backend/state-mgmt/simple.md
----

\# Simple app state management > A simple form of state management.

<!--?code-excerpt path-base="state_mgmt/simple/"?-->

Now that you know about \[declarative UI programming]\[] and the difference between \[ephemeral and app state]\[], you are ready to learn about simple app state management. On this page, we are going to be using the \`provider\` package. If you are new to Flutter and you don't have a strong reason to choose another approach (Redux, Rx, hooks, etc.), this is probably the approach you should start with. The \`provider\` package is easy to understand and it doesn't use much code. It also uses concepts that are applicable in every other approach. That said, if you have a strong background in state management from other reactive frameworks, you can find packages and tutorials listed on the \[options page]\[]. ## Our example For illustration, consider the following simple app. The app has two separate screens: a catalog, and a cart (represented by the \`MyCatalog\`, and \`MyCart\` widgets, respectively). It could be a shopping app, but you can imagine the same structure in a simple social networking app (replace catalog for "wall" and cart for "favorites"). The catalog screen includes a custom app bar (\`MyAppBar\`) and a scrolling view of many list items (\`MyListItems\`). Here's the app visualized as a widget tree. So we have at least 5 subclasses of \`Widget\`. Many of them need access to state that "belongs" elsewhere. For example, each \`MyListItem\` needs to be able to add itself to the cart. It might also want to see whether the currently displayed item is already in the cart. This takes us to our first question: where should we put the current state of the cart? ## Lifting state up In Flutter, it makes sense to keep the state above the widgets that use it. Why? In declarative frameworks like Flutter, if you want to change the UI, you have to rebuild it. There is no easy way to have \`MyCart.updateWith(somethingNew)\`. In other words, it's hard to imperatively change a widget from outside, by calling a method on it. And even if you could make this work, you would be fighting the framework instead of letting it help you. \`\`\`dart // BAD: DO NOT DO THIS void myTapHandler() { var cartWidget = somehowGetMyCartWidget(); cartWidget.updateWith(item); } \`\`\` Even if you get the above code to work, you would then have to deal with the following in the \`MyCart\` widget: \`\`\`dart // BAD: DO NOT DO THIS Widget build(BuildContext context) { return SomeWidget( // The initial state of the cart. ); } void updateWith(Item item) { // Somehow you need to change the UI from here. } \`\`\` You would need to take into consideration the current state of the UI and apply the new data to it. It's hard to avoid bugs this way. In Flutter, you construct a new widget every time its contents change. Instead of \`MyCart.updateWith(somethingNew)\` (a method call) you use \`MyCart(contents)\` (a constructor). Because you can only construct new widgets in the build methods of their parents, if you want to change \`contents\`, it needs to live in \`MyCart\`'s parent or above.

<!--?code-excerpt "lib/src/provider.dart (my-tap-handler)"?-->

\`\`\`dart // GOOD void myTapHandler(BuildContext context) { var cartModel = somehowGetMyCartModel(context); cartModel.add(item); } \`\`\` Now \`MyCart\` has only one code path for building any version of the UI.

<!--?code-excerpt "lib/src/provider.dart (build)"?-->

\`\`\`dart // GOOD Widget build(BuildContext context) { var cartModel = somehowGetMyCartModel(context); return SomeWidget( // Just construct the UI once, using the current state of the cart. // ··· ); } \`\`\` In our example, \`contents\` needs to live in \`MyApp\`. Whenever it changes, it rebuilds \`MyCart\` from above (more on that later). Because of this, \`MyCart\` doesn't need to worry about lifecycle—it just declares what to show for any given \`contents\`. When that changes, the old \`MyCart\` widget disappears and is completely replaced by the new one. This is what we mean when we say that widgets are immutable. They don't change—they get replaced. Now that we know where to put the state of the cart, let's see how to access it. ## Accessing the state When a user clicks on one of the items in the catalog, it's added to the cart. But since the cart lives above \`MyListItem\`, how do we do that? A simple option is to provide a callback that \`MyListItem\` can call when it is clicked. Dart's functions are first class objects, so you can pass them around any way you want. So, inside \`MyCatalog\` you can define the following:

<!--?code-excerpt "lib/src/passing_callbacks.dart (methods)"?-->

\`\`\`dart @override Widget build(BuildContext context) { return SomeWidget( // Construct the widget, passing it a reference to the method above. MyListItem(myTapCallback), ); } void myTapCallback(Item item) { print('user tapped on $item'); } \`\`\` This works okay, but for an app state that you need to modify from many different places, you'd have to pass around a lot of callbacks—which gets old pretty quickly. Fortunately, Flutter has mechanisms for widgets to provide data and services to their descendants (in other words, not just their children, but any widgets below them). As you would expect from Flutter, where \_Everything is a Widget™\_, these mechanisms are just special kinds of widgets—\`InheritedWidget\`, \`InheritedNotifier\`, \`InheritedModel\`, and more. We won't be covering those here, because they are a bit low-level for what we're trying to do. Instead, we are going to use a package that works with the low-level widgets but is simple to use. It's called \`provider\`. Before working with \`provider\`, don't forget to add the dependency on it to your \`pubspec.yaml\`. To add the \`provider\` package as a dependency, run \`flutter pub add\`: \`\`\`console $ flutter pub add provider \`\`\` Now you can \`import 'package:provider/provider.dart';\` and start building. With \`provider\`, you don't need to worry about callbacks or \`InheritedWidgets\`. But you do need to understand 3 concepts: \* ChangeNotifier \* ChangeNotifierProvider \* Consumer ## ChangeNotifier \`ChangeNotifier\` is a simple class included in the Flutter SDK which provides change notification to its listeners. In other words, if something is a \`ChangeNotifier\`, you can subscribe to its changes. (It is a form of Observable, for those familiar with the term.) In \`provider\`, \`ChangeNotifier\` is one way to encapsulate your application state. For very simple apps, you get by with a single \`ChangeNotifier\`. In complex ones, you'll have several models, and therefore several \`ChangeNotifiers\`. (You don't need to use \`ChangeNotifier\` with \`provider\` at all, but it's an easy class to work with.) In our shopping app example, we want to manage the state of the cart in a \`ChangeNotifier\`. We create a new class that extends it, like so:

<!--?code-excerpt "lib/src/provider.dart (model)" replace="/ChangeNotifier/[!$&!]/g;/notifyListeners/[!$&!]/g"?-->

\`\`\`dart class CartModel extends \[!ChangeNotifier!] { /// Internal, private state of the cart. final List \_items = \[]; /// An unmodifiable view of the items in the cart. UnmodifiableListView get items => UnmodifiableListView(\_items); /// The current total price of all items (assuming all items cost $42). int get totalPrice => \_items.length \* 42; /// Adds \[item] to cart. This and \[removeAll] are the only ways to modify the /// cart from the outside. void add(Item item) { \_items.add(item); // This call tells the widgets that are listening to this model to rebuild. \[!notifyListeners!]\(); } /// Removes all items from the cart. void removeAll() { \_items.clear(); // This call tells the widgets that are listening to this model to rebuild. \[!notifyListeners!]\(); } } \`\`\` The only code that is specific to \`ChangeNotifier\` is the call to \`notifyListeners()\`. Call this method any time the model changes in a way that might change your app's UI. Everything else in \`CartModel\` is the model itself and its business logic. \`ChangeNotifier\` is part of \`flutter:foundation\` and doesn't depend on any higher-level classes in Flutter. It's easily testable (you don't even need to use \[widget testing]\[] for it). For example, here's a simple unit test of \`CartModel\`:

<!--?code-excerpt "test/model_test.dart (test)"?-->

\`\`\`dart test('adding item increases total cost', () { final cart = CartModel(); final startingPrice = cart.totalPrice; var i = 0; cart.addListener(() { expect(cart.totalPrice, greaterThan(startingPrice)); i++; }); cart.add(Item('Dash')); expect(i, 1); }); \`\`\` ## ChangeNotifierProvider \`ChangeNotifierProvider\` is the widget that provides an instance of a \`ChangeNotifier\` to its descendants. It comes from the \`provider\` package. We already know where to put \`ChangeNotifierProvider\`: above the widgets that need to access it. In the case of \`CartModel\`, that means somewhere above both \`MyCart\` and \`MyCatalog\`. You don't want to place \`ChangeNotifierProvider\` higher than necessary (because you don't want to pollute the scope). But in our case, the only widget that is on top of both \`MyCart\` and \`MyCatalog\` is \`MyApp\`.

<!--?code-excerpt "lib/main.dart (main)" replace="/ChangeNotifierProvider/[!$&!]/g"?-->

\`\`\`dart void main() { runApp( \[!ChangeNotifierProvider!]\( create: (context) => CartModel(), child: const MyApp(), ), ); } \`\`\` Note that we're defining a builder that creates a new instance of \`CartModel\`. \`ChangeNotifierProvider\` is smart enough \_not\_ to rebuild \`CartModel\` unless absolutely necessary. It also automatically calls \`dispose()\` on \`CartModel\` when the instance is no longer needed. If you want to provide more than one class, you can use \`MultiProvider\`:

<!--?code-excerpt "lib/main.dart (multi-provider-main)" replace="/multiProviderMain/main/g;/MultiProvider/[!$&!]/g"?-->

\`\`\`dart void main() { runApp( \[!MultiProvider!]\( providers: \[ ChangeNotifierProvider(create: (context) => CartModel()), Provider(create: (context) => SomeOtherClass()), ], child: const MyApp(), ), ); } \`\`\` ## Consumer Now that \`CartModel\` is provided to widgets in our app through the \`ChangeNotifierProvider\` declaration at the top, we can start using it. This is done through the \`Consumer\` widget.

<!--?code-excerpt "lib/src/provider.dart (descendant)" replace="/Consumer/[!$&!]/g"?-->

\`\`\`dart return \[!Consumer!]\( builder: (context, cart, child) { return Text('Total price: ${cart.totalPrice}'); }, ); \`\`\` We must specify the type of the model that we want to access. In this case, we want \`CartModel\`, so we write \`Consumer\`. If you don't specify the generic (\`\`), the \`provider\` package won't be able to help you. \`provider\` is based on types, and without the type, it doesn't know what you want. The only required argument of the \`Consumer\` widget is the builder. Builder is a function that is called whenever the \`ChangeNotifier\` changes. (In other words, when you call \`notifyListeners()\` in your model, all the builder methods of all the corresponding \`Consumer\` widgets are called.) The builder is called with three arguments. The first one is \`context\`, which you also get in every build method. The second argument of the builder function is the instance of the \`ChangeNotifier\`. It's what we were asking for in the first place. You can use the data in the model to define what the UI should look like at any given point. The third argument is \`child\`, which is there for optimization. If you have a large widget subtree under your \`Consumer\` that \_doesn't\_ change when the model changes, you can construct it once and get it through the builder.

<!--?code-excerpt "lib/src/performance.dart (child)" replace="/\bchild\b/[!$&!]/g"?-->

\`\`\`dart return Consumer( builder: (context, cart, \[!child!]) => Stack( children: \[ // Use SomeExpensiveWidget here, without rebuilding every time. ?\[!child!], Text('Total price: ${cart.totalPrice}'), ], ), // Build the expensive widget here. \[!child!]: const SomeExpensiveWidget(), ); \`\`\` It is best practice to put your \`Consumer\` widgets as deep in the tree as possible. You don't want to rebuild large portions of the UI just because some detail somewhere changed.

<!--?code-excerpt "lib/src/performance.dart (non-leaf-descendant)"?-->

\`\`\`dart // DON'T DO THIS return Consumer( builder: (context, cart, child) { return HumongousWidget( // ... child: AnotherMonstrousWidget( // ... child: Text('Total price: ${cart.totalPrice}'), ), ); }, ); \`\`\` Instead:

<!--?code-excerpt "lib/src/performance.dart (leaf-descendant)"?-->

\`\`\`dart // DO THIS return HumongousWidget( // ... child: AnotherMonstrousWidget( // ... child: Consumer( builder: (context, cart, child) { return Text('Total price: ${cart.totalPrice}'); }, ), ), ); \`\`\` ### Provider.of Sometimes, you don't really need the \_data\_ in the model to change the UI but you still need to access it. For example, a \`ClearCart\` button wants to allow the user to remove everything from the cart. It doesn't need to display the contents of the cart, it just needs to call the \`clear()\` method. We could use \`Consumer\` for this, but that would be wasteful. We'd be asking the framework to rebuild a widget that doesn't need to be rebuilt. For this use case, we can use \`Provider.of\`, with the \`listen\` parameter set to \`false\`.

<!--?code-excerpt "lib/src/performance.dart (non-rebuilding)" replace="/listen: false/[!$&!]/g"?-->

\`\`\`dart Provider.of(context, \[!listen: false!]).removeAll(); \`\`\` Using the above line in a build method won't cause this widget to rebuild when \`notifyListeners\` is called. \[declarative UI programming]: /data-and-backend/state-mgmt/declarative \[ephemeral and app state]: /data-and-backend/state-mgmt/ephemeral-vs-app \[options page]: /data-and-backend/state-mgmt/options \[widget testing]: /testing/overview#widget-tests

----
url: https://docs.flutter.dev/platform-integration/linux/building
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Build Linux apps with Flutter](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

This page discusses considerations unique to building Linux apps with Flutter, including shell integration and preparation of apps for distribution.

## Integrate with Linux

[#](#integrate-with-linux)

The Linux programming interface, comprising library functions and system calls, is designed around the C language and ABI. Fortunately, Dart provides the `dart:ffi` package, which enables Dart programs to call into C libraries.

Foreign Function Interfaces (FFI) allow Flutter apps to perform the following with native libraries:

* allocate native memory with `malloc` or `calloc`
* support pointers, structs, and callbacks
* support Application Binary Interface (ABI) types like `long` and `size_t`

To learn more about calling C libraries from Flutter, consult [C interop using `dart:ffi`](https://dart.dev/guides/libraries/c-interop). To bundle and bind your own native C or C++ code with your app, see [Bind to native code using FFI](/platform-integration/bind-native-code).

Many apps benefit from using a package that wraps the underlying library calls in a more convenient, idiomatic Dart API. [Canonical has built a series of packages](https://pub.dev/publishers/canonical.com/packages) with a focus on enabling Dart and Flutter on Linux, including support for desktop notifications, dbus, network management, and Bluetooth.

In general, many other [packages support creating Linux apps](https://pub.dev/packages?q=platform%3Alinux), including common packages such as [`url_launcher`](https://pub.dev/packages/url_launcher), [`shared_preferences`](https://pub.dev/packages/shared_preferences), [`file_selector`](https://pub.dev/packages/file_selector), and [`path_provider`](https://pub.dev/packages/path_provider).

## Prepare Linux apps for distribution

[#](#prepare-linux-apps-for-distribution)

The executable binary can be found in your project under `build/linux/x64/<build mode>/bundle/`. Alongside your executable binary in the `bundle` directory, you can find two directories:

* `lib` contains the required `.so` library files
* `data` contains the application's data assets, such as fonts or images

In addition to these files, your application also relies on various operating system libraries against which it's been compiled. To see the full list of libraries, use the `ldd` command on your application's directory.

For example, to create a new Flutter desktop application called `linux_desktop_test`, build it, and inspect its system library dependencies, run the following commands:

```
$ flutter create linux_desktop_test
$ cd linux_desktop_test
$ flutter build linux --release
$ ldd build/linux/x64/release/bundle/linux_desktop_test
```

To wrap up this application for distribution, include everything in the `bundle` directory and verify the target Linux system has all required system libraries.

This might only require using the following command.

```
$ sudo apt-get install libgtk-3-0 libblkid1 liblzma5
```

To learn how to publish a Linux application to the [Snap Store](https://snapcraft.io/store), consult [Build and release a Linux application to the Snap Store](/deployment/linux).

## Additional resources

[#](#additional-resources)

To learn how to create Linux Debian (`.deb`) and RPM (`.rpm`) builds of your Flutter desktop app, consult the step-by-step [Linux packaging guide](https://medium.com/@fluttergems/packaging-and-distributing-flutter-desktop-apps-the-missing-guide-part-3-linux-24ef8d30a5b4).

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-06-08. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/platform-integration/linux/building.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/platform-integration/linux/building\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/platform-integration/linux/building.md "Report an issue with this page").

----
url: https://docs.flutter.dev/cookbook/animation/page-route-animation.md
----

\# Animate a page route transition > How to animate from one page to another.

<!--?code-excerpt path-base="cookbook/animation/page_route_animation/"?-->

A design language, such as Material, defines standard behaviors when transitioning between routes (or screens). Sometimes, though, a custom transition between screens can make an app more unique. To help, \[\`PageRouteBuilder\`]\[] provides an \[\`Animation\`]\[] object. This \`Animation\` can be used with \[\`Tween\`]\[] and \[\`Curve\`]\[] objects to customize the transition animation. This recipe shows how to transition between routes by animating the new route into view from the bottom of the screen. To create a custom page route transition, this recipe uses the following steps: 1. Set up a \`PageRouteBuilder\`. 1. Create a \`Tween\`. 1. Add an \`AnimatedWidget\`. 1. Use a \`CurveTween\`. 1. Combine the two \`Tween\`s. ## 1. Set up a \`PageRouteBuilder\` To start, use a \[\`PageRouteBuilder\`]\[] to create a \[\`Route\`]\[]. \`PageRouteBuilder\` has two callbacks, one to build the content of the route (\`pageBuilder\`), and one to build the route's transition (\`transitionsBuilder\`). :::note The \`child\` parameter in \`transitionsBuilder\` is the widget returned from \`pageBuilder\`. The \`pageBuilder\` function is only called the first time the route is built. The framework can avoid extra work because \`child\` stays the same throughout the transition. ::: The following example creates two routes: a home route with a "Go!" button, and a second route titled "Page 2".

<!--?code-excerpt "lib/starter.dart (Starter)"?-->

\`\`\`dart import 'package:flutter/material.dart'; void main() { runApp(const MaterialApp(home: Page1())); } class Page1 extends StatelessWidget { const Page1({super.key}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(), body: Center( child: ElevatedButton( onPressed: () { Navigator.of(context).push(\_createRoute()); }, child: const Text('Go!'), ), ), ); } } Route \_createRoute() { return PageRouteBuilder( pageBuilder: (context, animation, secondaryAnimation) => const Page2(), transitionsBuilder: (context, animation, secondaryAnimation, child) { return child; }, ); } class Page2 extends StatelessWidget { const Page2({super.key}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(), body: const Center(child: Text('Page 2')), ); } } \`\`\` ## 2. Create a \`Tween\` To make the new page animate in from the bottom, it should animate from \`Offset(0.0, 1.0)\` to \`Offset(0.0, 0.0)\` (usually defined using the \`Offset.zero\` constructor). In this case, the \`Offset\` is a 2D vector for the \[\`FractionalTranslation\`]\[] widget. Setting the \`dy\` argument to 1 represents a vertical translation one full height of the page. The \`transitionsBuilder\` callback has an \`animation\` parameter. It's an \`Animation\` that produces values between 0 and 1. Convert the \`Animation\` into an \`Animation\` using a \`Tween\`:

<!--?code-excerpt "lib/starter.dart (step1)"?-->

\`\`\`dart transitionsBuilder: (context, animation, secondaryAnimation, child) { const begin = Offset(0.0, 1.0); const end = Offset.zero; final tween = Tween(begin: begin, end: end); final offsetAnimation = animation.drive(tween); return child; }, \`\`\` ## 3. Use an \`AnimatedWidget\` Flutter has a set of widgets extending \[\`AnimatedWidget\`]\[] that rebuild themselves when the value of the animation changes. For instance, \`SlideTransition\` takes an \`Animation\` and translates its child (using a \`FractionalTranslation\` widget) whenever the value of the animation changes. Return a \[\`SlideTransition\`]\[] with the \`Animation\` and the child widget:

<!--?code-excerpt "lib/starter.dart (step2)"?-->

\`\`\`dart transitionsBuilder: (context, animation, secondaryAnimation, child) { const begin = Offset(0.0, 1.0); const end = Offset.zero; final tween = Tween(begin: begin, end: end); final offsetAnimation = animation.drive(tween); return SlideTransition(position: offsetAnimation, child: child); }, \`\`\` ## 4. Use a \`CurveTween\` Flutter provides a selection of easing curves that adjust the rate of the animation over time. The \[\`Curves\`]\[] class provides a predefined set of commonly used curves. For example, \`Curves.easeOut\` makes the animation start quickly and end slowly. To use a \`Curve\`, create a new \[\`CurveTween\`]\[] and pass it a \`Curve\`:

<!--?code-excerpt "lib/starter.dart (step3)"?-->

\`\`\`dart const curve = Curves.ease; final curveTween = CurveTween(curve: curve); \`\`\` This new \`Tween\` still produces values from 0 to 1. In the next step, combine it with the \`Tween\` from step 2. ## 5. Combine the two \`Tween\`s To combine the tweens, use \[\`chain()\`]\[]:

<!--?code-excerpt "lib/main.dart (Tween)"?-->

\`\`\`dart const begin = Offset(0.0, 1.0); const end = Offset.zero; const curve = Curves.ease; final tween = Tween( begin: begin, end: end, ).chain(CurveTween(curve: curve)); \`\`\` Then use this tween by passing it to \`animation.drive()\`. This creates a new \`Animation\` that can be given to the \`SlideTransition\` widget:

<!--?code-excerpt "lib/main.dart (SlideTransition)"?-->

\`\`\`dart return SlideTransition(position: animation.drive(tween), child: child); \`\`\` This new \`Tween\` (or \`Animatable\`) produces \`Offset\` values by first evaluating the \`CurveTween\`, then evaluating the \`Tween\`. When the animation runs, the values are computed in this order: 1. The animation (provided to the \`transitionsBuilder\` callback) produces values from 0 to 1. 1. The \`CurveTween\` maps those values to new values between 0 and 1 based on its curve. 1. The \`Tween\` maps the \`double\` values to \`Offset\` values. Another way to create an \`Animation\` with an easing curve is to use a \[\`CurvedAnimation\`]\[]:

<!--?code-excerpt "lib/starter.dart (step4)" replace="/^\},$/}/g"?-->

\`\`\`dart transitionsBuilder: (context, animation, secondaryAnimation, child) { const begin = Offset(0.0, 1.0); const end = Offset.zero; const curve = Curves.ease; final tween = Tween(begin: begin, end: end); final curvedAnimation = CurvedAnimation(parent: animation, curve: curve); return SlideTransition( position: tween.animate(curvedAnimation), child: child, ); } \`\`\` ## Interactive example

<!--?code-excerpt "lib/main.dart"?-->

\`\`\`dartpad title="Flutter page routing hands-on example in DartPad" run="true" import 'package:flutter/material.dart'; void main() { runApp(const MaterialApp(home: Page1())); } class Page1 extends StatelessWidget { const Page1({super.key}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(), body: Center( child: ElevatedButton( onPressed: () { Navigator.of(context).push(\_createRoute()); }, child: const Text('Go!'), ), ), ); } } Route \_createRoute() { return PageRouteBuilder( pageBuilder: (context, animation, secondaryAnimation) => const Page2(), transitionsBuilder: (context, animation, secondaryAnimation, child) { const begin = Offset(0.0, 1.0); const end = Offset.zero; const curve = Curves.ease; final tween = Tween( begin: begin, end: end, ).chain(CurveTween(curve: curve)); return SlideTransition(position: animation.drive(tween), child: child); }, ); } class Page2 extends StatelessWidget { const Page2({super.key}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(), body: const Center(child: Text('Page 2')), ); } } \`\`\` \[\`AnimatedWidget\`]: https\://api.flutter.dev/flutter/widgets/AnimatedWidget-class.html \[\`Animation\`]: https\://api.flutter.dev/flutter/animation/Animation-class.html \[\`chain()\`]: https\://api.flutter.dev/flutter/animation/Animatable/chain.html \[\`Curve\`]: https\://api.flutter.dev/flutter/animation/Curve-class.html \[\`CurvedAnimation\`]: https\://api.flutter.dev/flutter/animation/CurvedAnimation-class.html \[\`Curves\`]: https\://api.flutter.dev/flutter/animation/Curves-class.html \[\`CurveTween\`]: https\://api.flutter.dev/flutter/animation/CurveTween-class.html \[\`FractionalTranslation\`]: https\://api.flutter.dev/flutter/widgets/FractionalTranslation-class.html \[\`PageRouteBuilder\`]: https\://api.flutter.dev/flutter/widgets/PageRouteBuilder-class.html \[\`Route\`]: https\://api.flutter.dev/flutter/widgets/Route-class.html \[\`SlideTransition\`]: https\://api.flutter.dev/flutter/widgets/SlideTransition-class.html \[\`Tween\`]: https\://api.flutter.dev/flutter/animation/Tween-class.html

----
url: https://docs.flutter.dev/cookbook/design/package-fonts.md
----

\# Export fonts from a package > How to export fonts from a package.

<!--?code-excerpt path-base="cookbook/design/package_fonts"?-->

Rather than declaring a font as part of an app, you can declare a font as part of a separate package. This is a convenient way to share the same font across several different projects, or for coders publishing their packages to \[pub.dev]\[]. This recipe uses the following steps: 1. Add a font to a package. 2. Add the package and font to the app. 3. Use the font. :::note Check out the \[google\_fonts]\[] package for direct access to almost 1000 open-sourced font families. ::: ## 1. Add a font to a package To export a font from a package, you need to import the font files into the \`lib\` folder of the package project. You can place font files directly in the \`lib\` folder or in a subdirectory, such as \`lib/fonts\`. In this example, assume you've got a Flutter library called \`awesome\_package\` with fonts living in a \`lib/fonts\` folder. \`\`\`plaintext awesome\_package/ lib/ awesome\_package.dart fonts/ Raleway-Regular.ttf Raleway-Italic.ttf \`\`\` ## 2. Add the package and fonts to the app Now you can use the fonts in the package by updating the \`pubspec.yaml\` in the \*app's\* root directory. ### Add the package to the app To add the \`awesome\_package\` package as a dependency, run \`flutter pub add\`: \`\`\`console $ flutter pub add awesome\_package \`\`\` ### Declare the font assets Now that you've imported the package, tell Flutter where to find the fonts from the \`awesome\_package\`. 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 flutter: fonts: - family: Raleway fonts: - asset: packages/awesome\_package/fonts/Raleway-Regular.ttf - asset: packages/awesome\_package/fonts/Raleway-Italic.ttf style: italic \`\`\` []()## 3. Use the font Use a \[\`TextStyle\`]\[] to change the appearance of text. To use package fonts, declare which font you'd like to use and which package the font belongs to.

<!--?code-excerpt "lib/main.dart (TextStyle)"?-->

\`\`\`dart child: Text( 'Using the Raleway font from the awesome\_package', style: TextStyle(fontFamily: 'Raleway'), ), \`\`\` ## Complete example ### Fonts The Raleway and RobotoMono fonts were downloaded from \[Google Fonts]\[]. ### \`pubspec.yaml\` \`\`\`yaml name: package\_fonts description: An example of how to use package fonts with Flutter dependencies: awesome\_package: flutter: sdk: flutter dev\_dependencies: flutter\_test: sdk: flutter flutter: fonts: - family: Raleway fonts: - asset: packages/awesome\_package/fonts/Raleway-Regular.ttf - asset: packages/awesome\_package/fonts/Raleway-Italic.ttf style: italic uses-material-design: true \`\`\` ### \`main.dart\`

<!--?code-excerpt "lib/main.dart"?-->

\`\`\`dart import 'package:material\_ui/material\_ui.dart'; void main() => runApp(const MyApp()); class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return const MaterialApp(title: 'Package Fonts', home: MyHomePage()); } } class MyHomePage extends StatelessWidget { const MyHomePage({super.key}); @override Widget build(BuildContext context) { return Scaffold( // The AppBar uses the app-default font. appBar: AppBar(title: const Text('Package Fonts')), body: const Center( // This Text widget uses the Raleway font. child: Text( 'Using the Raleway font from the awesome\_package', style: TextStyle(fontFamily: 'Raleway'), ), ), ); } } \`\`\` !\[Package Fonts Demo]\(/assets/images/docs/cookbook/package-fonts.png){:.site-mobile-screenshot} \[Google Fonts]: https\://fonts.google.com \[google\_fonts]: https\://pub.dev/packages/google\_fonts \[pub.dev]: https\://pub.dev \[\`TextStyle\`]: https\://api.flutter.dev/flutter/painting/TextStyle-class.html

----
url: https://docs.flutter.dev/platform-integration/ios/launch-screen.md
----

\# Adding a launch screen to your iOS app > Learn how to add a launch screen to your iOS app. \[Launch screens]\[] provide a simple initial experience while your iOS app loads. They set the stage for your application, while allowing time for the app engine to load and your app to initialize. \[Launch screens]: https\://developer.apple.com/design/human-interface-guidelines/launching#Launch-screens All apps submitted to the Apple App Store \[must provide a launch screen]\[apple-requirement] with an Xcode storyboard. ## Customize the launch screen The default Flutter template includes an Xcode storyboard named \`LaunchScreen.storyboard\` that can be customized with your own assets. By default, the storyboard displays a blank image, but you can change this. To do so, open the Flutter app's Xcode project by typing \`open ios/Runner.xcworkspace\` from the root of your app directory. Then select \`Runner/Assets.xcassets\` from the Project Navigator and drop in the desired images to the \`LaunchImage\` image set. Apple provides detailed guidance for launch screens as part of the \[Human Interface Guidelines]\[]. \[apple-requirement]: https\://developer.apple.com/documentation/xcode/specifying-your-apps-launch-screen \[Human Interface Guidelines]: https\://developer.apple.com/design/human-interface-guidelines/patterns/launching#launch-screens

----
url: https://docs.flutter.dev/platform-integration/linux/setup
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Set up Linux development](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

Learn how to set up your development environment to run, build, and deploy Flutter apps for the Linux desktop platform.

## Set up tooling

[#](#set-up-tooling)

To run and debug desktop Flutter apps on Linux, download and install the prerequisite packages.

Using your preferred package manager or mechanism, install the latest versions of the following packages:

* `clang`
* `cmake`
* `ninja-build`
* `pkg-config`
* `libgtk-3-dev`
* `libstdc++-12-dev`

On Debian-based distros with `apt-get`, such as Ubuntu, install these packages using the following commands:

```
$ sudo apt-get update -y && sudo apt-get upgrade -y
$ sudo apt-get install -y clang cmake ninja-build pkg-config libgtk-3-dev libstdc++-12-dev
```

## Validate your setup

[#](#validate-setup)

1. ### Check for toolchain issues

   To check for any issues with your Linux development setup, run the `flutter doctor` command in your preferred terminal:

   ```
   $ flutter doctor -v
   ```

   If you see any errors or tasks to complete under the **Linux toolchain** section, complete and resolve them, then run `flutter doctor -v` again to verify any changes.

2. ### Check for Linux devices

   To ensure Flutter can find and connect to your Linux device correctly, run `flutter devices` in your preferred terminal:

   ```
   $ flutter devices
   ```

   If you set everything up correctly, there should be at least one entry with the platform marked as **linux**.

3. ### Troubleshoot setup issues

   If you need help resolving any setup issues, check out [Install and setup troubleshooting](/install/troubleshoot).

   If you still have issues or questions, reach out on one of the Flutter [community](https://flutter.dev/community) channels.

## Start developing for Linux

[#](#start-developing)

Congratulations! Now that you've set up Linux desktop development for Flutter, you can continue your Flutter learning journey while testing on Linux or begin expanding integration with Linux.

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-09-10. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/platform-integration/linux/setup.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/platform-integration/linux/setup\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/platform-integration/linux/setup.md "Report an issue with this page").

----
url: https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.35.0
----

The 2.35.0 release of the Dart and Flutter DevTools includes the following changes among other general improvements. To learn more about DevTools, check out the [DevTools overview](/tools/devtools).

## General updates

[#](#general-updates)

* Changed to a single button for starting and stopping recording on the Network screen and CPU profiler screen. - [#7573](https://github.com/flutter/devtools/pull/7573)

## Inspector updates

[#](#inspector-updates)

* Add a preference for the default inspector view - [#6949](https://github.com/flutter/devtools/pull/6949)

## Memory updates

[#](#memory-updates)

* Replaced total size with reachable size in snapshot list. - [#7493](https://github.com/flutter/devtools/pull/7493)

## Debugger updates

[#](#debugger-updates)

* During a hot-restart, `pause_isolates_on_start` and only `resume` the app once breakpoints are set. - [#7234](https://github.com/flutter/devtools/pull/7234)

## Network profiler updates

[#](#network-profiler-updates)

* Added text selection in text viewer for requests and responses. - [#7596](https://github.com/flutter/devtools/pull/7596)

* Added a JSON copy experience to the JSON viewer. - [#7596](https://github.com/flutter/devtools/pull/7596)

* Fixed a bug where stopping and starting network recording listed requests that happened while not recording. - [#7626](https://github.com/flutter/devtools/pull/7626)

## Deep links tool updates

[#](#deep-links-tool-updates)

* Improve layout for narrow screens. - [#7524](https://github.com/flutter/devtools/pull/7524)
* Add error handling for missing schemes and domains - [#7559](https://github.com/flutter/devtools/pull/7559)

## VS Code Sidebar updates

[#](#vs-code-sidebar-updates)

* Added a DevTools section with a list of tools and extensions that are available without a debug session. - [#7598](https://github.com/flutter/devtools/pull/7598), [#7604](https://github.com/flutter/devtools/pull/7604)

## DevTools Extension updates

[#](#devtools-extension-updates)

* Support DevTools extensions that do not require a running app, and detect them from the user's IDE workspace. - [#7612](https://github.com/flutter/devtools/pull/7612)
* Deprecate the `DevToolsExtension.requiresRunningApplication` field in favor of the new optional `requiresConnection` field that can be added to an extension's `config.yaml` file. - [#7611](https://github.com/flutter/devtools/pull/7611), [#7602](https://github.com/flutter/devtools/pull/7602)
* Detect extensions for all types of run targets in a package. - [#7533](https://github.com/flutter/devtools/pull/7533), [#7535](https://github.com/flutter/devtools/pull/7535)

## Full commit history

[#](#full-commit-history)

To find a complete list of changes in this release, check out the [DevTools git log](https://github.com/flutter/devtools/tree/v2.35.0).

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/release-notes/release-notes-2.35.0.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.35.0\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/release-notes/release-notes-2.35.0.md "Report an issue with this page").

----
url: https://docs.flutter.dev/platform-integration/ios/launch-screen
----

[Launch screens](https://developer.apple.com/design/human-interface-guidelines/launching#Launch-screens) provide a simple initial experience while your iOS app loads. They set the stage for your application, while allowing time for the app engine to load and your app to initialize.

All apps submitted to the Apple App Store [must provide a launch screen](https://developer.apple.com/documentation/xcode/specifying-your-apps-launch-screen) with an Xcode storyboard.

## Customize the launch screen

[#](#customize-the-launch-screen)

The default Flutter template includes an Xcode storyboard named `LaunchScreen.storyboard` that can be customized with your own assets. By default, the storyboard displays a blank image, but you can change this. To do so, open the Flutter app's Xcode project by typing `open ios/Runner.xcworkspace` from the root of your app directory. Then select `Runner/Assets.xcassets` from the Project Navigator and drop in the desired images to the `LaunchImage` image set.

Apple provides detailed guidance for launch screens as part of the [Human Interface Guidelines](https://developer.apple.com/design/human-interface-guidelines/patterns/launching#launch-screens).

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/platform-integration/ios/launch-screen.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/platform-integration/ios/launch-screen\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/platform-integration/ios/launch-screen.md "Report an issue with this page").

----
url: https://docs.flutter.dev/flutter-for/react-native-devs.md
----

\# Flutter for React Native developers > Learn how to apply React Native developer knowledge when building Flutter apps.

<!--?code-excerpt path-base="get-started/flutter-for/react_native_devs"?-->

This document is for React Native (RN) developers looking to apply their existing RN knowledge to build mobile apps with Flutter. If you understand the fundamentals of the RN framework then you can use this document as a way to get started learning Flutter development. This document can be used as a cookbook by jumping around and finding questions that are most relevant to your needs. ## Introduction to Dart for JavaScript Developers (ES6) Like React Native, Flutter uses reactive-style views. However, while RN transpiles to native widgets, Flutter compiles all the way to native code. Flutter controls each pixel on the screen, which avoids performance problems caused by the need for a JavaScript bridge. Dart is an easy language to learn and offers the following features: \* Provides an open-source, scalable programming language for building web, server, and mobile apps. \* Provides an object-oriented, single inheritance language that uses a C-style syntax that is AOT-compiled into native. \* Transcompiles optionally into JavaScript. \* Supports interfaces and abstract classes. A few examples of the differences between JavaScript and Dart are described below. ### Entry point JavaScript doesn't have a pre-defined entry function—you define the entry point. \`\`\`js // JavaScript function startHere() { // Can be used as entry point } \`\`\` In Dart, every app must have a top-level \`main()\` function that serves as the entry point to the app.

<!--?code-excerpt "lib/main.dart (main)"?-->

\`\`\`dart /// Dart void main() {} \`\`\` ### Printing to the console To print to the console in Dart, use \`print()\`. \`\`\`js // JavaScript console.log('Hello world!'); \`\`\`

<!--?code-excerpt "lib/main.dart (print)"?-->

\`\`\`dart /// Dart print('Hello world!'); \`\`\` ### Variables Dart is type safe—it uses a combination of static type checking and runtime checks to ensure that a variable's value always matches the variable's static type. Although types are mandatory, some type annotations are optional because Dart performs type inference. #### Creating and assigning variables In JavaScript, variables cannot be typed. In \[Dart]\[], variables must either be explicitly typed or the type system must infer the proper type automatically. \`\`\`js // JavaScript let name = 'JavaScript'; \`\`\`

<!--?code-excerpt "lib/main.dart (variables)"?-->

\`\`\`dart /// Dart /// Both variables are acceptable. String name = 'dart'; // Explicitly typed as a \[String]. var otherName = 'Dart'; // Inferred \[String] type. \`\`\` For more information, see \[Dart's Type System]\[]. #### Default value In JavaScript, uninitialized variables are \`undefined\`. In Dart, uninitialized variables have an initial value of \`null\`. Because numbers are objects in Dart, even uninitialized variables with numeric types have the value \`null\`. :::note As of 2.12, Dart supports \[Sound Null Safety]\[], all underlying types are non-nullable by default, which must be initialized as a non-nullable value. ::: \`\`\`js // JavaScript let name; // == undefined \`\`\`

<!--?code-excerpt "lib/main.dart (null)"?-->

\`\`\`dart // Dart var name; // == null; raises a linter warning int? x; // == null \`\`\` For more information, see the documentation on \[variables]\[]. ### Checking for null or zero In JavaScript, values of 1 or any non-null objects are treated as \`true\` when using the \`==\` comparison operator. \`\`\`js // JavaScript let myNull = null; if (!myNull) { console.log('null is treated as false'); } let zero = 0; if (!zero) { console.log('0 is treated as false'); } \`\`\` In Dart, only the boolean value \`true\` is treated as true.

<!--?code-excerpt "lib/main.dart (true)"?-->

\`\`\`dart /// Dart var myNull = potentiallyNull(); if (myNull == null) { print('use "== null" to check null'); } var zero = 0; if (zero == 0) { print('use "== 0" to check zero'); } \`\`\` ### Functions Dart and JavaScript functions are generally similar. The primary difference is the declaration. \`\`\`js // JavaScript function fn() { return true; } \`\`\`

<!--?code-excerpt "lib/main.dart (function)"?-->

\`\`\`dart /// Dart /// You can explicitly define the return type. bool fn() { return true; } \`\`\` For more information, see the documentation on \[functions]\[]. ### Asynchronous programming #### Futures Like JavaScript, Dart supports single-threaded execution. In JavaScript, the Promise object represents the eventual completion (or failure) of an asynchronous operation and its resulting value. Dart uses \[\`Future\`]\[] objects to handle this. \`\`\`js // JavaScript class Example { \_getIPAddress() { const url = 'https\://httpbin.org/ip'; return fetch(url) .then(response => response.json()) .then(responseJson => { const ip = responseJson.origin; return ip; }); } } function main() { const example = new Example(); example .\_getIPAddress() .then(ip => console.log(ip)) .catch(error => console.error(error)); } main(); \`\`\`

<!--?code-excerpt "lib/futures.dart"?-->

\`\`\`dart // Dart import 'dart:convert'; import 'package:http/http.dart' as http; class Example { Future \_getIPAddress() { final url = Uri.https('httpbin.org', '/ip'); return http.get(url).then((response) { final ip = jsonDecode(response.body)\['origin'] as String; return ip; }); } } void main() { final example = Example(); example .\_getIPAddress() .then((ip) => print(ip)) .onError((error, \_) => print(error)); } \`\`\` For more information, see the documentation on \[\`Future\`]\[] objects. #### \`async\` and \`await\` The \`async\` function declaration defines an asynchronous function. In JavaScript, the \`async\` function returns a \`Promise\`. The \`await\` operator is used to wait for a \`Promise\`. \`\`\`js // JavaScript class Example { async function \_getIPAddress() { const url = 'https\://httpbin.org/ip'; const response = await fetch(url); const json = await response.json(); const data = json.origin; return data; } } async function main() { const example = new Example(); try { const ip = await example.\_getIPAddress(); console.log(ip); } catch (error) { console.error(error); } } main(); \`\`\` In Dart, an \`async\` function returns a \`Future\`, and the body of the function is scheduled for execution later. The \`await\` operator is used to wait for a \`Future\`.

<!--?code-excerpt "lib/async.dart"?-->

\`\`\`dart // Dart import 'dart:convert'; import 'package:http/http.dart' as http; class Example { Future \_getIPAddress() async { final url = Uri.https('httpbin.org', '/ip'); final response = await http.get(url); final ip = jsonDecode(response.body)\['origin'] as String; return ip; } } /// An async function returns a \`Future\`. /// It can also return \`void\`, unless you use /// the \`avoid\_void\_async\` lint. In that case, /// return \`Future\`. void main() async { final example = Example(); try { final ip = await example.\_getIPAddress(); print(ip); } catch (error) { print(error); } } \`\`\` For more information, see the documentation for \[async and await]\[]. ## The basics ### How do I create a Flutter app? To create an app using React Native, you would run \`create-react-native-app\` from the command line. \`\`\`console $ create-react-native-app \`\`\` To create an app in Flutter, do one of the following: \* Use an IDE with the Flutter and Dart plugins installed. \* Use the \`flutter create\` command from the command line. Make sure that the Flutter SDK is in your PATH. \`\`\`console $ flutter create \`\`\` For more information, see \[Getting started]\[], which walks you through creating a button-click counter app. Creating a Flutter project builds all the files that you need to run a sample app on both Android and iOS devices. ### How do I run my app? In React Native, you would run \`npm run\` or \`yarn run\` from the project directory. You can run Flutter apps in a couple of ways: \* Use the "run" option in an IDE with the Flutter and Dart plugins. \* Use \`flutter run\` from the project's root directory. Your app runs on a connected device, the iOS simulator, or the Android emulator. For more information, see the Flutter \[Getting started]\[] documentation. ### How do I import widgets? In React Native, you need to import each required component. \`\`\`js // React Native import React from 'react'; import { StyleSheet, Text, View } from 'react-native'; \`\`\` In Flutter, to use widgets from the Material Design library, import the \`material.dart\` package. To use iOS style widgets, import the Cupertino library. To use a more basic widget set, import the Widgets library. Or, you can write your own widget library and import that.

<!--?code-excerpt "lib/imports.dart (imports)"?-->

\`\`\`dart import 'package:cupertino\_ui/cupertino\_ui.dart'; import 'package:flutter/widgets.dart'; import 'package:material\_ui/material\_ui.dart'; import 'package:my\_widgets/my\_widgets.dart'; \`\`\` Whichever widget package you import, Dart pulls in only the widgets that are used in your app. For more information, see the \[Flutter Widget Catalog]\[]. ### What is the equivalent of the React Native "Hello world!" app in Flutter? In React Native, the \`HelloWorldApp\` class extends \`React.Component\` and implements the render method by returning a view component. \`\`\`js // React Native import React from 'react'; import { StyleSheet, Text, View } from 'react-native'; const App = () => { return ( Hello world! ); }; const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: '#fff', alignItems: 'center', justifyContent: 'center' } }); export default App; \`\`\` In Flutter, you can create an identical "Hello world!" app using the \`Center\` and \`Text\` widgets from the core widget library. The \`Center\` widget becomes the root of the widget tree and has one child, the \`Text\` widget.

<!--?code-excerpt "lib/hello_world.dart"?-->

\`\`\`dart // Flutter import 'package:material\_ui/material\_ui.dart'; void main() { runApp( const Center( child: Text('Hello, world!', textDirection: TextDirection.ltr), ), ); } \`\`\` The following images show the Android and iOS UI for the basic Flutter "Hello world!" app.

Android

iOS

Now that you've seen the most basic Flutter app, the next section shows how to take advantage of Flutter's rich widget libraries to create a modern, polished app. ### How do I use widgets and nest them to form a widget tree? In Flutter, almost everything is a widget. Widgets are the basic building blocks of an app's user interface. You compose widgets into a hierarchy, called a widget tree. Each widget nests inside a parent widget and inherits properties from its parent. Even the application object itself is a widget. There is no separate "application" object. Instead, the root widget serves this role. A widget can define: \* A structural element—like a button or menu \* A stylistic element—like a font or color scheme \* An aspect of layout—like padding or alignment The following example shows the "Hello world!" app using widgets from the Material library. In this example, the widget tree is nested inside the \`MaterialApp\` root widget.

<!--?code-excerpt "lib/widget_tree.dart"?-->

\`\`\`dart // Flutter import 'package:material\_ui/material\_ui.dart'; void main() => runApp(const MyApp()); class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( title: 'Welcome to Flutter', home: Scaffold( appBar: AppBar(title: const Text('Welcome to Flutter')), body: const Center(child: Text('Hello world')), ), ); } } \`\`\` The following images show "Hello world!" built from Material Design widgets. You get more functionality for free than in the basic "Hello world!" app.

Android

iOS

When writing an app, you'll use two types of widgets: \[\`StatelessWidget\`]\[] or \[\`StatefulWidget\`]\[]. A \`StatelessWidget\` is just what it sounds like—a widget with no state. A \`StatelessWidget\` is created once, and never changes its appearance. A \`StatefulWidget\` dynamically changes state based on data received, or user input. The important difference between stateless and stateful widgets is that \`StatefulWidget\`s have a \`State\` object that stores state data and carries it over across tree rebuilds, so it's not lost. In simple or basic apps it's easy to nest widgets, but as the code base gets larger and the app becomes complex, you should break deeply nested widgets into functions that return the widget or smaller classes. Creating separate functions and widgets allows you to reuse the components within the app. ### How do I create reusable components? In React Native, you would define a function (or a class) to create a reusable component and then use \`props\` methods to set or return properties and values of the selected elements. In the example below, the \`CustomCard\` function is defined and then used inside a parent component. \`\`\`js // React Native const CustomCard = ({ index, onPress }) => { return ( Card {index}

----
url: https://docs.flutter.dev/learn/pathway/tutorial/adaptive-layout
----

Learn how to create layouts that adapt to different screen widths.

fit\_screen Create responsive layouts with LayoutBuilder

devices Detect screen size to choose different layouts

view\_sidebar Build a sidebar and detail layout for large screens

***

## Steps

1### Introductionkeyboard\_arrow\_up

Modern apps need to work well on screens of all sizes. On this page, you'll learn how to create layouts that adapt to different screen widths. This app shows a sidebar on large screens and a navigation-based UI on small screens. Specifically, this app handles two screen sizes:

* **Large screens (tablets, desktop)**: Shows contact groups and contact details side-by-side.
* **Small screens (phones)**: Uses navigation to move between contact groups and details.

2### Create the contact groups pagekeyboard\_arrow\_up

First, create the basic structure of the `ContactGroupsPage` widget for your contact groups screen. Create `lib/screens/contact_groups.dart` and add the following basic structure:

dart

```
import 'package:cupertino_ui/cupertino_ui.dart';
​
class ContactGroupsPage extends StatelessWidget {
  const ContactGroupsPage({super.key});
​
  @override
  Widget build(BuildContext context) {
    return const CupertinoPageScaffold(
      backgroundColor: CupertinoColors.extraLightBackgroundGray,
      child: Center(child: Text('Contact Groups will go here')),
    );
  }
}
```

3### Create the contacts pagekeyboard\_arrow\_up

Similarly, create `lib/screens/contacts.dart` to eventually display individual contacts:

dart

```
import 'package:cupertino_ui/cupertino_ui.dart';
​
class ContactListsPage extends StatelessWidget {
  const ContactListsPage({super.key, required this.listId});
​
  final int listId;
​
  @override
  Widget build(BuildContext context) {
    return const CupertinoPageScaffold(
      backgroundColor: CupertinoColors.extraLightBackgroundGray,
      child: Center(child: Text('Lists of contacts will go here')),
    );
  }
}
```

The `ContactListsPage` widget and `ContactGroupsPage` widget are placeholder pages that are needed to implement the adaptive layout widget, which you'll do next.

4### Build the adaptive layout foundationkeyboard\_arrow\_up

Create `lib/screens/adaptive_layout.dart` and start with the following basic structure:

dart

```
import 'package:cupertino_ui/cupertino_ui.dart';
​
import 'contact_groups.dart';
​
class AdaptiveLayout extends StatefulWidget {
  const AdaptiveLayout({super.key});
​
  @override
  State<AdaptiveLayout> createState() => _AdaptiveLayoutState();
}
​
class _AdaptiveLayoutState extends State<AdaptiveLayout> {
  @override
  Widget build(BuildContext context) {
    return const ContactGroupsPage(); // Temporary placeholder
  }
}
```

This is a `StatefulWidget` because the adaptive layout eventually manages which contact group is currently selected.

Next, add the screen size detection logic to `lib/screens/adaptive_layout.dart`:

dart

```
import 'package:cupertino_ui/cupertino_ui.dart';
​
import 'contact_groups.dart';
​
const largeScreenMinWidth = 600;
​
class AdaptiveLayout extends StatefulWidget {
  const AdaptiveLayout({super.key});
​
  @override
  State<AdaptiveLayout> createState() => _AdaptiveLayoutState();
}
​
class _AdaptiveLayoutState extends State<AdaptiveLayout> {
  @override
  Widget build(BuildContext context) {
    return LayoutBuilder(
      builder: (context, constraints) {
        final isLargeScreen = constraints.maxWidth > largeScreenMinWidth;
​
        if (isLargeScreen) {
          return const Text('Large screen layout'); // Temporary
        } else {
          return const ContactGroupsPage();
        }
      },
    );
  }
}
```

The `LayoutBuilder` widget provides information about the parent's size constraints. In the `builder` callback, you receive a`BoxConstraints` object that tells you the maximum available width and height.

By checking if `constraints.maxWidth > largeScreenMinWidth`, you can decide which layout to show. The 600-pixel threshold is a common breakpoint that separates phone-sized screens from tablet-sized screens.

5### Update the main appkeyboard\_arrow\_up

Update `main.dart` to use the adaptive layout, so you can see your changes:

dart

```
import 'package:cupertino_ui/cupertino_ui.dart';
​
import 'data/contact_group.dart';
import 'screens/adaptive_layout.dart';
​
final contactGroupsModel = ContactGroupsModel();
​
void main() {
  runApp(const RolodexApp());
}
​
class RolodexApp extends StatelessWidget {
  const RolodexApp({super.key});
​
  @override
  Widget build(BuildContext context) {
    return const CupertinoApp(
      title: 'Rolodex',
      theme: CupertinoThemeData(
        barBackgroundColor: CupertinoDynamicColor.withBrightness(
          color: Color(0xFFF9F9F9),
          darkColor: Color(0xFF1D1D1D),
        ),
      ),
      home: AdaptiveLayout(),
    );
  }
}
```

If you're running in Chrome, you can resize the browser window to see layout changes.

6### Add list selection functionalitykeyboard\_arrow\_up

The large screen layout needs to track which contact group is selected. Update the state object in `lib/screens/adaptive_layout.dart` with the following code:

dart

```
​
import 'package:cupertino_ui/cupertino_ui.dart';
​
import 'contact_groups.dart';
​
const largeScreenMinWidth = 600;
​
class AdaptiveLayout extends StatefulWidget {
  const AdaptiveLayout({super.key});
​
  @override
  State<AdaptiveLayout> createState() => _AdaptiveLayoutState();
}
​
class _AdaptiveLayoutState extends State<AdaptiveLayout> {
  int selectedListId = 0;
​
  void _onContactListSelected(int listId) {
    setState(() {
      selectedListId = listId;
    });
  }
​
  @override
  Widget build(BuildContext context) {
    return LayoutBuilder(
      builder: (context, constraints) {
        final isLargeScreen = constraints.maxWidth > largeScreenMinWidth;
​
        if (isLargeScreen) {
          return const Text('Large screen layout');
        } else {
          return const ContactGroupsPage();
        }
      },
    );
  }
}
```

The `selectedListId` variable tracks the currently selected contact group, and `_onContactListSelected` updates this value when the user makes a choice.

7### Build the large screen layoutkeyboard\_arrow\_up

Now, implement the side-by-side layout for large screens in `lib/screens/adaptive_layout.dart`. First, replace the temporary text with a widget that contains the proper layout.

dart

```
​
import 'package:cupertino_ui/cupertino_ui.dart';
​
import 'contact_groups.dart';
​
const largeScreenMinWidth = 600;
​
class AdaptiveLayout extends StatefulWidget {
  const AdaptiveLayout({super.key});
​
  @override
  State<AdaptiveLayout> createState() => _AdaptiveLayoutState();
}
​
class _AdaptiveLayoutState extends State<AdaptiveLayout> {
  int selectedListId = 0;
​
  void _onContactListSelected(int listId) {
    setState(() {
      selectedListId = listId;
    });
  }
​
  @override
  Widget build(BuildContext context) {
    return LayoutBuilder(
      builder: (context, constraints) {
        final isLargeScreen = constraints.maxWidth > largeScreenMinWidth;
​
        if (isLargeScreen) {
          return _buildLargeScreenLayout();
        } else {
          // For small screens, use the original, navigation-style approach.
          return const ContactGroupsPage();
        }
      },
    );
  }
​
  Widget _buildLargeScreenLayout() {
    return const CupertinoPageScaffold(
      backgroundColor: CupertinoColors.extraLightBackgroundGray,
      child: SafeArea(child: Row(children: [Text('Sidebar'), Text('Details')])),
    );
  }
}
```

The large screen layout uses a `Row` to place the sidebar and details side-by-side. `SafeArea` ensures that the content doesn't overlap with system UI elements like the status bar.

Now, set the sizes of the two panels and add a visual divider in `lib/screens/adaptive_layout.dart`:

dart

```
Widget _buildLargeScreenLayout() {
  return CupertinoPageScaffold(
    backgroundColor: CupertinoColors.extraLightBackgroundGray,
    child: SafeArea(
      child: Row(
        children: [
          const SizedBox(width: 320, child: Text('Sidebar placeholder')),
          Container(width: 1, color: CupertinoColors.separator),
          const Expanded(child: Text('Details placeholder')),
        ],
      ),
    ),
  );
}
```

This layout creates the following:

* A fixed-width sidebar (320 pixels) for contact groups.
* A 1-pixel divider between the panels.
* A details panel that uses an `Expanded` widget to take the remaining space.

8### Test the adaptive layoutkeyboard\_arrow\_up

Hot reload your app and test the responsive behavior. If you're running in Chrome, you can resize the browser window to see the layout change:

* **Wide window (> 600px)**: Shows placeholder text for the sidebar and details side-by-side.
* **Narrow window (< 600px)**: Shows only the contact groups page.

Both the sidebar and main content area show placeholder text for now.

In the next lesson, you'll implement slivers to fill in the contact list content.

9### Reviewkeyboard\_arrow\_up

fit\_screen Created responsive layouts with LayoutBuilder keyboard\_arrow\_up

`LayoutBuilder` provides the parent's size constraints in its builder callback. By checking `constraints.maxWidth`, you can decide which layout to show based on available space.

devices Detected screen size to choose different layouts keyboard\_arrow\_up

You used a 600-pixel breakpoint to distinguish phone-sized screens from tablet-sized screens. This common threshold helps your app adapt its UI to provide the best experience on each device.

view\_sidebar Built a sidebar and detail layout for large screens keyboard\_arrow\_up

On large screens, you displayed a fixed-width sidebar and an `Expanded` detail panel side-by-side using a `Row`. This classic pattern maximizes screen real estate on tablets and desktops.

10### Test yourselfkeyboard\_arrow\_up

### Adaptive Layout Quiz

1 / 2

**What information does LayoutBuilder provide to its builder callback?**

1. The device's operating system and screen orientation.

   Not quite.

   LayoutBuilder provides size constraints, not OS or orientation info.

2. The parent's size constraints, including maximum width and height.

   That's right!

   LayoutBuilder's builder receives BoxConstraints that tell you the available space from the parent.

3. The current theme colors and typography.

   Not quite.

   Theme data comes from Theme.of(context), not LayoutBuilder.

4. The number of child widgets in the tree.

   Not quite.

   LayoutBuilder provides layout constraints, not widget tree information.

**In a large screen layout, which widget can be used to place a sidebar and details panel side-by-side?**

1. Column

   Not quite.

   Column arranges widgets vertically, not side-by-side.

2. Row

   That's right!

   Row arranges its children horizontally, making it ideal for placing a sidebar and details panel side-by-side.

3. Stack

   Not quite.

   Stack overlaps widgets on top of each other, not side-by-side.

4. ListView

   Not quite.

   ListView is for scrollable lists, not for side-by-side layout.

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-09-29. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/learn/pathway/tutorial/adaptive-layout.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/learn/pathway/tutorial/adaptive-layout\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/learn/pathway/tutorial/adaptive-layout.md "Report an issue with this page").

----
url: https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.38.0.md
----

\# DevTools 2.38.0 release notes > Release notes for Dart and Flutter DevTools version 2.38.0. The 2.38.0 release of the Dart and Flutter DevTools includes the following changes among other general improvements. To learn more about DevTools, check out the \[DevTools overview]\(/tools/devtools/overview). ## Performance updates \* Renamed the "Track" builds, paints, and layouts settings to "Trace" builds, paints, and layouts. - \[#8084]\(https\://github.com/flutter/devtools/pull/8084) \* Renamed the "Track widget build counts" setting to "Count widget builds". - \[#8084]\(https\://github.com/flutter/devtools/pull/8084) ## Debugger updates \* Added recommendation to debug code from an IDE, with links to IDE instructions. - \[#8085]\(https\://github.com/flutter/devtools/pull/8085) ## Network profiler updates \* Added support to export network requests as a HAR file (thanks to @hrajwade96!). - \[#7970]\(https\://github.com/flutter/devtools/pull/7970) ## DevTools Extension updates \* Fixed an issue where extensions did not load with the proper theme when embedded in an IDE. - \[#8034]\(https\://github.com/flutter/devtools/pull/8034) \* Added an API for copying text to clipboard by proxy of the parent DevTools web app, which has workarounds for copy issues when embedded inside an IDE. - \[#8130]\(https\://github.com/flutter/devtools/pull/8130) ## Full commit history To find a complete list of changes in this release, check out the \[DevTools git log]\(https\://github.com/flutter/devtools/tree/v2.38.0).

----
url: https://docs.flutter.dev/deployment/android.md
----

\# Build and release an Android app > How to prepare for and release an Android app to the Play store. To test an app, you can use \`flutter run\` at the command line, or the \*\*Run\*\* and \*\*Debug\*\* options in your IDE. When you're ready to prepare a \_release\_ version of your app, for example to \[publish to the Google Play Store]\[play], this page can help. Before publishing, you might want to put some finishing touches on your app. This guide explains how to perform the following tasks: \* \[Add a launcher icon]\(#add-a-launcher-icon) \* \[Enable Material Components]\(#enable-material-components) \* \[Sign the app]\(#sign-the-app) \* \[Shrink your code with R8]\(#shrink-your-code-with-r8) \* \[Enable multidex support]\(#enable-multidex-support) \* \[Review the app manifest]\(#review-the-app-manifest) \* \[Review the build configuration]\(#review-the-gradle-build-configuration) \* \[Build the app for release]\(#build-the-app-for-release) \* \[Publish to the Google Play Store]\(#publish-to-the-google-play-store) \* \[Update the app's version number]\(#update-the-apps-version-number) \* \[Android release FAQ]\(#android-release-faq) :::note Throughout this page, \`\[project]\` refers to the directory that contains your application. While following these instructions, substitute \`\[project]\` with your app's directory. ::: \[play]: https\://developer.android.com/distribute ## Add a launcher icon When a new Flutter app is created, it has a default launcher icon. To learn about customizing this icon, check out the \[flutter\_launcher\_icons]\[] package. Alternatively, you can do it manually using the following steps: 1. Review the Android \[adaptive icon guidelines]\[adaptive-icons] for icon design and sizing specifications. 1. In the \`\[project]/android/app/src/main/res/\` directory, place your icon files in folders named using \[configuration qualifiers]\[config-qual]. The default \`mipmap-\` folders demonstrate the correct naming convention. 1. In \`AndroidManifest.xml\`, update the \[\`application\`]\[applicationtag] tag's \`android:icon\` attribute to reference icons from the previous step (for example, \`/android/app/build.gradle.kts\`: \`\`\`kotlin dependencies { // ... implementation("com.google.android.material:material:") // ... } \`\`\` \`\`\`groovy dependencies { // ... implementation 'com.google.android.material:material:' // ... } \`\`\` To find out the latest version, visit \[Google Maven]\[maven-material]. 1. Set the light theme in \`/android/app/src/main/res/values/styles.xml\`: \`\`\`xml diff -

----
url: https://docs.flutter.dev/ui/accessibility/web-accessibility.md
----

\# Web accessibility > Information about web accessibility ## Background Flutter supports web accessibility by translating its internal Semantics tree into an accessible HTML DOM structure that screen readers can understand. Since Flutter renders its UI on a single canvas, it needs a special layer to expose the UI's meaning and structure to web browsers. ## Opt-in web accessibility ### Invisible button For performance reasons, Flutter's web accessibility is not on by default. To turn on accessibility, the user needs to press an invisible button with \`aria-label="Enable accessibility"\`. After pressing the button, the DOM tree will reflect all accessibility information for the widgets. ### Turn on accessibility mode in code An alternative approach is to turn on accessibility mode by adding the following code when running an app. \`\`\`dart import 'package:flutter/semantics.dart'; void main() { runApp(const MyApp()); if (kIsWeb) { SemanticsBinding.instance.ensureSemantics(); } } \`\`\` ## Enhancing Accessibility with Semantic Roles ### What are Semantic Roles? Semantic roles define the purpose of a UI element, helping screen readers and other assistive tools interpret and present your application effectively to users. For example, a role can indicate if a widget is a button, a link, to users. For example, a role can indicate whether a widget is a button, a link, a heading, a slider, or part of a table. While Flutter's standard widgets often provide these semantics automatically, a custom component without a clearly defined role can be incomprehensible to a screen reader user. By assigning appropriate roles, you ensure that: \* Screen readers can announce the type and purpose of elements correctly. \* Users can navigate your application more effectively using assistive technologies. \* Your application adheres to web accessibility standards, improving usability. ### Using \`SemanticsRole\` in Flutter for web Flutter provides the \[\`Semantics\` widget]\[] with the \[\`SemanticsRole\` enum]\[] to allow developers to assign specific roles to their widgets. When your Flutter web app is rendered, these Flutter-specific roles are translated into corresponding ARIA roles in the web page's HTML structure. \[\`Semantics\` widget]: https\://api.flutter.dev/flutter/widgets/Semantics-class.html \[\`SemanticsRole\` enum]: https\://api.flutter.dev/flutter/dart-ui/SemanticsRole.html \*\*1. Automatic Semantics from Standard Widgets\*\* Many standard Flutter widgets, like \`TabBar\`, \`MenuAnchor\`, and \`Table\`, automatically include semantic information along with their roles. Whenever possible, prefer using these standard widgets as they handle many accessibility aspects out-of-the-box. \*\*2. Explicitly adding or overriding roles\*\* For custom components or when the default semantics aren't sufficient, use the \`Semantics\` widget to define the role: Here's an example of how you might explicitly define a list and its items: \`\`\`dart import 'package:material\_ui/material\_ui.dart'; import 'package:flutter/semantics.dart'; class MyCustomListWidget extends StatelessWidget { const MyCustomListWidget({Key? key}) : super(key: key); @override Widget build(BuildContext context) { // This example shows how to explicitly assign list and listitem roles // when building a custom list structure. return Semantics( role: SemanticsRole.list, explicitChildNodes: true, child: Column( children: \[ Semantics( role: SemanticsRole.listItem, child: const Padding( padding: EdgeInsets.all(8.0), child: Text('Content of the first custom list item.'), ), ), Semantics( role: SemanticsRole.listItem, child: const Padding( padding: EdgeInsets.all(8.0), child: Text('Content of the second custom list item.'), ), ), ], ), ); } } \`\`\`

----
url: https://docs.flutter.dev/learn/pathway/tutorial/slivers.md
----

\# Advanced scrolling and slivers > Learn how to implement performant scrolling with slivers.

<!--?code-excerpt replace="/ *\/\/\s+ignore_for_file:[^\n]+\n//g;"?-->

In this lesson, you'll learn about slivers, which are special widgets that can take advantage of Flutter's powerful and composable scrolling system. Slivers enable you to create sophisticated scroll effects, including collapsible headers, search integration, and custom scroll behaviors. By the end of this section, you'll understand how to use \`CustomScrollView\`, create navigation bars that collapse, and organize content in scrollable sections. title: What you'll accomplish items: - title: Understand slivers and how they differ from widgets icon: view\_day - title: Build scrollable layouts with CustomScrollView icon: unfold\_more - title: Create collapsible navigation bars with search icon: search - title: Organize contacts in alphabetized sections icon: sort\_by\_alpha --- ### Slivers and widgets Slivers are scrollable areas that can be composed together in a \`CustomScrollView\` or other scroll views. Think of slivers as building blocks that each contribute a portion of the overall scrollable content. While slivers and widgets are both fundamental Flutter concepts, they serve different purposes and aren't interchangeable. - \*\*Widgets\*\* are general UI building blocks that can be used anywhere in your widget tree. - \*\*Slivers\*\* are specialized widgets designed specifically for scrollable layouts and have some constraints: - Slivers can \*\*only\*\* be direct children of scroll views, such as \`CustomScrollView\` and \`NestedScrollView\`. - Some scroll views \*\*only\*\* accept slivers as children. You can't pass regular widgets to \`CustomScrollView\.slivers\`. - To use regular widgets within a sliver context, wrap them in \`SliverToBoxAdapter\` or \`SliverFillRemaining\`. This architectural separation allows Flutter to optimize scrolling performance while it maintains clear boundaries between different types of UI components. ### Add a basic sliver structure to contact groups First, replace the placeholder content in your contact groups page. To avoid duplicating code between the phone layout and the tablet sidebar, you can create a private, reusable widget. Update \`lib/screens/contact\_groups.dart\` by adding \`\_ContactGroupsView\` to the bottom of the file.

<!--?code-excerpt "fwe/rolodex/lib/step3_slivers/screens/contact_groups_v1.dart (contact_groups_view)"?-->

\`\`\`dart import 'package:cupertino\_ui/cupertino\_ui.dart'; import '../data/contact\_group.dart'; import '../main.dart'; class ContactGroupsPage extends StatelessWidget { const ContactGroupsPage({super.key}); @override Widget build(BuildContext context) { return \_ContactGroupsView( selectedListId: 0, onListSelected: (list) { debugPrint(list.toString()); }, ); } } // ··· class \_ContactGroupsView extends StatelessWidget { const \_ContactGroupsView({required this.onListSelected, this.selectedListId}); final int? selectedListId; final void Function(ContactGroup) onListSelected; @override Widget build(BuildContext context) { return CupertinoPageScaffold( backgroundColor: CupertinoColors.extraLightBackgroundGray, child: CustomScrollView( slivers: \[ const CupertinoSliverNavigationBar(largeTitle: Text('Lists')), SliverFillRemaining( child: ValueListenableBuilder>( valueListenable: contactGroupsModel.listsNotifier, builder: (context, contactLists, child) { return CupertinoListSection.insetGrouped( header: const Text('iPhone'), children: \[ for (final ContactGroup contactList in contactLists) CupertinoListTile( title: Text(contactList.label), onTap: () => onListSelected(contactList), ), ], ); }, ), ), ], ), ); } } \`\`\` This private widget contains the shared UI for displaying the list of contact groups. On small screens, it will be used as a page, and on large screens it will be used to fill the left column. This widget introduces several slivers: - \`CupertinoSliverNavigationBar\`: An opinionated navigation bar that collapses as the page scrolls. - \`SliverList\`: A scrollable list of items. - \`SliverFillRemaining\`: A sliver that takes up the remaining space in the scroll area, and whose child is a non-sliver widget. It accepts a callback function, \`onListSelected\`, to handle taps, which makes it adaptable for both navigation and sidebar selection. Now, update \`ContactGroupsPage\` in \`lib/screens/contact\_groups.dart\` to use your new \`\_ContactGroupsView\` widget:

<!--?code-excerpt "fwe/rolodex/lib/step3_slivers/screens/contact_groups_v1.dart (contact_groups_page)"?-->

\`\`\`dart class ContactGroupsPage extends StatelessWidget { const ContactGroupsPage({super.key}); @override Widget build(BuildContext context) { return \_ContactGroupsView( selectedListId: 0, onListSelected: (list) { debugPrint(list.toString()); }, ); } } \`\`\` This structure keeps the \`ContactGroupsPage\` clean and focused on its primary responsibility: navigation, which you'll learn about in the next section of this tutorial. ### Enhance the list with icons and visual elements Now, add icons and contact counts to make the list more informative. Add this \`\_buildTrailing\` helper method to your \`\_ContactGroupsView\` class in \`lib/screens/contact\_groups.dart\`:

<!--?code-excerpt "fwe/rolodex/lib/step3_slivers/screens/contact_groups.dart (build_trailing)"?-->

\`\`\`dart Widget \_buildTrailing(List contacts, BuildContext context) { final TextStyle style = CupertinoTheme.of(context).textTheme.textStyle .copyWith(color: CupertinoColors.systemGrey); return Row( mainAxisSize: MainAxisSize.min, children: \[ Text(contacts.length.toString(), style: style), const Icon( CupertinoIcons.forward, color: CupertinoColors.systemGrey3, size: 18, ), ], ); } \`\`\` This helper creates the trailing content for each list item. It shows the contact count and a forward arrow. Now, update the \`CupertinoListSection\` in \`\_ContactGroupsView\` to use icons and the trailing helper. Update the code within the \`ValueListenableBuilder.builder\` callback in the \`build\` method:

<!--?code-excerpt "fwe/rolodex/lib/step3_slivers/screens/contact_groups.dart (cupertino_list_section)"?-->

\`\`\`dart child: ValueListenableBuilder>( valueListenable: contactGroupsModel.listsNotifier, builder: (context, contactLists, child) { const groupIcon = Icon( CupertinoIcons.group, weight: 900, size: 32, ); const pairIcon = Icon( CupertinoIcons.person\_2, weight: 900, size: 24, ); return CupertinoListSection.insetGrouped( header: const Text('iPhone'), children: \[ for (final ContactGroup contactList in contactLists) CupertinoListTile( leading: contactList.id == 0 ? groupIcon : pairIcon, title: Text(contactList.label), trailing: \_buildTrailing(contactList.contacts, context), onTap: () => onListSelected(contactList), ), ], ); }, ), \`\`\` The updated code now shows icons that differentiate between the main "All iPhone" group and user-created groups, along with contact counts and navigation indicators. ### Create advanced scrolling for contacts Next, you'll implement the contacts list page. In the next lesson, you'll implement navigation for small screens. To see your progress on the contacts list page in the meantime, first update \`lib/screens/adaptive\_layout.dart\` to display the contacts list page:

<!--?code-excerpt "fwe/rolodex/lib/step3_slivers/screens/adaptive_layout.dart"?-->

\`\`\`dart import 'package:cupertino\_ui/cupertino\_ui.dart'; import 'contacts.dart'; const largeScreenMinWidth = 600; class AdaptiveLayout extends StatefulWidget { const AdaptiveLayout({super.key}); @override State createState() => \_AdaptiveLayoutState(); } class \_AdaptiveLayoutState extends State { int selectedListId = 0; void \_onContactListSelected(int listId) { setState(() { selectedListId = listId; }); } @override Widget build(BuildContext context) { return LayoutBuilder( builder: (context, constraints) { final isLargeScreen = constraints.maxWidth > largeScreenMinWidth; if (isLargeScreen) { return \_buildLargeScreenLayout(); } else { return const ContactListsPage(listId: 0); // New, temporary } }, ); } Widget \_buildLargeScreenLayout() { return CupertinoPageScaffold( backgroundColor: CupertinoColors.extraLightBackgroundGray, child: SafeArea( child: Row( children: \[ const SizedBox(width: 320, child: Text('Sidebar placeholder')), Container(width: 1, color: CupertinoColors.separator), const Expanded(child: Text('Details placeholder')), ], ), ), ); } } \`\`\` Update \`lib/screens/contacts.dart\` by adding \`\_ContactListView\` to the bottom of the file:

<!--?code-excerpt "fwe/rolodex/lib/step3_slivers/screens/contacts_v1.dart (contact_list_view)"?-->

\`\`\`dart class \_ContactListView extends StatelessWidget { const \_ContactListView({ required this.listId, this.automaticallyImplyLeading = true, }); final int listId; final bool automaticallyImplyLeading; @override Widget build(BuildContext context) { return CupertinoPageScaffold( child: ValueListenableBuilder>( valueListenable: contactGroupsModel.listsNotifier, builder: (context, contactGroups, child) { final contactList = contactGroupsModel.findContactList(listId); return CustomScrollView( slivers: \[ CupertinoSliverNavigationBar( largeTitle: Text(contactList.title), automaticallyImplyLeading: automaticallyImplyLeading, ), SliverFillRemaining( child: Center( child: Text( '${contactList.contacts.length} contacts in ${contactList.label}', ), ), ), ], ); }, ), ); } } \`\`\` Now, update \`ContactListsPage\` to use this view:

<!--?code-excerpt "fwe/rolodex/lib/step3_slivers/screens/contacts_v1.dart (contact_lists_page)"?-->

\`\`\`dart class ContactListsPage extends StatelessWidget { const ContactListsPage({super.key, required this.listId}); final int listId; @override Widget build(BuildContext context) { return \_ContactListView(listId: listId); } } \`\`\` This basic implementation demonstrates how to use slivers with dynamic data in a reusable component. ### Add search integration with slivers Now, enhance the contacts page with integrated search functionality UI. Update the \`CustomScrollView\` in \`\_ContactListView\` to use the \`CupertinoSliverNavigationBar.search\` constructor instead of the default \`CupertinoSliverNavigationBar\` constructor:

<!--?code-excerpt "fwe/rolodex/lib/step3_slivers/screens/contacts_v2.dart (search)"?-->

\`\`\`dart class \_ContactListView extends StatelessWidget { // ··· @override Widget build(BuildContext context) { return CupertinoPageScaffold( child: ValueListenableBuilder>( valueListenable: contactGroupsModel.listsNotifier, builder: (context, contactGroups, child) { final contactList = contactGroupsModel.findContactList(listId); return CustomScrollView( slivers: \[ // Now using a search bar: CupertinoSliverNavigationBar.search( largeTitle: Text(contactList.title), searchField: const CupertinoSearchTextField( suffixIcon: Icon(CupertinoIcons.mic\_fill), suffixMode: OverlayVisibilityMode.always, ), ), SliverFillRemaining( child: Center( child: Text( '${contactList.contacts.length} contacts in ${contactList.label}', ), ), ), ], ); }, ), ); } } \`\`\` The \`CupertinoSliverNavigationBar.search\` constructor provides integrated search functionality. As you scroll down, the search field smoothly transitions into the collapsed navigation bar. ### Create alphabetized contact sections Real-world contact apps organize contacts alphabetically. To do this, create sections for each letter. Add the following widget to the bottom of your \`contacts.dart\` file. This widget doesn't contain any slivers.

<!--?code-excerpt "fwe/rolodex/lib/step3_slivers/screens/contacts.dart (contact_list_section)"?-->

\`\`\`dart class ContactListSection extends StatelessWidget { const ContactListSection({ super.key, required this.lastInitial, required this.contacts, }); final String lastInitial; final List contacts; @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsetsDirectional.fromSTEB(20, 0, 20, 0), child: Column( children: \[ const SizedBox(height: 15), Align( alignment: AlignmentDirectional.bottomStart, child: Text( lastInitial, style: const TextStyle( color: CupertinoColors.systemGrey, fontSize: 15, fontWeight: FontWeight.w700, ), ), ), CupertinoListSection( backgroundColor: CupertinoColors.systemBackground, dividerMargin: 0, additionalDividerMargin: 0, topMargin: 4, children: \[ for (final Contact contact in contacts) CupertinoListTile( padding: const EdgeInsets.all(0), title: Text('${contact.firstName} ${contact.lastName}'), ), ], ), ], ), ); } } \`\`\` This widget creates the familiar alphabetized sections that you see in the iOS Contacts app. ### Use \`SliverList\` for the alphabetized sections Now, replace the placeholder content in \`\_ContactListView\` with the alphabetized sections:

<!--?code-excerpt "fwe/rolodex/lib/step3_slivers/screens/contacts.dart (alphabetized)"?-->

\`\`\`dart class \_ContactListView extends StatelessWidget { // ··· @override Widget build(BuildContext context) { return CupertinoPageScaffold( child: ValueListenableBuilder>( valueListenable: contactGroupsModel.listsNotifier, builder: (context, contactGroups, child) { final contactList = contactGroupsModel.findContactList(listId); final contacts = contactList.alphabetizedContacts; return CustomScrollView( slivers: \[ CupertinoSliverNavigationBar.search( largeTitle: Text(contactList.title), automaticallyImplyLeading: automaticallyImplyLeading, searchField: const CupertinoSearchTextField( suffixIcon: Icon(CupertinoIcons.mic\_fill), suffixMode: OverlayVisibilityMode.always, ), ), SliverList.list( children: \[ const SizedBox(height: 20), ...contacts.keys.map( (initial) => ContactListSection( lastInitial: initial, contacts: contacts\[initial]!, ), ), ], ), ], ); }, ), ); } } \`\`\` \`SliverList.list\` allows you to provide a list of widgets that become part of the scrollable content. This is the simplest way to add a list of normal widgets to a scrollable sliver area. In the next lesson, you'll learn about stack-based navigation and update the UI on small screens to navigate between the contacts list view and the contacts view. ### Review title: What you accomplished subtitle: Here's a summary of what you built and learned in this lesson. completed: true items: - title: Understood slivers and how they differ from widgets icon: view\_day details: >- Slivers are specialized widgets for scrollable layouts. They can only be direct children of scroll views like \`CustomScrollView\`. In \`CustomScrollView\` and other sliver contexts, regular widgets must be wrapped in \`SliverToBoxAdapter\` or \`SliverFillRemaining\`. - title: Built scrollable layouts with CustomScrollView icon: unfold\_more details: >- \`CustomScrollView\` lets you compose multiple slivers together. You used \`CupertinoSliverNavigationBar\`, \`SliverFillRemaining\`, and \`SliverList\` to create sophisticated scrollable interfaces. - title: Created collapsible navigation bars with search icon: search details: >- You used the \`CupertinoSliverNavigationBar.search\` constructor to create a collapsible navigation bar with integrated search functionality. - title: Organized contacts in alphabetized sections icon: sort\_by\_alpha details: >- You created \`ContactListSection\` widgets grouped by last name initial, then used \`SliverList.list\` to add them to the scrollable area. This mirrors the familiar iOS Contacts app experience. ### Test yourself - question: What is the key difference between slivers and regular widgets? options: - text: Slivers are faster to render than regular widgets. correct: false explanation: Both are optimized; the difference is their purpose and context. - text: Slivers are specialized widgets designed for scrollable layouts and can only be direct children of scroll views. correct: true explanation: Slivers work within scroll views like CustomScrollView; regular widgets can be used anywhere. - text: Slivers can have an unlimited number of children. correct: false explanation: Some slivers like SliverList can have many children, but that's not what distinguishes them. - text: Slivers automatically handle user gestures. correct: false explanation: Gesture handling is separate; slivers are about scrollable layout composition. - question: How do you use a regular widget inside a CustomScrollView's slivers list? options: - text: Just add it directly; CustomScrollView accepts any widget. correct: false explanation: CustomScrollView only accepts slivers; regular widgets must be wrapped. - text: Wrap it in a SliverToBoxAdapter or SliverFillRemaining. correct: true explanation: These adapters convert regular widgets into slivers so they can be used in sliver contexts. - text: "Convert the widget to a sliver by calling \`.toSliver()\` on it." correct: false explanation: "There's no \`.toSliver()\` method; you use adapter widgets like SliverToBoxAdapter." - text: "Pass it to the \`child\` property instead of \`slivers\`." correct: false explanation: CustomScrollView uses the slivers property; there's no child property for this purpose.

----
url: https://docs.flutter.dev/tools/devtools/memory.md
----

\# Use the Memory view > Learn how to use the DevTools memory view. The memory view provides insights into details of the application's memory allocation and tools to detect and debug specific issues. :::note This page is up to date for DevTools 2.23.0. ::: For information on how to locate DevTools screens in different IDEs, check out the \[DevTools overview]\(/tools/devtools). To better understand the insights found on this page, the first section explains how Dart manages memory. If you already understand Dart's memory management, you can skip to the \[Memory view guide]\(#memory-view-guide). ## Reasons to use the memory view Use the memory view for preemptive memory optimization or when your application experiences one of the following conditions: \* Crashes when it runs out of memory \* Slows down \* Causes the device to slow down or become unresponsive \* Shuts down because it exceeded the memory limit, enforced by operating system \* Exceeds memory usage limit \* This limit can vary depending on the type of devices your app targets. \* Suspect a memory leak ## Basic memory concepts Dart objects created using a class constructor (for example, by using \`MyClass()\`) live in a portion of memory called the \_heap\_. The memory in the heap is managed by the Dart VM (virtual machine). The Dart VM allocates memory for the object at the moment of the object creation, and releases (or deallocates) the memory when the object is no longer used (see \[Dart garbage collection]\[]). \[Dart garbage collection]: https\://flutter.dev/blog/flutter-dont-fear-the-garbage-collector ### Object types #### Disposable object A disposable object is any Dart object that defines a \`dispose()\` method. To avoid memory leaks, invoke \`dispose\` when the object isn't needed anymore. #### Memory-risky object A memory-risky object is an object that \_might\_ cause a memory leak, if it is not disposed properly or disposed but not GCed. ### Root object, retaining path, and reachability #### Root object Every Dart application creates a \_root object\_ that references, directly or indirectly, all other objects the application allocates. #### Reachability If, at some moment of the application run, the root object stops referencing an allocated object, the object becomes \_unreachable\_, which is a signal for the garbage collector (GC) to deallocate the object's memory. #### Retaining path The sequence of references from root to an object is called the object's \_retaining\_ path, as it retains the object's memory from the garbage collection. One object can have many retaining paths. Objects with at least one retaining path are called \_reachable\_ objects. #### Example The following example illustrates the concepts: \`\`\`dart class Child{} class Parent { Child? child; } Parent parent1 = Parent(); void myFunction() { Child? child = Child(); // The \`child\` object was allocated in memory. // It's now retained from garbage collection // by one retaining path (root …-> myFunction -> child). Parent? parent2 = Parent()..child = child; parent1.child = child; // At this point the \`child\` object has three retaining paths: // root …-> myFunction -> child // root …-> myFunction -> parent2 -> child // root -> parent1 -> child child = null; parent1.child = null; parent2 = null; // At this point, the \`child\` instance is unreachable // and will eventually be garbage collected. … } \`\`\` ### Shallow size vs retained size \*\*Shallow size\*\* includes only the size of the object and its references, while \*\*retained size\*\* also includes the size of the retained objects. The \*\*retained size\*\* of the root object includes all reachable Dart objects. In the following example, the size of \`myHugeInstance\` isn't part of the parent's or child's shallow sizes, but is part of their retained sizes: \`\`\`dart class Child{ /// The instance is part of both \[parent] and \[parent.child] /// retained sizes. final myHugeInstance = MyHugeInstance(); } class Parent { Child? child; } Parent parent = Parent()..child = Child(); \`\`\` In DevTools calculations, if an object has more than one retaining path, its size is assigned as retained only to the members of the shortest retaining path. In this example the object \`x\` has two retaining paths: \`\`\`console root -> a -> b -> c -> x root -> d -> e -> x (shortest retaining path to \`x\`) \`\`\` Only members of the shortest path (\`d\` and \`e\`) will include \`x\` into their retaining size. ### Memory leaks happen in Dart? Garbage collector cannot prevent all types of memory leaks, and developers still need to watch objects to have leak-free lifecycle. #### Why can't the garbage collector prevent all leaks? While the garbage collector takes care of all unreachable objects, it's the responsibility of the application to ensure that unneeded objects are no longer reachable (referenced from the root). So, if non-needed objects are left referenced (in a global or static variable, or as a field of a long-living object), the garbage collector can't recognize them, the memory allocation grows progressively, and the app eventually crashes with an \`out-of-memory\` error. #### Why closures require extra attention One hard-to-catch leak pattern relates to using closures. In the following code, a reference to the designed-to-be short-living \`myHugeObject\` is implicitly stored in the closure context and passed to \`setHandler\`. As a result, \`myHugeObject\` won't be garbage collected as long as \`handler\` is reachable. \`\`\`dart final handler = () => print(myHugeObject.name); setHandler(handler); \`\`\` #### Why \`BuildContext\` requires extra attention An example of a large, short-living object that might squeeze into a long-living area and thus cause leaks, is the \`context\` parameter passed to Flutter's \`build\` method. The following code is leak prone, as \`useHandler\` might store the handler in a long-living area: \`\`\`dart // BAD: DO NOT DO THIS // This code is leak prone: @override Widget build(BuildContext context) { final handler = () => apply(Theme.of(context)); useHandler(handler); … \`\`\` #### How to fix leak prone code? The following code is not leak prone, because: 1. The closure doesn't use the large and short-living \`context\` object. 2. The \`theme\` object (used instead) is long-living. It is created once and shared between \`BuildContext\` instances. \`\`\`dart // GOOD @override Widget build(BuildContext context) { final theme = Theme.of(context); final handler = () => apply(theme); useHandler(handler); … \`\`\` #### General rule for \`BuildContext\` In general, use the following rule for a \`BuildContext\`: if the closure doesn't outlive the widget, it's ok to pass the context to the closure. Stateful widgets require extra attention. They consist of two classes: the \[widget and the widget state]\[interactive], where the widget is short living, and the state is long living. The build context, owned by the widget, should never be referenced from the state's fields, as the state won't be garbage collected together with the widget, and can significantly outlive it. \[interactive]: /ui/interactivity#creating-a-stateful-widget ### Memory leak vs memory bloat In a memory leak, an application progressively uses memory, for example, by repeatedly creating a listener, but not disposing it. Memory bloat uses more memory than is necessary for optimal performance, for example, by using overly large images or keeping streams open through their lifetime. Both leaks and bloats, when large, cause an application to crash with an \`out-of-memory\` error. However, leaks are more likely to cause memory issues, because even a small leak, if repeated many times, leads to a crash. ## Memory view guide The DevTools memory view helps you investigate memory allocations (both in the heap and external), memory leaks, memory bloat, and more. The view has the following features: \[\*\*Expandable chart\*\*]\(#expandable-chart) : Get a high-level trace of memory allocation, and view both standard events (like garbage collection) and custom events (like image allocation). \[\*\*Profile Memory\*\* tab]\(#profile-memory-tab) : See current memory allocation listed by class and memory type. \[\*\*Diff Snapshots\*\* tab]\(#diff-snapshots-tab) : Detect and investigate a feature's memory management issues. \[\*\*Trace Instances\*\* tab]\(#trace-instances-tab) : Investigate a feature's memory management for a specified set of classes. ### Expandable chart The expandable chart provides the following features: #### Memory anatomy A timeseries graph visualizes the state of Flutter memory at successive intervals of time. Each data point on the chart corresponds to the timestamp (x-axis) of measured quantities (y-axis) of the heap. For example, usage, capacity, external, garbage collection, and resident set size are captured. !\[Screenshot of a memory anatomy page]\(/assets/images/docs/tools/devtools/memory\_chart\_anatomy.png){:width="100%"} #### Memory overview chart The memory overview chart is a timeseries graph of collected memory statistics. It visually presents the state of the Dart or Flutter heap and Dart's or Flutter's native memory over time. The chart's x-axis is a timeline of events (timeseries). The data plotted in the y-axis all has a timestamp of when the data was collected. In other words, it shows the polled state (capacity, used, external, RSS (resident set size), and GC (garbage collection)) of the memory every 500 ms. This helps provide a live appearance on the state of the memory as the application is running. Clicking the \*\*Legend\*\* button displays the collected measurements, symbols, and colors used to display the data. !\[Screenshot of a memory anatomy page]\(/assets/images/docs/tools/devtools/memory\_chart\_anatomy.png){:width="100%"} The \*\*Memory Size Scale\*\* y-axis automatically adjusts to the range of data collected in the current visible chart range. The quantities plotted on the y-axis are as follows: \*\*Dart/Flutter Heap\*\* : Objects (Dart and Flutter objects) in the heap. \*\*Dart/Flutter Native\*\* : Memory that isn't in the Dart/Flutter heap but is still part of the total memory footprint. Objects in this memory would be native objects (for example, from reading a file into memory, or a decoded image). The native objects are exposed to the Dart VM from the native OS (such as Android, Linux, Windows, iOS) using a Dart embedder. The embedder creates a Dart wrapper with a finalizer, allowing Dart code to communicate with these native resources. Flutter has an embedder for Android and iOS. For more information, see \[Command-line and server apps]\[], \[Dart on the server with Dart Frog]\[frog], \[Custom Flutter Engine Embedders]\[], \[Dart web server deployment with Heroku]\[heroku]. \*\*Timeline\*\* : The timestamps of all collected memory statistics and events at a particular point in time (timestamp). \*\*Raster Cache\*\* : The size of the Flutter engine's raster cache layer(s) or picture(s), while performing the final rendering after compositing. For more information, see the \[Flutter architectural overview]\[] and \[DevTools Performance view]\[]. \*\*Allocated\*\* : The current total capacity of all Dart heaps. This is typically slightly larger than the total size of all heap objects. \*\*RSS - Resident Set Size\*\* : The resident set size displays the amount of memory for a process. It doesn't include memory that is swapped out. It includes memory from shared libraries that are loaded, as well as all stack and heap memory. For more information, see \[Dart VM internals]\[]. \[Command-line and server apps]: https\://dart.dev/server \[Custom Flutter engine embedders]: https\://github.com/flutter/flutter/blob/main/docs/engine/Custom-Flutter-Engine-Embedders.md \[Dart VM internals]: https\://mrale.ph/dartvm/ \[DevTools Performance view]: /tools/devtools/performance \[Flutter architectural overview]: /resources/architectural-overview \[frog]: https\://dartfrog.vgv.dev/ \[heroku]: https\://www\.youtube.com/watch?v=nkTUMVNelXA []()### Profile Memory tab Use the \*\*Profile Memory\*\* tab to see current memory allocation by class and memory type. For a deeper analysis in Google Sheets or other tools, download the data in CSV format. Toggle \*\*Refresh on GC\*\*, to see allocation in real time. !\[Screenshot of the profile tab page]\(/assets/images/docs/tools/devtools/profile-tab.png){:width="100%"} ### Diff Snapshots tab Use the \*\*Diff Snapshots\*\* tab to investigate a feature's memory management. Follow the guidance on the tab to take snapshots before and after interaction with the application, and diff the snapshots: !\[Screenshot of the diff tab page]\(/assets/images/docs/tools/devtools/diff-tab.png){:width="100%"} Tap the \*\*Filter classes and packages\*\* button, to narrow the data: !\[Screenshot of the filter options ui]\(/assets/images/docs/tools/devtools/filter-ui.png) For a deeper analysis in Google Sheets or other tools, download the data in CSV format. []()### Trace Instances tab Use the \*\*Trace Instances\*\* tab to investigate what methods allocate memory for a set of classes during feature execution: 1. Select classes to trace 1. Interact with your app to trigger the code you are interested in 1. Tap \*\*Refresh\*\* 1. Select a traced class 1. Review the collected data !\[Screenshot of a trace tab]\(/assets/images/docs/tools/devtools/trace-instances-tab.png){:width="100%"} #### Bottom up vs call tree view Switch between bottom-up and call tree views depending on specifics of your tasks. !\[Screenshot of a trace allocations]\(/assets/images/docs/tools/devtools/trace-view\.png) The call tree view shows the method allocations for each instance. The view is a top-down representation of the call stack, meaning that a method can be expanded to show its callees. The bottom-up view shows the list of different call stacks that have allocated the instances. ## Other resources For more information, check out the following resources: \* To learn how to monitor an app's memory usage and detect memory leaks using DevTools, check out a guided \[Memory View tutorial]\[memory-tutorial]. \* To understand Android memory structure, check out \[Android: Memory allocation among processes]\[]. \[memory-tutorial]: https\://medium.com/@fluttergems/mastering-dart-flutter-devtools-memory-view-part-7-of-8-e7f5aaf07e15 \[Android: Memory allocation among processes]: https\://developer.android.com/topic/performance/memory-management

----
url: https://docs.flutter.dev/ui
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Building user interfaces with Flutter](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

Flutter widgets are built using a modern framework that takes inspiration from [React](https://react.dev). The central idea is that you build your UI out of widgets. Widgets describe what their view should look like given their current configuration and state. When a widget's state changes, the widget rebuilds its description, which the framework diffs against the previous description in order to determine the minimal changes needed in the underlying render tree to transition from one state to the next.

Flutter structures its UI layer into two tiers: the style-neutral core `widgets` library (provided directly by the Flutter SDK) and design system packages `material_ui` and `cupertino_ui`, available on pub.dev.

Understanding the distinction helps you decide which package to import for your app or package.

## The `widgets` package

[#](#the-widgets-package)

The `widgets`library (`package:flutter/widgets.dart`) provides the foundational, style-neutral building blocks of the Flutter framework. It includes layout primitives (`Row,` `Column,` `Stack)`, interaction models (`GestureDetector`), animation controllers, and base presentation widgets (`CustomPaint`, `Image`, `RichText`).

### When to use `widgets`

[#](#when-to-use-widgets)

* **Building a custom design system:** If your application uses a bespoke visual language that doesn't strictly follow Material Design or Apple Human Interface Guidelines, build on top of widgets rather than overriding opinionated design system defaults.
* **Creating design-agnostic packages:** If you publish a reusable component or utility package on pub.dev, importing `package:flutter/widgets.dart` ensures your package can be used in both Material and Cupertino applications without introducing unnecessary dependencies.
* **Embedded and non-standard devices:** For automotive, kiosks, or embedded displays where platform conventions don't apply and minimal bundle size is critical.

## The `material_ui` package

[#](#the-material-ui-package)

The `material_ui` package (`package:material_ui/material_ui.dart`) implements Google's Material 3 design system. It layers visual styling, dynamic color, elevation, typography, and interaction feedback (such as ripples) on top of the base widgets.

### When to use `material_ui`

[#](#when-to-use-material-ui)

* **Building Android or cross-platform apps:** When you want a modern, cohesive look across Android, web, and desktop with out-of-the-box accessibility, transitions, and theming.
* **Rapid prototyping and feature development:** Material 3 supplies pre-composed screen scaffolding (`Scaffold`, `AppBar`, `NavigationBar`) and interactive controls (`ElevatedButton,` `FloatingActionButton,` `TextField,` `Dialog)` so you don't need to assemble them from scratch.
* **Leveraging Material theming:** When you want to take advantage of `ColorScheme.fromSeed`, light and dark mode switching, and dynamic color extraction on Android.

## The `cupertino_ui` package

[#](#the-cupertino-ui-package)

The `cupertino_ui` package (`package:cupertino_ui/cupertino_ui.dart`) implements Apple's Human Interface Guidelines. It recreates iOS-specific visual styles, translucent blurs, haptic touches, and navigation gestures.

### When to use `cupertino_ui`

[#](#when-to-use-cupertino-ui)

* **Targeting iOS and macOS native aesthetics:** When your application's primary goal is to blend seamlessly into the Apple ecosystem with familiar iOS controls (`CupertinoNavigationBar`, `CupertinoButton`, `CupertinoTabBar`, `CupertinoActionSheet`).
* **Platform-adaptive applications:** When building adaptive experiences where your code renders `material_ui` components on Android and `cupertino_ui` components on iOS.

The following table summarizes the differences between these packages:

| Feature              | `widgets`                                | `material_ui`                 | `cupertino_ui`            |
| -------------------- | ---------------------------------------- | ----------------------------- | ------------------------- |
| Source               | Flutter SDK (`flutter`)                  | pub.dev                       | pub.dev                   |
| Design system        | None (style-neutral)                     | Material 3                    | Apple HIG (iOS) 3         |
| Application root     | `WidgetsApp`                             | `MaterialApp`                 | `CupertinoApp`            |
| Scaffolding & layout | Raw layout (`Stack`, `Column`)           | `Scaffold`, `AppBar`          | `CupertinoPageScaffold`   |
| Primary use case     | Custom design systems, reusable packages | Android & cross-platform apps | iOS-first & adaptive apps |

## Hello world

[#](#hello-world)

The minimal Flutter app simply calls the [`runApp()`](https://api.flutter.dev/flutter/widgets/runApp.html) function with a widget:

```
import 'package:flutter/material.dart';

void main() {
  runApp(
    const Center(
      child: Text(
        'Hello, world!',
        textDirection: TextDirection.ltr,
        style: TextStyle(color: Colors.blue),
      ),
    ),
  );
}
```

The `runApp()` function takes the given [`Widget`](https://api.flutter.dev/flutter/widgets/State-class.html#widget) and makes it the root of the widget tree. In this example, the widget tree consists of two widgets, the [`Center`](https://api.flutter.dev/flutter/widgets/Center-class.html) widget and its child, the [`Text`](https://api.flutter.dev/flutter/widgets/Text-class.html) widget. The framework forces the root widget to cover the screen, which means the text "Hello, world" ends up centered on screen. The text direction needs to be specified in this instance; when the `MaterialApp` widget is used, this is taken care of for you, as demonstrated later.

When writing an app, you'll commonly author new widgets that are subclasses of either [`StatelessWidget`](https://api.flutter.dev/flutter/widgets/StatelessWidget-class.html) or [`StatefulWidget`](https://api.flutter.dev/flutter/widgets/StatefulWidget-class.html), depending on whether your widget manages any state. A widget's main job is to implement a [`build()`](https://api.flutter.dev/flutter/widgets/StatelessWidget/build.html) function, which describes the widget in terms of other, lower-level widgets. The framework builds those widgets in turn until the process bottoms out in widgets that represent the underlying [`RenderObject`](https://api.flutter.dev/flutter/rendering/RenderObject-class.html), which computes and describes the geometry of the widget.

## Basic widgets

[#](#basic-widgets)

Flutter comes with a suite of powerful basic widgets, of which the following are commonly used:

* **[`Text`](https://api.flutter.dev/flutter/widgets/Text-class.html)**

  The `Text` widget lets you create a run of styled text within your application.

* **[`Row`](https://api.flutter.dev/flutter/widgets/Row-class.html) , [`Column`](https://api.flutter.dev/flutter/widgets/Column-class.html)**

  These flex widgets let you create flexible layouts in both the horizontal (`Row`) and vertical (`Column`) directions. The design of these objects is based on the web's flexbox layout model.

* **[`Stack`](https://api.flutter.dev/flutter/widgets/Stack-class.html)**

  Instead of being linearly oriented (either horizontally or vertically), a `Stack` widget lets you place widgets on top of each other in paint order. You can then use the [`Positioned`](https://api.flutter.dev/flutter/widgets/Positioned-class.html) widget on children of a `Stack` to position them relative to the top, right, bottom, or left edge of the stack. Stacks are based on the web's absolute positioning layout model.

* **[`Container`](https://api.flutter.dev/flutter/widgets/Container-class.html)**

  The `Container` widget lets you create a rectangular visual element. A container can be decorated with a [`BoxDecoration`](https://api.flutter.dev/flutter/painting/BoxDecoration-class.html), such as a background, a border, or a shadow. A `Container` can also have margins, padding, and constraints applied to its size. In addition, a `Container` can be transformed in three-dimensional space using a matrix.

Below are some simple widgets that combine these and other widgets:

```
import 'package:flutter/material.dart';

class MyAppBar extends StatelessWidget {
  const MyAppBar({required this.title, super.key});

  // Fields in a Widget subclass are always marked "final".

  final Widget title;

  @override
  Widget build(BuildContext context) {
    return Container(
      height: 56, // in logical pixels
      padding: const EdgeInsets.symmetric(horizontal: 8),
      decoration: BoxDecoration(color: Colors.blue[500]),
      // Row is a horizontal, linear layout.
      child: Row(
        children: [
          const IconButton(
            icon: Icon(Icons.menu),
            tooltip: 'Navigation menu',
            onPressed: null, // null disables the button
          ),
          // Expanded expands its child
          // to fill the available space.
          Expanded(child: title),
          const IconButton(
            icon: Icon(Icons.search),
            tooltip: 'Search',
            onPressed: null,
          ),
        ],
      ),
    );
  }
}

class MyScaffold extends StatelessWidget {
  const MyScaffold({super.key});

  @override
  Widget build(BuildContext context) {
    // Material is a conceptual piece
    // of paper on which the UI appears.
    return Material(
      // Column is a vertical, linear layout.
      child: Column(
        children: [
          MyAppBar(
            title: Text(
              'Example title',
              style:
                  Theme.of(context) //
                      .primaryTextTheme
                      .titleLarge,
            ),
          ),
          const Expanded(child: Center(child: Text('Hello, world!'))),
        ],
      ),
    );
  }
}

void main() {
  runApp(
    const MaterialApp(
      title: 'My app', // used by the OS task switcher
      home: SafeArea(child: MyScaffold()),
    ),
  );
}
```

Be sure to have a `uses-material-design: true` entry in the `flutter` section of your `pubspec.yaml` file. It allows you to use the predefined set of [Material icons](https://design.google.com/icons/). It's generally a good idea to include this line if you are using the Materials library.

yaml

```
name: my_app
flutter:
  uses-material-design: true
```

Many Material Design widgets need to be inside of a [`MaterialApp`](https://pub.dev/documentation/material_ui/latest/material_ui/MaterialApp-class.html) to display properly, in order to inherit theme data. Therefore, run the application with a `MaterialApp`.

The `MyAppBar` widget creates a [`Container`](https://api.flutter.dev/flutter/widgets/Container-class.html) with a height of 56 device-independent pixels with an internal padding of 8 pixels, both on the left and the right. Inside the container, `MyAppBar` uses a [`Row`](https://api.flutter.dev/flutter/widgets/Row-class.html) layout to organize its children. The middle child, the `title` widget, is marked as [`Expanded`](https://api.flutter.dev/flutter/widgets/Expanded-class.html), which means it expands to fill any remaining available space that hasn't been consumed by the other children. You can have multiple `Expanded` children and determine the ratio in which they consume the available space using the [`flex`](https://api.flutter.dev/flutter/widgets/Expanded-class.html#flex) argument to `Expanded`.

The `MyScaffold` widget organizes its children in a vertical column. At the top of the column it places an instance of `MyAppBar`, passing the app bar a [`Text`](https://api.flutter.dev/flutter/widgets/Text-class.html) widget to use as its title. Passing widgets as arguments to other widgets is a powerful technique that lets you create generic widgets that can be reused in a wide variety of ways. Finally, `MyScaffold` uses an [`Expanded`](https://api.flutter.dev/flutter/widgets/Expanded-class.html) to fill the remaining space with its body, which consists of a centered message.

For more information, check out [Layouts](/ui/widgets/layout).

## Using Material components

[#](#using-material-components)

Flutter provides a number of widgets that help you build apps that follow Material Design. A Material app starts with the [`MaterialApp`](https://pub.dev/documentation/material_ui/latest/material_ui/MaterialApp-class.html) widget, which builds a number of useful widgets at the root of your app, including a [`Navigator`](https://api.flutter.dev/flutter/widgets/Navigator-class.html), which manages a stack of widgets identified by strings, also known as "routes". The `Navigator` lets you transition smoothly between screens of your application. Using the [`MaterialApp`](https://pub.dev/documentation/material_ui/latest/material_ui/MaterialApp-class.html) widget is entirely optional but a good practice.

```
import 'package:flutter/material.dart';

void main() {
  runApp(const MaterialApp(title: 'Flutter Tutorial', home: TutorialHome()));
}

class TutorialHome extends StatelessWidget {
  const TutorialHome({super.key});

  @override
  Widget build(BuildContext context) {
    // Scaffold is a layout for
    // the major Material Components.
    return Scaffold(
      appBar: AppBar(
        leading: const IconButton(
          icon: Icon(Icons.menu),
          tooltip: 'Navigation menu',
          onPressed: null,
        ),
        title: const Text('Example title'),
        actions: const [
          IconButton(
            icon: Icon(Icons.search),
            tooltip: 'Search',
            onPressed: null,
          ),
        ],
      ),
      // body is the majority of the screen.
      body: const Center(child: Text('Hello, world!')),
      floatingActionButton: const FloatingActionButton(
        tooltip: 'Add', // used by assistive technologies
        onPressed: null,
        child: Icon(Icons.add),
      ),
    );
  }
}
```

Now that the code has switched from `MyAppBar` and `MyScaffold` to the [`AppBar`](https://pub.dev/documentation/material_ui/latest/material_ui/AppBar-class.html) and [`Scaffold`](https://pub.dev/documentation/material_ui/latest/material_ui/Scaffold-class.html) widgets, and from `material.dart`, the app is starting to look a bit more Material. For example, the app bar has a shadow and the title text inherits the correct styling automatically. A floating action button is also added.

Notice that widgets are passed as arguments to other widgets. The [`Scaffold`](https://pub.dev/documentation/material_ui/latest/material_ui/Scaffold-class.html) widget takes a number of different widgets as named arguments, each of which are placed in the `Scaffold` layout in the appropriate place. Similarly, the [`AppBar`](https://pub.dev/documentation/material_ui/latest/material_ui/AppBar-class.html) widget lets you pass in widgets for the [`leading`](https://pub.dev/documentation/material_ui/latest/material_ui/AppBar-class.html#leading) widget, and the [`actions`](https://pub.dev/documentation/material_ui/latest/material_ui/AppBar-class.html#actions) of the [`title`](https://pub.dev/documentation/material_ui/latest/material_ui/AppBar-class.html#title) widget. This pattern recurs throughout the framework and is something you might consider when designing your own widgets.

For more information, check out [Material Components widgets](/ui/widgets/material).

## Handling gestures

[#](#handling-gestures)

Most applications include some form of user interaction with the system. The first step in building an interactive application is to detect input gestures. See how that works by creating a simple button:

```
import 'package:flutter/material.dart';

class MyButton extends StatelessWidget {
  const MyButton({super.key});

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: () {
        print('MyButton was tapped!');
      },
      child: Container(
        height: 50,
        padding: const EdgeInsets.all(8),
        margin: const EdgeInsets.symmetric(horizontal: 8),
        decoration: BoxDecoration(
          borderRadius: BorderRadius.circular(5),
          color: Colors.lightGreen[500],
        ),
        child: const Center(child: Text('Engage')),
      ),
    );
  }
}

void main() {
  runApp(
    const MaterialApp(
      home: Scaffold(body: Center(child: MyButton())),
    ),
  );
}
```

The [`GestureDetector`](https://api.flutter.dev/flutter/widgets/GestureDetector-class.html) widget doesn't have a visual representation but instead detects gestures made by the user. When the user taps the [`Container`](https://api.flutter.dev/flutter/widgets/Container-class.html), the `GestureDetector` calls its [`onTap()`](https://api.flutter.dev/flutter/widgets/GestureDetector-class.html#onTap) callback, in this case printing a message to the console. You can use `GestureDetector` to detect a variety of input gestures, including taps, drags, and scales.

Many widgets use a [`GestureDetector`](https://api.flutter.dev/flutter/widgets/GestureDetector-class.html) to provide optional callbacks for other widgets. For example, the [`IconButton`](https://pub.dev/documentation/material_ui/latest/material_ui/IconButton-class.html), [`ElevatedButton`](https://pub.dev/documentation/material_ui/latest/material_ui/ElevatedButton-class.html), and [`FloatingActionButton`](https://pub.dev/documentation/material_ui/latest/material_ui/FloatingActionButton-class.html) widgets have [`onPressed()`](https://pub.dev/documentation/material_ui/latest/material_ui/ElevatedButton-class.html#onPressed) callbacks that are triggered when the user taps the widget.

For more information, check out [Gestures in Flutter](/ui/interactivity/gestures).

## Changing widgets in response to input

[#](#changing-widgets-in-response-to-input)

So far, this page has used only stateless widgets. Stateless widgets receive arguments from their parent widget, which they store in [`final`](https://dart.dev/language/variables#final-and-const) member variables. When a widget is asked to [`build()`](https://api.flutter.dev/flutter/widgets/StatelessWidget/build.html), it uses these stored values to derive new arguments for the widgets it creates.

In order to build more complex experiences—for example, to react in more interesting ways to user input—applications typically carry some state. Flutter uses `StatefulWidgets` to capture this idea. `StatefulWidgets` are special widgets that know how to generate `State` objects, which are then used to hold state. Consider this basic example, using the [`ElevatedButton`](https://pub.dev/documentation/material_ui/latest/material_ui/ElevatedButton-class.html) mentioned earlier:

```
import 'package:flutter/material.dart';

class Counter extends StatefulWidget {
  // This class is the configuration for the state.
  // It holds the values (in this case nothing) provided
  // by the parent and used by the build  method of the
  // State. Fields in a Widget subclass are always marked
  // "final".

  const Counter({super.key});

  @override
  State<Counter> createState() => _CounterState();
}

class _CounterState extends State<Counter> {
  int _counter = 0;

  void _increment() {
    setState(() {
      // This call to setState tells the Flutter framework
      // that something has changed in this State, which
      // causes it to rerun the build method below so that
      // the display can reflect the updated values. If you
      // change _counter without calling setState(), then
      // the build method won't be called again, and so
      // nothing would appear to happen.
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    // This method is rerun every time setState is called,
    // for instance, as done by the _increment method above.
    // The Flutter framework has been optimized to make
    // rerunning build methods fast, so that you can just
    // rebuild anything that needs updating rather than
    // having to individually changes instances of widgets.
    return Row(
      mainAxisAlignment: MainAxisAlignment.center,
      children: <Widget>[
        ElevatedButton(onPressed: _increment, child: const Text('Increment')),
        const SizedBox(width: 16),
        Text('Count: $_counter'),
      ],
    );
  }
}

void main() {
  runApp(
    const MaterialApp(
      home: Scaffold(body: Center(child: Counter())),
    ),
  );
}
```

You might wonder why `StatefulWidget` and `State` are separate objects. In Flutter, these two types of objects have different life cycles. `Widgets` are temporary objects, used to construct a presentation of the application in its current state. `State` objects, on the other hand, are persistent between calls to `build()`, allowing them to remember information.

The example above accepts user input and directly uses the result in its `build()` method. In more complex applications, different parts of the widget hierarchy might be responsible for different concerns; for example, one widget might present a complex user interface with the goal of gathering specific information, such as a date or location, while another widget might use that information to change the overall presentation.

In Flutter, change notifications flow "up" the widget hierarchy by way of callbacks, while current state flows "down" to the stateless widgets that do presentation. The common parent that redirects this flow is the `State`. The following slightly more complex example shows how this works in practice:

```
import 'package:flutter/material.dart';

class CounterDisplay extends StatelessWidget {
  const CounterDisplay({required this.count, super.key});

  final int count;

  @override
  Widget build(BuildContext context) {
    return Text('Count: $count');
  }
}

class CounterIncrementor extends StatelessWidget {
  const CounterIncrementor({required this.onPressed, super.key});

  final VoidCallback onPressed;

  @override
  Widget build(BuildContext context) {
    return ElevatedButton(onPressed: onPressed, child: const Text('Increment'));
  }
}

class Counter extends StatefulWidget {
  const Counter({super.key});

  @override
  State<Counter> createState() => _CounterState();
}

class _CounterState extends State<Counter> {
  int _counter = 0;

  void _increment() {
    setState(() {
      ++_counter;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Row(
      mainAxisAlignment: MainAxisAlignment.center,
      children: <Widget>[
        CounterIncrementor(onPressed: _increment),
        const SizedBox(width: 16),
        CounterDisplay(count: _counter),
      ],
    );
  }
}

void main() {
  runApp(
    const MaterialApp(
      home: Scaffold(body: Center(child: Counter())),
    ),
  );
}
```

Notice the creation of two new stateless widgets, cleanly separating the concerns of *displaying* the counter (`CounterDisplay`) and *changing* the counter (`CounterIncrementor`). Although the net result is the same as the previous example, the separation of responsibility allows greater complexity to be encapsulated in the individual widgets, while maintaining simplicity in the parent.

For more information, check out:

* [`StatefulWidget`](https://api.flutter.dev/flutter/widgets/StatefulWidget-class.html)
* [`setState()`](https://api.flutter.dev/flutter/widgets/State/setState.html)

## Bringing it all together

[#](#bringing-it-all-together)

What follows is a more complete example that brings together these concepts: A hypothetical shopping application displays various products offered for sale, and maintains a shopping cart for intended purchases. Start by defining the presentation class, `ShoppingListItem`:

```
import 'package:flutter/material.dart';

class Product {
  const Product({required this.name});

  final String name;
}

typedef CartChangedCallback = void Function(Product product, bool inCart);

class ShoppingListItem extends StatelessWidget {
  ShoppingListItem({
    required this.product,
    required this.inCart,
    required this.onCartChanged,
  }) : super(key: ObjectKey(product));

  final Product product;
  final bool inCart;
  final CartChangedCallback onCartChanged;

  Color _getColor(BuildContext context) {
    // The theme depends on the BuildContext because different
    // parts of the tree can have different themes.
    // The BuildContext indicates where the build is
    // taking place and therefore which theme to use.

    return inCart //
        ? Colors.black54
        : Theme.of(context).primaryColor;
  }

  TextStyle? _getTextStyle(BuildContext context) {
    if (!inCart) return null;

    return const TextStyle(
      color: Colors.black54,
      decoration: TextDecoration.lineThrough,
    );
  }

  @override
  Widget build(BuildContext context) {
    return ListTile(
      onTap: () {
        onCartChanged(product, inCart);
      },
      leading: CircleAvatar(
        backgroundColor: _getColor(context),
        child: Text(product.name[0]),
      ),
      title: Text(product.name, style: _getTextStyle(context)),
    );
  }
}

void main() {
  runApp(
    MaterialApp(
      home: Scaffold(
        body: Center(
          child: ShoppingListItem(
            product: const Product(name: 'Chips'),
            inCart: true,
            onCartChanged: (product, inCart) {},
          ),
        ),
      ),
    ),
  );
}
```

The `ShoppingListItem` widget follows a common pattern for stateless widgets. It stores the values it receives in its constructor in [`final`](https://dart.dev/language/variables#final-and-const) member variables, which it then uses during its [`build()`](https://api.flutter.dev/flutter/widgets/StatelessWidget/build.html) function. For example, the `inCart` boolean toggles between two visual appearances: one that uses the primary color from the current theme, and another that uses gray.

When the user taps the list item, the widget doesn't modify its `inCart` value directly. Instead, the widget calls the `onCartChanged` function it received from its parent widget. This pattern lets you store state higher in the widget hierarchy, which causes the state to persist for longer periods of time. In the extreme, the state stored on the widget passed to [`runApp()`](https://api.flutter.dev/flutter/widgets/runApp.html) persists for the lifetime of the application.

When the parent receives the `onCartChanged` callback, the parent updates its internal state, which triggers the parent to rebuild and create a new instance of `ShoppingListItem` with the new `inCart` value. Although the parent creates a new instance of `ShoppingListItem` when it rebuilds, that operation is cheap because the framework compares the newly built widgets with the previously built widgets and applies only the differences to the underlying [`RenderObject`](https://api.flutter.dev/flutter/rendering/RenderObject-class.html).

Here's an example parent widget that stores mutable state:

```
import 'package:flutter/material.dart';

class Product {
  const Product({required this.name});

  final String name;
}

typedef CartChangedCallback = void Function(Product product, bool inCart);

class ShoppingListItem extends StatelessWidget {
  ShoppingListItem({
    required this.product,
    required this.inCart,
    required this.onCartChanged,
  }) : super(key: ObjectKey(product));

  final Product product;
  final bool inCart;
  final CartChangedCallback onCartChanged;

  Color _getColor(BuildContext context) {
    // The theme depends on the BuildContext because different
    // parts of the tree can have different themes.
    // The BuildContext indicates where the build is
    // taking place and therefore which theme to use.

    return inCart //
        ? Colors.black54
        : Theme.of(context).primaryColor;
  }

  TextStyle? _getTextStyle(BuildContext context) {
    if (!inCart) return null;

    return const TextStyle(
      color: Colors.black54,
      decoration: TextDecoration.lineThrough,
    );
  }

  @override
  Widget build(BuildContext context) {
    return ListTile(
      onTap: () {
        onCartChanged(product, inCart);
      },
      leading: CircleAvatar(
        backgroundColor: _getColor(context),
        child: Text(product.name[0]),
      ),
      title: Text(product.name, style: _getTextStyle(context)),
    );
  }
}

class ShoppingList extends StatefulWidget {
  const ShoppingList({required this.products, super.key});

  final List<Product> products;

  // The framework calls createState the first time
  // a widget appears at a given location in the tree.
  // If the parent rebuilds and uses the same type of
  // widget (with the same key), the framework re-uses
  // the State object instead of creating a new State object.

  @override
  State<ShoppingList> createState() => _ShoppingListState();
}

class _ShoppingListState extends State<ShoppingList> {
  final _shoppingCart = <Product>{};

  void _handleCartChanged(Product product, bool inCart) {
    setState(() {
      // When a user changes what's in the cart, you need
      // to change _shoppingCart inside a setState call to
      // trigger a rebuild.
      // The framework then calls build, below,
      // which updates the visual appearance of the app.

      if (!inCart) {
        _shoppingCart.add(product);
      } else {
        _shoppingCart.remove(product);
      }
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Shopping List')),
      body: ListView(
        padding: const EdgeInsets.symmetric(vertical: 8),
        children: widget.products.map((product) {
          return ShoppingListItem(
            product: product,
            inCart: _shoppingCart.contains(product),
            onCartChanged: _handleCartChanged,
          );
        }).toList(),
      ),
    );
  }
}

void main() {
  runApp(
    const MaterialApp(
      title: 'Shopping App',
      home: ShoppingList(
        products: [
          Product(name: 'Eggs'),
          Product(name: 'Flour'),
          Product(name: 'Chocolate chips'),
        ],
      ),
    ),
  );
}
```

The `ShoppingList` class extends [`StatefulWidget`](https://api.flutter.dev/flutter/widgets/StatefulWidget-class.html), which means this widget stores mutable state. When the `ShoppingList` widget is first inserted into the tree, the framework calls the [`createState()`](https://api.flutter.dev/flutter/widgets/StatefulWidget-class.html#createState) function to create a fresh instance of `_ShoppingListState` to associate with that location in the tree. (Notice that subclasses of [`State`](https://api.flutter.dev/flutter/widgets/State-class.html) are typically named with leading underscores to indicate that they are private implementation details.) When this widget's parent rebuilds, the parent creates a new instance of `ShoppingList`, but the framework reuses the `_ShoppingListState` instance that is already in the tree rather than calling `createState` again.

To access properties of the current `ShoppingList`, the `_ShoppingListState` can use its [`widget`](https://api.flutter.dev/flutter/widgets/State-class.html#widget) property. If the parent rebuilds and creates a new `ShoppingList`, the `_ShoppingListState` rebuilds with the new widget value. If you wish to be notified when the `widget` property changes, override the [`didUpdateWidget()`](https://api.flutter.dev/flutter/widgets/State-class.html#didUpdateWidget) function, which is passed an `oldWidget` to let you compare the old widget with the current widget.

When handling the `onCartChanged` callback, the `_ShoppingListState` mutates its internal state by either adding or removing a product from `_shoppingCart`. To signal to the framework that it changed its internal state, it wraps those calls in a [`setState()`](https://api.flutter.dev/flutter/widgets/State/setState.html) call. Calling `setState` marks this widget as dirty and schedules it to be rebuilt the next time your app needs to update the screen. If you forget to call `setState` when modifying the internal state of a widget, the framework won't know your widget is dirty and might not call the widget's [`build()`](https://api.flutter.dev/flutter/widgets/StatelessWidget/build.html) function, which means the user interface might not update to reflect the changed state. By managing state in this way, you don't need to write separate code for creating and updating child widgets. Instead, you simply implement the `build` function, which handles both situations.

## Responding to widget lifecycle events

[#](#responding-to-widget-lifecycle-events)

After calling [`createState()`](https://api.flutter.dev/flutter/widgets/StatefulWidget-class.html#createState) on the `StatefulWidget`, the framework inserts the new state object into the tree and then calls [`initState()`](https://api.flutter.dev/flutter/widgets/State-class.html#initState) on the state object. A subclass of [`State`](https://api.flutter.dev/flutter/widgets/State-class.html) can override `initState` to do work that needs to happen just once. For example, override `initState` to configure animations or to subscribe to platform services. Implementations of `initState` are required to start by calling `super.initState`.

When a state object is no longer needed, the framework calls [`dispose()`](https://api.flutter.dev/flutter/widgets/State-class.html#dispose) on the state object. Override the `dispose` function to do cleanup work. For example, override `dispose` to cancel timers or to unsubscribe from platform services. Implementations of `dispose` typically end by calling `super.dispose`.

For more information, check out [`State`](https://api.flutter.dev/flutter/widgets/State-class.html).

## Keys

[#](#keys)

Use keys to control which widgets the framework matches up with other widgets when a widget rebuilds. By default, the framework matches widgets in the current and previous build according to their [`runtimeType`](https://api.flutter.dev/flutter/widgets/Widget-class.html#runtimeType) and the order in which they appear. With keys, the framework requires that the two widgets have the same [`key`](https://api.flutter.dev/flutter/widgets/Widget-class.html#key) as well as the same `runtimeType`.

Keys are most useful in widgets that build many instances of the same type of widget. For example, the `ShoppingList` widget, which builds just enough `ShoppingListItem` instances to fill its visible region:

* Without keys, the first entry in the current build would always sync with the first entry in the previous build, even if, semantically, the first entry in the list just scrolled off screen and is no longer visible in the viewport.

* By assigning each entry in the list a "semantic" key, the infinite list can be more efficient because the framework syncs entries with matching semantic keys and therefore similar (or identical) visual appearances. Moreover, syncing the entries semantically means that state retained in stateful child widgets remains attached to the same semantic entry rather than the entry in the same numerical position in the viewport.

For more information, check out the [`Key`](https://api.flutter.dev/flutter/widgets/Widget-class.html#key) API.

## Global keys

[#](#global-keys)

Use global keys to uniquely identify child widgets. Global keys must be globally unique across the entire widget hierarchy, unlike local keys which need only be unique among siblings. Because they are globally unique, a global key can be used to retrieve the state associated with a widget.

For more information, check out the [`GlobalKey`](https://api.flutter.dev/flutter/widgets/GlobalKey-class.html) API.

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-10-14. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/ui/index.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/ui\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/ui/index.md "Report an issue with this page").

----
url: https://docs.flutter.dev/tools/devtools/debugger
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Use the debugger](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

## Getting started

[#](#getting-started)

DevTools includes a full source-level debugger, supporting breakpoints, stepping, and variable inspection.

When you open the debugger tab, you should see the source for the main entry-point for your app loaded in the debugger.

In order to browse around more of your application sources, click **Libraries** (top right) or press `Ctrl` / `Cmd` + `P`. This opens the libraries window and allows you to search for other source files.

## Setting breakpoints

[#](#setting-breakpoints)

To set a breakpoint, click the left margin (the line number ruler) in the source area. Clicking once sets a breakpoint, which should also show up in the **Breakpoints** area on the left. Clicking again removes the breakpoint.

## The call stack and variable areas

[#](#the-call-stack-and-variable-areas)

When your application encounters a breakpoint, it pauses there, and the DevTools debugger shows the paused execution location in the source area. In addition, the `Call stack` and `Variables` areas populate with the current call stack for the paused isolate, and the local variables for the selected frame. Selecting other frames in the `Call stack` area changes the contents of the variables.

Within the `Variables` area, you can inspect individual objects by toggling them open to see their fields. Hovering over an object in the `Variables` area calls `toString()` for that object and displays the result.

## Stepping through source code

[#](#stepping-through-source-code)

When paused, the three stepping buttons become active.

* Use **Step in** to step into a method invocation, stopping at the first executable line in that invoked method.
* Use **Step over** to step over a method invocation; this steps through source lines in the current method.
* Use **Step out** to step out of the current method, without stopping at any intermediary lines.

In addition, the **Resume** button continues regular execution of the application.

## Console output

[#](#console-output)

Console output for the running app (stdout and stderr) is displayed in the console, below the source code area. You can also see the output in the [Logging view](/tools/devtools/logging).

## Breaking on exceptions

[#](#breaking-on-exceptions)

To adjust the stop-on-exceptions behavior, toggle the **Ignore** dropdown at the top of the debugger view.

Breaking on unhandled excepts only pauses execution if the breakpoint is considered uncaught by the application code. Breaking on all exceptions causes the debugger to pause whether or not the breakpoint was caught by application code.

## Known issues

[#](#known-issues)

When performing a hot restart for a Flutter application, user breakpoints are cleared.

## Other resources

[#](#other-resources)

For more information on debugging and profiling, see the [Debugging](/testing/debugging) page.

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/debugger.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/tools/devtools/debugger\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/debugger.md "Report an issue with this page").

----
url: https://docs.flutter.dev/resources/ads-overview
----

The Google Mobile Ads SDK for Flutter works with both AdMob and Ad Manager. It supports a variety of ad formats such as app open, banner, interstitial, native, rewarded, and rewarded interstitial formats, and features like mediation.

Easily integrate Google AdMob and Ad Manager ads directly in your Flutter App

The following resources can help get you started:

* [Add ads to your mobile Flutter app or game](/cookbook/plugins/google-mobile-ads) (cookbook recipe)
* [Adding AdMob ads to a Flutter app](https://codelabs.developers.google.com/codelabs/admob-ads-in-flutter#0) (codelab)
* [Adding an AdMob banner and native inline ads to a Flutter app](https://codelabs.developers.google.com/codelabs/admob-inline-ads-in-flutter#0) (codelab)
* [How to support Google Mobile Ads (AdMob) mediation](https://developers.google.com/admob/flutter/mediation) (guide)

Listed in alphabetical order, the following packages support AdMob mediation:

* AppLovin ([gma\_mediation\_applovin](https://pub.dev/packages/gma_mediation_applovin))
* DT Exchange ([gma\_mediation\_dtexchange](https://pub.dev/packages/gma_mediation_dtexchange))
* InMobi ([gma\_mediation\_inmobi](https://pub.dev/packages/gma_mediation_inmobi))
* IronSource ([gma\_mediation\_ironsource](https://pub.dev/packages/gma_mediation_ironsource))
* Liftoff ([gma\_mediation\_liftoffmonetize](https://pub.dev/packages/gma_mediation_liftoffmonetize))
* Meta ([gma\_mediation\_meta](https://pub.dev/packages/gma_mediation_meta))
* Mintegral ([gma\_mediation\_mintegral](https://pub.dev/packages/gma_mediation_mintegral))
* Pangle ([gma\_mediation\_pangle](https://pub.dev/packages/gma_mediation_pangle))
* Unity ([gma\_mediation\_unity](https://pub.dev/packages/gma_mediation_unity))

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-11. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/resources/ads-overview.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/resources/ads-overview\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/resources/ads-overview.md "Report an issue with this page").

----
url: https://docs.flutter.dev/deployment
----

[Build and release a web app](/deployment/web)

[How to prepare for and release a web app.](/deployment/web)

[Set up Flutter flavors for Windows](/deployment/flavors-windows)

[How to create Flutter flavors for Windows desktop apps.](/deployment/flavors-windows)

[Build and release a Windows desktop app](/deployment/windows)

[How to release a Flutter app to the Microsoft Store.](/deployment/windows)

[Set up Flutter flavors for Linux](/deployment/flavors-linux)

[How to create Flutter flavors for Linux desktop apps.](/deployment/flavors-linux)

[Build and release an iOS app](/deployment/ios)

[How to release a Flutter app to the App Store.](/deployment/ios)

[Set up Flutter flavors for iOS and macOS](/deployment/flavors-ios)

[How to create Flutter flavors for an iOS or macOS app.](/deployment/flavors-ios)

[Build and release a macOS app](/deployment/macos)

[How to release a Flutter app to the macOS App Store.](/deployment/macos)

[Continuous delivery with Flutter](/deployment/cd)

[How to automate continuous building and releasing of your Flutter app.](/deployment/cd)

[Set up Flutter flavors for Android](/deployment/flavors)

[How to create build flavors specific to different release types or development environments.](/deployment/flavors)

[Build and release a Linux app to the Snap Store](/deployment/linux)

[How to prepare for and release a Linux app to the Snap store.](/deployment/linux)

[Obfuscate Dart code](/deployment/obfuscate)

[How to remove function and class names from your Dart binary.](/deployment/obfuscate)

[Build and release an Android app](/deployment/android)

[How to prepare for and release an Android app to the Play store.](/deployment/android)

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/deployment/index.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/deployment\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/deployment/index.md "Report an issue with this page").

----
url: https://docs.flutter.dev/data-and-backend/serialization
----

[JSON and serialization](/data-and-backend/serialization/json)

[How to use JSON with Flutter.](/data-and-backend/serialization/json)

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/data-and-backend/serialization/index.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/data-and-backend/serialization\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/data-and-backend/serialization/index.md "Report an issue with this page").

----
url: https://docs.flutter.dev/cookbook/effects/download-button
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Create a download button](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

Apps are filled with buttons that execute long-running behaviors. For example, a button might trigger a download, which starts a download process, receives data over time, and then provides access to the downloaded asset. It's helpful to show the user the progress of a long-running process, and the button itself is a good place to provide this feedback. In this recipe, you'll build a download button that transitions through multiple visual states, based on the status of an app download.

The following animation shows the app's behavior:

## Define a new stateless widget

[#](#define-a-new-stateless-widget)

Your button widget needs to change its appearance over time. Therefore, you need to implement your button with a custom stateless widget.

Define a new stateless widget called `DownloadButton`.

dart

```
@immutable
class DownloadButton extends StatelessWidget {
  const DownloadButton({super.key});
​
  @override
  Widget build(BuildContext context) {
    // TODO:
    return const SizedBox();
  }
}
```

## Define the button's possible visual states

[#](#define-the-buttons-possible-visual-states)

The download button's visual presentation is based on a given download status. Define the possible states of the download, and then update `DownloadButton` to accept a `DownloadStatus` and a `Duration` for how long the button should take to animate from one status to another.

dart

```
enum DownloadStatus { notDownloaded, fetchingDownload, downloading, downloaded }
​
@immutable
class DownloadButton extends StatelessWidget {
  const DownloadButton({
    super.key,
    required this.status,
    this.transitionDuration = const Duration(milliseconds: 500),
  });
​
  final DownloadStatus status;
  final Duration transitionDuration;
​
  @override
  Widget build(BuildContext context) {
    // TODO: We'll add more to this later.
    return const SizedBox();
  }
}
```

## Display the button shape

[#](#display-the-button-shape)

The download button changes its shape based on the download status. The button displays a grey, rounded rectangle during the `notDownloaded` and `downloaded` states. The button displays a transparent circle during the `fetchingDownload` and `downloading` states.

Based on the current `DownloadStatus`, build an `AnimatedContainer` with a `ShapeDecoration` that displays a rounded rectangle or a circle.

Consider defining the shape's widget tree in a separated `Stateless` widget so that the main `build()` method remains simple, allowing for the additions that follow. Instead of creating a function to return a widget, like `Widget _buildSomething() {}`, always prefer creating a `StatelessWidget` or a `StatefulWidget` which is more performant. More considerations on this can be found in the [documentation](https://api.flutter.dev/flutter/widgets/StatelessWidget-class.html) or in a dedicated video in the Flutter [YouTube channel](https://www.youtube.com/watch?v=IOyq-eTRhvo).

For now, the `AnimatedContainer` child is just a `SizedBox` because we will come back at it in another step.

dart

```
@immutable
class DownloadButton extends StatelessWidget {
  const DownloadButton({
    super.key,
    required this.status,
    this.transitionDuration = const Duration(milliseconds: 500),
  });
​
  final DownloadStatus status;
  final Duration transitionDuration;
​
  bool get _isDownloading => status == DownloadStatus.downloading;
​
  bool get _isFetching => status == DownloadStatus.fetchingDownload;
​
  bool get _isDownloaded => status == DownloadStatus.downloaded;
​
  @override
  Widget build(BuildContext context) {
    return ButtonShapeWidget(
      transitionDuration: transitionDuration,
      isDownloaded: _isDownloaded,
      isDownloading: _isDownloading,
      isFetching: _isFetching,
    );
  }
}
​
@immutable
class ButtonShapeWidget extends StatelessWidget {
  const ButtonShapeWidget({
    super.key,
    required this.isDownloading,
    required this.isDownloaded,
    required this.isFetching,
    required this.transitionDuration,
  });
​
  final bool isDownloading;
  final bool isDownloaded;
  final bool isFetching;
  final Duration transitionDuration;
​
  @override
  Widget build(BuildContext context) {
    final ShapeDecoration shape;
    if (isDownloading || isFetching) {
      shape = const ShapeDecoration(
        shape: CircleBorder(),
        color: Colors.transparent,
      );
    } else {
      shape = const ShapeDecoration(
        shape: StadiumBorder(),
        color: CupertinoColors.lightBackgroundGray,
      );
    }
​
    return AnimatedContainer(
      duration: transitionDuration,
      curve: Curves.ease,
      width: double.infinity,
      decoration: shape,
      child: const SizedBox(),
    );
  }
}
```

You might wonder why you need a `ShapeDecoration` widget for a transparent circle, given that it's invisible. The purpose of the invisible circle is to orchestrate the desired animation. The `AnimatedContainer` begins with a rounded rectangle. When the `DownloadStatus` changes to `fetchingDownload`, the `AnimatedContainer` needs to animate from a rounded rectangle to a circle, and then fade out as the animation takes place. The only way to implement this animation is to define both the beginning shape of a rounded rectangle and the ending shape of a circle. But, you don't want the final circle to be visible, so you make it transparent, which causes an animated fade-out.

## Display the button text

[#](#display-the-button-text)

The `DownloadButton` displays `GET` during the `notDownloaded` phase, `OPEN` during the `downloaded` phase, and no text in between.

Add widgets to display text during each download phase, and animate the text's opacity in between. Add the text widget tree as a child of the `AnimatedContainer` in the button wrapper widget.

dart

```
@immutable
class ButtonShapeWidget extends StatelessWidget {
  const ButtonShapeWidget({
    super.key,
    required this.isDownloading,
    required this.isDownloaded,
    required this.isFetching,
    required this.transitionDuration,
  });
​
  final bool isDownloading;
  final bool isDownloaded;
  final bool isFetching;
  final Duration transitionDuration;
​
  @override
  Widget build(BuildContext context) {
    final ShapeDecoration shape;
    if (isDownloading || isFetching) {
      shape = const ShapeDecoration(
        shape: CircleBorder(),
        color: Colors.transparent,
      );
    } else {
      shape = const ShapeDecoration(
        shape: StadiumBorder(),
        color: CupertinoColors.lightBackgroundGray,
      );
    }
​
    return AnimatedContainer(
      duration: transitionDuration,
      curve: Curves.ease,
      width: double.infinity,
      decoration: shape,
      child: Padding(
        padding: const EdgeInsets.symmetric(vertical: 6),
        child: AnimatedOpacity(
          duration: transitionDuration,
          opacity: isDownloading || isFetching ? 0.0 : 1.0,
          curve: Curves.ease,
          child: Text(
            isDownloaded ? 'OPEN' : 'GET',
            textAlign: TextAlign.center,
            style: Theme.of(context).textTheme.labelLarge?.copyWith(
              fontWeight: FontWeight.bold,
              color: CupertinoColors.activeBlue,
            ),
          ),
        ),
      ),
    );
  }
}
```

## Display a spinner while fetching download

[#](#display-a-spinner-while-fetching-download)

During the `fetchingDownload` phase, the `DownloadButton` displays a radial spinner. This spinner fades in from the `notDownloaded` phase and fades out to the `fetchingDownload` phase.

Implement a radial spinner that sits on top of the button shape and fades in and out at the appropriate times.

We have removed the `ButtonShapeWidget`'s constructor to keep the focus on its build method and the `Stack` widget we've added.

dart

```
@override
Widget build(BuildContext context) {
  return GestureDetector(
    onTap: _onPressed,
    child: Stack(
      children: [
        ButtonShapeWidget(
          transitionDuration: transitionDuration,
          isDownloaded: _isDownloaded,
          isDownloading: _isDownloading,
          isFetching: _isFetching,
        ),
        Positioned.fill(
          child: AnimatedOpacity(
            duration: transitionDuration,
            opacity: _isDownloading || _isFetching ? 1.0 : 0.0,
            curve: Curves.ease,
            child: ProgressIndicatorWidget(
              downloadProgress: downloadProgress,
              isDownloading: _isDownloading,
              isFetching: _isFetching,
            ),
          ),
        ),
      ],
    ),
  );
}
```

## Display the progress and a stop button while downloading

[#](#display-the-progress-and-a-stop-button-while-downloading)

After the `fetchingDownload` phase is the `downloading` phase. During the `downloading` phase, the `DownloadButton` replaces the radial progress spinner with a growing radial progress bar. The `DownloadButton` also displays a stop button icon so that the user can cancel an in-progress download.

Add a progress property to the `DownloadButton` widget, and then update the progress display to switch to a radial progress bar during the `downloading` phase.

Next, add a stop button icon at the center of the radial progress bar.

dart

```
@override
Widget build(BuildContext context) {
  return GestureDetector(
    onTap: _onPressed,
    child: Stack(
      children: [
        ButtonShapeWidget(
          transitionDuration: transitionDuration,
          isDownloaded: _isDownloaded,
          isDownloading: _isDownloading,
          isFetching: _isFetching,
        ),
        Positioned.fill(
          child: AnimatedOpacity(
            duration: transitionDuration,
            opacity: _isDownloading || _isFetching ? 1.0 : 0.0,
            curve: Curves.ease,
            child: Stack(
              alignment: Alignment.center,
              children: [
                ProgressIndicatorWidget(
                  downloadProgress: downloadProgress,
                  isDownloading: _isDownloading,
                  isFetching: _isFetching,
                ),
                if (_isDownloading)
                  const Icon(
                    Icons.stop,
                    size: 14.0,
                    color: CupertinoColors.activeBlue,
                  ),
              ],
            ),
          ),
        ),
      ],
    ),
  );
}
```

## Add button tap callbacks

[#](#add-button-tap-callbacks)

The last detail that your `DownloadButton` needs is the button behavior. The button must do things when the user taps it.

Add widget properties for callbacks to start a download, cancel a download, and open a download.

Finally, wrap `DownloadButton`'s existing widget tree with a `GestureDetector` widget, and forward the tap event to the corresponding callback property.

dart

```
@immutable
class DownloadButton extends StatelessWidget {
  const DownloadButton({
    super.key,
    required this.status,
    this.downloadProgress = 0,
    required this.onDownload,
    required this.onCancel,
    required this.onOpen,
    this.transitionDuration = const Duration(milliseconds: 500),
  });
​
  final DownloadStatus status;
  final double downloadProgress;
  final VoidCallback onDownload;
  final VoidCallback onCancel;
  final VoidCallback onOpen;
  final Duration transitionDuration;
​
  bool get _isDownloading => status == DownloadStatus.downloading;
​
  bool get _isFetching => status == DownloadStatus.fetchingDownload;
​
  bool get _isDownloaded => status == DownloadStatus.downloaded;
​
  void _onPressed() {
    switch (status) {
      case DownloadStatus.notDownloaded:
        onDownload();
      case DownloadStatus.fetchingDownload:
        // do nothing.
        break;
      case DownloadStatus.downloading:
        onCancel();
      case DownloadStatus.downloaded:
        onOpen();
    }
  }
​
  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: _onPressed,
      child: const Stack(
        children: [
          /* ButtonShapeWidget and progress indicator */
        ],
      ),
    );
  }
}
```

Congratulations! You have a button that changes its display depending on which phase the button is in: not downloaded, fetching download, downloading, and downloaded. Now, the user can tap to start a download, tap to cancel an in-progress download, and tap to open a completed download.

## Interactive example

[#](#interactive-example)

Run the app:

* Click the **GET** button to kick off a simulated download.
* The button changes to a progress indicator to simulate an in-progress download.
* When the simulated download is complete, the button transitions to **OPEN**, to indicate that the app is ready for the user to open the downloaded asset.

```
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';

void main() {
  runApp(
    const MaterialApp(
      home: ExampleCupertinoDownloadButton(),
      debugShowCheckedModeBanner: false,
    ),
  );
}

@immutable
class ExampleCupertinoDownloadButton extends StatefulWidget {
  const ExampleCupertinoDownloadButton({super.key});

  @override
  State<ExampleCupertinoDownloadButton> createState() =>
      _ExampleCupertinoDownloadButtonState();
}

class _ExampleCupertinoDownloadButtonState
    extends State<ExampleCupertinoDownloadButton> {
  late final List<DownloadController> _downloadControllers;

  @override
  void initState() {
    super.initState();
    _downloadControllers = List<DownloadController>.generate(
      20,
      (index) => SimulatedDownloadController(
        onOpenDownload: () {
          _openDownload(index);
        },
      ),
    );
  }

  void _openDownload(int index) {
    ScaffoldMessenger.of(context)
        .showSnackBar(SnackBar(content: Text('Open App ${index + 1}')));
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Apps')),
      body: ListView.separated(
        itemCount: _downloadControllers.length,
        separatorBuilder: (_, _) => const Divider(),
        itemBuilder: _buildListItem,
      ),
    );
  }

  Widget _buildListItem(BuildContext context, int index) {
    final theme = Theme.of(context);
    final downloadController = _downloadControllers[index];

    return ListTile(
      leading: const DemoAppIcon(),
      title: Text(
        'App ${index + 1}',
        overflow: TextOverflow.ellipsis,
        style: theme.textTheme.titleLarge,
      ),
      subtitle: Text(
        'Lorem ipsum dolor #${index + 1}',
        overflow: TextOverflow.ellipsis,
        style: theme.textTheme.bodySmall,
      ),
      trailing: SizedBox(
        width: 96,
        child: AnimatedBuilder(
          animation: downloadController,
          builder: (context, child) {
            return DownloadButton(
              status: downloadController.downloadStatus,
              downloadProgress: downloadController.progress,
              onDownload: downloadController.startDownload,
              onCancel: downloadController.stopDownload,
              onOpen: downloadController.openDownload,
            );
          },
        ),
      ),
    );
  }
}

@immutable
class DemoAppIcon extends StatelessWidget {
  const DemoAppIcon({super.key});

  @override
  Widget build(BuildContext context) {
    return const AspectRatio(
      aspectRatio: 1,
      child: FittedBox(
        child: SizedBox(
          width: 80,
          height: 80,
          child: DecoratedBox(
            decoration: BoxDecoration(
              gradient: LinearGradient(colors: [Colors.red, Colors.blue]),
              borderRadius: BorderRadius.all(Radius.circular(20)),
            ),
            child: Center(
              child: Icon(Icons.ac_unit, color: Colors.white, size: 40),
            ),
          ),
        ),
      ),
    );
  }
}

enum DownloadStatus { notDownloaded, fetchingDownload, downloading, downloaded }

abstract class DownloadController implements ChangeNotifier {
  DownloadStatus get downloadStatus;
  double get progress;

  void startDownload();
  void stopDownload();
  void openDownload();
}

class SimulatedDownloadController extends DownloadController
    with ChangeNotifier {
  SimulatedDownloadController({
    this._downloadStatus = DownloadStatus.notDownloaded,
    this._progress = 0.0,
    required this._onOpenDownload,
  });

  DownloadStatus _downloadStatus;
  @override
  DownloadStatus get downloadStatus => _downloadStatus;

  double _progress;
  @override
  double get progress => _progress;

  final VoidCallback _onOpenDownload;

  bool _isDownloading = false;

  @override
  void startDownload() {
    if (downloadStatus == DownloadStatus.notDownloaded) {
      _doSimulatedDownload();
    }
  }

  @override
  void stopDownload() {
    if (_isDownloading) {
      _isDownloading = false;
      _downloadStatus = DownloadStatus.notDownloaded;
      _progress = 0.0;
      notifyListeners();
    }
  }

  @override
  void openDownload() {
    if (downloadStatus == DownloadStatus.downloaded) {
      _onOpenDownload();
    }
  }

  Future<void> _doSimulatedDownload() async {
    _isDownloading = true;
    _downloadStatus = DownloadStatus.fetchingDownload;
    notifyListeners();

    // Wait a second to simulate fetch time.
    await Future<void>.delayed(const Duration(seconds: 1));

    // If the user chose to cancel the download, stop the simulation.
    if (!_isDownloading) {
      return;
    }

    // Shift to the downloading phase.
    _downloadStatus = DownloadStatus.downloading;
    notifyListeners();

    const downloadProgressStops = [0.0, 0.15, 0.45, 0.8, 1.0];
    for (final stop in downloadProgressStops) {
      // Wait a second to simulate varying download speeds.
      await Future<void>.delayed(const Duration(seconds: 1));

      // If the user chose to cancel the download, stop the simulation.
      if (!_isDownloading) {
        return;
      }

      // Update the download progress.
      _progress = stop;
      notifyListeners();
    }

    // Wait a second to simulate a final delay.
    await Future<void>.delayed(const Duration(seconds: 1));

    // If the user chose to cancel the download, stop the simulation.
    if (!_isDownloading) {
      return;
    }

    // Shift to the downloaded state, completing the simulation.
    _downloadStatus = DownloadStatus.downloaded;
    _isDownloading = false;
    notifyListeners();
  }
}

@immutable
class DownloadButton extends StatelessWidget {
  const DownloadButton({
    super.key,
    required this.status,
    this.downloadProgress = 0.0,
    required this.onDownload,
    required this.onCancel,
    required this.onOpen,
    this.transitionDuration = const Duration(milliseconds: 500),
  });

  final DownloadStatus status;
  final double downloadProgress;
  final VoidCallback onDownload;
  final VoidCallback onCancel;
  final VoidCallback onOpen;
  final Duration transitionDuration;

  bool get _isDownloading => status == DownloadStatus.downloading;

  bool get _isFetching => status == DownloadStatus.fetchingDownload;

  bool get _isDownloaded => status == DownloadStatus.downloaded;

  void _onPressed() {
    switch (status) {
      case DownloadStatus.notDownloaded:
        onDownload();
      case DownloadStatus.fetchingDownload:
        // do nothing.
        break;
      case DownloadStatus.downloading:
        onCancel();
      case DownloadStatus.downloaded:
        onOpen();
    }
  }

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: _onPressed,
      child: Stack(
        children: [
          ButtonShapeWidget(
            transitionDuration: transitionDuration,
            isDownloaded: _isDownloaded,
            isDownloading: _isDownloading,
            isFetching: _isFetching,
          ),
          Positioned.fill(
            child: AnimatedOpacity(
              duration: transitionDuration,
              opacity: _isDownloading || _isFetching ? 1.0 : 0.0,
              curve: Curves.ease,
              child: Stack(
                alignment: Alignment.center,
                children: [
                  ProgressIndicatorWidget(
                    downloadProgress: downloadProgress,
                    isDownloading: _isDownloading,
                    isFetching: _isFetching,
                  ),
                  if (_isDownloading)
                    const Icon(
                      Icons.stop,
                      size: 14,
                      color: CupertinoColors.activeBlue,
                    ),
                ],
              ),
            ),
          ),
        ],
      ),
    );
  }
}

@immutable
class ButtonShapeWidget extends StatelessWidget {
  const ButtonShapeWidget({
    super.key,
    required this.isDownloading,
    required this.isDownloaded,
    required this.isFetching,
    required this.transitionDuration,
  });

  final bool isDownloading;
  final bool isDownloaded;
  final bool isFetching;
  final Duration transitionDuration;

  @override
  Widget build(BuildContext context) {
    final ShapeDecoration shape;
    if (isDownloading || isFetching) {
      shape = const ShapeDecoration(
        shape: CircleBorder(),
        color: Colors.transparent,
      );
    } else {
      shape = const ShapeDecoration(
        shape: StadiumBorder(),
        color: CupertinoColors.lightBackgroundGray,
      );
    }

    return AnimatedContainer(
      duration: transitionDuration,
      curve: Curves.ease,
      width: double.infinity,
      decoration: shape,
      child: Padding(
        padding: const EdgeInsets.symmetric(vertical: 6),
        child: AnimatedOpacity(
          duration: transitionDuration,
          opacity: isDownloading || isFetching ? 0.0 : 1.0,
          curve: Curves.ease,
          child: Text(
            isDownloaded ? 'OPEN' : 'GET',
            textAlign: TextAlign.center,
            style: Theme.of(context).textTheme.labelLarge?.copyWith(
              fontWeight: FontWeight.bold,
              color: CupertinoColors.activeBlue,
            ),
          ),
        ),
      ),
    );
  }
}

@immutable
class ProgressIndicatorWidget extends StatelessWidget {
  const ProgressIndicatorWidget({
    super.key,
    required this.downloadProgress,
    required this.isDownloading,
    required this.isFetching,
  });

  final double downloadProgress;
  final bool isDownloading;
  final bool isFetching;

  @override
  Widget build(BuildContext context) {
    return AspectRatio(
      aspectRatio: 1,
      child: TweenAnimationBuilder<double>(
        tween: Tween(begin: 0, end: downloadProgress),
        duration: const Duration(milliseconds: 200),
        builder: (context, progress, child) {
          return CircularProgressIndicator(
            backgroundColor: isDownloading
                ? CupertinoColors.lightBackgroundGray
                : Colors.transparent,
            valueColor: AlwaysStoppedAnimation(
              isFetching
                  ? CupertinoColors.lightBackgroundGray
                  : CupertinoColors.activeBlue,
            ),
            strokeWidth: 2,
            value: isFetching ? null : progress,
          );
        },
      ),
    );
  }
}
```

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-08-24. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/effects/download-button.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/cookbook/effects/download-button\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/effects/download-button.md "Report an issue with this page").

----
url: https://docs.flutter.dev/cookbook/forms/validation.md
----

\# Build a form with validation > How to build a form that validates input.

<!--?code-excerpt path-base="cookbook/forms/validation"?-->

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. 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, process the information. If the user submits incorrect information, display a friendly error message letting them know what went wrong. In this example, learn how to add validation to a form that has a single text field using the following steps: 1. Create a \`Form\` with a \`GlobalKey\`. 1. Add a \`TextFormField\` with validation logic. 1. Create a button to validate and submit the form. ## 1. Create a \`Form\` with a \`GlobalKey\` Create a \[\`Form\`]\[]. The \`Form\` widget acts as a container for grouping and validating multiple form fields. When creating the form, provide a \[\`GlobalKey\`]\[]. This assigns a unique identifier to your \`Form\`. It also allows you to validate the form later. Create the form as a \`StatefulWidget\`. This allows you to create a unique \`GlobalKey()\` once. You can then store it as a variable and access it at different points. If you made this a \`StatelessWidget\`, you'd need to store this key \_somewhere\_. As it is resource expensive, you wouldn't want to generate a new \`GlobalKey\` each time you run the \`build\` method.

<!--?code-excerpt "lib/form.dart"?-->

\`\`\`dart import 'package:flutter/material.dart'; // Define a custom Form widget. class MyCustomForm extends StatefulWidget { const MyCustomForm({super.key}); @override State createState() => \_MyCustomFormState(); } // Define a corresponding State class. // This class holds data related to the form. class \_MyCustomFormState extends State { // Create a global key that uniquely identifies the Form widget // and allows validation of the form. // // Note: This is a \`GlobalKey\`, // not a GlobalKey<\_MyCustomFormState>. final \_formKey = GlobalKey(); @override Widget build(BuildContext context) { // Build a Form widget using the \_formKey created above. return Form( key: \_formKey, child: const Column( children: \[ // Add TextFormFields and ElevatedButton here. ], ), ); } } \`\`\` :::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()\`]\[] method to access the form within nested widgets. ::: ## 2. Add a \`TextFormField\` with validation logic Although the \`Form\` is in place, it doesn't have a way for users to enter text. That's the job of a \[\`TextFormField\`]\[]. The \`TextFormField\` widget renders a Material Design text field and can display validation errors when they occur. Validate the input by providing a \`validator()\` function to the \`TextFormField\`. If the user's input isn't valid, the \`validator\` function returns a \`String\` containing an error message. If there are no errors, the validator must return \`null\`. For this example, create a \`validator\` that ensures the \`TextFormField\` isn't empty. If it is empty, return a friendly error message.

<!--?code-excerpt "lib/main.dart (TextFormField)"?-->

\`\`\`dart TextFormField( // The validator receives the text that the user has entered. validator: (value) { if (value == null || value.isEmpty) { return 'Please enter some text'; } return null; }, ), \`\`\` :::tip To validate fields automatically as the user interacts with them or when a field loses focus, configure the \[\`autovalidateMode\`]\[] property on your \`Form\` or \`TextFormField\` using \[\`AutovalidateMode\`]\[] (such as \`AutovalidateMode.onUserInteraction\` or \`AutovalidateMode.onUnfocus\`). ::: ## 3. Create a button to validate and submit the form 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, check if the form is valid. If it is, display a success message. If it isn't (the text field has no content) display the error message.

<!--?code-excerpt "lib/main.dart (ElevatedButton)" replace="/^child\: //g"?-->

\`\`\`dart ElevatedButton( onPressed: () { // Validate returns true if the form is valid, or false otherwise. if (\_formKey.currentState!.validate()) { // 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. ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Processing Data')), ); } }, child: const Text('Submit'), ), \`\`\` ### How does this work? To validate the form, use the \`\_formKey\` created in step 1. You can use the \`\_formKey.currentState\` accessor to access the \[\`FormState\`]\[], which is automatically created by Flutter when building a \`Form\`. 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 \`validate()\` method returns \`true\`. If any text field contains errors, the \`validate()\` method rebuilds the form to display any error messages and returns \`false\`. ## Interactive example

<!--?code-excerpt "lib/main.dart"?-->

\`\`\`dartpad title="Flutter form validation hands-on example in DartPad" run="true" import 'package:flutter/material.dart'; void main() => runApp(const MyApp()); class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { const appTitle = 'Form Validation Demo'; return MaterialApp( title: appTitle, home: Scaffold( appBar: AppBar(title: const Text(appTitle)), body: const MyCustomForm(), ), ); } } // Create a Form widget. class MyCustomForm extends StatefulWidget { const MyCustomForm({super.key}); @override State createState() => \_MyCustomFormState(); } // Create a corresponding State class. // This class holds data related to the form. class \_MyCustomFormState extends State { // Create a global key that uniquely identifies the Form widget // and allows validation of the form. // // Note: This is a GlobalKey, // not a GlobalKey<\_MyCustomFormState>. final \_formKey = GlobalKey(); @override Widget build(BuildContext context) { // Build a Form widget using the \_formKey created above. return Form( key: \_formKey, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: \[ TextFormField( // The validator receives the text that the user has entered. validator: (value) { if (value == null || value.isEmpty) { return 'Please enter some text'; } return null; }, ), Padding( padding: const EdgeInsets.symmetric(vertical: 16), child: ElevatedButton( onPressed: () { // Validate returns true if the form is valid, or false otherwise. if (\_formKey.currentState!.validate()) { // 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. ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Processing Data')), ); } }, child: const Text('Submit'), ), ), ], ), ); } } \`\`\` To learn how to retrieve these values, consult the \[Retrieve the value of a text field]\[] recipe. \[\`AutovalidateMode\`]: https\://api.flutter.dev/flutter/widgets/AutovalidateMode.html \[\`autovalidateMode\`]: https\://api.flutter.dev/flutter/widgets/Form/autovalidateMode.html \[Retrieve the value of a text field]: /cookbook/forms/retrieve-input \[\`Form\`]: https\://api.flutter.dev/flutter/widgets/Form-class.html \[\`Form.of()\`]: https\://api.flutter.dev/flutter/widgets/Form/of.html \[\`FormState\`]: https\://api.flutter.dev/flutter/widgets/FormState-class.html \[\`GlobalKey\`]: https\://api.flutter.dev/flutter/widgets/GlobalKey-class.html \[\`TextFormField\`]: https\://pub.dev/documentation/material\_ui/latest/material\_ui/TextFormField-class.html

----
url: https://docs.flutter.dev/testing/build-modes.md
----

\# Flutter's build modes > Describes Flutter's build modes and when you should use debug, release, or profile mode. The Flutter tooling supports three modes when compiling your app, and a headless mode for testing. You choose a compilation mode depending on where you are in the development cycle. Are you debugging your code? Do you need profiling information? Are you ready to deploy your app? A quick summary for when to use which mode is as follows: \* Use \[debug]\(#debug) mode during development, when you want to use \[hot reload]\[]. \* Use \[profile]\(#profile) mode when you want to analyze performance. \* Use \[release]\(#release) mode when you are ready to release your app. The rest of the page details these modes. \* To learn about the headless testing mode, refer to the engine wiki's docs on \[Flutter's build modes]\[]. \* To learn how to detect the build mode, check out the \[Check for Debug/Release Mode in Flutter Apps] blog post. \[Check for Debug/Release Mode in Flutter Apps]: https\://retroportalstudio.medium.com/check-for-debug-release-mode-in-flutter-apps-d8d545f20da3 ## Debug In \_debug mode\_, the app is set up for debugging on the physical device, emulator, or simulator. Debug mode for mobile apps mean that: \* \[Assertions]\[] are enabled. \* Service extensions are enabled. \* Compilation is optimized for fast development and run cycles (but not for execution speed, binary size, or deployment). \* Debugging is enabled, and tools supporting source level debugging (such as \[DevTools]\[]) can connect to the process. Debug mode for a web app means that: \* The build is \_not\_ minified and tree shaking has \_not\_ been performed. \* The app is compiled with the \[dartdevc]\[] compiler for easier debugging. By default, \`flutter run\` compiles to debug mode. Your IDE supports this mode. Android Studio, for example, provides a \*\*Run > Debug...\*\* menu option, as well as a green bug icon overlaid with a small triangle on the project page. :::note \* Hot reload works \_only\_ in debug mode. \* The emulator and simulator execute \_only\_ in debug mode. \* Application performance can be janky in debug mode. Measure performance in \[profile]\(#profile) mode on an actual device. ::: ## Release Use \_release mode\_ for deploying the app, when you want maximum optimization and minimal footprint size. For mobile, release mode (which is not supported on the simulator or emulator), means that: \* Assertions are disabled. \* Debugging information is stripped out. \* Debugging is disabled. \* Compilation is optimized for fast startup, fast execution, and small package sizes. \* Service extensions are disabled. Release mode for a web app means that: \* The build is minified and tree shaking has been performed. \* The app is compiled with the \[dart2js]\[] compiler for best performance. The command \`flutter run --release\` compiles to release mode. Your IDE supports this mode. Android Studio, for example, provides a \*\*Run > Run...\*\* menu option, as well as a triangular green run button icon on the project page. You can compile to release mode for a specific target with \`flutter build \`. For a list of supported targets, use \`flutter help build\`. For more information, see the docs on releasing \[iOS]\[] and \[Android]\[] apps. ## Profile In \_profile mode\_, some debugging ability is maintained—enough to profile your app's performance. Profile mode is disabled on the emulator and simulator, because their behavior is not representative of real performance. On mobile, profile mode is similar to release mode, with the following differences: \* Some service extensions, such as the one that enables the performance overlay, are enabled. \* Tracing is enabled, and tools supporting source-level debugging (such as \[DevTools]\[]) can connect to the process. Profile mode for a web app means that: \* The build is \_not\_ minified but tree shaking has been performed. \* The app is compiled with the \[dart2js]\[] compiler. \* DevTools can't connect to a Flutter web app running in profile mode. Use Chrome DevTools to \[generate timeline events]\[] for a web app. Your IDE supports this mode. Android Studio, for example, provides a \*\*Run > Profile...\*\* menu option. The command \`flutter run --profile\` compiles to profile mode. :::note Use the \[DevTools]\[] suite to profile your app's performance. ::: For more information on the build modes, see \[Flutter's build modes]\[]. \[Android]: /deployment/android \[Assertions]: https\://dart.dev/language/error-handling#assert \[dart2js]: https\://dart.dev/tools/dart2js \[dartdevc]: https\://dart.dev/tools/dartdevc \[DevTools]: /tools/devtools \[Flutter's build modes]: https\://github.com/flutter/flutter/blob/main/docs/engine/Flutter's-modes.md \[generate timeline events]: https\://developers.google.com/web/tools/chrome-devtools/evaluate-performance/performance-reference \[hot reload]: /tools/hot-reload \[iOS]: /deployment/ios

----
url: https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.51.1
----

The 2.51.1 release of the Dart and Flutter DevTools includes the following changes among other general improvements. To learn more about DevTools, check out the [DevTools overview](/tools/devtools/overview).

## General updates

[#](#general-updates)

* Flutter beta channel users were opted into the DevTools-on-Wasm experiment. All other users can still enable the Wasm-compiled DevTools from the settings dialog. - [#9455](https://github.com/flutter/devtools/pull/9455)
* Added a horizontal scrollbar to data tables to help with navigation. - [#9482](https://github.com/flutter/devtools/pull/9482)
* Made it possible to resize data table columns by dragging the header separators. - [#9485](https://github.com/flutter/devtools/pull/9485)

## Inspector updates

[#](#inspector-updates)

* Fixed an issue where selecting a widget with the Inspector would open the widget definition file instead of the user's project file. - [#176530](https://github.com/flutter/flutter/pull/176530)

To find a complete list of changes in this release, check out the [DevTools git log](https://github.com/flutter/devtools/tree/v2.51.1).

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/release-notes/release-notes-2.51.1.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.51.1\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/release-notes/release-notes-2.51.1.md "Report an issue with this page").

----
url: https://docs.flutter.dev/tools/devtools/logging.md
----

\# Use the Logging view > Learn how to use the DevTools logging view. :::note The logging view works with all Flutter and Dart applications. ::: ## What is it? The logging view displays events from the Dart runtime, application frameworks (like Flutter), and application-level logging events. ## Standard logging events By default, the logging view shows: \* Garbage collection events from the Dart runtime \* Flutter framework events, like frame creation events \* \`stdout\` and \`stderr\` from applications \* Custom logging events from applications !\[Screenshot of a logging view]\(/assets/images/docs/tools/devtools/logging\_log\_entries.png){:width="100%"} ## Logging from your application To implement logging in your code, see the \[Logging]\[] section in the \[Debugging Flutter apps programmatically]\[] page. ## Clearing logs To clear the log entries in the logging view, click the \*\*Clear logs\*\* button. \[Logging]: /testing/code-debugging#add-logging-to-your-application \[Debugging Flutter apps programmatically]: /testing/code-debugging ## Other resources To learn about different methods of logging and how to effectively use DevTools to analyze and debug Flutter apps faster, check out a guided \[Logging View tutorial]\[logging-tutorial]. \[logging-tutorial]: https\://medium.com/@fluttergems/mastering-dart-flutter-devtools-logging-view-part-5-of-8-b634f3a3af26

----
url: https://docs.flutter.dev/flutter-for/xamarin-forms-devs
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Flutter for Xamarin.Forms developers](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

This document is meant for Xamarin.Forms developers looking to apply their existing knowledge to build mobile apps with Flutter. If you understand the fundamentals of the Xamarin.Forms framework, then you can use this document as a jump start to Flutter development.

Your Android and iOS knowledge and skill set are valuable when building with Flutter, because Flutter relies on the native operating system configurations, similar to how you would configure your native Xamarin.Forms projects. The Flutter Frameworks is also similar to how you create a single UI, that is used on multiple platforms.

This document can be used as a cookbook by jumping around and finding questions that are most relevant to your needs.

## Project setup

[#](#project-setup)

### How does the app start?

[#](#how-does-the-app-start)

For each platform in Xamarin.Forms, you call the `LoadApplication` method, which creates a new application and starts your app.

csharp

```
LoadApplication(new App());
```

In Flutter, the default main entry point is `main` where you load your Flutter app.

dart

```
void main() {
  runApp(const MyApp());
}
```

In Xamarin.Forms, you assign a `Page` to the `MainPage` property in the `Application` class.

csharp

```
public class App : Application
{
    public App()
    {
        MainPage = new ContentPage
        {
            Content = new Label
            {
                Text = "Hello World",
                HorizontalOptions = LayoutOptions.Center,
                VerticalOptions = LayoutOptions.Center
            }
        };
    }
}
```

In Flutter, "everything is a widget", even the application itself. The following example shows `MyApp`, a simple application `Widget`.

dart

```
class MyApp extends StatelessWidget {
  /// This widget is the root of your application.
  const MyApp({super.key});
​
  @override
  Widget build(BuildContext context) {
    return const Center(
      child: Text('Hello World!', textDirection: TextDirection.ltr),
    );
  }
}
```

### How do you create a page?

[#](#how-do-you-create-a-page)

Xamarin.Forms has many types of pages; `ContentPage` is the most common. In Flutter, you specify an application widget that holds your root page. You can use a [`MaterialApp`](https://pub.dev/documentation/material_ui/latest/material_ui/MaterialApp-class.html) widget, which supports [Material Design](https://m3.material.io/styles), or you can use a [`CupertinoApp`](https://pub.dev/documentation/cupertino_ui/latest/cupertino_ui/CupertinoApp-class.html) widget, which supports an iOS-style app, or you can use the lower level [`WidgetsApp`](https://api.flutter.dev/flutter/widgets/WidgetsApp-class.html), which you can customize in any way you want.

The following code defines the home page, a stateful widget. In Flutter, all widgets are immutable, but two types of widgets are supported: *Stateful* and *Stateless*. Examples of a stateless widget are titles, icons, or images.

The following example uses `MaterialApp`, which holds its root page in the `home` property.

dart

```
class MyApp extends StatelessWidget {
  /// This widget is the root of your application.
  const MyApp({super.key});
​
  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      title: 'Flutter Demo',
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}
```

From here, your actual first page is another `Widget`, in which you create your state.

A *Stateful* widget, such as `MyHomePage` below, consists of two parts. The first part, which is itself immutable, creates a `State` object that holds the state of the object. The `State` object persists over the life of the widget.

dart

```
class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});
​
  final String title;
​
  @override
  State<MyHomePage> createState() => _MyHomePageState();
}
```

The `State` object implements the `build()` method for the stateful widget.

When the state of the widget tree changes, call `setState()`, which triggers a build of that portion of the UI. Make sure to call `setState()` only when necessary, and only on the part of the widget tree that has changed, or it can result in poor UI performance.

dart

```
class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;
​
  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }
​
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        // Take the value from the MyHomePage object that was created by
        // the App.build method, and use it to set the appbar title.
        title: Text(widget.title),
      ),
      body: Center(
        // Center is a layout widget. It takes a single child and positions it
        // in the middle of the parent.
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            const Text('You have pushed the button this many times:'),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.headlineMedium,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ),
    );
  }
}
```

In Flutter, the UI (also known as widget tree), is immutable, meaning you can't change its state once it's built. You change fields in your `State` class, then call `setState()` to rebuild the entire widget tree again.

This way of generating UI is different from Xamarin.Forms, but there are many benefits to this approach.

## Views

[#](#views)

### What is the equivalent of a Page or Element in Flutter?

[#](#what-is-the-equivalent-of-a-page-or-element-in-flutter)

`ContentPage`, `TabbedPage`, `FlyoutPage` are all types of pages you might use in a Xamarin.Forms application. These pages would then hold `Element`s to display the various controls. In Xamarin.Forms an `Entry` or `Button` are examples of an `Element`.

In Flutter, almost everything is a widget. A `Page`, called a `Route` in Flutter, is a widget. Buttons, progress bars, and animation controllers are all widgets. When building a route, you create a widget tree.

Flutter includes the [Material Components](/ui/widgets/material) library. These are widgets that implement the [Material Design guidelines](https://m3.material.io/styles). Material Design is a flexible design system [optimized for all platforms](https://m2.material.io/design/platform-guidance/cross-platform-adaptation.html#cross-platform-guidelines), including iOS.

But Flutter is flexible and expressive enough to implement any design language. For example, on iOS, you can use the [Cupertino widgets](/ui/widgets/cupertino) to produce an interface that looks like [Apple's iOS design language](https://developer.apple.com/design/resources/).

### How do I update widgets?

[#](#how-do-i-update-widgets)

In Xamarin.Forms, each `Page` or `Element` is a stateful class, that has properties and methods. You update your `Element` by updating a property, and this is propagated down to the native control.

In Flutter, `Widget`s are immutable and you can't directly update them by changing a property, instead you have to work with the widget's state.

This is where the concept of Stateful vs Stateless widgets comes from. A `StatelessWidget` is just what it sounds like— a widget with no state information.

`StatelessWidgets` are useful when the part of the user interface you are describing doesn't depend on anything other than the configuration information in the object.

For example, in Xamarin.Forms, this is similar to placing an `Image` with your logo. The logo is not going to change during runtime, so use a `StatelessWidget` in Flutter.

If you want to dynamically change the UI based on data received after making an HTTP call or a user interaction, then you have to work with `StatefulWidget` and tell the Flutter framework that the widget's `State` has been updated, so it can update that widget.

The important thing to note here is at the core both stateless and stateful widgets behave the same. They rebuild every frame, the difference is the `StatefulWidget` has a `State` object that stores state data across frames and restores it.

If you are in doubt, then always remember this rule: if a widget changes (because of user interactions, for example) it's stateful. However, if a widget reacts to change, the containing parent widget can still be stateless if it doesn't itself react to change.

The following example shows how to use a `StatelessWidget`. A common `StatelessWidget` is the `Text` widget. If you look at the implementation of the `Text` widget you'll find it subclasses `StatelessWidget`.

dart

```
const Text(
  'I like Flutter!',
  style: TextStyle(fontWeight: FontWeight.bold),
);
```

As you can see, the `Text` widget has no state information associated with it, it renders what is passed in its constructors and nothing more.

But, what if you want to make "I Like Flutter" change dynamically, for example, when clicking a `FloatingActionButton`?

To achieve this, wrap the `Text` widget in a `StatefulWidget` and update it when the user clicks the button, as shown in the following example:

dart

```
import 'package:material_ui/material_ui.dart';
​
void main() {
  runApp(const SampleApp());
}
​
class SampleApp extends StatelessWidget {
  /// This widget is the root of your application.
  const SampleApp({super.key});
​
  @override
  Widget build(BuildContext context) {
    return const MaterialApp(title: 'Sample App', home: SampleAppPage());
  }
}
​
class SampleAppPage extends StatefulWidget {
  const SampleAppPage({super.key});
​
  @override
  State<SampleAppPage> createState() => _SampleAppPageState();
}
​
class _SampleAppPageState extends State<SampleAppPage> {
  /// Default placeholder text
  String textToShow = 'I Like Flutter';
​
  void _updateText() {
    setState(() {
      // Update the text
      textToShow = 'Flutter is Awesome!';
    });
  }
​
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Sample App')),
      body: Center(child: Text(textToShow)),
      floatingActionButton: FloatingActionButton(
        onPressed: _updateText,
        tooltip: 'Update Text',
        child: const Icon(Icons.update),
      ),
    );
  }
}
```

### How do I lay out my widgets? What is the equivalent of an XAML file?

[#](#how-do-i-lay-out-my-widgets-what-is-the-equivalent-of-an-xaml-file)

In Xamarin.Forms, most developers write layouts in XAML, though sometimes in C#. In Flutter, you write your layouts with a widget tree in code.

The following example shows how to display a simple widget with padding:

dart

```
@override
Widget build(BuildContext context) {
  return Scaffold(
    appBar: AppBar(title: const Text('Sample App')),
    body: Center(
      child: ElevatedButton(
        style: ElevatedButton.styleFrom(
          padding: const EdgeInsets.only(left: 20, right: 30),
        ),
        onPressed: () {},
        child: const Text('Hello'),
      ),
    ),
  );
}
```

You can view the layouts that Flutter has to offer in the [widget catalog](/ui/widgets/layout).

### How do I add or remove an Element from my layout?

[#](#how-do-i-add-or-remove-an-element-from-my-layout)

In Xamarin.Forms, you had to remove or add an `Element` in code. This involved either setting the `Content` property or calling `Add()` or `Remove()` if it was a list.

In Flutter, because widgets are immutable there is no direct equivalent. Instead, you can pass a function to the parent that returns a widget, and control that child's creation with a boolean flag.

The following example shows how to toggle between two widgets when the user clicks the `FloatingActionButton`:

dart

```
class SampleApp extends StatelessWidget {
  /// This widget is the root of your application.
  const SampleApp({super.key});
​
  @override
  Widget build(BuildContext context) {
    return const MaterialApp(title: 'Sample App', home: SampleAppPage());
  }
}
​
class SampleAppPage extends StatefulWidget {
  const SampleAppPage({super.key});
​
  @override
  State<SampleAppPage> createState() => _SampleAppPageState();
}
​
class _SampleAppPageState extends State<SampleAppPage> {
  /// Default value for toggle
  bool toggle = true;
  void _toggle() {
    setState(() {
      toggle = !toggle;
    });
  }
​
  Widget _getToggleChild() {
    if (toggle) {
      return const Text('Toggle One');
    }
    return CupertinoButton(onPressed: () {}, child: const Text('Toggle Two'));
  }
​
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Sample App')),
      body: Center(child: _getToggleChild()),
      floatingActionButton: FloatingActionButton(
        onPressed: _toggle,
        tooltip: 'Update Text',
        child: const Icon(Icons.update),
      ),
    );
  }
}
```

### How do I animate a widget?

[#](#how-do-i-animate-a-widget)

In Xamarin.Forms, you create simple animations using ViewExtensions that include methods such as `FadeTo` and `TranslateTo`. You would use these methods on a view to perform the required animations.

xml

```
<Image Source="{Binding MyImage}" x:Name="myImage" />
```

Then in code behind, or a behavior, this would fade in the image, over a 1-second period.

csharp

```
myImage.FadeTo(0, 1000);
```

In Flutter, you animate widgets using the animation library by wrapping widgets inside an animated widget. Use an `AnimationController`, which is an `Animation<double>` that can pause, seek, stop and reverse the animation. It requires a `Ticker` that signals when vsync happens, and produces a linear interpolation between 0 and 1 on each frame while it's running. You then create one or more`Animation`s and attach them to the controller.

For example, you might use `CurvedAnimation` to implement an animation along an interpolated curve. In this sense, the controller is the "master" source of the animation progress and the `CurvedAnimation` computes the curve that replaces the controller's default linear motion. Like widgets, animations in Flutter work with composition.

When building the widget tree, you assign the `Animation` to an animated property of a widget, such as the opacity of a `FadeTransition`, and tell the controller to start the animation.

The following example shows how to write a `FadeTransition` that fades the widget into a logo when you press the `FloatingActionButton`:

dart

```
import 'package:material_ui/material_ui.dart';
​
void main() {
  runApp(const FadeAppTest());
}
​
class FadeAppTest extends StatelessWidget {
  /// This widget is the root of your application.
  const FadeAppTest({super.key});
​
  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      title: 'Fade Demo',
      home: MyFadeTest(title: 'Fade Demo'),
    );
  }
}
​
class MyFadeTest extends StatefulWidget {
  const MyFadeTest({super.key, required this.title});
​
  final String title;
​
  @override
  State<MyFadeTest> createState() => _MyFadeTest();
}
​
class _MyFadeTest extends State<MyFadeTest> with TickerProviderStateMixin {
  late final AnimationController controller;
  late final CurvedAnimation curve;
​
  @override
  void initState() {
    super.initState();
    controller = AnimationController(
      duration: const Duration(milliseconds: 2000),
      vsync: this,
    );
    curve = CurvedAnimation(parent: controller, curve: Curves.easeIn);
  }
​
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text(widget.title)),
      body: Center(
        child: FadeTransition(
          opacity: curve,
          child: const FlutterLogo(size: 100),
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          controller.forward();
        },
        tooltip: 'Fade',
        child: const Icon(Icons.brush),
      ),
    );
  }
}
```

For more information, see [Animation & Motion widgets](/ui/widgets/animation), the [Animations tutorial](/ui/animations/tutorial), and the [Animations overview](/ui/animations).

### How do I draw/paint on the screen?

[#](#how-do-i-drawpaint-on-the-screen)

Xamarin.Forms never had a built-in way to draw directly on the screen. Many would use SkiaSharp, if they needed a custom image drawn. In Flutter, you have direct access to the Skia Canvas and can easily draw on screen.

Flutter has two classes that help you draw to the canvas: `CustomPaint` and `CustomPainter`, the latter of which implements your algorithm to draw to the canvas.

To learn how to implement a signature painter in Flutter, see Collin's answer on [Custom Paint](https://stackoverflow.com/questions/46241071/create-signature-area-for-mobile-app-in-dart-flutter).

dart

```
import 'package:material_ui/material_ui.dart';
​
void main() {
  runApp(const MaterialApp(home: DemoApp()));
}
​
class DemoApp extends StatelessWidget {
  const DemoApp({super.key});
​
  @override
  Widget build(BuildContext context) => const Scaffold(body: Signature());
}
​
class Signature extends StatefulWidget {
  const Signature({super.key});
​
  @override
  SignatureState createState() => SignatureState();
}
​
class SignatureState extends State<Signature> {
  List<Offset?> _points = <Offset?>[];
​
  void _onPanUpdate(DragUpdateDetails details) {
    setState(() {
      final RenderBox referenceBox = context.findRenderObject() as RenderBox;
      final Offset localPosition = referenceBox.globalToLocal(
        details.globalPosition,
      );
      _points = List.from(_points)..add(localPosition);
    });
  }
​
  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onPanUpdate: _onPanUpdate,
      onPanEnd: (details) => _points.add(null),
      child: CustomPaint(
        painter: SignaturePainter(_points),
        size: Size.infinite,
      ),
    );
  }
}
​
class SignaturePainter extends CustomPainter {
  const SignaturePainter(this.points);
​
  final List<Offset?> points;
​
  @override
  void paint(Canvas canvas, Size size) {
    final Paint paint = Paint()
      ..color = Colors.black
      ..strokeCap = StrokeCap.round
      ..strokeWidth = 5;
    for (int i = 0; i < points.length - 1; i++) {
      if (points[i] != null && points[i + 1] != null) {
        canvas.drawLine(points[i]!, points[i + 1]!, paint);
      }
    }
  }
​
  @override
  bool shouldRepaint(SignaturePainter oldDelegate) =>
      oldDelegate.points != points;
}
```

### Where is the widget's opacity?

[#](#where-is-the-widgets-opacity)

On Xamarin.Forms, all `VisualElement`s have an Opacity. In Flutter, you need to wrap a widget in an [`Opacity` widget](https://api.flutter.dev/flutter/widgets/Opacity-class.html) to accomplish this.

### How do I build custom widgets?

[#](#how-do-i-build-custom-widgets)

In Xamarin.Forms, you typically subclass `VisualElement`, or use a pre-existing `VisualElement`, to override and implement methods that achieve the desired behavior.

In Flutter, build a custom widget by [composing](/resources/architectural-overview#composition) smaller widgets (instead of extending them). It is somewhat similar to implementing a custom control based off a `Grid` with numerous `VisualElement`s added in, while extending with custom logic.

For example, how do you build a `CustomButton` that takes a label in the constructor? Create a CustomButton that composes a `ElevatedButton` with a label, rather than by extending `ElevatedButton`:

dart

```
class CustomButton extends StatelessWidget {
  const CustomButton(this.label, {super.key});
​
  final String label;
​
  @override
  Widget build(BuildContext context) {
    return ElevatedButton(onPressed: () {}, child: Text(label));
  }
}
```

Then use `CustomButton`, just as you'd use any other Flutter widget:

dart

```
@override
Widget build(BuildContext context) {
  return const Center(child: CustomButton('Hello'));
}
```

[#](#navigation)

### How do I navigate between pages?

[#](#how-do-i-navigate-between-pages)

In Xamarin.Forms, the `NavigationPage` class provides a hierarchical navigation experience where the user is able to navigate through pages, forwards and backwards.

Flutter has a similar implementation, using a `Navigator` and `Routes`. A `Route` is an abstraction for a `Page` of an app, and a `Navigator` is a [widget](/resources/architectural-overview#widgets) that manages routes.

A route roughly maps to a `Page`. The navigator works in a similar way to the Xamarin.Forms `NavigationPage`, in that it can `push()` and `pop()` routes depending on whether you want to navigate to, or back from, a view.

To navigate between pages, you have a couple options:

* Specify a `Map` of route names. (`MaterialApp`)
* Directly navigate to a route. (`WidgetsApp`)

The following example builds a `Map`.

dart

```
void main() {
  runApp(
    MaterialApp(
      home: const MyAppHome(), // becomes the route named '/'
      routes: <String, WidgetBuilder>{
        '/a': (context) => const MyPage(title: 'page A'),
        '/b': (context) => const MyPage(title: 'page B'),
        '/c': (context) => const MyPage(title: 'page C'),
      },
    ),
  );
}
```

Navigate to a route by pushing its name to the `Navigator`.

dart

```
Navigator.of(context).pushNamed('/b');
```

The `Navigator` is a stack that manages your app's routes. Pushing a route to the stack moves to that route. Popping a route from the stack, returns to the previous route. This is done by awaiting on the `Future` returned by `push()`.

`async`/`await` is very similar to the .NET implementation and is explained in more detail in [Async UI](#async-ui).

For example, to start a `location` route that lets the user select their location, you might do the following:

dart

```
Object? coordinates = await Navigator.of(context).pushNamed('/location');
```

And then, inside your 'location' route, once the user has selected their location, pop the stack with the result:

dart

```
Navigator.of(context).pop({'lat': 43.821757, 'long': -79.226392});
```

### How do I navigate to another app?

[#](#how-do-i-navigate-to-another-app)

In Xamarin.Forms, to send the user to another application, you use a specific URI scheme, using `Device.OpenUrl("mailto://")`.

To implement this functionality in Flutter, create a native platform integration, or use an [existing plugin](https://pub.dev/flutter), such as[`url_launcher`](https://pub.dev/packages/url_launcher), available with many other packages on [pub.dev](https://pub.dev).

## Async UI

[#](#async-ui)

### What is the equivalent of Device.BeginOnMainThread() in Flutter?

[#](#what-is-the-equivalent-of-device-beginonmainthread-in-flutter)

Dart has a single-threaded execution model, with support for `Isolate`s (a way to run Dart codes on another thread), an event loop, and asynchronous programming. Unless you spawn an `Isolate`, your Dart code runs in the main UI thread and is driven by an event loop.

Dart's single-threaded model doesn't mean you need to run everything as a blocking operation that causes the UI to freeze. Much like Xamarin.Forms, you need to keep the UI thread free. You would use `async`/`await` to perform tasks, where you must wait for the response.

In Flutter, use the asynchronous facilities that the Dart language provides, also named `async`/`await`, to perform asynchronous work. This is very similar to C# and should be very easy to use for any Xamarin.Forms developer.

For example, you can run network code without causing the UI to hang by using `async`/`await` and letting Dart do the heavy lifting:

dart

```
Future<void> loadData() async {
  final Uri dataURL = Uri.parse('https://jsonplaceholder.typicode.com/posts');
  final http.Response response = await http.get(dataURL);
  setState(() {
    data = (jsonDecode(response.body) as List).cast<Map<String, Object?>>();
  });
}
```

Once the awaited network call is done, update the UI by calling `setState()`, which triggers a rebuild of the widget subtree and updates the data.

The following example loads data asynchronously and displays it in a `ListView`:

dart

```
import 'dart:convert';
​
import 'package:http/http.dart' as http;
import 'package:material_ui/material_ui.dart';
​
void main() {
  runApp(const SampleApp());
}
​
class SampleApp extends StatelessWidget {
  const SampleApp({super.key});
​
  @override
  Widget build(BuildContext context) {
    return const MaterialApp(title: 'Sample App', home: SampleAppPage());
  }
}
​
class SampleAppPage extends StatefulWidget {
  const SampleAppPage({super.key});
​
  @override
  State<SampleAppPage> createState() => _SampleAppPageState();
}
​
class _SampleAppPageState extends State<SampleAppPage> {
  List<Map<String, Object?>> data = [];
​
  @override
  void initState() {
    super.initState();
    loadData();
  }
​
  Future<void> loadData() async {
    final Uri dataURL = Uri.parse('https://jsonplaceholder.typicode.com/posts');
    final http.Response response = await http.get(dataURL);
    setState(() {
      data = (jsonDecode(response.body) as List).cast<Map<String, Object?>>();
    });
  }
​
  Widget getRow(int index) {
    return Padding(
      padding: const EdgeInsets.all(10),
      child: Text('Row ${data[index]['title']}'),
    );
  }
​
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Sample App')),
      body: ListView.builder(
        itemCount: data.length,
        itemBuilder: (context, index) {
          return getRow(index);
        },
      ),
    );
  }
}
```

Refer to the next section for more information on doing work in the background, and how Flutter differs from Android.

### How do you move work to a background thread?

[#](#how-do-you-move-work-to-a-background-thread)

Since Flutter is single threaded and runs an event loop, you don't have to worry about thread management or spawning background threads. This is very similar to Xamarin.Forms. If you're doing I/O-bound work, such as disk access or a network call, then you can safely use `async`/`await` and you're all set.

If, on the other hand, you need to do computationally intensive work that keeps the CPU busy, you want to move it to an `Isolate` to avoid blocking the event loop, like you would keep *any* sort of work out of the main thread. This is similar to when you move things to a different thread via `Task.Run()` in Xamarin.Forms.

For I/O-bound work, declare the function as an `async` function, and `await` on long-running tasks inside the function:

dart

```
Future<void> loadData() async {
  final Uri dataURL = Uri.parse('https://jsonplaceholder.typicode.com/posts');
  final http.Response response = await http.get(dataURL);
  setState(() {
    data = (jsonDecode(response.body) as List).cast<Map<String, Object?>>();
  });
}
```

This is how you would typically do network or database calls, which are both I/O operations.

However, there are times when you might be processing a large amount of data and your UI hangs. In Flutter, use `Isolate`s to take advantage of multiple CPU cores to do long-running or computationally intensive tasks.

Isolates are separate execution threads that do not share any memory with the main execution memory heap. This is a difference between `Task.Run()`. This means you can't access variables from the main thread, or update your UI by calling `setState()`.

The following example shows, in a simple isolate, how to share data back to the main thread to update the UI.

dart

```
Future<void> loadData() async {
  final ReceivePort receivePort = ReceivePort();
  await Isolate.spawn(dataLoader, receivePort.sendPort);
​
  // The 'echo' isolate sends its SendPort as the first message
  final SendPort sendPort = await receivePort.first as SendPort;
  final List<Map<String, dynamic>> msg = await sendReceive(
    sendPort,
    'https://jsonplaceholder.typicode.com/posts',
  );
  setState(() {
    data = msg;
  });
}
​
// The entry point for the isolate
static Future<void> dataLoader(SendPort sendPort) async {
  // Open the ReceivePort for incoming messages.
  final ReceivePort port = ReceivePort();
​
  // Notify any other isolates what port this isolate listens to.
  sendPort.send(port.sendPort);
  await for (final dynamic msg in port) {
    final String url = msg[0] as String;
    final SendPort replyTo = msg[1] as SendPort;
​
    final Uri dataURL = Uri.parse(url);
    final http.Response response = await http.get(dataURL);
    // Lots of JSON to parse
    replyTo.send(jsonDecode(response.body) as List<Map<String, dynamic>>);
  }
}
​
Future<List<Map<String, dynamic>>> sendReceive(SendPort port, String msg) {
  final ReceivePort response = ReceivePort();
  port.send(<dynamic>[msg, response.sendPort]);
  return response.first as Future<List<Map<String, dynamic>>>;
}
```

Here, `dataLoader()` is the `Isolate` that runs in its own separate execution thread. In the isolate, you can perform more CPU intensive processing (parsing a big JSON, for example), or perform computationally intensive math, such as encryption or signal processing.

You can run the full example below:

dart

```
import 'dart:async';
import 'dart:convert';
import 'dart:isolate';
​
import 'package:http/http.dart' as http;
import 'package:material_ui/material_ui.dart';
​
void main() {
  runApp(const SampleApp());
}
​
class SampleApp extends StatelessWidget {
  const SampleApp({super.key});
​
  @override
  Widget build(BuildContext context) {
    return const MaterialApp(title: 'Sample App', home: SampleAppPage());
  }
}
​
class SampleAppPage extends StatefulWidget {
  const SampleAppPage({super.key});
​
  @override
  State<SampleAppPage> createState() => _SampleAppPageState();
}
​
class _SampleAppPageState extends State<SampleAppPage> {
  List<Map<String, Object?>> data = [];
​
  @override
  void initState() {
    super.initState();
    loadData();
  }
​
  bool get showLoadingDialog => data.isEmpty;
​
  Future<void> loadData() async {
    final ReceivePort receivePort = ReceivePort();
    await Isolate.spawn(dataLoader, receivePort.sendPort);
​
    // The 'echo' isolate sends its SendPort as the first message
    final SendPort sendPort = await receivePort.first as SendPort;
    final List<Map<String, dynamic>> msg = await sendReceive(
      sendPort,
      'https://jsonplaceholder.typicode.com/posts',
    );
    setState(() {
      data = msg;
    });
  }
​
  // The entry point for the isolate
  static Future<void> dataLoader(SendPort sendPort) async {
    // Open the ReceivePort for incoming messages.
    final ReceivePort port = ReceivePort();
​
    // Notify any other isolates what port this isolate listens to.
    sendPort.send(port.sendPort);
    await for (final dynamic msg in port) {
      final String url = msg[0] as String;
      final SendPort replyTo = msg[1] as SendPort;
​
      final Uri dataURL = Uri.parse(url);
      final http.Response response = await http.get(dataURL);
      // Lots of JSON to parse
      replyTo.send(jsonDecode(response.body) as List<Map<String, dynamic>>);
    }
  }
​
  Future<List<Map<String, dynamic>>> sendReceive(SendPort port, String msg) {
    final ReceivePort response = ReceivePort();
    port.send(<dynamic>[msg, response.sendPort]);
    return response.first as Future<List<Map<String, dynamic>>>;
  }
​
  Widget getBody() {
    if (showLoadingDialog) {
      return getProgressDialog();
    }
    return getListView();
  }
​
  Widget getProgressDialog() {
    return const Center(child: CircularProgressIndicator());
  }
​
  ListView getListView() {
    return ListView.builder(
      itemCount: data.length,
      itemBuilder: (context, index) {
        return getRow(index);
      },
    );
  }
​
  Widget getRow(int index) {
    return Padding(
      padding: const EdgeInsets.all(10),
      child: Text('Row ${data[index]['title']}'),
    );
  }
​
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Sample App')),
      body: getBody(),
    );
  }
}
```

### How do I make network requests?

[#](#how-do-i-make-network-requests)

In Xamarin.Forms you would use `HttpClient`. Making a network call in Flutter is easy when you use the popular [`http` package](https://pub.dev/packages/http). This abstracts away a lot of the networking that you might normally implement yourself, making it simple to make network calls.

To use the `http` package, add it to your dependencies in `pubspec.yaml`:

yaml

```
dependencies:
  http: ^1.4.0
```

To make a network request, call `await` on the `async` function `http.get()`:

dart

```
Future<void> loadData() async {
  final Uri dataURL = Uri.parse('https://jsonplaceholder.typicode.com/posts');
  final http.Response response = await http.get(dataURL);
  setState(() {
    data = (jsonDecode(response.body) as List).cast<Map<String, Object?>>();
  });
}
```

### How do I show the progress for a long-running task?

[#](#how-do-i-show-the-progress-for-a-long-running-task)

In Xamarin.Forms you would typically create a loading indicator, either directly in XAML or through a 3rd party plugin such as AcrDialogs.

In Flutter, use a `ProgressIndicator` widget. Show the progress programmatically by controlling when it's rendered through a boolean flag. Tell Flutter to update its state before your long-running task starts, and hide it after it ends.

In the example below, the build function is separated into three different functions. If `showLoadingDialog` is `true` (when `widgets.length == 0`), then render the `ProgressIndicator`. Otherwise, render the `ListView` with the data returned from a network call.

dart

```
import 'dart:async';
import 'dart:convert';
​
import 'package:http/http.dart' as http;
import 'package:material_ui/material_ui.dart';
​
void main() {
  runApp(const SampleApp());
}
​
class SampleApp extends StatelessWidget {
  const SampleApp({super.key});
​
  @override
  Widget build(BuildContext context) {
    return const MaterialApp(title: 'Sample App', home: SampleAppPage());
  }
}
​
class SampleAppPage extends StatefulWidget {
  const SampleAppPage({super.key});
​
  @override
  State<SampleAppPage> createState() => _SampleAppPageState();
}
​
class _SampleAppPageState extends State<SampleAppPage> {
  List<Map<String, Object?>> data = [];
​
  @override
  void initState() {
    super.initState();
    loadData();
  }
​
  bool get showLoadingDialog => data.isEmpty;
​
  Future<void> loadData() async {
    final Uri dataURL = Uri.parse('https://jsonplaceholder.typicode.com/posts');
    final http.Response response = await http.get(dataURL);
    setState(() {
      data = (jsonDecode(response.body) as List).cast<Map<String, Object?>>();
    });
  }
​
  Widget getBody() {
    if (showLoadingDialog) {
      return getProgressDialog();
    }
    return getListView();
  }
​
  Widget getProgressDialog() {
    return const Center(child: CircularProgressIndicator());
  }
​
  ListView getListView() {
    return ListView.builder(
      itemCount: data.length,
      itemBuilder: (context, index) {
        return getRow(index);
      },
    );
  }
​
  Widget getRow(int index) {
    return Padding(
      padding: const EdgeInsets.all(10),
      child: Text('Row ${data[index]['title']}'),
    );
  }
​
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Sample App')),
      body: getBody(),
    );
  }
}
```

## Project structure & resources

[#](#project-structure-resources)

### Where do I store my image files?

[#](#where-do-i-store-my-image-files)

Xamarin.Forms has no platform independent way of storing images, you had to place images in the iOS `xcasset` folder, or on Android in the various `drawable` folders.

While Android and iOS treat resources and assets as distinct items, Flutter apps have only assets. All resources that would live in the `Resources/drawable-*` folders on Android, are placed in an assets' folder for Flutter.

Flutter follows a simple density-based format like iOS. Assets might be `1.0x`, `2.0x`, `3.0x`, or any other multiplier. Flutter doesn't have `dp`s but there are logical pixels, which are basically the same as device-independent pixels. Flutter's [`devicePixelRatio`](https://api.flutter.dev/flutter/dart-ui/FlutterView/devicePixelRatio.html) expresses the ratio of physical pixels in a single logical pixel.

The equivalent to Android's density buckets are:

| Android density qualifier | Flutter pixel ratio |
| ------------------------- | ------------------- |
| `ldpi`                    | `0.75x`             |
| `mdpi`                    | `1.0x`              |
| `hdpi`                    | `1.5x`              |
| `xhdpi`                   | `2.0x`              |
| `xxhdpi`                  | `3.0x`              |
| `xxxhdpi`                 | `4.0x`              |

Assets are located in any arbitrary folder— Flutter has no predefined folder structure. You declare the assets (with location) in the `pubspec.yaml` file, and Flutter picks them up.

To add a new image asset called `my_icon.png` to our Flutter project, for example, and deciding that it should live in a folder we arbitrarily called `images`, you would put the base image (1.0x) in the `images` folder, and all the other variants in sub-folders called with the appropriate ratio multiplier:

```
images/my_icon.png       // Base: 1.0x image
images/2.0x/my_icon.png  // 2.0x image
images/3.0x/my_icon.png  // 3.0x image
```

Next, you'll need to declare these images in your `pubspec.yaml` file:

yaml

```
assets:
 - images/my_icon.png
```

You can directly access your images in an `Image.asset` widget:

dart

```
@override
Widget build(BuildContext context) {
  return Image.asset('images/my_icon.png');
}
```

or using `AssetImage`:

dart

```
@override
Widget build(BuildContext context) {
  return const Image(image: AssetImage('images/my_image.png'));
}
```

More detailed information can be found in [Adding assets and images](/ui/assets/assets-and-images).

### Where do I store strings? How do I handle localization?

[#](#where-do-i-store-strings-how-do-i-handle-localization)

Unlike .NET which has `resx` files, Flutter doesn't currently have a dedicated system for handling strings. At the moment, the best practice is to declare your copy text in a class as static fields and access them from there. For example:

dart

```
class Strings {
  static const String welcomeMessage = 'Welcome To Flutter';
}
```

You can access your strings as such:

dart

```
Text(Strings.welcomeMessage);
```

By default, Flutter only supports US English for its strings. If you need to add support for other languages, include the `flutter_localizations` package. You might also need to add Dart's [`intl`](https://pub.dev/packages/intl) package to use i10n machinery, such as date/time formatting.

yaml

```
dependencies:
  flutter_localizations:
    sdk: flutter
  intl: any # Use version of intl from flutter_localizations.
```

To use the `flutter_localizations` package, specify the `localizationsDelegates` and `supportedLocales` on the app widget:

dart

```
import 'package:flutter_localizations/flutter_localizations.dart'
    hide GlobalMaterialLocalizations;
import 'package:material_ui/material_ui.dart';
​
class MyWidget extends StatelessWidget {
  const MyWidget({super.key});
​
  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      localizationsDelegates: <LocalizationsDelegate<dynamic>>[
        // Add app-specific localization delegate[s] here
        GlobalMaterialLocalizations.delegate,
        GlobalWidgetsLocalizations.delegate,
      ],
      supportedLocales: <Locale>[
        Locale('en', 'US'), // English
        Locale('he', 'IL'), // Hebrew
        // ... other locales the app supports
      ],
    );
  }
}
```

The delegates contain the actual localized values, while the `supportedLocales` defines which locales the app supports. The above example uses a `MaterialApp`, so it has both a `GlobalWidgetsLocalizations` for the base widgets localized values, and a `MaterialWidgetsLocalizations` for the Material widgets localizations. If you use `WidgetsApp` for your app, you don't need the latter. Note that these two delegates contain "default" values, but you'll need to provide one or more delegates for your own app's localizable copy, if you want those to be localized too.

When initialized, the `WidgetsApp` (or `MaterialApp`) creates a [`Localizations`](https://api.flutter.dev/flutter/widgets/Localizations-class.html) widget for you, with the delegates you specify. The current locale for the device is always accessible from the `Localizations` widget from the current context (in the form of a `Locale` object), or using the [`Window.locale`](https://api.flutter.dev/flutter/dart-ui/Window/locale.html).

To access localized resources, use the `Localizations.of()` method to access a specific localizations class that is provided by a given delegate. Use the [`intl_translation`](https://pub.dev/packages/intl_translation) package to extract translatable copy to [arb](https://github.com/google/app-resource-bundle) files for translating, and importing them back into the app for using them with `intl`.

For further details on internationalization and localization in Flutter, see the [internationalization guide](/ui/internationalization), which has sample code with and without the `intl` package.

### Where is my project file?

[#](#where-is-my-project-file)

In Xamarin.Forms you will have a `csproj` file. The closest equivalent in Flutter is pubspec.yaml, which contains package dependencies and various project details. Similar to .NET Standard, files within the same directory are considered part of the project.

### What is the equivalent of Nuget? How do I add dependencies?

[#](#what-is-the-equivalent-of-nuget-how-do-i-add-dependencies)

In the .NET ecosystem, native Xamarin projects and Xamarin.Forms projects had access to Nuget and the built-in package management system. Flutter apps contain a native Android app, native iOS app and Flutter app.

In Android, you add dependencies by adding to your Gradle build script. In iOS, you add dependencies by adding to your `Podfile`.

Flutter uses Dart's own build system, and the Pub package manager. The tools delegate the building of the native Android and iOS wrapper apps to the respective build systems.

In general, use `pubspec.yaml` to declare external dependencies to use in Flutter. A good place to find Flutter packages is on [pub.dev](https://pub.dev).

## Application lifecycle

[#](#application-lifecycle)

### How do I listen to application lifecycle events?

[#](#how-do-i-listen-to-application-lifecycle-events)

In Xamarin.Forms, you have an `Application` that contains `OnStart`, `OnResume` and `OnSleep`. In Flutter, you can instead listen to similar lifecycle events by hooking into the `WidgetsBinding` observer and listening to the `didChangeAppLifecycleState()` change event.

The observable lifecycle events are:

* `inactive`

  The application is in an inactive state and is not receiving user input. This event is iOS only.

* `paused`

  The application is not currently visible to the user, is not responding to user input, but is running in the background.

* `resumed`

  The application is visible and responding to user input.

* `suspending`

  The application is suspended momentarily. This event is Android only.

For more details on the meaning of these states, see the [`AppLifecycleStatus` documentation](https://api.flutter.dev/flutter/dart-ui/AppLifecycleState.html).

## Layouts

[#](#layouts)

### What is the equivalent of a StackLayout?

[#](#what-is-the-equivalent-of-a-stacklayout)

In Xamarin.Forms you can create a `StackLayout` with an `Orientation` of horizontal or vertical. Flutter has a similar approach, however you would use the `Row` or `Column` widgets.

If you notice the two code samples are identical except the `Row` and `Column` widget. The children are the same and this feature can be exploited to develop rich layouts that can change overtime with the same children.

dart

```
@override
Widget build(BuildContext context) {
  return const Row(
    mainAxisAlignment: MainAxisAlignment.center,
    children: <Widget>[
      Text('Row One'),
      Text('Row Two'),
      Text('Row Three'),
      Text('Row Four'),
    ],
  );
}
```

dart

```
@override
Widget build(BuildContext context) {
  return const Column(
    mainAxisAlignment: MainAxisAlignment.center,
    children: <Widget>[
      Text('Column One'),
      Text('Column Two'),
      Text('Column Three'),
      Text('Column Four'),
    ],
  );
```

### What is the equivalent of a Grid?

[#](#what-is-the-equivalent-of-a-grid)

The closest equivalent of a `Grid` would be a `GridView`. This is much more powerful than what you are used to in Xamarin.Forms. A `GridView` provides automatic scrolling when the content exceeds its viewable space.

dart

```
@override
Widget build(BuildContext context) {
  return GridView.count(
    // Create a grid with 2 columns. If you change the scrollDirection to
    // horizontal, this would produce 2 rows.
    crossAxisCount: 2,
    // Generate 100 widgets that display their index in the list.
    children: List<Widget>.generate(100, (index) {
      return Center(
        child: Text(
          'Item $index',
          style: Theme.of(context).textTheme.headlineMedium,
        ),
      );
    }),
  );
}
```

You might have used a `Grid` in Xamarin.Forms to implement widgets that overlay other widgets. In Flutter, you accomplish this with the `Stack` widget.

This sample creates two icons that overlap each other.

dart

```
@override
Widget build(BuildContext context) {
  return const Stack(
    children: <Widget>[
      Icon(Icons.add_box, size: 24, color: Colors.black),
      Positioned(
        left: 10,
        child: Icon(Icons.add_circle, size: 24, color: Colors.black),
      ),
    ],
  );
}
```

### What is the equivalent of a ScrollView?

[#](#what-is-the-equivalent-of-a-scrollview)

In Xamarin.Forms, a `ScrollView` wraps around a `VisualElement`, and if the content is larger than the device screen, it scrolls.

In Flutter, the closest match is the `SingleChildScrollView` widget. You simply fill the Widget with the content that you want to be scrollable.

dart

```
@override
Widget build(BuildContext context) {
  return const SingleChildScrollView(child: Text('Long Content'));
}
```

If you have many items you want to wrap in a scroll, even of different `Widget` types, you might want to use a `ListView`. This might seem like overkill, but in Flutter this is far more optimized and less intensive than a Xamarin.Forms `ListView`, which is backing on to platform specific controls.

dart

```
@override
Widget build(BuildContext context) {
  return ListView(
    children: const <Widget>[
      Text('Row One'),
      Text('Row Two'),
      Text('Row Three'),
      Text('Row Four'),
    ],
  );
}
```

### How do I handle landscape transitions in Flutter?

[#](#how-do-i-handle-landscape-transitions-in-flutter)

Landscape transitions can be handled automatically by setting the `configChanges` property in the AndroidManifest.xml:

xml

```
<activity android:configChanges="orientation|screenSize" />
```

## Gesture detection and touch event handling

[#](#gesture-detection-and-touch-event-handling)

### How do I add GestureRecognizers to a widget in Flutter?

[#](#how-do-i-add-gesturerecognizers-to-a-widget-in-flutter)

In Xamarin.Forms, `Element`s might contain a click event you can attach to. Many elements also contain a `Command` that is tied to this event. Alternatively you would use the `TapGestureRecognizer`. In Flutter there are two very similar ways:

1. If the widget supports event detection, pass a function to it and handle it in the function. For example, the ElevatedButton has an `onPressed` parameter:

   dart

   ```
   @override
   Widget build(BuildContext context) {
     return ElevatedButton(
       onPressed: () {
         developer.log('click');
       },
       child: const Text('Button'),
     );
   }
   ```

2. If the widget doesn't support event detection, wrap the widget in a `GestureDetector` and pass a function to the `onTap` parameter.

   dart

   ```
   class SampleApp extends StatelessWidget {
     const SampleApp({super.key});
   ​
     @override
     Widget build(BuildContext context) {
       return Scaffold(
         body: Center(
           child: GestureDetector(
             onTap: () {
               developer.log('tap');
             },
             child: const FlutterLogo(size: 200),
           ),
         ),
       );
     }
   }
   ```

### How do I handle other gestures on widgets?

[#](#how-do-i-handle-other-gestures-on-widgets)

In Xamarin.Forms you would add a `GestureRecognizer` to the `View`. You would normally be limited to `TapGestureRecognizer`, `PinchGestureRecognizer`, `PanGestureRecognizer`, `SwipeGestureRecognizer`, `DragGestureRecognizer` and `DropGestureRecognizer` unless you built your own.

In Flutter, using the GestureDetector, you can listen to a wide range of Gestures such as:

* Tap

- `onTapDown`

  A pointer that might cause a tap has contacted the screen at a particular location.

- `onTapUp`

  A pointer that triggers a tap has stopped contacting the screen at a particular location.

- `onTap`

  A tap has occurred.

- `onTapCancel`

  The pointer that previously triggered the `onTapDown` won't cause a tap.

* Double tap

- `onDoubleTap`

  The user tapped the screen at the same location twice in quick succession.

* Long press

- `onLongPress`

  A pointer has remained in contact with the screen at the same location for a long period of time.

* Vertical drag

- `onVerticalDragStart`

  A pointer has contacted the screen and might begin to move vertically.

- `onVerticalDragUpdate`

  A pointer in contact with the screen has moved further in the vertical direction.

- `onVerticalDragEnd`

  A pointer that was previously in contact with the screen and moving vertically is no longer in contact with the screen and was moving at a specific velocity when it stopped contacting the screen.

* Horizontal drag

- `onHorizontalDragStart`

  A pointer has contacted the screen and might begin to move horizontally.

- `onHorizontalDragUpdate`

  A pointer in contact with the screen has moved further in the horizontal direction.

- `onHorizontalDragEnd`

  A pointer that was previously in contact with the screen and moving horizontally is no longer in contact with the screen and was moving at a specific velocity when it stopped contacting the screen.

The following example shows a `GestureDetector` that rotates the Flutter logo on a double tap:

dart

```
class RotatingFlutterDetector extends StatefulWidget {
  const RotatingFlutterDetector({super.key});
​
  @override
  State<RotatingFlutterDetector> createState() =>
      _RotatingFlutterDetectorState();
}
​
class _RotatingFlutterDetectorState extends State<RotatingFlutterDetector>
    with SingleTickerProviderStateMixin {
  late final AnimationController controller;
  late final CurvedAnimation curve;
​
  @override
  void initState() {
    super.initState();
    controller = AnimationController(
      duration: const Duration(milliseconds: 2000),
      vsync: this,
    );
    curve = CurvedAnimation(parent: controller, curve: Curves.easeIn);
  }
​
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: GestureDetector(
          onDoubleTap: () {
            if (controller.isCompleted) {
              controller.reverse();
            } else {
              controller.forward();
            }
          },
          child: RotationTransition(
            turns: curve,
            child: const FlutterLogo(size: 200),
          ),
        ),
      ),
    );
  }
}
```

## Listviews and adapters

[#](#listviews-and-adapters)

### What is the equivalent to a ListView in Flutter?

[#](#what-is-the-equivalent-to-a-listview-in-flutter)

The equivalent to a `ListView` in Flutter is … a `ListView`!

In a Xamarin.Forms `ListView`, you create a `ViewCell` and possibly a `DataTemplateSelector`and pass it into the `ListView`, which renders each row with what your `DataTemplateSelector` or `ViewCell` returns. However, you often have to make sure you turn on Cell Recycling otherwise you will run into memory issues and slow scrolling speeds.

Due to Flutter's immutable widget pattern, you pass a list of widgets to your `ListView`, and Flutter takes care of making sure that scrolling is fast and smooth.

dart

```
import 'package:material_ui/material_ui.dart';
​
void main() {
  runApp(const SampleApp());
}
​
class SampleApp extends StatelessWidget {
  /// This widget is the root of your application.
  const SampleApp({super.key});
​
  @override
  Widget build(BuildContext context) {
    return const MaterialApp(title: 'Sample App', home: SampleAppPage());
  }
}
​
class SampleAppPage extends StatelessWidget {
  const SampleAppPage({super.key});
​
  List<Widget> _getListData() {
    return List<Widget>.generate(
      100,
      (index) =>
          Padding(padding: const EdgeInsets.all(10), child: Text('Row $index')),
    );
  }
​
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Sample App')),
      body: ListView(children: _getListData()),
    );
  }
}
```

### How do I know which list item has been clicked?

[#](#how-do-i-know-which-list-item-has-been-clicked)

In Xamarin.Forms, the ListView has an `ItemTapped` method to find out which item was clicked. There are many other techniques you might have used such as checking when `SelectedItem` or `EventToCommand` behaviors change.

In Flutter, use the touch handling provided by the passed-in widgets.

dart

```
import 'dart:developer' as developer;
​
import 'package:material_ui/material_ui.dart';
​
void main() {
  runApp(const SampleApp());
}
​
class SampleApp extends StatelessWidget {
  // This widget is the root of your application.
  const SampleApp({super.key});
​
  @override
  Widget build(BuildContext context) {
    return const MaterialApp(title: 'Sample App', home: SampleAppPage());
  }
}
​
class SampleAppPage extends StatefulWidget {
  const SampleAppPage({super.key});
​
  @override
  State<SampleAppPage> createState() => _SampleAppPageState();
}
​
class _SampleAppPageState extends State<SampleAppPage> {
  List<Widget> _getListData() {
    return List<Widget>.generate(
      100,
      (index) => GestureDetector(
        onTap: () {
          developer.log('Row $index tapped');
        },
        child: Padding(
          padding: const EdgeInsets.all(10),
          child: Text('Row $index'),
        ),
      ),
    );
  }
​
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Sample App')),
      body: ListView(children: _getListData()),
    );
  }
}
```

### How do I update a ListView dynamically?

[#](#how-do-i-update-a-listview-dynamically)

In Xamarin.Forms, if you bound the `ItemsSource` property to an `ObservableCollection`, you would just update the list in your ViewModel. Alternatively, you could assign a new `List` to the `ItemSource` property.

In Flutter, things work a little differently. If you update the list of widgets inside a `setState()` method, you would quickly see that your data did not change visually. This is because when `setState()` is called, the Flutter rendering engine looks at the widget tree to see if anything has changed. When it gets to your `ListView`, it performs a `==` check, and determines that the two `ListView`s are the same. Nothing has changed, so no update is required.

For a simple way to update your `ListView`, create a new `List` inside of `setState()`, and copy the data from the old list to the new list. While this approach is simple, it is not recommended for large data sets, as shown in the next example.

dart

```
import 'dart:developer' as developer;
​
import 'package:material_ui/material_ui.dart';
​
void main() {
  runApp(const SampleApp());
}
​
class SampleApp extends StatelessWidget {
  /// This widget is the root of your application.
  const SampleApp({super.key});
​
  @override
  Widget build(BuildContext context) {
    return const MaterialApp(title: 'Sample App', home: SampleAppPage());
  }
}
​
class SampleAppPage extends StatefulWidget {
  const SampleAppPage({super.key});
​
  @override
  State<SampleAppPage> createState() => _SampleAppPageState();
}
​
class _SampleAppPageState extends State<SampleAppPage> {
  List<Widget> widgets = <Widget>[];
​
  @override
  void initState() {
    super.initState();
    for (int i = 0; i < 100; i++) {
      widgets.add(getRow(i));
    }
  }
​
  Widget getRow(int index) {
    return GestureDetector(
      onTap: () {
        setState(() {
          widgets = List<Widget>.from(widgets);
          widgets.add(getRow(widgets.length));
          developer.log('Row $index');
        });
      },
      child: Padding(
        padding: const EdgeInsets.all(10),
        child: Text('Row $index'),
      ),
    );
  }
​
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Sample App')),
      body: ListView(children: widgets),
    );
  }
}
```

The recommended, efficient, and effective way to build a list uses a `ListView.Builder`. This method is great when you have a dynamic list or a list with very large amounts of data. This is essentially the equivalent of RecyclerView on Android, which automatically recycles list elements for you:

dart

```
import 'dart:developer' as developer;
​
import 'package:material_ui/material_ui.dart';
​
void main() {
  runApp(const SampleApp());
}
​
class SampleApp extends StatelessWidget {
  /// This widget is the root of your application.
  const SampleApp({super.key});
​
  @override
  Widget build(BuildContext context) {
    return const MaterialApp(title: 'Sample App', home: SampleAppPage());
  }
}
​
class SampleAppPage extends StatefulWidget {
  const SampleAppPage({super.key});
​
  @override
  State<SampleAppPage> createState() => _SampleAppPageState();
}
​
class _SampleAppPageState extends State<SampleAppPage> {
  List<Widget> widgets = [];
​
  @override
  void initState() {
    super.initState();
    for (int i = 0; i < 100; i++) {
      widgets.add(getRow(i));
    }
  }
​
  Widget getRow(int index) {
    return GestureDetector(
      onTap: () {
        setState(() {
          widgets.add(getRow(widgets.length));
          developer.log('Row $index');
        });
      },
      child: Padding(
        padding: const EdgeInsets.all(10),
        child: Text('Row $index'),
      ),
    );
  }
​
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Sample App')),
      body: ListView.builder(
        itemCount: widgets.length,
        itemBuilder: (context, index) {
          return getRow(index);
        },
      ),
    );
  }
}
```

Instead of creating a `ListView`, create a `ListView.builder` that takes two key parameters: the initial length of the list, and an item builder function.

The item builder function is similar to the `getView` function in an Android adapter; it takes a position, and returns the row you want rendered at that position.

Finally, but most importantly, notice that the `onTap()` function doesn't recreate the list anymore, but instead adds to it.

For more information, see [Your first Flutter app](https://codelabs.developers.google.com/codelabs/flutter-codelab-first) codelab.

## Working with text

[#](#working-with-text)

### How do I set custom fonts on my text widgets?

[#](#how-do-i-set-custom-fonts-on-my-text-widgets)

In Xamarin.Forms, you would have to add a custom font in each native project. Then, in your `Element` you would assign this font name to the `FontFamily` attribute using `filename#fontname` and just `fontname` for iOS.

In Flutter, place the font file in a folder and reference it in the `pubspec.yaml` file, similar to how you import images.

yaml

```
fonts:
  - family: MyCustomFont
    fonts:
      - asset: fonts/MyCustomFont.ttf
      - style: italic
```

Then assign the font to your `Text` widget:

dart

```
@override
Widget build(BuildContext context) {
  return Scaffold(
    appBar: AppBar(title: const Text('Sample App')),
    body: const Center(
      child: Text(
        'This is a custom font text',
        style: TextStyle(fontFamily: 'MyCustomFont'),
      ),
    ),
  );
}
```

### How do I style my text widgets?

[#](#how-do-i-style-my-text-widgets)

## Form input

[#](#form-input)

### How do I retrieve user input?

[#](#how-do-i-retrieve-user-input)

Xamarin.Forms `element`s allow you to directly query the `element` to determine the state of its properties, or whether it's bound to a property in a `ViewModel`.

Retrieving information in Flutter is handled by specialized widgets and is different from how you are used to. If you have a `TextField`or a `TextFormField`, you can supply a [`TextEditingController`](https://api.flutter.dev/flutter/widgets/TextEditingController-class.html) to retrieve user input:

dart

```
import 'package:material_ui/material_ui.dart';
​
class MyForm extends StatefulWidget {
  const MyForm({super.key});
​
  @override
  State<MyForm> createState() => _MyFormState();
}
​
class _MyFormState extends State<MyForm> {
  /// Create a text controller and use it to retrieve the current value
  /// of the TextField.
  final TextEditingController myController = TextEditingController();
​
  @override
  void dispose() {
    // Clean up the controller when disposing of the widget.
    myController.dispose();
    super.dispose();
  }
​
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Retrieve Text Input')),
      body: Padding(
        padding: const EdgeInsets.all(16),
        child: TextField(controller: myController),
      ),
      floatingActionButton: FloatingActionButton(
        // When the user presses the button, show an alert dialog with the
        // text that the user has typed into our text field.
        onPressed: () {
          showDialog<void>(
            context: context,
            builder: (context) {
              return AlertDialog(
                // Retrieve the text that the user has entered using the
                // TextEditingController.
                content: Text(myController.text),
              );
            },
          );
        },
        tooltip: 'Show me the value!',
        child: const Icon(Icons.text_fields),
      ),
    );
  }
}
```

You can find more information and the full code listing in [Retrieve the value of a text field](/cookbook/forms/retrieve-input).

### What is the equivalent of a Placeholder on an Entry?

[#](#what-is-the-equivalent-of-a-placeholder-on-an-entry)

In Xamarin.Forms, some `Elements` support a `Placeholder` property that you can assign a value to. For example:

xml

```
<Entry Placeholder="This is a hint">
```

In Flutter, you can easily show a "hint" or a placeholder text for your input by adding an `InputDecoration` object to the `decoration` constructor parameter for the text widget.

dart

```
TextField(decoration: InputDecoration(hintText: 'This is a hint')),
```

### How do I show validation errors?

[#](#how-do-i-show-validation-errors)

With Xamarin.Forms, if you wished to provide a visual hint of a validation error, you would need to create new properties and `VisualElement`s surrounding the `Element`s that had validation errors.

In Flutter, you pass through an InputDecoration object to the decoration constructor for the text widget.

However, you don't want to start off by showing an error. Instead, when the user has entered invalid data, update the state, and pass a new `InputDecoration` object.

dart

```
import 'package:material_ui/material_ui.dart';
​
void main() {
  runApp(const SampleApp());
}
​
class SampleApp extends StatelessWidget {
  /// This widget is the root of your application.
  const SampleApp({super.key});
​
  @override
  Widget build(BuildContext context) {
    return const MaterialApp(title: 'Sample App', home: SampleAppPage());
  }
}
​
class SampleAppPage extends StatefulWidget {
  const SampleAppPage({super.key});
​
  @override
  State<SampleAppPage> createState() => _SampleAppPageState();
}
​
class _SampleAppPageState extends State<SampleAppPage> {
  String? _errorText;
​
  String? _getErrorText() {
    return _errorText;
  }
​
  bool isEmail(String em) {
    const String emailRegexp =
        r'^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|'
        r'(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|'
        r'(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$';
    final RegExp regExp = RegExp(emailRegexp);
    return regExp.hasMatch(em);
  }
​
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Sample App')),
      body: Center(
        child: TextField(
          onSubmitted: (text) {
            setState(() {
              if (!isEmail(text)) {
                _errorText = 'Error: This is not an email';
              } else {
                _errorText = null;
              }
            });
          },
          decoration: InputDecoration(
            hintText: 'This is a hint',
            errorText: _getErrorText(),
          ),
        ),
      ),
    );
  }
}
```

## Flutter plugins

[#](#flutter-plugins)

## Interacting with hardware, third party services, and the platform

[#](#interacting-with-hardware-third-party-services-and-the-platform)

### How do I interact with the platform, and with platform native code?

[#](#how-do-i-interact-with-the-platform-and-with-platform-native-code)

Flutter doesn't run code directly on the underlying platform; rather, the Dart code that makes up a Flutter app is run natively on the device, "sidestepping" the SDK provided by the platform. That means, for example, when you perform a network request in Dart, it runs directly in the Dart context. You don't use the Android or iOS APIs you normally take advantage of when writing native apps. Your Flutter app is still hosted in a native app's `ViewController` or `Activity` as a view, but you don't have direct access to this, or the native framework.

This doesn't mean Flutter apps can't interact with those native APIs, or with any native code you have. Flutter provides [platform channels](/platform-integration/platform-channels) that communicate and exchange data with the `ViewController` or `Activity` that hosts your Flutter view. Platform channels are essentially an asynchronous messaging mechanism that bridges the Dart code with the host `ViewController` or `Activity` and the iOS or Android framework it runs on. You can use platform channels to execute a method on the native side, or to retrieve some data from the device's sensors, for example.

In addition to directly using platform channels, you can use a variety of pre-made [plugins](/packages-and-plugins/using-packages) that encapsulate the native and Dart code for a specific goal. For example, you can use a plugin to access the camera roll and the device camera directly from Flutter, without having to write your own integration. Plugins are found on [pub.dev](https://pub.dev), Dart and Flutter's open source package repository. Some packages might support native integrations on iOS, or Android, or both.

If you can't find a plugin on pub.dev that fits your needs, you can [write your own](/packages-and-plugins/developing-packages), and [publish it on pub.dev](/packages-and-plugins/developing-packages#publish).

### How do I access the GPS sensor?

[#](#how-do-i-access-the-gps-sensor)

Use the [`geolocator`](https://pub.dev/packages/geolocator) community plugin.

### How do I access the camera?

[#](#how-do-i-access-the-camera)

The [`camera`](https://pub.dev/packages/camera) plugin is popular for accessing the camera.

### How do I log in with Facebook?

[#](#how-do-i-log-in-with-facebook)

To log in with Facebook, use the [`flutter_facebook_auth`](https://pub.dev/packages/flutter_facebook_auth) community plugin.

If there is platform-specific functionality that Flutter or its community plugins are missing, you can build your own following the [developing packages and plugins](/packages-and-plugins/developing-packages) page.

Flutter's plugin architecture, in a nutshell, is much like using an Event bus in Android: you fire off a message and let the receiver process and emit a result back to you. In this case, the receiver is code running on the native side on Android or iOS.

## Themes (Styles)

[#](#themes-styles)

### How do I theme my app?

[#](#how-do-i-theme-my-app)

Flutter comes with a beautiful, built-in implementation of Material Design, which handles much of the styling and theming needs that you would typically do.

Xamarin.Forms does have a global `ResourceDictionary` where you can share styles across your app. Alternatively, there is Theme support currently in preview.

In Flutter, you declare themes in the top level widget.

To take full advantage of Material Components in your app, you can declare a top level widget `MaterialApp` as the entry point to your application. `MaterialApp` is a convenience widget that wraps a number of widgets that are commonly required for applications implementing Material Design. It builds upon a `WidgetsApp` by adding Material-specific functionality.

You can also use a `WidgetsApp` as your app widget, which provides some of the same functionality, but is not as rich as `MaterialApp`.

To customize the colors and styles of any child components, pass a `ThemeData` object to the `MaterialApp` widget. For example, in the following code, the color scheme from seed is set to deepPurple and text selection color is red.

dart

```
class SampleApp extends StatelessWidget {
  /// This widget is the root of your application.
  const SampleApp({super.key});
​
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Sample App',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        textSelectionTheme: const TextSelectionThemeData(
          selectionColor: Colors.red,
        ),
      ),
      home: const SampleAppPage(),
    );
  }
}
```

## Databases and local storage

[#](#databases-and-local-storage)

### How do I access shared preferences or UserDefaults?

[#](#how-do-i-access-shared-preferences-or-userdefaults)

Xamarin.Forms developers will likely be familiar with the `Xam.Plugins.Settings` plugin.

In Flutter, access equivalent functionality using the [`shared_preferences`](https://pub.dev/packages/shared_preferences) plugin. This plugin wraps the functionality of both `UserDefaults` and the Android equivalent, `SharedPreferences`.

### How do I access SQLite in Flutter?

[#](#how-do-i-access-sqlite-in-flutter)

In Xamarin.Forms most applications would use the `sqlite-net-pcl` plugin to access SQLite databases.

In Flutter, on macOS, Android, and iOS, access this functionality using the [`sqflite`](https://pub.dev/packages/sqflite) plugin.

In Flutter, access this functionality using the [`firebase_messaging`](https://pub.dev/packages/firebase_messaging) plugin. For more information on using the Firebase Cloud Messaging API, see the [`firebase_messaging`](https://pub.dev/packages/firebase_messaging) plugin documentation.

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-09-29. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/flutter-for/xamarin-forms-devs.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/flutter-for/xamarin-forms-devs\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/flutter-for/xamarin-forms-devs.md "Report an issue with this page").

----
url: https://docs.flutter.dev/tools/devtools/console
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Use the Debug console](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

The DevTools Debug console allows you to watch an application's standard output (`stdout`), evaluate expressions for a paused or running app in debug mode, and analyze inbound and outbound references for objects.

The Debug console is available from the [Inspector](/tools/devtools/inspector), [Debugger](/tools/devtools/debugger), and [Memory](/tools/devtools/memory) views.

## Watch application output

[#](#watch-application-output)

The console shows the application's standard output (`stdout`):

## Explore inspected widgets

[#](#explore-inspected-widgets)

If you click a widget on the **Inspector** screen, the variable for this widget displays in the **Console**:

## Evaluate expressions

[#](#evaluate-expressions)

In the console, you can evaluate expressions for a paused or running application, assuming that you are running your app in debug mode:

To assign an evaluated object to a variable, use `$0`, `$1` (through `$5`) in the form of `var x = $0`:

## Browse heap snapshot

[#](#browse-heap-snapshot)

To drop a variable to the console from a heap snapshot, do the following:

1. Navigate to **Devtools > Memory > Diff Snapshots**.
2. Record a memory heap snapshot.
3. Click on the context menu `[⋮]` to view the number of **Instances** for the desired **Class**.
4. Select whether you want to store a single instance as a console variable, or whether you want to store *all* currently alive instances in the app.

The Console screen displays both live and static inbound and outbound references, as well as field values:

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/console.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/tools/devtools/console\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/console.md "Report an issue with this page").

----
url: https://docs.flutter.dev/cookbook/testing
----

[Widget testing](/cookbook/testing/widget)

[A catalog of recipes for adding widget testing to your Flutter app.](/cookbook/testing/widget)

[Integration testing](/cookbook/testing/integration)

[A catalog of recipes for adding integration testing to your Flutter app.](/cookbook/testing/integration)

[Unit testing](/cookbook/testing/unit)

[A catalog of recipes for adding unit testing to your Flutter app.](/cookbook/testing/unit)

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/testing/index.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/cookbook/testing\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/testing/index.md "Report an issue with this page").

----
url: https://docs.flutter.dev/cookbook/navigation/named-routes
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Navigate with named routes](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

In the [Navigate to a new screen and back](/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`](https://api.flutter.dev/flutter/widgets/Navigator-class.html).

However, if you need to navigate to the same screen in many parts of your app, this approach can result in code duplication. The solution is to define a *named route*, and use the named route for navigation.

To work with named routes, use the [`Navigator.pushNamed()`](https://api.flutter.dev/flutter/widgets/Navigator/pushNamed.html) function. This example replicates the functionality from the original recipe, demonstrating how to use named routes using the following steps:

1. Create two screens.
2. Define the routes.
3. Navigate to the second screen using `Navigator.pushNamed()`.
4. Return to the first screen using `Navigator.pop()`.

## 1. Create two screens

[#](#1-create-two-screens)

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

```
import 'package:flutter/material.dart';
​
class FirstScreen extends StatelessWidget {
  const FirstScreen({super.key});
​
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('First Screen')),
      body: Center(
        child: ElevatedButton(
          onPressed: () {
            // Navigate to the second screen when tapped.
          },
          child: const Text('Launch screen'),
        ),
      ),
    );
  }
}
​
class SecondScreen extends StatelessWidget {
  const SecondScreen({super.key});
​
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Second Screen')),
      body: Center(
        child: ElevatedButton(
          onPressed: () {
            // Navigate back to first screen when tapped.
          },
          child: const Text('Go back!'),
        ),
      ),
    );
  }
}
```

## 2. Define the routes

[#](#2-define-the-routes)

Next, define the routes by providing additional properties to the [`MaterialApp`](https://pub.dev/documentation/material_ui/latest/material_ui/MaterialApp-class.html) constructor: the `initialRoute` and the `routes` themselves.

The `initialRoute` property defines which route the app should start with. The `routes` property defines the available named routes and the widgets to build when navigating to those routes.

dart

```
MaterialApp(
  title: 'Named Routes Demo',
  // Start the app with the "/" named route. In this case, the app starts
  // on the FirstScreen widget.
  initialRoute: '/',
  routes: {
    // When navigating to the "/" route, build the FirstScreen widget.
    '/': (context) => const FirstScreen(),
    // When navigating to the "/second" route, build the SecondScreen widget.
    '/second': (context) => const SecondScreen(),
  },
)
```

## 3. Navigate to the second screen

[#](#3-navigate-to-the-second-screen)

With the widgets and routes in place, trigger navigation by using the [`Navigator.pushNamed()`](https://api.flutter.dev/flutter/widgets/Navigator/pushNamed.html) method. This tells Flutter to build the widget defined in the `routes` table and launch the screen.

In the `build()` method of the `FirstScreen` widget, update the `onPressed()` callback:

dart

```
// Within the `FirstScreen` widget
onPressed: () {
  // Navigate to the second screen using a named route.
  Navigator.pushNamed(context, '/second');
}
```

## 4. Return to the first screen

[#](#4-return-to-the-first-screen)

To navigate back to the first screen, use the [`Navigator.pop()`](https://api.flutter.dev/flutter/widgets/Navigator/pop.html) function.

dart

```
// Within the SecondScreen widget
onPressed: () {
  // Navigate back to the first screen by popping the current route
  // off the stack.
  Navigator.pop(context);
}
```

## Interactive example

[#](#interactive-example)

```
import 'package:flutter/material.dart';

void main() {
  runApp(
    MaterialApp(
      title: 'Named Routes Demo',
      // Start the app with the "/" named route. In this case, the app starts
      // on the FirstScreen widget.
      initialRoute: '/',
      routes: {
        // When navigating to the "/" route, build the FirstScreen widget.
        '/': (context) => const FirstScreen(),
        // When navigating to the "/second" route, build the SecondScreen widget.
        '/second': (context) => const SecondScreen(),
      },
    ),
  );
}

class FirstScreen extends StatelessWidget {
  const FirstScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('First Screen')),
      body: Center(
        child: ElevatedButton(
          // Within the `FirstScreen` widget
          onPressed: () {
            // Navigate to the second screen using a named route.
            Navigator.pushNamed(context, '/second');
          },
          child: const Text('Launch screen'),
        ),
      ),
    );
  }
}

class SecondScreen extends StatelessWidget {
  const SecondScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Second Screen')),
      body: Center(
        child: ElevatedButton(
          // Within the SecondScreen widget
          onPressed: () {
            // Navigate back to the first screen by popping the current route
            // off the stack.
            Navigator.pop(context);
          },
          child: const Text('Go back!'),
        ),
      ),
    );
  }
}
```

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-09-29. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/navigation/named-routes.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/cookbook/navigation/named-routes\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/navigation/named-routes.md "Report an issue with this page").

----
url: https://docs.flutter.dev/cookbook/design/snackbars
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Display a snackbar](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

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`](https://pub.dev/documentation/material_ui/latest/material_ui/SnackBar-class.html). This recipe implements a snackbar using the following steps:

1. Create a `Scaffold`.
2. Display a `SnackBar`.
3. Provide an optional action.

## 1. Create a `Scaffold`

[#](#1-create-a-scaffold)

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`](https://pub.dev/documentation/material_ui/latest/material_ui/Scaffold-class.html) widget, from the [material library](https://pub.dev/documentation/material_ui/latest/material_ui), creates this visual structure and ensures that important widgets don't overlap.

dart

```
return MaterialApp(
  title: 'SnackBar Demo',
  home: Scaffold(
    appBar: AppBar(title: const Text('SnackBar Demo')),
    body: const SnackBarPage(),
  ),
);
```

## 2. Display a `SnackBar`

[#](#2-display-a-snackbar)

With the `Scaffold` in place, display a `SnackBar`. First, create a `SnackBar`, then display it using `ScaffoldMessenger`.

dart

```
const snackBar = SnackBar(content: Text('Yay! A SnackBar!'));
​
// Find the ScaffoldMessenger in the widget tree
// and use it to show a SnackBar.
ScaffoldMessenger.of(context).showSnackBar(snackBar);
```

## 3. Provide an optional action

[#](#3-provide-an-optional-action)

You might want to provide an action to the user when the `SnackBar` is displayed. For example, if the user accidentally deletes a message, an `action` in the `SnackBar` could allow recovering the message.

Here's an example that provides an additional `action` to the `SnackBar` widget:

dart

```
final snackBar = SnackBar(
  content: const Text('Yay! A SnackBar!'),
  action: SnackBarAction(
    label: 'Undo',
    onPressed: () {
      // Some code to undo the change.
    },
  ),
);
```

## Interactive example

[#](#interactive-example)

```
import 'package:flutter/material.dart';

void main() => runApp(const SnackBarDemo());

class SnackBarDemo extends StatelessWidget {
  const SnackBarDemo({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'SnackBar Demo',
      home: Scaffold(
        appBar: AppBar(title: const Text('SnackBar Demo')),
        body: const SnackBarPage(),
      ),
    );
  }
}

class SnackBarPage extends StatelessWidget {
  const SnackBarPage({super.key});

  @override
  Widget build(BuildContext context) {
    return Center(
      child: ElevatedButton(
        onPressed: () {
          final snackBar = SnackBar(
            content: const Text('Yay! A SnackBar!'),
            action: SnackBarAction(
              label: 'Undo',
              onPressed: () {
                // Some code to undo the change.
              },
            ),
          );

          // Find the ScaffoldMessenger in the widget tree
          // and use it to show a SnackBar.
          ScaffoldMessenger.of(context).showSnackBar(snackBar);
        },
        child: const Text('Show SnackBar'),
      ),
    );
  }
}
```

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-09-29. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/design/snackbars.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/cookbook/design/snackbars\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/design/snackbars.md "Report an issue with this page").

----
url: https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.23.1.md
----

\# DevTools 2.23.1 release notes > Release notes for Dart and Flutter DevTools version 2.23.1. The 2.23.1 release of the Dart and Flutter DevTools includes the following changes among other general improvements. To learn more about DevTools, check out the \[DevTools overview]\(https\://docs.flutter.dev/tools/devtools). ## General updates \* Update DevTools to the new Material 3 design - \[#5429]\(https\://github.com/flutter/devtools/pull/5429) \* Use the default Flutter service worker - \[#5331]\(https\://github.com/flutter/devtools/pull/5331) \* Added the new verbose logging feature for helping us debug user issues - \[#5404]\(https\://github.com/flutter/devtools/pull/5404) !\[verbose logging]\(/assets/images/docs/tools/devtools/release-notes/images-2.23.1/verbose-logging.png "verbose\_logging") \* Fix a bug where some asynchronous errors were not being reported - \[#5456]\(https\://github.com/flutter/devtools/pull/5456) \* Added support for viewing data after an app disconnects for screens that support offline viewing (currently only the Performance and CPU profiler pages) - \[#5509]\(https\://github.com/flutter/devtools/pull/5509) \* Include settings button in the footer of the embedded view - \[#5528]\(https\://github.com/flutter/devtools/pull/5528) ## Performance updates \* Fix a performance regression in timeline event processing - \[#5460]\(https\://github.com/flutter/devtools/pull/5460) \* Persist a user's preference for whether the Flutter Frames chart should be shown by default - \[#5339]\(https\://github.com/flutter/devtools/pull/5339) \* Point users to \[Impeller]\(https\://docs.flutter.dev/perf/impeller) when shader compilation jank is detected on an iOS device - \[#5455]\(https\://github.com/flutter/devtools/pull/5455) \* Remove the CPU profiler from the legacy trace viewer - \[#5539]\(https\://github.com/flutter/devtools/pull/5539) ## CPU profiler updates \* Add a Method Table to the CPU profiler - \[#5366]\(https\://github.com/flutter/devtools/pull/5366) !\[Method table]\(/assets/images/docs/tools/devtools/release-notes/images-2.23.1/cpu-method-table.png "method\_table") \* Improve the performance of data processing in the CPU profiler - \[#5468]\(https\://github.com/flutter/devtools/pull/5468), \[#5533]\(https\://github.com/flutter/devtools/pull/5533), \[#5535]\(https\://github.com/flutter/devtools/pull/5535) \* Polish and performance improvements for the CPU profile flame chart - \[#5529]\(https\://github.com/flutter/devtools/pull/5529) \* Add ability to inspect statistics for a CPU profile - \[#5340]\(https\://github.com/flutter/devtools/pull/5340) \* Fix a bug where Native stack frames were missing their name - \[#5344]\(https\://github.com/flutter/devtools/pull/5344) \* Fix an error in total and self time calculations for the bottom up tree - \[#5348]\(https\://github.com/flutter/devtools/pull/5348) \* Add support for zooming and navigating the flame chart with ,AOE keys (helpful for Dvorak users) - \[#5545]\(https\://github.com/flutter/devtools/pull/5545) ## Memory updates \* Fix filtering bug in the "Trace Instances" view - \[#5406]\(https\://github.com/flutter/devtools/pull/5406) \* Enabled evaluation and browsing for instances in heap snapshot - \[#5542]\(https\://github.com/flutter/devtools/pull/5542) \* Fix heap snapshot failure - \[#5520]\(https\://github.com/flutter/devtools/pull/5520) \* Stop displaying external sizes in the allocation profile - \[#5555]\(https\://github.com/flutter/devtools/pull/5555) \* Expose totals for memory in heap snapshot - \[#5593]\(https\://github.com/flutter/devtools/pull/5593) ## Debugger updates \* Fix a bug where variable inspection for instances sometimes showed no children - \[#5356]\(https\://github.com/flutter/devtools/pull/5356) \* Hide "search in file" dialog if the "file search" dialog is open - \[#5393]\(https\://github.com/flutter/devtools/pull/5393) \* Fix file search bug where last letter disappeared when searching at end of file name - \[#5397]\(https\://github.com/flutter/devtools/pull/5397) \* Add search icon in file bar to make file search more discoverable - \[#5351]\(https\://github.com/flutter/devtools/issues/5351) \* Allow expression evaluation when pausing in JS for web apps - \[#5427]\(https\://github.com/flutter/devtools/pull/5427) \* Update syntax highlighting to \[dart-lang/dart-syntax-highlight v1.2.0]\(https\://github.com/dart-lang/dart-syntax-highlight/blob/master/CHANGELOG.md#120-2023-01-30) - \[#5477]\(https\://github.com/flutter/devtools/pull/5477) \* Debugger panel respects "dense mode" - \[#5517]\(https\://github.com/flutter/devtools/pull/5517) ## Network profiler updates \* Fix a bug viewing JSON responses with null values - \[#5424]\(https\://github.com/flutter/devtools/pull/5424) \* Fix a bug where JSON requests were shown in plain text, instead of the formatted JSON viewer - \[#5463]\(https\://github.com/flutter/devtools/pull/5463) \* Fix a UI issue where the copy button on the response or request tab would let you copy while still loading the data - \[#5476]\(https\://github.com/flutter/devtools/pull/5476) ## Full commit history To find a complete list of changes since the previous release, check out \[the diff on GitHub]\(https\://github.com/flutter/devtools/compare/v2.22.2...v2.23.1).

----
url: https://docs.flutter.dev/flutter-for/compose-devs
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Flutter for Jetpack Compose developers](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

Flutter is a framework for building cross-platform applications that uses the Dart programming language.

Your Jetpack Compose knowledge and experience are highly valuable when building with Flutter.

This document can be used as a reference by jumping around and finding questions that are most relevant to your needs. This guide embeds sample code.

## Overview

[#](#overview)

Flutter and Jetpack Compose code describe how the UI looks and works. Developers call this type of code a *declarative framework*.

While there are key differences especially when it comes to interacting with legacy Android code, there are many commonalities between the two frameworks.

### Composables vs. Widgets

[#](#composables-vs-widgets)

**Jetpack Compose** represents UI components as *composable functions*, later noted in this document as *composables*. Composables can be altered or decorated through the use of *Modifier* objects.

kotlin

```
Text("Hello, World!",
   modifier: Modifier.padding(10.dp)
)
Text("Hello, World!",
    modifier = Modifier.padding(10.dp))
```

**Flutter** represents UI components as *widgets*.

Both composables and widgets only exist until they need to change. These languages call this property *immutability*. Jetpack Compose modifies UI component properties using an optional *modifier* property backed by a `Modifier` object. By contrast, Flutter widgets configure their properties directly through constructor parameters.

dart

```
Padding(                         // <-- This is a Widget
  padding: EdgeInsets.all(10.0), // <-- a parameter to Padding
  child: Text("Hello, World!"),  // <-- This is also a Widget
);
```

To compose layouts, both Jetpack Compose and Flutter nest UI components within one another. Jetpack Compose nests `Composables` while Flutter nests `Widgets`.

### Layout process

[#](#layout-process)

Jetpack Compose and Flutter handle layout in similar ways. Both of them lay out the UI in a single pass and parent elements provide layout constraints down to their children. More specifically,

1. The parent measures itself and its children recursively providing any constraints from the parent to the child.
2. The children try to size themselves using the above methods and provide their own children both their constraints and any that might apply from their ancestor nodes.
3. Upon encountering a leaf node (a node with no children), the size and properties are determined based on the provided constraints and the element is placed in the UI.
4. With all the children sized and placed, the root nodes can determine their measurement, size, and placement.

In both Jetpack Compose and Flutter, the parent component can override or constrain the child's desired size. The widget cannot have any size it wants. It also cannot *usually* know or decide its position on screen as its parent makes that decision.

To force a child widget to render at a specific size, the parent must set tight constraints. A constraint becomes tight when its constraint's minimum size value equals its maximum size value.

To learn how constraints work in Flutter, visit [Understanding constraints](/ui/layout/constraints).

### Design system

[#](#design-system)

Because Flutter targets multiple platforms, your app doesn't need to conform to any design system. While this guide features [Material](https://m3.material.io/develop/flutter/) widgets, your Flutter app can use many different design systems:

* Custom Material widgets
* Community built widgets
* Your own custom widgets

If you're looking for a great reference app that features a custom design system, check out [Wonderous](https://flutter.gskinner.com/wonderous/?utm_source=flutterdocs\&utm_medium=docs).

## UI basics

[#](#ui-basics)

This section covers the basics of UI development in Flutter and how it compares to Jetpack Compose. This includes how to start developing your app, display static text, create buttons, react to on-press events, display lists, grids, and more.

### Getting started

[#](#getting-started)

For **Compose** apps, your main entry point will be *Activity* or one of its descendants, generally *ComponentActivity*.

kotlin

```
class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()
        setContent {
            SampleTheme {
                Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
                    Greeting(
                        name = "Android",
                        modifier = Modifier.padding(innerPadding)
                    )
                }
            }
        }
    }
}
​
@Composable
fun Greeting(name: String, modifier: Modifier = Modifier) {
    Text(
        text = "Hello $name!",
        modifier = modifier
    )
}
```

To start your **Flutter** app, pass an instance of your app to the `runApp` function.

dart

```
void main() {
  runApp(const MyApp());
}
```

`App` is a widget. It's `build` method describes the part of the user interface it represents. It's common to begin your app with a [`WidgetApp`](https://api.flutter.dev/flutter/widgets/WidgetsApp-class.html) class, like [`MaterialApp`](https://pub.dev/documentation/material_ui/latest/material_ui/MaterialApp-class.html).

dart

```
class MyApp extends StatelessWidget {
  const MyApp({super.key});
​
  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: HomePage(),
    );
  }
}
```

The widget used in the `HomePage` might begin with the `Scaffold` class. `Scaffold` implements a basic layout structure for an app.

dart

```
class HomePage extends StatelessWidget {
  const HomePage({super.key});
​
  @override
  Widget build(BuildContext context) {
    return const Scaffold(
      body: Center(
        child: Text(
          'Hello, World!',
        ),
      ),
    );
  }
}
```

Note how Flutter uses the [`Center`](https://api.flutter.dev/flutter/widgets/Center-class.html) widget.

Compose has a number of defaults from its ancestor Android Views. Unless otherwise specified, most components "wrap" their size to content meaning they only take up as much space as needed when rendered. That's not always the case with Flutter.

To center the text, wrap it in a `Center` widget. To learn about different widgets and their default behaviors, check out the [Widget catalog](/ui/widgets/layout).

### Adding Buttons

[#](#adding-buttons)

In **Compose**, you use the `Button` composable or one of its variants to create a button. `Button` is an alias for `FilledTonalButton` when using a Material theme.

kotlin

```
Button(onClick = {}) {
    Text("Do something")
}
```

To achieve the same result in **Flutter**, use the `FilledButton` class:

dart

```
FilledButton(
  onPressed: () {
    // This closure is called when your button is tapped.
  },
  const Text('Do something'),
),
```

**Flutter** gives you access to a variety of buttons with pre-defined styles.

### Aligning components horizontally or vertically

[#](#aligning-components-horizontally-or-vertically)

Jetpack Compose and Flutter handle horizontal and vertical collections of items similarly.

The following Compose snippet adds a globe image and text in both `Row` and `Column` containers with centering of the items:

kotlin

```
Row(horizontalArrangement = Arrangement.Center) {
   Image(Icons.Default.Public, contentDescription = "")
   Text("Hello, world!")
}
​
Column(verticalArrangement = Arrangement.Center) {
   Image(Icons.Default.Public, contentDescription = "")
   Text("Hello, world!")
}
```

**Flutter** uses [`Row`](https://api.flutter.dev/flutter/widgets/Row-class.html) and [`Column`](https://api.flutter.dev/flutter/widgets/Column-class.html) as well but there are some slight differences for specifying child widgets and alignment. The following is equivalent to the Compose example.

dart

```
Row(
  mainAxisAlignment: MainAxisAlignment.center,
  children: [
    Icon(Icons.public),
    Text('Hello, world!'),
  ],
),
​
Column(
  mainAxisAlignment: MainAxisAlignment.center,
  children: [
    Icon(MaterialIcons.globe),
    Text('Hello, world!'),
  ],
)
```

`Row` and `Column` require a `List<Widget>` in the `children` parameter. The `mainAxisAlignment` property tells Flutter how to position children with extra space. `MainAxisAlignment.center` positions children in the center of the main axis. For `Row`, the main axis is the horizontal axis, inversely for `Column`, the main axis is the vertical axis.

::: note Whereas Flutter's `Row` and `Column` have `MainAxisAlignment` and `CrossAxisAlignment` to control how items are placed, the properties that control placement in Jetpack Compose are one vertical and horizontal property from the following: `verticalArrangement`, `verticalAlignment`, `horizontalAlignment`, and `horizontalArrangement`. The trick to determine which is the `MainAxis` is to look for the property that ends in `arrangement`. The `CrossAxis` will be the property that ends in `alignment`. :::

### Displaying a list view

[#](#displaying-a-list-view)

In **Compose**, you have a couple ways to create a list based on the size of the list you need to display. For a small number of items that can all be displayed at once, you can iterate over a collection inside a `Column` or `Row`.

For a list with a large number of items, `LazyList` has better performance. It only lays out the components that will be visible versus all of them.

kotlin

```
data class Person(val name: String)
​
val people = arrayOf(
   Person(name = "Person 1"),
   Person(name = "Person 2"),
   Person(name = "Person 3")
)
​
@Composable
fun ListDemo(people: List<Person>) {
   Column {
      people.forEach {
         Text(it.name)
      }
   }
}
​
@Composable
fun ListDemo2(people: List<Person>) {
   LazyColumn {
      items(people) { person ->
         Text(person.name)
      }
   }
}
```

To lazily build a list in Flutter, ....

dart

```
class Person {
  String name;
  Person(this.name);
}
​
var items = [
  Person('Person 1'),
  Person('Person 2'),
  Person('Person 3'),
];
​
class HomePage extends StatelessWidget {
  const HomePage({super.key});
​
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: ListView.builder(
        itemCount: items.length,
        itemBuilder: (context, index) {
          return ListTile(
            title: Text(items[index].name),
          );
        },
      ),
    );
  }
}
```

Flutter has some conventions for lists:

* The [`ListView`](https://api.flutter.dev/flutter/widgets/ListView-class.html) widget has a builder method. This works like the `item` closure inside a Compose `LazyList`.

* The `itemCount` parameter of the `ListView` sets how many items the `ListView` displays.

* The `itemBuilder` has an index parameter that will be between zero and one less than itemCount.

The previous example returned a [`ListTile`](https://api.flutter.dev/flutter/widgets/ListTitle-class.html) widget for each item. The `ListTile` widget includes properties like `height` and `font-size`. These properties help build a list. However, Flutter allows you to return almost any widget that represents your data.

### Displaying a grid

[#](#displaying-a-grid)

Constructing a grid in **Compose** is similar to a LazyList (`LazyColumn` or `LazyRow`). You can use the same `items` closure. There are properties on each grid type to specify how to arrange the items, whether or not to use adaptive or fixed layout, amongst others.

kotlin

```
val widgets = arrayOf(
        "Row 1",
        Icons.Filled.ArrowDownward,
        Icons.Filled.ArrowUpward,
        "Row 2",
        Icons.Filled.ArrowDownward,
        Icons.Filled.ArrowUpward
    )
​
    LazyVerticalGrid (
        columns = GridCells.Fixed(3),
        contentPadding = PaddingValues(8.dp)
    ) {
        items(widgets) { i ->
            if (i is String) {
                Text(i)
            } else {
                Image(i as ImageVector, "")
            }
        }
    }
```

To display grids in **Flutter**, use the [`GridView`](https://api.flutter.dev/flutter/widgets/GridView-class.html) widget. This widget has various constructors. Each constructor has a similar goal, but uses different input parameters. The following example uses the `.builder()` initializer:

dart

```
const widgets = [
  Text('Row 1'),
  Icon(Icons.arrow_downward),
  Icon(Icons.arrow_upward),
  Text('Row 2'),
  Icon(Icons.arrow_downward),
  Icon(Icons.arrow_upward),
];
​
class HomePage extends StatelessWidget {
  const HomePage({super.key});
​
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: GridView.builder(
        gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
          crossAxisCount: 3,
          mainAxisExtent: 40,
        ),
        itemCount: widgets.length,
        itemBuilder: (context, index) => widgets[index],
      ),
    );
  }
}
```

The `SliverGridDelegateWithFixedCrossAxisCount` delegate determines various parameters that the grid uses to lay out its components. This includes `crossAxisCount` that dictates the number of items displayed on each row.

Jetpack Compose's `LazyHorizontalGrid`, `LazyVerticalGrid`, and Flutter's `GridView` are somewhat similar. `GridView` uses a delegate to decide how the grid should lay out its components. The `rows`, `columns`, and other associated properties on `LazyHorizontalGrid` \ `LazyVerticalGrid` serve the same purpose.

### Creating a scroll view

[#](#creating-a-scroll-view)

`LazyColumn` and `LazyRow` in **Jetpack Compose** have built-in support for scrolling.

To create a scrolling view, **Flutter** uses [`SingleChildScrollView`](https://api.flutter.dev/flutter/widgets/SingleChildScrollView-class.html). In the following example, the function `mockPerson` mocks instances of the `Person` class to create the custom `PersonView` widget.

dart

```
SingleChildScrollView(
  child: Column(
    children: mockPersons
        .map(
          (person) => PersonView(
            person: person,
          ),
        )
        .toList(),
  ),
),
```

### Responsive and adaptive design

[#](#responsive-and-adaptive-design)

Adaptive Design in **Compose** is a complex topic with many viable solutions:

* Using a custom layout
* Using `WindowSizeClass` alone
* Using `BoxWithConstraints` to control what is shown based on available space
* Using the Material 3 adaptive library that uses `WindowSizeClass` along with specialized composable layouts for common layouts

For that reason, you are encouraged to look into the **Flutter** options directly and see what fits your requirements versus attempting to find something that is a one to one translation.

To create relative views in **Flutter**, you can use one of two options:

* Get the `BoxConstraints` object in the [`LayoutBuilder`](https://api.flutter.dev/flutter/widgets/LayoutBuilder-class.html) class.
* Use the [`MediaQuery.of()`](https://api.flutter.dev/flutter/widgets/MediaQuery-class.html) in your build functions to get the size and orientation of your current app.

To learn more, check out [Creating responsive and adaptive apps](/ui/adaptive-responsive).

### Managing state

[#](#managing-state)

**Compose** stores state with the `remember` API and descendants of the `MutableState` interface.

kotlin

```
Scaffold(
   content = { padding ->
      var _counter = remember {  mutableIntStateOf(0) }
      Column(horizontalAlignment = Alignment.CenterHorizontally,
         verticalArrangement = Arrangement.Center,
         modifier = Modifier.fillMaxSize().padding(padding)) {
            Text(_counter.value.toString())
            Spacer(modifier = Modifier.height(16.dp))
            FilledIconButton (onClick = { -> _counter.intValue += 1 }) {
               Text("+")
            }
      }
   }
)
```

**Flutter** manages local state using a [`StatefulWidget`](https://api.flutter.dev/flutter/widgets/StatefulWidget-class.html). Implement a stateful widget with the following two classes:

* a subclass of `StatefulWidget`
* a subclass of `State`

The `State` object stores the widget's state. To change a widget's state, call `setState()` from the `State` subclass to tell the framework to redraw the widget.

The following example shows a part of a counter app:

dart

```
class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key});
  @override
  State<MyHomePage> createState() => _MyHomePageState();
}
​
class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;
​
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text('$_counter'),
            TextButton(
              onPressed: () => setState(() {
                _counter++;
              }),
              child: const Text('+'),
            ),
          ],
        ),
      ),
    );
  }
}
```

To learn more ways to manage state, check out [State management](/data-and-backend/state-mgmt).

### Drawing on the Screen

[#](#drawing-on-the-screen)

In **Compose**, you use the `Canvas` composable to draw shapes, images, and text to the screen.

**Flutter** has an API based on the `Canvas` class, with two classes that help you draw:

1. [`CustomPaint`](https://api.flutter.dev/flutter/widgets/CustomPaint-class.html) that requires a painter:

   dart

   ```
   CustomPaint(
     painter: SignaturePainter(_points),
     size: Size.infinite,
   ),
   ```

2. [`CustomPainter`](https://api.flutter.dev/flutter/rendering/CustomPainter-class.html) that implements your algorithm to draw to the canvas.

   dart

   ```
   class SignaturePainter extends CustomPainter {
     SignaturePainter(this.points);
   ​
     final List<Offset?> points;
   ​
     @override
     void paint(Canvas canvas, Size size) {
       final Paint paint = Paint()
         ..color = Colors.black
         ..strokeCap = StrokeCap.round
         ..strokeWidth = 5;
       for (int i = 0; i < points.length - 1; i++) {
         if (points[i] != null && points[i + 1] != null) {
           canvas.drawLine(points[i]!, points[i + 1]!, paint);
         }
       }
     }
   ​
     @override
     bool shouldRepaint(SignaturePainter oldDelegate) =>
         oldDelegate.points != points;
   }
   ```

## Themes, styles, and media

[#](#themes-styles-and-media)

You can style Flutter apps with little effort. Styling includes switching between light and dark themes, changing the design of your text and UI components, and more. This section covers how to style your apps.

### Using dark mode

[#](#using-dark-mode)

In **Compose**, you can control light and dark at any arbitrary level by wrapping a component with a `Theme` composable.

In **Flutter**, you can control light and dark mode at the app-level. To control the brightness mode, use the `theme` property of the `App` class:

dart

```
const MaterialApp(
  theme: ThemeData(
    brightness: Brightness.dark,
  ),
  home: HomePage(),
);
```

### Styling text

[#](#styling-text)

In **Compose**, you use the properties on `Text` for one or two attributes or construct a `TextStyle` object to set many at once.

kotlin

```
Text("Hello, world!", color = Color.Green,
        fontWeight = FontWeight.Bold, fontSize = 30.sp)
```

kotlin

```
Text("Hello, world!",
   style = TextStyle(
      color = Color.Green,
      fontSize = 30.sp,
      fontWeight = FontWeight.Bold
   ),
)
```

To style text in **Flutter**, add a `TextStyle` widget as the value of the `style` parameter of the `Text` widget.

dart

```
Text(
  'Hello, world!',
  style: TextStyle(
    fontSize: 30,
    fontWeight: FontWeight.bold,
    color: Colors.blue,
  ),
),
```

### Styling buttons

[#](#styling-buttons)

In **Compose**, you modify the colors of a button using the `colors` property. If left unmodified, they use the defaults from the current theme.

kotlin

```
Button(onClick = {},
   colors = ButtonDefaults.buttonColors().copy(
      containerColor = Color.Yellow, contentColor = Color.Blue,
       )) {
    Text("Do something", fontSize = 30.sp, fontWeight = FontWeight.Bold)
}
```

To style button widgets in **Flutter**, you similarly set the style of its child, or modify properties on the button itself.

dart

```
FilledButton(
  onPressed: (){},
  style: FilledButton.styleFrom(backgroundColor: Colors.amberAccent),
  child: const Text(
    'Do something',
    style: TextStyle(
      color: Colors.blue,
      fontSize: 30,
      fontWeight: FontWeight.bold,
    )
  )
)
```

## Bundling assets for use in Flutter

[#](#bundling-assets-for-use-in-flutter)

There is commonly a need to bundle resources for use in your application. They can be animations, vector graphics, images, fonts, or other general files.

Unlike native Android apps that expect a set directory structure under `/res/<qualifier>/` where the qualifier could be indicating the type of file, a specific orientation, or android version, Flutter doesn't require a specific location as long as the referenced files are listed in the `pubspec.yaml` file. Below is an excerpt from a `pubspec.yaml` referencing several images and a font file.

yaml

```
flutter:
  assets:
    - assets/my_icon.png
    - assets/background.png
  fonts:
    - family: FiraSans
      fonts:
        - asset: fonts/FiraSans-Regular.ttf
```

### Using fonts

[#](#using-fonts)

In **Compose**, you have two options for using fonts in your app. You can use a runtime service I to retrieve them [Google Fonts](https://fonts.google.com/). Alternatively, they may be bundled in resource files.

**Flutter** has similar methods to use fonts, let's discuss them both inline.

### Using bundled fonts

[#](#using-bundled-fonts)

The following are roughly equivalent Compose and Flutter code for using a font file in the `/res/` or `fonts` directory as listed above.

kotlin

```
// Font files bundled with app
val firaSansFamily = FontFamily(
   Font(R.font.firasans_regular, FontWeight.Normal),
   // ...
)
​
// Usage
Text(text = "Compose", fontFamily = firaSansFamily, fontWeight = FontWeight.Normal)
```

dart

```
Text(
  'Flutter',
  style: TextStyle(
    fontSize: 40,
    fontFamily: 'FiraSans',
  ),
),
```

### Using a font provider (Google Fonts)

[#](#using-a-font-provider-google-fonts)

One point of difference is using fonts from a font provider like Google Fonts. In **Compose**, the instantiation is done inline with the same approximate code to reference a local file.

After instantiating a provider that references the special strings for the font service, you would use the same `FontFamily` declaration.

kotlin

```
// Font files bundled with app
val provider = GoogleFont.Provider(
    providerAuthority = "com.google.android.gms.fonts",
    providerPackage = "com.google.android.gms",
    certificates = R.array.com_google_android_gms_fonts_certs
)
​
val firaSansFamily = FontFamily(
    Font(
        googleFont = GoogleFont("FiraSans"),
        fontProvider = provider,
    )
)
​
// Usage
Text(text = "Compose", fontFamily = firaSansFamily, fontWeight = FontWeight.Light)
```

For Flutter, this is provided by the [google\_fonts](https://pub.dev/packages/google_fonts) plugin using the name of the font.

dart

```
import 'package:google_fonts/google_fonts.dart';
//...
Text(
  'Flutter',
  style: GoogleFonts.firaSans(),
  // or
  //style: GoogleFonts.getFont('FiraSans')
),
```

### Using images

[#](#using-images)

In **Compose**, typically image files to the drawable directory in resources `/res/drawable` and one uses `Image` composable to display the images. Assets are referenced by using the resource locator in the style of `R.drawable.<file name>` without the file extension.

In **Flutter**, the resource location is a listed in `pubspec.yaml` as shown in the snippet below.

yaml

```
    flutter:
      assets:
        - images/Blueberries.jpg
```

After adding your image, you can display it using the `Image` widget's `.asset()` constructor. This constructor:

To review a complete example, check out the [`Image`](https://api.flutter.dev/flutter/widgets/Image-class.html) docs.

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-09-29. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/flutter-for/compose-devs.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/flutter-for/compose-devs\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/flutter-for/compose-devs.md "Report an issue with this page").

----
url: https://docs.flutter.dev/reference/widgets
----

This is an alphabetical list of many of the widgets that are bundled with Flutter. You can also [browse widgets by category](/ui/widgets).

You might also want to check out our Widget of the Week video series on the [Flutter YouTube channel](https://www.youtube.com/@flutterdev). Each short episode features a different Flutter widget. For more video series, see our [videos](/resources/videos) page.

[Watch on YouTube in a new tab: "Introducing the Flutter Widget of the Week"](https://www.youtube.com/watch/b_sQ9bMltGU)

[Widget of the Week playlist](https://www.youtube.com/playlist?list=PLjxrf2q8roU23XGwz3Km7sQZFTdB996iG)

[Absorb​Pointer](https://api.flutter.dev/flutter/widgets/AbsorbPointer-class.html)

[A widget that absorbs pointers during hit testing. When absorbing is true, this widget prevents its subtree from receiving pointer events by terminating hit testing...](https://api.flutter.dev/flutter/widgets/AbsorbPointer-class.html)

[Alert​Dialog](https://pub.dev/documentation/material_ui/latest/material_ui/AlertDialog-class.html)

[Hovering containers that prompt app users to provide more data or make a decision.](https://pub.dev/documentation/material_ui/latest/material_ui/AlertDialog-class.html)

[Align](https://api.flutter.dev/flutter/widgets/Align-class.html)

[A widget that aligns its child within itself and optionally sizes itself based on the child's size.](https://api.flutter.dev/flutter/widgets/Align-class.html)

[App​Bar](https://pub.dev/documentation/material_ui/latest/material_ui/AppBar-class.html)

[Container that displays content and actions at the top of a screen.](https://pub.dev/documentation/material_ui/latest/material_ui/AppBar-class.html)

[Aspect​Ratio](https://api.flutter.dev/flutter/widgets/AspectRatio-class.html)

[A widget that attempts to size the child to a specific aspect ratio.](https://api.flutter.dev/flutter/widgets/AspectRatio-class.html)

[Asset​Bundle](https://api.flutter.dev/flutter/services/AssetBundle-class.html)

[Asset bundles contain resources, such as images and strings, that can be used by an application. Access to these resources is asynchronous so that they...](https://api.flutter.dev/flutter/services/AssetBundle-class.html)

[Autocomplete](https://pub.dev/documentation/material_ui/latest/material_ui/Autocomplete-class.html)

[A widget for helping the user make a selection by entering some text and choosing from among a list of options.](https://pub.dev/documentation/material_ui/latest/material_ui/Autocomplete-class.html)

[Backdrop​Filter](https://api.flutter.dev/flutter/widgets/BackdropFilter-class.html)

[A widget that applies a filter to the existing painted content and then paints a child. This effect is relatively expensive, especially if the filter...](https://api.flutter.dev/flutter/widgets/BackdropFilter-class.html)

[Badge](https://pub.dev/documentation/material_ui/latest/material_ui/Badge-class.html)

[Icon-like block that conveys dynamic content such as counts or status. It can include labels or numbers.](https://pub.dev/documentation/material_ui/latest/material_ui/Badge-class.html)

[Baseline](https://api.flutter.dev/flutter/widgets/Baseline-class.html)

[Container that positions its child according to the child's baseline.](https://api.flutter.dev/flutter/widgets/Baseline-class.html)

[Bottom app bar](https://pub.dev/documentation/material_ui/latest/material_ui/BottomAppBar-class.html)

[Container that displays navigation and key actions at the bottom of a screen.](https://pub.dev/documentation/material_ui/latest/material_ui/BottomAppBar-class.html)

[Bottom sheet](https://pub.dev/documentation/material_ui/latest/material_ui/BottomSheet-class.html)

[Containers that anchor supplementary content to the bottom of the screen.](https://pub.dev/documentation/material_ui/latest/material_ui/BottomSheet-class.html)

[Bottom​Navigation​Bar](https://pub.dev/documentation/material_ui/latest/material_ui/BottomNavigationBar-class.html)

[Container that includes tools to explore and switch between top-level views in a single tap.](https://pub.dev/documentation/material_ui/latest/material_ui/BottomNavigationBar-class.html)

[Card](https://pub.dev/documentation/material_ui/latest/material_ui/Card-class.html)

[Container for short, related pieces of content displayed in a box with rounded corners and a drop shadow.](https://pub.dev/documentation/material_ui/latest/material_ui/Card-class.html)

[Carousel​View](https://pub.dev/documentation/material_ui/latest/material_ui/CarouselView-class.html)

[A Material carousel widget that presents a scrollable list of items, each of which can dynamically change size based on the chosen layout.](https://pub.dev/documentation/material_ui/latest/material_ui/CarouselView-class.html)

[Center](https://api.flutter.dev/flutter/widgets/Center-class.html)

[Alignment block that centers its child within itself.](https://api.flutter.dev/flutter/widgets/Center-class.html)

[Checkbox](https://pub.dev/documentation/material_ui/latest/material_ui/Checkbox-class.html)

[Form control that app users can set or clear to select one or more options from a set.](https://pub.dev/documentation/material_ui/latest/material_ui/Checkbox-class.html)

[Chip](https://pub.dev/documentation/material_ui/latest/material_ui/Chip-class.html)

[Small blocks that simplify entering information, making selections, filtering content, or triggering actions.](https://pub.dev/documentation/material_ui/latest/material_ui/Chip-class.html)

[Circular​Progress​Indicator](https://pub.dev/documentation/material_ui/latest/material_ui/CircularProgressIndicator-class.html)

[Circular progress indicator that spins to indicate a busy application.](https://pub.dev/documentation/material_ui/latest/material_ui/CircularProgressIndicator-class.html)

[Clip​Oval](https://api.flutter.dev/flutter/widgets/ClipOval-class.html)

[A widget that clips its child using an oval.](https://api.flutter.dev/flutter/widgets/ClipOval-class.html)

[Clip​Path](https://api.flutter.dev/flutter/widgets/ClipPath-class.html)

[A widget that clips its child using a path.](https://api.flutter.dev/flutter/widgets/ClipPath-class.html)

[Clip​Rect](https://api.flutter.dev/flutter/widgets/ClipRect-class.html)

[A widget that clips its child using a rectangle.](https://api.flutter.dev/flutter/widgets/ClipRect-class.html)

[Column](https://api.flutter.dev/flutter/widgets/Column-class.html)

[Layout a list of child widgets in the vertical direction.](https://api.flutter.dev/flutter/widgets/Column-class.html)

[Common buttons](https://pub.dev/documentation/material_ui/latest/material_ui/ButtonStyle-class.html#material-3-button-types)

[Clickable blocks that start an action, such as sending an email, sharing a document, or liking a comment.](https://pub.dev/documentation/material_ui/latest/material_ui/ButtonStyle-class.html#material-3-button-types)

[Constrained​Box](https://api.flutter.dev/flutter/widgets/ConstrainedBox-class.html)

[A widget that imposes additional constraints on its child.](https://api.flutter.dev/flutter/widgets/ConstrainedBox-class.html)

[Container](https://api.flutter.dev/flutter/widgets/Container-class.html)

[A convenience widget that combines common painting, positioning, and sizing widgets.](https://api.flutter.dev/flutter/widgets/Container-class.html)

[Cupertino​Scroll​Behavior](https://pub.dev/documentation/cupertino_ui/latest/cupertino_ui/CupertinoScrollBehavior-class.html)

[Describes how Scrollable widgets behave for CupertinoApps.](https://pub.dev/documentation/cupertino_ui/latest/cupertino_ui/CupertinoScrollBehavior-class.html)

[Cupertino​Scrollbar](https://pub.dev/documentation/cupertino_ui/latest/cupertino_ui/CupertinoScrollbar-class.html)

[An iOS-style scrollbar that indicates which portion of a scrollable widget is currently visible.](https://pub.dev/documentation/cupertino_ui/latest/cupertino_ui/CupertinoScrollbar-class.html)

[Cupertino​Theme​Data](https://pub.dev/documentation/cupertino_ui/latest/cupertino_ui/CupertinoThemeData-class.html)

[Styling specifications for a CupertinoTheme.](https://pub.dev/documentation/cupertino_ui/latest/cupertino_ui/CupertinoThemeData-class.html)

[Cupertino​Thumb​Painter](https://pub.dev/documentation/cupertino_ui/latest/cupertino_ui/CupertinoThumbPainter-class.html)

[Paints an iOS-style slider thumb or switch thumb.](https://pub.dev/documentation/cupertino_ui/latest/cupertino_ui/CupertinoThumbPainter-class.html)

[Cupertino​Timer​Picker](https://pub.dev/documentation/cupertino_ui/latest/cupertino_ui/CupertinoTimerPicker-class.html)

[An iOS-style countdown timer picker.](https://pub.dev/documentation/cupertino_ui/latest/cupertino_ui/CupertinoTimerPicker-class.html)

[Custom​Multi​Child​Layout](https://api.flutter.dev/flutter/widgets/CustomMultiChildLayout-class.html)

[A widget that uses a delegate to size and position multiple children.](https://api.flutter.dev/flutter/widgets/CustomMultiChildLayout-class.html)

[Custom​Paint](https://api.flutter.dev/flutter/widgets/CustomPaint-class.html)

[A widget that provides a canvas on which to draw during the paint phase.](https://api.flutter.dev/flutter/widgets/CustomPaint-class.html)

[Custom​Scroll​View](https://api.flutter.dev/flutter/widgets/CustomScrollView-class.html)

[A ScrollView that creates custom scroll effects using slivers.](https://api.flutter.dev/flutter/widgets/CustomScrollView-class.html)

[Custom​Single​Child​Layout](https://api.flutter.dev/flutter/widgets/CustomSingleChildLayout-class.html)

[A widget that defers the layout of its single child to a delegate.](https://api.flutter.dev/flutter/widgets/CustomSingleChildLayout-class.html)

[Data​Table](https://pub.dev/documentation/material_ui/latest/material_ui/DataTable-class.html)

[Data tables display sets of raw data. They usually appear in desktop enterprise products. The DataTable widget implements this component.](https://pub.dev/documentation/material_ui/latest/material_ui/DataTable-class.html)

[Date​Picker](https://pub.dev/documentation/material_ui/latest/material_ui/showDatePicker.html)

[Calendar interface used to select a date or a range of dates.](https://pub.dev/documentation/material_ui/latest/material_ui/showDatePicker.html)

[Decorated​Box](https://api.flutter.dev/flutter/widgets/DecoratedBox-class.html)

[A widget that paints a Decoration either before or after its child paints.](https://api.flutter.dev/flutter/widgets/DecoratedBox-class.html)

[Decorated​Box​Transition](https://api.flutter.dev/flutter/widgets/DecoratedBoxTransition-class.html)

[Animated version of a DecoratedBox that animates the different properties of its Decoration.](https://api.flutter.dev/flutter/widgets/DecoratedBoxTransition-class.html)

[Default​Text​Style](https://api.flutter.dev/flutter/widgets/DefaultTextStyle-class.html)

[The text style to apply to descendant Text widgets without explicit style.](https://api.flutter.dev/flutter/widgets/DefaultTextStyle-class.html)

[Default​Text​Style​Transition](https://api.flutter.dev/flutter/widgets/DefaultTextStyleTransition-class.html)

[Animated version of a DefaultTextStyle that animates the different properties of its TextStyle.](https://api.flutter.dev/flutter/widgets/DefaultTextStyleTransition-class.html)

[Dismissible](https://api.flutter.dev/flutter/widgets/Dismissible-class.html)

[A widget that can be dismissed by dragging in the indicated direction. Dragging or flinging this widget in the DismissDirection causes the child to slide...](https://api.flutter.dev/flutter/widgets/Dismissible-class.html)

[Divider](https://pub.dev/documentation/material_ui/latest/material_ui/Divider-class.html)

[Thin line that groups content in lists and containers.](https://pub.dev/documentation/material_ui/latest/material_ui/Divider-class.html)

[Drag​Target](https://api.flutter.dev/flutter/widgets/DragTarget-class.html)

[A widget that receives data when a Draggable widget is dropped. When a draggable is dragged on top of a drag target, the drag target...](https://api.flutter.dev/flutter/widgets/DragTarget-class.html)

[Draggable](https://api.flutter.dev/flutter/widgets/Draggable-class.html)

[A widget that can be dragged from to a DragTarget. When a draggable widget recognizes the start of a drag gesture, it displays a feedback...](https://api.flutter.dev/flutter/widgets/Draggable-class.html)

[Draggable​Scrollable​Sheet](https://api.flutter.dev/flutter/widgets/DraggableScrollableSheet-class.html)

[A container for a Scrollable that responds to drag gestures by resizing the scrollable until a limit is reached, and then scrolling.](https://api.flutter.dev/flutter/widgets/DraggableScrollableSheet-class.html)

[Drawer](https://pub.dev/documentation/material_ui/latest/material_ui/Drawer-class.html)

[A Material Design panel that slides in horizontally from the edge of a Scaffold to show navigation links in an application.](https://pub.dev/documentation/material_ui/latest/material_ui/Drawer-class.html)

[Dropdown​Button](https://pub.dev/documentation/material_ui/latest/material_ui/DropdownButton-class.html)

[Shows the currently selected item and an arrow that opens a menu for selecting another item.](https://pub.dev/documentation/material_ui/latest/material_ui/DropdownButton-class.html)

[Elevated​Button](https://pub.dev/documentation/material_ui/latest/material_ui/ElevatedButton-class.html)

[A Material Design elevated button. A filled button whose material elevates when pressed.](https://pub.dev/documentation/material_ui/latest/material_ui/ElevatedButton-class.html)

[Exclude​Semantics](https://api.flutter.dev/flutter/widgets/ExcludeSemantics-class.html)

[A widget that drops all the semantics of its descendants. This can be used to hide subwidgets that would otherwise be reported but that would...](https://api.flutter.dev/flutter/widgets/ExcludeSemantics-class.html)

[Expanded](https://api.flutter.dev/flutter/widgets/Expanded-class.html)

[A widget that expands a child of a Row, Column, or Flex.](https://api.flutter.dev/flutter/widgets/Expanded-class.html)

[Expansion​Panel](https://pub.dev/documentation/material_ui/latest/material_ui/ExpansionPanel-class.html)

[Expansion panels contain creation flows and allow lightweight editing of an element. The ExpansionPanel widget implements this component.](https://pub.dev/documentation/material_ui/latest/material_ui/ExpansionPanel-class.html)

[Extended Floating​Action​Button](https://pub.dev/documentation/material_ui/latest/material_ui/FloatingActionButton/FloatingActionButton.extended.html)

[Clickable block that triggers an action. These wider blocks can fit a text label and provide a larger target area.](https://pub.dev/documentation/material_ui/latest/material_ui/FloatingActionButton/FloatingActionButton.extended.html)

[Fade​Transition](https://api.flutter.dev/flutter/widgets/FadeTransition-class.html)

[Animates the opacity of a widget.](https://api.flutter.dev/flutter/widgets/FadeTransition-class.html)

[Fitted​Box](https://api.flutter.dev/flutter/widgets/FittedBox-class.html)

[Scales and positions its child within itself according to fit.](https://api.flutter.dev/flutter/widgets/FittedBox-class.html)

[Floating​Action​Button](https://pub.dev/documentation/material_ui/latest/material_ui/FloatingActionButton-class.html)

[Clickable block containing an icon that keeps a key action always in reach.](https://pub.dev/documentation/material_ui/latest/material_ui/FloatingActionButton-class.html)

[Flow](https://api.flutter.dev/flutter/widgets/Flow-class.html)

[A widget that implements the flow layout algorithm.](https://api.flutter.dev/flutter/widgets/Flow-class.html)

[Flutter​Logo](https://api.flutter.dev/flutter/widgets/FlutterLogo-class.html)

[The Flutter logo, in widget form. This widget respects the IconTheme.](https://api.flutter.dev/flutter/widgets/FlutterLogo-class.html)

[Form](https://api.flutter.dev/flutter/widgets/Form-class.html)

[An optional container for grouping together multiple form field widgets (e.g. TextField widgets).](https://api.flutter.dev/flutter/widgets/Form-class.html)

[Form​Field](https://api.flutter.dev/flutter/widgets/FormField-class.html)

[A single form field. This widget maintains the current state of the form field, so that updates and validation errors are visually reflected in the...](https://api.flutter.dev/flutter/widgets/FormField-class.html)

[Fractional​Translation](https://api.flutter.dev/flutter/widgets/FractionalTranslation-class.html)

[A widget that applies a translation expressed as a fraction of the box's size before painting its child.](https://api.flutter.dev/flutter/widgets/FractionalTranslation-class.html)

[Fractionally​Sized​Box](https://api.flutter.dev/flutter/widgets/FractionallySizedBox-class.html)

[A widget that sizes its child to a fraction of the total available space. For more details about the layout algorithm, see RenderFractionallySizedOverflowBox.](https://api.flutter.dev/flutter/widgets/FractionallySizedBox-class.html)

[Future​Builder](https://api.flutter.dev/flutter/widgets/FutureBuilder-class.html)

[Widget that builds itself based on the latest snapshot of interaction with a Future.](https://api.flutter.dev/flutter/widgets/FutureBuilder-class.html)

[Gesture​Detector](https://api.flutter.dev/flutter/widgets/GestureDetector-class.html)

[A widget that detects gestures. Attempts to recognize gestures that correspond to its non-null callbacks. If this widget has a child, it defers to that...](https://api.flutter.dev/flutter/widgets/GestureDetector-class.html)

[Grid​View](https://api.flutter.dev/flutter/widgets/GridView-class.html)

[A grid list consists of a repeated pattern of cells arrayed in a vertical and horizontal layout. The GridView widget implements this component.](https://api.flutter.dev/flutter/widgets/GridView-class.html)

[Hero](https://api.flutter.dev/flutter/widgets/Hero-class.html)

[A widget that marks its child as being a candidate for hero animations.](https://api.flutter.dev/flutter/widgets/Hero-class.html)

[Icon](https://api.flutter.dev/flutter/widgets/Icon-class.html)

[A Material Design icon.](https://api.flutter.dev/flutter/widgets/Icon-class.html)

[Icon​Button](https://pub.dev/documentation/material_ui/latest/material_ui/IconButton-class.html)

[Clickable icons to prompt app users to take supplementary actions.](https://pub.dev/documentation/material_ui/latest/material_ui/IconButton-class.html)

[Ignore​Pointer](https://api.flutter.dev/flutter/widgets/IgnorePointer-class.html)

[A widget that is invisible during hit testing. When ignoring is true, this widget (and its subtree) is invisible to hit testing. It still consumes...](https://api.flutter.dev/flutter/widgets/IgnorePointer-class.html)

[Image](https://api.flutter.dev/flutter/widgets/Image-class.html)

[A widget that displays an image.](https://api.flutter.dev/flutter/widgets/Image-class.html)

[Implicitly​Animated​Widget](https://api.flutter.dev/flutter/widgets/ImplicitlyAnimatedWidget-class.html)

[An abstract class for building widgets that animate changes to their properties.](https://api.flutter.dev/flutter/widgets/ImplicitlyAnimatedWidget-class.html)

[Indexed​Stack](https://api.flutter.dev/flutter/widgets/IndexedStack-class.html)

[A Stack that shows a single child from a list of children.](https://api.flutter.dev/flutter/widgets/IndexedStack-class.html)

[Interactive​Viewer](https://api.flutter.dev/flutter/widgets/InteractiveViewer-class.html)

[A widget that enables pan and zoom interactions with its child.](https://api.flutter.dev/flutter/widgets/InteractiveViewer-class.html)

[Intrinsic​Height](https://api.flutter.dev/flutter/widgets/IntrinsicHeight-class.html)

[A widget that sizes its child to the child's intrinsic height.](https://api.flutter.dev/flutter/widgets/IntrinsicHeight-class.html)

[Intrinsic​Width](https://api.flutter.dev/flutter/widgets/IntrinsicWidth-class.html)

[A widget that sizes its child to the child's intrinsic width.](https://api.flutter.dev/flutter/widgets/IntrinsicWidth-class.html)

[Keyboard​Listener](https://api.flutter.dev/flutter/widgets/KeyboardListener-class.html)

[A widget that calls a callback whenever the user presses or releases a key on a keyboard.](https://api.flutter.dev/flutter/widgets/KeyboardListener-class.html)

[Layout​Builder](https://api.flutter.dev/flutter/widgets/LayoutBuilder-class.html)

[Builds a widget tree that can depend on the parent widget's size.](https://api.flutter.dev/flutter/widgets/LayoutBuilder-class.html)

[Limited​Box](https://api.flutter.dev/flutter/widgets/LimitedBox-class.html)

[A box that limits its size only when it's unconstrained.](https://api.flutter.dev/flutter/widgets/LimitedBox-class.html)

[Linear​Progress​Indicator](https://pub.dev/documentation/material_ui/latest/material_ui/LinearProgressIndicator-class.html)

[Vertical line that changes color as an ongoing process, such as loading an app or submitting a form, completes.](https://pub.dev/documentation/material_ui/latest/material_ui/LinearProgressIndicator-class.html)

[List​Body](https://api.flutter.dev/flutter/widgets/ListBody-class.html)

[A widget that arranges its children sequentially along a given axis, forcing them to the dimension of the parent in the other axis.](https://api.flutter.dev/flutter/widgets/ListBody-class.html)

[List​Tile](https://pub.dev/documentation/material_ui/latest/material_ui/ListTile-class.html)

[A single fixed-height row that typically contains some text as well as a leading or trailing icon.](https://pub.dev/documentation/material_ui/latest/material_ui/ListTile-class.html)

[List​View](https://api.flutter.dev/flutter/widgets/ListView-class.html)

[A scrollable, linear list of widgets. ListView is the most commonly used scrolling widget. It displays its children one after another in the scroll direction....](https://api.flutter.dev/flutter/widgets/ListView-class.html)

[Long​Press​Draggable](https://api.flutter.dev/flutter/widgets/LongPressDraggable-class.html)

[Makes its child draggable starting from long press.](https://api.flutter.dev/flutter/widgets/LongPressDraggable-class.html)

[Material​App](https://pub.dev/documentation/material_ui/latest/material_ui/MaterialApp-class.html)

[A convenience widget that wraps a number of widgets that are commonly required for applications implementing Material Design.](https://pub.dev/documentation/material_ui/latest/material_ui/MaterialApp-class.html)

[Matrix​Transition](https://api.flutter.dev/flutter/widgets/MatrixTransition-class.html)

[Animates the Matrix4 of a transformed widget.](https://api.flutter.dev/flutter/widgets/MatrixTransition-class.html)

[Media​Query](https://api.flutter.dev/flutter/widgets/MediaQuery-class.html)

[Establishes a subtree in which media queries resolve to the given data.](https://api.flutter.dev/flutter/widgets/MediaQuery-class.html)

[Menu](https://pub.dev/documentation/material_ui/latest/material_ui/MenuAnchor-class.html)

[Container that displays a list of choices on a temporary surface.](https://pub.dev/documentation/material_ui/latest/material_ui/MenuAnchor-class.html)

[Merge​Semantics](https://api.flutter.dev/flutter/widgets/MergeSemantics-class.html)

[A widget that merges the semantics of its descendants.](https://api.flutter.dev/flutter/widgets/MergeSemantics-class.html)

[Navigation rail](https://pub.dev/documentation/material_ui/latest/material_ui/NavigationRail-class.html)

[Persistent container on the leading edge of tablet and desktop screens to navigate to parts of an app.](https://pub.dev/documentation/material_ui/latest/material_ui/NavigationRail-class.html)

[Navigation​Bar](https://pub.dev/documentation/material_ui/latest/material_ui/NavigationBar-class.html)

[Persistent container that enables switching between primary destinations in an app.](https://pub.dev/documentation/material_ui/latest/material_ui/NavigationBar-class.html)

[Navigation​Drawer](https://pub.dev/documentation/material_ui/latest/material_ui/NavigationDrawer-class.html)

[Container that slides from the leading edge of the app to navigate to other sections in an app.](https://pub.dev/documentation/material_ui/latest/material_ui/NavigationDrawer-class.html)

[Navigator](https://api.flutter.dev/flutter/widgets/Navigator-class.html)

[A widget that manages a set of child widgets with a stack discipline. Many apps have a navigator near the top of their widget hierarchy...](https://api.flutter.dev/flutter/widgets/Navigator-class.html)

[Nested​Scroll​View](https://api.flutter.dev/flutter/widgets/NestedScrollView-class.html)

[A scrolling view inside of which can be nested other scrolling views, with their scroll positions being intrinsically linked.](https://api.flutter.dev/flutter/widgets/NestedScrollView-class.html)

[Notification​Listener](https://api.flutter.dev/flutter/widgets/NotificationListener-class.html)

[A widget that listens for Notifications bubbling up the tree.](https://api.flutter.dev/flutter/widgets/NotificationListener-class.html)

[Offstage](https://api.flutter.dev/flutter/widgets/Offstage-class.html)

[A widget that lays the child out as if it was in the tree, but without painting anything, without making the child available for hit...](https://api.flutter.dev/flutter/widgets/Offstage-class.html)

[Opacity](https://api.flutter.dev/flutter/widgets/Opacity-class.html)

[A widget that makes its child partially transparent.](https://api.flutter.dev/flutter/widgets/Opacity-class.html)

[Outlined​Button](https://pub.dev/documentation/material_ui/latest/material_ui/OutlinedButton-class.html)

[A Material Design outlined button, essentially a TextButton with an outlined border.](https://pub.dev/documentation/material_ui/latest/material_ui/OutlinedButton-class.html)

[Overflow​Bar](https://api.flutter.dev/flutter/widgets/OverflowBar-class.html)

[A widget that lays out its children in a row unless they "overflow" the available horizontal space, in which case it lays them out in...](https://api.flutter.dev/flutter/widgets/OverflowBar-class.html)

[Overflow​Box](https://api.flutter.dev/flutter/widgets/OverflowBox-class.html)

[A widget that imposes different constraints on its child than it gets from its parent, possibly allowing the child to overflow the parent.](https://api.flutter.dev/flutter/widgets/OverflowBox-class.html)

[Padding](https://api.flutter.dev/flutter/widgets/Padding-class.html)

[A widget that insets its child by the given padding.](https://api.flutter.dev/flutter/widgets/Padding-class.html)

[Page​View](https://api.flutter.dev/flutter/widgets/PageView-class.html)

[A scrollable list that works page by page.](https://api.flutter.dev/flutter/widgets/PageView-class.html)

[Placeholder](https://api.flutter.dev/flutter/widgets/Placeholder-class.html)

[A widget that draws a box that represents where other widgets will one day be added.](https://api.flutter.dev/flutter/widgets/Placeholder-class.html)

[Popup​Menu​Button](https://pub.dev/documentation/material_ui/latest/material_ui/PopupMenuButton-class.html)

[Displays a menu when pressed and calls onSelected when the menu is dismissed because an item was selected.](https://pub.dev/documentation/material_ui/latest/material_ui/PopupMenuButton-class.html)

[Positioned​Transition](https://api.flutter.dev/flutter/widgets/PositionedTransition-class.html)

[Animated version of Positioned which takes a specific Animation to transition the child's position from a start position to and end position over the lifetime...](https://api.flutter.dev/flutter/widgets/PositionedTransition-class.html)

[Radio](https://pub.dev/documentation/material_ui/latest/material_ui/Radio-class.html)

[Form control that app users can set or clear to select only one option from a set.](https://pub.dev/documentation/material_ui/latest/material_ui/Radio-class.html)

[Raw​Image](https://api.flutter.dev/flutter/widgets/RawImage-class.html)

[A widget that displays a dart:ui.Image directly.](https://api.flutter.dev/flutter/widgets/RawImage-class.html)

[Refresh​Indicator](https://pub.dev/documentation/material_ui/latest/material_ui/RefreshIndicator-class.html)

[A Material Design pull-to-refresh wrapper for scrollables.](https://pub.dev/documentation/material_ui/latest/material_ui/RefreshIndicator-class.html)

[Relative​Positioned​Transition](https://api.flutter.dev/flutter/widgets/RelativePositionedTransition-class.html)

[Animated version of Positioned which transitions the child's position based on the value of rect relative to a bounding box with the specified size.](https://api.flutter.dev/flutter/widgets/RelativePositionedTransition-class.html)

[Reorderable​List​View](https://pub.dev/documentation/material_ui/latest/material_ui/ReorderableListView-class.html)

[A list whose items the user can interactively reorder by dragging.](https://pub.dev/documentation/material_ui/latest/material_ui/ReorderableListView-class.html)

[Repeating​Animation​Builder](https://api.flutter.dev/flutter/widgets/RepeatingAnimationBuilder-class.html)

[A widget that animates an Animatable value and repeats indefinitely.](https://api.flutter.dev/flutter/widgets/RepeatingAnimationBuilder-class.html)

[Rich​Text](https://api.flutter.dev/flutter/widgets/RichText-class.html)

[The RichText widget displays text that uses multiple different styles. The text to display is described using a tree of TextSpan objects, each of which...](https://api.flutter.dev/flutter/widgets/RichText-class.html)

[Rotated​Box](https://api.flutter.dev/flutter/widgets/RotatedBox-class.html)

[A widget that rotates its child by a integral number of quarter turns.](https://api.flutter.dev/flutter/widgets/RotatedBox-class.html)

[Rotation​Transition](https://api.flutter.dev/flutter/widgets/RotationTransition-class.html)

[Animates the rotation of a widget.](https://api.flutter.dev/flutter/widgets/RotationTransition-class.html)

[Row](https://api.flutter.dev/flutter/widgets/Row-class.html)

[Layout a list of child widgets in the horizontal direction.](https://api.flutter.dev/flutter/widgets/Row-class.html)

[Scaffold](https://pub.dev/documentation/material_ui/latest/material_ui/Scaffold-class.html)

[Implements the basic Material Design visual layout structure. This class provides APIs for showing drawers, snack bars, and bottom sheets.](https://pub.dev/documentation/material_ui/latest/material_ui/Scaffold-class.html)

[Scale​Transition](https://api.flutter.dev/flutter/widgets/ScaleTransition-class.html)

[Animates the scale of transformed widget.](https://api.flutter.dev/flutter/widgets/ScaleTransition-class.html)

[Scroll​Configuration](https://api.flutter.dev/flutter/widgets/ScrollConfiguration-class.html)

[Controls how Scrollable widgets behave in a subtree.](https://api.flutter.dev/flutter/widgets/ScrollConfiguration-class.html)

[Scrollable](https://api.flutter.dev/flutter/widgets/Scrollable-class.html)

[Scrollable implements the interaction model for a scrollable widget, including gesture recognition, but does not have an opinion about how the viewport, which actually displays...](https://api.flutter.dev/flutter/widgets/Scrollable-class.html)

[Scrollbar](https://pub.dev/documentation/material_ui/latest/material_ui/Scrollbar-class.html)

[A Material Design scrollbar. A scrollbar indicates which portion of a Scrollable widget is actually visible.](https://pub.dev/documentation/material_ui/latest/material_ui/Scrollbar-class.html)

[Segmented​Button](https://pub.dev/documentation/material_ui/latest/material_ui/SegmentedButton-class.html)

[Single or multiple selected clickable blocks to help people select options, switch views, or sort elements.](https://pub.dev/documentation/material_ui/latest/material_ui/SegmentedButton-class.html)

[Semantics](https://api.flutter.dev/flutter/widgets/Semantics-class.html)

[A widget that annotates the widget tree with a description of the meaning of the widgets. Used by accessibility tools, search engines, and other semantic...](https://api.flutter.dev/flutter/widgets/Semantics-class.html)

[Simple​Dialog](https://pub.dev/documentation/material_ui/latest/material_ui/SimpleDialog-class.html)

[Simple dialogs can provide additional details or actions about a list item. For example they can display avatars icons clarifying subtext or orthogonal actions (such...](https://pub.dev/documentation/material_ui/latest/material_ui/SimpleDialog-class.html)

[Single​Child​Scroll​View](https://api.flutter.dev/flutter/widgets/SingleChildScrollView-class.html)

[A box in which a single widget can be scrolled. This widget is useful when you have a single box that will normally be entirely...](https://api.flutter.dev/flutter/widgets/SingleChildScrollView-class.html)

[Size​Transition](https://api.flutter.dev/flutter/widgets/SizeTransition-class.html)

[Animates its own size and clips and aligns the child.](https://api.flutter.dev/flutter/widgets/SizeTransition-class.html)

[Sized​Box](https://api.flutter.dev/flutter/widgets/SizedBox-class.html)

[A box with a specified size. If given a child, this widget forces its child to have a specific width and/or height (assuming values are...](https://api.flutter.dev/flutter/widgets/SizedBox-class.html)

[Sized​Overflow​Box](https://api.flutter.dev/flutter/widgets/SizedOverflowBox-class.html)

[A widget that is a specific size but passes its original constraints through to its child, which will probably overflow.](https://api.flutter.dev/flutter/widgets/SizedOverflowBox-class.html)

[Slide​Transition](https://api.flutter.dev/flutter/widgets/SlideTransition-class.html)

[Animates the position of a widget relative to its normal position.](https://api.flutter.dev/flutter/widgets/SlideTransition-class.html)

[Slider](https://pub.dev/documentation/material_ui/latest/material_ui/Slider-class.html)

[Form control that enables selecting a range of values.](https://pub.dev/documentation/material_ui/latest/material_ui/Slider-class.html)

[Sliver​App​Bar](https://pub.dev/documentation/material_ui/latest/material_ui/SliverAppBar-class.html)

[A material design app bar that integrates with a CustomScrollView.](https://pub.dev/documentation/material_ui/latest/material_ui/SliverAppBar-class.html)

[Sliver​Child​Builder​Delegate](https://api.flutter.dev/flutter/widgets/SliverChildBuilderDelegate-class.html)

[A delegate that supplies children for slivers using a builder callback.](https://api.flutter.dev/flutter/widgets/SliverChildBuilderDelegate-class.html)

[Sliver​Child​List​Delegate](https://api.flutter.dev/flutter/widgets/SliverChildListDelegate-class.html)

[A delegate that supplies children for slivers using an explicit list.](https://api.flutter.dev/flutter/widgets/SliverChildListDelegate-class.html)

[Sliver​Fade​Transition](https://api.flutter.dev/flutter/widgets/SliverFadeTransition-class.html)

[Animates the opacity of a sliver widget.](https://api.flutter.dev/flutter/widgets/SliverFadeTransition-class.html)

[Sliver​Fill​Remaining](https://api.flutter.dev/flutter/widgets/SliverFillRemaining-class.html)

[A sliver that contains a single box child that fills the remaining space in the viewport.](https://api.flutter.dev/flutter/widgets/SliverFillRemaining-class.html)

[Sliver​Fixed​Extent​List](https://api.flutter.dev/flutter/widgets/SliverFixedExtentList-class.html)

[A sliver that places multiple box children with the same main axis extent in a linear array.](https://api.flutter.dev/flutter/widgets/SliverFixedExtentList-class.html)

[Sliver​Grid](https://api.flutter.dev/flutter/widgets/SliverGrid-class.html)

[A sliver that places multiple box children in a two dimensional arrangement.](https://api.flutter.dev/flutter/widgets/SliverGrid-class.html)

[Sliver​List](https://api.flutter.dev/flutter/widgets/SliverList-class.html)

[A sliver that places multiple box children in a linear array along the main axis.](https://api.flutter.dev/flutter/widgets/SliverList-class.html)

[Sliver​Padding](https://api.flutter.dev/flutter/widgets/SliverPadding-class.html)

[A sliver that applies padding on each side of another sliver.](https://api.flutter.dev/flutter/widgets/SliverPadding-class.html)

[Sliver​Persistent​Header](https://api.flutter.dev/flutter/widgets/SliverPersistentHeader-class.html)

[A sliver whose size varies when the sliver is scrolled to the edge of the viewport opposite the sliver's GrowthDirection.](https://api.flutter.dev/flutter/widgets/SliverPersistentHeader-class.html)

[Sliver​To​Box​Adapter](https://api.flutter.dev/flutter/widgets/SliverToBoxAdapter-class.html)

[A sliver that contains a single box widget.](https://api.flutter.dev/flutter/widgets/SliverToBoxAdapter-class.html)

[Snack​Bar](https://pub.dev/documentation/material_ui/latest/material_ui/SnackBar-class.html)

[Brief messages about app processes that display at the bottom of the screen.](https://pub.dev/documentation/material_ui/latest/material_ui/SnackBar-class.html)

[Stack](https://api.flutter.dev/flutter/widgets/Stack-class.html)

[This class is useful if you want to overlap several children in a simple way, for example having some text and an image, overlaid with...](https://api.flutter.dev/flutter/widgets/Stack-class.html)

[Stepper](https://pub.dev/documentation/material_ui/latest/material_ui/Stepper-class.html)

[A Material Design stepper widget that displays progress through a sequence of steps.](https://pub.dev/documentation/material_ui/latest/material_ui/Stepper-class.html)

[Stream​Builder](https://api.flutter.dev/flutter/widgets/StreamBuilder-class.html)

[Widget that builds itself based on the latest snapshot of interaction with a Stream.](https://api.flutter.dev/flutter/widgets/StreamBuilder-class.html)

[Switch](https://pub.dev/documentation/material_ui/latest/material_ui/Switch-class.html)

[Toggle control that changes the state of a single item to on or off.](https://pub.dev/documentation/material_ui/latest/material_ui/Switch-class.html)

[Table](https://api.flutter.dev/flutter/widgets/Table-class.html)

[Displays child widgets in rows and columns.](https://api.flutter.dev/flutter/widgets/Table-class.html)

[Text](https://api.flutter.dev/flutter/widgets/Text-class.html)

[A run of text with a single style.](https://api.flutter.dev/flutter/widgets/Text-class.html)

[Text​Button](https://pub.dev/documentation/material_ui/latest/material_ui/TextButton-class.html)

[A Material Design text button. A simple flat button without a border outline.](https://pub.dev/documentation/material_ui/latest/material_ui/TextButton-class.html)

[Text​Field](https://pub.dev/documentation/material_ui/latest/material_ui/TextField-class.html)

[Box into which app users can enter text. They appear in forms and dialogs.](https://pub.dev/documentation/material_ui/latest/material_ui/TextField-class.html)

[Theme](https://pub.dev/documentation/material_ui/latest/material_ui/Theme-class.html)

[Applies a theme to descendant widgets. A theme describes the colors and typographic choices of an application.](https://pub.dev/documentation/material_ui/latest/material_ui/Theme-class.html)

[Time​Picker](https://pub.dev/documentation/material_ui/latest/material_ui/showTimePicker.html)

[Clock interface used to select and set a specific time.](https://pub.dev/documentation/material_ui/latest/material_ui/showTimePicker.html)

[Tooltip](https://pub.dev/documentation/material_ui/latest/material_ui/Tooltip-class.html)

[Tooltips provide text labels that help explain the function of a button or other user interface action. Wrap the button in a Tooltip widget to...](https://pub.dev/documentation/material_ui/latest/material_ui/Tooltip-class.html)

[Transform](https://api.flutter.dev/flutter/widgets/Transform-class.html)

[A widget that applies a transformation before painting its child.](https://api.flutter.dev/flutter/widgets/Transform-class.html)

[Widgets​App](https://api.flutter.dev/flutter/widgets/WidgetsApp-class.html)

[A convenience class that wraps a number of widgets that are commonly required for an application.](https://api.flutter.dev/flutter/widgets/WidgetsApp-class.html)

[Wrap](https://api.flutter.dev/flutter/widgets/Wrap-class.html)

[A widget that displays its children in multiple horizontal or vertical runs.](https://api.flutter.dev/flutter/widgets/Wrap-class.html)

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/reference/widgets.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/reference/widgets\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/reference/widgets.md "Report an issue with this page").

----
url: https://docs.flutter.dev/ui/navigation/deep-linking.md
----

\# Deep linking > Navigate to routes when the app receives a new URL. Deep links are links that not only open an app, but also take the user to a specific location "deep" inside the app. For example, a deep link from an advertisement for a pair of sneakers might open a shopping app and display the product page for those particular shoes. Flutter supports deep linking on iOS, Android, and the web. Opening a URL displays that screen in your app. With the following steps, you can launch and display routes by using named routes (either with the \[\`routes\`]\[routes] parameter or \[\`onGenerateRoute\`]\[onGenerateRoute]), or by using the \[\`Router\`]\[Router] widget. :::note Named routes are no longer recommended for most applications. For more information, see \[Limitations]\[] in the \[navigation overview]\[] page. ::: \[Limitations]: /ui/navigation#limitations \[navigation overview]: /ui/navigation If you're running the app in a web browser, there's no additional setup required. Route paths are handled in the same way as an iOS or Android deep link. By default, web apps read the deep link path from the url fragment using the pattern: \`/#/path/to/app/screen\`, but this can be changed by \[configuring the URL strategy]\[] for your app. If you are a visual learner, check out the following video: ## Get started To get started, see our cookbooks for Android and iOS:

[Android](/cookbook/navigation/set-up-app-links)

[iOS](/cookbook/navigation/set-up-universal-links)

\## Migrating from plugin-based deep linking If you have written a plugin to handle deep links, as described in \[Deep Links and Flutter applications]\[plugin-linking] (a free article on Medium), you should opt out the Flutter's default deep link handler. To do this, set \`FlutterDeepLinkingEnabled\` to false in \`Info.plist\` \_or\_ \`flutter\_deeplinking\_enabled\` to false in \`AndroidManifest.xml\`. ## Behavior The behavior varies slightly based on the platform and whether the app is launched and running. | Platform / Scenario | Using Navigator | Using Router | |--------------------------|---------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | iOS (not launched) | App gets initialRoute ("/") and a short time after gets a pushRoute | App gets initialRoute ("/") and a short time after uses the RouteInformationParser to parse the route and call RouterDelegate.setNewRoutePath, which configures the Navigator with the corresponding Page. | | Android - (not launched) | App gets initialRoute containing the route ("/deeplink") | App gets initialRoute ("/deeplink") and passes it to the RouteInformationParser to parse the route and call RouterDelegate.setNewRoutePath, which configures the Navigator with the corresponding Pages. | | iOS (launched) | pushRoute is called | Path is parsed, and the Navigator is configured with a new set of Pages. | | Android (launched) | pushRoute is called | Path is parsed, and the Navigator is configured with a new set of Pages. | {:.table .table-striped} When using the \[\`Router\`]\[Router] widget, your app has the ability to replace the current set of pages when a new deep link is opened while the app is running. ## To learn more \* \[Learning Flutter's new navigation and routing system]\[] provides an introduction to the Router system. \* \[Deep dive into Flutter deep linking]\[io-dl] video from Google I/O 2023 \* \[Flutter Deep Linking: The Ultimate Guide]\[], a step-by-step tutorial showing how to implement deep links in Flutter. \[io-dl]: https\://www\.youtube.com/watch?v=6RxuDcs6jVw\&t=3s \[Learning Flutter's new navigation and routing system]: https\://blog.flutter.dev/learning-flutters-new-navigation-and-routing-system-7c9068155ade \[routes]: https\://pub.dev/documentation/material\_ui/latest/material\_ui/MaterialApp/routes.html \[onGenerateRoute]: https\://pub.dev/documentation/material\_ui/latest/material\_ui/MaterialApp/onGenerateRoute.html \[Router]: https\://api.flutter.dev/flutter/widgets/Router-class.html \[plugin-linking]: https\://medium.com/flutter-community/deep-links-and-flutter-applications-how-to-handle-them-properly-8c9865af9283 \[Flutter Deep Linking: The Ultimate Guide]: https\://codewithandrea.com/articles/flutter-deep-links/ \[configuring the URL strategy]: /ui/navigation/url-strategies

----
url: https://docs.flutter.dev/learn/pathway/tutorial/stateful-widget.md
----

\# Stateful widgets > Learn about StatefulWidgets and rebuilding Flutter UI. Learn when widgets need to be stateful and how to trigger UI updates with setState. title: What you'll accomplish items: - title: Learn when widgets need to be stateful icon: change\_circle - title: Convert a StatelessWidget to a StatefulWidget icon: swap\_horiz - title: Trigger UI updates with setState icon: refresh --- ### Introduction So far, your app displays a grid and an input field, but the grid doesn't yet update to reflect the user's guesses. When this app is complete, each tile in the next unfilled row should update after each submitted user guess by: - Displaying the correct letter. - Changing color to reflect whether the letter is correct (green), is in the word but at an incorrect position (yellow), or doesn't appear in the word at all (grey). To handle this dynamic behavior, you need to convert \`GamePage\` from a \`StatelessWidget\` to a \[\`StatefulWidget\`]\[]. \[\`StatefulWidget\`]: https\://api.flutter.dev/flutter/widgets/StatefulWidget-class.html ### Why stateful widgets? When a widget's appearance or data needs to change during its lifetime, you need a \`StatefulWidget\` and a companion \`State\` object. While the \`StatefulWidget\` itself is still immutable (its properties can't change after creation), the \`State\` object is long-lived, can hold mutable data, and can be rebuilt when that data changes, causing the UI to update. For example, the following widget tree imagines a simple app that uses a stateful widget with a counter that increases when the button is pressed. Here is the basic \`StatefulWidget\` structure (doesn't do anything yet): \`\`\`dart class ExampleWidget extends StatefulWidget { ExampleWidget({super.key}); @override State createState() => \_ExampleWidgetState(); } class \_ExampleWidgetState extends State { @override Widget build(BuildContext context) { return Container(); } } \`\`\` ### Convert \`GamePage\` to a stateful widget To convert the \`GamePage\` (or any other) widget from a stateless widget to a stateful widget, do the following steps: 1. Change \`GamePage\` to extend \`StatefulWidget\` instead of \`StatelessWidget\`. 1. Create a new class named \`\_GamePageState\`, that extends \`State\`. This new class will hold the mutable state and the \`build\` method. Move the \`build\` method and all properties \*instantiated on the widget\* from \`GamePage\` to the state object. 1. Implement the \`createState()\` method in \`GamePage\`, which returns an instance of \`\_GamePageState\`. :::tip Quick assists You don't have to manually do this work, as the Flutter plugins for VS Code and IntelliJ provide \["quick assists"]\[] that can do this conversion for you. ::: Your modified code should look like this: \`\`\`dart class GamePage extends StatefulWidget { GamePage({super.key}); @override State createState() => \_GamePageState(); } class \_GamePageState extends State { final Game \_game = Game(); @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.all(8.0), child: Column( children: \[ for (var guess in \_game.guesses) Row( mainAxisAlignment: MainAxisAlignment.center, children: \[ for (var letter in guess) Padding( padding: const EdgeInsets.symmetric(horizontal: 2.5, vertical: 2.5), child: Tile(letter.char, letter.type), ) ], ), GuessInput( onSubmitGuess: (\_) { // TODO, handle guess print(guess); // Temporary }, ), ], ), ); } } \`\`\` \["quick assists"]: /tools/android-studio#assists-quick-fixes ### Updating the UI with \`setState\` Whenever you mutate a \`State\` object, you must call \[\`setState\`]\[] to signal the framework to update the user interface and call the \`build\` method again. In this app, when a user makes a guess, the word they guessed is saved on the \`Game\` object, which is a property on the \`GamePage\` class, and therefore is state that might change and require the UI to update. When this state is mutated, the grid should be re-drawn to show the user's guess. To implement this, update the callback function passed to \`GuessInput\`. The function needs to call \`setState\` and, within \`setState\`, it needs to execute the logic to determine whether the users guess was correct. :::note The game logic is abstracted away into the \`Game\` object, and outside the scope of this tutorial. ::: Update your code: \`\`\`dart class GamePage extends StatefulWidget { GamePage({super.key}); @override State createState() => \_GamePageState(); } class \_GamePageState extends State { final Game \_game = Game(); @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.all(8.0), child: Column( children: \[ for (var guess in \_game.guesses) Row( mainAxisAlignment: MainAxisAlignment.center, children: \[ for (var letter in guess) Padding( padding: const EdgeInsets.symmetric(horizontal: 2.5, vertical: 2.5), child: Tile(letter.char, letter.type), ) ], ), GuessInput( onSubmitGuess: (String guess) { setState(() { // NEW \_game.guess(guess); }); }, ), ], ), ); } } \`\`\` Now, when you type a legal guess into the \`TextInput\` and submit it, the application will reflect the user's guess. If you were to call \`\_game.guess(guess)\` \*without\* a calling \`setState\`, the internal game data would change, but Flutter wouldn't know it needs to repaint the screen, and the user wouldn't see any updates. \[\`setState\`]: https\://api.flutter.dev/flutter/widgets/State/setState.html ### Convert \`GuessInput\` to a stateful widget When \`GamePage\` rebuilds after calling \`setState\`, all of its child widgets are rebuilt as well. Because \`GuessInput\` was originally created as a \`StatelessWidget\`, its \`TextEditingController\` and \`FocusNode\` were stored directly in the widget class. Since a new widget instance is created on every rebuild, these controllers were recreated each time, causing the text input field to lose focus and leaving unused controllers without proper disposal. To keep focus on the text field between guesses and manage controller lifecycles properly, convert \`GuessInput\` into a \`StatefulWidget\`: 1. Change \`GuessInput\` to extend \`StatefulWidget\` instead of \`StatelessWidget\`. 1. Create a companion \`\_GuessInputState\` class extending \`State\`. 1. Move \`\_textEditingController\`, \`\_focusNode\`, \`\_onSubmit()\`, and \`build()\` into \`\_GuessInputState\`. 1. Implement \`dispose()\` to clean up \`\_textEditingController\` and \`\_focusNode\`. Your modified \`GuessInput\` widget should look like this:

<!--?code-excerpt "fwe/birdle/lib/step5_main.dart (GuessInput)"?-->

\`\`\`dart class GuessInput extends StatefulWidget { const GuessInput({super.key, required this.onSubmitGuess}); final void Function(String) onSubmitGuess; @override State createState() => \_GuessInputState(); } class \_GuessInputState extends State { final TextEditingController \_textEditingController = TextEditingController(); final FocusNode \_focusNode = FocusNode(); @override void dispose() { \_textEditingController.dispose(); \_focusNode.dispose(); super.dispose(); } void \_onSubmit() { widget.onSubmitGuess(\_textEditingController.text.trim()); \_textEditingController.clear(); \_focusNode.requestFocus(); } @override Widget build(BuildContext context) { return Row( children: \[ Expanded( child: Padding( padding: const EdgeInsets.all(8.0), child: TextField( maxLength: 5, focusNode: \_focusNode, autofocus: true, decoration: const InputDecoration( border: OutlineInputBorder( borderRadius: BorderRadius.all(Radius.circular(35)), ), ), controller: \_textEditingController, onSubmitted: (input) { \_onSubmit(); }, ), ), ), IconButton( padding: EdgeInsets.zero, icon: const Icon(Icons.arrow\_circle\_up), onPressed: \_onSubmit, ), ], ); } } \`\`\` By converting \`GuessInput\` to a \`StatefulWidget\`, \`\_GuessInputState\` persists across parent rebuilds, keeping focus on the text field after each guess is submitted and properly disposing of resources when the widget is unmounted. ### Review title: What you accomplished subtitle: Here's a summary of what you built and learned in this lesson. completed: true items: - title: Learned when widgets need to be stateful icon: change\_circle details: >- When a widget's appearance or data needs to change during its lifetime, you need a \`StatefulWidget\`. The widget itself stays immutable, but its companion \`State\` object holds mutable data and triggers rebuilds. - title: Converted GamePage and GuessInput to StatefulWidgets icon: swap\_horiz details: >- You refactored \`GamePage\` and \`GuessInput\` to be stateful by creating companion \`State\` classes, moving mutable properties and lifecycle management (like \`dispose\`) to them, and implementing \`createState()\`. Your IDE's support for quick assists can automate this conversion. - title: Made your app respond to user input with setState icon: refresh details: >- Calling \`setState\` tells Flutter to rebuild the UI of a widget. When a user submits a guess, you call \`setState\` to update the game state, and the grid automatically reflects the new data while maintaining text field focus. Your app is now truly interactive! ### Test yourself - question: When should you use a StatefulWidget instead of a StatelessWidget? options: - text: When the widget needs to make HTTP requests. correct: false explanation: HTTP requests can be made from either, but state changes require StatefulWidget. - text: When the widget's appearance or data needs to change during its lifetime. correct: true explanation: StatefulWidget is needed when the UI must update in response to data changes over time. - text: When the widget has more than three child widgets. correct: false explanation: The number of children doesn't determine whether a widget is stateful. - text: When the widget is at the root of the widget tree. correct: false explanation: Root widgets can be stateless; statefulness depends on whether data changes during the widget's lifetime. - question: What happens if you change data in a State object without calling setState? options: - text: The app will crash with an error. correct: false explanation: The app won't crash, but the UI won't update. - text: The data changes internally, but Flutter won't rebuild the UI to reflect the change. correct: true explanation: Without calling setState, Flutter doesn't know it needs to repaint, so the user won't see updates. - text: Flutter automatically detects the change and rebuilds the UI. correct: false explanation: Flutter requires setState to know when to rebuild; it doesn't auto-detect changes. - text: The widget is removed from the widget tree. correct: false explanation: The widget remains; it just won't visually update without setState.

----
url: https://docs.flutter.dev/cookbook/persistence/reading-writing-files
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Read and write files](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

In some cases, you need to read and write files to disk. For example, you might need to persist data across app launches, or download data from the internet and save it for later offline use.

To save files to disk on mobile or desktop apps, combine the [`path_provider`](https://pub.dev/packages/path_provider) plugin with the [`dart:io`](https://api.flutter.dev/flutter/dart-io/dart-io-library.html) library.

This recipe uses the following steps:

1. Find the correct local path.
2. Create a reference to the file location.
3. Write data to the file.
4. Read data from the file.

To learn more, watch this Package of the Week video on the `path_provider` package:

[Watch on YouTube in a new tab: "path\_provider | Flutter package of the week"](https://www.youtube.com/watch/Ci4t-NkOY3I)

## 1. Find the correct local path

[#](#1-find-the-correct-local-path)

This example displays a counter. When the counter changes, write data on disk so you can read it again when the app loads. Where should you store this data?

The [`path_provider`](https://pub.dev/packages/path_provider) package provides a platform-agnostic way to access commonly used locations on the device's file system. The plugin currently supports access to two file system locations:

* *Temporary directory*

  A temporary directory (cache) that the system can clear at any time. On iOS, this corresponds to the [`NSCachesDirectory`](https://developer.apple.com/documentation/foundation/nssearchpathdirectory/nscachesdirectory). On Android, this is the value that [`getCacheDir()`](https://developer.android.com/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 the `NSDocumentDirectory`. On Android, this is the `AppData` directory.

This example stores information in the documents directory. You can find the path to the documents directory as follows:

dart

```
import 'package:path_provider/path_provider.dart';
​
  // ···
  Future<String> get _localPath async {
    final directory = await getApplicationDocumentsDirectory();
​
    return directory.path;
  }
```

## 2. Create a reference to the file location

[#](#2-create-a-reference-to-the-file-location)

Once you know where to store the file, create a reference to the file's full location. You can use the [`File`](https://api.flutter.dev/flutter/dart-io/File-class.html) class from the [`dart:io`](https://api.flutter.dev/flutter/dart-io/dart-io-library.html) library to achieve this.

dart

```
Future<File> get _localFile async {
  final path = await _localPath;
  return File('$path/counter.txt');
}
```

## 3. Write data to the file

[#](#3-write-data-to-the-file)

Now that you have a `File` to work with, use it to read and write data. First, write some data to the file. The counter is an integer, but is written to the file as a string using the `'$counter'` syntax.

dart

```
Future<File> writeCounter(int counter) async {
  final file = await _localFile;
​
  // Write the counter as a string to the file.
  return file.writeAsString('$counter');
}
```

## 4. Read data from the file

[#](#4-read-data-from-the-file)

Now that you have some data on disk, you can read it. Once again, use the `File` class.

dart

```
Future<int> readCounter() async {
  try {
    final file = await _localFile;
​
    // Read the contents of the local file.
    final contents = await file.readAsString();
​
    return int.parse(contents);
  } catch (_) {
    // If an error was encountered, return 0.
    return 0;
  }
}
```

## Complete example

[#](#complete-example)

dart

```
import 'dart:async';
import 'dart:io';
​
import 'package:material_ui/material_ui.dart';
import 'package:path_provider/path_provider.dart';
​
void main() {
  runApp(
    MaterialApp(
      title: 'Reading and writing files',
      home: FlutterDemo(storage: CounterStorage()),
    ),
  );
}
​
class CounterStorage {
  Future<String> get _localPath async {
    final directory = await getApplicationDocumentsDirectory();
​
    return directory.path;
  }
​
  Future<File> get _localFile async {
    final path = await _localPath;
    return File('$path/counter.txt');
  }
​
  Future<int> readCounter() async {
    try {
      final file = await _localFile;
​
      // Read the contents of the local file.
      final contents = await file.readAsString();
​
      return int.parse(contents);
    } catch (_) {
      // If an error was encountered, return 0.
      return 0;
    }
  }
​
  Future<File> writeCounter(int counter) async {
    final file = await _localFile;
​
    // Write the counter as a string to the file.
    return file.writeAsString('$counter');
  }
}
​
class FlutterDemo extends StatefulWidget {
  const FlutterDemo({super.key, required this.storage});
​
  final CounterStorage storage;
​
  @override
  State<FlutterDemo> createState() => _FlutterDemoState();
}
​
class _FlutterDemoState extends State<FlutterDemo> {
  int _counter = 0;
​
  @override
  void initState() {
    super.initState();
    widget.storage.readCounter().then((value) {
      setState(() {
        _counter = value;
      });
    });
  }
​
  Future<File> _incrementCounter() {
    setState(() {
      _counter++;
    });
​
    // Write the variable as a string to the file.
    return widget.storage.writeCounter(_counter);
  }
​
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Reading and writing files')),
      body: Center(
        child: Text('Button tapped $_counter time${_counter == 1 ? '' : 's'}.'),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ),
    );
  }
}
```

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-09-29. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/persistence/reading-writing-files.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/cookbook/persistence/reading-writing-files\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/persistence/reading-writing-files.md "Report an issue with this page").

----
url: https://docs.flutter.dev/ui/adaptive-responsive/more-info.md
----

\# Additional resources > Other resources that you might find useful when writing adaptive apps. ## Examples If you'd like to see how the adaptive and responsive concepts (as described in these pages) come together, check out the source code for the following apps: \* \[Wonderous]\[] \* \[Flutter adaptive demo]\[] \[Flutter adaptive demo]: https\://github.com/gskinnerTeam/flutter-adaptive-demo \[Wonderous]: https\://github.com/gskinnerTeam/flutter-wonderous-app ## Learn more about basic usability principles Of course, these pages don't constitute an exhaustive list of the things you might consider. The more operating systems, form factors, and input devices you support, the more difficult it becomes to spec out every permutation in design. Taking time to learn basic usability principles as a developer empowers you to make better decisions, reduces back-and-forth iterations with design during production, and results in improved productivity with better outcomes. Here are some resources that you might find useful: \* \[Android large screen guidelines]\[] \* \[Material guidelines on applying layout]\[] \* \[Material design for large screens]\[] \* \[Material guidelines on canonical layouts]\[] \* \[Build high quality apps (Android)]\[] \* \[UI design do's and don'ts (Apple)]\[] \* \[Human interface guidelines (Apple)]\[] \* \[Responsive design techniques (Microsoft)]\[] \* \[Machine sizes and breakpoints (Microsoft)]\[] \* \[How to build Adaptive UI with Flutter]\[], a Google I/O 2024 video. \[Android large screen guidelines]: https\://developer.android.com/docs/quality-guidelines/large-screen-app-quality \[Build high quality apps (Android)]: https\://developer.android.com/quality \[How to build Adaptive UI with Flutter]: https\://www\.youtube.com/watch?v=LeKLGzpsz9I \[Material guidelines on applying layout]: https\://m3.material.io/foundations/layout/applying-layout/window-size-classes \[Material guidelines on canonical layouts]: https\://m3.material.io/foundations/layout/canonical-layouts/overview \[Human interface guidelines (Apple)]: https\://developer.apple.com/design/human-interface-guidelines/ \[Material design for large screens]: https\://m2.material.io/blog/material-design-for-large-screens \[Machine sizes and breakpoints (Microsoft)]: https\://learn.microsoft.com/windows/apps/design/layout/screen-sizes-and-breakpoints-for-responsive-design \[Responsive design techniques (Microsoft)]: https\://learn.microsoft.com/windows/apps/design/layout/responsive-design \[UI design do's and don'ts (Apple)]: https\://developer.apple.com/design/tips/

----
url: https://docs.flutter.dev/cookbook/testing/widget/tap-drag
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Tap, drag, and enter text](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

Many widgets not only display information, but also respond to user interaction. This includes buttons that can be tapped, and [`TextField`](https://pub.dev/documentation/material_ui/latest/material_ui/TextField-class.html) for entering text.

To test these interactions, you need a way to simulate them in the test environment. For this purpose, use the [`WidgetTester`](https://api.flutter.dev/flutter/flutter_test/WidgetTester-class.html) library.

The `WidgetTester` provides methods for entering text, tapping, and dragging.

* [`enterText()`](https://api.flutter.dev/flutter/flutter_test/WidgetTester/enterText.html)
* [`tap()`](https://api.flutter.dev/flutter/flutter_test/WidgetController/tap.html)
* [`drag()`](https://api.flutter.dev/flutter/flutter_test/WidgetController/drag.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()`](https://api.flutter.dev/flutter/flutter_test/WidgetTester/pump.html) or [`pumpAndSettle()`](https://api.flutter.dev/flutter/flutter_test/WidgetTester/pumpAndSettle.html) methods provided by the `WidgetTester`. This recipe uses the following steps:

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, create a basic todo app that tests three features:

1. Entering text into a `TextField`.
2. Tapping a `FloatingActionButton` to add the text to a list of todos.
3. Swiping-to-dismiss to remove the item from the list.

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](/cookbook/forms/text-input)
* [Handle taps](/cookbook/gestures/handling-taps)
* [Create a basic list](/cookbook/lists/basic-list)
* [Implement swipe to dismiss](/cookbook/gestures/dismissible)

dart

```
class TodoList extends StatefulWidget {
  const TodoList({super.key});
​
  @override
  State<TodoList> createState() => _TodoListState();
}
​
class _TodoListState extends State<TodoList> {
  static const _appTitle = 'Todo List';
  final todos = <String>[];
  final controller = TextEditingController();
​
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: _appTitle,
      home: Scaffold(
        appBar: AppBar(title: const Text(_appTitle)),
        body: Column(
          children: [
            TextField(controller: controller),
            Expanded(
              child: ListView.builder(
                itemCount: todos.length,
                itemBuilder: (context, index) {
                  final todo = todos[index];
​
                  return Dismissible(
                    key: Key('$todo$index'),
                    onDismissed: (direction) => todos.removeAt(index),
                    background: Container(color: Colors.red),
                    child: ListTile(title: Text(todo)),
                  );
                },
              ),
            ),
          ],
        ),
        floatingActionButton: FloatingActionButton(
          onPressed: () {
            setState(() {
              todos.add(controller.text);
              controller.clear();
            });
          },
          child: const Icon(Icons.add),
        ),
      ),
    );
  }
}
```

## 2. Enter text in the text field

[#](#2-enter-text-in-the-text-field)

Now that you have a todo app, begin writing the test. Start by entering text into the `TextField`.

Accomplish this task by:

1. Building the widget in the test environment.
2. Using the [`enterText()`](https://api.flutter.dev/flutter/flutter_test/WidgetTester/enterText.html) method from the `WidgetTester`.

dart

```
testWidgets('Add and remove a todo', (tester) async {
  // Build the widget
  await tester.pumpWidget(const TodoList());
​
  // Enter 'hi' into the TextField.
  await tester.enterText(find.byType(TextField), 'hi');
});
```

## 3. Ensure tapping a button adds the todo

[#](#3-ensure-tapping-a-button-adds-the-todo)

After entering text into the `TextField`, ensure that tapping the `FloatingActionButton` adds the item to the list.

This involves three steps:

1. Tap the add button using the [`tap()`](https://api.flutter.dev/flutter/flutter_test/WidgetController/tap.html) method.
2. Rebuild the widget after the state has changed using the [`pump()`](https://api.flutter.dev/flutter/flutter_test/WidgetTester/pump.html) method.
3. Ensure that the list item appears on screen.

dart

```
testWidgets('Add and remove a todo', (tester) async {
  // Enter text code...
​
  // Tap the add button.
  await tester.tap(find.byType(FloatingActionButton));
​
  // Rebuild the widget after the state has changed.
  await tester.pump();
​
  // Expect to find the item on screen.
  expect(find.text('hi'), findsOneWidget);
});
```

## 4. Ensure swipe-to-dismiss removes the todo

[#](#4-ensure-swipe-to-dismiss-removes-the-todo)

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()`](https://api.flutter.dev/flutter/flutter_test/WidgetController/drag.html) method to perform a swipe-to-dismiss action.
2. Use the [`pumpAndSettle()`](https://api.flutter.dev/flutter/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', (tester) async {
  // Enter text and add the item...
​
  // Swipe the item to dismiss it.
  await tester.drag(find.byType(Dismissible), const Offset(500, 0));
​
  // Build the widget until the dismiss animation ends.
  await tester.pumpAndSettle();
​
  // Ensure that the item is no longer on screen.
  expect(find.text('hi'), findsNothing);
});
```

## Complete example

[#](#complete-example)

dart

```
import 'package:flutter_test/flutter_test.dart';
import 'package:material_ui/material_ui.dart';
​
void main() {
  testWidgets('Add and remove a todo', (tester) async {
    // Build the widget.
    await tester.pumpWidget(const TodoList());
​
    // Enter 'hi' into the TextField.
    await tester.enterText(find.byType(TextField), 'hi');
​
    // Tap the add button.
    await tester.tap(find.byType(FloatingActionButton));
​
    // Rebuild the widget with the new item.
    await tester.pump();
​
    // Expect to find the item on screen.
    expect(find.text('hi'), findsOneWidget);
​
    // Swipe the item to dismiss it.
    await tester.drag(find.byType(Dismissible), const Offset(500, 0));
​
    // Build the widget until the dismiss animation ends.
    await tester.pumpAndSettle();
​
    // Ensure that the item is no longer on screen.
    expect(find.text('hi'), findsNothing);
  });
}
​
class TodoList extends StatefulWidget {
  const TodoList({super.key});
​
  @override
  State<TodoList> createState() => _TodoListState();
}
​
class _TodoListState extends State<TodoList> {
  static const _appTitle = 'Todo List';
  final todos = <String>[];
  final controller = TextEditingController();
​
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: _appTitle,
      home: Scaffold(
        appBar: AppBar(title: const Text(_appTitle)),
        body: Column(
          children: [
            TextField(controller: controller),
            Expanded(
              child: ListView.builder(
                itemCount: todos.length,
                itemBuilder: (context, index) {
                  final todo = todos[index];
​
                  return Dismissible(
                    key: Key('$todo$index'),
                    onDismissed: (direction) => todos.removeAt(index),
                    background: Container(color: Colors.red),
                    child: ListTile(title: Text(todo)),
                  );
                },
              ),
            ),
          ],
        ),
        floatingActionButton: FloatingActionButton(
          onPressed: () {
            setState(() {
              todos.add(controller.text);
              controller.clear();
            });
          },
          child: const Icon(Icons.add),
        ),
      ),
    );
  }
}
```

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-09-29. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/testing/widget/tap-drag.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/cookbook/testing/widget/tap-drag\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/testing/widget/tap-drag.md "Report an issue with this page").

----
url: https://docs.flutter.dev/resources/in-app-purchases-overview
----

Build in-app revenue with Google Play and App Store support for Flutter

The following resources can help get you started:

* [Add in-app purchases to your Flutter app](https://codelabs.developers.google.com/codelabs/flutter-in-app-purchases#0) (codelab)

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-11. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/resources/in-app-purchases-overview.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/resources/in-app-purchases-overview\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/resources/in-app-purchases-overview.md "Report an issue with this page").

----
url: https://docs.flutter.dev/ui/accessibility/assistive-technologies.md
----

\# Accessibility technologies > Information about accessibility technologies for Flutter developers. ## Summary Assistive technologies are essential for making digital content accessible to individuals with disabilities. This document provides an overview of two key categories of assistive technologies relevant to Flutter development: screen readers for users with visual impairments and mobility support tools for those with motor limitations. By understanding and testing with these technologies, you can ensure your Flutter application provides a more inclusive and user-friendly experience for everyone. ## Screen readers For mobile, screen readers (\[TalkBack]\[], \[VoiceOver]\[]) enable visually impaired users to get spoken feedback about the contents of the screen and interact with the UI by using gestures on mobile and keyboard shortcuts on desktop. Turn on VoiceOver or TalkBack on your mobile device and navigate around your app. \*\*To turn on the screen reader on your device, complete the following steps:\*\* 1. On your device, open \*\*Settings\*\*. 2. Select \*\*Accessibility\*\* and then \*\*TalkBack\*\*. 3. Turn 'Use TalkBack' on or off. 4. Select Ok. To learn how to find and customize Android's accessibility features, view the following video. 1. On your device, open \*\*Settings > Accessibility > VoiceOver\*\* 2. Turn the VoiceOver setting on or off To learn how to find and customize iOS accessibility features, view the following video. For web, the following screen readers are currently supported: Mobile browsers: \* iOS - VoiceOver \* Android - TalkBack Desktop browsers: \* macOS - VoiceOver \* Windows - JAWs & NVDA Screen readers users on web must toggle the "Enable accessibility" button to build the semantics tree. Users can skip this step if you programmatically auto-enable accessibility for your app using this API: \`\`\`dart import 'package:material\_ui/material\_ui.dart'; import 'package:flutter/semantics.dart'; void main() { runApp(const MyApp()); SemanticsBinding.instance.ensureSemantics(); } \`\`\` Windows comes with a screen reader called Narrator but some developers recommend using the more popular NVDA screen reader. To learn about using NVDA to test Windows apps, check out \[Screen Readers 101 For Front-End Developers (Windows)]\[nvda]. \[nvda]: https\://evinced.com/blog/screen-readers-101-for-front-end-developers-windows On a Mac, you can use the desktop version of VoiceOver, which is included in macOS. On Linux, a popular screen reader is called Orca. It comes pre-installed with some distributions and is available on package repositories such as \`apt\`. To learn about using Orca, check out \[Getting started with Orca screen reader on Gnome desktop]\[orca]. \[orca]: https\://www\.a11yproject.com/posts/getting-started-with-orca\
Check out the following \[video demo]\[] to see how to use VoiceOver with the now-archived \[Flutter Gallery]\[] web app. Flutter's standard widgets generate an accessibility tree automatically. However, if your app needs something different, it can be customized using the \[\`Semantics\` widget]\[]. When there is text in your app that should be voiced with a specific voice, inform the screen reader which voice to use by calling \[\`TextSpan.locale\`]\[]. \`MaterialApp.locale\` and \`Localizations.override\` will affect screen reader voices starting from flutter 3.38 release. Usually, the screen reader uses the system voice except where you explicitly set it with \`TextSpan.locale\`. \[Flutter Gallery]: https\://flutter-gallery-archive.web.app \[\`TextSpan.locale\`]: https\://api.flutter.dev/flutter/painting/TextSpan/locale.html \[\`Semantics\` widget]: https\://api.flutter.dev/flutter/widgets/Semantics-class.html \[TalkBack]: https\://support.google.com/accessibility/android/answer/6283677?hl=en \[VoiceOver]: https\://www\.apple.com/lae/accessibility/iphone/vision/ \[video demo]: https\://www\.youtube.com/watch?v=A6Sx0lBP8PI ## Mobility support For users with limited dexterity or hand strength, mobility support features can be helpful. Both Android and iOS offer a range of tools designed to make navigation and control easier. These features allow users to operate their devices through external switches, voice commands, or simplified on-screen menus. Android provides Switch Access, Voice Access and Accessibility Menu, while iOS offers Switch Control, Voice Control, and AssistiveTouch. Understanding these tools helps in creating apps that are usable by people with diverse physical abilities. | OS | Features | Functions | | --- | --- | --- | | Android | \*\*Switch Access\*\* | As an alternate input method, you can use Switch Access and Camera Switches | | Android | \*\*Voice Access\*\* | Control your device with your voice | | Android | \*\*Accessibility Menu\*\* | A floating, on-screen menu that provides simplified buttons to control essential phone functions | | iOS | \*\*Switch Control\*\* | Use switches as an alternate input methods | | iOS | \*\*Voice Control\*\* | Control your device with your voice | | iOS | \*\*AssistiveTouch\*\* | Use AssistiveTouch to replace multi-finger gestures or hardware button actions| {:.table .table-striped}

----
url: https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.31.0
----

The 2.31.0 release of the Dart and Flutter DevTools includes the following changes among other general improvements. To learn more about DevTools, check out the [DevTools overview](https://docs.flutter.dev/tools/devtools).

## General updates

[#](#general-updates)

* Added a new feature for deep link validation, supporting deep link web checks on Android. - [#6935](https://github.com/flutter/devtools/pull/6935)
* Added the basic plumbing to allow connections to a Dart Tooling Daemon. - [#7009](https://github.com/flutter/devtools/pull/7009)
* Made table text selectable [#6919](https://github.com/flutter/devtools/pull/6919)

## Inspector updates

[#](#inspector-updates)

* When done typing in the search field, the next selection is now automatically selected - [#6677](https://github.com/flutter/devtools/pull/6677)

* Added link to package directory documentation, from the inspect settings dialog - [#6825](https://github.com/flutter/devtools/pull/6825)

* Fix bug where widgets owned by the Flutter framework were showing up in the widget tree view - [#6857](https://github.com/flutter/devtools/pull/6857)

* Only cache pub root directories added by the user - [#6897](https://github.com/flutter/devtools/pull/6897)

* Remove Flutter pub root if it was accidentally cached - [#6911](https://github.com/flutter/devtools/pull/6911)

## Performance updates

[#](#performance-updates)

* Changed raster layer preview background to a checkerboard. - [#6827](https://github.com/flutter/devtools/pull/6827)

## CPU profiler updates

[#](#cpu-profiler-updates)

* Added hover cards to show sampling rate for the item in drop down. - [#7010](https://github.com/flutter/devtools/pull/7010)

## Debugger updates

[#](#debugger-updates)

* Highlight `extension type` as a declaration keyword, highlight the `$` in identifier interpolation as part of the interpolation, and properly highlight comments within type arguments. - [6837](https://github.com/flutter/devtools/pull/6837)

## Logging updates

[#](#logging-updates)

* Added scrollbar to details pane. - [#6917](https://github.com/flutter/devtools/pull/6917)

## VS Code Sidebar updates

[#](#vs-code-sidebar-updates)

* Fixed an issue that prevented the VS code sidebar from loading in recent beta/master builds. - [#6984](https://github.com/flutter/devtools/pull/6984)

## DevTools Extension updates

[#](#devtools-extension-updates)

* Fixed a couple bugs preventing Dart server apps from connecting to DevTools extensions. - [#6982](https://github.com/flutter/devtools/pull/6982), [#6993](https://github.com/flutter/devtools/pull/6993)

## Full commit history

[#](#full-commit-history)

To find a complete list of changes in this release, check out the [DevTools git log](https://github.com/flutter/devtools/tree/v2.31.0).

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/release-notes/release-notes-2.31.0.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.31.0\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/release-notes/release-notes-2.31.0.md "Report an issue with this page").

----
url: https://docs.flutter.dev/platform-integration/web/initialization.md
----

\# Flutter web app initialization > Customize how Flutter apps are initialized on the web. This page details the initialization process for Flutter web apps and how it can be customized. ## Bootstrapping The \`flutter build web\` command produces a script called \`flutter\_bootstrap.js\` in the build output directory (\`build/web\`). This file contains the JavaScript code needed to initialize and run your Flutter app. You can use this script by placing an async-script tag for it in your \`index.html\` file in the \`web\` subdirectory of your Flutter app: \`\`\`html highlightLines=3 \`\`\` Alternatively, you can inline the entire contents of the \`flutter\_bootstrap.js\` file by inserting the template token \`{{flutter\_bootstrap\_js}}\` in your \`index.html\` file: \`\`\`html highlightLines=4 \`\`\` The \`{{flutter\_bootstrap\_js}}\` token is replaced with the contents of the \`flutter\_bootstrap.js\` file when the \`index.html\` file is copied to the output directory (\`build/web\`) during the build step. []()## Customize initialization By default, \`flutter build web\` generates a \`flutter\_bootstrap.js\` file that does a simple initialization of your Flutter app. However, in some scenarios, you might have a reason to customize this initialization process, such as: \* Setting a custom Flutter configuration for your app. \* Integrating a custom service worker for offline support or caching. \* Writing custom JavaScript code to run at different stages of the startup process. To write your own custom bootstrapping logic instead of using the default script produced by the build step, you can place a \`flutter\_bootstrap.js\` file in the \`web\` subdirectory of your project, which is copied over and used instead of the default script produced by the build. This file is also templated, and you can insert several special tokens that the build step substitutes at build time when copying the \`flutter\_bootstrap.js\` file to the output directory. The following table lists the tokens that the build step will substitute in either the \`flutter\_bootstrap.js\` or \`index.html\` files: | Token | Replaced with | |---|---| | \`{{flutter\_js}}\` | The JavaScript code that makes the \`FlutterLoader\` object available in the \`\_flutter.loader\` global variable. (See the \`\_flutter.loader.load() API\` section below for more details.) | | \`{{flutter\_build\_config}}\` | A JavaScript statement that sets metadata produced by the build process which gives the \`FlutterLoader\` information needed to properly bootstrap your application. | | \`{{flutter\_service\_worker\_version}}\` | A unique number representing the build version for custom service worker configurations (Flutter no longer generates a service worker by default; see the \[Web FAQ]\(/platform-integration/web/faq#how-do-i-configure-a-service-worker)). | | \`{{flutter\_bootstrap\_js}}\` | As mentioned above, this inlines the contents of the \`flutter\_bootstrap.js\` file directly into the \`index.html\` file. Note that this token can only be used in the \`index.html\` and not the \`flutter\_bootstrap.js\` file itself. | {:.table} []()## Write a custom bootstrap script {:#custom-bootstrap-js} Any custom \`flutter\_bootstrap.js\` script needs to have three components in order to successfully start your Flutter app: \* A \`{{flutter\_js}}\` token, to make \`\_flutter.loader\` available. \* A \`{{flutter\_build\_config}}\` token, which provides information about the build to the \`FlutterLoader\` needed to start your app. \* A call to \`\_flutter.loader.load()\`, which actually starts the app. The most basic \`flutter\_bootstrap.js\` file would look something like this: \`\`\`js {{flutter\_js}} {{flutter\_build\_config}} \_flutter.loader.load(); \`\`\` ## Customize the Flutter loader The \`\_flutter.loader.load()\` JavaScript API can be invoked with optional arguments to customize initialization behavior: | Name | Description | JS type | |-------------------------|-------------------------------------------------------------------------------------------------------------------------------|--------------| | \`config\` | The Flutter configuration of your app. | \`Object\` | | \`onEntrypointLoaded\` | The function called when the engine is ready to be initialized. Receives an \`engineInitializer\` object as its only parameter. | \`Function\` | {:.table} The \`config\` argument is an object that can have the following optional fields: | Name | Description | Dart type | |---|---|---| |\`assetBase\`| The base URL of the \`assets\` directory of the app. Add this when Flutter loads from a different domain or subdirectory than the actual web app. You might need this when you embed Flutter web into another app, or when you deploy its assets to a CDN. |\`String\`| |\`canvasKitBaseUrl\`| The base URL from where \`canvaskit.wasm\` is downloaded. |\`String\`| |\`canvasKitVariant\`| The CanvasKit variant to download. Your options cover:\
\
1\. \`auto\`: Downloads the optimal variant for the browser. The option defaults to this value.\
2\. \`full\`: Downloads the full variant of CanvasKit that works in all browsers.\
3\. \`chromium\`: Downloads a smaller variant of CanvasKit that uses Chromium compatible APIs. \*\*\_Warning\_\*\*: Don't use the \`chromium\` option unless you plan on only using Chromium-based browsers. |\`String\`| |\`canvasKitForceCpuOnly\`| When \`true\`, forces CPU-only rendering in CanvasKit (the engine won't use WebGL). |\`bool\`| |\`canvasKitMaximumSurfaces\`| The maximum number of overlay surfaces that the CanvasKit renderer can use. |\`double\`| |\`debugShowSemanticsNodes\`| If \`true\`, Flutter visibly renders the semantics tree onscreen (for debugging). |\`bool\`| |\`entrypointBaseUrl\`| The base URL of your Flutter app's entrypoint. Defaults to "/". |\`String\`| |\`fontFallbackBaseUrl\`| Base URL for downloading fallback fonts when bundled fonts lack required glyphs. Defaults to \`"https\://fonts.gstatic.com/s/"\`. |\`String\`| |\`forceSingleThreadedSkwasm\`| Forces single-threaded SkWasm mode for compatibility (only impacts apps compiled with \`--wasm\` when \[WebAssembly]\[wasm-support] is selected at runtime). |\`bool\`| |\`hostElement\`| HTML Element into which Flutter renders the app (see \[embedding Flutter views]\[embedding-flutter-web]). When not set, Flutter web takes over the whole page. |\`HtmlElement\`| |\`multiViewEnabled\`| Enables multi-view mode for \[embedding Flutter views]\[embedding-flutter-web] into host DOM elements dynamically. |\`bool\`| |\`nonce\`| A \[Content Security Policy (CSP) nonce]\[nonce-mdn] string applied to inline scripts and styles created by the engine. |\`String\`| {:.table} \[embedding-flutter-web]: /platform-integration/web/embedding-flutter-web \[nonce-mdn]: https\://developer.mozilla.org/docs/Web/HTML/Global\_attributes/nonce \[wasm-support]: /platform-integration/web/wasm ## forceSingleThreadedSkwasm A boolean flag to force the Skia WebAssembly (skwasm) renderer to run in \*\*single-threaded mode\*\*. This is useful if: \* Your environment doesn't support multi-threaded WASM. For example, \`SharedArrayBuffer\` is not available or required security headers are missing. \* You want maximum browser compatibility. \* Use \`false\` (default) to allow multi-threaded rendering when supported, which improves performance. ## Example usage \`\`\`js \_flutter.loader.load({ config: { forceSingleThreadedSkwasm: true, }, }); \`\`\` ## Example: Customizing Flutter configuration based on URL query parameters The following example shows a custom \`flutter\_bootstrap.js\` that enables single-threaded mode when a \`force\_st\` query parameter is present in the URL: \`\`\`js {{flutter\_js}} {{flutter\_build\_config}} const searchParams = new URLSearchParams(window\.location.search); const forceSt = searchParams.has('force\_st'); const userConfig = forceSt ? { forceSingleThreadedSkwasm: true } : {}; \_flutter.loader.load({ config: userConfig, }); \`\`\` This script evaluates the \`URLSearchParams\` of the page to determine whether the user passed a \`force\_st\` query parameter and configures the app accordingly. ## The onEntrypointLoaded callback You can also pass an \`onEntrypointLoaded\` callback into the \`load\` API in order to perform custom logic at different parts of the initialization process. The initialization process is split into the following stages: \*\*Loading the entrypoint script\*\* : The \`load\` function calls the \`onEntrypointLoaded\` callback once the main entrypoint script has been downloaded and run by the browser. Flutter also calls \`onEntrypointLoaded\` on every hot restart during development. \*\*Initializing the Flutter engine\*\* : The \`onEntrypointLoaded\` callback receives an \*\*engine initializer\*\* object as its only parameter. Use the engine initializer \`initializeEngine()\` function to set the run-time configuration, like \`multiViewEnabled: true\`, and start the Flutter web engine. \*\*Running the app\*\* : The \`initializeEngine()\` function returns a \[\`Promise\`]\[js-promise] that resolves with an \*\*app runner\*\* object. The app runner has a single method, \`runApp()\`, that runs the Flutter app. \*\*Adding views to (or removing views from) an app\*\* : The \`runApp()\` method returns a \*\*flutter app\*\* object. In multi-view mode, the \`addView\` and \`removeView\` methods can be used to manage app views from the host app. To learn more, check out \[Embedded mode]\[embedded-mode]. \[embedded-mode]: /platform-integration/web/embedding-flutter-web#embedded-mode \[js-promise]: https\://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global\_Objects/Promise :::warning When you provide your own \`onEntrypointLoaded\` callback, the \`config\` that you pass to \`\_flutter.loader.load()\` is \*\*not\*\* forwarded to the engine automatically. Flutter only applies that \`config\` for you when you omit \`onEntrypointLoaded\` and let the loader start the app. In a custom callback, pass the configuration to \`initializeEngine()\` yourself, otherwise it's silently ignored: \`\`\`js \_flutter.loader.load({ onEntrypointLoaded: async function(engineInitializer) { const appRunner = await engineInitializer.initializeEngine({ // Set your run-time configuration here. renderer: 'canvaskit', }); await appRunner.runApp(); }, }); \`\`\` ::: ## Example: Display a progress indicator To give the user of your application feedback during the initialization process, use the hooks provided for each stage to update the DOM: \`\`\`js {{flutter\_js}} {{flutter\_build\_config}} const loading = document.createElement('div'); document.body.appendChild(loading); loading.textContent = "Loading Entrypoint..."; \_flutter.loader.load({ onEntrypointLoaded: async function(engineInitializer) { loading.textContent = "Initializing engine..."; // If you have a \`config\`, pass it here. The config given to \`load()\` // is not forwarded when you supply your own \`onEntrypointLoaded\`. const appRunner = await engineInitializer.initializeEngine(); loading.textContent = "Running app..."; await appRunner.runApp(); } }); \`\`\` ## Common warnings {:#common-warnings} This section describes common web build and initialization warnings and how to resolve them. ### Service worker version deprecation If you experience a warning similar to the following: \`\`\`text Warning: In index.html:37: Local variable for "serviceWorkerVersion" is deprecated. Use "" template token instead. \`\`\` Delete the following line from the \`web/index.html\` file: \`\`\`html title="web/index.html" var serviceWorkerVersion = null; \`\`\` ### FlutterLoader.loadEntrypoint deprecation If you experience a warning similar to the following: \`\`\`text Warning: In index.html:40: "FlutterLoader.loadEntrypoint" is deprecated. Use "FlutterLoader.load" instead. \`\`\` This warning occurs when an older \`web/index.html\` file uses \`\_flutter.loader.loadEntrypoint()\` instead of \`\_flutter.loader.load()\`. The CLI message references the \`FlutterLoader\` interface, which JavaScript code accesses through \`\_flutter.loader\`. If your app uses the default initialization and doesn't require custom bootstrapping, replace the legacy script block in \`web/index.html\` with the standard script tag: \`\`\`html title="web/index.html" \`\`\` If you customized your initialization logic, update your bootstrap code in \`web/index.html\` (or a custom \`web/flutter\_bootstrap.js\` file) to call \`\_flutter.loader.load()\` instead: \`\`\`js \_flutter.loader.load({ onEntrypointLoaded: async function(engineInitializer) { const appRunner = await engineInitializer.initializeEngine(); await appRunner.runApp(); } }); \`\`\` To review an example that updates DOM elements during startup, consult the \[progress indicator example]\[]. \[progress indicator example]: #example-display-a-progress-indicator

----
url: https://docs.flutter.dev/cookbook/networking/background-parsing
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Parse JSON in the background](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

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 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, using a separate [Isolate](https://api.flutter.dev/flutter/dart-isolate/Isolate-class.html). This recipe uses the following steps:

1. Add the `http` package.
2. Make a network request using the `http` package.
3. Convert the response into a list of photos.
4. Move this work to a separate isolate.

## 1. Add the `http` package

[#](#1-add-the-http-package)

First, add the [`http`](https://pub.dev/packages/http) package to your project. The `http` package makes it easier to perform network requests, such as fetching data from a JSON endpoint.

To add the `http` package as a dependency, run `flutter pub add`:

```
$ flutter pub add http
```

## 2. Make a network request

[#](#2-make-a-network-request)

This example covers how to fetch a large JSON document that contains a list of 5000 photo objects from the [JSONPlaceholder REST API](https://jsonplaceholder.typicode.com), using the [`http.get()`](https://pub.dev/documentation/http/latest/http/get.html) method.

dart

```
Future<http.Response> fetchPhotos(http.Client client) async {
  return client.get(Uri.parse('https://jsonplaceholder.typicode.com/photos'));
}
```

## 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](/cookbook/networking/fetch-data) recipe, convert the `http.Response` into a list of Dart objects. This makes the data easier to work with.

### Create a `Photo` class

[#](#create-a-photo-class)

First, create a `Photo` class that contains data about a photo. Include a `fromJson()` factory method to make it easy to create a `Photo` starting with a JSON object.

dart

```
class Photo {
  final int albumId;
  final int id;
  final String title;
  final String url;
  final String thumbnailUrl;
​
  const Photo({
    required this.albumId,
    required this.id,
    required this.title,
    required this.url,
    required this.thumbnailUrl,
  });
​
  factory Photo.fromJson(Map<String, Object?> json) {
    return Photo(
      albumId: json['albumId'] as int,
      id: json['id'] as int,
      title: json['title'] as String,
      url: json['url'] as String,
      thumbnailUrl: json['thumbnailUrl'] as String,
    );
  }
}
```

### Convert the response into a list of photos

[#](#convert-the-response-into-a-list-of-photos)

Now, use the following instructions to update the `fetchPhotos()` function so that it returns a `Future<List<Photo>>`:

1. Create a `parsePhotos()` function that converts the response body into a `List<Photo>`.
2. Use the `parsePhotos()` function in the `fetchPhotos()` function.

dart

```
// A function that converts a response body into a List<Photo>.
List<Photo> parsePhotos(String responseBody) {
  final parsed = (jsonDecode(responseBody) as List<Object?>)
      .cast<Map<String, Object?>>();
​
  return parsed.map<Photo>(Photo.fromJson).toList();
}
​
Future<List<Photo>> fetchPhotos(http.Client client) async {
  final response = await client.get(
    Uri.parse('https://jsonplaceholder.typicode.com/photos'),
  );
​
  // Synchronously run parsePhotos in the main isolate.
  return parsePhotos(response.body);
}
```

## 4. Move this work to a separate isolate

[#](#4-move-this-work-to-a-separate-isolate)

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 get rid of it.

You can remove the jank by moving the parsing and conversion to a background isolate using the [`compute()`](https://api.flutter.dev/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, run the `parsePhotos()` function in the background.

dart

```
Future<List<Photo>> fetchPhotos(http.Client client) async {
  final response = await client.get(
    Uri.parse('https://jsonplaceholder.typicode.com/photos'),
  );
​
  // Use the compute function to run parsePhotos in a separate isolate.
  return compute(parsePhotos, response.body);
}
```

## Notes on working with isolates

[#](#notes-on-working-with-isolates)

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<Photo>` in this example.

You might experience errors if you try to pass more complex objects, such as a `Future` or `http.Response` between isolates.

As an alternate solution, check out the [`worker_manager`](https://pub.dev/packages/worker_manager) or [`workmanager`](https://pub.dev/packages/workmanager) packages for background processing.

## Complete example

[#](#complete-example)

dart

```
import 'dart:async';
import 'dart:convert';
​
import 'package:flutter/foundation.dart';
import 'package:http/http.dart' as http;
import 'package:material_ui/material_ui.dart';
​
Future<List<Photo>> fetchPhotos(http.Client client) async {
  final response = await client.get(
    Uri.parse('https://jsonplaceholder.typicode.com/photos'),
  );
​
  // 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<Photo>.
List<Photo> parsePhotos(String responseBody) {
  final parsed = (jsonDecode(responseBody) as List<Object?>)
      .cast<Map<String, Object?>>();
​
  return parsed.map<Photo>(Photo.fromJson).toList();
}
​
class Photo {
  final int albumId;
  final int id;
  final String title;
  final String url;
  final String thumbnailUrl;
​
  const Photo({
    required this.albumId,
    required this.id,
    required this.title,
    required this.url,
    required this.thumbnailUrl,
  });
​
  factory Photo.fromJson(Map<String, Object?> json) {
    return Photo(
      albumId: json['albumId'] as int,
      id: json['id'] as int,
      title: json['title'] as String,
      url: json['url'] as String,
      thumbnailUrl: json['thumbnailUrl'] as String,
    );
  }
}
​
void main() => runApp(const MyApp());
​
class MyApp extends StatelessWidget {
  const MyApp({super.key});
​
  @override
  Widget build(BuildContext context) {
    const appTitle = 'Isolate Demo';
​
    return const MaterialApp(
      title: appTitle,
      home: MyHomePage(title: appTitle),
    );
  }
}
​
class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});
​
  final String title;
​
  @override
  State<MyHomePage> createState() => _MyHomePageState();
}
​
class _MyHomePageState extends State<MyHomePage> {
  late Future<List<Photo>> futurePhotos;
​
  @override
  void initState() {
    super.initState();
    futurePhotos = fetchPhotos(http.Client());
  }
​
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text(widget.title)),
      body: FutureBuilder<List<Photo>>(
        future: futurePhotos,
        builder: (context, snapshot) {
          if (snapshot.hasError) {
            return const Center(child: Text('An error has occurred!'));
          } else if (snapshot.hasData) {
            return PhotosList(photos: snapshot.data!);
          } else {
            return const Center(child: CircularProgressIndicator());
          }
        },
      ),
    );
  }
}
​
class PhotosList extends StatelessWidget {
  const PhotosList({super.key, required this.photos});
​
  final List<Photo> photos;
​
  @override
  Widget build(BuildContext context) {
    return GridView.builder(
      gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
        crossAxisCount: 2,
      ),
      itemCount: photos.length,
      itemBuilder: (context, index) {
        return Image.network(photos[index].thumbnailUrl);
      },
    );
  }
}
```

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-09-29. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/networking/background-parsing.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/cookbook/networking/background-parsing\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/networking/background-parsing.md "Report an issue with this page").

----
url: https://docs.flutter.dev/data-and-backend/state-mgmt/simple
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Simple app state management](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

Now that you know about [declarative UI programming](/data-and-backend/state-mgmt/declarative) and the difference between [ephemeral and app state](/data-and-backend/state-mgmt/ephemeral-vs-app), you are ready to learn about simple app state management.

On this page, we are going to be using the `provider` package. If you are new to Flutter and you don't have a strong reason to choose another approach (Redux, Rx, hooks, etc.), this is probably the approach you should start with. The `provider` package is easy to understand and it doesn't use much code. It also uses concepts that are applicable in every other approach.

That said, if you have a strong background in state management from other reactive frameworks, you can find packages and tutorials listed on the [options page](/data-and-backend/state-mgmt/options).

## Our example

[#](#our-example)

For illustration, consider the following simple app.

The app has two separate screens: a catalog, and a cart (represented by the `MyCatalog`, and `MyCart` widgets, respectively). It could be a shopping app, but you can imagine the same structure in a simple social networking app (replace catalog for "wall" and cart for "favorites").

The catalog screen includes a custom app bar (`MyAppBar`) and a scrolling view of many list items (`MyListItems`).

Here's the app visualized as a widget tree.

So we have at least 5 subclasses of `Widget`. Many of them need access to state that "belongs" elsewhere. For example, each `MyListItem` needs to be able to add itself to the cart. It might also want to see whether the currently displayed item is already in the cart.

This takes us to our first question: where should we put the current state of the cart?

## Lifting state up

[#](#lifting-state-up)

In Flutter, it makes sense to keep the state above the widgets that use it.

Why? In declarative frameworks like Flutter, if you want to change the UI, you have to rebuild it. There is no easy way to have `MyCart.updateWith(somethingNew)`. In other words, it's hard to imperatively change a widget from outside, by calling a method on it. And even if you could make this work, you would be fighting the framework instead of letting it help you.

dart

```
// BAD: DO NOT DO THIS
void myTapHandler() {
  var cartWidget = somehowGetMyCartWidget();
  cartWidget.updateWith(item);
}
```

Even if you get the above code to work, you would then have to deal with the following in the `MyCart` widget:

dart

```
// BAD: DO NOT DO THIS
Widget build(BuildContext context) {
  return SomeWidget(
    // The initial state of the cart.
  );
}
​
void updateWith(Item item) {
  // Somehow you need to change the UI from here.
}
```

You would need to take into consideration the current state of the UI and apply the new data to it. It's hard to avoid bugs this way.

In Flutter, you construct a new widget every time its contents change. Instead of `MyCart.updateWith(somethingNew)` (a method call) you use `MyCart(contents)` (a constructor). Because you can only construct new widgets in the build methods of their parents, if you want to change `contents`, it needs to live in `MyCart`'s parent or above.

dart

```
// GOOD
void myTapHandler(BuildContext context) {
  var cartModel = somehowGetMyCartModel(context);
  cartModel.add(item);
}
```

Now `MyCart` has only one code path for building any version of the UI.

dart

```
// GOOD
Widget build(BuildContext context) {
  var cartModel = somehowGetMyCartModel(context);
  return SomeWidget(
    // Just construct the UI once, using the current state of the cart.
    // ···
  );
}
```

In our example, `contents` needs to live in `MyApp`. Whenever it changes, it rebuilds `MyCart` from above (more on that later). Because of this, `MyCart` doesn't need to worry about lifecycle—it just declares what to show for any given `contents`. When that changes, the old `MyCart` widget disappears and is completely replaced by the new one.

This is what we mean when we say that widgets are immutable. They don't change—they get replaced.

Now that we know where to put the state of the cart, let's see how to access it.

## Accessing the state

[#](#accessing-the-state)

When a user clicks on one of the items in the catalog, it's added to the cart. But since the cart lives above `MyListItem`, how do we do that?

A simple option is to provide a callback that `MyListItem` can call when it is clicked. Dart's functions are first class objects, so you can pass them around any way you want. So, inside `MyCatalog` you can define the following:

dart

```
@override
Widget build(BuildContext context) {
  return SomeWidget(
    // Construct the widget, passing it a reference to the method above.
    MyListItem(myTapCallback),
  );
}
​
void myTapCallback(Item item) {
  print('user tapped on $item');
}
```

This works okay, but for an app state that you need to modify from many different places, you'd have to pass around a lot of callbacks—which gets old pretty quickly.

Fortunately, Flutter has mechanisms for widgets to provide data and services to their descendants (in other words, not just their children, but any widgets below them). As you would expect from Flutter, where *Everything is a Widget™*, these mechanisms are just special kinds of widgets—`InheritedWidget`, `InheritedNotifier`, `InheritedModel`, and more. We won't be covering those here, because they are a bit low-level for what we're trying to do.

Instead, we are going to use a package that works with the low-level widgets but is simple to use. It's called `provider`.

Before working with `provider`, don't forget to add the dependency on it to your `pubspec.yaml`.

To add the `provider` package as a dependency, run `flutter pub add`:

```
$ flutter pub add provider
```

Now you can `import 'package:provider/provider.dart';` and start building.

With `provider`, you don't need to worry about callbacks or `InheritedWidgets`. But you do need to understand 3 concepts:

* ChangeNotifier
* ChangeNotifierProvider
* Consumer

## ChangeNotifier

[#](#changenotifier)

`ChangeNotifier` is a simple class included in the Flutter SDK which provides change notification to its listeners. In other words, if something is a `ChangeNotifier`, you can subscribe to its changes. (It is a form of Observable, for those familiar with the term.)

In `provider`, `ChangeNotifier` is one way to encapsulate your application state. For very simple apps, you get by with a single `ChangeNotifier`. In complex ones, you'll have several models, and therefore several `ChangeNotifiers`. (You don't need to use `ChangeNotifier` with `provider` at all, but it's an easy class to work with.)

In our shopping app example, we want to manage the state of the cart in a `ChangeNotifier`. We create a new class that extends it, like so:

dart

```
class CartModel extends ChangeNotifier {
  /// Internal, private state of the cart.
  final List<Item> _items = [];
​
  /// An unmodifiable view of the items in the cart.
  UnmodifiableListView<Item> get items => UnmodifiableListView(_items);
​
  /// The current total price of all items (assuming all items cost $42).
  int get totalPrice => _items.length * 42;
​
  /// Adds [item] to cart. This and [removeAll] are the only ways to modify the
  /// cart from the outside.
  void add(Item item) {
    _items.add(item);
    // This call tells the widgets that are listening to this model to rebuild.
    notifyListeners();
  }
​
  /// Removes all items from the cart.
  void removeAll() {
    _items.clear();
    // This call tells the widgets that are listening to this model to rebuild.
    notifyListeners();
  }
}
```

The only code that is specific to `ChangeNotifier` is the call to `notifyListeners()`. Call this method any time the model changes in a way that might change your app's UI. Everything else in `CartModel` is the model itself and its business logic.

`ChangeNotifier` is part of `flutter:foundation` and doesn't depend on any higher-level classes in Flutter. It's easily testable (you don't even need to use [widget testing](/testing/overview#widget-tests) for it). For example, here's a simple unit test of `CartModel`:

dart

```
test('adding item increases total cost', () {
  final cart = CartModel();
  final startingPrice = cart.totalPrice;
  var i = 0;
  cart.addListener(() {
    expect(cart.totalPrice, greaterThan(startingPrice));
    i++;
  });
  cart.add(Item('Dash'));
  expect(i, 1);
});
```

## ChangeNotifierProvider

[#](#changenotifierprovider)

`ChangeNotifierProvider` is the widget that provides an instance of a `ChangeNotifier` to its descendants. It comes from the `provider` package.

We already know where to put `ChangeNotifierProvider`: above the widgets that need to access it. In the case of `CartModel`, that means somewhere above both `MyCart` and `MyCatalog`.

You don't want to place `ChangeNotifierProvider` higher than necessary (because you don't want to pollute the scope). But in our case, the only widget that is on top of both `MyCart` and `MyCatalog` is `MyApp`.

dart

```
void main() {
  runApp(
    ChangeNotifierProvider(
      create: (context) => CartModel(),
      child: const MyApp(),
    ),
  );
}
```

Note that we're defining a builder that creates a new instance of `CartModel`. `ChangeNotifierProvider` is smart enough *not* to rebuild `CartModel` unless absolutely necessary. It also automatically calls `dispose()` on `CartModel` when the instance is no longer needed.

If you want to provide more than one class, you can use `MultiProvider`:

dart

```
void main() {
  runApp(
    MultiProvider(
      providers: [
        ChangeNotifierProvider(create: (context) => CartModel()),
        Provider(create: (context) => SomeOtherClass()),
      ],
      child: const MyApp(),
    ),
  );
}
```

## Consumer

[#](#consumer)

Now that `CartModel` is provided to widgets in our app through the `ChangeNotifierProvider` declaration at the top, we can start using it.

This is done through the `Consumer` widget.

dart

```
return Consumer<CartModel>(
  builder: (context, cart, child) {
    return Text('Total price: ${cart.totalPrice}');
  },
);
```

We must specify the type of the model that we want to access. In this case, we want `CartModel`, so we write `Consumer<CartModel>`. If you don't specify the generic (`<CartModel>`), the `provider` package won't be able to help you. `provider` is based on types, and without the type, it doesn't know what you want.

The only required argument of the `Consumer` widget is the builder. Builder is a function that is called whenever the `ChangeNotifier` changes. (In other words, when you call `notifyListeners()` in your model, all the builder methods of all the corresponding `Consumer` widgets are called.)

The builder is called with three arguments. The first one is `context`, which you also get in every build method.

The second argument of the builder function is the instance of the `ChangeNotifier`. It's what we were asking for in the first place. You can use the data in the model to define what the UI should look like at any given point.

The third argument is `child`, which is there for optimization. If you have a large widget subtree under your `Consumer` that *doesn't* change when the model changes, you can construct it once and get it through the builder.

dart

```
return Consumer<CartModel>(
  builder: (context, cart, child) => Stack(
    children: [
      // Use SomeExpensiveWidget here, without rebuilding every time.
      ?child,
      Text('Total price: ${cart.totalPrice}'),
    ],
  ),
  // Build the expensive widget here.
  child: const SomeExpensiveWidget(),
);
```

It is best practice to put your `Consumer` widgets as deep in the tree as possible. You don't want to rebuild large portions of the UI just because some detail somewhere changed.

dart

```
// DON'T DO THIS
return Consumer<CartModel>(
  builder: (context, cart, child) {
    return HumongousWidget(
      // ...
      child: AnotherMonstrousWidget(
        // ...
        child: Text('Total price: ${cart.totalPrice}'),
      ),
    );
  },
);
```

Instead:

dart

```
// DO THIS
return HumongousWidget(
  // ...
  child: AnotherMonstrousWidget(
    // ...
    child: Consumer<CartModel>(
      builder: (context, cart, child) {
        return Text('Total price: ${cart.totalPrice}');
      },
    ),
  ),
);
```

### Provider.of

[#](#provider-of)

Sometimes, you don't really need the *data* in the model to change the UI but you still need to access it. For example, a `ClearCart` button wants to allow the user to remove everything from the cart. It doesn't need to display the contents of the cart, it just needs to call the `clear()` method.

We could use `Consumer<CartModel>` for this, but that would be wasteful. We'd be asking the framework to rebuild a widget that doesn't need to be rebuilt.

For this use case, we can use `Provider.of`, with the `listen` parameter set to `false`.

dart

```
Provider.of<CartModel>(context, listen: false).removeAll();
```

Using the above line in a build method won't cause this widget to rebuild when `notifyListeners` is called.

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-29. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/data-and-backend/state-mgmt/simple.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/data-and-backend/state-mgmt/simple\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/data-and-backend/state-mgmt/simple.md "Report an issue with this page").

----
url: https://docs.flutter.dev/platform-integration/web.md
----

\# Web support for Flutter > Details of how Flutter supports the creation of web experiences. Flutter delivers the same experiences on the web as on mobile. Building on the portability of Dart, the power of the web platform, the flexibility of the Flutter framework, and the performance of WebAssembly, you can build apps for iOS, Android, and the browser from the same codebase. The web is just another device target for your app. To get started, visit \[Building a web application with Flutter]\[]. ## Powered by WebAssembly Dart and Flutter can compile to WebAssembly, a binary instruction format that enables fast apps on all major browsers. For a glimpse into the benefits of using WebAssembly, check out the following video. ## How it works Adding web support to Flutter involved implementing Flutter's core drawing layer on top of standard browser APIs, in addition to compiling Dart to JavaScript, instead of the ARM machine code that is used for mobile applications. Using a combination of DOM, Canvas, and WebAssembly, Flutter can provide a portable, high-quality, and performant user experience across modern browsers. We implemented the core drawing layer completely in Dart and used Dart's optimized JavaScript compiler to compile the Flutter core and framework along with your application into a single, minified source file that can be deployed to any web server. ## What types of apps can I build? While you can do a lot on the web, Flutter's web support is most valuable in the following scenarios: \*\*Single Page Application\*\* : Flutter's web support enables complex standalone web apps that are rich with graphics and interactive content to reach end users on a wide variety of devices. \*\*Existing mobile applications\*\* : Web support for Flutter provides a browser-based delivery model for existing Flutter mobile apps. Not every HTML scenario is ideally suited for Flutter at this time. For example, text-rich, flow-based, static content such as blog articles benefit from the document-centric model that the web is built around, rather than the app-centric services that a UI framework like Flutter can deliver. However, you \_can\_ use Flutter to embed interactive experiences into these websites. However, if you want to use Dart and wish to implement a traditional DOM-based website, a community-released Dart package, \[Jaspr]\[], supports static websites; in fact, the \[Dart documentation]\[] and \[Flutter documentation]\[] and \[marketing]\[] websites were migrated to use the Jaspr package. Note that Jaspr uses Dart (but not Flutter), and makes SEO work in the same way a traditional website would. \[Dart documentation]: https\://dart.dev \[Flutter documentation]: https\://flutter.dev \[Jaspr]: https\://jaspr.site/ \[marketing]: https\://flutter.dev ## Get started The following resources can help you get started: \* To add web support to an existing app, or to create a new app that includes web support, visit \[Building a web application with Flutter]\[]. \* To configure web development server settings in a centralized file, check out \[Set up a web development configuration file]\[]. \* To learn how to create a responsive Flutter app, check out \[Creating responsive apps]\[]. \* To view commonly asked questions and answers, visit the \[web FAQ]\[]. \* For code examples, check out the \[web samples for Flutter]\[]. \* For a Flutter web app demo, check out the \[Wonderous app]\[]. \* To learn about deploying a web app, visit \[Preparing an app for web release]\[]. \* \[File an issue]\[] on the main Flutter repo. \* You can chat and ask web-related questions on the \*\*#help\*\* channel on \[Discord]\[]. \[Building a web application with Flutter]: /platform-integration/web/building \[Set up a web development configuration file]: /platform-integration/web/web-dev-config-file \[Creating responsive apps]: /ui/adaptive-responsive \[Discord]: https\://discordapp.com/invite/yeZ6s7k \[file an issue]: https\://goo.gle/flutter\_web\_issue \[Wonderous app]: https\://wonderous.app//web \[Preparing an app for web release]: /deployment/web \[Progressive Web Application]: https\://web.dev/progressive-web-apps/ \[web FAQ]: /platform-integration/web/faq \[web samples for Flutter]: https\://github.com/flutter/samples/#?platform=web

----
url: https://docs.flutter.dev/platform-integration/web/wasm
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Support for WebAssembly (Wasm)](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

Flutter and Dart support [WebAssembly](https://webassembly.org/) as a compilation target when building applications for the web.

## Get started

[#](#get-started)

To try a pre-built Flutter web app using Wasm, check out the [Wonderous demo app](https://wonderous.app/web/).

To experiment with Wasm in your own apps, use the following steps.

### Switch to the latest version of Flutter

[#](#switch-to-the-latest-version-of-flutter)

Switch to Flutter version 3.24 or higher to run and compile Flutter applications to WebAssembly. To ensure you are running the latest version, run `flutter upgrade`.

### Ensure that your app's dependencies are compatible

[#](#ensure-that-your-apps-dependencies-are-compatible)

Try the default template [sample app](/reference/create-new-app), or choose any Flutter application that has been migrated to be [compatible with Wasm](#js-interop-wasm).

### Modify the index page

[#](#modify-the-index-page)

Make sure your app's `web/index.html` is updated to the latest [Flutter web app initialization](/platform-integration/web/initialization) for Flutter 3.22 and later.

If you would like to use the default, delete the contents of the `web/` directory and run the following command to regenerate them:

```
$ flutter create . --platforms web
```

### Run or build your app

[#](#run-or-build-your-app)

To run the app with Wasm for development or testing, use the `--wasm` flag with the `flutter run` command.

```
$ flutter run -d chrome --wasm
```

To build a web application with Wasm, add the `--wasm` flag to the existing `flutter build web` command.

```
$ flutter build web --wasm
```

The command produces output into the `build/web` directory relative to the package root, just like `flutter build web`.

#### Wasm production debugging

[#](#wasm-production-debugging)

By default, Wasm release builds strip debug symbols and omit source maps to minimize binary size.

* **For error monitoring (recommended)**: Pass `--source-maps` to generate a `main.dart.wasm.map` file for symbolication in error tracking services. For security recommendations and deployment warnings, see [Source maps](/deployment/web#source-maps).
* **For staging or QA builds**: Pass `--no-strip-wasm` to preserve Wasm function names directly in browser console stack traces, at the cost of an approximate **47% increase** in Wasm binary size.

```
# Generate source maps for production error tracking:
$ flutter build web --wasm --source-maps

# Preserve Wasm function names for staging/QA debugging:
$ flutter build web --wasm --no-strip-wasm
```

### Open the app in a compatible web browser

[#](#open-the-app-in-a-compatible-web-browser)

Even with the `--wasm` flag, Flutter will still compile the application to JavaScript. If WasmGC support is not detected at runtime, the JavaScript output is used so the application will continue to work in all major browsers.

You can verify whether the app is actually running with Wasm by checking for the `dart2wasm` environment variable, set during compilation (preferred).

dart

```
const isRunningWithWasm = bool.fromEnvironment('dart.tool.dart2wasm');
```

Alternatively, you can use differences in number representations to test whether the native (Wasm) number representation is used.

dart

```
final isRunningWithWasm = identical(double.nan, double.nan);
```

### Serve the built output with an HTTP server

[#](#serve-the-built-output-with-an-http-server)

Flutter web WebAssembly can use multiple threads to render your application faster, with less jank. To do this, Flutter uses advanced browser features that require specific HTTP response headers.

| Name                           | Value                              |
| ------------------------------ | ---------------------------------- |
| `Cross-Origin-Embedder-Policy` | `credentialless` or `require-corp` |
| `Cross-Origin-Opener-Policy`   | `same-origin`                      |

To learn more about these headers, check out [Load cross-origin resources without CORP headers using COEP: credentialless](https://developer.chrome.com/blog/coep-credentialless-origin-trial).

## Learn more about browser compatibility

[#](#learn-more-about-browser-compatibility)

To run a Flutter app that has been compiled to Wasm, you need a browser that supports [WasmGC](https://github.com/WebAssembly/gc/tree/main/proposals/gc).

[Chromium and V8](https://chromestatus.com/feature/6062715726462976) support WasmGC since version 119. Chrome on iOS uses WebKit, which doesn't yet [support WasmGC](https://bugs.webkit.org/show_bug.cgi?id=247394). Firefox announced stable support for WasmGC in Firefox 120, but currently doesn't work due to a known limitation (see details below).

* **Why not Firefox?** Firefox versions 120 and later were previously able to run Flutter/Wasm, but they're experiencing a bug that is blocking compatibility with Flutter's Wasm renderer. Follow [this bug](https://bugzilla.mozilla.org/show_bug.cgi?id=1788206) for details.
* **Why not Safari?** Safari now supports WasmGC, but is experiencing a similar bug that is blocking compatibility with Flutter's Wasm renderer. Follow [this bug](https://bugs.webkit.org/show_bug.cgi?id=267291) for details.

## Using compatible JS interop libraries

[#](#js-interop-wasm)

To support compilation to Wasm, Dart has changed how it enables interop with browser and JavaScript APIs. This prevents Dart code that uses `dart:html` or `package:js` from compiling to Wasm.

Instead, Dart now provides new, lightweight interop solutions built around static JS interop:

* [`package:web`](https://pub.dev/packages/web), which replaces `dart:html` (and other web libraries)
* [`dart:js_interop`](https://api.dart.dev/dart-js_interop/dart-js_interop-library.html), which replaces `package:js` and `dart:js`

For a detailed guide on migrating existing code, see the [`package:web` migration guide](https://dart.dev/interop/js-interop/package-web).

When compiling to Wasm, be aware of runtime differences in JS interop types (such as `is`/`as` type casts and Zone propagation in callbacks). For details, check out Dart's [JS interop types documentation](https://dart.dev/interop/js-interop/js-types#compatibility-type-checks-and-casts) and the [package:web Zones section](https://dart.dev/interop/js-interop/package-web#zones).

To learn more about JS interop in Dart, see Dart's [JS interop](https://dart.dev/interop/js-interop) documentation page.

### Diagnosing Wasm compilation errors

[#](#diagnosing-wasm-compilation-errors)

When compiling a Flutter web project with `--wasm`, if your application or its packages import unsupported web APIs (such as `dart:html` or `package:js`), the compilation will fail.

By default, the Dart Wasm compiler emits a clean, structured dependency chain tree, but this output can be buried in a long terminal exception stack trace.

#### 1. Perform early detection

[#](#1-perform-early-detection)

You can detect incompatibilities early with dry-run warnings. When you run `flutter build web` without the `--wasm` flag, a Wasm dry run is still performed automatically. If incompatibilities are found, you will see a non-fatal warning like this:

```
Wasm dry run failed:
Found incompatibilities with WebAssembly.

package:my_app/main.dart 1:1 - dart:html unsupported (0)
```

#### 2. Isolate the compiler dependency tree

[#](#2-isolate-the-compiler-dependency-tree)

If you run `flutter build web --wasm` and it fails, `flutter_tools` outputs a large exception stack trace (for example, `Target dart2wasm failed...`).

**Ignore the long stack trace and command string.** Instead, scroll up to the top of the error output to find the structured `Context` tree. This clean tree tells you exactly which package and file imported the unsupported library:

```
Context: The unavailable library 'dart:html' is imported through these packages:

    main.dart => package:my_app => dart:html

Detailed import paths for (some of) these imports:

    main.dart => package:my_app/main.dart => dart:html
```

#### 3. Migrate legacy imports

[#](#3-migrate-legacy-imports)

To fix these compilation errors, you must migrate from legacy JS interop packages to modern WebAssembly-compatible alternatives:

* Replace `dart:html` and other legacy web libraries with [`package:web`](https://pub.dev/packages/web).
* Replace `dart:js` and `package:js` with [`dart:js_interop`](https://api.dart.dev/dart-js_interop/dart-js_interop-library.html).

For comprehensive migration instructions, check out the [`package:web` migration guide on dart.dev](https://dart.dev/interop/js-interop/package-web) and Dart's [JS interop usage guide](https://dart.dev/interop/js-interop).

If you need to support both legacy and modern environments during your migration, use conditional imports by checking for `dart.library.js_interop`:

dart

```
import 'fallback.dart'
  if (dart.library.js) 'legacy_web_interop.dart'
  if (dart.library.js_interop) 'wasm_web_interop.dart';
```

To learn more about selecting implementations at compile time, see the [conditional imports documentation on dart.dev](https://dart.dev/interop/js-interop/package-web#conditional-imports).

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-08-18. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/platform-integration/web/wasm.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/platform-integration/web/wasm\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/platform-integration/web/wasm.md "Report an issue with this page").

----
url: https://docs.flutter.dev/testing/overview
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Testing Flutter apps](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

The more features your app has, the harder it is to test manually. Automated tests help ensure that your app performs correctly before you publish it, while retaining your feature and bug fix velocity.

Automated testing falls into a few categories:

* A [*unit test*](#unit-tests) tests a single function, method, or class.
* A [*widget test*](#widget-tests) (in other UI frameworks referred to as *component test*) tests a single widget.
* An [*integration test*](#integration-tests) tests a complete app or a large part of an app.

Generally speaking, a well-tested app has many unit and widget tests, tracked by [code coverage](https://en.wikipedia.org/wiki/Code_coverage), plus enough integration tests to cover all the important use cases. This advice is based on the fact that there are trade-offs between different kinds of testing, seen below.

| Tradeoff             | Unit  | Widget | Integration |
| -------------------- | ----- | ------ | ----------- |
| **Confidence**       | Low   | Higher | Highest     |
| **Maintenance cost** | Low   | Higher | Highest     |
| **Dependencies**     | Few   | More   | Most        |
| **Execution speed**  | Quick | Quick  | Slow        |

## Unit tests

[#](#unit-tests)

A *unit test* tests a single function, method, or class. The goal of a unit test is to verify the correctness of a unit of logic under a variety of conditions. External dependencies of the unit under test are generally [mocked out](/cookbook/testing/unit/mocking). Unit tests generally don't read from or write to disk, render to screen, or receive user actions from outside the process running the test. For more information regarding unit tests, you can view the following recipes or run `flutter test --help` in your terminal.

### Recipes

[#](#recipes)

* [Introduction to unit testing](/cookbook/testing/unit/introduction)
* [Mock dependencies using Mockito](/cookbook/testing/unit/mocking)

## Widget tests

[#](#widget-tests)

A *widget test* (in other UI frameworks referred to as *component test*) tests a single widget. The goal of a widget test is to verify that the widget's UI looks and interacts as expected. Testing a widget involves multiple classes and requires a test environment that provides the appropriate widget lifecycle context.

For example, the Widget being tested should be able to receive and respond to user actions and events, perform layout, and instantiate child widgets. A widget test is therefore more comprehensive than a unit test. However, like a unit test, a widget test's environment is replaced with an implementation much simpler than a full-blown UI system.

### Recipes

[#](#recipes-1)

* [Introduction to widget testing](/cookbook/testing/widget/introduction)
* [Find widgets using finders](/cookbook/testing/widget/finders)
* [Handling scrolling in widget tests](/cookbook/testing/widget/scrolling)
* [Tap, drag, and enter text in widget tests](/cookbook/testing/widget/tap-drag)
* [Test different orientations](/cookbook/testing/widget/orientation)

## Integration tests

[#](#integration-tests)

An *integration test* tests a complete app or a large part of an app. The goal of an integration test is to verify that all the widgets and services being tested work together as expected. Furthermore, you can use integration tests to verify your app's performance.

Generally, an *integration test* runs on a real device or an OS emulator, such as iOS Simulator or Android Emulator. The app under test is typically isolated from the test driver code to avoid skewing the results.

The Flutter SDK includes the [`integration_test`](https://github.com/flutter/flutter/tree/main/packages/integration_test) package. However, this package can't interact with native platform UI, such as permission dialogs, notifications, or platform views. For apps that need native interactions, you can use the [`patrol`](https://pub.dev/packages/patrol) package, an open-source framework that extends Flutter's testing capabilities with native platform support.

For more information on how to write integration tests, see the [integration testing page](/cookbook/testing/integration/introduction).

### Recipes

[#](#recipes-2)

* [Integration testing concepts](/cookbook/testing/integration/introduction)
* [Write and run a Flutter integration test](/testing/integration-tests)
* [Write and run a Patrol integration test](https://patrol.leancode.co/documentation/write-your-first-test)
* [Measure performance with an integration test](/cookbook/testing/integration/profiling)

## Continuous integration services

[#](#continuous-integration-services)

Continuous integration (CI) services allow you to run your tests automatically when pushing new code changes. This provides timely feedback on whether the code changes work as expected and do not introduce bugs.

For information on running tests on various continuous integration services, see the following:

* [Continuous delivery using fastlane with Flutter](/deployment/cd#fastlane)
* [Test Flutter apps on Appcircle](https://blog.appcircle.io/article/flutter-ci-cd-github-ios-android-web#)
* [Test Flutter apps on Travis](https://blog.flutter.dev/test-flutter-apps-on-travis-3fd5142ecd8c)
* [Test Flutter apps on Cirrus](https://cirrus-ci.org/examples/#flutter)
* [Codemagic CI/CD for Flutter](https://blog.codemagic.io/getting-started-with-codemagic/)
* [Codemagic CI/CD for Patrol](https://docs.codemagic.io/integrations/patrol-integration/)
* [Flutter CI/CD with Bitrise](https://devcenter.bitrise.io/en/getting-started/quick-start-guides/getting-started-with-flutter-apps)

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/testing/overview.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/testing/overview\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/testing/overview.md "Report an issue with this page").

----
url: https://docs.flutter.dev/ai/best-practices/structure-output.md
----

\# Structure & output > Learn how to use structured input and output schemas to receive reliable, parsable JSON data from an LLM. When you're writing programs against an LLM, you want to provide unambiguous input and get unambiguous output. ### Structured input As input, an LLM can take pretty much anything you can render as text. That includes free form text and semi-structured text like Markdown, but also includes structured formats like CSV, JSON, and XML. If you have data with structure, format the data with that structure and the LLM is going to give you better results. In addition to structured text input, you can also pass binary data, like images or PDFs. In the sample, the app passes the crossword puzzle screenshot images to Gemini for it to infer the grid data: \`\`\`dart final imageParts = \[]; for (final image in images) { final imageBytes = await image.readAsBytes(); final mimeType = lookupMimeType(image.path, headerBytes: imageBytes)!; imageParts.add(InlineDataPart(mimeType, imageBytes)); } final content = \[ Content.multi(\[ TextPart(\_crosswordPrompt), ...imageParts, ]), ]; final response = await \_crosswordModel.generateContent(content); ... \`\`\` This code passes the prompt and the images to Gemini as part of the same request. ### Structured output An LLM can have a harder time with structured output than with structured input. You want to be clear and thorough when asking the model for JSON output to ensure you get something that you can reliably parse in your apps. Start by initializing the model instance with your expected output format: \`\`\`dart // the schema for the clue solver output static final \_crosswordSchema = Schema( SchemaType.object, properties: { 'width': Schema(SchemaType.integer), 'height': Schema(SchemaType.integer), 'grid': Schema( SchemaType.array, items: Schema( SchemaType.array, items: Schema( SchemaType.object, properties: { 'color': Schema(SchemaType.string), 'clueNumber': Schema(SchemaType.integer, nullable: true), }, ), ), ), 'clues': Schema( SchemaType.object, properties: { 'across': Schema( SchemaType.array, items: Schema( SchemaType.object, properties: { 'number': Schema(SchemaType.integer), 'text': Schema(SchemaType.string), }, ), ), 'down': Schema( SchemaType.array, items: Schema( SchemaType.object, properties: { 'number': Schema(SchemaType.integer), 'text': Schema(SchemaType.string), }, ), ), }, ), }, ); // The model for inferring crossword data from images. \_crosswordModel = FirebaseAI.googleAI().generativeModel( model: 'gemini-2.5-pro', generationConfig: GenerationConfig( responseMimeType: 'application/json', responseSchema: \_crosswordSchema, ), ); \`\`\` And while this might be enough, the most reliable results come when you also specify the output schema in the system instruction: \`\`\`dart final \_crosswordPrompt = ''' Analyze the following crossword puzzle images and return a JSON object representing the grid size, contents, and clues. The images may contain different parts of the same puzzle (e.g., the grid the across clues, the down clues). Combine them to form a complete puzzle. The JSON schema is as follows: ${jsonEncode(\_crosswordSchema.toJson())} ''' \`\`\` Now you can parse the model's text response as JSON: \`\`\`dart final response = await \_crosswordModel.generateContent(content); final json = jsonDecode(response.text!); final width = json\['width'] as int; final height = json\['height'] as int; final gridData = json\['grid'] as List; final cluesData = json\['clues'] as Map; ... \`\`\` Reliable JSON output from the model is what makes it possible to integrate AI into your app. The data might or might not be correct, but it will be in a format your app can work with.

----
url: https://docs.flutter.dev/ui/accessibility.md
----

\# Accessibility > Information on Flutter's accessibility support. ## Background Ensuring that apps are accessible to a broad range of users is an essential part of building a high-quality app. Applications that are poorly designed create barriers to people of all ages. The \[UN Convention on the Rights of Persons with Disabilities]\[CRPD] states the moral and legal imperative to ensure universal access to information systems; countries around the world enforce accessibility as a requirement; and companies recognize the business advantages of maximizing access to their services. We strongly encourage you to include an accessibility checklist as a key criteria before shipping your app. Flutter is committed to supporting developers in making their apps more accessible, and includes first-class framework support for accessibility in addition to that provided by the underlying operating system, including: \[UI Design and styling]\[] \[Assistive Technologies (Screen Reader) supports]\[] \[UI Design and styling]: /ui/accessibility/ui-design-and-styling \[Assistive Technologies (Screen Reader) supports]:/ui/accessibility/assistive-technologies ## Accessibility regulations Accessibility standards and regulations help ensure that products are accessible to people with disabilities. Many of these have been enacted into laws and policies, making them requirements for products and services. \* \*\*WCAG 2\*\*: \[Web Content Accessibility Guidelines (WCAG) 2]\[] is an internationally recognized standard for making web content more accessible to people with disabilities. It is a stable, technical standard developed by the World Wide Web Consortium (W3C). \* \*\*EN 301 549\*\*: \[EN 301 549]\[] is the European harmonized standard for accessibility requirements for Information and Communication Technology (ICT) products and services. \* \*\*VPAT\*\*: The \[Voluntary Product Accessibility Template (VPAT)]\[] is a free template that translates accessibility requirements and standards into actionable testing criteria for products and services. Laws around the world require digital content and services to be accessible to people with disabilities. In the U.S., the \[Americans with Disabilities Act (ADA)]\[] prohibits discrimination in public accommodations. \[Section 508 of the Rehabilitation Act ]\[] requires federal agencies and their contractors to meet WCAG standards for all ICT. In the EU, the \[European Accessibility Act (EAA)]\[] requires a wide range of public and private sector services to be accessible, primarily using the \[EN 301 549]\[] as its technical basis. \[Web Content Accessibility Guidelines (WCAG) 2]: https\://www\.w3.org/WAI/standards-guidelines/wcag/ \[EN 301 549]: https\://www\.etsi.org/deliver/etsi\_en/301500\_301599/301549/03.02.01\_60/en\_301549v030201p.pdf \[Voluntary Product Accessibility Template (VPAT)]: https\://www\.itic.org/policy/accessibility/vpat \[Americans with Disabilities Act (ADA)]: https\://www\.ada.gov/ \[Section 508 of the Rehabilitation Act]: https\://www\.section508.gov/ \[European Accessibility Act (EAA)]: https\://commission.europa.eu/strategy-and-policy/policies/justice-and-fundamental-rights/disability/union-equality-strategy-rights-persons-disabilities-2021-2030/european-accessibility-act\_en ## Building with accessibility in mind Ensuring that your app can be used by everyone means building accessibility into it from the start. For some apps, that's easier said than done. In the video below, two of our engineers take a mobile app from a dire accessibility state to one that takes advantage of Flutter's built-in widgets to offer a dramatically more accessible experience. ## Accessibility release checklist Here is a non-exhaustive list of things to consider as you prepare your app for release. \* \*\*Active interactions\*\*. Ensure that all active interactions do something. Any button that can be pushed should do something when pushed. For example, if you have a no-op callback for an \`onPressed\` event, change it to show a \`SnackBar\` on the screen explaining which control you just pushed. \* \*\*Screen reader testing\*\*. The screen reader should be able to describe all controls on the page when you tap on them, and the descriptions should be intelligible. Test your app with \[TalkBack]\[] (Android) and \[VoiceOver]\[] (iOS). \* \*\*Contrast ratios\*\*. We encourage you to have a contrast ratio of at least 4.5:1 between controls or text and the background, with the exception of disabled components. Images should also be vetted for sufficient contrast. \* \*\*Context switching\*\*. Nothing should change the user's context automatically while typing in information. Generally, the widgets should avoid changing the user's context without some sort of confirmation action. \* \*\*Tappable targets\*\*. All tappable targets should be at least 48x48 pixels. \* \*\*Errors\*\*. Important actions should be able to be undone. In fields that show errors, suggest a correction if possible. \* \*\*Color vision deficiency testing\*\*. Controls should be usable and legible in colorblind and grayscale modes. \* \*\*Scale factors\*\*. The UI should remain legible and usable at very large scale factors for text size and display scaling. \[TalkBack]: https\://support.google.com/accessibility/android/answer/6283677?hl=en \[VoiceOver]: https\://www\.apple.com/lae/accessibility/iphone/vision/ ## Learn more To learn more about Flutter and accessibility, check out the following articles written by community members: \* \[A deep dive into Flutter's accessibility widgets]\[] \* \[Flutter: Crafting a great experience for screen readers]\[] \[A deep dive into Flutter's accessibility widgets]: https\://medium.com/flutter-community/a-deep-dive-into-flutters-accessibility-widgets-eb0ef9455bc \[CRPD]: https\://social.desa.un.org/issues/disability/crpd/article-9-accessibility \[Flutter: Crafting a great experience for screen readers]: https\://blog.gskinner.com/archives/2022/09/flutter-crafting-a-great-experience-for-screen-readers.html

----
url: https://docs.flutter.dev/cookbook/effects/parallax-scrolling
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Create a scrolling parallax effect](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

When you scroll a list of cards (containing images, for example) in an app, you might notice that those images appear to scroll more slowly than the rest of the screen. It almost looks as if the cards in the list are in the foreground, but the images themselves sit far off in the distant background. This effect is known as parallax.

In this recipe, you create the parallax effect by building a list of cards (with rounded corners containing some text). Each card also contains an image. As the cards slide up the screen, the images within each card slide down.

The following animation shows the app's behavior:

## Create a list to hold the parallax items

[#](#create-a-list-to-hold-the-parallax-items)

To display a list of parallax scrolling images, you must first display a list.

Create a new stateless widget called `ParallaxRecipe`. Within `ParallaxRecipe`, build a widget tree with a `SingleChildScrollView` and a `Column`, which forms a list.

dart

```
class ParallaxRecipe extends StatelessWidget {
  const ParallaxRecipe({super.key});
​
  @override
  Widget build(BuildContext context) {
    return const SingleChildScrollView(child: Column(children: []));
  }
}
```

## Display items with text and a static image

[#](#display-items-with-text-and-a-static-image)

Each list item displays a rounded-rectangle background image, representing one of seven locations in the world. Stacked on top of that background image is the name of the location and its country, positioned in the lower left. Between the background image and the text is a dark gradient, which improves the legibility of the text against the background.

Implement a stateless widget called `LocationListItem` that consists of the previously mentioned visuals. For now, use a static `Image` widget for the background. Later, you'll replace that widget with a parallax version.

dart

```
@immutable
class LocationListItem extends StatelessWidget {
  const LocationListItem({
    super.key,
    required this.imageUrl,
    required this.name,
    required this.country,
  });
​
  final String imageUrl;
  final String name;
  final String country;
​
  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16),
      child: AspectRatio(
        aspectRatio: 16 / 9,
        child: ClipRRect(
          borderRadius: BorderRadius.circular(16),
          child: Stack(
            children: [
              _buildParallaxBackground(context),
              _buildGradient(),
              _buildTitleAndSubtitle(),
            ],
          ),
        ),
      ),
    );
  }
​
  Widget _buildParallaxBackground(BuildContext context) {
    return Positioned.fill(child: Image.network(imageUrl, fit: BoxFit.cover));
  }
​
  Widget _buildGradient() {
    return Positioned.fill(
      child: DecoratedBox(
        decoration: BoxDecoration(
          gradient: LinearGradient(
            colors: [Colors.transparent, Colors.black.withValues(alpha: 0.7)],
            begin: Alignment.topCenter,
            end: Alignment.bottomCenter,
            stops: const [0.6, 0.95],
          ),
        ),
      ),
    );
  }
​
  Widget _buildTitleAndSubtitle() {
    return Positioned(
      left: 20,
      bottom: 20,
      child: Column(
        mainAxisSize: MainAxisSize.min,
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Text(
            name,
            style: const TextStyle(
              color: Colors.white,
              fontSize: 20,
              fontWeight: FontWeight.bold,
            ),
          ),
          Text(
            country,
            style: const TextStyle(color: Colors.white, fontSize: 14),
          ),
        ],
      ),
    );
  }
}
```

Next, add the list items to your `ParallaxRecipe` widget.

dart

```
class ParallaxRecipe extends StatelessWidget {
  const ParallaxRecipe({super.key});
​
  @override
  Widget build(BuildContext context) {
    return SingleChildScrollView(
      child: Column(
        children: [
          for (final location in locations)
            LocationListItem(
              imageUrl: location.imageUrl,
              name: location.name,
              country: location.place,
            ),
        ],
      ),
    );
  }
}
```

You now have a typical, scrollable list of cards that displays seven unique locations in the world. In the next step, you add a parallax effect to the background image.

## Implement the parallax effect

[#](#implement-the-parallax-effect)

A parallax scrolling effect is achieved by slightly pushing the background image in the opposite direction of the rest of the list. As the list items slide up the screen, each background image slides slightly downward. Conversely, as the list items slide down the screen, each background image slides slightly upward. Visually, this results in parallax.

The parallax effect depends on the list item's current position within its ancestor `Scrollable`. As the list item's scroll position changes, the position of the list item's background image must also change. This is an interesting problem to solve. The position of a list item within the `Scrollable` isn't available until Flutter's layout phase is complete. This means that the position of the background image must be determined in the paint phase, which comes after the layout phase. Fortunately, Flutter provides a widget called `Flow`, which is specifically designed to give you control over the transform of a child widget immediately before the widget is painted. In other words, you can intercept the painting phase and take control to reposition your child widgets however you want.

Wrap your background `Image` widget with a [`Flow`](https://api.flutter.dev/flutter/widgets/Flow-class.html) widget.

dart

```
Widget _buildParallaxBackground(BuildContext context) {
  return Flow(
    children: [Image.network(imageUrl, fit: BoxFit.cover)],
  );
}
```

Introduce a new `FlowDelegate` called `ParallaxFlowDelegate`.

dart

```
Widget _buildParallaxBackground(BuildContext context) {
  return Flow(
    delegate: ParallaxFlowDelegate(),
    children: [Image.network(imageUrl, fit: BoxFit.cover)],
  );
}
```

dart

```
class ParallaxFlowDelegate extends FlowDelegate {
  ParallaxFlowDelegate();
​
  @override
  BoxConstraints getConstraintsForChild(int i, BoxConstraints constraints) {
    // TODO: We'll add more to this later.
  }
​
  @override
  void paintChildren(FlowPaintingContext context) {
    // TODO: We'll add more to this later.
  }
​
  @override
  bool shouldRepaint(covariant FlowDelegate oldDelegate) {
    // TODO: We'll add more to this later.
    return true;
  }
}
```

A `FlowDelegate` controls how its children are sized and where those children are painted. In this case, your `Flow` widget has only one child: the background image. That image must be exactly as wide as the `Flow` widget.

Return tight width constraints for your background image child.

dart

```
@override
BoxConstraints getConstraintsForChild(int i, BoxConstraints constraints) {
  return BoxConstraints.tightFor(width: constraints.maxWidth);
}
```

Your background images are now sized appropriately, but you still need to calculate the vertical position of each background image based on its scroll position, and then paint it.

There are three critical pieces of information that you need to compute the desired position of a background image:

* The bounds of the ancestor `Scrollable`
* The bounds of the individual list item
* The size of the image after it's scaled down to fit in the list item

To look up the bounds of the `Scrollable`, you pass a `ScrollableState` into your `FlowDelegate`.

To look up the bounds of your individual list item, pass your list item's `BuildContext` into your `FlowDelegate`.

To look up the final size of your background image, assign a `GlobalKey` to your `Image` widget, and then you pass that `GlobalKey` into your `FlowDelegate`.

Make this information available to `ParallaxFlowDelegate`.

dart

```
@immutable
class LocationListItem extends StatelessWidget {
  final GlobalKey _backgroundImageKey = GlobalKey();
​
  Widget _buildParallaxBackground(BuildContext context) {
    return Flow(
      delegate: ParallaxFlowDelegate(
        scrollable: Scrollable.of(context),
        listItemContext: context,
        backgroundImageKey: _backgroundImageKey,
      ),
      children: [
        Image.network(imageUrl, key: _backgroundImageKey, fit: BoxFit.cover),
      ],
    );
  }
}
```

dart

```
class ParallaxFlowDelegate extends FlowDelegate {
  ParallaxFlowDelegate({
    required this.scrollable,
    required this.listItemContext,
    required this.backgroundImageKey,
  });
​
  final ScrollableState scrollable;
  final BuildContext listItemContext;
  final GlobalKey backgroundImageKey;
}
```

Having all the information needed to implement parallax scrolling, implement the `shouldRepaint()` method.

dart

```
@override
bool shouldRepaint(ParallaxFlowDelegate oldDelegate) {
  return scrollable != oldDelegate.scrollable ||
      listItemContext != oldDelegate.listItemContext ||
      backgroundImageKey != oldDelegate.backgroundImageKey;
}
```

Now, implement the layout calculations for the parallax effect.

First, calculate the pixel position of a list item within its ancestor `Scrollable`.

dart

```
@override
void paintChildren(FlowPaintingContext context) {
  // Calculate the position of this list item within the viewport.
  final scrollableBox = scrollable.context.findRenderObject() as RenderBox;
  final listItemBox = listItemContext.findRenderObject() as RenderBox;
  final listItemOffset = listItemBox.localToGlobal(
    listItemBox.size.centerLeft(Offset.zero),
    ancestor: scrollableBox,
  );
}
```

Use the pixel position of the list item to calculate its percentage from the top of the `Scrollable`. A list item at the top of the scrollable area should produce 0%, and a list item at the bottom of the scrollable area should produce 100%.

dart

```
@override
void paintChildren(FlowPaintingContext context) {
  // Calculate the position of this list item within the viewport.
  final scrollableBox = scrollable.context.findRenderObject() as RenderBox;
  final listItemBox = listItemContext.findRenderObject() as RenderBox;
  final listItemOffset = listItemBox.localToGlobal(
    listItemBox.size.centerLeft(Offset.zero),
    ancestor: scrollableBox,
  );
​
  // Determine the percent position of this list item within the
  // scrollable area.
  final viewportDimension = scrollable.position.viewportDimension;
  final scrollFraction = (listItemOffset.dy / viewportDimension).clamp(
    0.0,
    1.0,
  );
  // ···
}
```

Use the scroll percentage to calculate an `Alignment`. At 0%, you want `Alignment(0.0, -1.0)`, and at 100%, you want `Alignment(0.0, 1.0)`. These coordinates correspond to top and bottom alignment, respectively.

dart

```
@override
void paintChildren(FlowPaintingContext context) {
  // Calculate the position of this list item within the viewport.
  final scrollableBox = scrollable.context.findRenderObject() as RenderBox;
  final listItemBox = listItemContext.findRenderObject() as RenderBox;
  final listItemOffset = listItemBox.localToGlobal(
    listItemBox.size.centerLeft(Offset.zero),
    ancestor: scrollableBox,
  );
​
  // Determine the percent position of this list item within the
  // scrollable area.
  final viewportDimension = scrollable.position.viewportDimension;
  final scrollFraction = (listItemOffset.dy / viewportDimension).clamp(
    0.0,
    1.0,
  );
​
  // Calculate the vertical alignment of the background
  // based on the scroll percent.
  final verticalAlignment = Alignment(0.0, scrollFraction * 2 - 1);
}
```

Use `verticalAlignment`, along with the size of the list item and the size of the background image, to produce a `Rect` that determines where the background image should be positioned.

dart

```
@override
void paintChildren(FlowPaintingContext context) {
  // Calculate the position of this list item within the viewport.
  final scrollableBox = scrollable.context.findRenderObject() as RenderBox;
  final listItemBox = listItemContext.findRenderObject() as RenderBox;
  final listItemOffset = listItemBox.localToGlobal(
    listItemBox.size.centerLeft(Offset.zero),
    ancestor: scrollableBox,
  );
​
  // Determine the percent position of this list item within the
  // scrollable area.
  final viewportDimension = scrollable.position.viewportDimension;
  final scrollFraction = (listItemOffset.dy / viewportDimension).clamp(
    0.0,
    1.0,
  );
​
  // Calculate the vertical alignment of the background
  // based on the scroll percent.
  final verticalAlignment = Alignment(0.0, scrollFraction * 2 - 1);
​
  // Convert the background alignment into a pixel offset for
  // painting purposes.
  final backgroundSize =
      (backgroundImageKey.currentContext!.findRenderObject() as RenderBox)
          .size;
  final listItemSize = context.size;
  final childRect = verticalAlignment.inscribe(
    backgroundSize,
    Offset.zero & listItemSize,
  );
}
```

Using `childRect`, paint the background image with the desired translation transformation. It's this transformation over time that gives you the parallax effect.

dart

```
@override
void paintChildren(FlowPaintingContext context) {
  // Calculate the position of this list item within the viewport.
  final scrollableBox = scrollable.context.findRenderObject() as RenderBox;
  final listItemBox = listItemContext.findRenderObject() as RenderBox;
  final listItemOffset = listItemBox.localToGlobal(
    listItemBox.size.centerLeft(Offset.zero),
    ancestor: scrollableBox,
  );
​
  // Determine the percent position of this list item within the
  // scrollable area.
  final viewportDimension = scrollable.position.viewportDimension;
  final scrollFraction = (listItemOffset.dy / viewportDimension).clamp(
    0.0,
    1.0,
  );
​
  // Calculate the vertical alignment of the background
  // based on the scroll percent.
  final verticalAlignment = Alignment(0.0, scrollFraction * 2 - 1);
​
  // Convert the background alignment into a pixel offset for
  // painting purposes.
  final backgroundSize =
      (backgroundImageKey.currentContext!.findRenderObject() as RenderBox)
          .size;
  final listItemSize = context.size;
  final childRect = verticalAlignment.inscribe(
    backgroundSize,
    Offset.zero & listItemSize,
  );
​
  // Paint the background.
  context.paintChild(
    0,
    transform: Transform.translate(offset: Offset(0.0, childRect.top))
        .transform,
  );
}
```

You need one final detail to achieve the parallax effect. The `ParallaxFlowDelegate` repaints when the inputs change, but the `ParallaxFlowDelegate` doesn't repaint every time the scroll position changes.

Pass the `ScrollableState`'s `ScrollPosition` to the `FlowDelegate` superclass so that the `FlowDelegate` repaints every time the `ScrollPosition` changes.

dart

```
class ParallaxFlowDelegate extends FlowDelegate {
  ParallaxFlowDelegate({
    required this.scrollable,
    required this.listItemContext,
    required this.backgroundImageKey,
  }) : super(repaint: scrollable.position);
```

Congratulations! You now have a list of cards with parallax, scrolling background images.

## Interactive example

[#](#interactive-example)

Run the app:

* Scroll up and down to observe the parallax effect.

```
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';

const Color darkBlue = Color.fromARGB(255, 18, 32, 47);

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData.dark().copyWith(scaffoldBackgroundColor: darkBlue),
      debugShowCheckedModeBanner: false,
      home: const Scaffold(body: Center(child: ExampleParallax())),
    );
  }
}

class ExampleParallax extends StatelessWidget {
  const ExampleParallax({super.key});

  @override
  Widget build(BuildContext context) {
    return SingleChildScrollView(
      child: Column(
        children: [
          for (final location in locations)
            LocationListItem(
              imageUrl: location.imageUrl,
              name: location.name,
              country: location.place,
            ),
        ],
      ),
    );
  }
}

class LocationListItem extends StatelessWidget {
  LocationListItem({
    super.key,
    required this.imageUrl,
    required this.name,
    required this.country,
  });

  final String imageUrl;
  final String name;
  final String country;
  final GlobalKey _backgroundImageKey = GlobalKey();

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16),
      child: AspectRatio(
        aspectRatio: 16 / 9,
        child: ClipRRect(
          borderRadius: BorderRadius.circular(16),
          child: Stack(
            children: [
              _buildParallaxBackground(context),
              _buildGradient(),
              _buildTitleAndSubtitle(),
            ],
          ),
        ),
      ),
    );
  }

  Widget _buildParallaxBackground(BuildContext context) {
    return Flow(
      delegate: ParallaxFlowDelegate(
        scrollable: Scrollable.of(context),
        listItemContext: context,
        backgroundImageKey: _backgroundImageKey,
      ),
      children: [
        Image.network(imageUrl, key: _backgroundImageKey, fit: BoxFit.cover),
      ],
    );
  }

  Widget _buildGradient() {
    return Positioned.fill(
      child: DecoratedBox(
        decoration: BoxDecoration(
          gradient: LinearGradient(
            colors: [Colors.transparent, Colors.black.withValues(alpha: 0.7)],
            begin: Alignment.topCenter,
            end: Alignment.bottomCenter,
            stops: const [0.6, 0.95],
          ),
        ),
      ),
    );
  }

  Widget _buildTitleAndSubtitle() {
    return Positioned(
      left: 20,
      bottom: 20,
      child: Column(
        mainAxisSize: MainAxisSize.min,
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Text(
            name,
            style: const TextStyle(
              color: Colors.white,
              fontSize: 20,
              fontWeight: FontWeight.bold,
            ),
          ),
          Text(
            country,
            style: const TextStyle(color: Colors.white, fontSize: 14),
          ),
        ],
      ),
    );
  }
}

class ParallaxFlowDelegate extends FlowDelegate {
  ParallaxFlowDelegate({
    required this.scrollable,
    required this.listItemContext,
    required this.backgroundImageKey,
  }) : super(repaint: scrollable.position);

  final ScrollableState scrollable;
  final BuildContext listItemContext;
  final GlobalKey backgroundImageKey;

  @override
  BoxConstraints getConstraintsForChild(int i, BoxConstraints constraints) {
    return BoxConstraints.tightFor(width: constraints.maxWidth);
  }

  @override
  void paintChildren(FlowPaintingContext context) {
    // Calculate the position of this list item within the viewport.
    final scrollableBox = scrollable.context.findRenderObject() as RenderBox;
    final listItemBox = listItemContext.findRenderObject() as RenderBox;
    final listItemOffset = listItemBox.localToGlobal(
      listItemBox.size.centerLeft(Offset.zero),
      ancestor: scrollableBox,
    );

    // Determine the percent position of this list item within the
    // scrollable area.
    final viewportDimension = scrollable.position.viewportDimension;
    final scrollFraction = (listItemOffset.dy / viewportDimension).clamp(
      0.0,
      1.0,
    );

    // Calculate the vertical alignment of the background
    // based on the scroll percent.
    final verticalAlignment = Alignment(0.0, scrollFraction * 2 - 1);

    // Convert the background alignment into a pixel offset for
    // painting purposes.
    final backgroundSize =
        (backgroundImageKey.currentContext!.findRenderObject() as RenderBox)
            .size;
    final listItemSize = context.size;
    final childRect = verticalAlignment.inscribe(
      backgroundSize,
      Offset.zero & listItemSize,
    );

    // Paint the background.
    context.paintChild(
      0,
      transform: Transform.translate(offset: Offset(0.0, childRect.top))
          .transform,
    );
  }

  @override
  bool shouldRepaint(ParallaxFlowDelegate oldDelegate) {
    return scrollable != oldDelegate.scrollable ||
        listItemContext != oldDelegate.listItemContext ||
        backgroundImageKey != oldDelegate.backgroundImageKey;
  }
}

class Parallax extends SingleChildRenderObjectWidget {
  const Parallax({super.key, required Widget background})
    : super(child: background);

  @override
  RenderObject createRenderObject(BuildContext context) {
    return RenderParallax(scrollable: Scrollable.of(context));
  }

  @override
  void updateRenderObject(
    BuildContext context,
    covariant RenderParallax renderObject,
  ) {
    renderObject.scrollable = Scrollable.of(context);
  }
}

class ParallaxParentData extends ContainerBoxParentData<RenderBox> {}

class RenderParallax extends RenderBox
    with RenderObjectWithChildMixin<RenderBox>, RenderProxyBoxMixin {
  RenderParallax({required this._scrollable});

  ScrollableState _scrollable;

  ScrollableState get scrollable => _scrollable;

  set scrollable(ScrollableState value) {
    if (value != _scrollable) {
      if (attached) {
        _scrollable.position.removeListener(markNeedsLayout);
      }
      _scrollable = value;
      if (attached) {
        _scrollable.position.addListener(markNeedsLayout);
      }
    }
  }

  @override
  void attach(covariant PipelineOwner owner) {
    super.attach(owner);
    _scrollable.position.addListener(markNeedsLayout);
  }

  @override
  void detach() {
    _scrollable.position.removeListener(markNeedsLayout);
    super.detach();
  }

  @override
  void setupParentData(covariant RenderObject child) {
    if (child.parentData is! ParallaxParentData) {
      child.parentData = ParallaxParentData();
    }
  }

  @override
  void performLayout() {
    size = constraints.biggest;

    // Force the background to take up all available width
    // and then scale its height based on the image's aspect ratio.
    final background = child!;
    final backgroundImageConstraints = BoxConstraints.tightFor(
      width: size.width,
    );
    background.layout(backgroundImageConstraints, parentUsesSize: true);

    // Set the background's local offset, which is zero.
    (background.parentData as ParallaxParentData).offset = Offset.zero;
  }

  @override
  void paint(PaintingContext context, Offset offset) {
    // Get the size of the scrollable area.
    final viewportDimension = scrollable.position.viewportDimension;

    // Calculate the global position of this list item.
    final scrollableBox = scrollable.context.findRenderObject() as RenderBox;
    final backgroundOffset = localToGlobal(
      size.centerLeft(Offset.zero),
      ancestor: scrollableBox,
    );

    // Determine the percent position of this list item within the
    // scrollable area.
    final scrollFraction = (backgroundOffset.dy / viewportDimension).clamp(
      0.0,
      1.0,
    );

    // Calculate the vertical alignment of the background
    // based on the scroll percent.
    final verticalAlignment = Alignment(0.0, scrollFraction * 2 - 1);

    // Convert the background alignment into a pixel offset for
    // painting purposes.
    final background = child!;
    final backgroundSize = background.size;
    final listItemSize = size;
    final childRect = verticalAlignment.inscribe(
      backgroundSize,
      Offset.zero & listItemSize,
    );

    // Paint the background.
    context.paintChild(
      background,
      (background.parentData as ParallaxParentData).offset +
          offset +
          Offset(0.0, childRect.top),
    );
  }
}

class Location {
  const Location({
    required this.name,
    required this.place,
    required this.imageUrl,
  });

  final String name;
  final String place;
  final String imageUrl;
}

const urlPrefix =
    'https://docs.flutter.dev/assets/images/exercise/effects/parallax';

const locations = [
  Location(
    name: 'Mount Rushmore',
    place: 'U.S.A',
    imageUrl: '$urlPrefix/01-mount-rushmore.jpg',
  ),
  Location(
    name: 'Gardens By The Bay',
    place: 'Singapore',
    imageUrl: '$urlPrefix/02-singapore.jpg',
  ),
  Location(
    name: 'Machu Picchu',
    place: 'Peru',
    imageUrl: '$urlPrefix/03-machu-picchu.jpg',
  ),
  Location(
    name: 'Vitznau',
    place: 'Switzerland',
    imageUrl: '$urlPrefix/04-vitznau.jpg',
  ),
  Location(
    name: 'Bali',
    place: 'Indonesia',
    imageUrl: '$urlPrefix/05-bali.jpg',
  ),
  Location(
    name: 'Mexico City',
    place: 'Mexico',
    imageUrl: '$urlPrefix/06-mexico-city.jpg',
  ),
  Location(name: 'Cairo', place: 'Egypt', imageUrl: '$urlPrefix/07-cairo.jpg'),
];
```

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-08-24. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/effects/parallax-scrolling.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/cookbook/effects/parallax-scrolling\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/cookbook/effects/parallax-scrolling.md "Report an issue with this page").

----
url: https://docs.flutter.dev/reference/flutter-cli.md
----

\# flutter: The Flutter command-line tool > The reference page for using 'flutter' in a terminal window. The \`flutter\` command-line tool is how developers (or IDEs on behalf of developers) interact with Flutter. For Dart related commands, you can use the \[\`dart\`]\[] command-line tool. Here's how you might use the \`flutter\` tool to create, analyze, test, and run an app: \`\`\`console $ flutter create my\_app $ cd my\_app $ flutter analyze $ flutter test $ flutter run lib/main.dart \`\`\` To run \[\`pub\`]\[\`dart pub\`] commands using the \`flutter\` tool: \`\`\`console $ flutter pub get $ flutter pub outdated $ flutter pub upgrade \`\`\` To view all commands that \`flutter\` supports: \`\`\`console $ flutter --help --verbose \`\`\` To get the current version of the Flutter SDK, including its framework, engine, and tools: \`\`\`console $ flutter --version \`\`\` ## \`flutter\` commands The following table shows which commands you can use with the \`flutter\` tool: | Command | Example of use | More information | |-----------------|------------------------------------------------|-----------------------------------------------------------------------------------| | analyze | \`flutter analyze -d \` | Analyzes the project's Dart source code.\
Use instead of \[\`dart analyze\`]\[]. | | assemble | \`flutter assemble -o \` | Assemble and build flutter resources. | | attach | \`flutter attach -d \` | Attach to a running application. | | bash-completion | \`flutter bash-completion\` | Output command line shell completion setup scripts. | | build | \`flutter build \` | Build a target-specific bundle.. | | channel | \`flutter channel \` | List or switch flutter channels. | | clean | \`flutter clean\` | Delete the \`build/\` and \`.dart\_tool/\` directories. | | config | \`flutter config --build-dir=\` | Configure Flutter settings. To remove a setting, configure it to an empty string. | | create | \`flutter create \` | Creates a new project. | | custom-devices | \`flutter custom-devices list\` | Add, delete, list, and reset custom devices. | | devices | \`flutter devices -d \` | List all connected devices. | | doctor | \`flutter doctor\` | Show information about the installed tooling. | | downgrade | \`flutter downgrade\` | Downgrade Flutter to the last active version for the current channel. | | drive | \`flutter drive\` | Runs Flutter Driver tests for the current project. | | emulators | \`flutter emulators\` | List, launch and create emulators. | | gen-l10n | \`flutter gen-l10n \` | Generate localizations for the Flutter project. | | install | \`flutter install -d \` | Install a Flutter app on an attached device. | | logs | \`flutter logs\` | Show log output for running Flutter apps. | | precache | \`flutter precache \` | Populates the Flutter tool's cache of binary artifacts. | | pub | \`flutter pub \` | Works with packages.\
Use instead of \[\`dart pub\`]\[]. | | run | \`flutter run \` | Runs a Flutter program. | | screenshot | \`flutter screenshot\` | Take a screenshot of a Flutter app from a connected device. | | symbolize | \`flutter symbolize --input=\` | Symbolize a stack trace from the AOT compiled flutter application. | | test | \`flutter test \[]\` | Runs tests in this package.\
Use instead of \[\`dart test\`]\[\`dart test\`]. | | upgrade | \`flutter upgrade\` | Upgrade your copy of Flutter. | {:.table .table-striped .nowrap} For additional help on any of the commands, enter \`flutter help

----
url: https://docs.flutter.dev/ai/genui/components.md
----

\# GenUI SDK main components and concepts > Familiarize yourself with the main components and concepts of the Flutter for GenUI SDK. :::experimental The \`genui\` package is in alpha and is likely to change. ::: ## Main components The \[\`genui\`]\[] package is built around the following main components: \`Conversation\` : The primary facade and entry point for the package. It includes the \`SurfaceController\` class, manages the conversation history, and orchestrates the entire generative UI process. \`Catalog\` : A collection of \`CatalogItem\` objects that defines the set of widgets that the AI is allowed to use. The \`SurfaceController\` supports multiple catalogs, allowing you to organize your widgets into logical groups. Each \`CatalogItem\` specifies a widget's name (for the AI to reference), a data schema for its properties, and a builder function to render the Flutter widget. \`DataModel\` : A centralized, observable store for all dynamic UI state. Widgets are \_bound\_ to data in this model. When data changes, only the widgets that depend on that specific piece of data are rebuilt. \`A2uiTransportAdapter\` : A bridge that parses raw text streams coming from your LLM into \`A2uiMessage\` commands for the \`SurfaceController\`. \`A2uiMessage\` : A message sent from the AI (parsed by the \`A2uiTransportAdapter\`) to the UI, instructing it to perform actions like \`createSurface\`, \`surfaceUpdate\`, \`dataModelUpdate\`, or \`deleteSurface\`. \`SurfaceController\` : Handles the processing of \`A2uiMessage\`s, manages the \`DataModel\`, and maintains the state of UI surfaces. ## How it works The \`Conversation\` manages the interaction cycle: 1. \*\*User input\*\* The user provides a prompt (for example, through a text field). The app calls \`conversation.sendMessage()\`. 2. \*\*AI invocation\*\* The \`Conversation\` sends the user's message to the LLM SDK. 3. \*\*AI response\*\* The LLM, guided by the widget schemas provided in its system prompt, sends back responses. 4. \*\*Stream handling\*\* The text stream from the LLM SDK is fed into the \`A2uiTransportAdapter\`. 5. \*\*UI state update\*\* \`A2uiMessages\` parsed by the adapter are passed to \`SurfaceController.handleMessage()\`, which updates the UI state and \`DataModel\`. 6. \*\*UI rendering\*\* The \`SurfaceController\` broadcasts an update, and any \`Surface\` widgets listening for that surface ID will rebuild. Widgets are bound to the \`DataModel\`, so they update automatically when their data changes. 7. \*\*Callbacks\*\* Text responses and errors trigger callbacks on the \`Conversation\` or are handled by your specific LLM integration flow. 8. \*\*User interaction\*\* The user interacts with the newly generated UI (for example, by typing in a text field). This interaction directly updates the \`DataModel\`. If the interaction is an action (like a button click), the \`Surface\` captures the event and forwards it to the \`SurfaceController\`, which automatically creates a new \`UserMessage\` containing the current state of the data model and restarts the cycle. {:.steps} For more detailed information on the implementation of GenUI SDK for Flutter, check out the \[design doc]\[]. The next section walks you through adding \`genui\` to your app. \[design doc]: https\://github.com/flutter/genui/blob/main/packages/genui/DESIGN.md \[\`genui\`]: https\://pub.dev/packages/genui

----
url: https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.12.2
----

The 2.12.2 release of the Dart and Flutter DevTools includes the following changes among other general improvements. To learn more about DevTools, check out the [DevTools overview](https://docs.flutter.dev/tools/devtools).

## General updates

[#](#general-updates)

* Recover from missing trace events - [#3960](https://github.com/flutter/devtools/pull/3960)

## Full commit history

[#](#full-commit-history)

To find a complete list of changes since the previous release, check out [the diff on GitHub](https://github.com/flutter/devtools/compare/v2.12.1...v2.12.2).

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/release-notes/release-notes-2.12.2.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.12.2\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/release-notes/release-notes-2.12.2.md "Report an issue with this page").

----
url: https://docs.flutter.dev/ai/get-started.md
----

\# Get started developing with AI > Learn how to set up and use AI agent plugins for Flutter and Dart using your preferred coding assistant. AI coding assistants can accelerate your Flutter development workflow by writing code, fixing errors, and building complete features. By default, general-purpose AI assistants might not have the latest context on Flutter patterns, project diagnostics, or third-party packages. Installing the official Flutter agent plugin equips your assistant with dedicated tools and recipes tailored for Flutter and Dart development. ## How Flutter AI plugins work An \*\*agent plugin\*\* bundles the tools and knowledge that an AI assistant needs to understand and modify your Flutter codebase. When you install an official Flutter agent plugin, it connects your assistant to four core capabilities: \* \*\*\[Agent skills]\(/ai/tools#agent-skills)\*\*: on-demand procedural guides from the official Flutter and Dart repositories that teach the assistant how to perform specific tasks, such as creating responsive layouts, managing state, or writing widget tests. \* \*\*\[Agent rules]\(/ai/tools#agent-rules)\*\*: persistent instructions that guide an assistant's responses, such as proactively triggering stateful hot reload when editing Flutter widgets. \* \*\*\[Dart and Flutter MCP server]\(/ai/tools#dart-and-flutter-mcp-server)\*\*: a Model Context Protocol (MCP) server that connects the assistant to the Dart SDK, giving it real-time access to analyzer diagnostics, symbol resolution, test runners, and runtime inspection. \* \*\*\[Specialized agents]\(/ai/tools#specialized-agents)\*\*: focused assistant personas tailored for dedicated workflows, such as the Flutter Accessibility (\`a11y\`) agent for automated accessibility audits and code fixes. These tools work together automatically: the assistant uses MCP tools to query live project state and static analysis, while using agent skills to guide its coding strategies and best practices. In addition to core Flutter plugins, you can also equip your assistant with skills that are shipped directly by third-party \`pub.dev\` packages using \[package skills]\(https\://dart.dev/ai/package-skills). To learn more about the underlying architecture and capabilities of each tool, check out \[How Flutter AI tools work]\(/ai/tools). ## Choose your AI coding agent Select your agent below for instructions on how to install official plugins and configure tools for Flutter development. \[Antigravity]\( https\://antigravity.google/docs/build-with-google/#dart-and-flutter) is an agentic development platform by Google that includes the Antigravity IDE, IDE extensions (for VS Code and other editors), and the Antigravity CLI. \*\*Antigravity IDE and extensions\*\* Equip the Antigravity IDE or Antigravity extension (such as in VS Code) with official Dart and Flutter tools: 1. Open \*\*Settings\*\* in Antigravity by clicking the gear icon or pressing `Cmd/Ctrl` + `,`. 1. Click the \*\*Customizations\*\* tab. 1. In the \*\*Build with Google Plugins\*\* section, click \*\*Customize\*\*. 1. Click \*\*Download\*\* next to the \*\*Dart and Flutter\*\* integration. To verify the installation, open the \*\*Agent\*\* panel (`Cmd/Ctrl` + `L`) and ask the agent: \`\`\`text Summarize the Dart and Flutter tools and skills available in this project. \`\`\` \*\*Antigravity CLI\*\* The Antigravity CLI runs as the \`agy\` command in your terminal. 1. Configure the Dart and Flutter MCP server in your project's \`.agents/mcp\_config.json\` file (or globally in \`\~/.gemini/config/mcp\_config.json\`): \`\`\`json { "mcpServers": { "dart": { "command": "dart", "args": \["mcp-server"] } } } \`\`\` 1. Install official Flutter and Dart skills into your workspace: \`\`\`bash # Install Flutter skills npx skills add flutter/agent-plugins --skill '\*' --agent universal --yes # Install Dart skills npx skills add dart-lang/skills --skill '\*' --agent universal --yes \`\`\` 1. Start an interactive session in your project root: \`\`\`bash agy \`\`\` \[Claude Code]\(https\://code.claude.com/) is an agentic coding assistant from Anthropic that runs in your terminal. \*\*Install the official plugin\*\* Equip Claude Code with official Flutter and Dart skills and MCP configuration: 1. Add the marketplace for Claude Code plugins: \`\`\`bash claude plugin marketplace add flutter/agent-plugins \`\`\` 1. Install the Flutter and Dart plugin: \`\`\`bash claude plugin install dart-flutter\@dart-flutter \`\`\` \*\*Configure agent rules\*\* Claude Code plugins discover skills and MCP servers, but don't automatically load plugin rules. To equip Claude Code with official Flutter rules, add the rules you want from the \[official rules repository]\(https\://github.com/flutter/agent-plugins/tree/main/rules) to your project's \`CLAUDE.md\` file. \*\*Verify installation\*\* Verify that the plugin is active in Claude Code: \`\`\`bash claude plugin marketplace list \`\`\` \[Cursor]\(https\://cursor.com/) is an AI-powered code editor built on top of VS Code. \*\*Install the official plugin\*\* Equip Cursor with official Flutter and Dart skills and MCP configuration using any of the following methods: \* Visit the \[Dart and Flutter plugin]\(https\://cursor.com/marketplace/flutter) on the Cursor Marketplace and select \*\*Add to Cursor\*\*. \* In Cursor, open \*\*Customize\*\*, then search for and add \*\*Dart and Flutter\*\*. \* In a Cursor chat session, run \`/add-plugin dart-flutter\`. Cursor automatically discovers the bundled skills and configures the Dart and Flutter MCP server. \*\*Configure agent rules\*\* Cursor supports file-scoped rules in the \`.cursor/rules/\` directory using \`.mdc\` files. To equip Cursor with official Flutter rules, download the \`.mdc\` files from the \[official rules repository]\(https\://github.com/flutter/agent-plugins/tree/main/rules) into your project's \`.cursor/rules/\` directory. \[Codex]\(https\://chatgpt.com/codex) is an agentic coding assistant designed for terminal and IDE workflows. \*\*Install the official plugin\*\* Equip Codex with official Flutter and Dart skills and MCP configuration: 1. Add the Dart and Flutter marketplace for Codex plugins: \`\`\`bash codex plugin marketplace add flutter/agent-plugins \`\`\` 1. Install the Dart and Flutter plugin: \`\`\`bash codex plugin add dart-flutter\@dart-flutter \`\`\` \*\*Configure agent rules\*\* Codex plugins install skills and MCP servers, but don't automatically bundle rules. To equip Codex with official Flutter rules, download the \`.md\` rule files from the \[official rules repository]\(https\://github.com/flutter/agent-plugins/tree/main/rules) into your project's \`.agent/rules/\` directory, or append the rule instructions to \`CODEX.md\`. \*\*Verify installation\*\* Verify that the plugin is active in Codex: \`\`\`bash codex plugin list \`\`\` \[GitHub Copilot]\(https\://github.com/features/copilot) in VS Code supports agentic coding, tool calling via MCP, and custom workspace instructions. \*\*Configure the MCP server\*\* 1. Ensure the \*\*Model Context Protocol (MCP)\*\* extension is enabled in VS Code. 1. Create or open \`.vscode/mcp.json\` in your workspace and add the Dart MCP server: \`\`\`json { "servers": { "dart": { "command": "dart", "args": \["mcp-server"] } } } \`\`\` \*\*Install agent skills\*\* In your Flutter project root, install official skills using the universal flag: \`\`\`bash # Install Flutter skills npx skills add flutter/agent-plugins --skill '\*' --agent universal --yes # Install Dart skills npx skills add dart-lang/skills --skill '\*' --agent universal --yes \`\`\` \*\*Configure agent rules\*\* GitHub Copilot supports custom workspace instructions in \`.github/copilot-instructions.md\`. To equip Copilot with official Flutter rules, add the rules you want from the \[official rules repository]\(https\://github.com/flutter/agent-plugins/tree/main/rules) to your project's \`.github/copilot-instructions.md\` file. If you're using another coding assistant (such as Windsurf, Zed, or Cline) that supports the Model Context Protocol (MCP) or Agent Skills specification, configure the tools manually. \*\*Configure the MCP server\*\* Most MCP-compatible clients use a standard JSON configuration format. Add the following entry to your client's MCP configuration file: \`\`\`json { "mcpServers": { "dart": { "command": "dart", "args": \["mcp-server"] } } } \`\`\` For client-specific setup guides, command-line arguments, and troubleshooting, check out the \[Dart and Flutter MCP server repository]\( https\://github.com/dart-lang/ai/tree/main/pkgs/dart\_mcp\_server). \*\*Install agent skills\*\* Use the \`skills\` CLI to download skills into your workspace's standard \`.agents/skills\` directory: \`\`\`bash # Install Flutter skills npx skills add flutter/agent-plugins --skill '\*' --agent universal --yes # Install Dart skills npx skills add dart-lang/skills --skill '\*' --agent universal --yes \`\`\` \*\*Configure agent rules\*\* If your coding assistant supports project instructions or rules (such as Windsurf \`.windsurfrules\` or Cline \`.clinerules\`), copy or download rules from the \[official rules repository]\(https\://github.com/flutter/agent-plugins/tree/main/rules) into your assistant's configuration file. ## Next steps \* To learn more about how skills and tools interact, check out \[How Flutter AI tools work]\(/ai/tools). \* To discover and install skills from dependencies, refer to \[Package skills]\(https\://dart.dev/ai/package-skills). \* To publish skills with your own packages, refer to \[Ship skills with packages]\(https\://dart.dev/tools/pub/package-skills). \* To give your assistant search access to official Flutter and Dart documentation, connect to the \[Developer Knowledge MCP server]\( https\://developers.google.com/knowledge/mcp). \* To explore the official plugins, check out the \[flutter/agent-plugins]\(https\://github.com/flutter/agent-plugins) repository.

----
url: https://docs.flutter.dev/platform-integration/android/splash-screen.md
----

\# Adding a splash screen to your Android app > Learn how to add a splash screen to your Android app. ## Overview A splash screens (also known as a launch screen) provides a simple initial experience while your Android app loads. It sets the stage for your application, while allowing time for the app engine to load and your app to initialize. You have a couple options for implementing a splash screen: 1. You can use one of the packages available on \[pub.dev]\[]. 2. You can implement it manually, as shown in the \[splash screen sample app]\[]. The rest of this page assumes the manual approach. \[pub.dev]: https\://pub.dev/packages?q=splash+screen \[splash screen sample app]: https\://github.com/flutter/samples/tree/main/android\_splash\_screen ## Initializing the app Every Android app requires initialization time while the operating system sets up the app's process. Android provides the concept of a \[launch screen]\[] to display a \`Drawable\` while the app is initializing. :::note For apps that embed one or more Flutter screens within an existing Android app, consider \[pre-warming a \`FlutterEngine\`]\[] and reusing the same engine throughout your app to minimize wait time associated with initialization of the Flutter engine. ::: A \`Drawable\` is an Android graphic. To learn how to add a \`Drawable\` to your Flutter project in Android Studio, check out \[Import drawables into your project]\[drawables]\[] in the Android developer documentation. The default Flutter project template includes a definition of a launch theme and a launch background. You can customize this by editing \`styles.xml\`, where you can define a theme whose \`windowBackground\` is set to the \`Drawable\` that should be displayed as the launch screen. \`\`\`xml \`\`\` In addition, \`styles.xml\` defines a \_normal theme\_ to be applied to \`FlutterActivity\` after the launch screen is gone. The normal theme background only shows for a very brief moment after the splash screen disappears, and during orientation change and \`Activity\` restoration. Therefore, it's recommended that the normal theme use a solid background color that looks similar to the primary background color of the Flutter UI. \`\`\`xml \`\`\` \[drawables]: https\://developer.android.com/studio/write/resource-manager#import ## Set up the FlutterActivity in AndroidManifest.xml In \`AndroidManifest.xml\`, set the \`theme\` of \`FlutterActivity\` to the launch theme. Then, add a metadata element to the desired \`FlutterActivity\` to instruct Flutter to switch from the launch theme to the normal theme at the appropriate time. \`\`\`xml \`\`\` The Android app now displays the desired launch screen while the app initializes. ## SplashScreen API Android 12 introduced the \[\`SplashScreen\`]\[] API. Use the \`SplashScreen\` API in your \`styles.xml\`file. For example: \`\`\`xml \`\`\` :::note If your Android app supports releases earlier than Android 12 \_and\_ post-Android 12 releases, consider using two different resources in your \`styles.xml\` file. Also, make sure that your background image is in line with the icon guidelines. For more information, visit \[Android Splash Screens]\[]. ::: \[Android Splash Screens]: https\://developer.android.com/develop/ui/views/launch/splash-screen \[\`SplashScreen\`]: https\://developer.android.com/reference/android/window/SplashScreen Some apps might want to continue showing the last frame of the Android launch screen in Flutter. For example, this preserves the illusion of a single frame while additional loading continues in Dart. To achieve this, the following Android APIs might be helpful: \`\`\`kotlin title="MainActivity.kt" import android.os.Build import android.os.Bundle import androidx.core.view\.WindowCompat import io.flutter.embedding.android.FlutterActivity class MainActivity : FlutterActivity() { override fun onCreate(savedInstanceState: Bundle?) { // Aligns the Flutter view vertically with the window. WindowCompat.setDecorFitsSystemWindows(getWindow(), false) if (Build.VERSION.SDK\_INT >= Build.VERSION\_CODES.S) { // Disable the Android splash screen fade out animation to avoid // a flicker before the similar frame is drawn in Flutter. splashScreen.setOnExitAnimationListener { splashScreenView -> splashScreenView\.remove() } } super.onCreate(savedInstanceState) } } \`\`\` \`\`\`java title="MainActivity.java" import android.os.Build; import android.os.Bundle; import android.window\.SplashScreenView; import androidx.core.view\.WindowCompat; import io.flutter.embedding.android.FlutterActivity; public class MainActivity extends FlutterActivity { @Override protected void onCreate(Bundle savedInstanceState) { // Aligns the Flutter view vertically with the window. WindowCompat.setDecorFitsSystemWindows(getWindow(), false); if (Build.VERSION.SDK\_INT >= Build.VERSION\_CODES.S) { // Disable the Android splash screen fade out animation to avoid // a flicker before the similar frame is drawn in Flutter. getSplashScreen() .setOnExitAnimationListener( (SplashScreenView splashScreenView) -> { splashScreenView\.remove(); }); } super.onCreate(savedInstanceState); } } \`\`\` Then, you can reimplement the first frame in Flutter that shows elements of your Android launch screen in the same positions on screen. For an example of this, check out the \[splash screen sample app]\[]. \[launch screen]: https\://developer.android.com/topic/performance/vitals/launch-time#themed \[pre-warming a \`FlutterEngine\`]: /add-to-app/android/add-flutter-fragment#using-a-pre-warmed-flutterengine

----
url: https://docs.flutter.dev/cookbook/networking/send-data.md
----

\# Send data to the internet > How to use the http package to send data over the internet.

<!--?code-excerpt path-base="cookbook/networking/send_data/"?-->

Sending data to the internet is necessary for most apps. The \`http\` package has got that covered, too. This recipe uses the following steps: 1. Add the \`http\` package. 2. Send data to a server using the \`http\` package. 3. Convert the response into a custom Dart object. 4. Get a \`title\` from user input. 5. Display the response on screen. ## 1. Add the \`http\` package To add the \`http\` package as a dependency, run \`flutter pub add\`: \`\`\`console $ flutter pub add http \`\`\` Import the \`http\` package.

<!--?code-excerpt "lib/main.dart (Http)"?-->

\`\`\`dart import 'package:http/http.dart' as http; import 'package:material\_ui/material\_ui.dart'; \`\`\` If you are deploying to Android, edit your \`AndroidManifest.xml\` file to add the Internet permission. \`\`\`xml \`\`\` Likewise, if you are deploying to macOS, edit your \`macos/Runner/DebugProfile.entitlements\` and \`macos/Runner/Release.entitlements\` files to include the network client entitlement. \`\`\`xml com.apple.security.network.client \`\`\` ## 2. Sending data to server This recipe covers how to create an \`Album\` by sending an album title to the \[JSONPlaceholder]\[] using the \[\`http.post()\`]\[] method. Import \`dart:convert\` for access to \`jsonEncode\` to encode the data:

<!--?code-excerpt "lib/create_album.dart (convert-import)"?-->

\`\`\`dart import 'dart:convert'; \`\`\` Use the \`http.post()\` method to send the encoded data:

<!--?code-excerpt "lib/create_album.dart (CreateAlbum)"?-->

\`\`\`dart Future createAlbum(String title) { return http.post( Uri.parse('https\://jsonplaceholder.typicode.com/albums'), headers: { 'Content-Type': 'application/json; charset=UTF-8', }, body: jsonEncode({'title': title}), ); } \`\`\` The \`http.post()\` method returns a \`Future\` that contains a \`Response\`. \* \[\`Future\`]\[] is a core Dart class for working with asynchronous operations. A Future object represents a potential value or error that will be available at some time in the future. \* The \`http.Response\` class contains the data received from a successful http call. \* The \`createAlbum()\` method takes an argument \`title\` that is sent to the server to create an \`Album\`. ## 3. Convert the \`http.Response\` to a custom Dart object While it's easy to make a network request, working with a raw \`Future\` isn't very convenient. To make your life easier, convert the \`http.Response\` into a Dart object. ### Create an Album class First, create an \`Album\` class that contains the data from the network request. It includes a factory constructor that creates an \`Album\` from JSON. Converting JSON with \[pattern matching]\[] is only one option. For more information, see the full article on \[JSON and serialization]\[].

<!--?code-excerpt "lib/main.dart (Album)"?-->

\`\`\`dart class Album { final int id; final String title; const Album({required this.id, required this.title}); factory Album.fromJson(Map json) { return switch (json) { {'id': int id, 'title': String title} => Album(id: id, title: title), \_ => throw const FormatException('Failed to load album.'), }; } } \`\`\` ### Convert the \`http.Response\` to an \`Album\` Use the following steps to update the \`createAlbum()\` function to return a \`Future\`: 1. Convert the response body into a JSON \`Map\` with the \`dart:convert\` package. 2. If the server returns a \`CREATED\` response with a status code of 201, then convert the JSON \`Map\` into an \`Album\` using the \`fromJson()\` factory method. 3. If the server doesn't return a \`CREATED\` response with a status code of 201, then throw an exception. (Even in the case of a "404 Not Found" server response, throw an exception. Do not return \`null\`. This is important when examining the data in \`snapshot\`, as shown below.)

<!--?code-excerpt "lib/main.dart (createAlbum)"?-->

\`\`\`dart Future createAlbum(String title) async { final response = await http.post( Uri.parse('https\://jsonplaceholder.typicode.com/albums'), headers: { 'Content-Type': 'application/json; charset=UTF-8', }, body: jsonEncode({'title': title}), ); if (response.statusCode == 201) { // If the server did return a 201 CREATED response, // then parse the JSON. return Album.fromJson(jsonDecode(response.body) as Map); } else { // If the server did not return a 201 CREATED response, // then throw an exception. throw Exception('Failed to create album.'); } } \`\`\` Hooray! Now you've got a function that sends the title to a server to create an album. ## 4. Get a title from user input Next, create a \`TextField\` to enter a title and a \`ElevatedButton\` to send data to server. Also define a \`TextEditingController\` to read the user input from a \`TextField\`. When the \`ElevatedButton\` is pressed, the \`\_futureAlbum\` is set to the value returned by \`createAlbum()\` method.

<!--?code-excerpt "lib/main.dart (Column)" replace="/^return //g;/^\);$/)/g"?-->

\`\`\`dart Column( mainAxisAlignment: MainAxisAlignment.center, children: \[ TextField( controller: \_controller, decoration: const InputDecoration(hintText: 'Enter Title'), ), ElevatedButton( onPressed: () { setState(() { \_futureAlbum = createAlbum(\_controller.text); }); }, child: const Text('Create Data'), ), ], ) \`\`\` On pressing the \*\*Create Data\*\* button, make the network request, which sends the data in the \`TextField\` to the server as a \`POST\` request. The Future, \`\_futureAlbum\`, is used in the next step. ## 5. Display the response on screen To display the data on screen, use the \[\`FutureBuilder\`]\[] widget. The \`FutureBuilder\` widget comes with Flutter and makes it easy to work with asynchronous data sources. You must provide two parameters: 1. The \`Future\` you want to work with. In this case, the future returned from the \`createAlbum()\` function. 2. A \`builder\` function that tells Flutter what to render, depending on the state of the \`Future\`: loading, success, or error. Note that \`snapshot.hasData\` only returns \`true\` when the snapshot contains a non-null data value. This is why the \`createAlbum()\` function should throw an exception even in the case of a "404 Not Found" server response. If \`createAlbum()\` returns \`null\`, then \`CircularProgressIndicator\` displays indefinitely.

<!--?code-excerpt "lib/main.dart (FutureBuilder)" replace="/^return //g;/^\);$/)/g"?-->

\`\`\`dart FutureBuilder( future: \_futureAlbum, builder: (context, snapshot) { if (snapshot.hasData) { return Text(snapshot.data!.title); } else if (snapshot.hasError) { return Text('${snapshot.error}'); } return const CircularProgressIndicator(); }, ) \`\`\` ## Complete example

<!--?code-excerpt "lib/main.dart"?-->

\`\`\`dart import 'dart:async'; import 'dart:convert'; import 'package:http/http.dart' as http; import 'package:material\_ui/material\_ui.dart'; Future createAlbum(String title) async { final response = await http.post( Uri.parse('https\://jsonplaceholder.typicode.com/albums'), headers: { 'Content-Type': 'application/json; charset=UTF-8', }, body: jsonEncode({'title': title}), ); if (response.statusCode == 201) { // If the server did return a 201 CREATED response, // then parse the JSON. return Album.fromJson(jsonDecode(response.body) as Map); } else { // If the server did not return a 201 CREATED response, // then throw an exception. throw Exception('Failed to create album.'); } } class Album { final int id; final String title; const Album({required this.id, required this.title}); factory Album.fromJson(Map json) { return switch (json) { {'id': int id, 'title': String title} => Album(id: id, title: title), \_ => throw const FormatException('Failed to load album.'), }; } } void main() { runApp(const MyApp()); } class MyApp extends StatefulWidget { const MyApp({super.key}); @override State createState() { return \_MyAppState(); } } class \_MyAppState extends State { final TextEditingController \_controller = TextEditingController(); Future? \_futureAlbum; @override Widget build(BuildContext context) { return MaterialApp( title: 'Create Data Example', theme: ThemeData( colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), ), home: Scaffold( appBar: AppBar(title: const Text('Create Data Example')), body: Container( alignment: Alignment.center, padding: const EdgeInsets.all(8), child: (\_futureAlbum == null) ? buildColumn() : buildFutureBuilder(), ), ), ); } Column buildColumn() { return Column( mainAxisAlignment: MainAxisAlignment.center, children: \[ TextField( controller: \_controller, decoration: const InputDecoration(hintText: 'Enter Title'), ), ElevatedButton( onPressed: () { setState(() { \_futureAlbum = createAlbum(\_controller.text); }); }, child: const Text('Create Data'), ), ], ); } FutureBuilder buildFutureBuilder() { return FutureBuilder( future: \_futureAlbum, builder: (context, snapshot) { if (snapshot.hasData) { return Text(snapshot.data!.title); } else if (snapshot.hasError) { return Text('${snapshot.error}'); } return const CircularProgressIndicator(); }, ); } } \`\`\` \[ConnectionState]: https\://api.flutter.dev/flutter/widgets/ConnectionState-class.html \[\`didChangeDependencies()\`]: https\://api.flutter.dev/flutter/widgets/State/didChangeDependencies.html \[Fetch Data]: /cookbook/networking/fetch-data \[\`Future\`]: https\://api.flutter.dev/flutter/dart-async/Future-class.html \[\`FutureBuilder\`]: https\://api.flutter.dev/flutter/widgets/FutureBuilder-class.html \[\`http\`]: https\://pub.dev/packages/http \[\`http.post()\`]: https\://pub.dev/documentation/http/latest/http/post.html \[\`http\` package]: https\://pub.dev/packages/http/install \[\`InheritedWidget\`]: https\://api.flutter.dev/flutter/widgets/InheritedWidget-class.html \[Introduction to unit testing]: /cookbook/testing/unit/introduction \[\`initState()\`]: https\://api.flutter.dev/flutter/widgets/State/initState.html \[JSONPlaceholder]: https\://jsonplaceholder.typicode.com/ \[JSON and serialization]: /data-and-backend/serialization/json \[Mock dependencies using Mockito]: /cookbook/testing/unit/mocking \[pattern matching]: https\://dart.dev/language/patterns \[\`State\`]: https\://api.flutter.dev/flutter/widgets/State-class.html

----
url: https://docs.flutter.dev/add-to-app/android/project-setup.md
----

\# Integrate a Flutter module into your Android project > Learn how to integrate a Flutter module into your existing Android project. Flutter can be embedded into your existing Android application piecemeal, as a source code Gradle subproject or as AARs. The integration flow can be done using the Android Studio IDE with the \[Flutter plugin]\[] or manually. :::warning Your existing Android app might support architectures such as \`mips\` or \`x86\`. Flutter currently \[only supports]\[] building ahead-of-time (AOT) compiled libraries for \`x86\_64\`, \`armeabi-v7a\`, and \`arm64-v8a\`. Consider using the \[\`abiFilters\`]\[] Android Gradle Plugin API to limit the supported architectures in your APK. Doing this avoids a missing \`libflutter.so\` runtime crash, for example: \`\`\`kotlin title="MyApp/app/build.gradle.kts" android { //... defaultConfig { ndk { // Filter for architectures supported by Flutter abiFilters += listOf("armeabi-v7a", "arm64-v8a", "x86\_64") } } } \`\`\` \`\`\`groovy title="MyApp/app/build.gradle" android { // ... defaultConfig { ndk { // Filter for architectures supported by Flutter abiFilters "armeabi-v7a", "arm64-v8a", "x86\_64" } } } \`\`\` The Flutter engine also has an \`x86\_64\` version. When using an emulator in debug Just-In-Time (JIT) mode, the Flutter module still runs correctly. ::: ## Integrate your Flutter module ### Integrate with Android Studio {:.no\_toc} The Android Studio IDE can help integrate your Flutter module. Using Android Studio, you can edit both your Android and Flutter code in the same IDE. You can also use IntelliJ Flutter plugin functionality like Dart code completion, hot reload, and widget inspector. To build your app, the Android Studio plugin configures your Android project to add your Flutter module as a dependency. 1. Open your Android project in Android Studio. 1. Go to \*\*File\*\* > \*\*New\*\* > \*\*New Project...\*\*. The \*\*New Project\*\* dialog displays. 1. Click \*\*Flutter\*\*. 1. If asked to provide your \*\*Flutter SDK path\*\*, do so and click \*\*Next\*\*. 1. Complete the configuration of your Flutter module. \* If you have an existing project: {: type="a"} 1. To choose an existing project, click \*\*...\*\* to the right of the \*\*Project location\*\* box. 1. Navigate to your Flutter project directory. 1. Click \*\*Open\*\*. \* If you need to create a new Flutter project: {: type="a"} 1. Complete the configuration dialog. 1. In the \*\*Project type\*\* menu, select \*\*Module\*\*. 1. Click \*\*Finish\*\*. :::tip By default, your project's Project pane might show the 'Android' view. If you can't see your new Flutter files in the Project pane, set your Project pane to display \*\*Project Files\*\*. This shows all files without filtering. ::: ### Integrate without Android Studio {:.no\_toc} To integrate a Flutter module with an existing Android app manually, without using Flutter's Android Studio plugin, follow these steps: #### Create a Flutter module Assuming that you have an existing Android app at \`some/path/MyApp\`, and that you want your Flutter project as a sibling, do the following: \`\`\`console cd some/path/ flutter create -t module --org com.example flutter\_module \`\`\` This creates a \`some/path/flutter\_module/\` Flutter module project with some Dart code to get you started and a \`.android/\` hidden subfolder. The \`.android\` folder contains an Android project that can both help you run a barebones standalone version of your Flutter module with \`flutter run\` and it's also a wrapper that helps bootstrap the Flutter module an embeddable Android library. :::warning \*\*Do not\*\* edit files in \`.android/\` to add native functionality. This folder is generated for testing purposes and \*\*will be overwritten\*\* whenever you run \`flutter pub get\` or build the module. \* To add native code that you can use across apps/modules, create a \[Flutter Plugin]\(/packages-and-plugins/developing-packages#plugin) and depend on it. \* To add \*\*app-specific\*\* native code, add it directly to your existing Android host application. The \`.android\` directory contains a generated Android project that uses Java to bootstrap the Flutter module, so \*\*do not\*\* add it to source control. This approach helps you run a barebones standalone version of your Flutter module (with \`flutter run\`) and verify basic functionality. Using Java here doesn't prevent you from using Kotlin in your host app or plugins. ::: :::note To avoid Dex merging issues, \`flutter.androidPackage\` should not be identical to your host app's package name. ::: #### Java version requirement Flutter requires your project to declare compatibility with Java 17 or later. Before attempting to connect your Flutter module project to your host Android app, ensure that your host Android app declares the following source compatibility within your app's \`build.gradle\` file, under the \`android { }\` block. \`\`\`groovy title="MyApp/app/build.gradle" android { // ... compileOptions { sourceCompatibility = JavaVersion.VERSION\_17 // The minimum value targetCompatibility = JavaVersion.VERSION\_17 // The minimum value } // ... } \`\`\` #### Centralize repository settings Starting with Gradle 7, Android recommends using centralized repository declarations in \`settings.gradle\` instead of project or module level declarations in \`build.gradle\` files. Before attempting to connect your Flutter module project to your host Android app, make the following changes to your host app: 1. Remove the \`repositories\` block in all of your app's \`build.gradle\` files. \`\`\`groovy // Remove the following block, starting on the next line repositories { google() mavenCentral() } // ...to the previous line \`\`\` 1. Add the \`dependencyResolutionManagement\` displayed in this step to the \`settings.gradle\` file. \`\`\`kotlin title="settings.gradle.kts" dependencyResolutionManagement { repositoriesMode.set(RepositoriesMode.PREFER\_SETTINGS) val storageUrl: String = System.getenv("FLUTTER\_STORAGE\_BASE\_URL") ?: "https\://storage.googleapis.com" repositories { google() mavenCentral() maven("$storageUrl/download.flutter.io") } } \`\`\` \`\`\`groovy title="settings.gradle" dependencyResolutionManagement { repositoriesMode = RepositoriesMode.PREFER\_SETTINGS String storageUrl = System.env.FLUTTER\_STORAGE\_BASE\_URL ?: "https\://storage.googleapis.com" repositories { google() mavenCentral() maven { url = uri("$storageUrl/download.flutter.io") } } } \`\`\` ## Add the Flutter module as a dependency Add the Flutter module as a dependency of your existing app in Gradle. You can achieve this in two ways. 1. \*\*Android archive\*\* The AAR mechanism creates generic Android AARs as intermediaries that packages your Flutter module. This is good when your downstream app builders don't want to have the Flutter SDK installed. But, it adds one more build step if you build frequently. 1. \*\*Module source code\*\* The source code subproject mechanism is a convenient one-click build process, but requires the Flutter SDK. This is the mechanism used by the Android Studio IDE plugin. ### Depend on the Android Archive (AAR) {:.no\_toc} This option packages your Flutter library as a generic local Maven repository composed of AARs and POMs artifacts. This option allows your team to build the host app without installing the Flutter SDK. You can then distribute the artifacts from a local or remote repository. Let's assume you built a Flutter module at \`some/path/flutter\_module\`, and then run: \`\`\`console cd some/path/flutter\_module flutter build aar \`\`\` Then, follow the on-screen instructions to integrate. More specifically, this command creates (by default all debug/profile/release modes) a \[local repository]\[], with the following files: - build/host/outputs/repo - com - example - flutter\_module - flutter\_release - 1.0 - flutter\_release-1.0.aar - flutter\_release-1.0.aar.md5 - flutter\_release-1.0.aar.sha1 - flutter\_release-1.0.pom - flutter\_release-1.0.pom.md5 - flutter\_release-1.0.pom.sha1 - maven-metadata.xml - maven-metadata.xml.md5 - maven-metadata.xml.sha1 - flutter\_profile - ... - flutter\_debug - ... To depend on the AAR, the host app must be able to find these files. To do that, edit \`settings.gradle\` in your host app so that it includes the local repository and the dependency: \`\`\`kotlin title="settings.gradle.kts" dependencyResolutionManagement { repositoriesMode.set(RepositoriesMode.PREFER\_SETTINGS) repositories { google() mavenCentral() maven("https\://storage.googleapis.com/download.flutter.io") maven(url = "/build/host/outputs/repo") } } \`\`\` \`\`\`groovy title="settings.gradle" dependencyResolutionManagement { repositoriesMode = RepositoriesMode.PREFER\_SETTINGS repositories { google() mavenCentral() // Add the new repositories starting on the next line... maven { url = uri("/build/host/outputs/repo") // This is relative to the location of the build.gradle file // if using a relative path. } maven { url = uri("https\://storage.googleapis.com/download.flutter.io") } // ...to before this line } } \`\`\`\
\### Kotlin DSL based Android Project After an \`aar\` build of a Kotlin DSL-based Android project, follow these steps to add the flutter\_module. Include the flutter module as a dependency in the host app's \`app/build.gradle\` file. \`\`\`kotlin title="MyApp/app/build.gradle.kts" android { buildTypes { release { ... } debug { ... } create("profile") { initWith(getByName("debug")) } } dependencies { // ... debugImplementation("com.example.flutter\_module:flutter\_debug:1.0") releaseImplementation("com.example.flutter\_module:flutter\_release:1.0") add("profileImplementation", "com.example.flutter\_module:flutter\_profile:1.0") } \`\`\` Add the custom \`profileImplementation\` dependency configuration to the end of the same app-level build.gradle file. \`\`\`kotlin title="MyApp/app/build.gradle.kts" configurations { getByName("profileImplementation") { } } \`\`\` :::important If you're located in China, use a mirror site rather than the \`storage.googleapis.com\` domain. To learn more about mirror sites, check out \[Using Flutter in China]\[] page. ::: :::tip You can also build an AAR for your Flutter module in Android Studio using the \`Build > Flutter > Build AAR\` menu. ::: ### Depend on the module's source code {:.no\_toc} This option enables a one-step build for both your Android project and Flutter project. This option is convenient when you work on both parts simultaneously and rapidly iterate, but your team must install the Flutter SDK to build the host app. :::tip By default, the host app provides the \`:app\` Gradle project. To change the name of this project, set \`flutter.hostAppProjectName\` in the Flutter module's \`gradle.properties\` file. Include this project in the host app's \`settings.gradle\` file. ::: #### Updating \`settings.gradle\` Include the Flutter module as a subproject in the host app's \`settings.gradle\`. This example assumes \`flutter\_module\` and \`MyApp\` exist in the same directory If you are using Kotlin, apply the following changes: \`\`\`kotlin title="MyApp/settings.gradle.kts" // Include the host app project. Assumed existing content. include(":app") // Replace "flutter\_module" with whatever package\_name you supplied when you ran: // \`$ flutter create -t module \[package\_name] val filePath = settingsDir.parentFile.toString() + "/flutter\_module/.android/include\_flutter.groovy" apply(from = File(filePath)) \`\`\` :::warning The ability to invoke \`include\_flutter.groovy\` from Kotlin code requires Flutter 3.27. To determine your current Flutter version, run \`flutter --version\`. If it isn't at least version 3.27, consider changing to either the \`main\` or \`beta\` channels. ::: If you are using Groovy, apply the following changes: \`\`\`groovy title="MyApp/settings.gradle" // Include the host app project. include(":app") // assumed existing content setBinding(new Binding(\[gradle: this])) // new def filePath = settingsDir.parentFile.toString() + "/flutter\_module/.android/include\_flutter.groovy" // new apply from: filePath // new \`\`\` The binding and script evaluation allows the Flutter module to \`include\` itself (as \`:flutter\`) and any Flutter plugins used by the module (such as \`:package\_info\` and \`:video\_player\`) in the evaluation context of your \`settings.gradle\`. #### Updating \`app/build.gradle\` Introduce an \`implementation\` dependency on the Flutter module from your app: \`\`\`groovy title="MyApp/app/build.gradle" dependencies { implementation(project(":flutter")) } \`\`\` :::note This code is identical between Groovy and Kotlin. ::: Your app now includes the Flutter module as a dependency. Continue to the \[Adding a Flutter screen to an Android app]\[] guide. \[\`abiFilters\`]: https\://developer.android.com/reference/tools/gradle-api/4.2/com/android/build/api/dsl/Ndk#abiFilters:kotlin.collections.MutableSet \[Adding a Flutter screen to an Android app]: /add-to-app/android/add-flutter-screen \[Flutter plugin]: https\://plugins.jetbrains.com/plugin/9212-flutter \[local repository]: https\://docs.gradle.org/current/userguide/declaring\_repositories.html#sub:maven\_local \[only supports]: /resources/faq#what-devices-and-os-versions-does-flutter-run-on \[Using Flutter in China]: /community/china

----
url: https://docs.flutter.dev/perf/faq
----

This page collects some frequently asked questions about evaluating and debugging Flutter's performance.

* Which performance dashboards have metrics that are related to Flutter?

  * [Flutter dashboard on appspot](https://flutter-dashboard.appspot.com/)
  * [Flutter Skia dashboard](https://flutter-flutter-perf.skia.org/t/?subset=regressions)
  * [Flutter Engine Skia dashboard](https://flutter-engine-perf.skia.org/t/?subset=regressions)

- How do I add a benchmark to Flutter?

  * [How to write a render speed test for Flutter](https://github.com/flutter/flutter/blob/main/docs/contributing/testing/How-to-write-a-render-speed-test-for-Flutter.md)
  * [How to write a memory test for Flutter](https://github.com/flutter/flutter/blob/main/docs/contributing/testing/How-to-write-a-memory-test-for-Flutter.md)

* What are some tools for capturing and analyzing performance metrics?

  * [Dart/Flutter DevTools](/tools/devtools)
  * [Apple instruments](https://en.wikipedia.org/wiki/Instruments_\(software\))
  * [Linux perf](https://en.wikipedia.org/wiki/Perf_\(Linux\))
  * [Chrome tracing (enter `about:tracing` in your Chrome URL field)](https://www.chromium.org/developers/how-tos/trace-event-profiling-tool)
  * [Android systrace (`adb systrace`)](https://developer.android.com/studio/profile/systrace)
  * [Fuchsia `fx traceutil`](https://fuchsia.dev/fuchsia-src/development/tracing/usage-guide)
  * [Perfetto](https://ui.perfetto.dev/)
  * [speedscope](https://www.speedscope.app/)

- My Flutter app looks janky or stutters. How do I fix it?
  * [Improving rendering performance](/perf/rendering-performance)

* What are some costly performance operations that I need to be careful with?

  * [`Opacity`](https://api.flutter.dev/flutter/widgets/Opacity-class.html), [`Clip.antiAliasWithSaveLayer`](https://api.flutter.dev/flutter/dart-ui/Clip.html#antiAliasWithSaveLayer), or anything that triggers [`saveLayer`](https://api.flutter.dev/flutter/dart-ui/Canvas/saveLayer.html)
  * [`ImageFilter`](https://api.flutter.dev/flutter/dart-ui/ImageFilter-class.html)
  * Also see [Performance best practices](/perf/best-practices)

- How do I tell which widgets in my Flutter app are rebuilt in each frame?

  * Set [`debugProfileBuildsEnabled`](https://api.flutter.dev/flutter/widgets/debugProfileBuildsEnabled.html) true in [widgets/debug.dart](https://github.com/flutter/flutter/blob/main/packages/flutter/lib/src/widgets/debug.dart).
  * Alternatively, change the `performRebuild` function in [widgets/framework.dart](https://github.com/flutter/flutter/blob/main/packages/flutter/lib/src/widgets/framework.dart) to ignore `debugProfileBuildsEnabled` and always call `Timeline.startSync(...)/finish`.
  * If you use IntelliJ, a GUI view of this data is available. Select **Track widget rebuilds**, and your IDE displays which the widgets rebuild.

* How do I query the target frames per second (of the display)?
  * [Get the display refresh rate](https://github.com/flutter/flutter/blob/main/docs/engine/Engine-specific-Service-Protocol-extensions.md#get-the-display-refresh-rate-_fluttergetdisplayrefreshrate)

- How to solve my app's poor animations caused by an expensive Dart async function call that is blocking the UI thread?
  * Spawn another isolate using the [`compute()`](https://api.flutter.dev/flutter/foundation/compute-constant.html) method, as demonstrated in [Parse JSON in the background](/cookbook/networking/background-parsing) cookbook.

* How do I determine my Flutter app's package size that a user will download?
  * See [Measuring your app's size](/perf/app-size)

- How do I see the breakdown of the Flutter engine size?
  * Visit the [binary size dashboard](https://storage.googleapis.com/flutter_infra_release/flutter/241c87ad800beeab545ab867354d4683d5bfb6ce/android-arm-release/sizes/index.html), and replace the git hash in the URL with a recent commit hash from [Flutter's GitHub commits](https://github.com/flutter/flutter/commits/main).

* How can I take a screenshot of an app that is running and export it as a SKP file?

  * Run `flutter screenshot --type=skia --observatory-uri=...`
  * Note a known issue viewing screenshots:
    * [Issue 21237](https://github.com/flutter/flutter/issues/21237): Doesn't record images in real devices.
  * To analyze and visualize the SKP file, check out the [Skia WASM debugger](https://debugger.skia.org/).

- How do I retrieve the shader persistent cache from a device?

  * On Android, you can do the following:
    ```
    adb shell
    run-as <com.your_app_package_name>
    cp <your_folder> <some_public_folder, e.g., /sdcard> -r
    adb pull <some_public_folder/your_folder>
    ```

- How do I perform a trace in Fuchsia?

  * See [Fuchsia tracing guidelines](https://fuchsia.dev/fuchsia-src/development/tracing/usage-guide)

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/perf/faq.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/perf/faq\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/perf/faq.md "Report an issue with this page").

----
url: https://docs.flutter.dev/ai/ai-toolkit.md
----

\# Flutter AI Toolkit > Learn how to add the AI Toolkit chatbot to your Flutter application. Hello and welcome to the Flutter AI Toolkit! The AI Toolkit is a set of AI chat-related widgets that make it easy to add an AI chat window to your Flutter app. The AI Toolkit is organized around an abstract LLM provider API to make it easy to swap out the LLM provider that you'd like your chat provider to use. Out of the box, it comes with support for \[Firebase AI Logic]\[]. \[Firebase AI Logic]: https\://firebase.google.com/docs/ai-logic ## Key features \* \*\*Multiturn chat\*\*: Maintains context across multiple interactions. \* \*\*Streaming responses\*\*: Displays AI responses in real-time as they are generated. \* \*\*Rich text display\*\*: Supports formatted text in chat messages. \* \*\*Voice input\*\*: Allows users to input prompts using speech. \* \*\*Multimedia attachments\*\*: Enables sending and receiving various media types. \* \*\*Function calling\*\*: Supports tool calls to the LLM provider. \* \*\*Custom styling\*\*: Offers extensive customization to match your app's design. \* \*\*Chat serialization/deserialization\*\*: Store and retrieve conversations between app sessions. \* \*\*Custom response widgets\*\*: Introduce specialized UI components to present LLM responses. \* \*\*Pluggable LLM support\*\*: Implement a simple interface to plug in your own LLM. \* \*\*Cross-platform support\*\*: Compatible with Android, iOS, web, and macOS platforms. ## Demo Here's what the demo example looks like hosting the AI Toolkit: The \[source code for this demo]\[src-code] is available in the repo on GitHub. \[src-code]: https\://github.com/flutter/ai/blob/main/example/lib/demo/demo.dart ## Get started

1. **Installation** Add the following dependencies to your \`pubspec.yaml\` file: \`\`\`yaml dependencies: flutter\_ai\_toolkit: ^latest\_version firebase\_ai: ^latest\_version firebase\_core: ^latest\_version \`\`\`

2. **Configuration** The AI Toolkit supports both the Gemini Developer API (for prototyping) and the Firebase AI Logic production endpoint. Both require a Firebase project and the \`firebase\_core\` package to be initialized, as described in the \[Get started with the Gemini API using the Firebase AI Logic SDKs]\[firebase\_ai] docs. \[firebase\_ai]: https\://firebase.google.com/docs/ai-logic/get-started?platform=flutter Once that's complete, integrate the new Firebase project into your Flutter app using the \`flutterfire CLI\` tool, as described in the \[Add Firebase to your Flutter app]\[firebase] docs. \[firebase]: https\://firebase.google.com/docs/flutter/setup After following these instructions, you're ready to use Firebase to integrate AI in your Flutter app. Start by initializing Firebase: \`\`\`dart import 'package:firebase\_core/firebase\_core.dart'; import 'package:firebase\_ai/firebase\_ai.dart'; import 'package:flutter\_ai\_toolkit/flutter\_ai\_toolkit.dart'; // ... other imports import 'firebase\_options.dart'; // from \`flutterfire config\` void main() async { WidgetsFlutterBinding.ensureInitialized(); await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform); runApp(const App()); } // ...app stuff here \`\`\` With Firebase properly initialized in your Flutter app, you're now ready to create an instance of the Firebase provider. You can do this in two ways. For prototyping, consider the Gemini AI endpoint: \`\`\`dart import 'package:firebase\_ai/firebase\_ai.dart'; import 'package:flutter\_ai\_toolkit/flutter\_ai\_toolkit.dart'; // ... app stuff here class ChatPage extends StatelessWidget { const ChatPage({super.key}); @override Widget build(BuildContext context) => Scaffold( appBar: AppBar(title: const Text(App.title)), // create the chat view, passing in the Firebase provider body: LlmChatView( provider: FirebaseProvider( // Use the Google AI endpoint model: FirebaseAI.googleAI().generativeModel( model: 'gemini-2.5-flash', ), ), ), ); } \`\`\` The \`FirebaseProvider\` class exposes the Firebase AI Logic SDK to the \`LlmChatView\`. Note that you provide a model name (\[you have several options]\[options] from which to choose), but you do not provide an API key. All of that is handled as part of the Firebase project. For production workloads, it's easy to swap in the Firebase AI Logic production endpoint: \`\`\`dart class ChatPage extends StatelessWidget { const ChatPage({super.key}); @override Widget build(BuildContext context) => Scaffold( appBar: AppBar(title: const Text(App.title)), body: LlmChatView( provider: FirebaseProvider( // Use the production endpoint model: FirebaseAI.vertexAI().generativeModel( model: 'gemini-2.5-flash', ), ), ), ); } \`\`\` For a complete example, check out the \[gemini.dart] and \[vertex.dart]\[] examples. \[options]: https\://firebase.google.com/docs/ai-logic/models \[gemini.dart]: https\://github.com/flutter/ai/blob/main/example/lib/gemini/gemini.dart \[vertex.dart]: https\://github.com/flutter/ai/blob/main/example/lib/vertex/vertex.dart

3. **Set up device permissions** To enable your users to take advantage of features like voice input and media attachments, ensure that your app has the necessary permissions: \* \*\*Network access:\*\* To enable network access on macOS, add the following to your \`\*.entitlements\` files: \`\`\`xml ... com.apple.security.network.client \`\`\` To enable network access on Android, ensure that your \`AndroidManifest.xml\` file contains the following: \`\`\`xml ... \`\`\` \* \*\*Microphone access\*\*: Configure according to the \[record package's permission setup instructions]\[record]. \* \*\*File selection\*\*: Follow the \[file\_selector plugin's instructions]\[file]. \* \*\*Image selection\*\*: To take a picture on \_or\_ select a picture from their device, refer to the \[image\_picker plugin's installation instructions]\[image\_picker]. \* \*\*Web photo\*\*: To take a picture on the web, configure the app according to the \[camera plugin's setup instructions]\[camera]. \[camera]: https\://pub.dev/packages/camera#setup \[file]: https\://pub.dev/packages/file\_selector#usage \[image\_picker]: https\://pub.dev/packages/image\_picker#installation \[record]: https\://pub.dev/packages/record#setup-permissions-and-others

\## Examples \*\*firebase\_options.dart\*\* To use the \[Firebase AI Logic example app]\[vertex-ex], place your Firebase configuration details into the \`example/lib/firebase\_options.dart\` file. You can do this with the \`flutterfire CLI\` tool as described in the \[Add Firebase to your Flutter app]\[add-fb] docs \*\*from within the \`example\` directory\*\*. :::note Security considerations for \`firebase\_options.dart\` If your Flutter app calls Gemini APIs directly from the client, do not commit \`firebase\_options.dart\` to a public repository. Anyone could reuse your app configuration to send requests to your AI endpoint, consuming quota and potentially causing billing costs. While this guide shows direct client-side calls for simplicity, for production apps, you should route AI requests through a backend service (for example \[Cloud Functions for Firebase]\(https\://firebase.google.com/docs/functions), \[Cloud Run]\(https\://cloud.google.com/run), or your own server). In that setup, the backend — not the client — controls access, and including \`firebase\_options.dart\` in your repository is safe. You should also review and follow the \[Firebase security checklist]\(https\://firebase.google.com/support/guides/security-checklist). ::: ## Feedback Along the way, as you use this package, please \[log issues and feature requests]\[file-issues] as well as submit any \[code you'd like to contribute]\[submit]. We want your feedback and your contributions to ensure that the AI Toolkit is just as robust and useful as it can be for your real-world apps. \[add-fb]: https\://firebase.google.com/docs/flutter/setup \[example apps]: https\://github.com/flutter/ai/tree/main/example/lib \[file-issues]: https\://github.com/flutter/ai/issues \[submit]: https\://github.com/flutter/ai/pulls \[vertex-ex]: https\://github.com/flutter/ai/blob/main/example/lib/vertex/vertex.dart

----
url: https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.14.0.md
----

\# DevTools 2.14.0 release notes > Release notes for Dart and Flutter DevTools version 2.14.0. The 2.14.0 release of the Dart and Flutter DevTools includes the following changes among other general improvements. To learn more about DevTools, check out the \[DevTools overview]\(https\://docs.flutter.dev/tools/devtools). ## General updates \* Added a link to the new DevTools \[Discord channel]\(https\://discord.com/channels/608014603317936148/958862085297672282) in the About DevTools dialog - \[#4102]\(https\://github.com/flutter/devtools/pull/4102) !\[about-devtools]\(/assets/images/docs/tools/devtools/release-notes/images-2.14.0/image1.png "about devtools") ## Network updates \* Added "Copy as URL" and "Copy as cURL" actions for selected requests in the network profiler (special thanks to \[@jankuss]\(https\://github.com/jankuss)!) - \[#4113]\(https\://github.com/flutter/devtools/pull/4113) !\[network-request-copy-actions]\(/assets/images/docs/tools/devtools/release-notes/images-2.14.0/image2.png "network request copy actions") ## Flutter inspector updates \* Added a setting to control whether hovering over a widget in the inspector displays its properties and values in a hover card - \[#4090]\(https\://github.com/flutter/devtools/pull/4090) ## Debugger updates \* Added auto complete suggestions in the console (special thanks to \[@jankuss]\(https\://github.com/jankuss)!) - \[#4062]\(https\://github.com/flutter/devtools/pull/4062) !\[auto-complete-suggestions]\(/assets/images/docs/tools/devtools/release-notes/images-2.14.0/image3.png "auto complete suggestions") \* Added the option to copy the full file path for a selected library - \[#4147]\(https\://github.com/flutter/devtools/pull/4147) \* Fixed formatting in the debugger exception menu - \[#4066]\(https\://github.com/flutter/devtools/pull/4066) ## Memory updates \* Fixed formatting for memory values in the heap tree view - \[#4153]\(https\://github.com/flutter/devtools/pull/4153) \* Fixed a bug that was preventing GC events from showing up in the memory chart - \[#4131]\(https\://github.com/flutter/devtools/pull/4131) ## Performance updates \* Warn users that the rendering layer toggles in the "More Debugging Options" menu are not available for profile mode apps - \[#4075]\(https\://github.com/flutter/devtools/pull/4075) ## Full commit history To find a complete list of changes since the previous release, check out \[the diff on GitHub]\(https\://github.com/flutter/devtools/compare/v2.13.1...v2.14.0).

----
url: https://docs.flutter.dev/reference/crash-reporting.md
----

\# Flutter crash reporting > How Google uses crash reporting, what is collected, and how to opt out. If you have not opted-out of Flutter's analytics and crash reporting, when a \`flutter\` command crashes, it attempts to send a crash report to Google in order to help Google contribute improvements to Flutter over time. A crash report might contain the following information: \* The name and version of your local operating system. \* The version of Flutter used to run the command. \* The runtime type of the error, for example \`StateError\` or \`NoSuchMethodError\`. \* The stack trace generated by the crash, which contains references to the Flutter CLI's own code and contains no references to your application code. \* A client ID: a constant and unique number generated for the computer where Flutter is installed. It helps us deduplicate multiple identical crash reports coming from the same computer. It also helps us verify if a fix works as intended after you upgrade to the next version of Flutter. Google handles all data reported by this tool in accordance with the \[Google Privacy Policy]\[]. You may review the recently reported data in the \`.dart-tool/dart-flutter-telemetry.log\` file. On macOS or Linux, this log is located in the home directory (\`\~/\`). On Windows, this log is located in the Roaming AppData directory (\`%APPDATA%\`). ## Disabling analytics reporting To opt out of anonymous crash reporting and feature usage statistics, run the following command: \`\`\`console $ flutter --disable-analytics \`\`\` If you opt out of analytics, Flutter sends an opt-out event. This Flutter installation neither sends nor stores any further information. To opt into analytics, run the following command: \`\`\`console $ flutter --enable-analytics \`\`\` To display the current setting, run the following command: \`\`\`console $ flutter config \`\`\` \[Google Privacy Policy]: https\://policies.google.com/privacy

----
url: https://docs.flutter.dev/ai/ai-toolkit/custom-llm-providers
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Custom LLM providers](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

The protocol connecting an LLM and the `LlmChatView` is expressed in the [`LlmProvider` interface](https://pub.dev/documentation/flutter_ai_toolkit/latest/flutter_ai_toolkit/LlmProvider-class.html):

dart

```
abstract class LlmProvider implements Listenable {
  Stream<String> generateStream(String prompt, {Iterable<Attachment> attachments});
  Stream<String> sendMessageStream(String prompt, {Iterable<Attachment> attachments});
  Iterable<ChatMessage> get history;
  set history(Iterable<ChatMessage> history);
}
```

The LLM could be in the cloud or local, it could be hosted in the Google Cloud Platform or on some other cloud provider, it could be a proprietary LLM or open source. Any LLM or LLM-like endpoint that can be used to implement this interface can be plugged into the chat view as an LLM provider. The AI Toolkit comes with two providers out of the box, both of which implement the `LlmProvider` interface that is required to plug the provider into the following:

* The [Firebase AI Logic provider](https://pub.dev/documentation/flutter_ai_toolkit/latest/flutter_ai_toolkit/FirebaseProvider-class.html), which wraps the `firebase_ai` package
* The [Echo provider](https://pub.dev/documentation/flutter_ai_toolkit/latest/flutter_ai_toolkit/EchoProvider-class.html), which is useful as a minimal provider example

## Implementation

[#](#implementation)

To build your own provider, you need to implement the `LlmProvider` interface with these things in mind:

1. Providing for full configuration support

2. Handling history

3. Translating messages and attachments to the underlying LLM

4. Calling the underlying LLM

5. Configuration To support full configurability in your custom provider, you should allow the user to create the underlying model and pass that in as a parameter, as the MyLlmProvider does:

dart

```
class MyLlmProvider extends LlmProvider ... {
  @immutable
  MyLlmProvider({
    required GenerativeModel model,
    ...
  })  : _model = model,
        ...
​
  final GenerativeModel _model;
  ...
}
```

In this way, no matter what changes come to the underlying model in the future, the configuration knobs will all be available to the user of your custom provider.

2. History History is a big part of any provider—not only does the provider need to allow history to be manipulated directly, but it has to notify listeners as it changes. In addition, to support serialization and changing provider parameters, it must also support saving history as part of the construction process.

The Firebase provider handles this as shown:

dart

```
class MyLlmProvider extends LlmProvider with ChangeNotifier {
  @immutable
  MyLlmProvider({
    required GenerativeModel model,
    Iterable<ChatMessage>? history,
    ...
  })  : _model = model,
        _history = history?.toList() ?? [],
        ... { ... }
​
  final GenerativeModel _model;
  final List<ChatMessage> _history;
  ...
​
  @override
  Stream<String> sendMessageStream(
    String prompt, {
    Iterable<Attachment> attachments = const [],
  }) async* {
    final userMessage = ChatMessage.user(prompt, attachments);
    final llmMessage = ChatMessage.llm();
    _history.addAll([userMessage, llmMessage]);
​
    final response = _generateStream(
      prompt: prompt,
      attachments: attachments,
      contentStreamGenerator: _chat!.sendMessageStream,
    );
​
    yield* response.map((chunk) {
      llmMessage.append(chunk);
      return chunk;
    });
​
    notifyListeners();
  }
​
  @override
  Iterable<ChatMessage> get history => _history;
​
  @override
  set history(Iterable<ChatMessage> history) {
    _history.clear();
    _history.addAll(history);
    _chat = _startChat(history);
    notifyListeners();
  }
​
  ...
}
```

You'll notice several things in this code:

* The use of `ChangeNotifier` to implement the `Listenable` method requirements from the `LlmProvider` interface
* The ability to pass initial history in as a constructor parameter
* Notifying listeners when there's a new user prompt/LLM response pair
* Notifying listeners when the history is changed manually
* Creating a new chat when the history changes, using the new history

Essentially, a custom provider manages the history for a single chat session with the underlying LLM. As the history changes, the underlying chat either needs to be kept up to date automatically (as the Firebase provider does when you call the underlying chat-specific methods) or manually recreated (as the Firebase provider does whenever the history is set manually).

3. Messages and attachments

Attachments must be mapped from the standard `ChatMessage` class exposed by the `LlmProvider` type to whatever is handled by the underlying LLM. For example, the Firebase provider maps from the `ChatMessage` class from the AI Toolkit to the `Content` type provided by the Firebase Logic AI SDK, as shown in the following example:

dart

```
import 'package:firebase_ai/firebase_ai.dart';
...
​
class MyLlmProvider extends LlmProvider with ChangeNotifier {
  ...
  static Part _partFrom(Attachment attachment) => switch (attachment) {
        (final FileAttachment a) => DataPart(a.mimeType, a.bytes),
        (final LinkAttachment a) => FilePart(a.url),
      };
​
  static Content _contentFrom(ChatMessage message) => Content(
        message.origin.isUser ? 'user' : 'model',
        [
          TextPart(message.text ?? ''),
          ...message.attachments.map(_partFrom),
        ],
      );
}
```

The `_contentFrom` method is called whenever a user prompt needs to be sent to the underlying LLM. Every provider needs to provide for its own mapping.

4. Calling the LLM

How you call the underlying LLM to implement `generateStream` and `sendMessageStream` methods depends on the protocol it exposes. The Firebase provider in the AI Toolkit handles configuration and history but calls to `generateStream` and `sendMessageStream` each end up in a call to an API from the Firebase Logic AI SDK:

dart

```
class MyLlmProvider extends LlmProvider with ChangeNotifier {
  ...
​
  @override
  Stream<String> generateStream(
    String prompt, {
    Iterable<Attachment> attachments = const [],
  }) =>
      _generateStream(
        prompt: prompt,
        attachments: attachments,
        contentStreamGenerator: (c) => _model.generateContentStream([c]),
      );
​
  @override
  Stream<String> sendMessageStream(
    String prompt, {
    Iterable<Attachment> attachments = const [],
  }) async* {
    final userMessage = ChatMessage.user(prompt, attachments);
    final llmMessage = ChatMessage.llm();
    _history.addAll([userMessage, llmMessage]);
​
    final response = _generateStream(
      prompt: prompt,
      attachments: attachments,
      contentStreamGenerator: _chat!.sendMessageStream,
    );
​
    yield* response.map((chunk) {
      llmMessage.append(chunk);
      return chunk;
    });
​
    notifyListeners();
  }
​
  Stream<String> _generateStream({
    required String prompt,
    required Iterable<Attachment> attachments,
    required Stream<GenerateContentResponse> Function(Content)
        contentStreamGenerator,
  }) async* {
    final content = Content('user', [
      TextPart(prompt),
      ...attachments.map(_partFrom),
    ]);
​
    final response = contentStreamGenerator(content);
    yield* response
        .map((chunk) => chunk.text)
        .where((text) => text != null)
        .cast<String>();
  }
​
  @override
  Iterable<ChatMessage> get history => _history;
​
  @override
  set history(Iterable<ChatMessage> history) {
    _history.clear();
    _history.addAll(history);
    _chat = _startChat(history);
    notifyListeners();
  }
}
```

## Examples

[#](#examples)

The [Firebase provider](https://pub.dev/documentation/flutter_ai_toolkit/latest/flutter_ai_toolkit/FirebaseProvider-class.html) implementation provides a good starting point for your own custom provider. If you'd like to see an example provider implementation with all of the calls to the underlying LLM stripped away, check out the [Echo example app](https://github.com/flutter/ai/blob/main/lib/src/providers/implementations/echo_provider.dart), which simply formats the user's prompt and attachments as Markdown to send back to the user as its response.

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-08-19. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/ai/ai-toolkit/custom-llm-providers.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/ai/ai-toolkit/custom-llm-providers\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/ai/ai-toolkit/custom-llm-providers.md "Report an issue with this page").

----
url: https://docs.flutter.dev/testing/common-errors.md
----

\# Common Flutter errors > How to recognize and resolve common Flutter framework errors.

<!--?code-excerpt path-base="testing/common_errors"?-->

\## Introduction This page explains several frequently-encountered Flutter framework errors (including layout errors) and gives suggestions on how to resolve them. This is a living document with more errors to be added in future revisions, and your contributions are welcomed. Feel free to \[open an issue]\[] or \[submit a pull request]\[] to make this page more useful to you and the Flutter community. \[open an issue]: https\://github.com/flutter/website/issues/new/choose \[submit a pull request]: https\://github.com/flutter/website/pulls ## A solid red or grey screen when running your app Typically called a "red (or grey) screen of death", this is sometimes how Flutter lets you know that there's an error. The red screen can appear when the app runs in debug or profile mode. The grey screen can appear when the app runs in release mode. Generally, these errors occur when there's an uncaught exception (and you might need another try-catch block), or when there is some rendering error, such as an overflow error. The following articles provide some useful insights on debugging this sort of error: \* \[Flutter errors demystified]\[] by Abishek \* \[Understanding and addressing the grey screen in Flutter]\[] by Christopher Nwosu-Madueke \* \[Flutter stuck on white screen]\[] by Kesar Bhimani \[Flutter errors demystified]: https\://medium.com/@hpatilabhi10/flutter-errors-demystified-red-screen-errors-vs-debug-console-errors-acb3b8ed2625 \[Flutter stuck on white screen]: https\://www\.dhiwise.com/post/flutter-stuck-on-white-screen-understanding-and-fixing \[Understanding and addressing the grey screen in Flutter]: https\://medium.com/@LordChris/understanding-and-addressing-the-grey-screen-in-flutter-5e72c31f408f ## 'A RenderFlex overflowed…' RenderFlex overflow is one of the most frequently encountered Flutter framework errors, and you've probably run into it already. \*\*What does the error look like?\*\* When it happens, yellow and black stripes appear, indicating the area of overflow in the app UI. In addition, an error message displays in the debug console: \`\`\`plaintext The following assertion was thrown during layout: A RenderFlex overflowed by 1146 pixels on the right. The relevant error-causing widget was Row lib/errors/renderflex\_overflow\_column.dart:23 The overflowing RenderFlex has an orientation of Axis.horizontal. The edge of the RenderFlex that is overflowing has been marked in the rendering with a yellow and black striped pattern. This is usually caused by the contents being too big for the RenderFlex. (Additional lines of this message omitted) \`\`\` \*\*How might you run into this error?\*\* The error often occurs when a \`Column\` or \`Row\` has a child widget that isn't constrained in its size. For example, the code snippet below demonstrates a common scenario:

<!--?code-excerpt "lib/renderflex_overflow.dart (problem)"?-->

\`\`\`dart Widget build(BuildContext context) { return Row( children: \[ const Icon(Icons.message), Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: \[ Text('Title', style: Theme.of(context).textTheme.headlineMedium), const Text( 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed ' 'do eiusmod tempor incididunt ut labore et dolore magna ' 'aliqua. Ut enim ad minim veniam, quis nostrud ' 'exercitation ullamco laboris nisi ut aliquip ex ea ' 'commodo consequat.', ), ], ), ], ); } \`\`\` In the above example, the \`Column\` tries to be wider than the space the \`Row\` (its parent) can allocate to it, causing an overflow error. Why does the \`Column\` try to do that? To understand this layout behavior, you need to know how the Flutter framework performs layout: "\_To perform layout, Flutter walks the render tree in a depth-first traversal and \*\*passes down size constraints\*\* from parent to child… Children respond by \*\*passing up a size\*\* to their parent object within the constraints the parent established.\_" – \[Flutter architectural overview]\[] In this case, the \`Row\` widget doesn't constrain the size of its children, nor does the \`Column\` widget. Lacking constraints from its parent widget, the second \`Text\` widget tries to be as wide as all the characters it needs to display. The self-determined width of the \`Text\` widget then gets adopted by the \`Column\`, which clashes with the maximum amount of horizontal space its parent, the \`Row\` widget, can provide. \[Flutter architectural overview]: /resources/architectural-overview#layout-and-rendering \*\*How to fix it?\*\* Well, you need to make sure the \`Column\` won't attempt to be wider than it can be. To achieve this, you need to constrain its width. One way to do it is to wrap the \`Column\` in an \`Expanded\` widget:

<!--?code-excerpt "lib/renderflex_overflow.dart (solution)"?-->

\`\`\`dart return const Row( children: \[ Icon(Icons.message), Expanded( child: Column( // code omitted ), ), ], ); \`\`\` Another way is to wrap the \`Column\` in a \`Flexible\` widget and specify a \`flex\` factor. In fact, the \`Expanded\` widget is equivalent to the \`Flexible\` widget with a \`flex\` factor of 1.0, as \[its source code]\[] shows. To further understand how to use the \`Flex\` widget in Flutter layouts, check out \[this 90-second Widget of the Week video]\[flexible-video] on the \`Flexible\` widget. \*\*Further information:\*\* The resources linked below provide further information about this error. \* \[Flexible (Flutter Widget of the Week)]\[flexible-video] \* \[How to debug layout issues with the Flutter Inspector]\[medium-article] \* \[Understanding constraints]\[] \[its source code]: https\://github.com/flutter/flutter/blob/c8e42b47f5ea8b5ff7bf2f2b0a2a8e765f1aa51d/packages/flutter/lib/src/widgets/basic.dart#L5166-L5174 \[flexible-video]: https\://www\.youtube.com/watch?v=CI7x0mAZiY0 \[medium-article]: https\://blog.flutter.dev/how-to-debug-layout-issues-with-the-flutter-inspector-87460a7b9db#738b \[Understanding constraints]: /ui/layout/constraints ## 'RenderBox was not laid out' While this error is pretty common, it's often a side effect of a primary error occurring earlier in the rendering pipeline. \*\*What does the error look like?\*\* The message shown by the error looks like this: \`\`\`plaintext RenderBox was not laid out: RenderViewport#5a477 NEEDS-LAYOUT NEEDS-PAINT NEEDS-COMPOSITING-BITS-UPDATE \`\`\` \*\*How might you run into this error?\*\* Usually, the issue is related to violation of box constraints, and it needs to be solved by providing more information to Flutter about how you'd like to constrain the widgets in question. You can learn more about how constraints work in Flutter on the \[Understanding constraints]\[] page. The \`RenderBox was not laid out\` error is often caused by one of two other errors: \* 'Vertical viewport was given unbounded height' \* 'An InputDecorator...cannot have an unbounded width' []()## 'Vertical viewport was given unbounded height' This is another common layout error you could run into while creating a UI in your Flutter app. \*\*What does the error look like?\*\* The message shown by the error looks like this: \`\`\`plaintext The following assertion was thrown during performResize(): Vertical viewport was given unbounded height. Viewports expand in the scrolling direction to fill their container. In this case, a vertical viewport was given an unlimited amount of vertical space in which to expand. This situation typically happens when a scrollable widget is nested inside another scrollable widget. (Additional lines of this message omitted) \`\`\` \*\*How might you run into this error?\*\* The error is often caused when a \`ListView\` (or other kinds of scrollable widgets such as \`GridView\`) is placed inside a \`Column\`. A \`ListView\` takes all the vertical space available to it, unless it's constrained by its parent widget. However, a \`Column\` doesn't impose any constraint on its children's height by default. The combination of the two behaviors leads to the failure of determining the size of the \`ListView\`.

<!--?code-excerpt "lib/unbounded_height.dart (problem)"?-->

\`\`\`dart Widget build(BuildContext context) { return Center( child: Column( children: \[ const Text('Header'), ListView( children: const \[ ListTile(leading: Icon(Icons.map), title: Text('Map')), ListTile(leading: Icon(Icons.subway), title: Text('Subway')), ], ), ], ), ); } \`\`\` \*\*How to fix it?\*\* To fix this error, specify how tall the \`ListView\` should be. To make it as tall as the remaining space in the \`Column\`, wrap it using an \`Expanded\` widget (as shown in the following example). Otherwise, specify an absolute height using a \`SizedBox\` widget or a relative height using a \`Flexible\` widget.

<!--?code-excerpt "lib/unbounded_height.dart (solution)"?-->

\`\`\`dart Widget build(BuildContext context) { return Center( child: Column( children: \[ const Text('Header'), Expanded( child: ListView( children: const \[ ListTile(leading: Icon(Icons.map), title: Text('Map')), ListTile(leading: Icon(Icons.subway), title: Text('Subway')), ], ), ), ], ), ); } \`\`\` \*\*Further information:\*\* The resources linked below provide further information about this error. \* \[How to debug layout issues with the Flutter Inspector]\[medium-article] \* \[Understanding constraints]\[] ## 'An InputDecorator...cannot have an unbounded width' The error message suggests that it's also related to box constraints, which are important to understand to avoid many of the most common Flutter framework errors. \*\*What does the error look like?\*\* The message shown by the error looks like this: \`\`\`plaintext The following assertion was thrown during performLayout(): An InputDecorator, which is typically created by a TextField, cannot have an unbounded width. This happens when the parent widget does not provide a finite width constraint. For example, if the InputDecorator is contained by a \`Row\`, then its width must be constrained. An \`Expanded\` widget or a SizedBox can be used to constrain the width of the InputDecorator or the TextField that contains it. (Additional lines of this message omitted) \`\`\` \*\*How might you run into the error?\*\* This error occurs, for example, when a \`Row\` contains a \`TextFormField\` or a \`TextField\` but the latter has no width constraint.

<!--?code-excerpt "lib/unbounded_width.dart (problem)"?-->

\`\`\`dart Widget build(BuildContext context) { return MaterialApp( home: Scaffold( appBar: AppBar(title: const Text('Unbounded Width of the TextField')), body: const Row(children: \[TextField()]), ), ); } \`\`\` \*\*How to fix it?\*\* As suggested by the error message, fix this error by constraining the text field using either an \`Expanded\` or \`SizedBox\` widget. The following example demonstrates using an \`Expanded\` widget:

<!--?code-excerpt "lib/unbounded_width.dart (solution)"?-->

\`\`\`dart Widget build(BuildContext context) { return MaterialApp( home: Scaffold( appBar: AppBar(title: const Text('Unbounded Width of the TextField')), body: Row(children: \[Expanded(child: TextFormField())]), ), ); } \`\`\` ## 'Incorrect use of ParentData widget' This error is about missing an expected parent widget. \*\*What does the error look like?\*\* The message shown by the error looks like this: \`\`\`plaintext The following assertion was thrown while looking for parent data: Incorrect use of ParentDataWidget. (Some lines of this message omitted) Usually, this indicates that at least one of the offending ParentDataWidgets listed above is not placed directly inside a compatible ancestor widget. \`\`\` \*\*How might you run into the error?\*\* While Flutter's widgets are generally flexible in how they can be composed together in a UI, a small subset of those widgets expect specific parent widgets. When this expectation can't be satisfied in your widget tree, you're likely to encounter this error. Here is an \_incomplete\_ list of widgets that expect specific parent widgets within the Flutter framework. Feel free to submit a PR (using the doc icon in the top right corner of the page) to expand this list. | Widget | Expected parent widget(s) | |:--------------------------------------|---------------------------:| | \`Flexible\` | \`Row\`, \`Column\`, or \`Flex\` | | \`Expanded\` (a specialized \`Flexible\`) | \`Row\`, \`Column\`, or \`Flex\` | | \`Positioned\` | \`Stack\` | | \`TableCell\` | \`Table\` | \*\*How to fix it?\*\* The fix should be obvious once you know which parent widget is missing. ## 'setState called during build' The \`build\` method in your Flutter code isn't a good place to call \`setState\`, either directly or indirectly. \*\*What does the error look like?\*\* When the error occurs, the following message is displayed in the console: \`\`\`plaintext The following assertion was thrown building DialogPage(dirty, dependencies: \[\_InheritedTheme, \_LocalizationsScope-\[GlobalKey#59a8e]], state: \_DialogPageState#f121e): setState() or markNeedsBuild() called during build. This Overlay widget cannot be marked as needing to build because the framework is already in the process of building widgets. (Additional lines of this message omitted) \`\`\` \*\*How might you run into the error?\*\* In general, this error occurs when the \`setState\` method is called within the \`build\` method. A common scenario where this error occurs is when attempting to trigger a \`Dialog\` from within the \`build\` method. This is often motivated by the need to immediately show information to the user, but \`setState\` should never be called from a \`build\` method. The following snippet seems to be a common culprit of this error:

<!--?code-excerpt "lib/set_state_build.dart (problem)"?-->

\`\`\`dart Widget build(BuildContext context) { // Don't do this. showDialog( context: context, builder: (context) { return const AlertDialog(title: Text('Alert Dialog')); }, ); return const Center( child: Column(children: \[Text('Show Material Dialog')]), ); } \`\`\` This code doesn't make an explicit call to \`setState\`, but it's called by \`showDialog\`. The \`build\` method isn't the right place to call \`showDialog\` because \`build\` can be called by the framework for every frame, for example, during an animation. \*\*How to fix it?\*\* One way to avoid this error is to use the \`Navigator\` API to trigger the dialog as a route. In the following example, there are two pages. The second page has a dialog to be displayed upon entry. When the user requests the second page by clicking a button on the first page, the \`Navigator\` pushes two routes–one for the second page and another for the dialog.

<!--?code-excerpt "lib/set_state_build.dart (solution)"?-->

\`\`\`dart class FirstScreen extends StatelessWidget { const FirstScreen({super.key}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('First Screen')), body: Center( child: ElevatedButton( child: const Text('Launch screen'), onPressed: () { // Navigate to the second screen using a named route. Navigator.pushNamed(context, '/second'); // Immediately show a dialog upon loading the second screen. Navigator.push( context, PageRouteBuilder( barrierDismissible: true, opaque: false, pageBuilder: (\_, anim1, anim2) => const MyDialog(), ), ); }, ), ), ); } } \`\`\` ## \`The ScrollController is attached to multiple scroll views\` This error can occur when multiple scrolling widgets (such as \`ListView\`) appear on the screen at the same time. It's more likely for this error to occur on a web or desktop app, than a mobile app since it's rare to encounter this scenario on mobile. For more information and to learn how to fix, check out the following video on \[\`PrimaryScrollController\`]\[controller-video]: \[controller-video]: https\://api.flutter.dev/flutter/widgets/PrimaryScrollController-class.html ## References To learn more about how to debug errors, especially layout errors in Flutter, check out the following resources: \* \[How to debug layout issues with the Flutter Inspector]\[medium-article] \* \[Understanding constraints]\[] \* \[Flutter architectural overview]\[] \[Flutter architectural overview]: /resources/architectural-overview#layout-and-rendering

----
url: https://docs.flutter.dev/add-to-app/performance.md
----

\# Load sequence, performance, and memory > What are the steps involved when showing a Flutter UI. This page describes the breakdown of the steps involved to show a Flutter UI. Knowing this, you can make better, more informed decisions about when to pre-warm the Flutter engine, which operations are possible at which stage, and the latency and memory costs of those operations. ## Loading Flutter Android and iOS apps (the two supported platforms for integrating into existing apps), full Flutter apps, and add-to-app patterns have a similar sequence of conceptual loading steps when displaying the Flutter UI. ### Finding the Flutter resources Flutter's engine runtime and your application's compiled Dart code are both bundled as shared libraries on Android and iOS. The first step of loading Flutter is to find those resources in your .apk/.ipa/.app (along with other Flutter assets such as images, fonts, and JIT code, if applicable). This happens when you construct a \`FlutterEngine\` for the first time on both \*\*\[Android]\[android-engine]\*\* and \*\*\[iOS]\[ios-engine]\*\* APIs. ### Loading the Flutter library After it's found, the engine's shared libraries are memory loaded once per process. On \*\*Android\*\*, this also happens when the \[\`FlutterEngine\`]\[android-engine] is constructed because the JNI connectors need to reference the Flutter C++ library. On \*\*iOS\*\*, this happens when the \[\`FlutterEngine\`]\[ios-engine] is first run, such as by running \[\`runWithEntrypoint:\`]\[]. ### Starting the Dart VM The Dart runtime is responsible for managing Dart memory and concurrency for your Dart code. In JIT mode, it's additionally responsible for compiling the Dart source code into machine code during runtime. A single Dart runtime exists per application session on Android and iOS. A one-time Dart VM start is done when constructing the \[\`FlutterEngine\`]\[android-engine] for the first time on \*\*Android\*\* and when \[running a Dart entrypoint]\[ios-engine] for the first time on \*\*iOS\*\*. At this point, your Dart code's \[snapshot]\[] is also loaded into memory from your application's files. This is a generic process that also occurs if you used the \[Dart SDK]\[] directly, without the Flutter engine. The Dart VM never shuts down after it's started. ### Creating and running a Dart Isolate After the Dart runtime is initialized, the Flutter engine's usage of the Dart runtime is the next step. This is done by starting a \[Dart \`Isolate\`]\[] in the Dart runtime. The isolate is Dart's container for memory and threads. A number of \[auxiliary threads]\[] on the host platform are also created at this point to support the isolate, such as a thread for offloading GPU handling and another for image decoding. One isolate exists per \`FlutterEngine\` instance, and multiple isolates can be hosted by the same Dart VM. On \*\*Android\*\*, this happens when you call \[\`DartExecutor.executeDartEntrypoint()\`]\[] on a \`FlutterEngine\` instance. On \*\*iOS\*\*, this happens when you call \[\`runWithEntrypoint:\`]\[] on a \`FlutterEngine\`. At this point, your Dart code's selected entrypoint (the \`main()\` function of your Dart library's \`main.dart\` file, by default) is executed. If you called the Flutter function \[\`runApp()\`]\[] in your \`main()\` function, then your Flutter app or your library's widget tree is also created and built. If you need to prevent certain functionalities from executing in your Flutter code, then the \`AppLifecycleState.detached\` enum value indicates that the \`FlutterEngine\` isn't attached to any UI components such as a \`FlutterViewController\` on iOS or a \`FlutterActivity\` on Android. ### Attaching a UI to the Flutter engine A standard, full Flutter app moves to reach this state as soon as the app is launched. In an add-to-app scenario, this happens when you attach a \`FlutterEngine\` to a UI component such as by calling \[\`startActivity()\`]\[] with an \[\`Intent\`]\[] built using \[\`FlutterActivity.withCachedEngine()\`]\[] on \*\*Android\*\*. Or, by presenting a \[\`FlutterViewController\`]\[] initialized by using \[\`initWithEngine: nibName: bundle:\`]\[] on \*\*iOS\*\*. This is also the case if a Flutter UI component was launched without pre-warming a \`FlutterEngine\` such as with \[\`FlutterActivity.createDefaultIntent()\`]\[] on \*\*Android\*\*, or with \[\`FlutterViewController initWithProject: nibName: bundle:\`]\[] on \*\*iOS\*\*. An implicit \`FlutterEngine\` is created in these cases. Behind the scene, both platform's UI components provide the \`FlutterEngine\` with a rendering surface such as a \[\`Surface\`]\[] on \*\*Android\*\* or a \[CAEAGLLayer]\[] or \[CAMetalLayer]\[] on \*\*iOS\*\*. At this point, the \[\`Layer\`]\[] tree generated by your Flutter program, per frame, is converted into OpenGL (or Vulkan or Metal) GPU instructions. \[android-engine]: https\://api.flutter.dev/javadoc/io/flutter/embedding/engine/FlutterEngine.html \[auxiliary threads]: https\://github.com/flutter/flutter/blob/main/docs/about/The-Engine-architecture.md#threading \[CAEAGLLayer]: https\://developer.apple.com/documentation/quartzcore/caeagllayer \[CAMetalLayer]: https\://developer.apple.com/documentation/quartzcore/cametallayer \[Dart \`Isolate\`]: https\://api.dart.dev/dart-isolate/Isolate-class.html \[Dart SDK]: https\://dart.dev/tools/sdk \[\`DartExecutor.executeDartEntrypoint()\`]: https\://api.flutter.dev/javadoc/io/flutter/embedding/engine/dart/DartExecutor.html#executeDartEntrypoint-io.flutter.embedding.engine.dart.DartExecutor.DartEntrypoint- \[\`FlutterActivity.createDefaultIntent()\`]: https\://api.flutter.dev/javadoc/io/flutter/embedding/android/FlutterActivity.html#createDefaultIntent-android.content.Context- \[\`FlutterActivity.withCachedEngine()\`]: https\://api.flutter.dev/javadoc/io/flutter/embedding/android/FlutterActivity.html#withCachedEngine-java.lang.String- \[\`FlutterViewController\`]: https\://api.flutter.dev/ios-embedder/interface\_flutter\_view\_controller.html \[\`FlutterViewController initWithProject: nibName: bundle:\`]: https\://api.flutter.dev/ios-embedder/interface\_flutter\_view\_controller.html#aa3aabfb89e958602ce6a6690c919f655 \[\`initWithEngine: nibName: bundle:\`]: https\://api.flutter.dev/ios-embedder/interface\_flutter\_view\_controller.html#a0aeea9525c569d5efbd359e2d95a7b31 \[\`Intent\`]: https\://developer.android.com/reference/android/content/Intent.html \[ios-engine]: https\://api.flutter.dev/ios-embedder/interface\_flutter\_engine.html \[\`Layer\`]: https\://api.flutter.dev/flutter/rendering/Layer-class.html \[multiple Flutters]: /add-to-app/multiple-flutters \[\`runApp()\`]: https\://api.flutter.dev/flutter/widgets/runApp.html \[\`runWithEntrypoint:\`]: https\://api.flutter.dev/ios-embedder/interface\_flutter\_engine.html#a019d6b3037eff6cfd584fb2eb8e9035e \[snapshot]: https\://github.com/dart-lang/sdk/wiki/Snapshots \[\`startActivity()\`]: https\://developer.android.com/reference/android/content/Context#startActivity(android.content.Intent) \[\`Surface\`]: https\://developer.android.com/reference/android/view/Surface

----
url: https://docs.flutter.dev/platform-integration/web/initialization
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Flutter web app initialization](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

This page details the initialization process for Flutter web apps and how it can be customized.

## Bootstrapping

[#](#bootstrapping)

The `flutter build web` command produces a script called `flutter_bootstrap.js` in the build output directory (`build/web`). This file contains the JavaScript code needed to initialize and run your Flutter app. You can use this script by placing an async-script tag for it in your `index.html` file in the `web` subdirectory of your Flutter app:

html

```
<html>
  <body>
    <script src="flutter_bootstrap.js" async></script>
  </body>
</html>
```

Alternatively, you can inline the entire contents of the `flutter_bootstrap.js` file by inserting the template token `{{flutter_bootstrap_js}}` in your `index.html` file:

html

```
<html>
  <body>
    <script>
      {{flutter_bootstrap_js}}
    </script>
  </body>
</html>
```

The `{{flutter_bootstrap_js}}` token is replaced with the contents of the `flutter_bootstrap.js` file when the `index.html` file is copied to the output directory (`build/web`) during the build step.

[]()

## Customize initialization

[#](#customize-initialization)

By default, `flutter build web` generates a `flutter_bootstrap.js` file that does a simple initialization of your Flutter app. However, in some scenarios, you might have a reason to customize this initialization process, such as:

* Setting a custom Flutter configuration for your app.
* Integrating a custom service worker for offline support or caching.
* Writing custom JavaScript code to run at different stages of the startup process.

To write your own custom bootstrapping logic instead of using the default script produced by the build step, you can place a `flutter_bootstrap.js` file in the `web` subdirectory of your project, which is copied over and used instead of the default script produced by the build. This file is also templated, and you can insert several special tokens that the build step substitutes at build time when copying the `flutter_bootstrap.js` file to the output directory. The following table lists the tokens that the build step will substitute in either the `flutter_bootstrap.js` or `index.html` files:

| Token                                | Replaced with                                                                                                                                                                                                                             |
| ------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `{{flutter_js}}`                     | The JavaScript code that makes the `FlutterLoader` object available in the `_flutter.loader` global variable. (See the `_flutter.loader.load() API` section below for more details.)                                                      |
| `{{flutter_build_config}}`           | A JavaScript statement that sets metadata produced by the build process which gives the `FlutterLoader` information needed to properly bootstrap your application.                                                                        |
| `{{flutter_service_worker_version}}` | A unique number representing the build version for custom service worker configurations (Flutter no longer generates a service worker by default; see the [Web FAQ](/platform-integration/web/faq#how-do-i-configure-a-service-worker) ). |
| `{{flutter_bootstrap_js}}`           | As mentioned above, this inlines the contents of the `flutter_bootstrap.js` file directly into the `index.html` file. Note that this token can only be used in the `index.html` and not the `flutter_bootstrap.js` file itself.           |

[]()

## Write a custom bootstrap script

[#](#custom-bootstrap-js)

Any custom `flutter_bootstrap.js` script needs to have three components in order to successfully start your Flutter app:

* A `{{flutter_js}}` token, to make `_flutter.loader` available.
* A `{{flutter_build_config}}` token, which provides information about the build to the `FlutterLoader` needed to start your app.
* A call to `_flutter.loader.load()`, which actually starts the app.

The most basic `flutter_bootstrap.js` file would look something like this:

js

```
{{flutter_js}}
{{flutter_build_config}}
​
_flutter.loader.load();
```

## Customize the Flutter loader

[#](#customize-the-flutter-loader)

The `_flutter.loader.load()` JavaScript API can be invoked with optional arguments to customize initialization behavior:

| Name                 | Description                                                                                                                   | JS type    |
| -------------------- | ----------------------------------------------------------------------------------------------------------------------------- | ---------- |
| `config`             | The Flutter configuration of your app.                                                                                        | `Object`   |
| `onEntrypointLoaded` | The function called when the engine is ready to be initialized. Receives an `engineInitializer` object as its only parameter. | `Function` |

The `config` argument is an object that can have the following optional fields:

| Name                        | Description                                                                                                                                                                                                                                                                                                                                                                                                                              | Dart type     |
| --------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- |
| `assetBase`                 | The base URL of the `assets` directory of the app. Add this when Flutter loads from a different domain or subdirectory than the actual web app. You might need this when you embed Flutter web into another app, or when you deploy its assets to a CDN.                                                                                                                                                                                 | `String`      |
| `canvasKitBaseUrl`          | The base URL from where `canvaskit.wasm` is downloaded.                                                                                                                                                                                                                                                                                                                                                                                  | `String`      |
| `canvasKitVariant`          | The CanvasKit variant to download. Your options cover: 1. `auto` : Downloads the optimal variant for the browser. The option defaults to this value. 2. `full` : Downloads the full variant of CanvasKit that works in all browsers. 3. `chromium` : Downloads a smaller variant of CanvasKit that uses Chromium compatible APIs. ***Warning*** : Don't use the `chromium` option unless you plan on only using Chromium-based browsers. | `String`      |
| `canvasKitForceCpuOnly`     | When `true`, forces CPU-only rendering in CanvasKit (the engine won't use WebGL).                                                                                                                                                                                                                                                                                                                                                        | `bool`        |
| `canvasKitMaximumSurfaces`  | The maximum number of overlay surfaces that the CanvasKit renderer can use.                                                                                                                                                                                                                                                                                                                                                              | `double`      |
| `debugShowSemanticsNodes`   | If `true`, Flutter visibly renders the semantics tree onscreen (for debugging).                                                                                                                                                                                                                                                                                                                                                          | `bool`        |
| `entrypointBaseUrl`         | The base URL of your Flutter app's entrypoint. Defaults to "/".                                                                                                                                                                                                                                                                                                                                                                          | `String`      |
| `fontFallbackBaseUrl`       | Base URL for downloading fallback fonts when bundled fonts lack required glyphs. Defaults to `"https://fonts.gstatic.com/s/"` .                                                                                                                                                                                                                                                                                                          | `String`      |
| `forceSingleThreadedSkwasm` | Forces single-threaded SkWasm mode for compatibility (only impacts apps compiled with `--wasm` when [WebAssembly](/platform-integration/web/wasm) is selected at runtime).                                                                                                                                                                                                                                                               | `bool`        |
| `hostElement`               | HTML Element into which Flutter renders the app (see [embedding Flutter views](/platform-integration/web/embedding-flutter-web) ). When not set, Flutter web takes over the whole page.                                                                                                                                                                                                                                                  | `HtmlElement` |
| `multiViewEnabled`          | Enables multi-view mode for [embedding Flutter views](/platform-integration/web/embedding-flutter-web) into host DOM elements dynamically.                                                                                                                                                                                                                                                                                               | `bool`        |
| `nonce`                     | A [Content Security Policy (CSP) nonce](https://developer.mozilla.org/docs/Web/HTML/Global_attributes/nonce) string applied to inline scripts and styles created by the engine.                                                                                                                                                                                                                                                          | `String`      |

## forceSingleThreadedSkwasm

[#](#forcesinglethreadedskwasm)

A boolean flag to force the Skia WebAssembly (skwasm) renderer to run in **single-threaded mode**. This is useful if:

* Your environment doesn't support multi-threaded WASM. For example, `SharedArrayBuffer` is not available or required security headers are missing.
* You want maximum browser compatibility.
* Use `false` (default) to allow multi-threaded rendering when supported, which improves performance.

## Example usage

[#](#example-usage)

js

```
_flutter.loader.load({
  config: {
    forceSingleThreadedSkwasm: true,
  },
});
```

## Example: Customizing Flutter configuration based on URL query parameters

[#](#example-customizing-flutter-configuration-based-on-url-query-parameters)

The following example shows a custom `flutter_bootstrap.js` that enables single-threaded mode when a `force_st` query parameter is present in the URL:

js

```
{{flutter_js}}
{{flutter_build_config}}
​
const searchParams = new URLSearchParams(window.location.search);
const forceSt = searchParams.has('force_st');
const userConfig = forceSt ? { forceSingleThreadedSkwasm: true } : {};
_flutter.loader.load({
  config: userConfig,
});
```

This script evaluates the `URLSearchParams` of the page to determine whether the user passed a `force_st` query parameter and configures the app accordingly.

## The onEntrypointLoaded callback

[#](#the-onentrypointloaded-callback)

You can also pass an `onEntrypointLoaded` callback into the `load` API in order to perform custom logic at different parts of the initialization process. The initialization process is split into the following stages:

* **Loading the entrypoint script**

  The `load` function calls the `onEntrypointLoaded` callback once the main entrypoint script has been downloaded and run by the browser. Flutter also calls `onEntrypointLoaded` on every hot restart during development.

* **Initializing the Flutter engine**

  The `onEntrypointLoaded` callback receives an **engine initializer** object as its only parameter. Use the engine initializer `initializeEngine()` function to set the run-time configuration, like `multiViewEnabled: true`, and start the Flutter web engine.

* **Running the app**

  The `initializeEngine()` function returns a [`Promise`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) that resolves with an **app runner** object. The app runner has a single method, `runApp()`, that runs the Flutter app.

* **Adding views to (or removing views from) an app**

  The `runApp()` method returns a **flutter app** object. In multi-view mode, the `addView` and `removeView` methods can be used to manage app views from the host app. To learn more, check out [Embedded mode](/platform-integration/web/embedding-flutter-web#embedded-mode).

## Example: Display a progress indicator

[#](#example-display-a-progress-indicator)

To give the user of your application feedback during the initialization process, use the hooks provided for each stage to update the DOM:

js

```
{{flutter_js}}
{{flutter_build_config}}
​
const loading = document.createElement('div');
document.body.appendChild(loading);
loading.textContent = "Loading Entrypoint...";
_flutter.loader.load({
  onEntrypointLoaded: async function(engineInitializer) {
    loading.textContent = "Initializing engine...";
    // If you have a `config`, pass it here. The config given to `load()`
    // is not forwarded when you supply your own `onEntrypointLoaded`.
    const appRunner = await engineInitializer.initializeEngine();
​
    loading.textContent = "Running app...";
    await appRunner.runApp();
  }
});
```

## Common warnings

[#](#common-warnings)

This section describes common web build and initialization warnings and how to resolve them.

### Service worker version deprecation

[#](#service-worker-version-deprecation)

If you experience a warning similar to the following:

```
Warning: In index.html:37: Local variable for "serviceWorkerVersion" is deprecated.
Use "" template token instead.
```

Delete the following line from the `web/index.html` file:

web/index.html

html

```
var serviceWorkerVersion = null;
```

### FlutterLoader.loadEntrypoint deprecation

[#](#flutterloader-loadentrypoint-deprecation)

If you experience a warning similar to the following:

```
Warning: In index.html:40: "FlutterLoader.loadEntrypoint" is deprecated.
Use "FlutterLoader.load" instead.
```

This warning occurs when an older `web/index.html` file uses `_flutter.loader.loadEntrypoint()` instead of `_flutter.loader.load()`. The CLI message references the `FlutterLoader` interface, which JavaScript code accesses through `_flutter.loader`.

If your app uses the default initialization and doesn't require custom bootstrapping, replace the legacy script block in `web/index.html` with the standard script tag:

web/index.html

html

```
<script src="flutter_bootstrap.js" async></script>
```

If you customized your initialization logic, update your bootstrap code in `web/index.html` (or a custom `web/flutter_bootstrap.js` file) to call `_flutter.loader.load()` instead:

js

```
_flutter.loader.load({
  onEntrypointLoaded: async function(engineInitializer) {
    const appRunner = await engineInitializer.initializeEngine();
    await appRunner.runApp();
  }
});
```

To review an example that updates DOM elements during startup, consult the [progress indicator example](#example-display-a-progress-indicator).

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-09-08. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/platform-integration/web/initialization.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/platform-integration/web/initialization\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/platform-integration/web/initialization.md "Report an issue with this page").

----
url: https://docs.flutter.dev/cookbook/design/snackbars.md
----

\# Display a snackbar > How to implement a snackbar to display messages.

<!--?code-excerpt path-base="cookbook/design/snackbars/"?-->

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. !\[SnackBar Demo]\(/assets/images/docs/cookbook/snackbar.webp){:.site-mobile-screenshot} In Material Design, this is the job of a \[\`SnackBar\`]\[]. This recipe implements a snackbar using the following steps: 1. Create a \`Scaffold\`. 2. Display a \`SnackBar\`. 3. Provide an optional action. ## 1. Create a \`Scaffold\` 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\`]\[] widget, from the \[material library]\[], creates this visual structure and ensures that important widgets don't overlap.

<!--?code-excerpt "lib/partial.dart (Scaffold)"?-->

\`\`\`dart return MaterialApp( title: 'SnackBar Demo', home: Scaffold( appBar: AppBar(title: const Text('SnackBar Demo')), body: const SnackBarPage(), ), ); \`\`\` ## 2. Display a \`SnackBar\` With the \`Scaffold\` in place, display a \`SnackBar\`. First, create a \`SnackBar\`, then display it using \`ScaffoldMessenger\`.

<!--?code-excerpt "lib/partial.dart (DisplaySnackBar)"?-->

\`\`\`dart const snackBar = SnackBar(content: Text('Yay! A SnackBar!')); // Find the ScaffoldMessenger in the widget tree // and use it to show a SnackBar. ScaffoldMessenger.of(context).showSnackBar(snackBar); \`\`\` :::note To learn more, watch the following two-minute Widget of the Week video on the \`ScaffoldMessenger\` widget: ::: ## 3. Provide an optional action You might want to provide an action to the user when the \`SnackBar\` is displayed. For example, if the user accidentally deletes a message, an \`action\` in the \`SnackBar\` could allow recovering the message. Here's an example that provides an additional \`action\` to the \`SnackBar\` widget:

<!--?code-excerpt "lib/main.dart (SnackBarAction)"?-->

\`\`\`dart final snackBar = SnackBar( content: const Text('Yay! A SnackBar!'), action: SnackBarAction( label: 'Undo', onPressed: () { // Some code to undo the change. }, ), ); \`\`\` ## Interactive example :::note In this example, the \`SnackBar\` displays when a user taps a button. For more information on working with user input, see the \[Gestures]\[] section of the cookbook. :::

<!--?code-excerpt "lib/main.dart"?-->

\`\`\`dartpad title="Flutter snackbar hands-on example in DartPad" run="true" import 'package:flutter/material.dart'; void main() => runApp(const SnackBarDemo()); class SnackBarDemo extends StatelessWidget { const SnackBarDemo({super.key}); @override Widget build(BuildContext context) { return MaterialApp( title: 'SnackBar Demo', home: Scaffold( appBar: AppBar(title: const Text('SnackBar Demo')), body: const SnackBarPage(), ), ); } } class SnackBarPage extends StatelessWidget { const SnackBarPage({super.key}); @override Widget build(BuildContext context) { return Center( child: ElevatedButton( onPressed: () { final snackBar = SnackBar( content: const Text('Yay! A SnackBar!'), action: SnackBarAction( label: 'Undo', onPressed: () { // Some code to undo the change. }, ), ); // Find the ScaffoldMessenger in the widget tree // and use it to show a SnackBar. ScaffoldMessenger.of(context).showSnackBar(snackBar); }, child: const Text('Show SnackBar'), ), ); } } \`\`\` \[Gestures]: /cookbook/gestures \[material library]: https\://pub.dev/documentation/material\_ui/latest/material\_ui \[\`Scaffold\`]: https\://pub.dev/documentation/material\_ui/latest/material\_ui/Scaffold-class.html \[\`SnackBar\`]: https\://pub.dev/documentation/material\_ui/latest/material\_ui/SnackBar-class.html

----
url: https://docs.flutter.dev/platform-integration/macos/platform-views.md
----

\# Hosting native macOS views in your Flutter app with Platform Views > Learn how to host native macOS views in your Flutter app with Platform Views.

<!--?code-excerpt path-base="platform_integration/platform_views"?-->

Platform views allow you to embed native views in a Flutter app, so you can apply transforms, clips, and opacity to the native view from Dart. This allows you, for example, to use the native web views directly inside your Flutter app. :::note This page discusses how to host your own native macOS views within a Flutter app. If you'd like to embed native Android views in your Flutter app, see \[Hosting native Android views]\[]. If you'd like to embed native iOS views in your Flutter app, see \[Hosting native iOS views]\[]. ::: \[Hosting native Android views]: /platform-integration/android/platform-views \[Hosting native iOS views]: /platform-integration/ios/platform-views :::version-note Platform view support on macOS isn't fully functional as of the current release. For example, gesture support isn't yet available on macOS. Stay tuned for a future stable release. ::: macOS uses Hybrid composition, which means that the native \`NSView\` is appended to the view hierarchy. To create a platform view on macOS, use the following instructions: ## On the Dart side On the Dart side, create a \`Widget\` and add the build implementation, as shown in the following steps: In the Dart widget file, make changes similar to those shown in \`native\_view\_example.dart\`: 1. Add the following imports:

<!--?code-excerpt "lib/native_view_example_4.dart (import)"?-->

\`\`\`dart import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; \`\`\` 1. Implement a \`build()\` method:

<!--?code-excerpt "lib/native_view_example_4.dart (macos-composition)"?-->

\`\`\`dart Widget build(BuildContext context) { // This is used in the platform side to register the view. const String viewType = ''; // Pass parameters to the platform side. final Map creationParams = {}; return AppKitView( viewType: viewType, layoutDirection: TextDirection.ltr, creationParams: creationParams, creationParamsCodec: const StandardMessageCodec(), ); } \`\`\` For more information, check out the \[\`AppKitView\`]\[] API docs. \[\`AppKitView\`]: https\://api.flutter.dev/flutter/widgets/AppKitView-class.html ## On the platform side Implement the factory and the platform view. The \`NativeViewFactory\` creates the platform view, and the platform view provides a reference to the \`NSView\`. For example, \`NativeView\.swift\`: \`\`\`swift title="NativeView\.swift" import Cocoa import FlutterMacOS class NativeViewFactory: NSObject, FlutterPlatformViewFactory { private var messenger: FlutterBinaryMessenger init(messenger: FlutterBinaryMessenger) { self.messenger = messenger super.init() } func create( withViewIdentifier viewId: Int64, arguments args: Any? ) -> NSView { return NativeView( viewIdentifier: viewId, arguments: args, binaryMessenger: messenger) } /// Implementing this method is only necessary when /// the \`arguments\` in \`createWithFrame\` is not \`nil\`. public func createArgsCodec() -> (FlutterMessageCodec & NSObjectProtocol)? { return FlutterStandardMessageCodec.sharedInstance() } } class NativeView: NSView { init( viewIdentifier viewId: Int64, arguments args: Any?, binaryMessenger messenger: FlutterBinaryMessenger? ) { super.init(frame: CGRect(x: 0, y: 0, width: 200, height: 200)) wantsLayer = true layer?.backgroundColor = NSColor.systemBlue.cgColor // macOS views can be created here createNativeView(view: self) } required init?(coder nsCoder: NSCoder) { super.init(coder: nsCoder) } func createNativeView(view \_view: NSView) { let nativeLabel = NSTextField() nativeLabel.frame = CGRect(x: 0, y: 0, width: 180, height: 48.0) nativeLabel.stringValue = "Native text from macOS" nativeLabel.textColor = NSColor.black nativeLabel.font = NSFont.systemFont(ofSize: 14) nativeLabel.isBezeled = false nativeLabel.focusRingType = .none nativeLabel.isEditable = true nativeLabel.sizeToFit() \_view\.addSubview(nativeLabel) } } \`\`\` Finally, register the platform view. This can be done in an app or a plugin. For app registration, modify the App's \`MainFlutterWindow\.swift\`: \`\`\`swift title="MainFlutterWindow\.swift" import Cocoa import FlutterMacOS class MainFlutterWindow: NSWindow { override func awakeFromNib() { // ... let registrar = flutterViewController.registrar(forPlugin: "plugin-name") let factory = NativeViewFactory(messenger: registrar.messenger) registrar.register( factory, withId: "") } } \`\`\` For plugin registration, modify the plugin's main file: \`\`\`swift title="Plugin.swift" import Cocoa import FlutterMacOS public class Plugin: NSObject, FlutterPlugin { public static func register(with registrar: FlutterPluginRegistrar) { let factory = NativeViewFactory(messenger: registrar.messenger) registrar.register(factory, withId: "") } } \`\`\` For more information, check out the API docs for: \* \[\`FlutterPlatformViewFactory\`]\[] \* \[\`FlutterPlatformView\`]\[] \* \[\`PlatformView\`]\[] \[\`FlutterPlatformView\`]: https\://api.flutter.dev/ios-embedder/protocol\_flutter\_platform\_view-p.html \[\`FlutterPlatformViewFactory\`]: https\://api.flutter.dev/ios-embedder/protocol\_flutter\_platform\_view\_factory-p.html \[\`PlatformView\`]: https\://api.flutter.dev/javadoc/io/flutter/plugin/platform/PlatformView\.html ## Putting it together When implementing the \`build()\` method in Dart, you can use \[\`defaultTargetPlatform\`]\[] to detect the platform, and decide which widget to use:

<!--?code-excerpt "lib/native_view_example_4.dart (together-widget)"?-->

\`\`\`dart Widget build(BuildContext context) { // This is used in the platform side to register the view. const String viewType = ''; // Pass parameters to the platform side. final Map creationParams = {}; switch (defaultTargetPlatform) { case TargetPlatform.android: // return widget on Android. case TargetPlatform.iOS: // return widget on iOS. case TargetPlatform.macOS: // return widget on macOS. default: throw UnsupportedError('Unsupported platform view'); } } \`\`\` \[\`defaultTargetPlatform\`]: https\://api.flutter.dev/flutter/foundation/defaultTargetPlatform.html ## Performance Platform views in Flutter come with performance trade-offs. For example, in a typical Flutter app, the Flutter UI is composed on a dedicated raster thread. This allows Flutter apps to be fast, as this thread is rarely blocked. When a platform view is rendered with hybrid composition, the Flutter UI continues to be composed from the dedicated raster thread, but the platform view performs graphics operations on the platform thread. To rasterize the combined contents, Flutter performs synchronization between its raster thread and the platform thread. As such, any slow or blocking operations on the platform thread can negatively impact Flutter graphics performance.

----
url: https://docs.flutter.dev/ui/layout.md
----

\# Layouts in Flutter > Learn how Flutter's layout mechanism works and how to build your app's layout. ## Overview :::secondary What's the point? \* Layouts in Flutter are built with widgets. \* Widgets are classes used to build UIs. \* Widgets are also used to build UI elements. \* Compose simple widgets to build complex widgets. ::: The core of Flutter's layout mechanism is widgets. In Flutter, almost everything is a widget—even layout models are widgets. The images, icons, and text that you see in a Flutter app are all widgets. But things you don't see are also widgets, such as the rows, columns, and grids that arrange, constrain, and align the visible widgets. You create a layout by composing widgets to build more complex widgets. ## Conceptual example In the following example, the first screenshot displays three icons with labels and the second screenshot includes the visual layout for rows and columns. In the second screenshot, \`debugPaintSizeEnabled\` is set to \`true\` so you can see the visual layout.Here's a diagram of the widget tree for the previous example: Most of this should look as you might expect, but you might be wondering about the containers (shown in pink). \[\`Container\`]\[] is a widget class that allows you to customize its child widget. Use a \`Container\` when you want to add padding, margins, borders, or background color, to name some of its capabilities. Each \[\`Text\`]\[] widget is placed in a \`Container\` to add margins. The entire \[\`Row\`]\[] is also placed in a \`Container\` to add padding around the row. The rest of the UI is controlled by properties. Set an \[\`Icon\`]\[]'s color using its \`color\` property. Use the \`Text.style\` property to set the font, its color, weight, and so on. Columns and rows have properties that allow you to specify how their children are aligned vertically or horizontally, and how much space the children should occupy. :::note Most of the screenshots in this tutorial are displayed with \`debugPaintSizeEnabled\` set to \`true\` so you can see the visual layout. For more information, see \[Debugging layout issues visually]\[]. ::: \[\`Container\`]: https\://api.flutter.dev/flutter/widgets/Container-class.html \[Debugging layout issues visually]: /tools/devtools/inspector#debugging-layout-issues-visually \[\`Icon\`]: https\://pub.dev/documentation/material\_ui/latest/material\_ui/Icons-class.html \[\`Row\`]: https\://api.flutter.dev/flutter/widgets/Row-class.html \[\`Text\`]: https\://api.flutter.dev/flutter/widgets/Text-class.html ## Lay out a widget How do you lay out a single widget in Flutter? This section shows you how to create and display a simple widget. It also shows the entire code for a simple Hello World app. In Flutter, it takes only a few steps to put text, an icon, or an image on the screen. ### 1. Select a layout widget Choose from a variety of \[layout widgets]\[] based on how you want to align or constrain a visible widget, as these characteristics are typically passed on to the contained widget. For example, you could use the \[\`Center\`]\[] layout widget to center a visible widget horizontally and vertically: \`\`\`dart Center( // Content to be centered here. ) \`\`\` \[\`Center\`]: https\://api.flutter.dev/flutter/widgets/Center-class.html \[layout widgets]: /ui/widgets/layout ### 2. Create a visible widget Choose a \[visible widget]\[] for your app to contain visible elements, such as \[text]\[], \[images]\[], or \[icons]\[]. For example, you could use the \[\`Text\`]\[] widget display some text: \`\`\`dart Text('Hello World') \`\`\` \[icons]: https\://pub.dev/documentation/material\_ui/latest/material\_ui/Icons-class.html \[images]: https\://api.flutter.dev/flutter/widgets/Image-class.html \[text]: https\://api.flutter.dev/flutter/widgets/Text-class.html \[\`Text\`]: https\://api.flutter.dev/flutter/widgets/Text-class.html \[visible widget]: /ui/widgets ### 3. Add the visible widget to the layout widget

<!--?code-excerpt path-base="layout/base"?-->

All layout widgets have either of the following: \* A \`child\` property if they take a single child—for example, \`Center\` or \`Container\` \* A \`children\` property if they take a list of widgets—for example, \`Row\`, \`Column\`, \`ListView\`, or \`Stack\`. Add the \`Text\` widget to the \`Center\` widget:

<!--?code-excerpt "lib/main.dart (centered-text)" replace="/body: //g"?-->

\`\`\`dart const Center( child: Text('Hello World'), ), \`\`\` ### 4. Add the layout widget to the page A Flutter app is itself a widget, and most widgets have a \[\`build()\`]\[] method. Instantiating and returning a widget in the app's \`build()\` method displays the widget. []()[]()[]()For a general app, you can add the \`Container\` widget to the app's \`build()\` method:

<!--?code-excerpt path-base="layout/non_material"?-->

<!--?code-excerpt "lib/main.dart (my-app)"?-->

\`\`\`dart class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return Container( decoration: const BoxDecoration(color: Colors.white), child: const Center( child: Text( 'Hello World', textDirection: TextDirection.ltr, style: TextStyle(fontSize: 32, color: Colors.black87), ), ), ); } } \`\`\` By default, a general app doesn't include an \`AppBar\`, title, or background color. If you want these features in a general app, you have to build them yourself. This app changes the background color to white and the text to dark grey to mimic a Material app. For a \`Material\` app, you can use a \[\`Scaffold\`]\[] widget; it provides a default banner, background color, and has API for adding drawers, snack bars, and bottom sheets. Then you can add the \`Center\` widget directly to the \`body\` property for the home page.

<!--?code-excerpt path-base="layout/base"?-->

<!--?code-excerpt "lib/main.dart (my-app)"?-->

\`\`\`dart class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { const String appTitle = 'Flutter layout demo'; return MaterialApp( title: appTitle, home: Scaffold( appBar: AppBar(title: const Text(appTitle)), body: const Center( child: Text('Hello World'), ), ), ); } } \`\`\` :::note The \[Material library]\[] implements widgets that follow \[Material Design]\[] principles. When designing your UI, you can exclusively use widgets from the standard \[widgets library]\[], or you can use widgets from the Material library. You can mix widgets from both libraries, you can customize existing widgets, or you can build your own set of custom widgets. ::: To create a \`Cupertino\` app, use the \`CupertinoApp\` and \[\`CupertinoPageScaffold\`]\[] widgets. Unlike \`Material\`, it doesn't provide a default banner or background color. You need to set these yourself. \* To set default colors, pass in a configured \[\`CupertinoThemeData\`]\[] to your app's \`theme\` property. \* To add an iOS-styled navigation bar to the top of your app, add a \[\`CupertinoNavigationBar\`]\[] widget to the \`navigationBar\` property of your scaffold. You can use the colors that \[\`CupertinoColors\`]\[] provides to configure your widgets to match iOS design. \* To lay out the body of your app, set the \`child\` property of your scaffold with the desired widget as its value, like \`Center\` or \`Column\`. To learn what other UI components you can add, check out the \[Cupertino library]\[].

<!--?code-excerpt "lib/cupertino.dart (my-app)"?-->

\`\`\`dart class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return const CupertinoApp( title: 'Flutter layout demo', theme: CupertinoThemeData( brightness: Brightness.light, primaryColor: CupertinoColors.systemBlue, ), home: CupertinoPageScaffold( navigationBar: CupertinoNavigationBar( backgroundColor: CupertinoColors.systemGrey, middle: Text('Flutter layout demo'), ), child: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: \[Text('Hello World')], ), ), ), ); } } \`\`\` :::note The \[Cupertino library]\[] implements widgets that follow \[Apple's Human Interface Guidelines for iOS]\[]. When designing your UI, you can use widgets from the standard \[widgets library]\[] or the Cupertino library. You can mix widgets from both libraries, you can customize existing widgets, or you can build your own set of custom widgets. ::: \[\`CupertinoColors\`]: https\://pub.dev/documentation/cupertino\_ui/latest/cupertino\_ui/CupertinoColors-class.html \[\`CupertinoPageScaffold\`]: https\://pub.dev/documentation/cupertino\_ui/latest/cupertino\_ui/CupertinoPageScaffold-class.html \[\`CupertinoThemeData\`]: https\://pub.dev/documentation/cupertino\_ui/latest/cupertino\_ui/CupertinoThemeData-class.html \[\`CupertinoNavigationBar\`]: https\://pub.dev/documentation/cupertino\_ui/latest/cupertino\_ui/CupertinoNavigationBar-class.html \[Cupertino library]: https\://pub.dev/documentation/cupertino\_ui/latest/cupertino\_ui \[Apple's Human Interface Guidelines for iOS]: https\://developer.apple.com/design/human-interface-guidelines/designing-for-ios \[\`build()\`]: https\://api.flutter.dev/flutter/widgets/StatelessWidget/build.html \[Material library]: https\://pub.dev/documentation/material\_ui/latest/material\_ui \[\`Scaffold\`]: https\://pub.dev/documentation/material\_ui/latest/material\_ui/Scaffold-class.html \[widgets library]: https\://api.flutter.dev/flutter/widgets/widgets-library.html ### 5. Run your app

After you've added your widgets, run your app. When you run the app, you should see \_Hello World\_. App source code: \* \[Material app]\(https\://github.com/flutter/website/tree/main/examples/layout/base) \* \[Non-Material app]\(https\://github.com/flutter/website/tree/main/examples/layout/non\_material)

***

\## Lay out multiple widgets vertically and horizontally

<!--?code-excerpt path-base=""?-->

One of the most common layout patterns is to arrange widgets vertically or horizontally. You can use a \`Row\` widget to arrange widgets horizontally, and a \`Column\` widget to arrange widgets vertically. :::secondary What's the point? \* \`Row\` and \`Column\` are two of the most commonly used layout patterns. \* \`Row\` and \`Column\` each take a list of child widgets. \* A child widget can itself be a \`Row\`, \`Column\`, or other complex widget. \* You can specify how a \`Row\` or \`Column\` aligns its children, both vertically and horizontally. \* You can stretch or constrain specific child widgets. \* You can specify how child widgets use the \`Row\`'s or \`Column\`'s available space. ::: To create a row or column in Flutter, you add a list of children widgets to a \[\`Row\`]\[] or \[\`Column\`]\[] widget. In turn, each child can itself be a row or column, and so on. The following example shows how it is possible to nest rows or columns inside of rows or columns. This layout is organized as a \`Row\`. The row contains two children: a column on the left, and an image on the right: The left column's widget tree nests rows and columns. You'll implement some of Pavlova's layout code in \[Nesting rows and columns]\(#nesting-rows-and-columns). :::note \`Row\` and \`Column\` are basic primitive widgets for horizontal and vertical layouts—these low-level widgets allow for maximum customization. Flutter also offers specialized, higher-level widgets that might be sufficient for your needs. For example, instead of \`Row\` you might prefer \[\`ListTile\`]\[], an easy-to-use widget with properties for leading and trailing icons, and up to 3 lines of text. Instead of Column, you might prefer \[\`ListView\`]\[], a column-like layout that automatically scrolls if its content is too long to fit the available space. For more information, see \[Common layout widgets]\[]. ::: \[Common layout widgets]: #common-layout-widgets \[\`Column\`]: https\://api.flutter.dev/flutter/widgets/Column-class.html \[\`ListTile\`]: https\://pub.dev/documentation/material\_ui/latest/material\_ui/ListTile-class.html \[\`ListView\`]: https\://api.flutter.dev/flutter/widgets/ListView-class.html \[\`Row\`]: https\://api.flutter.dev/flutter/widgets/Row-class.html ### Aligning widgets You control how a row or column aligns its children using the \`mainAxisAlignment\` and \`crossAxisAlignment\` properties. For a row, the main axis runs horizontally and the cross axis runs vertically. For a column, the main axis runs vertically and the cross axis runs horizontally.The \[\`MainAxisAlignment\`]\[] and \[\`CrossAxisAlignment\`]\[] enums offer a variety of constants for controlling alignment. :::note When you add images to your project, you need to update the \`pubspec.yaml\` file to access them—this example uses \`Image.asset\` to display the images. For more information, see this example's \[\`pubspec.yaml\` file]\[] or \[Adding assets and images]\[]. You don't need to do this if you're referencing online images using \`Image.network\`. ::: In the following example, each of the 3 images is 100 pixels wide. The render box (in this case, the entire screen) is more than 300 pixels wide, so setting the main axis alignment to \`spaceEvenly\` divides the free horizontal space evenly between, before, and after each image.

<!--?code-excerpt "layout/row_column/lib/main.dart (row)" replace="/Row/[!$&!]/g"?-->

\`\`\`dart \[!Row!]\( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: \[ Image.asset('images/pic1.jpg'), Image.asset('images/pic2.jpg'), Image.asset('images/pic3.jpg'), ], ); \`\`\`

\*\*App source:\*\* \[row\_column]\(https\://github.com/flutter/website/tree/main/examples/layout/row\_column)

Columns work the same way as rows. The following example shows a column of 3 images, each is 100 pixels high. The height of the render box (in this case, the entire screen) is more than 300 pixels, so setting the main axis alignment to \`spaceEvenly\` divides the free vertical space evenly between, above, and below each image.

<!--?code-excerpt "layout/row_column/lib/main.dart (column)" replace="/Column/[!$&!]/g"?-->

\`\`\`dart \[!Column!]\( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: \[ Image.asset('images/pic1.jpg'), Image.asset('images/pic2.jpg'), Image.asset('images/pic3.jpg'), ], ); \`\`\`

\*\*App source:\*\* \[row\_column]\(https\://github.com/flutter/website/tree/main/examples/layout/row\_column)

\[\`CrossAxisAlignment\`]: https\://api.flutter.dev/flutter/rendering/CrossAxisAlignment.html \[\`MainAxisAlignment\`]: https\://api.flutter.dev/flutter/rendering/MainAxisAlignment.html \[\`pubspec.yaml\` file]: https\://github.com/flutter/website/tree/main/examples/layout/row\_column/pubspec.yaml ### Sizing widgets When a layout is too large to fit a device, a yellow and black striped pattern appears along the affected edge. Here is an \[example]\[sizing] of a row that is too wide: Widgets can be sized to fit within a row or column by using the \[\`Expanded\`]\[] widget. To fix the previous example where the row of images is too wide for its render box, wrap each image with an \`Expanded\` widget.

<!--?code-excerpt "layout/sizing/lib/main.dart (expanded-images)" replace="/Expanded/[!$&!]/g"?-->

\`\`\`dart Row( crossAxisAlignment: CrossAxisAlignment.center, children: \[ \[!Expanded!]\(child: Image.asset('images/pic1.jpg')), \[!Expanded!]\(child: Image.asset('images/pic2.jpg')), \[!Expanded!]\(child: Image.asset('images/pic3.jpg')), ], ); \`\`\`

\*\*App source:\*\* \[sizing]\(https\://github.com/flutter/website/tree/main/examples/layout/sizing)

Perhaps you want a widget to occupy twice as much space as its siblings. For this, use the \`Expanded\` widget \`flex\` property, an integer that determines the flex factor for a widget. The default flex factor is 1. The following code sets the flex factor of the middle image to 2:

<!--?code-excerpt "layout/sizing/lib/main.dart (expanded-images-with-flex)" replace="/flex.*/[!$&!]/g"?-->

\`\`\`dart Row( crossAxisAlignment: CrossAxisAlignment.center, children: \[ Expanded(child: Image.asset('images/pic1.jpg')), Expanded(\[!flex: 2, child: Image.asset('images/pic2.jpg')),!] Expanded(child: Image.asset('images/pic3.jpg')), ], ); \`\`\`

\*\*App source:\*\* \[sizing]\(https\://github.com/flutter/website/tree/main/examples/layout/sizing)

\[\`Expanded\`]: https\://api.flutter.dev/flutter/widgets/Expanded-class.html \[sizing]: https\://github.com/flutter/website/tree/main/examples/layout/sizing ### Packing widgets By default, a row or column occupies as much space along its main axis as possible, but if you want to pack the children closely together, set its \`mainAxisSize\` to \`MainAxisSize.min\`. The following example uses this property to pack the star icons together.

<!--?code-excerpt "layout/pavlova/lib/main.dart (stars)" replace="/mainAxisSize.*/[!$&!]/g; /\w+ \w+ = //g; /;//g"?-->

\`\`\`dart Row( \[!mainAxisSize: MainAxisSize.min,!] children: \[ Icon(Icons.star, color: Colors.green\[500]), Icon(Icons.star, color: Colors.green\[500]), Icon(Icons.star, color: Colors.green\[500]), const Icon(Icons.star, color: Colors.black), const Icon(Icons.star, color: Colors.black), ], ) \`\`\`

\*\*App source:\*\* \[pavlova]\(https\://github.com/flutter/website/tree/main/examples/layout/pavlova)

\### Nesting rows and columns The layout framework allows you to nest rows and columns inside of rows and columns as deeply as you need. Let's look at the code for the outlined section of the following layout: The outlined section is implemented as two rows. The ratings row contains five stars and the number of reviews. The icons row contains three columns of icons and text. The widget tree for the ratings row: The \`ratings\` variable creates a row containing a smaller row of 5-star icons, and text:

<!--?code-excerpt "layout/pavlova/lib/main.dart (ratings)" replace="/ratings/[!$&!]/g"?-->

\`\`\`dart final stars = Row( mainAxisSize: MainAxisSize.min, children: \[ Icon(Icons.star, color: Colors.green\[500]), Icon(Icons.star, color: Colors.green\[500]), Icon(Icons.star, color: Colors.green\[500]), const Icon(Icons.star, color: Colors.black), const Icon(Icons.star, color: Colors.black), ], ); final \[!ratings!] = Container( padding: const EdgeInsets.all(20), child: Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: \[ stars, const Text( '170 Reviews', style: TextStyle( color: Colors.black, fontWeight: FontWeight.w800, fontFamily: 'Roboto', letterSpacing: 0.5, fontSize: 20, ), ), ], ), ); \`\`\` :::tip To minimize the visual confusion that can result from heavily nested layout code, implement pieces of the UI in variables and functions. ::: The icons row, below the ratings row, contains 3 columns; each column contains an icon and two lines of text, as you can see in its widget tree: The \`iconList\` variable defines the icons row:

<!--?code-excerpt "layout/pavlova/lib/main.dart (icon-list)" replace="/iconList/[!$&!]/g"?-->

\`\`\`dart const descTextStyle = TextStyle( color: Colors.black, fontWeight: FontWeight.w800, fontFamily: 'Roboto', letterSpacing: 0.5, fontSize: 18, height: 2, ); // DefaultTextStyle.merge() allows you to create a default text // style that is inherited by its child and all subsequent children. final \[!iconList!] = DefaultTextStyle.merge( style: descTextStyle, child: Container( padding: const EdgeInsets.all(20), child: Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: \[ Column( children: \[ Icon(Icons.kitchen, color: Colors.green\[500]), const Text('PREP:'), const Text('25 min'), ], ), Column( children: \[ Icon(Icons.timer, color: Colors.green\[500]), const Text('COOK:'), const Text('1 hr'), ], ), Column( children: \[ Icon(Icons.restaurant, color: Colors.green\[500]), const Text('FEEDS:'), const Text('4-6'), ], ), ], ), ), ); \`\`\` The \`leftColumn\` variable contains the ratings and icons rows, as well as the title and text that describes the Pavlova:

<!--?code-excerpt "layout/pavlova/lib/main.dart (left-column)" replace="/leftColumn/[!$&!]/g"?-->

\`\`\`dart final \[!leftColumn!] = Container( padding: const EdgeInsets.fromLTRB(20, 30, 20, 20), child: Column(children: \[titleText, subTitle, ratings, iconList]), ); \`\`\` The left column is placed in a \`SizedBox\` to constrain its width. Finally, the UI is constructed with the entire row (containing the left column and the image) inside a \`Card\`. The \[Pavlova image]\[] is from \[Pixabay]\[]. You can embed an image from the net using \`Image.network()\` but, for this example, the image is saved to an images directory in the project, added to the \[pubspec file]\[], and accessed using \`Images.asset()\`. For more information, see \[Adding assets and images]\[].

<!--?code-excerpt "layout/pavlova/lib/main.dart (body)"?-->

\`\`\`dart body: Center( child: Container( margin: const EdgeInsets.fromLTRB(0, 40, 0, 30), height: 600, child: Card( child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: \[ SizedBox(width: 440, child: leftColumn), mainImage, ], ), ), ), ), \`\`\` :::tip The Pavlova example runs best horizontally on a wide device, such as a tablet. If you are running this example in the iOS simulator, you can select a different device using the \*\*Hardware > Device\*\* menu. For this example, we recommend the iPad Pro. You can change its orientation to landscape mode using \*\*Hardware > Rotate\*\*. You can also change the size of the simulator window (without changing the number of logical pixels) using \*\*Window > Scale\*\*. ::: \*\*App source:\*\* \[pavlova]\(https\://github.com/flutter/website/tree/main/examples/layout/pavlova)

***

\[Pavlova image]: https\://pixabay.com/en/photos/pavlova \[Pixabay]: https\://pixabay.com/en/photos/pavlova \[pubspec file]: https\://github.com/flutter/website/tree/main/examples/layout/pavlova/pubspec.yaml ## Common layout widgets Flutter has a rich library of layout widgets. Here are a few of those most commonly used. The intent is to get you up and running as quickly as possible, rather than overwhelm you with a complete list. For information on other available widgets, refer to the \[Widget catalog]\[], or use the Search box in the \[API reference docs]\[]. Also, the widget pages in the API docs often make suggestions about similar widgets that might better suit your needs. The following widgets fall into two categories: standard widgets from the \[widgets library]\[], and specialized widgets from the \[Material library]\[]. Any app can use the widgets library but only Material apps can use the Material Components library. []()[]()\[\`Container\`]\(#container) : Adds padding, margins, borders, background color, or other decorations to a widget. \[\`GridView\`]\(#gridview) : Lays widgets out as a scrollable grid. \[\`ListView\`]\(#listview) : Lays widgets out as a scrollable list. \[\`Stack\`]\(#stack) : Overlaps a widget on top of another. \[\`Scaffold\`]\[] : Provides a structured layout framework with slots for common Material Design app elements. \[\`AppBar\`]\[] : Creates a horizontal bar that's typically displayed at the top of a screen. \[\`Card\`]\(#card) : Organizes related info into a box with rounded corners and a drop shadow. \[\`ListTile\`]\(#listtile) : Organizes up to 3 lines of text, and optional leading and trailing icons, into a row. \[\`CupertinoPageScaffold\`]\[] : Provides the basic layout structure for an iOS-style page. \[\`CupertinoNavigationBar\`]\[] : Creates an iOS-style navigation bar at the top of the screen. \[\`CupertinoSegmentedControl\`]\[] : Creates a segmented control for selecting. \[\`CupertinoTabBar\`]\[] and \[\`CupertinoTabScaffold\`]\[] : Creates the characteristic iOS bottom tab bar. \[\`Scaffold\`]: https\://pub.dev/documentation/material\_ui/latest/material\_ui/Scaffold-class.html \[\`AppBar\`]: https\://pub.dev/documentation/material\_ui/latest/material\_ui/AppBar-class.html \[\`Container\`]: https\://api.flutter.dev/flutter/widgets/Container-class.html \[\`CupertinoPageScaffold\`]: https\://pub.dev/documentation/cupertino\_ui/latest/cupertino\_ui/CupertinoPageScaffold-class.html \[\`CupertinoNavigationBar\`]: https\://pub.dev/documentation/cupertino\_ui/latest/cupertino\_ui/CupertinoNavigationBar-class.html \[\`CupertinoSegmentedControl\`]: https\://pub.dev/documentation/cupertino\_ui/latest/cupertino\_ui/CupertinoSegmentedControl-class.html \[\`CupertinoTabBar\`]: https\://pub.dev/documentation/cupertino\_ui/latest/cupertino\_ui/CupertinoTabBar-class.html \[\`CupertinoTabScaffold\`]: https\://pub.dev/documentation/cupertino\_ui/latest/cupertino\_ui/CupertinoTabScaffold-class.html \[\`GridView\`]: https\://api.flutter.dev/flutter/widgets/GridView-class.html \[\`ListTile\`]: https\://pub.dev/documentation/material\_ui/latest/material\_ui/ListTile-class.html \[\`ListView\`]: https\://api.flutter.dev/flutter/widgets/ListView-class.html \[Material library]: https\://pub.dev/documentation/material\_ui/latest/material\_ui \[widgets library]: https\://api.flutter.dev/flutter/widgets/widgets-library.html ### Container Many layouts make liberal use of \[\`Container\`]\[]s to separate widgets using padding, or to add borders or margins. You can change the device's background by placing the entire layout into a \`Container\` and changing its background color or image.

\[\`Container\`]: https\://api.flutter.dev/flutter/widgets/Container-class.html #### Summary (Container) \* Add padding, margins, borders \* Change background color or image \* Contains a single child widget, but that child can be a \`Row\`, \`Column\`, or even the root of a widget tree

\#### Examples (Container) This layout consists of a column with two rows, each containing 2 images. A \[\`Container\`]\[] is used to change the background color of the column to a lighter grey.

<!--?code-excerpt "layout/container/lib/main.dart (column)" replace="/\bContainer/[!$&!]/g;"?-->

\`\`\`dart Widget \_buildImageColumn() { return \[!Container!]\( decoration: const BoxDecoration(color: Colors.black26), child: Column(children: \[\_buildImageRow(1), \_buildImageRow(3)]), ); } \`\`\`

A \`Container\` is also used to add a rounded border and margins to each image:

<!--?code-excerpt "layout/container/lib/main.dart (row)" replace="/\bContainer/[!$&!]/g;"?-->

\`\`\`dart Widget \_buildDecoratedImage(int imageIndex) => Expanded( child: \[!Container!]\( decoration: BoxDecoration( border: Border.all(width: 10, color: Colors.black38), borderRadius: const BorderRadius.all(Radius.circular(8)), ), margin: const EdgeInsets.all(4), child: Image.asset('images/pic$imageIndex.jpg'), ), ); Widget \_buildImageRow(int imageIndex) => Row( children: \[ \_buildDecoratedImage(imageIndex), \_buildDecoratedImage(imageIndex + 1), ], ); \`\`\` You can find more \`Container\` examples in the \[tutorial]\[]. \*\*App source:\*\* \[container]\(https\://github.com/flutter/website/tree/main/examples/layout/container)

***

\[\`Container\`]: https\://api.flutter.dev/flutter/widgets/Container-class.html \[tutorial]: /ui/layout/tutorial ### GridView Use \[\`GridView\`]\[] to lay widgets out as a two-dimensional list. \`GridView\` provides two pre-fabricated lists, or you can build your own custom grid. When a \`GridView\` detects that its contents are too long to fit the render box, it automatically scrolls. \[\`GridView\`]: https\://api.flutter.dev/flutter/widgets/GridView-class.html #### Summary (GridView) \* Lays widgets out in a grid \* Detects when the column content exceeds the render box and automatically provides scrolling \* Build your own custom grid, or use one of the provided grids: \* \`GridView\.count\` allows you to specify the number of columns \* \`GridView\.extent\` allows you to specify the maximum pixel width of a tile :::note When displaying a two-dimensional list where it's important which row and column a cell occupies (for example, it's the entry in the "calorie" column for the "avocado" row), use \[\`Table\`]\[] or \[\`DataTable\`]\[]. ::: \[\`DataTable\`]: https\://pub.dev/documentation/material\_ui/latest/material\_ui/DataTable-class.html \[\`Table\`]: https\://api.flutter.dev/flutter/widgets/Table-class.html #### Examples (GridView)

Uses \`GridView\.extent\` to create a grid with tiles a maximum 150 pixels wide. \*\*App source:\*\* \[grid\_and\_list]\(https\://github.com/flutter/website/tree/main/examples/layout/grid\_and\_list)

Uses \`GridView\.count\` to create a grid that's 2 tiles wide in portrait mode, and 3 tiles wide in landscape mode. The titles are created by setting the \`footer\` property for each \[\`GridTile\`]\[]. \*\*Dart code:\*\* \[\`grid\_list\_demo.dart\`]\(https\://github.com/flutter/website/tree/main/examples/layout/gallery/lib/grid\_list\_demo.dart)

<!--?code-excerpt "layout/grid_and_list/lib/main.dart (grid)" replace="/\GridView/[!$&!]/g;"?-->

\`\`\`dart Widget \_buildGrid() => \[!GridView!].extent( maxCrossAxisExtent: 150, padding: const EdgeInsets.all(4), mainAxisSpacing: 4, crossAxisSpacing: 4, children: \_buildGridTileList(30), ); // The images are saved with names pic0.jpg, pic1.jpg...pic29.jpg. // The List.generate() constructor allows an easy way to create // a list when objects have a predictable naming pattern. List \_buildGridTileList(int count) => List.generate(count, (i) => Image.asset('images/pic$i.jpg')); \`\`\`

***

\[\`GridTile\`]: https\://pub.dev/documentation/material\_ui/latest/material\_ui/GridTile-class.html ### ListView \[\`ListView\`]\[], a column-like widget, automatically provides scrolling when its content is too long for its render box. \[\`ListView\`]: https\://api.flutter.dev/flutter/widgets/ListView-class.html #### Summary (ListView) \* A specialized \[\`Column\`]\[] for organizing a list of boxes \* Can be laid out horizontally or vertically \* Detects when its content won't fit and provides scrolling \* Less configurable than \`Column\`, but easier to use and supports scrolling \[\`Column\`]: https\://api.flutter.dev/flutter/widgets/Column-class.html #### Examples (ListView)

Uses \`ListView\` to display a list of businesses using \`ListTile\`s. A \`Divider\` separates the theaters from the restaurants. \*\*App source:\*\* \[grid\_and\_list]\(https\://github.com/flutter/website/tree/main/examples/layout/grid\_and\_list)

Uses \`ListView\` to display the \[\`Colors\`]\[] from the \[Material 2 Design palette]\[] for a particular color family. \*\*Dart code:\*\* \[\`colors\_demo.dart\`]\(https\://github.com/flutter/website/tree/main/examples/layout/gallery/lib/colors\_demo.dart)

<!--?code-excerpt "layout/grid_and_list/lib/main.dart (list)" replace="/\ListView/[!$&!]/g;"?-->

\`\`\`dart Widget \_buildList() { return \[!ListView!]\( children: \[ \_tile('CineArts at the Empire', '85 W Portal Ave', Icons.theaters), \_tile('The Castro Theater', '429 Castro St', Icons.theaters), \_tile('Alamo Drafthouse Cinema', '2550 Mission St', Icons.theaters), \_tile('Roxie Theater', '3117 16th St', Icons.theaters), \_tile( 'United Artists Stonestown Twin', '501 Buckingham Way', Icons.theaters, ), \_tile('AMC Metreon 16', '135 4th St #3000', Icons.theaters), const Divider(), \_tile('K\\'s Kitchen', '757 Monterey Blvd', Icons.restaurant), \_tile('Emmy\\'s Restaurant', '1923 Ocean Ave', Icons.restaurant), \_tile('Chaiya Thai Restaurant', '272 Claremont Blvd', Icons.restaurant), \_tile('La Ciccia', '291 30th St', Icons.restaurant), ], ); } ListTile \_tile(String title, String subtitle, IconData icon) { return ListTile( title: Text( title, style: const TextStyle(fontWeight: FontWeight.w500, fontSize: 20), ), subtitle: Text(subtitle), leading: Icon(icon, color: Colors.blue\[500]), ); } \`\`\`

***

\[\`Colors\`]: https\://pub.dev/documentation/material\_ui/latest/material\_ui/Colors-class.html \[Material 2 Design palette]: https\://m2.material.io/design/color/the-color-system.html#tools-for-picking-colors ### Stack Use \[\`Stack\`]\[] to arrange widgets on top of a base widget—often an image. The widgets can completely or partially overlap the base widget. \[\`Stack\`]: https\://api.flutter.dev/flutter/widgets/Stack-class.html #### Summary (Stack) \* Use for widgets that overlap another widget \* The first widget in the list of children is the base widget; subsequent children are overlaid on top of that base widget \* A \`Stack\`'s content can't scroll \* You can choose to clip children that exceed the render box #### Examples (Stack)

Uses \`Stack\` to overlay a \`Container\` (that displays its \`Text\` on a translucent black background) on top of a \`CircleAvatar\`. The \`Stack\` offsets the text using the \`alignment\` property and \`Alignment\`s. \*\*App source:\*\* \[card\_and\_stack]\(https\://github.com/flutter/website/tree/main/examples/layout/card\_and\_stack)

Uses \`Stack\` to overlay an icon on top of an image. \*\*Dart code:\*\* \[\`bottom\_navigation\_demo.dart\`]\(https\://github.com/flutter/website/tree/main/examples/layout/gallery/lib/bottom\_navigation\_demo.dart)

<!--?code-excerpt "layout/card_and_stack/lib/main.dart (stack)" replace="/\bStack/[!$&!]/g;"?-->

\`\`\`dart Widget \_buildStack() { return \[!Stack!]\( alignment: const Alignment(0.6, 0.6), children: \[ const CircleAvatar( backgroundImage: AssetImage('images/pic.jpg'), radius: 100, ), Container( decoration: const BoxDecoration(color: Colors.black45), child: const Text( 'Mia B', style: TextStyle( fontSize: 20, fontWeight: FontWeight.bold, color: Colors.white, ), ), ), ], ); } \`\`\`

***

\### Card A \[\`Card\`]\[], from the \[Material library]\[], contains related nuggets of information and can be composed of almost any widget, but is often used with \[\`ListTile\`]\[]. \`Card\` has a single child, but its child can be a column, row, list, grid, or other widget that supports multiple children. By default, a \`Card\` shrinks its size to 0 by 0 pixels. You can use \[\`SizedBox\`]\[] to constrain the size of a card. In Flutter, a \`Card\` features slightly rounded corners and a drop shadow, giving it a 3D effect. Changing a \`Card\`'s \`elevation\` property allows you to control the drop shadow effect. Setting the elevation to 24, for example, visually lifts the \`Card\` further from the surface and causes the shadow to become more dispersed. For a list of supported elevation values, see \[Elevation]\[] in the \[Material guidelines]\[Material Design]. Specifying an unsupported value disables the drop shadow entirely. \[\`Card\`]: https\://pub.dev/documentation/material\_ui/latest/material\_ui/Card-class.html \[Elevation]: https\://m3.material.io/styles/elevation \[\`ListTile\`]: https\://pub.dev/documentation/material\_ui/latest/material\_ui/ListTile-class.html \[Material Design]: https\://m3.material.io/styles \[\`SizedBox\`]: https\://api.flutter.dev/flutter/widgets/SizedBox-class.html \[Material library]: https\://pub.dev/documentation/material\_ui/latest/material\_ui #### Summary (Card) \* Implements a \[Material card]\[] \* Used for presenting related nuggets of information \* Accepts a single child, but that child can be a \`Row\`, \`Column\`, or other widget that holds a list of children \* Displayed with rounded corners and a drop shadow \* A \`Card\`'s content can't scroll \* From the \[Material library]\[] \[Material card]: https\://m3.material.io/components/cards \[Material library]: https\://pub.dev/documentation/material\_ui/latest/material\_ui #### Examples (Card)

A \`Card\` containing 3 ListTiles and sized by wrapping it with a \`SizedBox\`. A \`Divider\` separates the first and second \`ListTiles\`. \*\*App source:\*\* \[card\_and\_stack]\(https\://github.com/flutter/website/tree/main/examples/layout/card\_and\_stack)

A \`Card\` containing an image and text. \*\*Dart code:\*\* \[\`cards\_demo.dart\`]\(https\://github.com/flutter/website/tree/main/examples/layout/gallery/lib/cards\_demo.dart)

<!--?code-excerpt "layout/card_and_stack/lib/main.dart (card)" replace="/\bCard/[!$&!]/g;"?-->

\`\`\`dart Widget \_buildCard() { return SizedBox( height: 210, child: \[!Card!]\( child: Column( children: \[ ListTile( title: const Text( '1625 Main Street', style: TextStyle(fontWeight: FontWeight.w500), ), subtitle: const Text('My City, CA 99984'), leading: Icon(Icons.restaurant\_menu, color: Colors.blue\[500]), ), const Divider(), ListTile( title: const Text( '(408) 555-1212', style: TextStyle(fontWeight: FontWeight.w500), ), leading: Icon(Icons.contact\_phone, color: Colors.blue\[500]), ), ListTile( title: const Text('costa\@example.com'), leading: Icon(Icons.contact\_mail, color: Colors.blue\[500]), ), ], ), ), ); } \`\`\`

***

\### ListTile Use \[\`ListTile\`]\[], a specialized row widget from the \[Material library]\[], for an easy way to create a row containing up to 3 lines of text and optional leading and trailing icons. \`ListTile\` is most commonly used in \[\`Card\`]\[] or \[\`ListView\`]\[], but can be used elsewhere. \[\`Card\`]: https\://pub.dev/documentation/material\_ui/latest/material\_ui/Card-class.html \[\`ListTile\`]: https\://pub.dev/documentation/material\_ui/latest/material\_ui/ListTile-class.html \[\`ListView\`]: https\://api.flutter.dev/flutter/widgets/ListView-class.html \[Material library]: https\://pub.dev/documentation/material\_ui/latest/material\_ui #### Summary (ListTile) \* A specialized row that contains up to 3 lines of text and optional icons \* Less configurable than \`Row\`, but easier to use \* From the \[Material library]\[] \[Material library]: https\://pub.dev/documentation/material\_ui/latest/material\_ui #### Examples (ListTile)

A \`Card\` containing 3 \`ListTile\`s. \*\*App source:\*\* \[card\_and\_stack]\(https\://github.com/flutter/website/tree/main/examples/layout/card\_and\_stack)

Uses \`ListTile\` with leading widgets. \*\*Dart code:\*\* \[\`list\_demo.dart\`]\(https\://github.com/flutter/website/tree/main/examples/layout/gallery/lib/list\_demo.dart)

***

\## Constraints To fully understand Flutter's layout system, you need to learn how Flutter positions and sizes the components in a layout. For more information, see \[Understanding constraints]\[]. \[Understanding constraints]: /ui/layout/constraints ## Videos The following videos, part of the \[Flutter in Focus]\[] series, explain \`Stateless\` and \`Stateful\` widgets. \[Flutter in Focus playlist]\(https\://www\.youtube.com/playlist?list=PLjxrf2q8roU2HdJQDjJzOeO6J3FoFLWr2) --- Each episode of the \[Widget of the Week series]\[] focuses on a widget. Several of them include layout widgets. \[Flutter Widget of the Week playlist]\(https\://www\.youtube.com/playlist?list=PLjxrf2q8roU23XGwz3Km7sQZFTdB996iG) \[Widget of the Week series]: https\://www\.youtube.com/playlist?list=PLjxrf2q8roU23XGwz3Km7sQZFTdB996iG \[Flutter in Focus]: https\://www\.youtube.com/watch?v=wgTBLj7rMPM\&list=PLjxrf2q8roU2HdJQDjJzOeO6J3FoFLWr2 ## Other resources The following resources might help when writing layout code. \[Layout tutorial]\[] : Learn how to build a layout. \[Widget catalog]\[] : Describes many of the widgets available in Flutter. \[HTML/CSS Analogs in Flutter]\[] : For those familiar with web programming, this page maps HTML/CSS functionality to Flutter features. \[API reference docs]\[] : Reference documentation for all of the Flutter libraries. \[Adding assets and images]\[] : Explains how to add images and other assets to your app's package. \[Zero to One with Flutter]\[] : One person's experience writing their first Flutter app. \[Layout tutorial]: /ui/layout/tutorial \[Widget catalog]: /ui/widgets \[HTML/CSS Analogs in Flutter]: /flutter-for/web-devs \[API reference docs]: https\://api.flutter.dev/flutter \[Adding assets and images]: /ui/assets/assets-and-images \[Zero to One with Flutter]: https\://medium.com/@mravn/zero-to-one-with-flutter-43b13fd7b354

----
url: https://docs.flutter.dev/platform-integration/ios/ios-debugging.md
----

\# iOS debugging > iOS-specific debugging techniques for Flutter apps Due to security around \[local network permissions in iOS 14 or later]\[], you must accept a permission dialog box to enable Flutter debugging functionalities such as hot-reload and DevTools. !\[Screenshot of "allow network connections" dialog]\(/assets/images/docs/development/device-connect.png) This affects debug and profile builds only and won't appear in release builds. You can also allow this permission by enabling \*\*Settings > Privacy > Local Network > Your App\*\*. \[local network permissions in iOS 14 or later]: https\://developer.apple.com/news/?id=0oi77447

----
url: https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.7.0.md
----

\# DevTools 2.7.0 release notes > Release notes for Dart and Flutter DevTools version 2.7.0. The 2.7.0 release of the Dart and Flutter DevTools includes the following changes among other general improvements. To learn more about DevTools, check out the \[DevTools overview]\(/tools/devtools). ## General updates \* Improvements for initial page load time - \[#3309]\(https\://github.com/flutter/devtools/pull/3309) \* Fix a couple scrollbar-related issues - \[#3393]\(https\://github.com/flutter/devtools/pull/3393), \[#3401]\(https\://github.com/flutter/devtools/pull/3401) ## Debugger updates \* Add an open file dialog (ctrl / cmd + p) - \[#3342]\(https\://github.com/flutter/devtools/pull/3342), \[#3354]\(https\://github.com/flutter/devtools/pull/3354), \[#3371]\(https\://github.com/flutter/devtools/pull/3371), \[#3384]\(https\://github.com/flutter/devtools/pull/3384) !\[Open file dialog]\(/assets/images/docs/tools/devtools/release-notes/images-2.7.0/image1.gif "Open file dialog") \* Add a copy button to the call stack view - \[#3334]\(https\://github.com/flutter/devtools/pull/3334) !\[Call stack view]\(/assets/images/docs/tools/devtools/release-notes/images-2.7.0/image2.png "Call stack view") ## CPU profiler updates \* Added functionality to load an app startup profile for Flutter apps. This profile will contain CPU samples from the initialization of the Dart VM up until the first Flutter frame has been rendered - \[#3357]\(https\://github.com/flutter/devtools/pull/3357) !\[Profile button]\(/assets/images/docs/tools/devtools/release-notes/images-2.7.0/image3.png "Profile button") When the app startup profile has been loaded, you will see that the "AppStartUp" user tag is selected for the profile. You can also load the app startup profile by selecting this user tag filter, when present, in the list of available user tags. !\[User tag example]\(/assets/images/docs/tools/devtools/release-notes/images-2.7.0/image4.png "User tag example") \* Added multi-isolate support. Select which isolate you want to profile from the isolate selector at the bottom of the page - \[#3362]\(https\://github.com/flutter/devtools/pull/3362) !\[isolate selector]\(/assets/images/docs/tools/devtools/release-notes/images-2.7.0/image5.png "isolate selector") \* Add class names to CPU stack frames in the profiler - \[#3385]\(https\://github.com/flutter/devtools/pull/3385) !\[Class names]\(/assets/images/docs/tools/devtools/release-notes/images-2.7.0/image6.png "Class names") ## Full commit history To find a complete list of changes since the previous release, check out \[the diff on GitHub]\(https\://github.com/flutter/devtools/compare/v2.6.0...v2.7.0).

----
url: https://docs.flutter.dev/ui/design/text/typography.md
----

\# Flutter's fonts and typography > Learn about Flutter's support for typography. \[\_Typography\_]\[] covers the style and appearance of type or fonts: it specifies how heavy the font is, the slant of the font, the spacing between the letters, and other visual aspects of the text. All fonts are \_not\_ created the same. A font style is defined by, at minimum, a typeface, representing the set of common character rules describing fonts in the same type family, such as \*\*Roboto\*\* or \*\*Noto\*\*, a font weight (for example, Regular, Bold, or a numeric value), and a style (like Regular, \_Italic\_, etc). All of these and additional pre-set attributes come together to make up what we would call a static font. Variable fonts allow some of these attributes to be modified at runtime and store what would normally be multiple static fonts in a single file. \[\_Typography\_]: https\://en.wikipedia.org/wiki/Typography ## Typographic Scale A typographical scale is a set of related text styles to provide balance, cohesion, and visual variety in your apps. The common type scale in Flutter, provided by \[\`TextTheme\`]\[], includes five categories of text indicating the function: \* Display \* Headline \* Title \* Label \* Body There are also three size variations for each: \* Small \* Medium \* Large Each of these fifteen combinations of a category and text size are represented by a single \[\`TextStyle\`]\[]. All the platform specific typographical scales that Flutter exposes are contained in the \[\`Typography\`]\[] class. Usually, you will not need to reference this class directly as the \`TextTheme\` will be localized to your target platform. \[\`TextTheme\`]: https\://pub.dev/documentation/material\_ui/latest/material\_ui/TextTheme-class.html \[\`TextStyle\`]: https\://api.flutter.dev/flutter/painting/TextStyle-class.html \[\`Typography\`]: https\://pub.dev/documentation/material\_ui/latest/material\_ui/Typography-class.html ## Variable fonts \[Variable fonts]\[] allow you to control pre-defined aspects of text styling. Variable fonts support specific axes, such as width, weight, slant (to name a few). The user can select \_any value along the continuous axis\_ when specifying the type. \[Variable fonts]: https\://fonts.google.com/knowledge/introducing\_type/introducing\_variable\_fonts ### Using the Google Fonts type tester A growing number of fonts on Google Fonts offer some variable font capabilities. You can see the range of options by using the Type Tester and see how you might vary a single font. In real time, move the slider on any of the axes to see how it affects the font. When programming a variable font, use the \[\`FontVariation\`]\[] class to modify the font's design axes. The \`FontVariation\` class conforms to the \[OpenType font variables spec]\[]. \[\`FontVariation\`]: https\://api.flutter.dev/flutter/dart-ui/FontVariation-class.html \[Google Fonts]: https\://fonts.google.com/ \[OpenType font variables spec]: https\://learn.microsoft.com/en-us/typography/opentype/spec/otvaroverview ## Static fonts Google Fonts also contains static fonts. As with variable fonts, you need to know how the font is designed to know what options are available to you. Once again, the Google Fonts site can help. ### Using the Google Fonts package While you can download fonts from the site and install them manually in your apps, you can elect to use theme directly from the \[google\_fonts]\[] package on \[pub.dev]\[]. They can be used as is by referencing simply the font name: \`\`\`dart Text( 'This is Google Fonts', style: GoogleFonts.lato(), ), \`\`\` or customized by setting properties on the resulting \`TextStyle\`: \`\`\`dart Text( 'This is Google Fonts', style: GoogleFonts.lato( textStyle: Theme.of(context).textTheme.displayLarge, fontSize: 48, fontWeight: FontWeight.w700, fontStyle: FontStyle.italic, ), ), \`\`\` ### Modifying fonts Use the following API to programmatically alter a static font (but remember that this only works if the font was \_designed\_ to support the feature): \* \[\`FontFeature\`]\[] to select glyphs \* \[\`FontWeight\`]\[] to modify weight \* \[\`FontStyle\`]\[] to italicize \* \[\`FontVariation\`]\[] to specify a range of values for a specific property. A \`FontFeature\` corresponds to an \[OpenType feature tag]\[] and can be thought of as a boolean flag to enable or disable a feature of a given font. \[\`FontFeature\`]: https\://api.flutter.dev/flutter/dart-ui/FontFeature-class.html \[\`FontStyle\`]: https\://api.flutter.dev/flutter/dart-ui/FontStyle.html \[\`FontWeight\`]: https\://api.flutter.dev/flutter/dart-ui/FontWeight-class.html \[OpenType feature tag]: https\://learn.microsoft.com/en-us/typography/opentype/spec/featuretags \[pub.dev]: https\://pub.dev \[google\_fonts]: https\://pub.dev/packages/google\_fonts ## Other resources The following video shows you some of the capabilities of Flutter's typography and combines it with the Material \_and\_ Cupertino look and feel (depending on the platform the app runs on), animation, and custom fragment shaders: To read one engineer's experience customizing variable fonts and animating them as they morph (and was the basis for the above video), check out \[Playful typography with Flutter]\[article], a post on the Flutter blog. The associated example also uses a custom shader. \[article]: https\://blog.flutter.dev/playful-typography-with-flutter-f030385058b4

----
url: https://docs.flutter.dev/cookbook/effects/drag-a-widget.md
----

\# Drag a UI element > How to implement a draggable UI element.

<!--?code-excerpt path-base="cookbook/effects/drag_a_widget"?-->

Drag and drop is a common mobile app interaction. As the user long presses (sometimes called \_touch & hold\_) on a widget, another widget appears beneath the user's finger, and the user drags the widget to a final location and releases it. In this recipe, you'll build a drag-and-drop interaction where the user long presses on a choice of food, and then drags that food to the picture of the customer who is paying for it. The following animation shows the app's behavior: !\[Ordering the food by dragging it to the person]\(/assets/images/docs/cookbook/effects/DragAUIElement.webp){:.site-mobile-screenshot} This recipe begins with a prebuilt list of menu items and a row of customers. The first step is to recognize a long press and display a draggable photo of a menu item. ## Press and drag Flutter provides a widget called \[\`LongPressDraggable\`]\[] that provides the exact behavior that you need to begin a drag-and-drop interaction. A \`LongPressDraggable\` widget recognizes when a long press occurs and then displays a new widget near the user's finger. As the user drags, the widget follows the user's finger. \`LongPressDraggable\` gives you full control over the widget that the user drags. Each menu list item is displayed with a custom \`MenuListItem\` widget.

<!--?code-excerpt "lib/main.dart (MenuListItem)" replace="/^child: //g;/^\),$/)/g"?-->

\`\`\`dart MenuListItem( name: item.name, price: item.formattedTotalItemPrice, photoProvider: item.imageProvider, ) \`\`\` Wrap the \`MenuListItem\` widget with a \`LongPressDraggable\` widget.

<!--?code-excerpt "lib/main.dart (LongPressDraggable)" replace="/^return //g;/^\),$/)/g"?-->

\`\`\`dart LongPressDraggable( data: item, dragAnchorStrategy: pointerDragAnchorStrategy, feedback: DraggingListItem( dragKey: \_draggableKey, photoProvider: item.imageProvider, ), child: MenuListItem( name: item.name, price: item.formattedTotalItemPrice, photoProvider: item.imageProvider, ), ); \`\`\` In this case, when the user long presses on the \`MenuListItem\` widget, the \`LongPressDraggable\` widget displays a \`DraggingListItem\`. This \`DraggingListItem\` displays a photo of the selected food item, centered beneath the user's finger. The \`dragAnchorStrategy\` property is set to \[\`pointerDragAnchorStrategy\`]\[]. This property value instructs \`LongPressDraggable\` to base the \`DraggableListItem\`'s position on the user's finger. As the user moves a finger, the \`DraggableListItem\` moves with it. Dragging and dropping is of little use if no information is transmitted when the item is dropped. For this reason, \`LongPressDraggable\` takes a \`data\` parameter. In this case, the type of \`data\` is \`Item\`, which holds information about the food menu item that the user pressed on. The \`data\` associated with a \`LongPressDraggable\` is sent to a special widget called \`DragTarget\`, where the user releases the drag gesture. You'll implement the drop behavior next. ## Drop the draggable The user can drop a \`LongPressDraggable\` wherever they choose, but dropping the draggable has no effect unless it's dropped on top of a \`DragTarget\`. When the user drops a draggable on top of a \`DragTarget\` widget, the \`DragTarget\` widget can either accept or reject the data from the draggable. In this recipe, the user should drop a menu item on a \`CustomerCart\` widget to add the menu item to the user's cart.

<!--?code-excerpt "lib/main.dart (CustomerCart)" replace="/^return //g;/^\),$/)/g"?-->

\`\`\`dart CustomerCart( hasItems: customer.items.isNotEmpty, highlighted: candidateItems.isNotEmpty, customer: customer, ); \`\`\` Wrap the \`CustomerCart\` widget with a \`DragTarget\` widget.

<!--?code-excerpt "lib/main.dart (DragTarget)" replace="/^child: //g;/^\),$/)/g"?-->

\`\`\`dart DragTarget( builder: (context, candidateItems, rejectedItems) { return CustomerCart( hasItems: customer.items.isNotEmpty, highlighted: candidateItems.isNotEmpty, customer: customer, ); }, onAcceptWithDetails: (details) { \_itemDroppedOnCustomerCart(item: details.data, customer: customer); }, ) \`\`\` The \`DragTarget\` displays your existing widget and also coordinates with \`LongPressDraggable\` to recognize when the user drags a draggable on top of the \`DragTarget\`. The \`DragTarget\` also recognizes when the user drops a draggable on top of the \`DragTarget\` widget. When the user drags a draggable on the \`DragTarget\` widget, \`candidateItems\` contains the data items that the user is dragging. This draggable allows you to change what your widget looks like when the user is dragging over it. In this case, the \`Customer\` widget turns red whenever any items are dragged above the \`DragTarget\` widget. The red visual appearance is configured with the \`highlighted\` property within the \`CustomerCart\` widget. When the user drops a draggable on the \`DragTarget\` widget, the \`onAcceptWithDetails\` callback is invoked. This is when you get to decide whether or not to accept the data that was dropped. In this case, the item is always accepted and processed. You might choose to inspect the incoming item to make a different decision. Notice that the type of item dropped on \`DragTarget\` must match the type of the item dragged from \`LongPressDraggable\`. If the types are not compatible, then the \`onAcceptWithDetails\` method isn't invoked. With a \`DragTarget\` widget configured to accept your desired data, you can now transmit data from one part of your UI to another by dragging and dropping. In the next step, you update the customer's cart with the dropped menu item. ## Add a menu item to a cart Each customer is represented by a \`Customer\` object, which maintains a cart of items and a price total.

<!--?code-excerpt "lib/main.dart (CustomerClass)"?-->

\`\`\`dart class Customer { Customer({required this.name, required this.imageProvider, List? items}) : items = items ?? \[]; final String name; final ImageProvider imageProvider; final List items; String get formattedTotalItemPrice { final totalPriceCents = items.fold( 0, (prev, item) => prev + item.totalPriceCents, ); return '\\$${(totalPriceCents / 100.0).toStringAsFixed(2)}'; } } \`\`\` The \`CustomerCart\` widget displays the customer's photo, name, total, and item count based on a \`Customer\` instance. To update a customer's cart when a menu item is dropped, add the dropped item to the associated \`Customer\` object.

<!--?code-excerpt "lib/main.dart (AddCart)"?-->

\`\`\`dart void \_itemDroppedOnCustomerCart({ required Item item, required Customer customer, }) { setState(() { customer.items.add(item); }); } \`\`\` The \`\_itemDroppedOnCustomerCart\` method is invoked in \`onAcceptWithDetails()\` when the user drops a menu item on a \`CustomerCart\` widget. By adding the dropped item to the \`customer\` object, and invoking \`setState()\` to cause a layout update, the UI refreshes with the new customer's price total and item count. Congratulations! You have a drag-and-drop interaction that adds food items to a customer's shopping cart. ## Interactive example Run the app: \* Scroll through the food items. \* Press and hold on one with your finger or click and hold with the mouse. \* While holding, the food item's image will appear above the list. \* Drag the image and drop it on one of the people at the bottom of the screen. The text under the image updates to reflect the charge for that person. You can continue to add food items and watch the charges accumulate.

<!--?code-excerpt "lib/main.dart"?-->

\`\`\`dartpad title="Flutter drag a widget hands-on example in DartPad" run="true" import 'package:flutter/material.dart'; void main() { runApp( const MaterialApp( home: ExampleDragAndDrop(), debugShowCheckedModeBanner: false, ), ); } const \_urlPrefix = 'https\://docs.flutter.dev/assets/images/exercise/effects/split-check'; const List \_items = \[ Item( name: 'Spinach Pizza', totalPriceCents: 1299, uid: '1', imageProvider: NetworkImage('$\_urlPrefix/Food1.jpg'), ), Item( name: 'Veggie Delight', totalPriceCents: 799, uid: '2', imageProvider: NetworkImage('$\_urlPrefix/Food2.jpg'), ), Item( name: 'Chicken Parmesan', totalPriceCents: 1499, uid: '3', imageProvider: NetworkImage('$\_urlPrefix/Food3.jpg'), ), ]; @immutable class ExampleDragAndDrop extends StatefulWidget { const ExampleDragAndDrop({super.key}); @override State createState() => \_ExampleDragAndDropState(); } class \_ExampleDragAndDropState extends State with TickerProviderStateMixin { final List \_people = \[ Customer( name: 'Makayla', imageProvider: const NetworkImage('$\_urlPrefix/Avatar1.jpg'), ), Customer( name: 'Nathan', imageProvider: const NetworkImage('$\_urlPrefix/Avatar2.jpg'), ), Customer( name: 'Emilio', imageProvider: const NetworkImage('$\_urlPrefix/Avatar3.jpg'), ), ]; final GlobalKey \_draggableKey = GlobalKey(); void \_itemDroppedOnCustomerCart({ required Item item, required Customer customer, }) { setState(() { customer.items.add(item); }); } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: const Color(0xFFF7F7F7), appBar: \_buildAppBar(), body: \_buildContent(), ); } PreferredSizeWidget \_buildAppBar() { return AppBar( iconTheme: const IconThemeData(color: Color(0xFFF64209)), title: Text( 'Order Food', style: Theme.of(context).textTheme.headlineMedium?.copyWith( fontSize: 36, color: const Color(0xFFF64209), fontWeight: FontWeight.bold, ), ), backgroundColor: const Color(0xFFF7F7F7), elevation: 0, ); } Widget \_buildContent() { return Stack( children: \[ SafeArea( child: Column( children: \[ Expanded(child: \_buildMenuList()), \_buildPeopleRow(), ], ), ), ], ); } Widget \_buildMenuList() { return ListView\.separated( padding: const EdgeInsets.all(16), itemCount: \_items.length, separatorBuilder: (context, index) { return const SizedBox(height: 12); }, itemBuilder: (context, index) { final item = \_items\[index]; return \_buildMenuItem(item: item); }, ); } Widget \_buildMenuItem({required Item item}) { return LongPressDraggable( data: item, dragAnchorStrategy: pointerDragAnchorStrategy, feedback: DraggingListItem( dragKey: \_draggableKey, photoProvider: item.imageProvider, ), child: MenuListItem( name: item.name, price: item.formattedTotalItemPrice, photoProvider: item.imageProvider, ), ); } Widget \_buildPeopleRow() { return Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 20), child: Row(children: \_people.map(\_buildPersonWithDropZone).toList()), ); } Widget \_buildPersonWithDropZone(Customer customer) { return Expanded( child: Padding( padding: const EdgeInsets.symmetric(horizontal: 6), child: DragTarget( builder: (context, candidateItems, rejectedItems) { return CustomerCart( hasItems: customer.items.isNotEmpty, highlighted: candidateItems.isNotEmpty, customer: customer, ); }, onAcceptWithDetails: (details) { \_itemDroppedOnCustomerCart(item: details.data, customer: customer); }, ), ), ); } } class CustomerCart extends StatelessWidget { const CustomerCart({ super.key, required this.customer, this.highlighted = false, this.hasItems = false, }); final Customer customer; final bool highlighted; final bool hasItems; @override Widget build(BuildContext context) { final textColor = highlighted ? Colors.white : Colors.black; return Transform.scale( scale: highlighted ? 1.075 : 1.0, child: Material( elevation: highlighted ? 8 : 4, borderRadius: BorderRadius.circular(22), color: highlighted ? const Color(0xFFF64209) : Colors.white, child: Padding( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 24), child: Column( mainAxisSize: MainAxisSize.min, children: \[ ClipOval( child: SizedBox( width: 46, height: 46, child: Image( image: customer.imageProvider, fit: BoxFit.cover, ), ), ), const SizedBox(height: 8), Text( customer.name, style: Theme.of(context).textTheme.titleMedium?.copyWith( color: textColor, fontWeight: hasItems ? FontWeight.normal : FontWeight.bold, ), ), Visibility( visible: hasItems, maintainState: true, maintainAnimation: true, maintainSize: true, child: Column( children: \[ const SizedBox(height: 4), Text( customer.formattedTotalItemPrice, style: Theme.of(context).textTheme.bodySmall!.copyWith( color: textColor, fontSize: 16, fontWeight: FontWeight.bold, ), ), const SizedBox(height: 4), Text( '${customer.items.length} item${customer.items.length != 1 ? 's' : ''}', style: Theme.of(context).textTheme.titleMedium! .copyWith(color: textColor, fontSize: 12), ), ], ), ), ], ), ), ), ); } } class MenuListItem extends StatelessWidget { const MenuListItem({ super.key, this.name = '', this.price = '', required this.photoProvider, this.isDepressed = false, }); final String name; final String price; final ImageProvider photoProvider; final bool isDepressed; @override Widget build(BuildContext context) { return Material( elevation: 12, borderRadius: BorderRadius.circular(20), child: Padding( padding: const EdgeInsets.all(12), child: Row( mainAxisSize: MainAxisSize.max, children: \[ ClipRRect( borderRadius: BorderRadius.circular(12), child: SizedBox( width: 120, height: 120, child: Center( child: AnimatedContainer( duration: const Duration(milliseconds: 100), curve: Curves.easeInOut, height: isDepressed ? 115 : 120, width: isDepressed ? 115 : 120, child: Image(image: photoProvider, fit: BoxFit.cover), ), ), ), ), const SizedBox(width: 30), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: \[ Text( name, style: Theme.of(context).textTheme.titleMedium ?.copyWith(fontSize: 18), ), const SizedBox(height: 10), Text( price, style: Theme.of(context).textTheme.titleMedium ?.copyWith(fontWeight: FontWeight.bold, fontSize: 18), ), ], ), ), ], ), ), ); } } class DraggingListItem extends StatelessWidget { const DraggingListItem({ super.key, required this.dragKey, required this.photoProvider, }); final GlobalKey dragKey; final ImageProvider photoProvider; @override Widget build(BuildContext context) { return FractionalTranslation( translation: const Offset(-0.5, -0.5), child: ClipRRect( key: dragKey, borderRadius: BorderRadius.circular(12), child: SizedBox( height: 150, width: 150, child: Opacity( opacity: 0.85, child: Image(image: photoProvider, fit: BoxFit.cover), ), ), ), ); } } @immutable class Item { const Item({ required this.totalPriceCents, required this.name, required this.uid, required this.imageProvider, }); final int totalPriceCents; final String name; final String uid; final ImageProvider imageProvider; String get formattedTotalItemPrice => '\\$${(totalPriceCents / 100.0).toStringAsFixed(2)}'; } class Customer { Customer({required this.name, required this.imageProvider, List? items}) : items = items ?? \[]; final String name; final ImageProvider imageProvider; final List items; String get formattedTotalItemPrice { final totalPriceCents = items.fold( 0, (prev, item) => prev + item.totalPriceCents, ); return '\\$${(totalPriceCents / 100.0).toStringAsFixed(2)}'; } } \`\`\` \[\`pointerDragAnchorStrategy\`]: https\://api.flutter.dev/flutter/widgets/pointerDragAnchorStrategy.html \[\`LongPressDraggable\`]: https\://api.flutter.dev/flutter/widgets/LongPressDraggable-class.html

----
url: https://docs.flutter.dev/tools/devtools.md
----

\# Flutter and Dart DevTools > How to use Flutter DevTools with Flutter. ## What is DevTools? DevTools is a suite of performance and debugging tools for Dart and Flutter. \_Flutter DevTools\_ and \_Dart DevTools\_ refer to the same set of tools. !\[Dart DevTools Screens]\(/assets/images/docs/tools/devtools/dart-devtools.webp){:width="100%"} For a video introduction to DevTools, check out the following deep dive and use-case walkthrough: ## What can I do with DevTools? Here are some of the things you can do with DevTools: \* Inspect the UI layout and state of a Flutter app. \* Diagnose UI jank performance issues in a Flutter app. \* CPU profiling for a Flutter or Dart app. \* Network profiling for a Flutter app. \* Source-level debugging of a Flutter or Dart app. \* Debug memory issues in a Flutter or Dart command-line app. \* View general log and diagnostics information about a running Flutter or Dart command-line app. \* Analyze code and app size. \* Validate deep links in your Android or iOS app. We expect you to use DevTools in conjunction with your existing IDE or command-line based development workflow. []()[]()## How to launch DevTools {:#start} You can launch DevTools with the following tools: \* \[VS Code]\[] \* \[Android Studio/IntelliJ]\[] \* \[command line]\[] ## Troubleshooting some standard issues \*\*Question\*\*: My app looks janky or stutters. How do I fix it? \*\*Answer\*\*: Performance issues can cause \[UI frames]\[] to be janky and/or slow down some operations. 1. To detect which code impacts concrete late frames, start at \[Performance > Timeline]\[]. 2. To learn which code takes the most CPU time in the background, use the \[CPU profiler]\[]. For more information, check out the \[Performance]\[] page. \*\*Question\*\*: I see a lot of garbage collection (GC) events occurring. Is this a problem? \*\*Answer\*\*: Frequent GC events might display on the DevTools > Memory > Memory chart. In most cases, it's not a problem. If your app has frequent background activity with some idle time, Flutter might use that opportunity to collect the created objects without performance impact. \[CPU profiler]: /tools/devtools/cpu-profiler \[Performance]: /perf \[Performance > Timeline]: /tools/devtools/performance#timeline-events-tab \[UI frames]: /perf/ui-performance ## Providing feedback Please give DevTools a try, provide feedback, and file issues in the \[DevTools issue tracker]\[]. Thanks! ## DevTools versioning DevTools is distributed as part of the Flutter SDK. To get access to the latest DevTools functionality, run \`flutter upgrade\` to get the most up-to-date version of Flutter. To access DevTools features before they hit the Flutter \`stable\` channel, consider switching to the \`beta\` or \`main\` channels. ## Other resources For more information on debugging and profiling Flutter apps, see the \[Debugging]\[] page and, in particular, its list of \[other resources]\[]. For more information on using DevTools with Dart command-line apps, see the \[DevTools documentation on dart.dev]\(https\://dart.dev/tools/dart-devtools). \[Android Studio/IntelliJ]: /tools/devtools/android-studio \[VS Code]: /tools/devtools/vscode \[command line]: /tools/devtools/cli \[DevTools issue tracker]: https\://github.com/flutter/devtools/issues \[Debugging]: /testing/debugging \[Other resources]: /testing/debugging#other-resources

----
url: https://docs.flutter.dev/ui/animations/tutorial
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Animations tutorial](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

This tutorial shows you how to build explicit animations in Flutter. The examples build on each other, introducing you to different aspects of the animation library. The tutorial is built on essential concepts, classes, and methods in the animation library that you can learn about in [Introduction to animations](/ui/animations).

The Flutter SDK also provides built-in explicit animations, such as [`FadeTransition`](https://api.flutter.dev/flutter/widgets/FadeTransition-class.html), [`SizeTransition`](https://api.flutter.dev/flutter/widgets/SizeTransition-class.html), and [`SlideTransition`](https://api.flutter.dev/flutter/widgets/SlideTransition-class.html). These widgets take an `Animation` object, often driven by an `AnimationController`, and update the UI as its value changes. They require less code than the custom explicit animations described in this tutorial.

The following sections walk you through several animation examples. Each section provides a link to the source code for that example.

## Rendering animations

[#](#rendering-animations)

An `Animation` object generates a sequence of numbers over time, but doesn't render anything to the screen. To render with an `Animation` object, store the `Animation` object as a member of your widget, then use its value to decide how to draw.

Consider the following app that draws the Flutter logo without animation:

dart

```
import 'package:material_ui/material_ui.dart';
​
void main() => runApp(const LogoApp());
​
class LogoApp extends StatefulWidget {
  const LogoApp({super.key});
​
  @override
  State<LogoApp> createState() => _LogoAppState();
}
​
class _LogoAppState extends State<LogoApp> {
  @override
  Widget build(BuildContext context) {
    return Center(
      child: Container(
        margin: const EdgeInsets.symmetric(vertical: 10),
        height: 300,
        width: 300,
        child: const FlutterLogo(),
      ),
    );
  }
}
```

**App source:** [animate0](https://github.com/flutter/website/tree/main/examples/animation/animate0)

The following shows the same code modified to animate the logo to grow from nothing to full size. When defining an `AnimationController`, you must pass in a `vsync` object. The `vsync` parameter is described in the [`AnimationController` section](/ui/animations#animationcontroller).

The changes from the non-animated example are highlighted:

dart

```
class _LogoAppState extends State<LogoApp> {
class _LogoAppState extends State<LogoApp> with SingleTickerProviderStateMixin {
  late Animation<double> animation;
  late AnimationController controller;
​
  @override
  void initState() {
    super.initState();
    controller = AnimationController(
      duration: const Duration(seconds: 2),
      vsync: this,
    );
    animation = Tween<double>(begin: 0, end: 300).animate(controller)
      ..addListener(() {
        setState(() {
          // The state that has changed here is the animation object's value.
        });
      });
    controller.forward();
  }
​
  @override
  Widget build(BuildContext context) {
    return Center(
      child: Container(
        margin: const EdgeInsets.symmetric(vertical: 10),
        height: 300,
        width: 300,
        height: animation.value,
        width: animation.value,
        child: const FlutterLogo(),
      ),
    );
  }
​
  @override
  void dispose() {
    controller.dispose();
    super.dispose();
  }
}
```

**App source:** [animate1](https://github.com/flutter/website/tree/main/examples/animation/animate1)

The callback registered with `addListener()` calls `setState()`, so every time the `Animation` generates a new number, the widget is marked for rebuilding. In `build()`, the container changes size because its height and width now use `animation.value` instead of a hardcoded value. Dispose of the controller when the `State` object is discarded to prevent memory leaks.

With these few changes, you've created your first animation in Flutter.

## Simplifying with AnimatedWidget

[#](#simplifying-with-animatedwidget)

The `AnimatedWidget` base class allows you to separate out the core widget code from the animation code. `AnimatedWidget` manages its own `State` object and animation listener, so you don't need to implement them in your subclass. Add the following `AnimatedLogo` class:

dart

```
class AnimatedLogo extends AnimatedWidget {
  const AnimatedLogo({super.key, required Animation<double> animation})
    : super(listenable: animation);
​
  @override
  Widget build(BuildContext context) {
    final animation = listenable as Animation<double>;
    return Center(
      child: Container(
        margin: const EdgeInsets.symmetric(vertical: 10),
        height: animation.value,
        width: animation.value,
        child: const FlutterLogo(),
      ),
    );
  }
}
```

`AnimatedLogo` uses the current value of the `animation` when drawing itself.

The `LogoApp` still manages the `AnimationController` and the `Tween`, and it passes the `Animation` object to `AnimatedLogo`:

dart

```
void main() => runApp(const LogoApp());
​
class AnimatedLogo extends AnimatedWidget {
  const AnimatedLogo({super.key, required Animation<double> animation})
    : super(listenable: animation);
​
  @override
  Widget build(BuildContext context) {
    final animation = listenable as Animation<double>;
    return Center(
      child: Container(
        margin: const EdgeInsets.symmetric(vertical: 10),
        height: animation.value,
        width: animation.value,
        child: const FlutterLogo(),
      ),
    );
  }
}
​
class LogoApp extends StatefulWidget {
  // ...
}
​
class _LogoAppState extends State<LogoApp> with SingleTickerProviderStateMixin {
  // ...
​
  @override
  void initState() {
    super.initState();
    controller = AnimationController(
      duration: const Duration(seconds: 2),
      vsync: this,
    );
    animation = Tween<double>(begin: 0, end: 300).animate(controller)
      ..addListener(() {
        setState(() {
          // The state that has changed here is the animation object's value.
        });
      });
    animation = Tween<double>(begin: 0, end: 300).animate(controller);
    controller.forward();
  }
​
  @override
  Widget build(BuildContext context) {
    return Center(
      child: Container(
        margin: const EdgeInsets.symmetric(vertical: 10),
        height: animation.value,
        width: animation.value,
        child: const FlutterLogo(),
      ),
    );
  }
  Widget build(BuildContext context) => AnimatedLogo(animation: animation);
​
  // ...
}
```

**App source:** [animate2](https://github.com/flutter/website/tree/main/examples/animation/animate2)

[]()

## Monitoring the progress of the animation

[#](#monitoring-the-progress-of-the-animation)

It's often helpful to know when an animation changes state, such as finishing, moving forward, or reversing. You can get notifications for this with `addStatusListener()`. The following code modifies the previous example so that it listens for a state change and prints an update. The highlighted line shows the change:

dart

```
class _LogoAppState extends State<LogoApp> with SingleTickerProviderStateMixin {
  late Animation<double> animation;
  late AnimationController controller;
​
  @override
  void initState() {
    super.initState();
    controller = AnimationController(
      duration: const Duration(seconds: 2),
      vsync: this,
    );
    animation = Tween<double>(begin: 0, end: 300).animate(controller)
      ..addStatusListener((status) => debugPrint('$status'));
    controller.forward();
  }
  // ...
}
```

Running this code produces this output:

```
AnimationStatus.forward
AnimationStatus.completed
```

Next, use `addStatusListener()` to reverse the animation at the beginning or the end. This creates a "breathing" effect:

dart

```
void initState() {
  super.initState();
  controller = AnimationController(
    duration: const Duration(seconds: 2),
    vsync: this,
  );
  animation = Tween<double>(begin: 0, end: 300).animate(controller);
  animation = Tween<double>(begin: 0, end: 300).animate(controller)
    ..addStatusListener((status) {
      if (status == AnimationStatus.completed) {
        controller.reverse();
      } else if (status == AnimationStatus.dismissed) {
        controller.forward();
      }
    })
    ..addStatusListener((status) => debugPrint('$status'));
  controller.forward();
}
```

**App source:** [animate3](https://github.com/flutter/website/tree/main/examples/animation/animate3)

## Refactoring with AnimatedBuilder

[#](#refactoring-with-animatedbuilder)

One problem with the code in the [animate3](https://github.com/flutter/website/tree/main/examples/animation/animate3) example is that changing the animation required changing the widget that renders the logo. A better solution is to separate responsibilities into different classes:

* Render the logo
* Define the `Animation` object
* Render the transition

You can accomplish this separation with the help of the `AnimatedBuilder` class (or its general-purpose counterpart, [`ListenableBuilder`](https://api.flutter.dev/flutter/widgets/ListenableBuilder-class.html)). An `AnimatedBuilder` is a separate widget in the widget tree. Like `AnimatedWidget`, `AnimatedBuilder` automatically listens to notifications from the `Animation` object, and marks the widget tree dirty as necessary, so you don't need to call `addListener()`.

The widget tree for the [animate4](https://github.com/flutter/website/tree/main/examples/animation/animate4) example looks like this:

Starting from the bottom of the widget tree, the code for rendering the logo is straightforward:

dart

```
class LogoWidget extends StatelessWidget {
  const LogoWidget({super.key});
​
  // Leave out the height and width so it fills the animating parent.
  @override
  Widget build(BuildContext context) {
    return Container(
      margin: const EdgeInsets.symmetric(vertical: 10),
      child: const FlutterLogo(),
    );
  }
}
```

The middle three blocks in the diagram are all created in the `build()` method in `GrowTransition`, shown below. The `GrowTransition` widget itself is stateless and holds the set of final variables necessary to define the transition animation. The `build()` method creates and returns the `AnimatedBuilder`, which takes the anonymous `builder` callback and the `LogoWidget` object as parameters. The work of rendering the transition actually happens in the anonymous `builder` callback, which creates a `SizedBox` (labeled `Container` in the diagram) of the appropriate size to force the `LogoWidget` to shrink to fit.

One tricky point in the code below is that the child looks like it's specified twice. What's happening is that the outer reference to `child` is passed to `AnimatedBuilder`, which passes it to the anonymous closure, which then uses that object as its child. The net result is that the `AnimatedBuilder` is inserted in between the two widgets in the widget tree, and animation ticks don't rebuild the static `child` subtree.

dart

```
class GrowTransition extends StatelessWidget {
  const GrowTransition({
    required this.child,
    required this.animation,
    super.key,
  });
​
  final Widget child;
  final Animation<double> animation;
​
  @override
  Widget build(BuildContext context) {
    return Center(
      child: AnimatedBuilder(
        animation: animation,
        builder: (context, child) {
          return SizedBox(
            height: animation.value,
            width: animation.value,
            child: child,
          );
        },
        child: child,
      ),
    );
  }
}
```

Finally, the code to initialize the animation looks very similar to the [animate2](https://github.com/flutter/website/tree/main/examples/animation/animate2) example. The `initState()` method creates an `AnimationController` and a `Tween`, then binds them with `animate()`. Next, the `build()` method returns a `GrowTransition` object with a `LogoWidget` as a child, and an animation object to drive the transition. These are the three elements listed in the bullet points above.

dart

```
void main() => runApp(const LogoApp());
​
class LogoWidget extends StatelessWidget {
  const LogoWidget({super.key});
​
  // Leave out the height and width so it fills the animating parent.
  @override
  Widget build(BuildContext context) {
    return Container(
      margin: const EdgeInsets.symmetric(vertical: 10),
      child: const FlutterLogo(),
    );
  }
}
​
class GrowTransition extends StatelessWidget {
  const GrowTransition({
    required this.child,
    required this.animation,
    super.key,
  });
​
  final Widget child;
  final Animation<double> animation;
​
  @override
  Widget build(BuildContext context) {
    return Center(
      child: AnimatedBuilder(
        animation: animation,
        builder: (context, child) {
          return SizedBox(
            height: animation.value,
            width: animation.value,
            child: child,
          );
        },
        child: child,
      ),
    );
  }
}
​
class LogoApp extends StatefulWidget {
  // ...
}
​
class _LogoAppState extends State<LogoApp> with SingleTickerProviderStateMixin {
  // ...
​
  @override
  Widget build(BuildContext context) => AnimatedLogo(animation: animation);
  Widget build(BuildContext context) {
    return GrowTransition(animation: animation, child: const LogoWidget());
  }
​
  // ...
}
```

**App source:** [animate4](https://github.com/flutter/website/tree/main/examples/animation/animate4)

## Simultaneous animations

[#](#simultaneous-animations)

In this section, you'll build on the example from [Monitoring the progress of the animation](#monitoring) ([animate3](https://github.com/flutter/website/tree/main/examples/animation/animate3)), which used `AnimatedWidget` to animate in and out continuously. Consider the case where you want to animate in and out while the opacity animates from transparent to opaque.

Each tween manages an aspect of the animation. For example:

dart

```
controller = AnimationController(
  duration: const Duration(seconds: 2),
  vsync: this,
);
sizeAnimation = Tween<double>(begin: 0, end: 300).animate(controller);
opacityAnimation = Tween<double>(begin: 0.1, end: 1).animate(controller);
```

You can get the size with `sizeAnimation.value` and the opacity with `opacityAnimation.value`. However, `AnimatedLogo` already listens to the controller's animation, so it can calculate both values from that single animation instead.

Change `AnimatedLogo` to encapsulate its own `Tween` objects, and its `build()` method calls `Tween.evaluate()` on the parent's animation object to calculate the required size and opacity values. The following code shows the changes with highlights:

dart

```
class AnimatedLogo extends AnimatedWidget {
  const AnimatedLogo({super.key, required Animation<double> animation})
    : super(listenable: animation);
​
  // Make the tweens static because they don't change.
  static final Tween<double> _opacityTween = Tween(begin: 0.1, end: 1);
  static final Tween<double> _sizeTween = Tween(begin: 0, end: 300);
​
  @override
  Widget build(BuildContext context) {
    final animation = listenable as Animation<double>;
    return Center(
      child: Opacity(
        opacity: _opacityTween.evaluate(animation),
        child: Container(
          margin: const EdgeInsets.symmetric(vertical: 10),
          height: _sizeTween.evaluate(animation),
          width: _sizeTween.evaluate(animation),
          child: const FlutterLogo(),
        ),
      ),
    );
  }
}
​
class LogoApp extends StatefulWidget {
  const LogoApp({super.key});
​
  @override
  State<LogoApp> createState() => _LogoAppState();
}
​
class _LogoAppState extends State<LogoApp> with SingleTickerProviderStateMixin {
  late Animation<double> animation;
  late AnimationController controller;
​
  @override
  void initState() {
    super.initState();
    controller = AnimationController(
      duration: const Duration(seconds: 2),
      vsync: this,
    );
    animation = CurvedAnimation(parent: controller, curve: Curves.easeIn)
      ..addStatusListener((status) {
        if (status == AnimationStatus.completed) {
          controller.reverse();
        } else if (status == AnimationStatus.dismissed) {
          controller.forward();
        }
      });
    controller.forward();
  }
​
  @override
  Widget build(BuildContext context) => AnimatedLogo(animation: animation);
​
  @override
  void dispose() {
    controller.dispose();
    super.dispose();
  }
}
```

**App source:** [animate5](https://github.com/flutter/website/tree/main/examples/animation/animate5)

## Next steps

[#](#next-steps)

This tutorial gives you a foundation for creating explicit animations in Flutter using tweens, but there are many other animation APIs to explore:

* [Implicit animations](/ui/animations/implicit-animations) and [`TweenAnimationBuilder`](https://api.flutter.dev/flutter/widgets/TweenAnimationBuilder-class.html) for simpler animations that don't require managing an `AnimationController`.
* Specialized [`Tween`](https://api.flutter.dev/flutter/animation/Tween-class.html) subclasses, such as [`ColorTween`](https://api.flutter.dev/flutter/animation/ColorTween-class.html), [`RectTween`](https://api.flutter.dev/flutter/animation/RectTween-class.html), and [`IntTween`](https://api.flutter.dev/flutter/animation/IntTween-class.html).
* [Staggered animations](/ui/animations/staggered-animations) for coordinating sequential or overlapping motions.
* [Hero animations](/ui/animations/hero-animations) for shared element transitions between routes.
* [Physics simulations](/cookbook/animation/physics-simulation) and [`fling()`](https://api.flutter.dev/flutter/animation/AnimationController/fling.html) methods for motion modeled on real-world physics.
* [`ReverseAnimation`](https://api.flutter.dev/flutter/animation/ReverseAnimation-class.html) and the [Animations overview](/ui/animations/overview) for a deeper look at Flutter's animation architecture.

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-10-02. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/ui/animations/tutorial.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/ui/animations/tutorial\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/ui/animations/tutorial.md "Report an issue with this page").

----
url: https://docs.flutter.dev/perf/appendix.md
----

\# More thoughts about performance > What is performance, and why is performance important ## What is performance? Performance is a set of quantifiable properties of a performer. In this context, performance isn't the execution of an action itself; it's how well something or someone performs. Therefore, we use the adjective \_performant\_. While the \_how well\_ part can, in general, be described in natural languages, in our limited scope, the focus is on something that is quantifiable as a real number. Real numbers include integers and 0/1 binaries as special cases. Natural language descriptions are still very important. For example, a news article that heavily criticizes Flutter's performance by just using words without any numbers (a quantifiable value) could still be meaningful, and it could have great impacts. The limited scope is chosen only because of our limited resources. The required quantity to describe performance is often referred to as a metric. To navigate through countless performance issues and metrics, you can categorize based on performers. For example, most of the content on this website is about the Flutter app performance, where the performer is a Flutter app. Infra performance is also important to Flutter, where the performers are build bots and CI task runners: they heavily affect how fast Flutter can incorporate code changes, to improve the app's performance. Here, the scope was intentionally broadened to include performance issues other than just app performance issues because they can share many tools regardless of who the performers are. For example, Flutter app performance and infra performance might share the same dashboard and similar alert mechanisms. Broadening the scope also allows performers to be included that traditionally are easy to ignore. Document performance is such an example. The performer could be an API doc of the SDK, and a metric could be: the percentage of readers who find the API doc useful. ## Why is performance important? Answering this question is not only crucial for validating the work in performance, but also for guiding the performance work in order to be more useful. The answer to "why is performance important?" often is also the answer to "how is performance useful?" Simply speaking, performance is important and useful because, in the scope, performance must have quantifiable properties or metrics. This implies: 1. A performance report is easy to consume. 2. Performance has little ambiguity. 3. Performance is comparable and convertible. 4. Performance is fair. Not that non-performance, or non-measurable issues or descriptions are not important. They're meant to highlight the scenarios where performance can be more useful. ### 1. A performance report is easy to consume Performance metrics are numbers. Reading a number is much easier than reading a passage. For example, it probably takes an engineer 1 second to consume the performance rating as a number from 1 to 5. It probably takes the same engineer at least 1 minute to read the full, 500-word feedback summary. If there are many numbers, it's easy to summarize or visualize them for quick consumption. For example, you can quickly consume millions of numbers by looking at its histogram, average, quantiles, and so on. If a metric has a history of thousands of data points, then you can easily plot a timeline to read its trend. On the other hand, having \_n\_ number of 500-word texts almost guarantees an \_n\_-time cost to consume those texts. It would be a daunting task to analyze thousands of historical descriptions, each having 500 words. ### 2. Performance has little ambiguity Another advantage of having performance as a set of numbers is its unambiguity. When you want an animation to have a performance of 20 ms per frame or 50 fps, there's little room for different interpretations about the numbers. On the other hand, to describe the same animation in words, someone might call it good, while someone else might complain that it's bad. Similarly, the same word or phrase could be interpreted differently by different people. You might interpret an OK frame rate to be 60 fps, while someone else might interpret it to be 30 fps. Numbers can still be noisy. For example, the measured time per frame might be a true computation time of this frame, plus a random amount of time (noise) that CPU/GPU spends on some unrelated work. Hence, the metric fluctuates. Nevertheless, there's no ambiguity of what the number means. And, there are also rigorous theory and testing tools to handle such noise. For example, you could take multiple measurements to estimate the distribution of a random variable, or you could take the average of many measurements to eliminate the noise by \[the law of large numbers]\[1]. ### 3. Performance is comparable and convertible Performance numbers not only have unambiguous meanings, but they also have unambiguous comparisons. For example, there's no doubt that 5 is greater than 4. On the other hand, it might be subjective to figure out whether excellent is better or worse than superb. Similarly, could you figure out whether epic is better than legendary? Actually, the phrase \_strongly exceeds expectations\_ could be better than \_superb\_ in someone's interpretation. It only becomes unambiguous and comparable after a definition that maps strongly exceeds expectations to 4 and superb to 5. Numbers are also easily convertible using formulas and functions. For example, 60 fps can be converted to 16.67 ms per frame. A frame's rendering time \_x\_ (ms) can be converted to a binary indicator \`isSmooth = \[x <= 16] = (x <= 16 ? 1 :0)\`. Such conversion can be compounded or chained, so you can get a large variety of quantities using a single measurement without any added noise or ambiguity. The converted quantity can then be used for further comparisons and consumption. Such conversions are almost impossible if you're dealing with natural languages. ### 4. Performance is fair If issues rely on verbose words to be discovered, then an unfair advantage is given to people who are more verbose (more willing to chat or write) or those who are closer to the development team, who have a larger bandwidth and lower cost for chatting or face-to-face meetings. By having the same metrics to detect problems no matter how far away or how silent the users are, we can treat all issues fairly. That, in turn, allows us to focus on the right issues that have greater impact. ### How to make performance useful The following summarizes the 4 points discussed here, from a slightly different perspective: 1. Make performance metrics easy to consume. Do not overwhelm the readers with a lot of numbers (or words). If there are many numbers, then try to summarize them into a smaller set of numbers (for example, summarize many numbers into a single average number). Only notify readers when the numbers change significantly (for example, automatic alerts on spikes or regressions). 2. Make performance metrics as unambiguous as possible. Define the unit that the number is using. Precisely describe how the number is measured. Make the number easily reproducible. When there's a lot of noise, try to show the full distribution, or eliminate the noise as much as possible by aggregating many noisy measurements. 3. Make it easy to compare performance. For example, provide a timeline to compare the current version with the old version. Provide ways and tools to convert one metric to another. For example, if we can convert both memory increase and fps drops into the number of users dropped or revenue lost in dollars, then we can compare them and make an informed trade-off. 4. Make performance metrics monitor a population that is as wide as possible, so no one is left behind. \[1]: https\://en.wikipedia.org/wiki/Law\_of\_large\_numbers

----
url: https://docs.flutter.dev/tools/devtools/logging
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Use the Logging view](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

## What is it?

[#](#what-is-it)

The logging view displays events from the Dart runtime, application frameworks (like Flutter), and application-level logging events.

## Standard logging events

[#](#standard-logging-events)

By default, the logging view shows:

* Garbage collection events from the Dart runtime
* Flutter framework events, like frame creation events
* `stdout` and `stderr` from applications
* Custom logging events from applications

## Logging from your application

[#](#logging-from-your-application)

To implement logging in your code, see the [Logging](/testing/code-debugging#add-logging-to-your-application) section in the [Debugging Flutter apps programmatically](/testing/code-debugging) page.

## Clearing logs

[#](#clearing-logs)

To clear the log entries in the logging view, click the **Clear logs** button.

## Other resources

[#](#other-resources)

To learn about different methods of logging and how to effectively use DevTools to analyze and debug Flutter apps faster, check out a guided [Logging View tutorial](https://medium.com/@fluttergems/mastering-dart-flutter-devtools-logging-view-part-5-of-8-b634f3a3af26).

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-07-31. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/logging.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/tools/devtools/logging\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/logging.md "Report an issue with this page").

----
url: https://docs.flutter.dev/resources/faq.md
----

\# FAQ > Frequently asked questions and answers about Flutter. ## Introduction This page collects common questions about Flutter. You can also check out the following specialized FAQs: \* \[Web FAQ]\[] \* \[Performance FAQ]\[] \[Performance FAQ]: /perf/faq \[Web FAQ]: /platform-integration/web/faq ### What is Flutter? Flutter is Google's open source framework for crafting beautiful, natively compiled applications for mobile (iOS, Android), web, desktop (macOS, Windows, Linux), and embedded devices from a single codebase. Flutter works with existing code, is used by developers and organizations worldwide, and is free and open source. ### Who is Flutter for? For developers, Flutter accelerates app development and reduces the cost and complexity of building across platforms. For designers, Flutter provides a flexible canvas for high-end user experiences. It turns concepts into production code without the compromises imposed by traditional cross-platform frameworks. For engineering managers and businesses, Flutter enables the unification of app developers into a single team, building branded apps for multiple platforms from a single codebase. Flutter speeds feature development and synchronizes release schedules across your customer base. ### How much development experience do I need to use Flutter? Flutter is approachable to programmers familiar with object-oriented and imperative or declarative programming concepts. Experience with languages such as Java, Kotlin, Swift, TypeScript, or C# helps you learn Dart and Flutter quickly. ### What kinds of apps can I build with Flutter? Flutter supports building mobile apps on Android and iOS, web applications, desktop apps on macOS, Windows, and Linux, and embedded device interfaces. Apps that require custom, branded designs are well suited for Flutter. You can also create pixel-perfect experiences that match Android's Material Design and iOS's Cupertino design languages. Flutter's \[package ecosystem]\[] on pub.dev supports a wide variety of hardware (such as camera, GPS, Bluetooth, network, and local storage) and cloud services (such as authentication, notifications, payments, and storage). \[package ecosystem]: https\://pub.dev/flutter ### Who makes Flutter? Flutter is an open source project managed by Google, with contributions from a global community of developers, companies, and individuals. ### Who uses Flutter? Developers and organizations worldwide use Flutter to build natively compiled applications. To explore apps built with Flutter, visit the \[showcase]\[]. \[showcase]: https\://flutter.dev/showcase ### What makes Flutter unique? Flutter differs from most cross-platform alternatives because it doesn't rely on web browser views or system-provided platform widgets. Instead, Flutter renders its UI using its own high-performance rendering engine (\[Impeller]\[]). In addition, Flutter implements most of its framework (including compositing, gestures, animation, and widgets) in \[Dart]\[]. This architecture gives you deep control over the UI, high predictability, and the ability to inspect or modify the framework at any level. \[Dart]: https\://dart.dev/ \[Impeller]: /perf/impeller ### Should I build my next production app with Flutter? Yes. Thousands of organizations, from early-stage startups to large enterprises, deploy production Flutter apps to hundreds of millions of users. Explore real-world examples in the \[showcase]\[]. Flutter releases updates on a regular cadence, bringing performance improvements, new features, and platform updates to the stable channel. ## What does Flutter provide? ### What is inside the Flutter SDK? The Flutter SDK includes: \* High-performance 2D rendering engine (\[Impeller]\[]) \* Modern, reactive framework written in Dart \* Rich set of widgets implementing Material Design 3 and Cupertino (iOS-style) \* APIs for unit, widget, and integration tests \* Interop and plugin APIs to connect to system features and native SDKs \* Headless test runner for running tests on Windows, Linux, and macOS \* \[Flutter DevTools]\[] for profiling, debugging, and inspecting your app \* Command-line tools for creating, building, testing, and compiling apps \[Flutter DevTools]: /tools/devtools ### Does Flutter work with any editors or IDEs? Flutter provides plugins and extensions for \[VS Code]\[], \[Antigravity]\[] (an AI-assisted IDE), \[Android Studio]\[], and \[IntelliJ IDEA]\[]. See \[editor configuration]\[] for setup details, and \[VS Code]\[] and \[Android Studio/IntelliJ]\[] for usage guidance. You can also run the \`flutter\` command from a terminal along with any editor that supports \[editing Dart]\[]. \[Android Studio]: https\://developer.android.com/studio \[Android Studio/IntelliJ]: /tools/android-studio \[Antigravity]: /ai/get-started \[editing Dart]: https\://dart.dev/tools \[editor configuration]: /tools/editors \[IntelliJ IDEA]: https\://www\.jetbrains.com/idea/ \[VS Code]: https\://code.visualstudio.com/ ### Does Flutter come with a framework? Yes. Flutter includes a modern, reactive framework. The framework is layered and customizable, allowing you to use high-level widgets or interact directly with lower layers of the rendering pipeline. ### Does Flutter come with widgets? Yes. Flutter ships with a collection of high-quality \[Material Design and Cupertino (iOS-style) widgets]\[widgets], layouts, and themes. You can use these widgets as provided, customize them, or create custom widgets. \[widgets]: /ui/widgets ### Does Flutter support Material Design? Yes. Flutter provides full support for \[Material Design 3]\[widget catalog], Google's design system. \[widget catalog]: /ui/widgets/material ### Does Flutter come with a testing framework? Yes. Flutter provides built-in support for unit, widget, and integration testing. To learn more, see \[Testing overview]\[]. \[Testing overview]: /testing/overview ### Does Flutter come with debugging tools? Yes. Flutter includes \[Flutter DevTools]\[]. For more information, see \[Debugging Flutter apps]\[] and the \[Flutter DevTools]\[] documentation. \[Debugging Flutter apps]: /testing/debugging ### Does Flutter come with a dependency injection framework? Flutter doesn't require an opinionated dependency injection framework. The Flutter ecosystem offers several packages for dependency injection and service location, such as \[Riverpod]\[], \[get\_it]\[], \[Provider]\[], and \[injectable]\[]. \[get\_it]: https\://pub.dev/packages/get\_it \[injectable]: https\://pub.dev/packages/injectable \[Provider]: https\://pub.dev/packages/provider \[Riverpod]: https\://pub.dev/packages/riverpod ## Technology ### What technology is Flutter built with? Flutter is built with C, C++, and Dart. It uses \[Impeller]\[] as its default rendering engine, taking advantage of modern graphics APIs such as Metal on iOS and macOS, and Vulkan on Android. For a detailed description of Flutter's layered architecture, read the \[architectural overview]\[]. \[architectural overview]: /resources/architectural-overview ### How does Flutter run my code on Android? {:#run-android} The engine's C and C++ code is compiled using Android's NDK. The Dart code (both the framework and your application code) is ahead-of-time (AOT) compiled into native machine code. These libraries are packaged into a runner Android project and built into an APK or Android App Bundle (AAB). When launched, the app loads the Flutter engine, which executes the compiled code and renders the UI. During debug mode, Flutter runs inside the Dart VM to enable stateful \[hot reload]\[], allowing you to make changes to your running code without recompiling. A "debug" banner in the top right corner indicates that the app is running in debug mode. ### How does Flutter run my code on iOS? {:#run-ios} The engine's C and C++ code is compiled with LLVM. The Dart code (both the framework and your application code) is ahead-of-time (AOT) compiled into native ARM machine code. The compiled code is packaged into a runner iOS project and built into an \`.ipa\` bundle. When launched, the app loads the Flutter engine, which executes the compiled code and renders the UI using Impeller. During debug mode, Flutter runs inside the Dart VM to enable stateful \[hot reload]\[]. A "debug" banner in the top right corner indicates that the app is running in debug mode. ### Does Flutter use my operating system's built-in platform widgets? No. Instead, Flutter renders widgets directly using its own engine, providing implementations of Material Design and Cupertino (iOS-style) widgets. You can explore the available widgets in the \[widget catalog]\[catalog of Flutter's widgets]. By rendering pixels directly, Flutter avoids the performance constraints and behavioral differences of platform widget abstractions. This approach delivers consistent performance and appearance across platforms. For cases where you need a platform-native component (such as Google Maps or a web view), Flutter supports embedding \[platform views]\[] directly into the widget tree. \[catalog of Flutter's widgets]: /ui/widgets \[platform views]: /platform-integration/platform-views ### What happens when my mobile OS updates and introduces new widgets? The Flutter team and community track new design guidelines and platform widgets from Apple and Google, updating Flutter's widget libraries accordingly. Because Flutter widgets are written in Dart, you can also build or adapt widgets immediately without waiting for an OS or SDK update. ### What happens when my mobile OS updates and introduces new platform capabilities? Flutter's plugin architecture, \[platform channels]\[], and native interop tools (such as Dart FFI, \`ffigen\`, and \`jnigen\`) allow you to access new OS APIs immediately. You don't need to wait for a Flutter SDK release to integrate new platform capabilities. \[platform channels]: /platform-integration/platform-channels ### Does Flutter support code push? Flutter doesn't provide built-in code push functionality. Third-party solutions, such as \[Shorebird]\[], provide code push support for Flutter applications. \[Shorebird]: https\://shorebird.dev/ ### What operating systems can I use to build a Flutter app? Flutter supports development on macOS, Windows, Linux, and ChromeOS. For details on system requirements, see the \[install guide]\[install]. \[install]: /install ### What language is Flutter written in? The Flutter framework is written in \[Dart]\[]. The underlying engine and graphics pipeline are implemented in C and C++. ### Why did Flutter choose to use Dart? During initial development, the Flutter team evaluated multiple languages and runtimes before selecting Dart for the framework and widgets. Dart scored highest across several key evaluation criteria: \_Developer productivity\_ : Dart supports both JIT (Just-in-Time) compilation for fast development with stateful hot reload, and AOT (Ahead-of-Time) compilation for efficient native machine code in production. Because the framework is written in Dart, developers can inspect, debug, and modify the entire stack. \_Object orientation and UI composition\_ : Dart's object-oriented structure matches the requirements of building composable user interfaces without requiring a separate markup language. \_Predictable, high performance\_ : Dart compiles directly to native machine code without an interpreter or bridge, delivering consistent frame rates without unpredictable runtime pauses. \_Fast memory allocation\_ : Flutter's reactive framework creates many short-lived objects. Dart's generational garbage collector is optimized for fast allocation and cleanup of ephemeral objects. ### Can Flutter run any Dart code? Flutter can run any Dart code that doesn't depend on libraries unavailable on the target platform. For example, mobile and desktop apps can't import web-specific libraries, and web apps can't use \`dart:io\`. ### How does Flutter compile for the web? Flutter compiles Dart for the web to both JavaScript and WebAssembly (Wasm). WebAssembly compilation (WasmGC) delivers high-performance rendering comparable to native desktop and mobile apps. For details, see the \[Flutter web documentation]\[web]. \[web]: /platform-integration/web ### How big is the Flutter engine? {:#how-big-is-the-flutter-engine} A minimal release Flutter application includes the Flutter engine, the framework, and your application code. The engine adds a small baseline footprint (a few megabytes compressed), which varies by target platform and architecture. App size is largely determined by bundled assets, fonts, and third-party packages. To analyze and minimize the size of your application, see \[Measuring your app's size]\[]. \[Measuring your app's size]: /perf/app-size ### How does Flutter define a pixel? Flutter uses logical pixels, which correspond roughly to physical pixels on a standard-density display. Flutter automatically maps logical pixels to physical device pixels based on the view's \[\`devicePixelRatio\`]\[]. \[\`devicePixelRatio\`]: https\://api.flutter.dev/flutter/dart-ui/FlutterView/devicePixelRatio.html ## Capabilities ### What kind of app performance can I expect? Flutter is designed to deliver smooth 60fps and 120fps animations. Flutter apps compile directly to native machine code or WebAssembly, eliminating interpreter overhead. To learn more about performance optimization, see the \[Performance FAQ]\[]. ### What kind of developer cycles can I expect? How long between edit and refresh? {:#hot-reload} Flutter implements a stateful \_hot reload\_ developer cycle. You can expect sub-second reload times on physical devices, emulators, and simulators. Hot reload preserves your application state, allowing you to iterate rapidly without navigating back to the current screen after every code change. ### How is hot reload different from hot restart? Hot reload injects updated source code files into the running Dart VM without clearing the application state. Hot restart resets the application state and re-executes \`main()\`, which takes slightly longer than hot reload but remains faster than a full recompile. For more information, see \[Hot reload]\[]. \[Hot reload]: /tools/hot-reload ### Where can I deploy my Flutter app? You can compile and deploy Flutter apps to iOS, Android, \[web]\[], \[desktop]\[] (macOS, Windows, Linux), and embedded devices. \[desktop]: /platform-integration/desktop ### What devices and OS versions does Flutter run on? {:#what-devices-and-os-versions-does-flutter-run-on} Flutter supports a broad range of operating systems and hardware platforms: \* Mobile: Android (ARM32, ARM64, x86\_64) and iOS (ARM64). \* Desktop: macOS (x86\_64, ARM64), Windows (x86\_64, ARM64), and Linux (x86\_64, ARM64). \* Web: Modern browsers supporting WebAssembly or JavaScript. For the complete list of supported OS versions, see \[Supported platforms]\[]. \[Supported platforms]: /reference/supported-platforms ### Does Flutter run on the web? Yes. Flutter web support is available on the stable channel and supports compilation to both WebAssembly (Wasm) and JavaScript. For more details, see \[Web support for Flutter]\[web]. ### Can I use Flutter to build desktop apps? Yes. Desktop support is available on the stable channel for macOS, Windows, and Linux. For details, see \[Desktop support for Flutter]\[desktop]. ### Can I use Flutter inside of my existing native app? Yes. You can integrate Flutter into existing Android and iOS applications using \[Add-to-App]\[add-to-app]. \[add-to-app]: /add-to-app ### Can I access platform services and APIs like sensors and local storage? Yes. Flutter provides access to platform-specific APIs and hardware through plugins available on \[pub.dev]\[ready-made packages]. You can also create custom integrations using \[platform channels]\[] or native interop (Dart FFI). \[ready-made packages]: https\://pub.dev/flutter/ ### Can I extend and customize the bundled widgets? Yes. Flutter's widget system is built around composition. Instead of subclassing complex widgets, you compose smaller, single-purpose widgets to create custom designs. This architecture provides control over layout, painting, and gesture handling. ### Why would I want to share layout code across iOS and Android? Sharing layout and UI code reduces engineering effort, keeps feature releases synchronized, and ensures a consistent brand experience across platforms. When you want platform-specific behavior or styling, you can adapt the UI using platform checks or responsive layouts. ### Can I interop with my mobile platform's default programming language? Yes. Flutter supports calling native code on all supported platforms: \* On Android: Kotlin and Java via \[platform channels]\[] and \[JNI interop]\[] (\`jnigen\`). \* On iOS and macOS: Swift and Objective-C via \[platform channels]\[] and \[Objective-C/Swift interop]\[] (\`ffigen\`). \* On desktop and mobile: C and C++ via Dart FFI (\[\`dart:ffi\`]\[]). \[JNI interop]: https\://dart.dev/interop/java-interop \[Objective-C/Swift interop]: https\://dart.dev/interop/objective-c-interop \[\`dart:ffi\`]: https\://dart.dev/interop/c-interop ### Does Flutter come with a reflection / mirrors system? No. Dart disables \`dart:mirrors\` in Flutter to enable aggressive ahead-of-time (AOT) compilation and dead-code elimination (tree shaking). This keeps binary sizes small and startup times fast. For metadata inspection and serialization, Flutter applications use compile-time code generation. ### Are internationalization and localization supported? Yes. Flutter provides built-in internationalization (i18n) and localization (l10n) support, including pluralization, bidirectional text, and date formatting. To learn more, see \[Internationalizing Flutter apps]\[internationalization documentation]. \[internationalization documentation]: /ui/internationalization ### What accessibility is supported? Flutter supports standard accessibility features (a11y), including screen readers (TalkBack, VoiceOver), large text scaling, high-contrast themes, and hardware switch access. For details, see \[Accessibility]\[accessibility documentation]. \[accessibility documentation]: /ui/accessibility ### How do I write parallel and/or concurrent apps for Flutter? Dart uses an event loop for asynchronous operations (\`Future\` and \`Stream\`). For CPU-intensive tasks, Flutter supports isolates—isolated execution threads with independent memory heaps that communicate through message passing. You can use \[\`Isolate.run()\`]\[] or the \[\`compute()\`]\[] function to run heavy computations in the background. \[\`Isolate.run()\`]: https\://api.flutter.dev/flutter/dart-isolate/Isolate/run.html \[\`compute()\`]: https\://api.flutter.dev/flutter/foundation/compute.html ### Can I run Dart code in the background of a Flutter app? Yes. You can execute Dart code in background processes on Android and iOS using background execution packages and platform service integrations. ### Can I use JSON, XML, or Protocol Buffers with Flutter? Yes. The Dart ecosystem provides packages on \[pub.dev]\[] for JSON serialization, XML parsing, Protocol Buffers, and other data formats. For details on working with JSON, see \[JSON and serialization]\[JSON tutorial]. \[JSON tutorial]: /data-and-backend/serialization/json \[pub.dev]: https\://pub.dev ### Can I build 3D apps with Flutter? Yes. Flutter supports 3D graphics through Flutter GPU and packages such as \`flutter\_scene\`, which use the Impeller rendering backend to draw 3D scenes. You can also embed third-party 3D views (such as Unity or native 3D engines) using platform views. ### Why is my APK or IPA so big? App bundle size includes the Flutter engine, framework code, application logic, and bundled assets (such as images, fonts, and audio). Always create a release build (\`flutter build apk\` or \`flutter build appbundle\` for Android, and \`flutter build ipa\` for iOS), which removes debug symbols and enables compiler optimizations. To analyze and reduce app size, see \[Measuring your app's size]\[]. ### Is Flutter ABI compatible? Flutter and Dart don't provide application binary interface (ABI) compatibility across different compiler releases. Always compile your application using matching SDK versions. ### How does Flutter handle scrolling? Flutter provides platform-specific scroll physics that match the native scrolling behavior of each platform (such as momentum scrolling on iOS and overscroll stretch on Android). For more details, see the \[scrolling]\[] documentation. \[scrolling]: /ui/layout/scrolling ## Framework ### Why is the build() method on State, rather than StatefulWidget? Placing the \`Widget build(BuildContext context)\` method on \`State\` rather than \`StatefulWidget\` provides greater flexibility when subclassing \`StatefulWidget\` and managing lifecycle state. For a detailed explanation, see the \[API documentation for \`State.build\`]\[]. \[API documentation for \`State.build\`]: https\://api.flutter.dev/flutter/widgets/State/build.html ### Where is Flutter's markup language? Why doesn't Flutter have a markup syntax? Flutter UIs are defined in Dart code rather than a declarative markup language. Defining UI in code enables: \* Fast development with stateful hot reload. \* Full access to programming language features (loops, conditionals, functions). \* Strong type safety and compiler checks. \* Easy refactoring and code reuse across widgets. Because build methods are standard code, you can also create widgets dynamically from data or external configurations. ### My app has a Debug banner in the upper right. Why am I seeing that? By default, \`flutter run\` launches the app in debug mode. The debug build configuration runs code inside the Dart VM to enable stateful \[hot reload]\[], and enables runtime assertions to catch errors early. The "Debug" banner indicates that these checks are active. To run without assertions and with optimized performance, run the app in release mode using \`flutter run --release\` or build a release binary. ### What programming paradigm does Flutter's framework use? {:#what-programming-paradigm-does-flutters-framework-use} Flutter uses a combination of programming paradigms, applying each where it is most effective: \*\*Composition\*\* : The primary paradigm in Flutter is composing small, single-purpose widgets into complex user interfaces. For example, the Material \[\`TextButton\`]\[] widget is composed of an \[\`IconTheme\`]\[], an \[\`InkWell\`]\[], a \[\`Padding\`]\[], a \[\`Center\`]\[], a \[\`Material\`]\[], an \[\`AnimatedDefaultTextStyle\`]\[], and a \[\`ConstrainedBox\`]\[]. \*\*Functional programming\*\* : Applications can define UI using \[\`StatelessWidget\`]\[] instances, which act as functions mapping configuration inputs to child widgets. Flutter uses immutable data structures across its widget hierarchy, including \[\`Widget\`]\[], \[\`TextStyle\`]\[], and \[\`Rect\`]\[]. \*\*Event-driven programming\*\* : User interactions are represented by event objects dispatched to callbacks registered on widgets. Classes like \[\`Listenable\`]\[] formalize event subscription models. \*\*Class-based object-oriented programming\*\* : The framework APIs use inheritance to specialize behavior. For example, \[\`RenderBox\`]\[] extends \[\`RenderObject\`]\[] to establish a Cartesian coordinate system for 2D layout. \*\*Prototype-based object-oriented programming\*\* : The \[\`ScrollPhysics\`]\[] class chains instances to compose scrolling physics dynamically at runtime. \*\*Imperative programming\*\* : Imperative code manages encapsulated state and test execution, where step-by-step logic is clearest. \*\*Reactive programming\*\* : Constructor parameters propagate changes down the widget tree, and user interactions trigger updates back up the tree through callbacks. State changes schedule rebuilds of affected subtrees. \*\*Declarative programming\*\* : Widget \`build\` methods describe the desired UI state declaratively, leaving layout and rendering updates to the framework. \*\*Generic programming\*\* : Generic types enforce type safety across the framework, such as parameterizing \[\`State\`]\[] with its widget type and \[\`GlobalKey\`]\[] with its state type. \*\*Concurrent programming\*\* : Flutter uses \[\`Future\`]\[] and \[\`Stream\`]\[] for asynchronous operations, and isolates for parallel background computation. \*\*Constraint programming\*\* : The layout system uses box constraints (minimum and maximum width and height) passed down from parent to child to compute geometry in a single pass. \[\`AnimatedDefaultTextStyle\`]: https\://api.flutter.dev/flutter/widgets/AnimatedDefaultTextStyle-class.html \[\`Center\`]: https\://api.flutter.dev/flutter/widgets/Center-class.html \[\`ConstrainedBox\`]: https\://api.flutter.dev/flutter/widgets/ConstrainedBox-class.html \[\`Future\`]: https\://api.flutter.dev/flutter/dart-async/Future-class.html \[\`GlobalKey\`]: https\://api.flutter.dev/flutter/widgets/GlobalKey-class.html \[\`IconTheme\`]: https\://api.flutter.dev/flutter/widgets/IconTheme-class.html \[\`InkWell\`]: https\://pub.dev/documentation/material\_ui/latest/material\_ui/InkWell-class.html \[\`Listenable\`]: https\://api.flutter.dev/flutter/foundation/Listenable-class.html \[\`Material\`]: https\://pub.dev/documentation/material\_ui/latest/material\_ui/Material-class.html \[\`Padding\`]: https\://api.flutter.dev/flutter/widgets/Padding-class.html \[\`Rect\`]: https\://api.flutter.dev/flutter/dart-ui/Rect-class.html \[\`RenderBox\`]: https\://api.flutter.dev/flutter/rendering/RenderBox-class.html \[\`RenderObject\`]: https\://api.flutter.dev/flutter/rendering/RenderObject-class.html \[\`ScrollPhysics\`]: https\://api.flutter.dev/flutter/widgets/ScrollPhysics-class.html \[\`State\`]: https\://api.flutter.dev/flutter/widgets/State-class.html \[\`StatelessWidget\`]: https\://api.flutter.dev/flutter/widgets/StatelessWidget-class.html \[\`Stream\`]: https\://api.flutter.dev/flutter/dart-async/Stream-class.html \[\`TextButton\`]: https\://pub.dev/documentation/material\_ui/latest/material\_ui/TextButton-class.html \[\`TextStyle\`]: https\://api.flutter.dev/flutter/painting/TextStyle-class.html \[\`Widget\`]: https\://api.flutter.dev/flutter/widgets/Widget-class.html ## Project ### Where can I get support? To report bugs or request features, file an issue in the \[Flutter issue tracker]\[issue tracker]. For community discussion, troubleshooting, and advice: \* Join the \[Flutter Discord]\[Discord]. \* Ask questions on \[Stack Overflow]\[] using the \`flutter\` tag. \* Participate in discussions on \[Reddit r/FlutterDev]\[Reddit]. \* Visit the \[Community]\[] page for additional resources. \[Community]: https\://flutter.dev/community \[Discord]: https\://discord.com/invite/rflutterdev \[issue tracker]: https\://github.com/flutter/flutter/issues \[Reddit]: https\://www\.reddit.com/r/FlutterDev/ \[Stack Overflow]: https\://stackoverflow\.com/tags/flutter ### How do I get involved? Flutter is open source, and contributions are welcome. You can help by: \* Reporting bugs and feature requests on the \[issue tracker]\[]. \* Contributing code and documentation by following the \[Contributing guide]\[]. \* Triaging issues or helping other developers on \[Discord]\[] and \[Stack Overflow]\[]. \[Contributing guide]: https\://github.com/flutter/flutter/blob/main/CONTRIBUTING.md ### Is Flutter open source? Yes. Flutter is open source and available under the BSD 3-Clause License on \[GitHub]\[]. \[GitHub]: https\://github.com/flutter/flutter ### Which software license(s) apply to Flutter and its dependencies? Flutter includes two main components: the engine (compiled binary with dependencies) and the framework (Dart source code). \* The framework is licensed under the \[BSD 3-Clause License]\[only one license]. \* The engine uses open source dependencies listed in its \[license file]\[]. \* Dart packages on pub.dev specify their own individual licenses. \[license file]: https\://github.com/flutter/flutter/blob/main/engine/src/flutter/sky/packages/sky\_engine/LICENSE \[only one license]: https\://github.com/flutter/flutter/blob/main/LICENSE ### How can I determine the licenses my Flutter application needs to show? Flutter provides built-in APIs to display open source licenses: \* If your application uses a \`Drawer\`, add an \[\`AboutListTile\`]\[]. \* If your application uses Material Design, call \[\`showAboutDialog\`]\[] or \[\`showLicensePage\`]\[]. \* For custom presentations, access the raw licenses from the \[\`LicenseRegistry\`]\[]. \[\`AboutListTile\`]: https\://pub.dev/documentation/material\_ui/latest/material\_ui/AboutListTile-class.html \[\`LicenseRegistry\`]: https\://api.flutter.dev/flutter/foundation/LicenseRegistry-class.html \[\`showAboutDialog\`]: https\://pub.dev/documentation/material\_ui/latest/material\_ui/showAboutDialog.html \[\`showLicensePage\`]: https\://pub.dev/documentation/material\_ui/latest/material\_ui/showLicensePage.html ### Who works on Flutter? Flutter is developed by engineers at Google along with hundreds of open source contributors worldwide. To join the community, see \[contribute to Flutter]\[]. \[contribute to Flutter]: https\://github.com/flutter/flutter/blob/main/CONTRIBUTING.md ### What are Flutter's guiding principles? Flutter is built around three core principles: \_Control\_ : Developers have full access to and control over every layer of the system. \_Performance\_ : Applications deliver smooth, responsive, jank-free experiences. \_Fidelity\_ : Applications achieve pixel-perfect, beautiful user experiences on every platform. ### Will Apple or Google reject my Flutter app? No. Thousands of Flutter apps are published, approved, and featured in the Apple App Store and Google Play Store. As with any application, ensure your app complies with Apple's \[App Store Review Guidelines]\[guidelines] and Google Play's Developer Program Policies. \[guidelines]: https\://developer.apple.com/app-store/review/guidelines/

----
url: https://docs.flutter.dev/tools/flutter-fix
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Flutter fix](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

As Flutter continues to evolve, we provide a tool to help you clean up deprecated APIs from your codebase. The tool ships as part of Flutter, and suggests changes that you might want to make to your code. The tool is available from the command line, and is also integrated into the IDE plugins for Android Studio and Visual Studio Code.

## Applying individual fixes

[#](#applying-individual-fixes)

You can use any supported IDE to apply a single fix at a time.

### IntelliJ and Android Studio

[#](#intellij-and-android-studio)

When the analyzer detects a deprecated API, a light bulb appears on that line of code. Clicking the light bulb displays the suggested fix that updates that code to the new API. Clicking the suggested fix performs the update.

A sample quick-fix in IntelliJ

### VS Code

[#](#vs-code)

When the analyzer detects a deprecated API, it presents an error. You can do any of the following:

* Hover over the error and then click the **Quick Fix** link. This presents a filtered list showing *only* fixes.

* Put the caret in the code with the error and click the light bulb icon that appears. This shows a list of all actions, including refactors.

* Put the caret in the code with the error and press the shortcut (**Command+.** on macOS, **Control+.** elsewhere) This shows a list of all actions, including refactors.

A sample code action in VS Code

## Applying project-wide fixes

[#](#applying-project-wide-fixes)

[dart fix Decoding Flutter](https://www.youtube.com/watch?v=OBIuSrg_Quo)

To see or apply changes to an entire project, you can use the command-line tool, [`dart fix`](https://dart.dev/tools/dart-fix).

This tool has two options:

* To see a full list of available changes, run the following command:

  ```
  $ dart fix --dry-run
  ```

* To apply all changes in bulk, run the following command:

  ```
  $ dart fix --apply
  ```

For more information on Flutter deprecations, read [Deprecation lifetime in Flutter](https://blog.flutter.dev/deprecation-lifetime-in-flutter-e4d76ee738ad) on the Flutter blog.

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-07-29. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/flutter-fix.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/tools/flutter-fix\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/flutter-fix.md "Report an issue with this page").

----
url: https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.60.0
----

The 2.60.0 release of the Dart and Flutter DevTools includes the following changes among other general improvements. To learn more about DevTools, check out the [DevTools overview](/tools/devtools).

## General updates

[#](#general-updates)

* Fixed an issue where DevTools could get stuck in a disconnected state (such as after a Mac goes to sleep) by adding a manual "Reconnect" button to the disconnected screen. - [#9838](https://github.com/flutter/devtools/issues/9838)
* Resolved several memory leaks. - [#9857](https://github.com/flutter/devtools/pull/9857)
* Fixed a bug where highlighted search matches in tables were unreadable in dark mode because the highlight color had become fully opaque. - [#9863](https://github.com/flutter/devtools/pull/9863)
* Rejected absolute paths in DevTools server file reads so they stay within the `~/.flutter-devtools/` directory and can't resolve to arbitrary files on disk. - [#9844](https://github.com/flutter/devtools/pull/9844)
* Validated the `devtoolsOptionsUri` parameter in the extension enabled-state handler so it must be a `file:` URI named `devtools_options.yaml`, preventing the DevTools server from writing to arbitrary file paths. - [#9834](https://github.com/flutter/devtools/pull/9834)

## Inspector updates

[#](#inspector-updates)

* Fixed an issue where the Inspector error badge count would improperly increase or disappear during navigation. - [#9524](https://github.com/flutter/devtools/issues/9524)

## Performance updates

[#](#performance-updates)

* Fixed a bug where the selected feature tab wasn't restored when loading exported Performance data. - [#9861](https://github.com/flutter/devtools/pull/9861)

## Network profiler updates

[#](#network-profiler-updates)

* Fixed the Network tab search field becoming disabled after clearing all requests, so the search query can now be edited at any time. - [#9855](https://github.com/flutter/devtools/pull/9855)

## Deep links tool updates

[#](#deep-links-tool-updates)

* Only validate deep links when connected to a Flutter app. - [#8081](https://github.com/flutter/devtools/issues/8081)

## Full commit history

[#](#full-commit-history)

To find a complete list of changes in this release, check out the [DevTools git log](https://github.com/flutter/devtools/tree/v2.60.0).

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-07-09. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/release-notes/release-notes-2.60.0.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.60.0\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/release-notes/release-notes-2.60.0.md "Report an issue with this page").

----
url: https://docs.flutter.dev/packages-and-plugins/using-packages
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Using packages](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

Flutter supports using shared packages contributed by other developers to the Flutter and Dart ecosystems. This allows quickly building an app without having to develop everything from scratch.

This page discusses using both [packages](/resources/glossary#package)PackageAt a minimum, a Dart package is a directory containing a \`pubspec.yaml\` file. [Learn more](/resources/glossary#package "Learn more about 'Package' and find related resources.") and [plugins](/resources/glossary#plugin)PluginA plugin package (generally referred to as \_a plugin\_), is a special kind of package that makes platform functionallity available to the app. [Learn more](/resources/glossary#plugin "Learn more about 'Plugin' and find related resources."). To write a new package, see [developing packages](/packages-and-plugins/developing-packages).

When you write an app, existing packages enable many use cases. For example, as of Flutter 3.47, [the stylized widget libraries have been moved out of core Flutter to packages that you can import](/ui). Other existing packages enable your app to make network requests ([`http`](/cookbook/networking/fetch-data)), handle navigation ([`go_router`](https://pub.dev/packages/go_router)), integrate with device APIs ([`url_launcher`](https://pub.dev/packages/url_launcher) and [`battery_plus`](https://pub.dev/packages/battery_plus)), and use third-party platform SDKs like Firebase ([FlutterFire](https://github.com/firebase/flutterfire)).

## Using packages

[#](#using-packages)

The following section describes how to use existing published packages.

### Searching for packages

[#](#searching-for-packages)

Packages are published to [pub.dev](https://pub.dev).

The [Flutter landing page](https://pub.dev/flutter) on pub.dev displays top packages that are compatible with Flutter (those that declare dependencies generally compatible with Flutter), and supports searching among all published packages.

The [Flutter Favorites](https://pub.dev/flutter/favorites) page on pub.dev lists the plugins and packages that have been identified as packages you should first consider using when writing your app. For more information on what it means to be a Flutter Favorite, see the [Flutter Favorites program](/packages-and-plugins/favorites).

You can also browse the packages on pub.dev by filtering on [Android](https://pub.dev/packages?q=sdk%3Aflutter+platform%3Aandroid), [iOS](https://pub.dev/packages?q=sdk%3Aflutter+platform%3Aios), [web](https://pub.dev/packages?q=sdk%3Aflutter+platform%3Aweb), [Linux](https://pub.dev/packages?q=sdk%3Aflutter+platform%3Alinux), [Windows](https://pub.dev/packages?q=sdk%3Aflutter+platform%3Awindows), [macOS](https://pub.dev/packages?q=sdk%3Aflutter+platform%3Amacos), or any combination thereof.

### Adding a package dependency to an app using `flutter pub add`

[#](#adding-a-package-dependency-to-an-app-using-flutter-pub-add)

To add the package `english_words` to an app:

1. Use the [`pub add`](https://dart.dev/tools/pub/cmd/pub-add) command from inside the project directory

   * `flutter pub add english_words`

2. Import it

   * Add a corresponding `import` statement in the Dart code.

3. Stop and restart the app, if necessary

   * If the package brings platform-specific code (Kotlin/Java 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 might be required to avoid errors like `MissingPluginException` when using the package.

### Adding a package dependency to an app

[#](#adding-a-package-dependency-to-an-app)

To add the package `english_words` to an app:

1. Depend on it

   * Open the `pubspec.yaml` file located inside the app folder, and add `english_words: ^4.0.0` under `dependencies`.

2. Install it

   * From the terminal: Run `flutter pub get`.\
     **OR**
   * From VS Code: Click **Get Packages** located in right side of the action ribbon at the top of `pubspec.yaml` indicated by the Download icon.
   * From Android Studio/IntelliJ: Click **Pub get** in the action ribbon at the top of `pubspec.yaml`.

3. Import it

   * Add a corresponding `import` statement in the Dart code.

4. Stop and restart the app, if necessary

   * If the package brings platform-specific code (Kotlin/Java 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 might be required to avoid errors like `MissingPluginException` when using the package.

### Removing a package dependency from an app using `flutter pub remove`

[#](#removing-a-package-dependency-from-an-app-using-flutter-pub-remove)

To remove the package `english_words` from an app:

1. Use the [`pub remove`](https://dart.dev/tools/pub/cmd/pub-remove) command from inside the project directory
   * `flutter pub remove english_words`

The [Installing tab](https://pub.dev/packages/english_words/install), available on any package page on pub.dev, is a handy reference for these steps.

For a complete example, see the [english\_words example](#english-words-example) below.

### Conflict resolution

[#](#conflict-resolution)

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. That causes a potential conflict. The best way to avoid this is for package authors to use [version ranges](https://dart.dev/tools/pub/dependencies#version-constraints) rather than specific versions when specifying dependencies.

yaml

```
dependencies:
  url_launcher: ^5.4.0    # Good, any version >= 5.4.0 but < 6.0.0
  image_picker: '5.4.3'   # Not as good, only version 5.4.3 works.
```

If `some_package` declares the dependencies above and `another_package` declares a compatible `url_launcher` dependency like `'5.4.6'` or `^5.5.0`, pub resolves the issue automatically. Platform-specific dependencies on [Gradle modules](https://docs.gradle.org/current/userguide/declaring_dependencies.html) and/or [CocoaPods](https://guides.cocoapods.org/syntax/podspec.html#dependency) are solved in a similar way.

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.

For example, to force the use of `url_launcher` version `5.4.0`, make the following changes to the app's `pubspec.yaml` file:

yaml

```
dependencies:
  some_package:
  another_package:
dependency_overrides:
  url_launcher: '5.4.0'
```

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 `28.0`, make the following changes to the app's `android/build.gradle` file:

* [Kotlin](#16-tab-panel)
* [Groovy](#17-tab-panel)

android/app/build.gradle.kts

kotlin

```
configurations.all {
    resolutionStrategy {
        force("com.google.guava:guava:28.0-android")
    }
}
```

android/app/build.gradle

groovy

```
configurations.all {
    resolutionStrategy {
        force 'com.google.guava:guava:28.0-android'
    }
}
```

CocoaPods doesn't currently offer dependency override functionality.

## Developing new packages

[#](#developing-new-packages)

If no package exists for your specific use case, you can [write a custom package](/packages-and-plugins/developing-packages).

## Managing package dependencies and versions

[#](#managing-package-dependencies-and-versions)

To minimize the risk of version collisions, specify a version range in the `pubspec.yaml` file.

### Package versions

[#](#package-versions)

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`](https://pub.dev/packages/url_launcher) package), as well as a list of all prior versions (see [`url_launcher` versions](https://pub.dev/packages/url_launcher/versions)).

To ensure that the app doesn't break when you update a package, specify a version range using one of the following formats.

* **Ranged constraints:** Specify a minimum and maximum version.

  yaml

  ```
  dependencies:
    url_launcher: '>=5.4.0 <6.0.0'
  ```

* **Ranged constraints using the [caret syntax](https://dart.dev/tools/pub/dependencies#caret-syntax):** Specify the version that serves as the inclusive minimum version. This covers all versions from that version to the next major version.

  yaml

  ```
  dependencies:
    collection: '^5.4.0'
  ```

  This syntax means the same as the one noted in the first bullet.

To learn more, check out [package dependency management](/packages-and-plugins/dependency-management) and the [package versioning guide](https://dart.dev/tools/pub/versioning).

### Updating package dependencies

[#](#updating-package-dependencies)

When running `flutter pub get` for the first time after adding a package, Flutter saves the concrete package version found in the `pubspec.lock` [lockfile](https://dart.dev/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`.

To upgrade to a new version of the package, for example to use new features in that package, run `flutter pub upgrade` to retrieve the highest available version of the package that is allowed by the version constraint specified in `pubspec.yaml`. Note that this is a different command from `flutter upgrade` or `flutter update-packages`, which both update Flutter itself.

### Dependencies on unpublished packages

[#](#dependencies-on-unpublished-packages)

Packages can be used even when not published on pub.dev. For private packages, or for packages not ready for publishing, additional dependency options are available:

* **Path dependency**

  A Flutter app can depend on a package using a file system `path:` dependency. The path can be either relative or absolute. Relative paths are evaluated relative to the directory containing `pubspec.yaml`. For example, to depend on a package, packageA, located in a directory next to the app, use the following syntax:

  yaml

  ```
    dependencies:
    packageA:
      path: ../packageA/
  ```

* **Git dependency**

  You can also depend on a package stored in a Git repository. If the package is located at the root of the repo, use the following syntax:

  yaml

  ```
    dependencies:
      packageA:
        git:
          url: https://github.com/flutter/packageA.git
  ```

* **Git dependency using SSH**

  If the repository is private and you can connect to it using SSH, depend on the package by using the repo's SSH url:

  yaml

  ```
    dependencies:
      packageA:
        git:
          url: git@github.com:flutter/packageA.git
  ```

* **Git dependency on a package in a folder**

  Pub assumes the package is located in the root of the Git repository. If that isn't the case, specify the location with the `path` argument. For example:

  yaml

  ```
  dependencies:
    packageA:
      git:
        url: https://github.com/flutter/packages.git
        path: packages/packageA
  ```

  Finally, use the `ref` argument to pin the dependency to a specific git commit, branch, or tag. For more details, see [Package dependencies](https://dart.dev/tools/pub/dependencies).

## Examples

[#](#examples)

The following examples walk through the necessary steps for using packages.

### Example: Using the english\_words package

[#](#english-words-example)

The [`english_words`](https://pub.dev/packages/english_words) package contains a few thousand of the most used English words plus some utility functions.

To use this package:

1. Create a new project called `words_demo`.

2. Run `dart pub add english_words` to add the dependency.

3. Open `lib/main.dart` and replace its full contents with:

   dart

   ```
   import 'package:english_words/english_words.dart';
   import 'package:material_ui/material_ui.dart';
   ​
   void main() {
     runApp(const MyApp());
   }
   ​
   class MyApp extends StatelessWidget {
     const MyApp({super.key});
   ​
     @override
     Widget build(BuildContext context) {
       return const MaterialApp(home: DemoPage());
     }
   }
   ​
   class DemoPage extends StatelessWidget {
     const DemoPage({super.key});
   ​
     @override
     Widget build(BuildContext context) {
       return Scaffold(
         body: Center(child: Text(generateWordPairs().first.asPascalCase)),
       );
     }
   }
   ```

4. Run the app. The app's text should display a random English word pair.

### Example: Using the url\_launcher package to launch the browser

[#](#url-example)

The [`url_launcher`](https://pub.dev/packages/url_launcher) plugin package enables opening the default browser on the mobile platform to display a given URL, and is supported on Android, iOS, web, Windows, Linux, and macOS. This package is a special Dart package called a *plugin package* (or *plugin*), which includes platform-specific code.

To use this plugin:

1. Create a new project called `launchdemo`.

2. Open `pubspec.yaml`, and add the `url_launcher` dependency:

   yaml

   ```
   dependencies:
     flutter:
       sdk: flutter
     url_launcher: ^5.4.0
   ```

3. Run `flutter pub get` in the terminal, or click **Get Packages get** in VS Code.

4. Open `lib/main.dart` and replace its full contents with the following:

   dart

   ```
   import 'package:material_ui/material_ui.dart';
   import 'package:url_launcher/url_launcher.dart';
   ​
   void main() {
     runApp(const MyApp());
   }
   ​
   class MyApp extends StatelessWidget {
     const MyApp({super.key});
   ​
     @override
     Widget build(BuildContext context) {
       return const MaterialApp(home: DemoPage());
     }
   }
   ​
   class DemoPage extends StatelessWidget {
     const DemoPage({super.key});
   ​
     void launchURL() {
       launchUrl(Uri.parse('https://flutter.dev'));
     }
   ​
     @override
     Widget build(BuildContext context) {
       return Scaffold(
         body: Center(
           child: ElevatedButton(
             onPressed: launchURL,
             child: const Text('Show Flutter homepage'),
           ),
         ),
       );
     }
   }
   ```

5. 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 homepage for flutter.dev.

## Install AI skills from packages

[#](#install-ai-skills-from-packages)

Many `pub.dev` packages bundle official **package skills** that teach AI coding assistants how to use their APIs and follow recommended patterns.

If your project dependencies include skills, you can discover and install them into your local workspace with a single command:

bash

```
dart run skills@ get
```

To learn more about how skills help coding assistants understand your dependencies, check out [Package skills](https://dart.dev/ai/package-skills).

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-09-29. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/packages-and-plugins/using-packages.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/packages-and-plugins/using-packages\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/packages-and-plugins/using-packages.md "Report an issue with this page").

----
url: https://docs.flutter.dev/learn/pathway/tutorial/stateful-widget
----

Learn when widgets need to be stateful and how to trigger UI updates with setState.

[Watch on YouTube in a new tab: "Stateful widgets in Flutter"](https://www.youtube.com/watch/Gzz8FwSlsUg)

change\_circle Learn when widgets need to be stateful

swap\_horiz Convert a StatelessWidget to a StatefulWidget

refresh Trigger UI updates with setState

***

## Steps

1### Introductionkeyboard\_arrow\_up

So far, your app displays a grid and an input field, but the grid doesn't yet update to reflect the user's guesses. When this app is complete, each tile in the next unfilled row should update after each submitted user guess by:

* Displaying the correct letter.
* Changing color to reflect whether the letter is correct (green), is in the word but at an incorrect position (yellow), or doesn't appear in the word at all (grey).

To handle this dynamic behavior, you need to convert `GamePage` from a `StatelessWidget` to a [`StatefulWidget`](https://api.flutter.dev/flutter/widgets/StatefulWidget-class.html).

2### Why stateful widgets?keyboard\_arrow\_up

When a widget's appearance or data needs to change during its lifetime, you need a `StatefulWidget` and a companion `State` object. While the `StatefulWidget` itself is still immutable (its properties can't change after creation), the `State` object is long-lived, can hold mutable data, and can be rebuilt when that data changes, causing the UI to update.

For example, the following widget tree imagines a simple app that uses a stateful widget with a counter that increases when the button is pressed.

Here is the basic `StatefulWidget` structure (doesn't do anything yet):

dart

```
class ExampleWidget extends StatefulWidget {
  ExampleWidget({super.key});
​
  @override
  State<ExampleWidget> createState() => _ExampleWidgetState();
}
​
class _ExampleWidgetState extends State<ExampleWidget> {
  @override
  Widget build(BuildContext context) {
    return Container();
  }
}
```

3### Convert `GamePage` to a stateful widgetkeyboard\_arrow\_up

To convert the `GamePage` (or any other) widget from a stateless widget to a stateful widget, do the following steps:

1. Change `GamePage` to extend `StatefulWidget` instead of `StatelessWidget`.
2. Create a new class named `_GamePageState`, that extends `State<GamePage>`. This new class will hold the mutable state and the `build` method. Move the `build` method and all properties *instantiated on the widget* from `GamePage` to the state object.
3. Implement the `createState()` method in `GamePage`, which returns an instance of `_GamePageState`.

Your modified code should look like this:

dart

```
class GamePage extends StatefulWidget {
  GamePage({super.key});
​
  @override
  State<GamePage> createState() => _GamePageState();
}
​
class _GamePageState extends State<GamePage> {
  final Game _game = Game();
​
  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(8.0),
      child: Column(
        children: [
          for (var guess in _game.guesses)
            Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                for (var letter in guess)
                  Padding(
                    padding: const EdgeInsets.symmetric(horizontal: 2.5, vertical: 2.5),
                    child: Tile(letter.char, letter.type),
                  )
              ],
            ),
          GuessInput(
            onSubmitGuess: (_) {
              // TODO, handle guess
              print(guess); // Temporary
            },
          ),
        ],
      ),
    );
  }
}
```

4### Updating the UI with `setState`keyboard\_arrow\_up

Whenever you mutate a `State` object, you must call [`setState`](https://api.flutter.dev/flutter/widgets/State/setState.html) to signal the framework to update the user interface and call the `build` method again.

In this app, when a user makes a guess, the word they guessed is saved on the `Game` object, which is a property on the `GamePage` class, and therefore is state that might change and require the UI to update. When this state is mutated, the grid should be re-drawn to show the user's guess.

To implement this, update the callback function passed to `GuessInput`. The function needs to call `setState` and, within `setState`, it needs to execute the logic to determine whether the users guess was correct.

Update your code:

dart

```
class GamePage extends StatefulWidget {
  GamePage({super.key});
​
  @override
  State<GamePage> createState() => _GamePageState();
}
​
class _GamePageState extends State<GamePage> {
  final Game _game = Game();
​
  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(8.0),
      child: Column(
        children: [
          for (var guess in _game.guesses)
            Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                for (var letter in guess)
                  Padding(
                    padding: const EdgeInsets.symmetric(horizontal: 2.5, vertical: 2.5),
                    child: Tile(letter.char, letter.type),
                  )
              ],
            ),
          GuessInput(
            onSubmitGuess: (String guess) {
              setState(() { // NEW
                _game.guess(guess);
              });
            },
          ),
        ],
      ),
    );
  }
}
```

Now, when you type a legal guess into the `TextInput` and submit it, the application will reflect the user's guess. If you were to call `_game.guess(guess)` *without* a calling `setState`, the internal game data would change, but Flutter wouldn't know it needs to repaint the screen, and the user wouldn't see any updates.

5### Convert `GuessInput` to a stateful widgetkeyboard\_arrow\_up

When `GamePage` rebuilds after calling `setState`, all of its child widgets are rebuilt as well. Because `GuessInput` was originally created as a `StatelessWidget`, its `TextEditingController` and `FocusNode` were stored directly in the widget class. Since a new widget instance is created on every rebuild, these controllers were recreated each time, causing the text input field to lose focus and leaving unused controllers without proper disposal.

To keep focus on the text field between guesses and manage controller lifecycles properly, convert `GuessInput` into a `StatefulWidget`:

1. Change `GuessInput` to extend `StatefulWidget` instead of `StatelessWidget`.
2. Create a companion `_GuessInputState` class extending `State<GuessInput>`.
3. Move `_textEditingController`, `_focusNode`, `_onSubmit()`, and `build()` into `_GuessInputState`.
4. Implement `dispose()` to clean up `_textEditingController` and `_focusNode`.

Your modified `GuessInput` widget should look like this:

dart

```
class GuessInput extends StatefulWidget {
  const GuessInput({super.key, required this.onSubmitGuess});
​
  final void Function(String) onSubmitGuess;
​
  @override
  State<GuessInput> createState() => _GuessInputState();
}
​
class _GuessInputState extends State<GuessInput> {
  final TextEditingController _textEditingController = TextEditingController();
  final FocusNode _focusNode = FocusNode();
​
  @override
  void dispose() {
    _textEditingController.dispose();
    _focusNode.dispose();
    super.dispose();
  }
​
  void _onSubmit() {
    widget.onSubmitGuess(_textEditingController.text.trim());
    _textEditingController.clear();
    _focusNode.requestFocus();
  }
​
  @override
  Widget build(BuildContext context) {
    return Row(
      children: [
        Expanded(
          child: Padding(
            padding: const EdgeInsets.all(8.0),
            child: TextField(
              maxLength: 5,
              focusNode: _focusNode,
              autofocus: true,
              decoration: const InputDecoration(
                border: OutlineInputBorder(
                  borderRadius: BorderRadius.all(Radius.circular(35)),
                ),
              ),
              controller: _textEditingController,
              onSubmitted: (input) {
                _onSubmit();
              },
            ),
          ),
        ),
        IconButton(
          padding: EdgeInsets.zero,
          icon: const Icon(Icons.arrow_circle_up),
          onPressed: _onSubmit,
        ),
      ],
    );
  }
}
```

By converting `GuessInput` to a `StatefulWidget`, `_GuessInputState` persists across parent rebuilds, keeping focus on the text field after each guess is submitted and properly disposing of resources when the widget is unmounted.

6### Reviewkeyboard\_arrow\_up

change\_circle Learned when widgets need to be stateful keyboard\_arrow\_up

When a widget's appearance or data needs to change during its lifetime, you need a `StatefulWidget`. The widget itself stays immutable, but its companion `State` object holds mutable data and triggers rebuilds.

swap\_horiz Converted GamePage and GuessInput to StatefulWidgets keyboard\_arrow\_up

You refactored `GamePage` and `GuessInput` to be stateful by creating companion `State` classes, moving mutable properties and lifecycle management (like `dispose`) to them, and implementing `createState()`. Your IDE's support for quick assists can automate this conversion.

refresh Made your app respond to user input with setState keyboard\_arrow\_up

Calling `setState` tells Flutter to rebuild the UI of a widget. When a user submits a guess, you call `setState` to update the game state, and the grid automatically reflects the new data while maintaining text field focus. Your app is now truly interactive!

7### Test yourselfkeyboard\_arrow\_up

### Stateful Widgets Quiz

1 / 2

**When should you use a StatefulWidget instead of a StatelessWidget?**

1. When the widget needs to make HTTP requests.

   Not quite.

   HTTP requests can be made from either, but state changes require StatefulWidget.

2. When the widget's appearance or data needs to change during its lifetime.

   That's right!

   StatefulWidget is needed when the UI must update in response to data changes over time.

3. When the widget has more than three child widgets.

   Not quite.

   The number of children doesn't determine whether a widget is stateful.

4. When the widget is at the root of the widget tree.

   Not quite.

   Root widgets can be stateless; statefulness depends on whether data changes during the widget's lifetime.

**What happens if you change data in a State object without calling setState?**

1. The app will crash with an error.

   Not quite.

   The app won't crash, but the UI won't update.

2. The data changes internally, but Flutter won't rebuild the UI to reflect the change.

   That's right!

   Without calling setState, Flutter doesn't know it needs to repaint, so the user won't see updates.

3. Flutter automatically detects the change and rebuilds the UI.

   Not quite.

   Flutter requires setState to know when to rebuild; it doesn't auto-detect changes.

4. The widget is removed from the widget tree.

   Not quite.

   The widget remains; it just won't visually update without setState.

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-08-21. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/learn/pathway/tutorial/stateful-widget.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/learn/pathway/tutorial/stateful-widget\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/learn/pathway/tutorial/stateful-widget.md "Report an issue with this page").

----
url: https://docs.flutter.dev/packages-and-plugins/using-packages.md
----

\# Using packages > How to use packages in your Flutter app.

<!--?code-excerpt path-base="platform_integration/plugin_api_migration"?-->

Flutter supports using shared packages contributed by other developers to the Flutter and Dart ecosystems. This allows quickly building an app without having to develop everything from scratch. This page discusses using both \[packages]\[] and \[plugins]\[]. To write a new package, see \[developing packages]\[]. \[packages]: /resources/glossary#package \[plugins]: /resources/glossary#plugin When you write an app, existing packages enable many use cases. For example, as of Flutter 3.47, \[the stylized widget libraries have been moved out of core Flutter to packages that you can import]\[ui-page]. Other existing packages enable your app to make network requests (\[\`http\`]\[]), handle navigation (\[\`go\_router\`]\[]), integrate with device APIs (\[\`url\_launcher\`]\[] and \[\`battery\_plus\`]\[]), and use third-party platform SDKs like Firebase (\[FlutterFire]\[]). \[\`battery\_plus\`]: https\://pub.dev/packages/battery\_plus \[developing packages]: /packages-and-plugins/developing-packages \[FlutterFire]: https\://github.com/firebase/flutterfire \[\`http\`]: /cookbook/networking/fetch-data \[\`go\_router\`]: https\://pub.dev/packages/go\_router \[pub.dev]: https\://pub.dev \[ui-page]: /ui \[\`url\_launcher\`]: https\://pub.dev/packages/url\_launcher ## Using packages The following section describes how to use existing published packages. ### Searching for packages Packages are published to \[pub.dev]\[]. The \[Flutter landing page]\[] on pub.dev displays top packages that are compatible with Flutter (those that declare dependencies generally compatible with Flutter), and supports searching among all published packages. The \[Flutter Favorites]\[] page on pub.dev lists the plugins and packages that have been identified as packages you should first consider using when writing your app. For more information on what it means to be a Flutter Favorite, see the \[Flutter Favorites program]\[]. You can also browse the packages on pub.dev by filtering on \[Android]\[], \[iOS]\[], \[web]\[], \[Linux]\[], \[Windows]\[], \[macOS]\[], or any combination thereof. \[Android]: https\://pub.dev/packages?q=sdk%3Aflutter+platform%3Aandroid \[Flutter Favorites]: https\://pub.dev/flutter/favorites \[Flutter Favorites program]: /packages-and-plugins/favorites \[Flutter landing page]: https\://pub.dev/flutter \[Linux]: https\://pub.dev/packages?q=sdk%3Aflutter+platform%3Alinux \[iOS]: https\://pub.dev/packages?q=sdk%3Aflutter+platform%3Aios \[macOS]: https\://pub.dev/packages?q=sdk%3Aflutter+platform%3Amacos \[web]: https\://pub.dev/packages?q=sdk%3Aflutter+platform%3Aweb \[Windows]: https\://pub.dev/packages?q=sdk%3Aflutter+platform%3Awindows ### Adding a package dependency to an app using \`flutter pub add\` To add the package \`english\_words\` to an app: 1. Use the \[\`pub add\`]\[] command from inside the project directory \* \`flutter pub add english\_words\` 1. Import it \* Add a corresponding \`import\` statement in the Dart code. 1. Stop and restart the app, if necessary \* If the package brings platform-specific code (Kotlin/Java 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 might be required to avoid errors like \`MissingPluginException\` when using the package. \[\`pub add\`]: https\://dart.dev/tools/pub/cmd/pub-add ### Adding a package dependency to an app To add the package \`english\_words\` to an app: 1. Depend on it \* Open the \`pubspec.yaml\` file located inside the app folder, and add \`english\_words: ^4.0.0\` under \`dependencies\`. 1. Install it \* From the terminal: Run \`flutter pub get\`.\
\*\*OR\*\* \* From VS Code: Click \*\*Get Packages\*\* located in right side of the action ribbon at the top of \`pubspec.yaml\` indicated by the Download icon. \* From Android Studio/IntelliJ: Click \*\*Pub get\*\* in the action ribbon at the top of \`pubspec.yaml\`. 1. Import it \* Add a corresponding \`import\` statement in the Dart code. 1. Stop and restart the app, if necessary \* If the package brings platform-specific code (Kotlin/Java 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 might be required to avoid errors like \`MissingPluginException\` when using the package. ### Removing a package dependency from an app using \`flutter pub remove\` To remove the package \`english\_words\` from an app: 1. Use the \[\`pub remove\`]\[] command from inside the project directory \* \`flutter pub remove english\_words\` The \[Installing tab]\[], available on any package page on pub.dev, is a handy reference for these steps. For a complete example, see the \[english\_words example]\[] below. \[english\_words example]: #english-words-example \[Installing tab]: https\://pub.dev/packages/english\_words/install \[\`pub remove\`]: https\://dart.dev/tools/pub/cmd/pub-remove ### Conflict resolution 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. That causes a potential conflict. The best way to avoid this is for package authors to use \[version ranges]\[] rather than specific versions when specifying dependencies. \`\`\`yaml dependencies: url\_launcher: ^5.4.0 # Good, any version >= 5.4.0 but < 6.0.0 image\_picker: '5.4.3' # Not as good, only version 5.4.3 works. \`\`\` If \`some\_package\` declares the dependencies above and \`another\_package\` declares a compatible \`url\_launcher\` dependency like \`'5.4.6'\` or \`^5.5.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 \`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. For example, to force the use of \`url\_launcher\` version \`5.4.0\`, make the following changes to the app's \`pubspec.yaml\` file: \`\`\`yaml dependencies: some\_package: another\_package: dependency\_overrides: url\_launcher: '5.4.0' \`\`\` 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 \`28.0\`, make the following changes to the app's \`android/build.gradle\` file: \`\`\`kotlin title="android/app/build.gradle.kts" configurations.all { resolutionStrategy { force("com.google.guava:guava:28.0-android") } } \`\`\` \`\`\`groovy title="android/app/build.gradle" configurations.all { resolutionStrategy { force 'com.google.guava:guava:28.0-android' } } \`\`\` CocoaPods doesn't currently offer dependency override functionality. \[CocoaPods]: https\://guides.cocoapods.org/syntax/podspec.html#dependency \[Gradle modules]: https\://docs.gradle.org/current/userguide/declaring\_dependencies.html \[version ranges]: https\://dart.dev/tools/pub/dependencies#version-constraints ## Developing new packages If no package exists for your specific use case, you can \[write a custom package]\[]. \[write a custom package]: /packages-and-plugins/developing-packages ## Managing package dependencies and versions To minimize the risk of version collisions, specify a version range in the \`pubspec.yaml\` file. ### Package versions 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\`]\[] package), as well as a list of all prior versions (see \[\`url\_launcher\` versions]\[]). To ensure that the app doesn't break when you update a package, specify a version range using one of the following formats. \* \*\*Ranged constraints:\*\* Specify a minimum and maximum version. \`\`\`yaml dependencies: url\_launcher: '>=5.4.0 <6.0.0' \`\`\` \* \*\*Ranged constraints using the \[caret syntax]\[]:\*\* Specify the version that serves as the inclusive minimum version. This covers all versions from that version to the next major version. \`\`\`yaml dependencies: collection: '^5.4.0' \`\`\` This syntax means the same as the one noted in the first bullet. To learn more, check out \[package dependency management]\[] and the \[package versioning guide]\[]. \[caret syntax]: https\://dart.dev/tools/pub/dependencies#caret-syntax \[package dependency management]: /packages-and-plugins/dependency-management \[package versioning guide]: https\://dart.dev/tools/pub/versioning \[\`url\_launcher\` versions]: https\://pub.dev/packages/url\_launcher/versions ### Updating package dependencies When running \`flutter pub get\` for the first time after adding a package, Flutter saves the concrete package version found in the \`pubspec.lock\` \[lockfile]\[]. This ensures that you get the same version again if you, or another developer on your team, run \`flutter pub get\`. To upgrade to a new version of the package, for example to use new features in that package, run \`flutter pub upgrade\` to retrieve the highest available version of the package that is allowed by the version constraint specified in \`pubspec.yaml\`. Note that this is a different command from \`flutter upgrade\` or \`flutter update-packages\`, which both update Flutter itself. \[lockfile]: https\://dart.dev/tools/pub/glossary#lockfile ### Dependencies on unpublished packages Packages can be used even when not published on pub.dev. For private packages, or for packages not ready for publishing, additional dependency options are available: \*\*Path dependency\*\* : A Flutter app can depend on a package using a file system \`path:\` dependency. The path can be either relative or absolute. Relative paths are evaluated relative to the directory containing \`pubspec.yaml\`. For example, to depend on a package, packageA, located in a directory next to the app, use the following syntax: \`\`\`yaml dependencies: packageA: path: ../packageA/ \`\`\` \*\*Git dependency\*\* : You can also depend on a package stored in a Git repository. If the package is located at the root of the repo, use the following syntax: \`\`\`yaml dependencies: packageA: git: url: https\://github.com/flutter/packageA.git \`\`\` \*\*Git dependency using SSH\*\* : If the repository is private and you can connect to it using SSH, depend on the package by using the repo's SSH url: \`\`\`yaml dependencies: packageA: git: url: git\@github.com:flutter/packageA.git \`\`\` \*\*Git dependency on a package in a folder\*\* : Pub assumes the package is located in the root of the Git repository. If that isn't the case, specify the location with the \`path\` argument. For example: \`\`\`yaml dependencies: packageA: git: url: https\://github.com/flutter/packages.git path: packages/packageA \`\`\` Finally, use the \`ref\` argument to pin the dependency to a specific git commit, branch, or tag. For more details, see \[Package dependencies]\[]. \[Package dependencies]: https\://dart.dev/tools/pub/dependencies ## Examples The following examples walk through the necessary steps for using packages. ### Example: Using the english\_words package {:#english-words-example} The \[\`english\_words\`]\[] package contains a few thousand of the most used English words plus some utility functions. To use this package: 1. Create a new project called \`words\_demo\`. 1. Run \`dart pub add english\_words\` to add the dependency. 1. Open \`lib/main.dart\` and replace its full contents with:

<!--?code-excerpt "lib/english_words.dart (english-words)"?-->

\`\`\`dart import 'package:english\_words/english\_words.dart'; import 'package:material\_ui/material\_ui.dart'; void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return const MaterialApp(home: DemoPage()); } } class DemoPage extends StatelessWidget { const DemoPage({super.key}); @override Widget build(BuildContext context) { return Scaffold( body: Center(child: Text(generateWordPairs().first.asPascalCase)), ); } } \`\`\` 1. Run the app. The app's text should display a random English word pair. \[\`english\_words\`]: https\://pub.dev/packages/english\_words ### Example: Using the url\_launcher package to launch the browser {:#url-example} The \[\`url\_launcher\`]\[] plugin package enables opening the default browser on the mobile platform to display a given URL, and is supported on Android, iOS, web, Windows, Linux, and macOS. This package is a special Dart package called a \_plugin package\_ (or \_plugin\_), which includes platform-specific code. To use this plugin: 1. Create a new project called \`launchdemo\`. 1. Open \`pubspec.yaml\`, and add the \`url\_launcher\` dependency: \`\`\`yaml dependencies: flutter: sdk: flutter url\_launcher: ^5.4.0 \`\`\` 1. Run \`flutter pub get\` in the terminal, or click \*\*Get Packages get\*\* in VS Code. 1. Open \`lib/main.dart\` and replace its full contents with the following:

<!--?code-excerpt "lib/url_launcher.dart (url-launcher)"?-->

\`\`\`dart import 'package:material\_ui/material\_ui.dart'; import 'package:url\_launcher/url\_launcher.dart'; void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return const MaterialApp(home: DemoPage()); } } class DemoPage extends StatelessWidget { const DemoPage({super.key}); void launchURL() { launchUrl(Uri.parse('https\://flutter.dev')); } @override Widget build(BuildContext context) { return Scaffold( body: Center( child: ElevatedButton( onPressed: launchURL, child: const Text('Show Flutter homepage'), ), ), ); } } \`\`\` 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 homepage for flutter.dev. ## Install AI skills from packages Many \`pub.dev\` packages bundle official \*\*package skills\*\* that teach AI coding assistants how to use their APIs and follow recommended patterns. If your project dependencies include skills, you can discover and install them into your local workspace with a single command: \`\`\`bash dart run skills@ get \`\`\` To learn more about how skills help coding assistants understand your dependencies, check out \[Package skills]\(https\://dart.dev/ai/package-skills).

----
url: https://docs.flutter.dev/ui/widgets/input
----

Take user input in addition to input widgets in Material components and Cupertino.

[Autocomplete](https://pub.dev/documentation/material_ui/latest/material_ui/Autocomplete-class.html)

[A widget for helping the user make a selection by entering some text and choosing from among a list of options.](https://pub.dev/documentation/material_ui/latest/material_ui/Autocomplete-class.html)

[Form](https://api.flutter.dev/flutter/widgets/Form-class.html)

[An optional container for grouping together multiple form field widgets (e.g. TextField widgets).](https://api.flutter.dev/flutter/widgets/Form-class.html)

[Form​Field](https://api.flutter.dev/flutter/widgets/FormField-class.html)

[A single form field. This widget maintains the current state of the form field, so that updates and validation errors are visually reflected in the...](https://api.flutter.dev/flutter/widgets/FormField-class.html)

[Keyboard​Listener](https://api.flutter.dev/flutter/widgets/KeyboardListener-class.html)

[A widget that calls a callback whenever the user presses or releases a key on a keyboard.](https://api.flutter.dev/flutter/widgets/KeyboardListener-class.html)

Find more widgets in the [widget catalog](/ui/widgets).

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-09-29. [Report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/ui/widgets/input "Report an issue with this page").

----
url: https://docs.flutter.dev/deployment/cd
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Continuous delivery with Flutter](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

Follow continuous delivery best practices with Flutter to make sure your application is delivered to your beta testers and validated on a frequent basis without resorting to manual workflows.

## CI/CD Options

[#](#cicd-options)

There are a number of continuous integration (CI) and continuous delivery (CD) options available to help automate the delivery of your application.

### All-in-one options with built-in Flutter functionality

[#](#all-in-one-options-with-built-in-flutter-functionality)

* [Codemagic](https://blog.codemagic.io/getting-started-with-codemagic/)
* [Bitrise](https://devcenter.bitrise.io/en/getting-started/quick-start-guides/getting-started-with-flutter-apps)
* [Appcircle](https://appcircle.io/blog/guide-to-automated-mobile-ci-cd-for-flutter-projects-with-appcircle/)

### Integrating fastlane with existing workflows

[#](#integrating-fastlane-with-existing-workflows)

You can use fastlane with the following tooling:

* [GitHub Actions](https://github.com/features/actions)
  * Example: [GitHub Action in Flutter Project](https://github.com/nabilnalakath/flutter-githubaction)
* [Cirrus](https://cirrus-ci.org)
* [Travis](https://travis-ci.org/)
* [GitLab](https://docs.gitlab.com/ee/ci/)
* [CircleCI](https://circleci.com)
  * [Building and deploying Flutter apps with Fastlane](https://circleci.com/blog/deploy-flutter-android)

This guide shows how to set up fastlane and then integrate it with your existing testing and continuous integration (CI) workflows. For more information, see "Integrating fastlane with existing workflow".

## fastlane

[#](#fastlane)

[fastlane](https://docs.fastlane.tools) is an open-source tool suite to automate releases and deployments for your app.

### Local setup

[#](#local-setup)

It's recommended that you test the build and deployment process locally before migrating to a cloud-based system. You could also choose to perform continuous delivery from a local machine.

1. Install fastlane `gem install fastlane` or `brew install fastlane`. Visit the [fastlane docs](https://docs.fastlane.tools) for more info.

2. Create an environment variable named `FLUTTER_ROOT`, and set it to the root directory of your Flutter SDK. (This is required for the scripts that deploy for iOS.)

3. Create your Flutter project, and when ready, make sure that your project builds via

   * `flutter build appbundle`; and
   * `flutter build ipa`.

4. Initialize the fastlane projects for each platform.

   * In your `[project]/android` directory, run `fastlane init`.
   * In your `[project]/ios` directory, run `fastlane init`.

5. Edit the `Appfile`s to ensure they have adequate metadata for your app.

   * Check that `package_name` in `[project]/android/fastlane/Appfile` matches your package name in AndroidManifest.xml.
   * Check that `app_identifier` in `[project]/ios/fastlane/Appfile` also matches Info.plist's bundle identifier. Fill in `apple_id`, `itc_team_id`, `team_id` with your respective account info.

6. Set up your local login credentials for the stores.

   * Follow the [Supply setup steps](https://docs.fastlane.tools/getting-started/android/setup/#setting-up-supply) and ensure that `fastlane supply init` successfully syncs data from your Play Store console. *Treat the .json file like your password and do not check it into any public source control repositories.*
   * Your iTunes Connect username is already in your `Appfile`'s `apple_id` field. Set the `FASTLANE_PASSWORD` shell environment variable with your iTunes Connect password. Otherwise, you'll be prompted when uploading to iTunes/TestFlight.

7. Set up code signing.

   * Follow the [Android app signing steps](/deployment/android#signing-the-app).

   * On iOS, create and sign using a distribution certificate instead of a development certificate when you're ready to test and deploy using TestFlight or App Store.

     * Create and download a distribution certificate in your [Apple Developer Account console](https://developer.apple.com/account/ios/certificate/).
     * `open [project]/ios/Runner.xcworkspace/` and select the distribution certificate in your target's settings pane.

8. Create a `Fastfile` script for each platform.

   * On Android, follow the [fastlane Android beta deployment guide](https://docs.fastlane.tools/getting-started/android/beta-deployment/). Your edit could be as simple as adding a `lane` that calls `upload_to_play_store`. Set the `aab` argument to `../build/app/outputs/bundle/release/app-release.aab` to use the app bundle `flutter build` already built.

   * On iOS, follow the [fastlane iOS beta deployment guide](https://docs.fastlane.tools/getting-started/ios/beta-deployment/). You can specify the archive path to avoid rebuilding the project. For example:

     ruby

     ```
     build_app(
       skip_build_archive: true,
       archive_path: "../build/ios/archive/Runner.xcarchive",
     )
     upload_to_testflight
     ```

You're now ready to perform deployments locally or migrate the deployment process to a continuous integration (CI) system.

### Running deployment locally

[#](#running-deployment-locally)

1. Build the release mode app.

   * `flutter build appbundle`.
   * `flutter build ipa`.

2. Run the Fastfile script on each platform.

   * `cd android` then `fastlane [name of the lane you created]`.
   * `cd ios` then `fastlane [name of the lane you created]`.

### Cloud build and deploy setup

[#](#cloud-build-and-deploy-setup)

First, follow the local setup section described in 'Local setup' to make sure the process works before migrating onto a cloud system like Travis.

The main thing to consider is that since cloud instances are ephemeral and untrusted, you won't be leaving your credentials like your Play Store service account JSON or your iTunes distribution certificate on the server.

Continuous Integration (CI) systems generally support encrypted environment variables to store private data. You can pass these environment variables using `--dart-define MY_VAR=MY_VALUE` while building the app.

**Take precaution not to re-echo those variable values back onto the console in your test scripts**. Those variables are also not available in pull requests until they're merged to ensure that malicious actors cannot create a pull request that prints these secrets out. Be careful with interactions with these secrets in pull requests that you accept and merge.

1. Make login credentials ephemeral.

   * On Android:

     * Remove the `json_key_file` field from `Appfile` and store the string content of the JSON in your CI system's encrypted variable. Read the environment variable directly in your `Fastfile`.
       ```
       upload_to_play_store(
         ...
         json_key_data: ENV['<variable name>']
       )
       ```
     * Serialize your upload key (for example, using base64) and save it as an encrypted environment variable. You can deserialize it on your CI system during the install phase with

       bash
       ```
       echo "$PLAY_STORE_UPLOAD_KEY" | base64 --decode > [path to your upload keystore]
       ```

   * On iOS:

     * Move the local environment variable `FASTLANE_PASSWORD` to use encrypted environment variables on the CI system.
     * The CI system needs access to your distribution certificate. fastlane's [Match](https://docs.fastlane.tools/actions/match/) system is recommended to synchronize your certificates across machines.

2. It's recommended to use a Gemfile instead of using an indeterministic `gem install fastlane` on the CI system each time to ensure the fastlane dependencies are stable and reproducible between local and cloud machines. However, this step is optional.

   * In both your `[project]/android` and `[project]/ios` folders, create a `Gemfile` containing the following content:
     ```
     source "https://rubygems.org"

     gem "fastlane"
     ```
   * In both directories, run `bundle update` and check both `Gemfile` and `Gemfile.lock` into source control.
   * When running locally, use `bundle exec fastlane` instead of `fastlane`.

3. Create the CI test script such as `.travis.yml` or `.cirrus.yml` in your repository root.

   * See [fastlane CI documentation](https://docs.fastlane.tools/best-practices/continuous-integration) for CI specific setup.

   * Shard your script to run on both Linux and macOS platforms.

   * During the setup phase of the CI task, do the following:

     * Ensure Bundler is available using `gem install bundler`.
     * Run `bundle install` in `[project]/android` or `[project]/ios`.
     * Make sure the Flutter SDK is available and set in `PATH`.
     * For Android, ensure the Android SDK is available and the `ANDROID_SDK_ROOT` path is set.
     * For iOS, you might have to specify a dependency on Xcode (for example, `osx_image: xcode9.2`).

   * In the script phase of the CI task:

     * Run `flutter build appbundle` or `flutter build ios --release --no-codesign --config-only`, depending on the platform.
     * `cd android` or `cd ios`
     * `bundle exec fastlane [name of the lane]`

## Xcode Cloud

[#](#xcode-cloud)

[Xcode Cloud](https://developer.apple.com/xcode-cloud) is a continuous integration and delivery service for building, testing, and distributing apps and frameworks for Apple platforms.

### Requirements

[#](#requirements)

* Xcode 13.4.1 or higher.
* Be enrolled in the [Apple Developer Program](https://developer.apple.com/programs).

### Custom build script

[#](#custom-build-script)

Xcode Cloud recognizes [custom build scripts](https://developer.apple.com/documentation/xcode/writing-custom-build-scripts) that can be used to perform additional tasks at a designated time. It also includes a set of [predefined environment variables](https://developer.apple.com/documentation/xcode/environment-variable-reference), such as `$CI_WORKSPACE`, which is the location of your cloned repository.

#### Post-clone script

[#](#post-clone-script)

Leverage the post-clone custom build script that runs after Xcode Cloud clones your Git repository using the following instructions:

Create a file at `ios/ci_scripts/ci_post_clone.sh` and add the content below.

sh

```
#!/bin/sh

# Fail this script if any subcommand fails.
set -e

# The default execution directory of this script is the ci_scripts directory.
cd $CI_PRIMARY_REPOSITORY_PATH # change working directory to the root of your cloned repo.

# Install Flutter using git.
git clone https://github.com/flutter/flutter.git --depth 1 -b stable $HOME/flutter
export PATH="$PATH:$HOME/flutter/bin"

# Install Flutter artifacts for iOS (--ios), or macOS (--macos) platforms.
flutter precache --ios

# Install Flutter dependencies.
flutter pub get

# Install CocoaPods using Homebrew.
HOMEBREW_NO_AUTO_UPDATE=1 # disable homebrew's automatic updates.
brew install cocoapods

# Install CocoaPods dependencies.
cd ios && pod install # run `pod install` in the `ios` directory.

exit 0
```

This file should be added to your git repository and marked as executable.

```
$ git add --chmod=+x ios/ci_scripts/ci_post_clone.sh
```

### Workflow configuration

[#](#workflow-configuration)

An [Xcode Cloud workflow](https://developer.apple.com/documentation/xcode/xcode-cloud-workflow-reference) defines the steps performed in the CI/CD process when your workflow is triggered.

To create a new workflow in Xcode, use the following instructions:

1. Choose **Product > Xcode Cloud > Create Workflow** to open the **Create Workflow** sheet.

2. Select the product (app) that the workflow should be attached to, then click the **Next** button.

3. The next sheet displays an overview of the default workflow provided by Xcode, and can be customized by clicking the **Edit Workflow** button.

#### Branch changes

[#](#branch-changes)

By default Xcode suggests the Branch Changes condition that starts a new build for every change to your Git repository's default branch.

For your app's iOS variant, it's reasonable that you would want Xcode Cloud to trigger your workflow after you've made changes to your flutter packages, or modified either the Dart or iOS source files within the `lib\` and `ios\` directories.

This can be achieved by using the following Files and Folders conditions:

### Next build number

[#](#next-build-number)

Xcode Cloud defaults the build number for new workflows to `1` and increments it per successful build. If you're using an existing app with a higher build number, you'll need to configure Xcode Cloud to use the correct build number for its builds by simply specifying the `Next Build Number` in your iteration.

Check out [Setting the next build number for Xcode Cloud builds](https://developer.apple.com/documentation/xcode/setting-the-next-build-number-for-xcode-cloud-builds#Set-the-next-build-number-to-a-custom-value) for more information.

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-07-31. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/deployment/cd.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/deployment/cd\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/deployment/cd.md "Report an issue with this page").

----
url: https://docs.flutter.dev/packages-and-plugins/swift-package-manager/for-app-developers
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Swift Package Manager for app developers](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

## How to turn on Swift Package Manager

[#](#how-to-turn-on-swift-package-manager)

As of the 3.44 release, Flutter's Swift Package Manager (SwiftPM) support is on by default. Upgrading Flutter and running your app automatically adds SwiftPM integration. This makes your project download the Swift packages that your Flutter plugins depend on. To use an older Flutter version, you might need to [remove Swift Package Manager integration](#how-to-remove-swift-package-manager-integration) from the app.

Note that Flutter falls back to CocoaPods for dependencies that don't yet support Swift Package Manager.

\[Optional] To check if your project is using SwiftPM:

1. In Xcode, run the app.

2. Ensure that **Run Prepare Flutter Framework Script** runs as a pre-action and that `FlutterGeneratedPluginSwiftPackage` is a target dependency.

   Ensure **Run Prepare Flutter Framework Script** runs as a pre-action

If you previously disabled SwiftPM, you might need to enable it with `flutter config --enable-swift-package-manager`.

If automatic migration works for you and all of your dependencies support Swift Package Manager, you can [remove CocoaPods integration](#how-to-remove-cocoapods-integration).

## How to remove CocoaPods integration

[#](#how-to-remove-cocoapods-integration)

**Flutter falls back to CocoaPods if any of your project dependencies do not support Swift Package Manager.** Ensure that all plugins in your project support Swift Package Manager before removing CocoaPods.

* [iOS project](#10-tab-panel)
* [macOS project](#11-tab-panel)

To remove CocoaPods from your project, use the following steps.

1. To de-integrate CocoaPods from your Xcode projects, run:

   sh

   ```
   cd ios
   pod deintegrate
   cd ..
   ```

2. Delete CocoaPods-related files and directories:

   Delete `ios/Podfile`, `ios/Podfile.lock`, the `ios/Pods/` directory, and the `ios/.symlinks/` directory (if present).

3. Check your configuration files:

   In `ios/Flutter/Debug.xcconfig` and `ios/Flutter/Release.xcconfig`, remove any `#include` lines referencing `Pods/Target Support Files` or CocoaPods `.xcconfig` files if they are still present.

4. Clean and build your project to verify the migration:

   sh

   ```
   flutter clean
   flutter pub get
   flutter run
   ```

To remove CocoaPods from your project, use the following steps.

1. To de-integrate CocoaPods from your Xcode projects, run:

   sh

   ```
   cd macos
   pod deintegrate
   cd ..
   ```

2. Delete CocoaPods-related files and directories:

   Delete `macos/Podfile`, the `macos/Pods/` directory, and the `macos/.symlinks/` directory (if present).

3. Check your configuration files:

   In `macos/Flutter/Flutter-Debug.xcconfig` and `macos/Flutter/Flutter-Release.xcconfig` remove any `#include` lines referencing `Pods/Target Support Files` or CocoaPods `.xcconfig` files if they are still present.

4. Clean and build your project to verify the migration:

   sh

   ```
   flutter clean
   flutter pub get
   flutter run
   ```

## How to add Swift Package Manager integration manually

[#](#how-to-add-swift-package-manager-integration-manually)

When you upgrade to Flutter 3.44 or later and run your app, SwiftPM integration is automatically added. You only need these instructions if problems occurred and you need to manually add SwiftPM integration to your project.

Most developers do not need to do this.

If you experience a problem automatically migrating your project to SwiftPM, please [file an issue](https://github.com/flutter/flutter/issues/new?template=2_bug.yml). Include the error message and, if possible, include a copy of the following files in your issue:

* `ios/Runner.xcodeproj/project.pbxproj`
* `ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme` (or the xcsheme for the flavor used)

- [iOS project](#12-tab-panel)
- [macOS project](#13-tab-panel)

Migrating to SwiftPM requires updating the `ios/Runner.xcodeproj/project.pbxproj` and `ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme` files.

### Step 1: Add FlutterGeneratedPluginSwiftPackage package dependency

[#](#step-1-add-fluttergeneratedpluginswiftpackage-package-dependency)

1. In Xcode, open `ios/Runner.xcworkspace`.

2. Navigate to **Package Dependencies** for the project.

   The project's package dependencies

3. Click the add button.

4. In the dialog that opens, click **Add Local...**.

5. Navigate to `ios/Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage` and click **Add Package**.

6. Ensure that it's added to the `Runner` target and click **Add Package**.

   Ensure that the package is added to the `Runner` target

7. Ensure that `FlutterGeneratedPluginSwiftPackage` was added to **Frameworks, Libraries, and Embedded Content**.

   Ensure that `FlutterGeneratedPluginSwiftPackage` was added to **Frameworks, Libraries, and Embedded Content**

### Step 2: Add Run Prepare Flutter Framework Script Pre-action

[#](#step-2-add-run-prepare-flutter-framework-script-pre-action)

**The following steps must be completed for each flavor.**

1. Go to **Product > Scheme > Edit Scheme**.

2. Expand the **Build** section in the left sidebar.

3. Click **Pre-actions**.

4. Click the add button and select **New Run Script Action** from the menu.

5. Click the **Run Script** title and change it to:

   ```
   Run Prepare Flutter Framework Script
   ```

6. Change the **Provide build settings from** to the `Runner` app.

7. Input the following in the text box:

   sh

   ```
   "$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh" prepare
   ```

   Add **Run Prepare Flutter Framework Script** build pre-action

### Step 3: Run app

[#](#step-3-run-app)

1. In Xcode, run the app.

2. Ensure that **Run Prepare Flutter Framework Script** runs as a pre-action and that `FlutterGeneratedPluginSwiftPackage` is a target dependency.

   Ensure **Run Prepare Flutter Framework Script** runs as a pre-action

3. Ensure that the app runs on the command line with `flutter run`.

Migrating to SwiftPM requires updating the `macos/Runner.xcodeproj/project.pbxproj` and `macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme` files.

### Step 1: Add FlutterGeneratedPluginSwiftPackage package dependency

[#](#step-1-add-fluttergeneratedpluginswiftpackage-package-dependency-1)

1. In Xcode, open `macos/Runner.xcworkspace`.

2. Navigate to **Package Dependencies** for the project.

   The project's package dependencies

3. Click the add button.

4. In the dialog that opens, click the **Add Local...**.

5. Navigate to `macos/Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage` and click the **Add Package**.

6. Ensure that it's added to the Runner Target and click **Add Package**.

   Ensure that the package is added to the `Runner` target

7. Ensure that `FlutterGeneratedPluginSwiftPackage` was added to **Frameworks, Libraries, and Embedded Content**.

   Ensure that `FlutterGeneratedPluginSwiftPackage` was added to **Frameworks, Libraries, and Embedded Content**

### Step 2: Add Run Prepare Flutter Framework Script Pre-action

[#](#step-2-add-run-prepare-flutter-framework-script-pre-action-1)

**The following steps must be completed for each flavor.**

1. Go to **Product > Scheme > Edit Scheme**.

2. Expand the **Build** section in the left sidebar.

3. Click **Pre-actions**.

4. Click the add button and select **New Run Script Action** from the menu.

5. Click the **Run Script** title and change it to:

   ```
   Run Prepare Flutter Framework Script
   ```

6. Change the **Provide build settings from** to the `Runner` target.

7. Input the following in the text box:

   sh

   ```
   "$FLUTTER_ROOT"/packages/flutter_tools/bin/macos_assemble.sh prepare
   ```

   Add **Run Prepare Flutter Framework Script** build pre-action

### Step 3: Run app

[#](#step-3-run-app-1)

1. In Xcode, run the app.

2. Ensure that **Run Prepare Flutter Framework Script** runs as a pre-action and that `FlutterGeneratedPluginSwiftPackage` is a target dependency.

   Ensure `Run Prepare Flutter Framework Script` runs as a pre-action

3. Ensure that the app runs on the command line with `flutter run`.

### Add to an existing app (add-to-app)

[#](#add-to-an-existing-app-add-to-app)

To use SwiftPM, consult one of the following pages, as appropriate:

* [Integrate a Flutter app into your iOS project](/add-to-app/ios/project-setup)
* [Integrate a Flutter app into your macOS project](/add-to-app/macos/project-setup)

### Add to a custom Xcode target

[#](#add-to-a-custom-xcode-target)

Your Flutter Xcode project can have custom [Xcode targets](https://developer.apple.com/documentation/xcode/configuring-a-new-target-in-your-project) to build additional products, like frameworks or unit tests. You can add Swift Package Manager integration to these custom Xcode targets.

Follow the steps in [How to add Swift Package Manager integration](#how-to-add-swift-package-manager-integration-manually).

In [Step 1](#step-1-add-fluttergeneratedpluginswiftpackage-package-dependency) for list item 6, use your custom target instead of the `Flutter` target.

In [Step 2](#step-2-add-run-prepare-flutter-framework-script-pre-action) for list item 6, use your custom target instead of the `Flutter` target.

## How to remove Swift Package Manager integration

[#](#how-to-remove-swift-package-manager-integration)

When your app is modified to support SwiftPM, the Xcode project is updated to add Flutter plugin dependencies.

To undo this migration:

1. [Turn off Swift Package Manager](#how-to-turn-off-swift-package-manager).

2. Clean your project:

   sh

   ```
   flutter clean
   ```

3. Open your app (`ios/Runner.xcworkspace` or `macos/Runner.xcworkspace`) in Xcode.

4. Navigate to **Package Dependencies** for the project.

5. Click the `FlutterGeneratedPluginSwiftPackage` package, then click the remove button.

   The `FlutterGeneratedPluginSwiftPackage` to remove

6. Navigate to **Frameworks, Libraries, and Embedded Content** for the `Runner` target.

7. Click `FlutterGeneratedPluginSwiftPackage`, then click the remove button.

   The `FlutterGeneratedPluginSwiftPackage` to remove

8. Go to **Product > Scheme > Edit Scheme**.

9. Expand the **Build** section in the left sidebar.

10. Click **Pre-actions**.

11. Expand **Run Prepare Flutter Framework Script**.

12. Click the delete button.

    The build pre-action to remove

## How to use a Swift Package Manager Flutter plugin that requires a higher OS version

[#](#how-to-use-a-swift-package-manager-flutter-plugin-that-requires-a-higher-os-version)

If a Swift Package Flutter Manager plugin requires a higher OS version than the project, you might get an error like this:

```
Target Integrity (Xcode): The package product 'plugin_name_ios' requires minimum platform version 14.0 for the iOS platform, but this target supports 12.0
```

To use the plugin:

1. Open your app (`ios/Runner.xcworkspace` or `macos/Runner.xcworkspace`) in Xcode.

2. Increase your app's target **Minimum Deployments**.

   The target's **Minimum Deployments** setting

3. If you updated your iOS app's **Minimum Deployments**, regenerate the iOS project's configuration files:

   sh

   ```
   flutter build ios --config-only
   ```

4. If you updated your macOS app's **Minimum Deployments**, regenerate the macOS project's configuration files:

   sh

   ```
   flutter build macos --config-only
   ```

## How to turn off Swift Package Manager

[#](#how-to-turn-off-swift-package-manager)

In general, don't do this. Remember that the CocoaPods registry becomes read-only on December 2, 2026 and disabling SwiftPM won't be allowed in the future.

Disabling Swift Package Manager causes Flutter to use CocoaPods for all dependencies. However, SwiftPM remains integrated with your project. To remove Swift Package Manager integration completely from your project, follow the [How to remove Swift Package Manager integration](#how-to-remove-swift-package-manager-integration) instructions.

### Turn off SwiftPM for a single project

[#](#turn-off-swiftpm-for-a-single-project)

In the project's `pubspec.yaml` file, under the `flutter` section, set `enable-swift-package-manager` to `false` in the `config` subsection.

pubspec.yaml

yaml

```
# The following section is specific to Flutter packages.
flutter:
  config:
    enable-swift-package-manager: false
```

This turns off Swift Package Manager for all contributors to this project.

### Turn off SwiftPM globally for all projects

[#](#turn-off-swiftpm-globally-for-all-projects)

Run the following command:

sh

```
flutter config --no-enable-swift-package-manager
```

This turns off Swift Package Manager for the current user.

If a project is incompatible with Swift Package Manager, all contributors need to run this command.

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-08-31. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/packages-and-plugins/swift-package-manager/for-app-developers.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/packages-and-plugins/swift-package-manager/for-app-developers\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/packages-and-plugins/swift-package-manager/for-app-developers.md "Report an issue with this page").

----
url: https://docs.flutter.dev/add-to-app/ios/project-setup.md
----

\# Integrate a Flutter app into your iOS project > Learn how to integrate a Flutter app into your existing iOS project. :::note As of the 3.44 release, Flutter uses \[Swift Package Manager]\[] to manage iOS and macOS native dependencies. Flutter continues to support CocoaPods in maintenance mode, however, the CocoaPods registry permanently becomes \[read-only on December 2, 2026]\[cocoapods]. ::: \[cocoapods]: https\://blog.cocoapods.org/CocoaPods-Specs-Repo/ \[Swift Package Manager]: https\://docs.swift.org/latest/documentation/packagemanagerdocs/ Flutter UI components can be incrementally added into your existing iOS application using Swift packages. ## Prerequisites \* Flutter 3.44 or later \* Xcode 15.0 or later ### Migrate from legacy integration (if applicable) {: #migrate-legacy-integration} If you've already integrated Flutter into your iOS app using CocoaPods or embedded frameworks, you must first remove that integration before following the Swift Package Manager instructions below.

Expand to see instructions to migrate from CocoaPods integration

If your app was previously integrated using CocoaPods, you must first remove the Flutter installation code from your Podfile. 1. Remove Flutter installation code from your Podfile \`\`\`ruby title="MyApp/Podfile" diff - flutter\_application\_path = '../my\_flutter' - load File.join(flutter\_application\_path, '.ios', 'Flutter', 'podhelper.rb') - install\_all\_flutter\_pods(flutter\_application\_path) - flutter\_post\_install(installer) if defined?(flutter\_post\_install) \`\`\` 1. Run \`pod install\`.

Expand to see instructions to migrate from embedded frameworks integration

If your app was previously integrated using frameworks generated by the \`flutter build ios-framework\` command, you must first remove the frameworks from your Xcode project. 1. Navigate to your target's General tab and remove all Flutter-related frameworks and libraries under \*\*Frameworks, Libraries, and Embedded Content\*\*. This includes the \`App.xcframework\`, \`Flutter.xcframework\`, \`FlutterPluginRegistrant.xcframework\`, and any Flutter plugins' \`xcframework\` files. 1. Remove the Flutter pod from your Podfile \`\`\`ruby title="MyApp/Podfile" diff - pod 'Flutter', :podspec => '/path/to/MyApp/Flutter/\[build mode]/Flutter.podspec' \`\`\` 1. Run \`pod install\`. The \[legacy integration guide]\[] is preserved for reference, but will not receive ongoing maintenance. ### Organize your projects relative to each other {: #organize-projects-relatively} This guide assumes that your existing iOS app and your Flutter app or module reside in sibling directories. If you have a different directory structure, you will need to adjust the example relative paths accordingly. :::note If integrating for the first time, it's recommended to use a Flutter application (instead of a module). Run the following command to create a new Flutter application: \`\`\`console flutter create my\_flutter\_app \`\`\` ::: The example directory structure resembles the following: - my\_flutter\_app/ - ios/ - lib/ - main.dart - MyNativeApp/ - MyNativeApp.xcodeproj/ - my\_flutter\_app/ - .ios/ - lib/ - main.dart - MyNativeApp/ - MyNativeApp.xcodeproj/ ## Integrate with Swift Package Manager {: #integrate-with-swiftpm} 1.

### Build the FlutterNativeIntegration Swift package

Within your Flutter application or module, run the following command: \`\`\`console flutter build swift-package --platform ios \`\`\` This will generate the following directories: - my\_flutter\_app/build/ios/SwiftPackages/ - FlutterNativeIntegration/ (A Swift package) - Scripts/ (Directory of scripts and other files needed) You can optionally change the location of this output with the \`--output\` flag. 1.

### Add FlutterNativeIntegration to your Xcode project

1\. Open your existing iOS app in Xcode. 1. In the Project navigator, right click on your project and select \*\*Add Files to "MyNativeApp"...\*\* 1. Navigate to and select the generated \`FlutterNativeIntegration\` Swift package and click \*\*Add\*\*. 1. Select \*\*Reference files in place\*\* and click \*\*Finish\*\*. 1. In the File inspector, verify the \*\*Location\*\* is \*\*Relative to Project\*\*. If it is not, you'll need to move the Flutter output directory to be a sibling directory of your native app. 1. Navigate to your target's \*\*General\*\* tab and add \`FlutterNativeIntegration\` under \*\*Frameworks, Libraries, and Embedded Content\*\*. 1.

### Add build settings

1\. In the \*\*Build Settings\*\* tab, set the location of the Flutter app's Swift package output directory: \`\`\` FLUTTER\_SWIFT\_PACKAGE\_OUTPUT=$SRCROOT/../my\_flutter\_app/build/ios/SwiftPackages \`\`\` 1. For custom configurations, set the Flutter build mode. Flutter supports three \[build modes]\[]: Debug, Profile, and Release. The build mode is determined using the \`CONFIGURATION\`. If your configuration does not match one of these, you can set the \`FLUTTER\_BUILD\_MODE\` build setting to one of these values. 1. (Optional) Allow Xcode to re-build your Flutter app. Add the below build settings to your target to allow Xcode to re-build your Flutter app as part of its build. This allows you to make changes to your Flutter application without needing to re-run \`flutter build swift-package\`. This requires Flutter to be installed on the machine. \`\`\` FLUTTER\_APPLICATION\_PATH=$SRCROOT/../my\_flutter\_app ENABLE\_USER\_SCRIPT\_SANDBOXING=NO \`\`\` :::tip This only re-builds the Flutter app's code. If you add new dependencies, you’ll need to re-run \`flutter build swift-package\`. ::: 1.

### Add Pre-action Run Script to Scheme

1\. Open \*\*Product\*\* > \*\*Scheme\*\* > \*\*Edit Scheme...\*\* > \*\*Build\*\* (in left side bar) > \*\*Pre-action\*\* > \*\*+\*\* > \*\*New Run Script Action\*\* 1. Select your project in the \*\*Provide build settings from\*\* dropdown. 1. Set the script to the following: \`\`\` /bin/sh $FLUTTER\_SWIFT\_PACKAGE\_OUTPUT/Scripts/flutter\_integration.sh prebuild \`\`\` 1.

### Add New Run Script Build Phase to Target

1\. Navigate to your target's \*\*Build Phases\*\* > \*\*+\*\* > \*\*New Run Script Phase\*\* 1. Set the script to the following: \`\`\` /bin/sh $FLUTTER\_SWIFT\_PACKAGE\_OUTPUT/Scripts/flutter\_integration.sh assemble \`\`\` 1. Uncheck \*\*Based on dependency analysis\*\* 1. Add the following to \*\*Input File Lists\*\*: \`\`\` $(FLUTTER\_SWIFT\_PACKAGE\_OUTPUT)/Scripts/FlutterAssembleInputs.xcfilelist \`\`\` 1.

### (Optional) Set LLDB Init File

Using Flutter's LLDB Init File improves performance when debugging on physical iOS 26+ devices. 1. Open \*\*Product\*\* > \*\*Scheme\*\* > \*\*Edit Scheme...\*\* > \*\*Run\*\* (in left side bar). 1. Set the \*\*LLDB Init File\*\* to the following path: \`\`\` $(FLUTTER\_SWIFT\_PACKAGE\_OUTPUT)/Scripts/flutter\_lldbinit \`\`\` Alternatively, if your scheme already has an LLDB Init File, you can add Flutter's LLDB file to it. The path to Flutter's LLDB Init File must be relative to the location of your project's LLDB Init File. \`\`\` command source --relative-to-command-file "../my\_flutter\_app/build/ios/SwiftPackages/Scripts/flutter\_lldbinit" \`\`\` {:.steps} ## Set local network privacy permissions {: #local-network-permissions} On iOS 14 and later, enable the Dart multicast DNS service in the \*\*Debug\*\* version of your iOS app. This adds \[debugging functionalities such as hot-reload and DevTools]\[] using \`flutter attach\`. :::warning Never enable this service in the \*\*Release\*\* version of your app. The Apple App Store might reject your app. ::: To set local network privacy permissions only in the Debug version of your app, create a separate \`Info.plist\` per build configuration. SwiftUI projects start without an \`Info.plist\` file. If you need to create a property list, you can do so through Xcode or text editor. The following instructions assume the default \*\*Debug\*\* and \*\*Release\*\*. Adjust the names as needed depending on your app's build configurations. 1. Create a new property list. 1. Open your project in Xcode. 1. In the \*\*Project Navigator\*\*, click on the project name. 1. From the \*\*Targets\*\* list in the Editor pane, click on your app. 1. Click the \*\*Info\*\* tab. 1. Expand \*\*Custom iOS Target Properties\*\*. 1. Right-click on the list and select \*\*Add Row\*\*. 1. From the dropdown menu, select \*\*Bonjour Services\*\*. This creates a new property list in the project directory called \`Info\`. This displays as \`Info.plist\` in the Finder. 1. Rename the \`Info.plist\` to \`Info-Debug.plist\` 1. Click on \*\*Info\*\* file in the project list at the left. 1. In the \*\*Identity and Type\*\* panel at the right, change the \*\*Name\*\* from \`Info.plist\` to \`Info-Debug.plist\`. 1. Create a Release property list. 1. In the \*\*Project Navigator\*\*, click on \`Info-Debug.plist\`. 1. Select \*\*File\*\* > \*\*Duplicate...\*\*. You can also press `Cmd` + `Shift` + `S`. 1. In the dialog box, set the \*\*Save As:\*\* field to \`Info-Release.plist\` and click \*\*Save\*\*. 1. Add the necessary properties to the \*\*Debug\*\* property list. 1. In the \*\*Project Navigator\*\*, click on \`Info-Debug.plist\`. 1. Add the String value \`\_dartVmService.\_tcp\` to the \*\*Bonjour Services\*\* array. 1. \_(Optional)\_ To set your desired customized permission dialog text, add the key \*\*Privacy - Local Network Usage Description\*\*. 1. Set the target to use different property lists for different build modes. 1. In the \*\*Project Navigator\*\*, click on your project. 1. Click the \*\*Build Settings\*\* tab. 1. Click \*\*All\*\* and \*\*Combined\*\* sub-tabs. 1. In the Search box, type \`plist\`. This limits the settings to those that include property lists. 1. Scroll through the list until you see \*\*Packaging\*\*. 1. Click on the \*\*Info.plist File\*\* setting. 1. Change the \*\*Info.plist File\*\* value from \`path/to/Info.plist\` to \`path/to/Info-$(CONFIGURATION).plist\`. This resolves to the path \*\*Info-Debug.plist\*\* in \*\*Debug\*\* and \*\*Info-Release.plist\*\* in \*\*Release\*\*. 1. Remove the \*\*Release\*\* property list from the \*\*Build Phases\*\*. 1. In the \*\*Project Navigator\*\*, click on your project. 1. Click the \*\*Build Phases\*\* tab. 1. Expand \*\*Copy Bundle Resources\*\*. 1. If this list includes \`Info-Release.plist\`, click on it and then click the \*\*-\*\* (minus sign) under it to remove the property list from the resources list. 1. The first Flutter screen your Debug app loads prompts for local network permission. Click \*\*OK\*\*. \_(Optional)\_ To grant permission before the app loads, enable \*\*Settings > Privacy > Local Network > Your App\*\*. \[debugging functionalities such as hot-reload and DevTools]: /add-to-app/debugging ## Next steps You can now \[add a Flutter screen]\[] to your existing iOS app. \[add a Flutter screen]: /add-to-app/ios/add-flutter-screen \[legacy integration guide]: /add-to-app/ios/project-setup-legacy \[build modes]: /testing/build-modes

----
url: https://docs.flutter.dev/perf/antialiasing.md
----

\# Impeller anti-aliasing > How does Impeller perform anti-aliasing? Aliasing is the visual artifacts that result from drawing geometry to a grid of pixels (rasterization). The artifacts show up as jagged or rough edges on shapes. Impeller employs a couple techniques to smooth out the mapping to raster graphics (anti-aliasing). ## Techniques ### Multisample anti-aliasing (MSAA) \[MSAA]\[] is a global anti-aliasing technique that operates on the whole contents of the screen. It is an optimization over rendering the whole screen at a larger scale and shrinking it down (\[SSAA]\[]). Instead of doing the fragment operation for each sub-sample, the calculation for the pixel is duplicated across the sub-samples where a bounds check happens. This limits smoothing to edges. Mobile phone GPUs have special hardware to optimize this process ( \[Tiled rendering]\[]). It comes in varying degrees of how many samples to consider. On desktop and mobile 4x MSAA is used for all rendering calls. ### Signed distance fields (\[SDFs]\[]) Typically, hardware accelerated computer graphics define a series of points and edges (a \[mesh]\[]) and \[shaders]\[]. Instead, SDF renders shapes in the fragment shader program as signed distance fields. Since the shape is defined in the fragment shader the edges can be smoothed at the fragment level instead of relying on the rasterization of a mesh. On desktop, rendering with SDFs is enabled by default. On mobile platforms, SDFs are an option that defaults to false. This technique is prioritized on desktop because SDF rendering puts more demand on the GPU and Flutter supports older mobile phones. Also, the physical pixel sizes on desktop computers are typically bigger than those of mobile phones. So any imperfection will be more evident there. ### Examples | No AA | MSAA 4x | MSAA 4x + SDF | | ------------------------------------------- | ---------------------------------------------- | ------------------------------------------------------- | | !\[No AA]\(/assets/images/docs/perf/noaa.png) | !\[MSAA 4x]\(/assets/images/docs/perf/msaa4.png) | !\[MSAA 4x + SDF]\(/assets/images/docs/perf/msaa4sdf.png) | ## Working with anti-aliasing ### SDFs with the FragmentShader API Standard primitive shapes in Flutter are drawn by default with SDFs. If a Flutter developer wants to define their own custom graphics with SDFs they can do so with the \[FragmentShader API]\[]. Using the \[drawPath()]\[] is sufficient for most use cases without resorting to high quality SDF rendering. Not all drawn paths are guaranteed to result in SDF rendering though. An example of rendering SDFs with the FragmentShader API can be found at \[\`simple\_sdf\`]\[]. ### Enabling SDFs on iOS SDFs can be enabled on iOS by adding a new field to the \`Info.plist\` for the project. \`\`\`xml FLTEnableSDFs \`\`\` \[MSAA]: https\://en.wikipedia.org/wiki/Multisample\_anti-aliasing \[SSAA]: https\://en.wikipedia.org/wiki/Supersampling \[Tiled rendering]: https\://en.wikipedia.org/wiki/Tiled\_rendering \[SDFs]: https\://en.wikipedia.org/wiki/Signed\_distance\_function \[mesh]: https\://en.wikipedia.org/wiki/Polygon\_mesh \[shaders]: https\://en.wikipedia.org/wiki/Shader \[FragmentShader API]: /ui/design/graphics/fragment-shaders \[drawPath()]: https\://api.flutter.dev/flutter/dart-ui/Canvas/drawPath.html \[\`simple\_sdf\`]: https\://github.com/flutter/samples/tree/main/simple\_sdf

----
url: https://docs.flutter.dev/tools/devtools/cli
----

[Skip to main content](#site-content-title)

* [asteriskGuides](/)
* [play\_lessonLearn](/learn/pathway)
* [auto\_awesomeAI](/ai/get-started)
* [apiReference](https://api.flutter.dev)
*

[vertical\_align\_top Run DevTools from the command line](#site-content-title)

Flutter 3.47 is here! Read the [blog post](https://flutter.dev/blog/whats-new-in-flutter-3-47).

To run DevTools from the CLI, you must have `dart` on your path. Then to launch DevTools, run the `dart devtools` command.

To upgrade DevTools, upgrade Flutter. If a newer Dart SDK (which is included in the Flutter SDK) has a newer version of DevTools, running `dart devtools` automatically launches this version. If `which dart` points to a Dart SDK *not* included in your Flutter SDK, updating that Dart SDK won't update the Flutter version.

When you run DevTools from the command line, you should see output that looks something like:

```
Serving DevTools at http://127.0.0.1:9100
```

## Start an application to debug

[#](#start-an-application-to-debug)

Next, start an app to connect to. This can be either a Flutter application or a Dart command-line application. The command below specifies a Flutter app:

```
cd path/to/flutter/app
flutter run
```

You need to have a device connected, or a simulator open, for `flutter run` to work. Once the app starts, you'll see a message in your terminal that looks like the following:

```
A Dart VM Service on macOS is available at:
http://127.0.0.1:51830/u37pq71Re0k=/
The Flutter DevTools debugger and profiler on macOS
is available at:
http://127.0.0.1:9100?uri=http://127.0.0.1:51830/u37pq71Re0k=/
```

Open the DevTools instance connected to your app by opening the second link in Chrome.

This URL contains a security token, so it's different for each run of your app. This means that if you stop your application and re-run it, you need to connect to DevTools again with the new URL.

## Connect to a new app instance

[#](#connect-to-a-new-app-instance)

If your app stops running or you opened DevTools manually, you should see a **Connect** dialog:

You can manually connect DevTools to a new app instance by copying the link you got from running your app, such as `http://127.0.0.1:51830/u37pq71Re0k=/`

Was this page's content helpful?

Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-05-05. [View source](https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/cli.md) or [report an issue](https://github.com/flutter/website/issues/new?template=1_page_issue.yml\&page-url=https://docs.flutter.dev/tools/devtools/cli\&page-source=https://github.com/flutter/website/blob/main/sites/docs/src/content/tools/devtools/cli.md "Report an issue with this page").

----
url: https://docs.flutter.dev/add-to-app/macos/project-setup.md
----

\# Integrate a Flutter app into your macOS project > Learn how to integrate a Flutter app into your existing macOS project. :::note As of the 3.44 release, Flutter uses \[Swift Package Manager]\[] to manage iOS and macOS native dependencies. Flutter continues to support CocoaPods in maintenance mode, however, the CocoaPods registry permanently becomes \[read-only on December 2, 2026]\[cocoapods]. ::: \[cocoapods]: https\://blog.cocoapods.org/CocoaPods-Specs-Repo/ \[Swift Package Manager]: https\://www\.swift.org/documentation/package-manager/ Flutter UI components can be incrementally added into your existing macOS application using Swift packages. ## Prerequisites \* Flutter 3.44 or later \* Xcode 15.0 or later ### Migrate from legacy integration (if applicable) {: #migrate-legacy-integration} If you've already integrated Flutter into your macOS app using embedded frameworks, you must first remove that integration before following the Swift Package Manager instructions below.

Expand to see instructions to migrate from embedded frameworks integration

If your app was previously integrated using frameworks generated by the \`flutter build macos-framework\` command, you must first remove the frameworks from your Xcode project. 1. Navigate to your target's \*\*General\*\* tab and remove all Flutter-related frameworks and libraries under \*\*Frameworks, Libraries, and Embedded Content\*\*. This includes the \`App.xcframework\`, \`FlutterMacOS.xcframework\`, \`FlutterPluginRegistrant.xcframework\`, and any Flutter plugins' \`xcframework\` files. 1. Remove the Flutter pod from your Podfile \`\`\`ruby title="MyApp/Podfile" diff - pod 'FlutterMacOS', :podspec => '/path/to/MyApp/Flutter/\[build mode]/FlutterMacOS.podspec' \`\`\` 1. Run \`pod install\`. ### Organize your projects relative to each other {: #organize-projects-relatively} This guide assumes that your existing macOS app and your Flutter app reside in sibling directories. If you have a different directory structure, you will need to adjust the example relative paths accordingly. :::note If integrating for the first time, run the following command to create a new Flutter application: \`\`\`console flutter create my\_flutter\_app \`\`\` ::: The example directory structure resembles the following: - my\_flutter\_app/ - macos/ - lib/ - main.dart - MyNativeApp/ - MyNativeApp.xcodeproj/ ## Integrate with Swift Package Manager {: #integrate-with-swiftpm} 1.

### Build the FlutterNativeIntegration Swift package

Within your Flutter application or module, run the following command: \`\`\`console flutter build swift-package --platform macos \`\`\` This generates the following directories: - my\_flutter\_app/build/macos/SwiftPackages/ - FlutterNativeIntegration/ (A Swift package) - Scripts/ (Directory of scripts and other files needed) You can optionally change the location of this output with the \`--output\` flag. 1.

### Add FlutterNativeIntegration to your Xcode project

1\. In the Project navigator, right click on your project and select \*\*Add Files to "MyNativeApp"...\*\* 1. Navigate to and select the generated \`FlutterNativeIntegration\` Swift package and click \*\*Add\*\*. 1. Select \*\*Reference files in place\*\* and click \*\*Finish\*\*. 1. In the File inspector, verify the \*\*Location\*\* is \*\*Relative to Project\*\*. If it is not, you'll need to move the Flutter output directory to be a sibling directory of your native app. 1. Navigate to your target's \*\*General\*\* tab and add \`FlutterNativeIntegration\` under \*\*Frameworks, Libraries, and Embedded Content\*\*. 1.

### Add build settings

1\. In the \*\*Build Settings\*\* tab, set the location of the Flutter app's Swift package output directory: \`\`\` FLUTTER\_SWIFT\_PACKAGE\_OUTPUT=$SRCROOT/../my\_flutter\_app/build/macos/SwiftPackages \`\`\` 1. For custom configurations, set the Flutter build mode. Flutter supports three \[build modes]\[]: Debug, Profile, and Release. The build mode is determined using the \`CONFIGURATION\` value. If your configuration does not match one of these, you can set the \`FLUTTER\_BUILD\_MODE\` build setting to one of these values. 1. For \*\*Debug\*\* configurations only, set the following build settings: \`\`\` ENABLE\_APP\_SANDBOX=YES ENABLE\_INCOMING\_NETWORK\_CONNECTIONS=YES RUNTIME\_EXCEPTION\_ALLOW\_JIT=YES \`\`\` 1. (Optional) Allow Xcode to re-build your Flutter app. Add the following build settings to your target to allow Xcode to re-build your Flutter app as part of its build. This allows you to make changes to your Flutter application without needing to re-run \`flutter build swift-package\`. This requires a Flutter installation on the machine. \`\`\` FLUTTER\_APPLICATION\_PATH=$SRCROOT/../my\_flutter\_app ENABLE\_USER\_SCRIPT\_SANDBOXING=NO \`\`\` :::tip This only re-builds the Flutter app's code. If you add new dependencies, you’ll need to re-run \`flutter build swift-package\`. ::: 1.

### Add Pre-action Run Script to Scheme

1\. Open \*\*Product\*\* > \*\*Scheme\*\* > \*\*Edit Scheme...\*\* > \*\*Build\*\* (in left side bar) > \*\*Pre-action\*\* > \*\*+\*\* > \*\*New Run Script Action\*\* 1. Select your project in the \*\*Provide build settings from\*\* dropdown. 1. Set the script to the following: \`\`\` /bin/sh $FLUTTER\_SWIFT\_PACKAGE\_OUTPUT/Scripts/flutter\_integration.sh prebuild \`\`\` 1.

### Add new run script build phase to your target

1\. Navigate to your target's \*\*Build Phases\*\* > \*\*+\*\* > \*\*New Run Script Phase\*\* 1. Set the script to the following: \`\`\` /bin/sh $FLUTTER\_SWIFT\_PACKAGE\_OUTPUT/Scripts/flutter\_integration.sh assemble \`\`\` 1. Uncheck \*\*Based on dependency analysis\*\* 1. Add the following to \*\*Input File Lists\*\*: \`\`\` $(FLUTTER\_SWIFT\_PACKAGE\_OUTPUT)/Scripts/FlutterAssembleInputs.xcfilelist \`\`\` {:.steps} ## Next steps You can now \[add a Flutter screen]\[] to your existing macOS app. \[add a Flutter screen]: /add-to-app/macos/add-flutter-screen \[build modes]: /testing/build-modes

----
url: https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.40.2.md
----

\# DevTools 2.40.2 release notes > Release notes for Dart and Flutter DevTools version 2.40.2. The 2.40.2 release of the Dart and Flutter DevTools includes the following changes among other general improvements. To learn more about DevTools, check out the \[DevTools overview]\(/tools/devtools/overview). ## General updates \* Add a setting that allows users to opt in to loading DevTools with WebAssembly. - \[#8270]\(https\://github.com/flutter/devtools/pull/8270) !\[Wasm opt-in setting]\(/assets/images/docs/tools/devtools/release-notes/images-2.40.2/wasm\_setting.png "DevTools setting to opt into wasm.") \* Removed the legacy Provider screen from DevTools. The \`package:provider\` tool is now distributed as a DevTools extension from \`package:provider\`. Upgrade your \`package:provider\` dependency to use the extension. - \[#8364]\(https\://github.com/flutter/devtools/pull/8364) \* Fixed a bug that was causing the DevTools release notes to always show. - \[#8277]\(https\://github.com/flutter/devtools/pull/8277) \* Added support for loading extensions in pub workspaces \[8347]\(https\://github.com/flutter/devtools/pull/8347). \* Mapped error stack traces to use the Dart source code locations so that they are human-readable. - \[#8385]\(https\://github.com/flutter/devtools/pull/8385) \* Added handling for IDE theme change events to update embedded DevTools UI. - \[#8336]\(https\://github.com/flutter/devtools/pull/8336) \* Fixed a bug that was causing data filters to be cleared when clearing data on the Network and Logging screens. - \[#8407]\(https\://github.com/flutter/devtools/pull/8407) \* Fixed a bug that was causing the navigator to lose state when opening the VM Flags dialog. - \[#8413]\(https\://github.com/flutter/devtools/pull/8413) \* Tables match IDE theme when embedded in an IDE. - \[#8498]\(https\://github.com/flutter/devtools/pull/8498) ## Inspector updates - Added a setting to the Flutter Inspector controls that allows users to opt in to the newly redesigned Flutter Inspector. - \[#8342]\(https\://github.com/flutter/devtools/pull/8342) !\[New inspector opt-in setting]\(/assets/images/docs/tools/devtools/release-notes/images-2.40.2/new\_inspector.png "DevTools setting to opt into the new Flutter Inspector.") ## Performance updates \* Fixed an issue with the "Refreshing timeline" overlay that was getting shown when it should not have been. - \[#8318]\(https\://github.com/flutter/devtools/pull/8318) ## Network profiler updates \* Resolved an issue in \`.har\` export where response content was sometimes missing in the data. - \[#8333]\(https\://github.com/flutter/devtools/pull/8333) ## Deep links tool updates - Added support for validating iOS deep link settings. - \[#8394]\(https\://github.com/flutter/devtools/pull/8394) !\[Deep link validator for iOS]\(/assets/images/docs/tools/devtools/release-notes/images-2.40.2/deep\_link\_ios.png "DevTools Deep link validator Page") ## Full commit history To find a complete list of changes in this release, check out the \[DevTools git log]\(https\://github.com/flutter/devtools/tree/v2.40.2).