Choose your development platform to get started
error Important
If you develop apps in China, check out using Flutter in China.

Test drive

What you’ll learn

Guide depends on your IDE
These tasks depend on which integrated development environment (IDE) you use.Option 1 explains how to code with Visual Studio Code and
its Flutter extension.Option 2 explains how to code with Android Studio or IntelliJ IDEA with
its Flutter plugin.Flutter supports IntelliJ IDEA Community, Educational, and Ultimate editions.Option 3 explains how to code with an editor of your choice and use
the terminal to compile and debug your code.
Choose your IDE
Select your preferred IDE for Flutter apps.
Run your sample Flutter app
Run your example application on your desktop platform, in the Chrome web browser, in an iOS simulator, or
Android emulator.Though you can deploy your app to the web,
note that the web target doesn’t support
hot reload at this time.Open the Command Palette.Go to View > Command Palette or
press + Shift + P.Type flutterSelect the Flutter: Select Device.If no devices are running, this command prompts you to enable a device.Select a target device from Select Device prompt.After you select a target, start the app.
Go to Run >
Start Debugging or press F5.Wait for the app to launch.You can watch the launch progress in the Debug Console view.After the app build completes, your device displays your app.
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 source code, run the hot reload command in
VS Code, and see the change in your target device.Open lib/main.dart.Change the word pushed to clicked in the following string.
It is on line 109 of the main.dart file as of this writing.error Important
Don’t stop your app. Let your app run.Save your changes: invoke Save All, or click Hot Reload
.Your app updates the string as you watch.
Create your sample Flutter app
Launch the IDE.Click New Flutter Project at the top of the
Welcome to Android Studio dialog.Under Generators, click Flutter.Verify the Flutter SDK path value against the Flutter SDK location
on your development machine.Click Next.Enter test_drive into the Project name field.
This follows best practices for naming Flutter projects.Set the directory in the Project location field to
C:\dev\test_drive on Microsoft Windows or
~/development/test_drive on other platforms.If you didn’t create that directory before, Android Studio displays
a warning that the Directory Does Not Exist. Click Create.From Project type dropdown, select Application.Change the Organization to com.example.flutter.testdrive.Android Studio asks for a company domain name.
Android uses the company domain name and project name together as its
package name when you release the app. iOS uses its Bundle ID.Use com.example.flutter.testdrive to prevent future conflicts.Ignore the remaining form fields. You don’t need to change them for
this test drive. Click Create.Wait for Android Studio to create the project.Open the lib directory, then the main.dart.To learn what each code block does, check out the comments in that Dart file.The previous commands create a Flutter project directory
called test_drive that contains a simple demo app that
uses Material Components.
Run your sample Flutter app
Locate the main Android Studio toolbar at the top of the
Android Studio edit window.In the target selector, select an Android device for running the app.
You created your Android target device in the Install section.To run your app, make one of the following choices:Click the run icon in the toolbar.Go to Run > Run.Press Ctrl + R.After the app build completes, your device displays your app.
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 source code, run the hot reload command in
Android Studio, and see the change in your target device.Open lib/main.dart.Change the word pushed to clicked in the following string.
It is on line 109 of the main.dart file as of this writing.error Important
Don’t stop your app. Let your app run.Save your changes: invoke Save All, or click Hot Reload
.Your app updates the string as you watch.
Create your sample Flutter app
To create a new Flutter app, run the following commands in your shell or Terminal.Run the flutter create command.The command creates a Flutter project directory called test_drive
that contains a simple demo app that uses Material Components.Change to the Flutter project directory.
Run your sample Flutter app
To verify that you have a running target device,
run the following command.You created your target device in the Install section.To run your app, run the following command.After the app build completes, your device displays your app.
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 source code, run the hot reload command in
VS Code, and see the change in your target device.Open lib/main.dart.Change the word pushed to clicked in the following string.
It is on line 109 of the main.dart file as of this writing.error Important
Don’t stop your app. Let your app run.Save your changes.Type r in the terminal window.Your app updates the string as you watch.

Write your first Flutter app
You are now ready to start the “First Flutter app” codelab.
In about an hour and a half,
you will learn the basics of Flutter
by creating an app
that works on mobile, desktop, and web.lightbulb Tip
The codelab above walks you through writing your first Flutter
app for all platforms — mobile, desktop and web.
You might prefer to take
another codelab written specifically for the web.If you prefer an instructor-led version of this codelab,
check out the following workshop:

Learn more
Learn more about the Flutter framework from the following pages:

Flutter basics

Apply your existing knowledge

Other resources
Reach out to us at our mailing list. We’d love to hear from you!Happy Fluttering!

Flutter for Android developers
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.info Note
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?
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, Widgets 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.
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:
import 'package:flutter/material.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:
@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:
import 'package:flutter/material.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
Animations 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:
import 'package:flutter/material.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
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.
import 'package:flutter/material.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;
}

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:
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:
@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 Intents: 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 (using a plugin).Flutter doesn’t really have a direct equivalent to activities and fragments;
rather, in Flutter you navigate between screens, using a Navigator and
Routes, 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:The following example builds a Map.
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.
Navigator.of(context).pushNamed('/b');
The other popular use-case for Intents 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: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:Finally, request the data from the Flutter side
when the widget is rendered:
import 'package:flutter/material.dart';
import 'package:flutter/services.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;
});
}
}
}

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 awaiting 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:
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:
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 Isolates
(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:
Future loadData() async {
var dataURL = Uri.parse('https://jsonplaceholder.typicode.com/posts');
http.Response response = await http.get(dataURL);
setState(() {
widgets = jsonDecode(response.body);
});
}
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:
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
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 {
var dataURL = Uri.parse('https://jsonplaceholder.typicode.com/posts');
http.Response response = await http.get(dataURL);
setState(() {
widgets = jsonDecode(response.body);
});
}
}
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:
Future loadData() async {
var dataURL = Uri.parse('https://jsonplaceholder.typicode.com/posts');
http.Response response = await http.get(dataURL);
setState(() {
widgets = jsonDecode(response.body);
});
}
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 Isolates 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.
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;
List msg = await sendReceive(
sendPort,
'https://jsonplaceholder.typicode.com/posts',
);
setState(() {
widgets = msg;
});
}
// 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 data = msg[0];
SendPort replyTo = msg[1];
String dataURL = data;
http.Response response = await http.get(Uri.parse(dataURL));
// Lots of JSON to parse
replyTo.send(jsonDecode(response.body));
}
}
Future sendReceive(SendPort port, 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:
import 'dart:async';
import 'dart:convert';
import 'dart:isolate';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
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;
List msg = await sendReceive(
sendPort,
'https://jsonplaceholder.typicode.com/posts',
);
setState(() {
widgets = msg;
});
}
// 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 data = msg[0];
SendPort replyTo = msg[1];
String dataURL = data;
http.Response response = await http.get(Uri.parse(dataURL));
// Lots of JSON to parse
replyTo.send(jsonDecode(response.body));
}
}
Future sendReceive(SendPort port, 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:To make a network call, call await on the async function http.get():
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.
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
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 {
var dataURL = Uri.parse('https://jsonplaceholder.typicode.com/posts');
http.Response response = await http.get(dataURL);
setState(() {
widgets = jsonDecode(response.body);
});
}
}

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 dps 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: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: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:Next, you’ll need to declare these images in your pubspec.yaml file:You can then access your images using AssetImage:
AssetImage('images/my_icon.jpeg')
or directly in an Image widget:
@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:
{
"@@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:
Text(AppLocalizations.of(context)!.hello('John'));
Flutter has basic support for accessibility on Android,
though this feature is a work in progress.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 Widgets.To learn more about the UI for building Activities and Fragments,
see the community-contributed Medium article,
Flutter for Android Developers: How to design Activity UI in Flutter.As mentioned in the Intents section,
screens in Flutter are represented by Widgets since everything is
a widget in Flutter. Use a Navigator to move between different
Routes 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: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:
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.
@override
Widget build(BuildContext context) {
return const Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('Row One'),
Text('Row Two'),
Text('Row Three'),
Text('Row Four'),
],
);
}
@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.
@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:

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:
@override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: () {
developer.log('click');
},
child: const Text('Button'),
);
}
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:TapDouble tapLong pressVertical dragHorizontal dragThe following example shows a GestureDetector
that rotates the Flutter logo on a double tap:
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,
),
),
),
),
);
}
}

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.
import 'package:flutter/material.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.
import 'dart:developer' as developer;
import 'package:flutter/material.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
ListViews 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.
import 'dart:developer' as developer;
import 'package:flutter/material.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:
import 'dart:developer' as developer;
import 'package:flutter/material.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 .adds 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.Then assign the font to your Text widget:
@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:

Form input
For more information on using Forms,
see Retrieve the value of a text field,
from the Flutter cookbook.

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.
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.
import 'package:flutter/material.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_login 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 team: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.
import 'package:flutter/material.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(),
);
}
}

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).
import 'dart:async';
import 'package:flutter/material.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, see 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 SwiftUI Developers
SwiftUI developers who want to write mobile apps using Flutter
should review this guide.
It explains how to apply existing SwiftUI knowledge to Flutter.info 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.info
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.
You can test full working examples on DartPad or view them on GitHub.

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.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.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:Flutter differs somewhat with its process: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.If the child expands and it has unbounded constraints,
Flutter returns an overflow warning: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: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.Another common SwiftUI practice places the app body within a struct
that conforms to the View protocol as follows:To start your Flutter app, pass in an instance of your app to
the runApp function.
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.
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.
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.To achieve the same result in Flutter,
use the CupertinoButton class:
CupertinoButton(
onPressed: () {
// This closure is called when your button is tapped.
},
child: 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:HStack for horizontal stack viewsVStack for vertical stack viewsThe following SwiftUI view adds a globe image and
text to a horizontal stack view:Flutter uses Row rather than HStack:
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.Flutter uses the same Dart code from the previous example,
except it swaps Column for Row:
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.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.
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 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.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:
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.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.
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: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: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.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: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:
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 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.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.
AnimatedRotation(
duration: const Duration(seconds: 1),
turns: turns,
curve: Curves.easeIn,
child: TextButton(
onPressed: () {
setState(() {
turns += .125;
});
},
child: 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:CustomPaint that requires a painter:
CustomPaint(
painter: SignaturePainter(_points),
size: Size.infinite,
),
CustomPainter that implements your algorithm to draw to the canvas.
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.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.Name each route in the class passed to the runApp() function.
The following example uses App:
// 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().
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,
);
},
);
},
),
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():
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.In Flutter, use the pop() function of the Navigator class:
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.In Flutter, use the url_launcher plugin.
CupertinoButton(
onPressed: () async {
await launchUrl(
Uri.parse('https://google.com'),
);
},
child: 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:
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:To style text in Flutter, add a TextStyle widget as the value
of the style parameter of the Text widget.
Text(
'Hello, world!',
style: TextStyle(
fontSize: 30,
fontWeight: FontWeight.bold,
color: CupertinoColors.systemYellow,
),
),

Styling buttons
In SwiftUI, you use modifier functions to style buttons.To style button widgets in Flutter, set the style of its child,
or modify properties on the button itself.In the following example:
child: CupertinoButton(
color: CupertinoColors.systemYellow,
onPressed: () {},
padding: const EdgeInsets.all(16),
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.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:Add your custom font(s) under the fonts section.After you add the font to your project, you can use it as in the
following example:
Text(
'Cupertino',
style: TextStyle(
fontSize: 40,
fontFamily: 'BungeeSpice',
),
)
info Note
To download custom fonts to use in your apps,
check out Google Fonts.

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.Add this asset to the pubspec.yaml file.After adding your image, display it using the Image widget’s
.asset() constructor. This constructor: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.To review a complete walkthrough, check out the video_player example.

Flutter for UIKit developers
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.info 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, see 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.info
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
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
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 StatefulWidgets 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 commonStatelessWidget is the Text widget.
If you look at the implementation of the Text widget,
you’ll find it subclasses StatelessWidget.
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:

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:
@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:
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
Animations 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:
import 'package:flutter/material.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.
import 'package:flutter/material.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:
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:
@override
Widget build(BuildContext context) {
return const Center(
child: CustomButton('Hello'),
);
}

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:The following example builds a Map.
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 pushing its name to the Navigator.
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 awaiting 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:
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:
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: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:
class Strings {
static const String welcomeMessage = 'Welcome To Flutter';
}
You can access your strings as such:
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.To use the flutter_localizations package,
specify the localizationsDelegates and
supportedLocales on the app widget:
import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.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.

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.

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 Routes
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: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.
import 'package:flutter/material.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.
import 'dart:developer' as developer;
import 'package:flutter/material.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 ListViews 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.
import 'dart:developer' as developer;
import 'package:flutter/material.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.
import 'dart:developer' as developer;
import 'package:flutter/material.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 .adds 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.
@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: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:
@override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: () {
developer.log('click');
},
child: const Text('Button'),
);
}
If the Widget doesn’t support event detection,
wrap the widget in a GestureDetector and pass a function
to the onTap parameter.
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:TappingDouble tappingLong pressingVertical draggingHorizontal draggingThe following example shows a GestureDetector
that rotates the Flutter logo on a double tap:
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.
import 'package:flutter/material.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.Then assign the font to your Text widget:
@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:

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:Declare the asset in the pubspec.yaml file:And then access it from code using an AssetBundle:
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:Next, declare these images in the pubspec.yaml file:You can now access your images using AssetImage:
AssetImage('images/a_dot_burr.jpeg')
or directly in an Image widget:
@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:
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,
from the Flutter cookbook.

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:
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.
import 'package:flutter/material.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 Isolates
(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:
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);
});
}
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:
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
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);
});
}
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:
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);
});
}
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 Isolates 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.
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:
import 'dart:async';
import 'dart:convert';
import 'dart:isolate';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
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:To make a network call,
call await on the async function http.get():
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);
});
}

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.
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
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);
});
}
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 React Native developers
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: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.In Dart, every app must have a top-level main() function that serves as the
entry point to the app.
/// Dart
void main() {}
Try it out in DartPad.

Printing to the console
To print to the console in Dart, use print().
/// Dart
print('Hello world!');
Try it out in DartPad.

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.
/// Dart
/// Both variables are acceptable.
String name = 'dart'; // Explicitly typed as a [String].
var otherName = 'Dart'; // Inferred [String] type.
Try it out in DartPad.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.info 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.
// Dart
var name; // == null; raises a linter warning
int? x; // == null
Try it out in DartPad.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.In Dart, only the boolean value true is treated as true.
/// Dart
var myNull;
var zero = 0;
if (zero == 0) {
print('use "== 0" to check zero');
}
Try it out in DartPad.

Functions
Dart and JavaScript functions are generally similar.
The primary difference is the declaration.
/// Dart
/// You can explicitly define the return type.
bool fn() {
return true;
}
Try it out in DartPad.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.
// 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))
.catchError((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.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
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.To create an app in Flutter, do one of the following: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: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.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.
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.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.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.
// Flutter
import 'package:flutter/material.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.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: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.
// Flutter
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(
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.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 StatefulWidgets 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 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 class is defined
and then used inside a parent class.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.
/// 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: [
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.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.

Project structure and resources

Where do I start writing the code?
Start with the lib/main.dart file.
It’s autogenerated when you create a Flutter app.
// 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?
When you create a new Flutter project,
it builds the following directory structure.
You can customize it later, but this is where you start.

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:Flutter uses the pubspec.yaml file,
located at the root of your project, to
identify assets required by an app.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.In Flutter, add a static image to your app
using the Image.asset constructor in a widget’s build method.
Image.asset('assets/background.png');
For more information, see Adding Assets and Images in Flutter.

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.
Image.network('https://docs.flutter.dev/assets/images/docs/owl.jpg');

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:
import 'package:flutter/material.dart';
For more information, see Using Packages and
Developing Packages & Plugins.You can find many packages shared by Flutter developers in the
Flutter packages section of pub.dev.

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.For more information about the core widgets from the Widgets package,
see Flutter Basic Widgets,
the Flutter Widget Catalog,
or the Flutter Widget Index.

Views

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, Column,
Row, and Center.
For more information, see the Layout Widgets catalog.

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.ListView is Flutter’s most commonly used scrolling widget.
The default constructor takes an explicit list of children.
ListView 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.
var data = [
'Hello',
'World',
];
return ListView.builder(
itemCount: data.length,
itemBuilder: (context, index) {
return Text(data[index]);
},
);
To learn how to implement an infinite scrolling list, see the official
infinite_list sample.

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.In Flutter, you can use the CustomPaint
and CustomPainter 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.
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()),
);
}
}

Layouts

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'.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 and Row widgets
take an array of children and align them
vertically and horizontally respectively.
A Container widget takes a combination of
layout and styling properties, and a
Center widget centers its child widgets.
@override
Widget build(BuildContext context) {
return Center(
child: Column(
children: [
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, Align, and Stack.For a complete list, see Layout Widgets.

How do I layer widgets?
In React Native, components can be layered using absolute positioning.Flutter uses the Stack
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.
@override
Widget build(BuildContext context) {
return Stack(
alignment: const Alignment(0.6, 0.6),
children: [
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.For more information, see the Stack class documentation.

Styling

How do I style my components?
In React Native, inline styling and stylesheets.create
are used to style components.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 class and use it for multiple Text widgets.
const TextStyle textStyle = TextStyle(
color: Colors.cyan,
fontSize: 32,
fontWeight: FontWeight.w600,
);
return const Center(
child: Column(
children: [
Text('Sample text', style: textStyle),
Padding(
padding: EdgeInsets.all(20),
child: Icon(
Icons.lightbulb_outline,
size: 48,
color: Colors.redAccent,
),
),
],
),
);

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 and colors.
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.Flutter’s Cupertino (iOS-style) 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.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 class provides colors
from the Material Design color palette.The following example sets the color scheme from seed to deepPurple
and the text selection to red.
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?
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
class and passing it to the theme property in the
MaterialApp widget.
@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 widget takes a ThemeData in its data parameter
and applies the ThemeData to all of its children widgets.
@override
Widget build(BuildContext context) {
return Theme(
data: ThemeData(
primaryColor: Colors.cyan,
brightness: brightness,
),
child: Scaffold(
backgroundColor: Theme.of(context).primaryColor,
//...
),
);
}

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 paired with a State object.For more information on ways to approach managing state in Flutter,
see State management.

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 in which the widget is inflated.AboutDialog, CircleAvatar, and Text are examples
of stateless widgets that subclass StatelessWidget.
import 'package:flutter/material.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—it contains immutable data.The build method of a stateless widget is typically called
in only three situations:

The StatefulWidget
A StatefulWidget 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, Radio, Slider, InkWell,
Form, and TextField
are examples of stateful widgets that subclass
StatefulWidget.The following example declares a StatefulWidget
that requires a createState() method.
This method creates the state object that manages the widget’s state,
_MyStatefulWidgetState.
class MyStatefulWidget extends StatefulWidget {
const MyStatefulWidget({
super.key,
required this.title,
});
final String title;
@override
State 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.
class _MyStatefulWidgetState extends State {
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: [
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?
Here are a few things to consider when designing your widget.In Flutter, widgets are either Stateful or Stateless—depending on whether
they depend on a state change.In Flutter, there are three primary ways to manage state:When deciding which approach to use, consider the following principles: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.
class MyStatefulWidget extends StatefulWidget {
const MyStatefulWidget({
super.key,
required this.title,
});
final String title;
@override
State createState() => _MyStatefulWidgetState();
}
class _MyStatefulWidgetState extends State {
@override
Widget build(BuildContext context) {
//...
}
}
Add your custom StatefulWidget to the widget tree
in the app’s build method.
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'),
);
}
}

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.In Flutter, you assign a local variable or function marked
final with the property received in the parameterized constructor.
/// 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: [
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');
},
);
}
}

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?
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.In Flutter, use the 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:
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.
Future 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
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?
In React Native, there are three main navigators:
StackNavigator, TabNavigator, and DrawerNavigator.
Each provides a way to configure and define the screens.In Flutter, there are two main widgets used to navigate between screens: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 and Navigator.pop.
A list of routes might be specified in the MaterialApp widget,
or they might be built on the fly, for example, in hero animations.
The following example specifies named routes in the MaterialApp widget.info Note
Named routes are no longer recommended for most
applications. For more information, see
Limitations in the navigation overview page.
class NavigationApp extends StatelessWidget {
// This widget is the root of your application.
const NavigationApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
//...
routes: {
'/a': (context) => const UsualNavScreen(),
'/b': (context) => const DrawerNavScreen(),
},
//...
);
}
}
To navigate to a named route, the Navigator.of()
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.
Navigator.of(context).pushNamed('/a');
You can also use the push method of Navigator which
adds the given Route to the history of the
navigator that most tightly encloses the given BuildContext,
and transitions to it. In the following example,
the MaterialPageRoute widget is a modal route that
replaces the entire screen with a platform-adaptive
transition. It takes a WidgetBuilder as a required parameter.
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const UsualNavScreen(),
),
);

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
In React Native, createBottomTabNavigator
and TabNavigation are used to
show tabs and for tab navigation.Flutter provides several specialized widgets for drawer and
tab navigation:
class _MyAppState extends State with SingleTickerProviderStateMixin {
late TabController controller = TabController(length: 2, vsync: this);
@override
Widget build(BuildContext context) {
return TabBar(
controller: controller,
tabs: const [
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 is an interface implemented
by classes that can vend Ticker 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. AnimationControllers
need a TickerProvider to obtain their Ticker.
If you are creating an AnimationController from a State,
then you can use the TickerProviderStateMixin
or SingleTickerProviderStateMixin
classes to obtain a suitable TickerProvider.The Scaffold 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.
class _NavigationHomePageState extends State
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(
icon: Icon(Icons.person),
),
Tab(
icon: Icon(Icons.email),
),
],
controller: controller,
),
),
body: TabBarView(
controller: controller,
children: const [HomeScreen(), TabScreen()],
));
}
}

Drawer navigation
In React Native, import the needed react-navigation packages and then use
createDrawerNavigator and DrawerNavigation.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 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, a Text widget,
or a list of items to display as the child to the Drawer widget.
In the following example, the ListTile
widget provides the navigation on tap.
@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.
@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(),
);
}

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,
which describe semantic actions
that consist of one or more pointer movements.

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.For more complex gestures and combining several touches into
a single gesture, PanResponder is used.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.
@override
Widget build(BuildContext context) {
return GestureDetector(
child: Scaffold(
appBar: AppBar(title: const Text('Gestures')),
body: const Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
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.

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?
React Native provides the Fetch API for networking—you make a fetch request
and then receive the response to get the data.Flutter uses the http package.To add the http package as a dependency, run flutter pub add:Flutter uses the dart:io core HTTP support client.
To create an HTTP Client, import dart:io.
import 'dart:io';
The client supports the following HTTP operations:
GET, POST, PUT, and DELETE.
final url = Uri.parse('https://httpbin.org/ip');
final httpClient = HttpClient();
Future 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;
});
}

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 and TextFormField.

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.In Flutter, use the TextEditingController
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.
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(
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
widget that displays the alert message, and the text from
the TextField is accessed by the text property of the
TextEditingController.

How do I use Form widgets?
In Flutter, use the Form widget where
TextFormField widgets along with the submit
button are passed as children.
The TextFormField widget has a parameter called
onSaved 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().
@override
Widget build(BuildContext context) {
return Form(
key: formKey,
child: Column(
children: [
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.
void _submit() {
final form = formKey.currentState;
if (form != null && form.validate()) {
form.save();
showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: const Text('Alert'),
content: Text('Email: $_email, password: $_password'));
},
);
}
}

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:In Flutter, use the following implementation:
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

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, see the
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?
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.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?
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:

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
class and the AnimationController 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?
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.To create the same animation in Flutter, create an
AnimationController 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 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
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
widget is used inside the FadeTransition widget.
import 'package:flutter/material.dart';
void main() {
runApp(const Center(child: LogoFade()));
}
class LogoFade extends StatefulWidget {
const LogoFade({super.key});
@override
State createState() => _LogoFadeState();
}
class _LogoFadeState extends State
with SingleTickerProviderStateMixin {
late Animation 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(),
),
);
}
}

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 widget and nest the child widgets.
return Dismissible(
key: Key(widget.key.toString()),
onDismissed: (dismissDirection) {
cards.removeLast();
},
child: Container(
//...
),
);

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.

Flutter for web developers
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.In Flutter, the default styling of the ‘Lorem ipsum’ text
is defined by the bold24Roboto variable as follows,
to keep the syntax simple:
TextStyle bold24Roboto = const TextStyle(
color: Colors.white,
fontSize: 24,
fontWeight: FontWeight.bold,
);
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.

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.

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.

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.

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.

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).

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.

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:
Vertical gradient

Horizontal gradient

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.

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: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.

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.

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.

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.

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.

Flutter for Xamarin.Forms developers
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.In Flutter, the default main entry point is
main where you load your Flutter app.
void main() {
runApp(const MyApp());
}
In Xamarin.Forms, you assign a Page to the
MainPage property in the Application class.In Flutter, “everything is a widget”, even the application itself.
The following example shows MyApp, a simple application Widget.
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.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.
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.
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.
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?
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 Elements 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, Widgets 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.
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:
import 'package:flutter/material.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:
@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:
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.Then in code behind, or a behavior, this would fade in the image,
over a 1-second period.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 moreAnimations 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:
import 'package:flutter/material.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.
import 'package:flutter/material.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 VisualElements 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 VisualElements 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:
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:
@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:The following example builds a Map.
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.
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:
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:
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 asurl_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 Isolates (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:
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);
});
}
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:
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
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);
});
}
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:
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);
});
}
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 Isolates 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.
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:
import 'dart:async';
import 'dart:convert';
import 'dart:isolate';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
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:To make a network request,
call await on the async function http.get():
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);
});
}

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.
import 'dart:async';
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
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);
});
}
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 dps 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: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:Next, you’ll need to declare these images in your pubspec.yaml file:You can directly access your images in an Image.asset widget:
@override
Widget build(BuildContext context) {
return Image.asset('images/my_icon.png');
}
or using AssetImage:
@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:
class Strings {
static const String welcomeMessage = 'Welcome To Flutter';
}
You can access your strings as such:
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.To use the flutter_localizations package,
specify the localizationsDelegates and
supportedLocales on the app widget:
import 'package:flutter_localizations/flutter_localizations.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:For more details on the meaning of these states,
see the AppLifecycleStatus documentation.

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.
@override
Widget build(BuildContext context) {
return const Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('Row One'),
Text('Row Two'),
Text('Row Three'),
Text('Row Four'),
],
);
}
@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.
@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.
@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.
@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.
@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:

Gesture detection and touch event handling

How do I add GestureRecognizers to a widget in Flutter?
In Xamarin.Forms, Elements 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: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:
@override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: () {
developer.log('click');
},
child: const Text('Button'),
);
}
If the widget doesn’t support event detection, wrap the
widget in a GestureDetector and pass a function
to the onTap parameter.
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:The following example shows a GestureDetector
that rotates the Flutter logo on a double tap:
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 DataTemplateSelectorand 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.
import 'package:flutter/material.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.
import 'dart:developer' as developer;
import 'package:flutter/material.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 ListViews 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.
import 'dart:developer' as developer;
import 'package:flutter/material.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:
import 'dart:developer' as developer;
import 'package:flutter/material.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 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.Then assign the font to your Text widget:
@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:

Form input

How do I retrieve user input?
Xamarin.Forms elements 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 TextFieldor a TextFormField,
you can supply a TextEditingController
to retrieve user input:
import 'package:flutter/material.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,
from the Flutter cookbook.

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: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.
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
VisualElements surrounding the Elements 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.
import 'package:flutter/material.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_login 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 team: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.
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, see 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.

Introduction to declarative UI
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: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 Widgets)
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.
// 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.
RenderObjects persist between frames and Flutter’s lightweight Widgets
tell the framework to mutate the RenderObjects between states.
The Flutter framework handles the rest.

Flutter concurrency for Swift developers
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.

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:Next, define the view model and mark it as an ObservableObject
so that it can return a value of type Weather?.
Use GCD create to a DispatchQueue to
send the work to the pool of threadsFinally, display the results: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.Next, define the view model as a state object using @StateObject,
with a load() function that can be called by the view model:In Dart, all work runs on the main isolate by default.
To implement the same example in Dart,
first, create the Weather enum:
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 ObservableObject.
In this example, a function within the view model
returns a Future object:
@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 and
StreamBuilder
widgets are used to display the results of a Future in the UI.
The following example uses a FutureBuilder:
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.

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.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.
As of Dart 2.19, you can use Isolate.run() to
spawn an isolate and run computations:In Flutter, you can also use the compute function
to spin up an isolate to run a callback function:In this case, the callback function is a top-level
function as shown below: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.

Upgrading Flutter
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.

Upgrading the Flutter SDK
To update the Flutter SDK use the flutter upgrade command: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.

Keeping informed
We publish migration guides for known breaking changes.We send announcements regarding these changes to the
Flutter announcements mailing list.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: https://api.flutter.dev

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.
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, 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 master branch
is at: https://main-api.flutter.dev

Changing channels
To view your current channel, use the following command: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:info Note
If you need a specific version of the Flutter SDK,
you can download it from the Flutter SDK archive.

Upgrading 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:To update to the latest possible version of
all the dependencies listed in the pubspec.yaml file,
use the upgrade --major-versions command: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.

Flutter SDK archive
The Stable channel contains the
most stable Flutter builds.
To learn more, check out Flutter’s channels.error Important
If you develop apps in China, check out using Flutter in China.To learn what’s new in the major Flutter releases,
check out the release notes page.A note on provenance: provenance
describes how software artifacts are built, including
what the download contains and who created it.
To view provenance in a more readable format
and where nothing is downloaded, run the following
command using the provenance file URL from a release (you might need to
download jq to easily parse the JSON).
Stable channel (Windows)
Select from the following scrollable list:
Beta channel (Windows)
Select from the following scrollable list:
Stable channel (macOS)
Select from the following scrollable list:
Beta channel (macOS)
Select from the following scrollable list:
Stable channel (Linux)
Select from the following scrollable list:
Beta channel (Linux)
Select from the following scrollable list:
Master channel
Installation bundles are not available for master.
However, you can get the SDK directly from
GitHub repo by cloning the master channel,
and then triggering a download of the SDK dependencies:For additional details on how our installation bundles are structured,
see Installation bundles.

What's new
This page contains current and recent announcements
of what’s new on the Flutter website and blog.
Find past what’s new information on the
what’s new archive page.
You might also check out the
Flutter SDK release notes.To stay on top of Flutter announcements including
breaking changes,
join the flutter-announce Google group.For Dart, you can join the Dart Announce Google group,
and review the Dart changelog.

15 February 2024: Valentine’s-Day-adjacent 3.19 release
Flutter 3.19 is live! For more information,
check out the Flutter 3.19 umbrella blog post
and the Flutter 3.19 technical blog post.You might also check out the Dart 3.3 release blog post.Docs updated or added since the 3.16 releaseOther updatesFor past releases, check out the
What’s new archive page.

Flutter release notes
This page links to announcements and release notes for
releases to the stable channel.info Note
For information about bug-fix releases, check out
Hotfixes to the Stable Channel on the Flutter wiki.

Breaking changes and migration guides
As described in the breaking change policy,
on occasion we publish guides
for migrating code across a breaking change.To be notified about future breaking changes,
join the groups Flutter announce and Dart announce.When facing Dart errors after upgrading Flutter,
consider using the dart fix command
to automatically migrate your code.
Not every breaking change is supported in this way,
but many are.To avoid being broken by future versions of Flutter,
consider submitting your tests to our test registry.The following guides are available. They are sorted by
release, and listed in alphabetical order:

Not yet released to stable

Released in Flutter 3.19

Released in Flutter 3.16

Released in Flutter 3.13

Released in Flutter 3.10

Released in Flutter 3.7

Released in Flutter 3.3

Released in Flutter 3

Released in Flutter 2.10

Released in Flutter 2.5

Reverted change in 2.2
The following breaking change was reverted in release 2.2:

Released in Flutter 2.2

Released in Flutter 2

Released in Flutter 1.22

Released in Flutter 1.20

Released in Flutter 1.17

Flutter compatibility policy
The Flutter team tries to balance the need for API stability with the
need to keep evolving APIs to fix bugs, improve API ergonomics,
and provide new features in a coherent manner.To this end, we have created a test registry where you can provide
unit tests for your own applications or libraries that we run
on every change to help us track changes that would break
existing applications. Our commitment is that we won’t make any
changes that break these tests without working with the developers of
those tests to (a) determine if the change is sufficiently valuable,
and (b) provide fixes for the code so that the tests continue to pass.If you would like to provide tests as part of this program, please
submit a PR to the flutter/tests repository.
The README on that repository describes
the process in detail.

Announcements and migration guides
If we do make a breaking change (defined as a change that caused one
or more of these submitted tests to require changes), we will announce
the change on our flutter-announce
mailing list as well as in our release notes.We provide a list of guides for migrating code affected by
breaking changes.

Deprecation policy
We will, on occasion, deprecate certain APIs rather than outright
break them overnight. This is independent of our compatibility policy
which is exclusively based on whether submitted tests fail, as
described above.Deprecated APIs are removed after a migration grace period. This grace
period is one calendar year after being released on the stable channel,
or after 4 stable releases, whichever is longer.When a deprecation does reach end of life, we follow the same procedures
listed above for making breaking changes in removing the deprecated API.

Dart and other libraries used by Flutter
The Dart language itself has a separate breaking-change policy,
with announcements on Dart announce.In general, the Flutter team doesn’t currently have any commitment
regarding breaking changes for other dependencies.
For example, it’s possible that a new version of
Flutter using a new version of Skia
(the graphics engine used by some platforms on Flutter)
or Harfbuzz (the font shaping engine used by Flutter)
would have changes that affect contributed tests.
Such changes wouldn’t necessarily be accompanied by a
migration guide.

Codelabs
The Flutter codelabs provide a guided,
hands-on coding experience. Some codelabs
run in DartPad—no downloads required!

Good for beginners
If you’re new to Flutter, we recommend starting with
one of the following codelabs:Building your first Flutter app (workshop)
An instructor-led version of our very popular
“Write your first Flutter app” codelab
(listed below).Your first Flutter app
Create a simple app that automatically generates cool-sounding names,
such as “newstay”, “lightstream”, “mainbrake”, or “graypine”.
This app is responsive and runs on mobile, desktop, and web.
(This also replaces the previous “write your first Flutter app”
for mobile, part 1 and part 2 codelabs.)Write your first Flutter app on the web
Implement a simple web app in DartPad (no downloads
required!) that displays a sign-in screen
containing three text fields. As the user fills out the
fields, a progress bar animates along the top of the
sign-in area. This codelab is written specifically for
the web, but if you have downloaded and configured
Android and iOS tooling, the completed app
works on Android and iOS devices, as well.

Next steps
Records and Patterns in Dart 3
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.Building scrolling experiences in Flutter (workshop)
Start with an app that performs simple, straightforward scrolling
and enhance it to create fancy and custom scrolling effects
by using slivers.Dart null safety in Action (workshop)
An instructor-led workshop introducing the features
that enable Dart’s null-safe type system.How to manage application states using inherited widgets (workshop)
Learn how to manage the state of your app’s data by
using the InheritedWidget class, one of the
low-level state management classes provided
by Flutter.

Designing a Flutter UI
Learn about Material Design and basic Flutter concepts,
like layout and animations:How to debug layout issues with the Flutter Inspector
Not an official codelab, but step-by-step instructions on
how to debug common layout problems using the Flutter
Inspector and Layout Explorer.Implicit animations
Use DartPad (no downloads required!) to learn how to use
implicit animations to add motion and create
visual effects for the widgets in your UI.Building Beautiful Transitions with Material Motion for Flutter
Learn how to use the Material animations package to
add pre-built transitions to a Material app called Reply.Take your Flutter app from boring to beautiful
Learn how to use some of the features in Material 3
to make your app more beautiful and more responsive.MDC-101 Flutter: Material Components (MDC) Basics
Learn the basics of using Material Components by building
a simple app with core components. The four MDC codelabs
guide you through building an e-commerce app called Shrine.
You’ll start by building a login page using several of MDC
Flutter’s components.MDC-102 Flutter: Material Structure and Layout
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.MDC-103 Flutter: Material Theming with Color, Shape, Elevation, and Type
Discover how Material Components for Flutter make it
easy to differentiate your product, and express your
brand through design. Continue building your e-commerce
app by adding a home screen that displays products.MDC-104 Flutter: Material Advanced Components
Improve your design and learn to use our advanced
component backdrop menu. Finish your e-commerce app
by adding a backdrop with a menu that filters
products by the selected category.Adaptive Apps in Flutter
Learn how to build a Flutter app that adapts to the
platform that it’s running on, be that Android, iOS,
the web, Windows, macOS, or Linux.Building next generation UIs in Flutter
Learn how to build a Flutter app that uses the power of flutter_animate,
fragment shaders, and particle fields. You will craft a user interface that
evokes those science fiction movies and TV shows we all love
watching when we aren’t coding.

Using Flutter with
Learn how to use Flutter with other technologies.

Monetizing Flutter
Adding AdMob Ads to a Flutter app
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.Adding an AdMob banner and native inline ads to a Flutter app
Learn how to implement inline banner and native ads
to a travel booking app that lists possible
flight destinations.Adding in-app purchases to your Flutter app
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.

Flutter and Firebase
Add a user authentication flow to a Flutter app using FirebaseUI
Learn how to add Firebase authentication to a Flutter app
with only a few lines of code.Get to know Firebase for Flutter (workshop)
An instructor-led version of our popular
“Get to know Firebase for Flutter” codelab
(listed below).Get to know Firebase for 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.Local development for your Flutter apps using the Firebase Emulator Suite
Learn how to use the Firebase Emulator Suite when
developing with Flutter. You will also learn to use
the Auth and Firestore emulators.

Flutter and TensorFlow
Create a custom text-classification model with TensorFlow Lite Model MakerCreate a Flutter app to classify texts with TensorFlow
Learn how to run a text-classification inference from a Flutter
app with TensorFlow Serving through REST and gRPC.Train a comment-spam detection model with TensorFlow Lite Model Maker
Learn how to install the TensorFlow Lite Model Maker with Colab,
how to use a data loader, and how to build a model.

Flutter and other technologies
Adding Google Maps to a Flutter app
Display a Google map in an app, retrieve data from a
web service, and display the data as markers on the map.Adding WebView to your Flutter app
With the WebView Flutter plugin you can add a WebView
widget to your Android or iOS Flutter app.Build voice bots for mobile with Dialogflow and Flutter (workshop)
An instructor-led version of the Dialogflow
and Flutter codelab (listed below).Build voice bots for Android with Dialogflow and Flutter
Learn how to build a mobile FAQ bot that can answer most
common questions about the tool Dialogflow. End users
can interact with the text interface or stream a voice
interaction via the built-in microphone of a mobile device.Introduction to Flame with Flutter
Build a Breakout clone using the Flame 2D game engine and
embed it in a Flutter wrapper. You will use Flame’s Effects
to animate and remove components, along with the google_fonts and
flutter_animate packages, to make the whole game look well designed.Using FFI in a Flutter plugin
Learn how to use Dart’s FFI (foreign function interface)
library, ffigen, allowing you to leverage
existing native libraries that provide a
C interface.Create haikus about Google products with the PaLM API and Flutter
Learn how to build an app that uses the PaLM API to
generate haikus based on Google product names. The
PaLM API gives you access to Google’s
state-of-the-art large language models.

Testing
Learn how to test your Flutter application.

Writing platform-specific code
Learn how to write code that’s targeted for specific platforms,
like iOS, Android, desktop, or the web.How to write a Flutter plugin
Learn how to write a plugin by creating a music plugin
for iOS and Android that processes audio on the host platform.
Then make an example app that uses your plugin to make a music keyboard.Using a plugin with a Flutter web app
Finish an app that reports the number of stars on a GitHub repository.
Use Dart DevTools to do some simple debugging, and
host your app on Firebase and, finally, use a Flutter plugin to
launch the app and open the hosted privacy policy.Write a Flutter desktop application
Build a Flutter desktop app (Windows, Linux, or macOS)
that accesses GitHub APIs to retrieve your repositories,
assigned issues, and pull requests. As part of this task,
create and use plugins to interact with native APIs and desktop applications,
and use code generation to build type-safe client libraries for GitHub’s APIs.Adding a Home Screen widget to your Flutter app NEW
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.

Other resources
For Dart-specific codelabs, see the
codelabs page on the Dart site.We also recommend the following online class:info Note
If you have trouble viewing any of the codelabs
on codelabs.developers.google.com, try
this mirror of the Flutter codelabs.

Cookbook
This cookbook contains recipes that demonstrate how to solve common problems
while writing Flutter apps. Each recipe is self-contained and can be used as a
reference to help you build up an application.

Animation

Design

Effects

Forms

Games

Gestures

Images

Lists

Maintenance

Navigation

Networking

Persistence

Plugins

Testing

Integration

Unit

Widget

Casual Games Toolkit
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 a series of actions 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 new game templates that provide
a starting point for building a casual game.A base game template
that includes the basics for:A card game template
that includes everything in the base template plus:An endless runner template created in partnership
with the open source game engine, Flame. It implements: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.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.error Important
Terms and conditions apply.
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:Review the README file for the first type of game you want to create.Review the codelabs and cookbook recipes.

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 using the Flame engine.
To learn about this game,
check out I/O Pinball Powered by Flutter and Firebase on Medium
and play the game in your browser.The Flutter team created I/O Flip, 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
on the Google Developers blog and play the game in your browser.

Other resources
Once you feel ready to go beyond these games templates,
investigate other resources that our community recommended.package_2 Flutter package
api API documentation
science Codelab
book_5 Cookbook recipe
handyman Desktop application
photo_album Game assets
quick_reference_all Guidebook_5 Special effects
handyman Spriter Pro
package_2 rive
package_2 spriteWidgetpackage_2 app_reviewpackage_2 audioplayersscience User Authentication using Firebasescience Add Firebase to your Flutter gamequick_reference_all Firebase Crashlytics overview
package_2 firebase_crashlyticspackage_2 win32_gamepadphoto_album CraftPix
photo_album Game Developer Studio
handyman GIMPpackage_2 Flame
package_2 Bonfire
package_2 forge2dbook_5 Add achievements and leaderboards to your game
book_5 Add multiplayer support to your gamepackage_2 games_servicesscience Use the Foreign Function Interface in a Flutter pluginhandyman Tiledbook_5 Add advertising to your Flutter game
science Add AdMob ads to a Flutter app
science Add in-app purchases to your Flutter app
quick_reference_all Gaming UX and Revenue Optimizations for Apps (PDF)package_2 shared_preferences
package_2 sqflite
package_2 cbl_flutter (Couchbase Lite)api Paint API
book_5 Special effectsscience Build next generation UIs in Flutter

Add achievements and leaderboards to your mobile game
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.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.

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:Open your Flutter project in Xcode.
Open ios/Runner.xcworkspaceSelect the root Runner project.Go to the Signing & Capabilities tab.Click the + button to add Game Center as a capability.Close Xcode.If you haven’t already,
register your game in App Store Connect
and from the My App section press the + icon.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
To enable Play Games Services on Android:If you haven’t already, go to Google Play Console
and register your game there.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.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.Go to Play Games Services → Setup and management → Publishing.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.Go to Play Games Services → Setup and management →
Configuration → Credentials.Find the Get resources button.
It returns an XML file with the Play Games Services IDs.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
Now that you have set up Game Center and Play Games Services, and
have your achievement & leaderboard IDs ready, it’s finally Dart time.Add a dependency on the games_services package.Before you can do anything else, you have to sign the player into
the game service.
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.lightbulb 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
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:
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.To display the achievements UI from your game, call the
games_services API:
await GamesServices.showAchievements();
This displays the platform achievements UI as an overlay on your game.To display the achievements in your own UI, use
GamesServices.loadAchievements().

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.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:
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.To display the leaderboard as an overlay over your game, make the
following call:
await GamesServices.showLeaderboards(
iOSLeaderboardID: 'some_id_from_app_store',
androidLeaderboardID: 'sOmE_iD_fRoM_gPlAy',
);
If you want to display the leaderboard scores in your own UI, you
can fetch them with GamesServices.loadLeaderboardScores().

5. Next steps
There’s more to the games_services plugin. With this plugin, you can: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:
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:

Add ads to your mobile Flutter app or game
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,
Google’s mobile advertising platform.
This recipe demonstrates how to use the
google_mobile_ads
package to add a banner ad to your app or game.info 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.

1. Get AdMob App IDs
Go to AdMob and set up an
account. This could take some time because you need to provide
banking information, sign contracts, and so on.With the AdMob account ready, create two Apps in AdMob: one for
Android and one for iOS.Open the App settings section.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
Update your Android and iOS configurations to include your App IDs.

Android
Add your AdMob app ID to your Android app.Open the app’s android/app/src/main/AndroidManifest.xml file.Add a new tag.Set the android:name element with a value of
com.google.android.gms.ads.APPLICATION_ID.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:

iOS
Add your AdMob app ID to your iOS app.Open your app’s ios/Runner/Info.plist file.Enclose GADApplicationIdentifier with a key tag.Enclose your AdMob app ID with a string tag. You created this AdMob
App ID in step 1.

3. Add the google_mobile_ads plugin
To add the google_mobile_ads plugin as a dependency, run
flutter pub add:info Note
Once you add the plugin, your Android app might fail to build with a
DexArchiveMergerException: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.

4. Initialize the Mobile Ads SDK
You need to initialize the Mobile Ads SDK before loading ads.Call MobileAds.instance.initialize() to initialize the Mobile Ads
SDK.
void main() async {
WidgetsFlutterBinding.ensureInitialized();
unawaited(MobileAds.instance.initialize());
runApp(MyApp());
}
Run the initialization step at startup, as shown above,
so that the AdMob SDK has enough time to initialize before it is needed.info 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.info Note
The following code snippet refers to fields such a adSize, adUnitId
and _bannerAd. This will all make more sense in a later step.
/// 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.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).
@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.
? 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.
_bannerAd?.dispose();

7. Configure ads
To show anything beyond test ads, you have to register ad units.Open AdMob.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
and through
official samples.Choose banner ads.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.Add these Ad unit IDs to the constructor of BannerAd,
depending on the target app platform.
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:Your app must be reviewed and approved before it can fully serve
ads.
Follow AdMob’s app readiness guidelines.
For example, your app must be listed on at least one of the
supported stores such as Google Play Store or Apple App Store.You must create an app-ads.txt
file and publish it on your developer website.To learn more about app and game monetization,
visit the official sites
of AdMob
and Ad Manager.

9. Complete example
The following code implements a simple stateful widget that loads a
banner ad and shows it.
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.
? 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();
}
}
lightbulb 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.

Add multiplayer support using Firestore
Multiplayer games need a way to synchronize game states between players.
Broadly speaking, two types of multiplayer games exist: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.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.
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.

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 template that you’ll find
in the flutter/games repository.
Run the following command to clone that repository:Open the project in templates/card.info Note
You can ignore this step and follow the recipe with your own game
project. Adapt the code at appropriate places.

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:The desired outcomes include: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
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.
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_core/firebase_core.dart';
import 'firebase_options.dart';
Add the following code just above the call to runApp()
in lib/main.dart:
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
This ensures that Firebase is initialized on game startup.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:
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.info 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.

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.
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 _matchRef = instance.collection('matches').doc('match_1');
late final _areaOneRef = _matchRef
.collection('areas')
.doc('area_one')
.withConverter>(
fromFirestore: _cardsFromFirestore, toFirestore: _cardsToFirestore);
late final _areaTwoRef = _matchRef
.collection('areas')
.doc('area_two')
.withConverter>(
fromFirestore: _cardsFromFirestore, toFirestore: _cardsToFirestore);
StreamSubscription? _areaOneFirestoreSubscription;
StreamSubscription? _areaTwoFirestoreSubscription;
StreamSubscription? _areaOneLocalSubscription;
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 [];
}
final list = List.castFrom>(data);
try {
return list.map((raw) => PlayingCard.fromJson(raw)).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
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.Import Firebase and the controller:
import 'package:cloud_firestore/cloud_firestore.dart';
import '../multiplayer/firestore_controller.dart';
Add a nullable field to the _PlaySessionScreenState class
to contain a controller instance:
FirestoreController? _firestoreController;
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.
final firestore = context.read();
if (firestore == null) {
_log.warning("Firestore instance wasn't provided. "
'Running without _firestoreController.');
} else {
_firestoreController = FirestoreController(
instance: firestore,
boardState: _boardState,
);
}
Dispose of the controller using the dispose() method
of the same class.
_firestoreController?.dispose();

6. Test the game
Run the game on two separate devices
or in 2 different windows on the same device.Watch how adding a card to an area on one device
makes it appear on the other one.Open the Firebase web console
and navigate to your project’s Firestore Database.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
The most common issues you might encounter when testing
Firebase integration include the following:

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
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.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.

Flutter News Toolkit
The Flutter News Toolkit enables you to accelerate
development of a mobile news app.
The toolkit assists you in building a customized
template app with prebuilt features required
for most news apps, such authentication and
monetization. After generating your
template app, your primary tasks are to connect to your
data source, and to customize the UI to reflect
your brand.The Flutter News Toolkit includes critical features,
such as:You can use these pre-integrated features out of the box,
or modify and swap them with other functionality that
you prefer.Generating your template app requires answering
a few simple questions, as described on the
Flutter News Toolkit Overview doc page.For complete documentation on how to configure your project,
create a template app, develop the app, how to
handle authentication, theming, work with an API
server, and much more, check out the
Flutter News Toolkit documentation.You might also check out:info Note
This is an early release of the news toolkit and,
while it has been tested by early adopters, it
might have issues or rough edges. Please don’t
hesitate to file an issue.

Building user interfaces with Flutter
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.info Note
If you would like to become better acquainted with Flutter by diving
into some code, check out building layouts,
and adding interactivity to your Flutter app.

Hello world
The minimal Flutter app simply calls the runApp()
function with a widget:
import 'package:flutter/material.dart';
void main() {
runApp(
const Center(
child: Text(
'Hello, world!',
textDirection: TextDirection.ltr,
),
),
);
}
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: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. It’s generally a good idea to include this line
if you are using the Materials library.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.
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.info Note
Material is one of the 2 bundled designs included with Flutter.
To create an iOS-centric design,
check out the Cupertino components package,
which has its own versions of
CupertinoApp, and CupertinoNavigationBar.

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 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:
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:
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:

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 = 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:
import 'package:flutter/material.dart';
class Product {
const Product({required this.name});
final String name;
}
typedef CartChangedCallback = 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.

Widget catalog
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.Make your app accessible.Bring animations to your app.Manage assets, display images, and show icons.Async patterns to your
Flutter application.Widgets you absolutely need to know before building your first Flutter app.Beautiful and high-fidelity widgets for current iOS design language.Take user
input in addition to input widgets in Material components and Cupertino.Respond to touch events and route users to different views.Arrange other widgets columns, rows, grids,
and many other layouts.Widgets implementing the Material 2 Design guidelines.Visual, behavioral, and motion-rich widgets implementing the Material 3 design specification.Material 3
is the default Flutter interface as of Flutter 3.16. To learn more about this transition, check out Flutter support for Material 3.These widgets apply visual effects to the children
without changing their layout, size, or position.Scroll multiple widgets as children of the parent.Manage the theme of your app, makes your app responsive to screen sizes, or add padding.Display and style text.
Widget of the Week
100+ short, 1-minute explainer videos to
help you quickly get started with Flutter widgets.See more Widget of the Weeks

Layouts in Flutter

What's the point?
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.
For example, the first screenshot below shows 3 icons with a label
under each one:The second screenshot displays the visual layout, showing a row of
3 columns where each column contains an icon and a label.info 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, a section in
Using the Flutter inspector.Here’s a diagram of the widget tree for this UI: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.In this example, 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 in this example 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.

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 the visible widget,
as these characteristics are typically passed on to the
contained widget.This example uses Center which centers its content
horizontally and vertically.

2. Create a visible widget
For example, create a Text widget:
Text('Hello World'),
Create an Image widget:
return Image.asset(
image,
fit: BoxFit.cover,
);
Create an Icon widget:
Icon(
Icons.star,
color: Colors.red[500],
),

3. Add the visible widget to the layout widget
All layout widgets have either of the following:Add the Text widget to the Center widget:
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.
Material apps
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.
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'),
),
),
);
}
}
info 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.
Cupertino apps
To create a Cupertino app, use CupertinoApp and CupertinoPageScaffold widgets.Unlike Material, it doesn’t provide a default banner or background color.
You need to set these yourself.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 learn what other UI components you can add, check out the
Cupertino library.
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'),
],
),
),
),
);
}
}
info 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.
Non-Material apps
For a non-Material app, you can add the Center widget to the app’s
build() method:
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 non-Material app doesn’t include an AppBar, title,
or background color. If you want these features in a non-Material 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.That’s it! When you run the app, you should see Hello World.App source code:

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.
What's the point?
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.info 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.

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.info 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.App source: row_columnColumns 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.App source: row_column

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 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.App source: sizingPerhaps 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:App source: 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.App source: 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:
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,
),
),
],
),
);
lightbulb 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:
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:
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.
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,
],
),
),
),
),
lightbulb 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

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.

Standard widgets

Material widgets

Container
Many layouts make liberal use of Containers 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)

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.A Container is also used to add a rounded border and margins
to each image:
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

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.
Summary (GridView)
info 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.
Examples (GridView)
Uses GridView.extent to create a grid with tiles a maximum
150 pixels wide.App source: grid_and_listUses 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
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) => Container(child: Image.asset('images/pic$i.jpg')));

ListView
ListView, a column-like widget, automatically
provides scrolling when its content is too long for
its render box.
Summary (ListView)

Examples (ListView)
Uses ListView to display a list of businesses using
ListTiles. A Divider separates the theaters from
the restaurants.App source: grid_and_listUses ListView to display the Colors from
the Material 2 Design palette
for a particular color family.Dart code:
colors_demo.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
Use Stack to arrange widgets on top of a base
widget—often an image. The widgets can completely
or partially overlap the base widget.
Summary (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
Alignments.App source: card_and_stackUses Stack to overlay an icon on top of an image.Dart code:
bottom_navigation_demo.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 from 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.
Specifying an unsupported value disables the drop shadow entirely.
Summary (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_stackA Card containing an image and text.Dart code:
cards_demo.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.
Summary (ListTile)

Examples (ListTile)
A Card containing 3 ListTiles.App source: card_and_stackUses ListTile with leading widgets.Dart code:
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.

Videos
The following videos, part of the
Flutter in Focus series,
explain Stateless and Stateful widgets.Flutter in Focus playlistEach episode of the
Widget of the Week series
focuses on a widget. Several of them includes layout widgets.Flutter Widget of the Week playlist

Other resources
The following resources might help when writing layout code.

Build a Flutter layout

What you’ll learn
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 on Unsplash.
Text by Switzerland Tourism.To get a better overview of the layout mechanism, start with
Flutter’s approach to 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.”Ask these questions to break the layout down to its basic elements.Identify the larger elements. In this example, you arrange the image, title,
buttons, and description into a column.Diagram each row.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.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.Set up your Flutter environment.Create a new Flutter app.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.
import 'package:flutter/material.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
In this section, create a TitleSection widget that resembles
the following layout.

Add the TitleSection Widget
Add the following code after the MyApp class.
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'),
],
),
);
}
}

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.These code updates change the app in the following ways.

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.lightbulb Tip

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.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.
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.
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.
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.

Add the Text section
In this section, add the text description to this app.

Add the TextSection widget
Add the following code as a separate widget after the ButtonSection widget.
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.

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.

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.Create an images directory at the top of the project.Download the lake.jpg image and add it to the new images directory.info
You can’t use wget to save this binary file.
You can download the image from Unsplash
under the Unsplash License. The small size comes in at 94.4 kB.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.lightbulb TipText 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.

Create the ImageSection widget
Define the following ImageSection widget after the other declarations.
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.

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

Next Steps
To add interactivity to this layout, follow the
interactivity tutorial.

Lists & grids

Topics

Use lists
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.
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
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'),
),
],
),
),
);
}
}

Create a 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.
ListView(
// This next line does the trick.
scrollDirection: Axis.horizontal,
children: [
Container(
width: 160,
color: Colors.red,
),
Container(
width: 160,
color: Colors.blue,
),
Container(
width: 160,
color: Colors.green,
),
Container(
width: 160,
color: Colors.yellow,
),
Container(
width: 160,
color: Colors.orange,
),
],
),

Interactive example
Desktop and web only:
This example works in the browser and on the desktop.
However, as this list scrolls on the horizontal axis
(left to right or right to left),
hold Shift while using the mouse scroll wheel to scroll the list.To learn more, read the breaking change page on the
default drag for scrolling devices.
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: ListView(
// This next line does the trick.
scrollDirection: Axis.horizontal,
children: [
Container(
width: 160,
color: Colors.red,
),
Container(
width: 160,
color: Colors.blue,
),
Container(
width: 160,
color: Colors.green,
),
Container(
width: 160,
color: Colors.yellow,
),
Container(
width: 160,
color: Colors.orange,
),
],
),
),
),
);
}
}

Create a grid list
In some cases, you might want to display your items as a grid rather than
a normal list of items that come one after the next.
For this task, 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.
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: Theme.of(context).textTheme.headlineSmall,
),
);
}),
),

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: Theme.of(context).textTheme.headlineSmall,
),
);
}),
),
),
);
}
}

Create lists with different types of items
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

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.
/// 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.
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.
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
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);
}

List with 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.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 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
Start by creating a LayoutBuilder. You need to provide
a builder callback function with two parameters: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.
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.
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.
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.
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'),
),
],
),
),
),
);
});
lightbulb 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.
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: CardTheme(color: Colors.blue.shade50),
useMaterial3: true,
),
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)),
),
);
}
}

Work with 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.
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.
ListView.builder(
itemCount: items.length,
prototypeItem: ListTile(
title: Text(items.first),
),
itemBuilder: (context, index) {
return ListTile(
title: Text(items[index]),
);
},
)

Interactive example
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 itemExtent or prototypeItem.
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.

Scrolling
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.

Specialized scrollable widgets
The following widgets provide more specific scrolling
behavior.A video on using DraggableScrollableSheetTurn the scrollable area into a wheel! ListWheelScrollView

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.

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:

Using slivers to achieve fancy scrolling
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:
API docs
To learn more about the available sliver APIs,
check out these related API docs:

Place a floating app bar above a list
To make it easier for users to view a list of items,
you might want to hide the app bar as the user scrolls down the list.
This is especially true if your app displays a “tall”
app bar that occupies a lot of vertical space.Typically, you create an app bar by providing an appBar property to the
Scaffold widget. This creates a fixed app bar that always remains above
the body of the Scaffold.Moving the app bar from a Scaffold widget into a
CustomScrollView allows you to create an app bar
that scrolls offscreen as you scroll through a
list of items contained inside the CustomScrollView.This recipe demonstrates how to use a CustomScrollView to display a list of
items with an app bar on top that scrolls offscreen as the user scrolls
down the list using the following steps:

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
SliverAppBar and a SliverList. In addition, remove any app bars
that you provide to the Scaffold widget.
Scaffold(
// No appBar property provided, only the body.
body: CustomScrollView(
// Add the app bar and list of items as slivers in the next steps.
slivers: []),
);

2. Use SliverAppBar to 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 scrolls offscreen as the user scrolls down the list.
Furthermore, you can configure the SliverAppBar to shrink and
expand as the user scrolls.To create this effect:
CustomScrollView(
slivers: [
// Add the app bar to the CustomScrollView.
const SliverAppBar(
// Provide a standard title.
title: Text(title),
// Allows the user to reveal the app bar if they begin scrolling
// back up the list of items.
floating: true,
// Display a placeholder widget to visualize the shrinking size.
flexibleSpace: Placeholder(),
// Make the initial height of the SliverAppBar larger than normal.
expandedHeight: 200,
),
],
)
lightbulb 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.

3. Add a list of items using a SliverList
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.The SliverList and SliverGrid widgets take one required parameter: a
SliverChildDelegate, which provides a list
of widgets to SliverList or SliverGrid.
For example, the SliverChildBuilderDelegate
allows you to create a list of items that are built lazily as you scroll,
just like the ListView.builder widget.
// Next, create a SliverList
SliverList(
// Use a delegate to build items as they're scrolled on screen.
delegate: SliverChildBuilderDelegate(
// The builder function returns a ListTile with a title that
// displays the index of the current item.
(context, index) => ListTile(title: Text('Item #$index')),
// Builds 1000 ListTiles
childCount: 1000,
),
)

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 = 'Floating App Bar';
return MaterialApp(
title: title,
home: Scaffold(
// No appbar provided to the 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),
// Allows the user to reveal the app bar if they begin scrolling
// back up the list of items.
floating: 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(
// Use a delegate to build items as they're scrolled on screen.
delegate: SliverChildBuilderDelegate(
// The builder function returns a ListTile with a title that
// displays the index of the current item.
(context, index) => ListTile(title: Text('Item #$index')),
// Builds 1000 ListTiles
childCount: 1000,
),
),
],
),
),
);
}
}

Create a scrolling parallax effect
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
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.
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.
@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.withOpacity(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.
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.info Note
To learn more, check out this short
Widget of the Week video on the Flow widget:info 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.
Widget _buildParallaxBackground(BuildContext context) {
return Flow(
children: [
Image.network(
imageUrl,
fit: BoxFit.cover,
),
],
);
}
Introduce a new FlowDelegate called ParallaxFlowDelegate.
Widget _buildParallaxBackground(BuildContext context) {
return Flow(
delegate: ParallaxFlowDelegate(),
children: [
Image.network(
imageUrl,
fit: BoxFit.cover,
),
],
);
}
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.
@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: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.
@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,
),
],
);
}
}
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.
@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.
@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%.
@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.
@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.
@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.
@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.
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:
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.withOpacity(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 ScrollableState scrollable,
}) : _scrollable = 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/cookbook/img-files/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',
),
];

Creating responsive and adaptive apps
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 def monitor.Two terms that describe concepts for this
scenario are adaptive and responsive. Ideally,
you’d want your app to be both but what,
exactly, does this mean?
These terms are similar, but they are not the same.

The difference between an adaptive and a responsive app
Adaptive and responsive can be viewed as separate
dimensions of an app: you can have an adaptive app
that is not responsive, or vice versa. And, of course,
an app can be both, or neither.Learn more in the following 5-minute video:Adaptive vs Responsive

Creating a responsive Flutter app
Flutter allows you to create apps that self-adapt
to the device’s screen size and orientation.There are two basic approaches to creating Flutter
apps with responsive design:Other useful widgets and classes for creating a responsive UI:

Other resources
For more information, here are a few resources,
including contributions from the Flutter community:

Creating an adaptive Flutter app
Learn more about creating an adaptive Flutter app with
Building adaptive apps, written by the gskinner team.You might also check out the following episodes
of The Boring Show:Adaptive layoutsAdaptive layouts, part 2For an excellent example of an adaptive app,
check out Flutter Folio, a scrapbooking app created
in collaboration with gskinner and the Flutter team:The Folio source code is also available on GitHub.
Learn more on the gskinner blog.

Other resources
You can learn more about creating platform adaptive apps
in the following resources:

Creating responsive and adaptive apps
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 def monitor.Two terms that describe concepts for this
scenario are adaptive and responsive. Ideally,
you’d want your app to be both but what,
exactly, does this mean?
These terms are similar, but they are not the same.

The difference between an adaptive and a responsive app
Adaptive and responsive can be viewed as separate
dimensions of an app: you can have an adaptive app
that is not responsive, or vice versa. And, of course,
an app can be both, or neither.Learn more in the following 5-minute video:Adaptive vs Responsive

Creating a responsive Flutter app
Flutter allows you to create apps that self-adapt
to the device’s screen size and orientation.There are two basic approaches to creating Flutter
apps with responsive design:Other useful widgets and classes for creating a responsive UI:

Other resources
For more information, here are a few resources,
including contributions from the Flutter community:

Creating an adaptive Flutter app
Learn more about creating an adaptive Flutter app with
Building adaptive apps, written by the gskinner team.You might also check out the following episodes
of The Boring Show:Adaptive layoutsAdaptive layouts, part 2For an excellent example of an adaptive app,
check out Flutter Folio, a scrapbooking app created
in collaboration with gskinner and the Flutter team:The Folio source code is also available on GitHub.
Learn more on the gskinner blog.

Other resources
You can learn more about creating platform adaptive apps
in the following resources:

Building adaptive apps

Overview
Flutter provides new opportunities to build apps that can
run on mobile, desktop, and the web from a single codebase.
However, with these opportunities, come new challenges.
You want your app to feel familiar to users,
adapting to each platform by maximizing usability and
ensuring a comfortable and seamless experience.
That is, you need to build apps that are not just
multiplatform, but are fully platform adaptive.There are many considerations for developing platform-adaptive
apps, but they fall into three major categories:This page covers all three categories in detail
using code snippets to illustrate the concepts.
If you’d like to see how these concepts come together,
check out the Flokk and Folio examples that
were built using the concepts described here.Original demo code for adaptive app development techniques from flutter-adaptive-demo.

Building adaptive layouts
One of the first things you must consider when writing
your app for multiple platforms is how to adapt
it to the various sizes and shapes of the screens that
it will run on.

Layout widgets
If you’ve been building apps or websites,
you’re probably familiar with creating responsive interfaces.
Luckily for Flutter developers,
there are a large set of widgets to make this easier.Some of Flutter’s most useful layout widgets include:Single childAlign—Aligns a child within itself.
It takes a double value between -1 and 1,
for both the vertical and horizontal alignment.AspectRatio—Attempts to size the
child to a specific aspect ratio.ConstrainedBox—Imposes size constraints on its child,
offering control over the minimum or maximum size.CustomSingleChildLayout—Uses a delegate function
to position a single child. The delegate can determine
the layout constraints and positioning for the child.Expanded and Flexible—Allows a child of a
Row or Column to shrink or grow to fill any available space.FractionallySizedBox—Sizes its child to a fraction
of the available space.LayoutBuilder—Builds a widget that can reflow
itself based on its parents size.SingleChildScrollView—Adds scrolling to a single child.
Often used with a Row or Column.MultichildColumn, Row, and Flex—Lays out children
in a single horizontal or vertical run.
Both Column and Row extend the Flex widget.CustomMultiChildLayout—Uses a delegate function to
position multiple children during the layout phase.Flow—Similar to CustomMultiChildLayout,
but more efficient because it’s performed during the
paint phase rather than the layout phase.ListView, GridView, and
CustomScrollView—Provides scrollable
lists of children.Stack—Layers and positions multiple children
relative to the edges of the Stack.
Functions similarly to position-fixed in CSS.Table—Uses a classic table layout algorithm for
its children, combining multiple rows and columns.Wrap—Displays its children in multiple horizontal
or vertical runs.To see more available widgets and example code, see
Layout widgets.

Visual density
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:
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:
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 this example, 1 density unit equals 6 pixels,
but this is totally up to your views 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 Components generally
use a value of around 4 logical pixels for each
visual density unit. For more information about the
supported components, see VisualDensity API.
For more information about density principles in general,
see the Material Design guide.

Contextual layout
If you need more than density changes and can’t find a
widget that does what you need, you can take a more
procedural approach to adjust parameters, calculate sizes,
swap widgets, or completely restructure your UI to suit
a particular form factor.
Screen-based breakpoints
The simplest form of procedural layouts uses
screen-based breakpoints. In Flutter,
this can be done with the MediaQuery API.
There are no hard and fast rules for the sizes to use
here, but these are general values:
class FormFactor {
static double desktop = 900;
static double tablet = 600;
static double handset = 300;
}
Using breakpoints, you can set up a simple system
to determine the device type:
ScreenType getFormFactor(BuildContext context) {
// Use .shortestSide to detect device type regardless of orientation
double deviceWidth = MediaQuery.of(context).size.shortestSide;
if (deviceWidth > FormFactor.desktop) return ScreenType.desktop;
if (deviceWidth > FormFactor.tablet) return ScreenType.tablet;
if (deviceWidth > FormFactor.handset) return ScreenType.handset;
return ScreenType.watch;
}
As an alternative, you could abstract it more
and define it in terms of small to large:
enum ScreenSize { small, normal, large, extraLarge }
ScreenSize getSize(BuildContext context) {
double deviceWidth = MediaQuery.of(context).size.shortestSide;
if (deviceWidth > 900) return ScreenSize.extraLarge;
if (deviceWidth > 600) return ScreenSize.large;
if (deviceWidth > 300) return ScreenSize.normal;
return ScreenSize.small;
}
Screen-based breakpoints are best used for making
top-level decisions in your app. Changing things like
visual density, paddings, or font-sizes are best when
defined on a global basis.You can also use screen-based breakpoints to reflow your
top-level widget trees. For example, you could switch
from a vertical to a horizontal layout when the user isn’t on a handset:
bool isHandset = MediaQuery.of(context).size.width < 600;
return Flex(
direction: isHandset ? Axis.vertical : Axis.horizontal,
children: const [Text('Foo'), Text('Bar'), Text('Baz')],
);
In another widget,
you might swap some of the children completely:
Widget foo = Row(
children: [
...isHandset ? _getHandsetChildren() : _getNormalChildren(),
],
);

Use LayoutBuilder for extra flexibility
Even though checking total screen size is great for
full-screen pages or making global layout decisions,
it’s often not ideal for nested subviews.
Often, subviews have their own internal breakpoints
and care only about the space that they have available to render.The simplest way to handle this in Flutter is using the
LayoutBuilder class. LayoutBuilder allows a
widget to respond to incoming local size constraints,
which can make the widget more versatile than if it
depended on a global value.The previous example could be rewritten using LayoutBuilder:
Widget foo = LayoutBuilder(builder: (context, constraints) {
bool useVerticalLayout = constraints.maxWidth < 400;
return Flex(
direction: useVerticalLayout ? Axis.vertical : Axis.horizontal,
children: const [
Text('Hello'),
Text('World'),
],
);
});
This widget can now be composed within a side panel,
dialog, or even a full-screen view,
and adapt its layout to whatever space is provided.
Device segmentation
There are times when you want to make layout decisions
based on the actual platform you’re running on,
regardless of size. For example, when building a
custom title bar, you might need to check the operating
system type and tweak the layout of your title bar,
so it doesn’t get covered by the native window buttons.To determine which combination of platforms you’re on,
you can use the Platform API along with the kIsWeb value:
bool get isMobileDevice => !kIsWeb && (Platform.isIOS || Platform.isAndroid);
bool get isDesktopDevice =>
!kIsWeb && (Platform.isMacOS || Platform.isWindows || Platform.isLinux);
bool get isMobileDeviceOrWeb => kIsWeb || isMobileDevice;
bool get isDesktopDeviceOrWeb => kIsWeb || isDesktopDevice;
The Platform API can’t be accessed from web builds without
throwing an exception, because the dart.io package isn’t
supported on the web target. As a result, the above code checks
for web first, and because of short-circuiting,
Dart never calls Platform on web targets.Use Platform/kIsWeb when the logic absolutely must
run for a given platform. For example,
talking to a plugin that only works on iOS,
or displaying a widget that only conforms to
Play Store policy and not the App Store’s.

Single source of truth for styling
You’ll probably find it easier to maintain your views
if you create a single source of truth for styling values
like padding, spacing, corner shape, font sizes, and so on.
This can be done easily with some helper classes:
class Insets {
static const double xsmall = 3;
static const double small = 4;
static const double medium = 5;
static const double large = 10;
static const double extraLarge = 20;
// etc
}
class Fonts {
static const String raleway = 'Raleway';
// etc
}
class TextStyles {
static const TextStyle raleway = TextStyle(
fontFamily: Fonts.raleway,
);
static TextStyle buttonText1 =
const TextStyle(fontWeight: FontWeight.bold, fontSize: 14);
static TextStyle buttonText2 =
const TextStyle(fontWeight: FontWeight.normal, fontSize: 11);
static TextStyle h1 =
const TextStyle(fontWeight: FontWeight.bold, fontSize: 22);
static TextStyle h2 =
const TextStyle(fontWeight: FontWeight.bold, fontSize: 16);
static TextStyle body1 = raleway.copyWith(color: const Color(0xFF42A5F5));
// etc
}
These constants can then be used in place of hard-coded numeric values:
return Padding(
padding: const EdgeInsets.all(Insets.small),
child: Text('Hello!', style: TextStyles.body1),
);
Use Theme.of(context).platform for theming and
design choices, like what kind of switches to show
and general Cupertino/Material adaptions.With all views referencing the same shared-design system rules,
they tend to look better and more consistent.
Making a change or adjusting a value for a specific platform
can be done in a single place, instead of using an error-prone
search and replace. Using shared rules has the added benefit
of helping enforce consistency on the design side.Some common design system categories that can be represented
this way are:Like most rules, there are exceptions:
one-off values that are used nowhere else in the app.
There is little point in cluttering up the styling rules
with these values, but it’s worth considering if they
should be derived from an existing value (for example,
padding + 1.0). You should also watch for reuse or duplication
of the same semantic values. Those values should likely be
added to the global styling ruleset.

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.

Use desktop build targets for rapid testing
One of the most effective ways to test adaptive
interfaces is to take advantage of the desktop build targets.When running on a desktop, you can easily resize the window
while the app is running to preview various screen sizes.
This, combined with hot reload, can greatly accelerate the
development of a responsive UI.

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.

Input
Of course, it isn’t enough to just adapt how your app looks,
you also have to support varying 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.

Scroll wheel
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 them 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.
return Listener(
onPointerSignal: (event) {
if (event is PointerScrollEvent) print(event.scrollDelta.dy);
},
child: ListView(),
);

Tab traversal and focus interactions
Users with physical keyboards expect that they can use
the tab key to quickly navigate your 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. It combines the functionality
of Actions, Shortcuts, MouseRegion, and
Focus widgets to create a detector that defines actions
and key bindings, and provides callbacks for handling focus
and hover highlights.
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(),
)
],
),
);
}
}

Controlling traversal order
To get more control over the order that
widgets are focused on when the user presses tab,
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:
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
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:
@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(),
),
),
),
);
}
}
If you’d like to apply a set of keyboard shortcuts to a
large section of the tree, you can use the Shortcuts widget:
// 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:
@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:
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:
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
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 usually see
a hand cursor when you hover over a button,
or an I cursor when you hover over text.The Material Component set has built-in support
for your standard button and text cursors.
To change the cursor from within your own widgets,
use MouseRegion:
// 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:
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,
),
);

Idioms and norms
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.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:
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:
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:
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:
return const SelectableText('Select me!');
To support rich text, then use TextSpan:
return const SelectableText.rich(
TextSpan(
children: [
TextSpan(text: 'Hello'),
TextSpan(text: 'Bold', style: TextStyle(fontWeight: FontWeight.bold)),
],
),
);

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 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
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:
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: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
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:
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
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
are: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.

Educate yourself on basic usability principles
Of course, this page doesn’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 to get you started:

Update the UI based on orientation
In some situations,
you want to update the display of an app when the 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.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
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.
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.
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,
);
},
),
info Note
If you’re interested in the orientation of the screen,
rather than the amount of space available to the parent,
use MediaQuery.of(context).orientation instead of an
OrientationBuilder widget.

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: Theme.of(context).textTheme.displayLarge,
),
);
}),
);
},
),
);
}
}

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: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.
void main() {
WidgetsFlutterBinding.ensureInitialized();
SystemChrome.setPreferredOrientations([
DeviceOrientation.portraitUp,
]);
runApp(const MyApp());
}

Use themes to share colors and font styles
info Note
This recipe uses Flutter’s support for Material 3 and
the google_fonts package. As of the Flutter 3.16 release,
Material 3 is Flutter’s default theme.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: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.As of the Flutter 3.16 release, Material 3 is Flutter’s
default theme.If you don’t specify a theme in the constructor,
Flutter creates a default theme for you.
MaterialApp(
title: appName,
theme: ThemeData(
useMaterial3: true,
// 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.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.
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:

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.
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.
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
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(
useMaterial3: true,
// 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),
),
),
);
}
}

Material Design for Flutter
Material Design is an open-source design system built
and supported by Google designers and developers.The latest version, Material 3, enables personal,
adaptive, and expressive experiences—from dynamic color
and enhanced accessibility, to foundations for
large screen layouts, and design tokens.warning Warning
As of the Flutter 3.16 release, Material 3 is
enabled by default. For now, you can opt out
of Material 3 by setting the useMaterial3 property
to false. But be aware that the useMaterial3
property and support for Material 2
will eventually be deprecated according to
Flutter’s deprecation policy.For most Flutter widgets, upgrading to Material 3
is seamless. But some widgets couldn’t be
updated—entirely new implementations were needed,
such as NavigationBar.
You must make these changes to your code manually.
Until your app is entirely updated,
the UI might look or act a bit strange.
You can find the entirely new Material components by
visiting the Affected widgets page.Explore the updated components, typography, color system,
and elevation support with the
interactive Material 3 demo:
More information
To learn more about Material Design and Flutter,
check out:

Migrate to Material 3

Summary
The Material library has been updated to match the Material 3 Design spec.
Changes include new components and component themes, updated component visuals,
and much more. Many of these updates are seamless. You’ll see the new version
of an affected widget when recompiling your app against the 3.16 (or later)
release. But some manual work is also required to complete the migration.

Migration guide
Prior to the 3.16 release, you could opt in to the Material 3 changes by
setting the useMaterial3 flag to true. As of the Flutter 3.16 release
(November 2023), useMaterial3 is true by default.By the way, you can revert to Material 2 behavior in your app by setting the
useMaterial3 to false. However, this is just a temporary solution. The
useMaterial3 flag and the Material 2 implementation will eventually be
removed as part of Flutter’s deprecation policy.

Colors
The default values for ThemeData.colorScheme are updated to match
the Material 3 Design spec.The ColorScheme.fromSeed constructor generates a ColorScheme
derived from the given seedColor. The colors generated by this
constructor are designed to work well together and meet contrast
requirements for accessibility in the Material 3 Design system.When updating to the 3.16 release, your UI might look a little strange
without the correct ColorScheme. To fix this, migrate to the
ColorScheme generated from the ColorScheme.fromSeed constructor.Code before migration:Code after migration:To generate a content-based dynamic color scheme, use the
ColorScheme.fromImageProvider static method. For an example of generating a
color scheme, check out the ColorScheme from a network image sample.Changes to Flutter Material 3 include a new background color.
ColorScheme.surfaceTint indicates an elevated widget.
Some widgets use different colors.To return your app’s UI to its previous behavior (which we don’t recommend):Code before migration:Code after migration:The ColorScheme.surfaceTint value indicates a component’s elevation in
Material 3. Some widgets might use both surfaceTint and shadowColor to
indicate elevation (for example, Card and ElevatedButton) and others might
only use surfaceTint to indicate elevation (such as AppBar).To return to the widget’s previous behavior, set, set Colors.transparent
to ColorScheme.surfaceTint in the theme. To differentiate a widget’s shadow
from the content (when it has no shadow), set the ColorScheme.shadow color to
the shadowColor property in the widget theme without a default shadow color.Code before migration:Code after migration:The ElevatedButton now styles itself with a new combination of colors.
Previously, when the useMaterial3 flag was set to false, ElevatedButton
styled itself with ColorScheme.primary for the background and
ColorScheme.onPrimary for the foreground. To achieve the same visuals, switch
to the new FilledButton widget without the elevation changes or drop shadow.Code before migration:Code after migration:

Typography
The default values for ThemeData.textTheme are updated to match the
Material 3 defaults. Changes include updated font size, font weight, letter
spacing, and line height. For more details, check out the TextTheme
documentation.As shown in the following example, prior to the 3.16 release, when a Text
widget with a long string using TextTheme.bodyLarge in a constrained layout
wrapped the text into two lines. However, the 3.16 release wraps the text into
three lines. If you must achieve the previous behavior, adjust the text style
and, if necessary, the letter spacing.Code before migration:Code after migration:

Components
Some components couldn’t merely be updated to match the Material 3 Design spec
but needed a whole new implementation. Such components require manual migration
since the Flutter SDK doesn’t know what, exactly, you want.Replace the Material 2 style BottomNavigationBar widget with the new
NavigationBar widget. It’s slightly taller, contains pill-shaped
navigation indicators, and uses new color mappings.Code before migration:Code after migration:Check out the complete sample on
migrating from BottomNavigationBar to NavigationBar.Replace the Drawer widget with NavigationDrawer, which provides
pill-shaped navigation indicators, rounded corners, and new color mappings.Code before migration:Code after migration:Check out the complete sample on migrating from Drawer to NavigationDrawer.Material 3 introduces medium and large app bars that display a larger headline
before scrolling. Instead of a drop shadow, ColorScheme.surfaceTint color
is used create a separation from the content when scrolling.The following code demonstrates how to implement the medium app bar:There are now two types of TabBar widgets: primary and secondary.
Secondary tabs are used within a content area to further separate
related content and establish hierarchy. Check out the TabBar.secondary
example.The new TabBar.tabAlignment property specifies the horizontal alignment
of the tabs.The following sample shows how to modify tab alignment in a scrollable TabBar:SegmentedButton, an updated version of ToggleButtons,
uses fully rounded corners, differs in layout height and
size, and uses a Dart Set to determine selected items.Code before migration:Code after migration:Check out the complete sample on
migrating from ToggleButtons to SegmentedButton.
New components

Timeline
In stable release: 3.16

References
Documentation:API documentation:Relevant issues:Relevant PRs:

Text

Topics

Flutter's fonts and 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. Fonts are a huge
topic and beyond the scope of this site, however,
this page discusses Flutter’s support for variable
and static fonts.

Variable fonts
Variable fonts (also called OpenType 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.However, the font must first define what axes are available,
and that isn’t always easy to figure out. If you are using
a Google Font, you can learn what axes are available using
the type tester feature, described in the next section.

Using the Google Fonts type tester
The Google Fonts site offers both variable and static fonts.
Use the type tester to learn more about its variable fonts.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.

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 site
Use the font’s details page to learn more about its static 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):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.
The following example is for CSS, but illustrates the concept:

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:Prototyping beautiful designs with FlutterTo 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,
a free article on Medium. The associated example also
uses a custom shader.

Use a custom font

What you’ll learn
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.You don’t need to follow each step as you go.
The guide offers completed example files at the end.info NoteThis guide makes the following presumptions:

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: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.Research what options a given font offers,
like more than one weight or style per font file,
variable font capability,
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.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.If necessary, to match the remaining steps in this guide,
change the name of your Flutter app to custom_fonts.Navigate to the root of your Flutter project.Create a fonts directory at the root of your Flutter project.Move or copy the font files in a fonts or assets
folder at the root of your Flutter project.The resulting folder structure should resemble the following:

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.Open the pubspec.yaml file at the root of your Flutter project.Paste the following YAML block after the flutter declaration.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 do 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.

Set styles and weights with font files
When you declare which font files represent styles or weights of a font,
you can apply the style or weight properties.
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 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 hadn’t added RobotoMono-Bold to your app,
Flutter attempts to make the 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 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 hadn’t added Raleway-Italic to your app,
Flutter attempts to make the 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.
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.
child: Text(
'Roboto Mono sample',
style: TextStyle(fontFamily: 'RobotoMono'),
),
error 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
Open the pubspec.yaml file at the root of your Flutter project.Replace its contents with the following YAML.

Use this main.dart file
Open the main.dart file in the lib/ directory of your Flutter project.Replace its contents with the following Dart code.
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(
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.

Export fonts from a package
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:info 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.

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:

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.

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.
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

main.dart
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: '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',
),
),
),
);
}
}

Custom drawing and graphics

Topics

Writing and using fragment shaders
info Note
Both the Skia and Impeller backends support writing a
custom shader. Except where noted, the same
instructions apply to both.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.

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.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.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.

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.

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:

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 method.
GLSL sampler values, which use the sampler2D type,
are set using the FragmentShader.setImageSampler 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 once for each value.For example, given the following uniforms declarations in a GLSL fragment program:The corresponding Dart code to initialize these uniform values is as follows:Observe that the indices used with FragmentShader.setFloat
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.
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: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.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.

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.

Other resources
For more information, here are a few resources.

Add interactivity to your Flutter app

What you'll learn
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.
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

What's the point?
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: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.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.
class FavoriteWidget extends StatefulWidget {
const FavoriteWidget({super.key});
@override
State createState() => _FavoriteWidgetState();
}
info 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:
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.
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.centerRight,
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'),
),
),
],
);
}
}
lightbulb 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:
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: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.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

What's the point?
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: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:
import 'package:flutter/material.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:The TapboxB class:
import 'package:flutter/material.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:The _TapboxCState object:
import 'package:flutter/material.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.lightbulb 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

Material Components

Resources
The following resources might help when adding interactivity
to your app.Gestures, a section in the Flutter cookbook.

Taps, drags, and other gestures
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: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.

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):TapDouble tapLong pressVertical dragHorizontal dragPan

Adding gesture detection to widgets
To listen to gestures from the widgets layer,
use a GestureDetector.info 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.

Gesture disambiguation
At a given location on screen,
there might be multiple gesture detectors.
For example: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.

Handle 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.info 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:
// 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

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'),
),
);
}
}

Drag outside an app
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.

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.

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.

Drag a UI element
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
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.
MenuListItem(
name: item.name,
price: item.formattedTotalItemPrice,
photoProvider: item.imageProvider,
)
Wrap the MenuListItem widget with a LongPressDraggable widget.
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.
CustomerCart(
hasItems: customer.items.isNotEmpty,
highlighted: candidateItems.isNotEmpty,
customer: customer,
);
Wrap the CustomerCart widget with a DragTarget widget.
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.
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.
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:
import 'package:flutter/material.dart';
void main() {
runApp(
const MaterialApp(
home: ExampleDragAndDrop(),
debugShowCheckedModeBanner: false,
),
);
}
const List
_items = [
Item(
name: 'Spinach Pizza',
totalPriceCents: 1299,
uid: '1',
imageProvider: NetworkImage('https://flutter'
'.dev/docs/cookbook/img-files/effects/split-check/Food1.jpg'),
),
Item(
name: 'Veggie Delight',
totalPriceCents: 799,
uid: '2',
imageProvider: NetworkImage('https://flutter'
'.dev/docs/cookbook/img-files/effects/split-check/Food2.jpg'),
),
Item(
name: 'Chicken Parmesan',
totalPriceCents: 1499,
uid: '3',
imageProvider: NetworkImage('https://flutter'
'.dev/docs/cookbook/img-files/effects/split-check/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('https://flutter'
'.dev/docs/cookbook/img-files/effects/split-check/Avatar1.jpg'),
),
Customer(
name: 'Nathan',
imageProvider: const NetworkImage('https://flutter'
'.dev/docs/cookbook/img-files/effects/split-check/Avatar2.jpg'),
),
Customer(
name: 'Emilio',
imageProvider: const NetworkImage('https://flutter'
'.dev/docs/cookbook/img-files/effects/split-check/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)}';
}
}

Add Material touch 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:
// 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
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'),
),
);
}
}

Implement swipe to dismiss
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
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.
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.
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:
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.

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 {
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),
),
);
},
),
),
);
}
}

Input & forms

Topics

Create and style a text field
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 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.
TextField(
decoration: InputDecoration(
border: OutlineInputBorder(),
hintText: 'Enter a search term',
),
),
To retrieve the value when it changes,
see 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.
TextFormField(
decoration: const InputDecoration(
border: UnderlineInputBorder(),
labelText: 'Enter your username',
),
),

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',
),
),
),
],
);
}
}
For more information on input validation, see the
Building a form with validation recipe.

Retrieve the value of a text field
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
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 of the TextEditingController when
you’ve finished using it. This ensures that you discard any resources
used by the object.
// 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:
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()
method 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.
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
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 the that user has entered by using the
// TextEditingController.
content: Text(myController.text),
);
},
);
},
tooltip: 'Show me the value!',
child: const Icon(Icons.text_fields),
),
);
}
}

Handle changes to a text field
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
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 when dealing with user input,
as text may contain complex characters.
This ensures that every character is counted correctly
as they appear to the user.
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:

Create a TextEditingController
Create a TextEditingController:
// 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.
}
}
info 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.
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.
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.
@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();
}

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 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('Retrieve Text Input'),
),
body: Padding(
padding: const EdgeInsets.all(16),
child: Column(
children: [
TextField(
onChanged: (text) {
print('First text field: $text (${text.characters.length})');
},
),
TextField(
controller: myController,
),
],
),
),
);
}
}

Focus and text fields
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.For more information on handling input and creating text fields,
see 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
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:
// 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 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.
@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.
FloatingActionButton(
// When the button is pressed,
// give focus to the text field using myFocusNode.
onPressed: () => myFocusNode.requestFocus(),
),

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 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 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),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
}

Build a form with 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
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.
import 'package:flutter/material.dart';
// Define a custom Form widget.
class MyCustomForm extends StatefulWidget {
const MyCustomForm({super.key});
@override
MyCustomFormState createState() {
return 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.
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.
],
),
);
}
}
lightbulb 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.
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
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.
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()
method 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
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
MyCustomFormState createState() {
return 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.
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, check out the
Retrieve the value of a text field recipe.

Display a snackbar
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.
This recipe implements a snackbar using the following steps:

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.
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.
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);
info Note
To learn more, watch this short 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,
they might use an optional action in the SnackBar to recover
the message.Here’s an example of providing
an additional action to the SnackBar widget:
final snackBar = SnackBar(
content: const Text('Yay! A SnackBar!'),
action: SnackBarAction(
label: 'Undo',
onPressed: () {
// Some code to undo the change.
},
),
);

Interactive example
info 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.
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'),
),
);
}
}

Using Actions and Shortcuts
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.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 Intents 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:
@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:
@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.info 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:
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:
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:
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 Actions:
@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:
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:
Object? result;
if (selectAll != null) {
result =
Actions.of(context).invokeAction(selectAll, const SelectAllIntent());
}
Combine that into one call with the following:
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.
@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, then the Actions widget gives
another enabled action higher in the widget hierarchy (if it exists) a chance to
execute.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:
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:
@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:

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 createState() => _CopyableTextFieldState();
}
class _CopyableTextFieldState extends State {
late final TextEditingController controller = TextEditingController();
@override
void dispose() {
controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Actions(
dispatcher: LoggingActionDispatcher(),
actions: >{
ClearIntent: ClearAction(controller),
CopyIntent: CopyAction(controller),
SelectAllIntent: SelectAllAction(controller),
},
child: Builder(builder: (context) {
return Scaffold(
body: Center(
child: Row(
children: [
const Spacer(),
Expanded(
child: TextField(controller: controller),
),
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);
final TextEditingController controller;
@override
Object? invoke(covariant SelectAllIntent intent) {
controller.selection = controller.selection.copyWith(
baseOffset: 0,
extentOffset: controller.text.length,
affinity: controller.selection.affinity,
);
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());

Understanding Flutter's keyboard focus system
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.

Focus use cases
Some examples of situations where you might need to know how to use the focus
system:

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.

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:

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.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.
import 'package:flutter/material.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:
@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:
@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:
@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.info 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.
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
hightlightStrategy 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.

Adding assets and images
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
Flutter uses the pubspec.yaml file,
located at the root of your project,
to identify assets required by an app.Here is an example:To include all assets under a directory,
specify the directory name with the / character at the end:info Note
Only files located directly in the directory are included.
Resolution-aware asset image variants are the only exception.
To add files located in subdirectories, create an entry per directory.

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.

Loading assets
Your app can access its assets through an
AssetBundle 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
Each Flutter app has a rootBundle
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, 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:
import 'package:flutter/services.dart' show rootBundle;
Future loadAsset() async {
return await rootBundle.loadString('assets/config.json');
}

Loading images
To load an image, use the AssetImage
class in a widget’s build() method.For example, your app can load the background
image from the asset declarations in the previous example:
return const Image(image: AssetImage('assets/background.png'));

Resolution-aware image assets
Flutter can load resolution-appropriate images for
the current device pixel ratio.AssetImage will map a logical requested
asset onto one that most closely matches the current
device pixel ratio.For this mapping to work, assets should be arranged
according to a particular directory structure: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: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.info Note
Device pixel ratio depends on MediaQueryData.size, which requires having either
MaterialApp or CupertinoApp as an ancestor of your AssetImage.
Bundling of resolution-aware image assets
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 or ImageCache,
you’ll also notice parameters related to scale.)

Asset images in package dependencies
To load an image from a package dependency,
the package argument must be provided to AssetImage.For instance, suppose your application depends on a package
called my_icons, which has the following directory structure:To load the image, use:
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
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:To include, say, the first image, the pubspec.yaml of the
application should specify it in the assets section: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:To load the image within your package, use:

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
On Android the assets are available through the
AssetManager API. The lookup key used in,
for instance openFd, is obtained from
lookupKeyForAsset on PluginRegistry.Registrar or
getLookupKeyForAsset on FlutterView.
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.yamlThis reflects the following structure in your Flutter app.To access icons/heart.png from your Java plugin code,
do the following:

Loading Flutter assets in iOS
On iOS the assets are available through the mainBundle.
The lookup key used in, for instance pathForResource:ofType:,
is obtained from lookupKeyForAsset or lookupKeyForAsset:fromPackage:
on FlutterPluginRegistrar, or lookupKeyForAsset: or
lookupKeyForAsset:fromPackage: on FlutterViewController.
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:To access icons/heart.png from your Swift app you
would do the following:For a more complete example, see the implementation of the
Flutter video_player plugin on pub.dev.The ios_platform_images plugin on pub.dev wraps
up this logic in a convenient category. You fetch
an image as follows:Objective-C:Swift:

Loading iOS images in Flutter
When implementing Flutter by
adding it to an existing iOS app,
you might have images hosted in iOS that you
want to use in Flutter. To accomplish
that, use the ios_platform_images plugin
available on pub.dev.

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 a Flutter application’s launch icon works
the same way as updating launch icons in native
Android or iOS applications.
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.info Note
If you rename the .png files, you must also update the
corresponding name in your AndroidManifest.xml’s
tag’s android:icon attribute.
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.
Keep the original file names.

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.info Note
This implies that if you don’t call runApp() in the
main() function of your app (or more specifically,
if you don’t call FlutterView.render() in response to
PlatformDispatcher.onDrawFrame),
the launch screen persists forever.
Android
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 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 to achieve the intended effect.For more details, see
Adding a splash screen to your Android app.
iOS
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.

Display images from the internet
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.
Image.network('https://picsum.photos/250?image=9'),

Bonus: animated gifs
One useful thing about the Image widget:
It supports animated gifs.
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.

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'),
),
);
}
}

Fade in images with a placeholder
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
In this example, use the transparent_image
package for a simple transparent placeholder.
FadeInImage.memoryNetwork(
placeholder: kTransparentImage,
image: 'https://picsum.photos/250?image=9',
),

Complete example
import 'package:flutter/material.dart';
import 'package:transparent_image/transparent_image.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: Stack(
children: [
const Center(child: CircularProgressIndicator()),
Center(
child: FadeInImage.memoryNetwork(
placeholder: kTransparentImage,
image: 'https://picsum.photos/250?image=9',
),
),
],
),
),
);
}
}

From asset bundle
You can also consider using local assets for placeholders.
First, add the asset to the project’s pubspec.yaml file
(for more details, see Adding assets and images):Then, use the FadeInImage.assetNetwork() constructor:
FadeInImage.assetNetwork(
placeholder: 'assets/loading.gif',
image: 'https://picsum.photos/250?image=9',
),

Complete 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 = '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',
),
),
),
);
}
}

Play and pause a 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 Warning
At this time,
the video_player plugin doesn’t work with any desktop platform.
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
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:

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.

iOS
For iOS, add the following to the Info.plist file found at
/ios/Runner/Info.plist.warning Warning
The video_player plugin can only play asset videos in iOS simulators.
You must test network-hosted videos on physical iOS devices.

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:
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.
// 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.
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
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,
),
),
);
}
}

Navigation and routing
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():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.

Using named routes
info Note
We don’t recommend using named routes for most applications.
For more information, see the Limitations section below.Applications with simple navigation and deep linking requirements can use the
Navigator for navigation and the MaterialApp.routes parameter for deep
links:Routes specified here are called named routes. For a complete example, follow
the Navigate with named routes recipe from the Flutter Cookbook.

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 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.

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 a
configuration for you. For example:Because packages like go_router are declarative, they will always display the
same screen(s) when a deep link is received.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.info Note
You can’t prevent navigation from page-backed screens using WillPopScope.
Instead, you should consult your routing package’s API documentation.

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:

Work with 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
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.
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.
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.info Note
Order is important and must correspond to the order of the tabs in the
TabBar.
body: const TabBarView(
children: [
Icon(Icons.directions_car),
Icon(Icons.directions_transit),
Icon(Icons.directions_bike),
],
),

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),
],
),
),
),
);
}
}

Navigate to a new screen and back
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.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
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:
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!'),
),
),
);
}
}

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 MaterialPageRoute,
which 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:
// Within the `FirstRoute` widget
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(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:
// Within the SecondRoute widget
onPressed: () {
Navigator.pop(context);
}

Interactive example
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!'),
),
),
);
}
}

Navigation with CupertinoPageRoute
In the previous example you learned how to navigate between screens
using the MaterialPageRoute from Material Components.
However, in Flutter you are not limited to Material design language,
instead, you also have access to Cupertino (iOS-style) widgets.Implementing navigation with Cupertino widgets follows the same steps
as when using MaterialPageRoute,
but instead you use CupertinoPageRoute
which provides an iOS-style transition animation.In the following example, these widgets have been replaced:This way, the example follows the current iOS design language.You don’t need to replace all Material widgets with Cupertino versions
to use CupertinoPageRoute
since Flutter allows you to mix and match Material and Cupertino widgets
depending on your needs.
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!'),
),
),
);
}
}

Send data to a new screen
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
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.
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
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
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!
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.
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.
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
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.
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.
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
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),
),
);
}
}

Return data from a screen
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
The home screen displays a button. When tapped,
it launches the selection screen.
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:
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.
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
ElevatedButton(
onPressed: () {
// Close the screen and return "Yep!" as the result.
Navigator.pop(context, 'Yep!');
},
child: const Text('Yep!'),
)

Nope button
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:
// 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
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.'),
),
)
],
),
),
);
}
}

Add a drawer to a screen
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
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:
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.
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.
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. 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).
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.info Note
For more information on implementing navigation,
check out the Navigation section of the cookbook.
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)),
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);
},
),
],
),
),
);
}
}

Deep linking
Flutter supports deep linking on iOS, Android, and web browsers.
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 parameter or
onGenerateRoute), or by
using the Router widget.info Note
Named routes are no longer recommended for most
applications. For more information, see
Limitations in the navigation overview page.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:Deep linking in Flutter

Get started
To get started, see our cookbooks for Android and iOS:

Migrating from plugin-based deep linking
If you have written a plugin to handle deep links, as described in
Deep Links and Flutter applications
(a free article on Medium),
it will continue to work until you opt in to this behavior by adding
FlutterDeepLinkingEnabled to Info.plist or
flutter_deeplinking_enabled to AndroidManifest.xml, respectively.

Behavior
The behavior varies slightly based on the platform and whether the app is
launched and running.When using the 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

Set up app links for Android
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.A 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.

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.To create a new application, type flutter create :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:To handle the routing,
create a GoRouter object in the main.dart file:
import 'package:flutter/material.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
Add the following metadata tag and intent filter inside the
tag with .MainActivity.Replace example.com with your own web domain.info Note
The metadata tag flutter_deeplinking_enabled opts
into Flutter’s default deeplink handler.
If you are using the third-party plugins,
such as uni_links, setting this metadata tag will
break these plugins. Omit this metadata tag
if you prefer to use third-party plugins.

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:

assetlinks.json
The hosted file should look similar to this:Set the package_name value to your Android application ID.Set sha256_cert_fingerprints to the value you got
from the previous step.Host the file at a URL that resembles the following:
/.well-known/assetlinks.jsonVerify that your browser can access this file.

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:info 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.If everything is set up correctly, the Flutter application
launches and displays the details screen:

Appendix
Source code: deeplink_cookbook

Set up universal links for iOS
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.A universal link is a type of deep link that uses http or https
and is exclusive to Apple devices.Setting up universal links requires one to own a web domain.
Otherwise, consider using Firebase Hosting or GitHub Pages
as a temporary solution.

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.To create a new application, type flutter create .To include the go_router package as a dependency,
run flutter pub add:To handle the routing, create a GoRouter object in the main.dart file:
import 'package:flutter/material.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. Adjust iOS build settings
Navigate to the Info Plist file in the ios/Runner folder.Update the key to FlutterDeepLinkingEnabled with a Boolean value set to YES.info Note
The FlutterDeepLinkingEnabled property opts into Flutter’s default deeplink handler. If
you are using the third-party plugins, such as uni_links, setting this property will
break these plugins. Skip this step if you prefer to use third-party plugins.Click Associated Domains.Enter applinks:. Replace with your own domain name.You have finished configuring the application for deep linking.

3. Hosting apple-app-site-association file
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, get the app ID of the Flutter app you created in the previous step.

App ID
Apple formats the app ID as ..For example: Given a team ID of S8QB4VV633
and a bundle ID of com.example.deeplinkCookbook, The app ID is
S8QB4VV633.com.example.deeplinkCookbook.

apple-app-site-association
The hosted file should have the following content:Set the appID value to ..Set the paths value to ["*"].
The paths field specifies the allowed universal links.
Using the asterisk, * redirects every path to the Flutter application.
If needed, change the paths value to a setting more appropriate
to your app.Host the file at a URL that resembles the following:
/.well-known/apple-app-site-associationVerify that your browser can access this file.

Testing
info 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.
The universal link won’t work until the CDN requests the file.
To bypass Apple’s CDN, check out the alternate mode section.You can use a real device or the Simulator to test a universal link,
but first make sure you have executed flutter run at least once on
the devices. This ensures that the Flutter application is installed.If using the Simulator, test using the Xcode CLI:Otherwise, type the URL in the Note app and click it.If everything is set up correctly, the Flutter application
launches and displays the details screen:

Appendix
Source code: deeplink_cookbook

Configuring the URL strategy on the web
Flutter web apps support two ways of configuring
URL-based navigation on the web:

Configuring the URL strategy
To configure Flutter to use the path instead, use the
usePathUrlStrategy function provided by the flutter_web_plugins library
in the 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 .

Introduction to animations
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.)(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: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.)Learn about Animation Basics with Implicit AnimationsTo create a custom implicit animation, watch
Creating your own custom implicit animations with TweenAnimationBuilder.
(Also published as a companion article.)Learn about building Custom Implicit Animations with TweenAnimationBuilderTo create an explicit animation (where you control the animation,
rather than letting the framework control it), perhaps
you can use one of the built-in explicit animations classes.
For more information, watch
Making your first directional animations with
built-in explicit animations.
(Also published as a companion article.)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.)For a deeper understanding of just how animations work in Flutter, watch
Animation deep dive.
(Also published as a companion article.)

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.Implicit animations codelab
Covers how to use implicit animations
using step-by-step instructions and interactive examples.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
Medium articles showing how to create an animated chart using tweening.Write your first Flutter app on the web
Codelab demonstrating how to create a form
that uses animation to show the user’s progress
as they fill in the fields.

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.The documents listed above, such as the
Animations tutorial, are not specifically
about tweening, but they use tweens in their examples.

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.Also see the API documentation for
AnimationController.animateWith and
SpringSimulation.

Pre-canned animations
If you are using Material widgets, you might check
out the animations package available on pub.dev.
This package contains pre-built animations for
the following commonly used patterns:
Container transforms, shared axis transitions,
fade through transitions, and fade transitions.

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.

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 easily implement
shared element transitions between routes (pages)
using the Hero widget.

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.

Other resources
Learn more about Flutter animations at the following links: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.

Animations tutorial

What you'll learn
This tutorial shows you how to build explicit animations in Flutter.
After introducing some of the essential concepts, classes,
and methods in the animation library, it walks you through 5
animation examples. The examples build on each other,
introducing you to different aspects of the animation library.The Flutter SDK also provides built-in explicit animations,
such as FadeTransition, SizeTransition,
and SlideTransition. These simple animations are
triggered by setting a beginning and ending point.
They are simpler to implement
than custom explicit animations, which are described here.

Essential animation concepts and classes

What's the point?
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 knows nothing about what
is onscreen. An Animation is an abstract class that
understands its current value and its state (completed or dismissed).
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 devise.
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 member.An Animation object knows nothing about rendering or
build() functions.

Curved­Animation
A CurvedAnimation defines the animation’s progress
as a non-linear curve.
animation = CurvedAnimation(parent: controller, curve: Curves.easeIn);
info Note
The Curves class defines many commonly used curves,
or you can create your own. For example:Browse the Curves documentation for a complete listing
(with visual previews) of the Curves constants that ship with Flutter.CurvedAnimation and AnimationController (described in the next section)
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.

Animation­Controller
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:
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, so typically 60 numbers are generated per
second. After each number is generated, each Animation object calls the
attached Listener objects. To create a custom display list for each
child, see 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 see an example of this in animate1 on GitHub.info Note
In some cases, a position might exceed the AnimationController’s
0.0-1.0 range. For example, the fling() function
allows you to provide velocity, force, and position
(via the Force object). The position can be anything and
so can be outside of the 0.0 to 1.0 range.A CurvedAnimation can also exceed the 0.0 to 1.0 range,
even if the 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 overshoots or undershoots 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:
tween = Tween(begin: -200, end: 0);
A Tween is a stateless object that takes only begin and end.
The sole job of a Tween is to define 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.
colorTween = ColorTween(begin: Colors.transparent, end: Colors.black54);
A Tween object doesn’t store any state. 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 can be found in the
.value method. The evaluate function also performs some housekeeping,
such as ensuring that begin and end are returned 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.
AnimationController controller = AnimationController(
duration: const Duration(milliseconds: 500), vsync: this);
Animation alpha = IntTween(begin: 0, end: 255).animate(controller);
info Note
The animate() method returns an Animation,
not an Animatable.The following example shows a controller, a curve, and a Tween:
AnimationController controller = AnimationController(
duration: const Duration(milliseconds: 500), vsync: this);
final Animation curve =
CurvedAnimation(parent: controller, curve: Curves.easeOut);
Animation alpha = IntTween(begin: 0, end: 255).animate(curve);

Animation notifications
An Animation object can have Listeners and StatusListeners,
defined 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 StatusListener is called when an animation begins,
ends, moves forward, or moves reverse, as defined by AnimationStatus.
The next section has an example of the addListener() method,
and Monitoring the progress of the animation shows an
example of addStatusListener().

Animation examples
This section walks you through 5 animation examples.
Each section provides a link to the source code for that example.

Rendering animations

What's the point?
So far you’ve learned how to generate a sequence of numbers over time.
Nothing has been rendered 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:
import 'package:flutter/material.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: animate0The 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:App source: animate1The addListener() function calls setState(),
so every time the Animation generates a new number,
the current frame is marked dirty, which forces
build() to be called again. 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!Dart language tricks:
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:This code is equivalent to:To learn more about cascades,
check out Cascade notation
in the Dart language documentation.

Simplifying with Animated­Widget

What's the point?
The AnimatedWidget base class allows you to separate out
the core widget code from the animation code.
AnimatedWidget doesn’t need to maintain a State
object to hold the animation. Add the following AnimatedLogo class:
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:App source: animate2

Monitoring the progress of the animation

What's the point?
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:
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) => print('$status'));
controller.forward();
}
// ...
}
Running this code produces this output:Next, use addStatusListener() to reverse the animation
at the beginning or the end. This creates a “breathing” effect:App source: animate3

Refactoring with AnimatedBuilder

What's the point?
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:You can accomplish this separation with the help of the
AnimatedBuilder class. An AnimatedBuilder is a
separate class in the render 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:
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() function creates and returns the AnimatedBuilder,
which takes the (Anonymous builder) method and the
LogoWidget object as parameters. The work of rendering the
transition actually happens in the (Anonymous builder)
method, which creates a Container 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 of 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 render tree.
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(). The magic happens in
the build() method, which 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.App source: animate4

Simultaneous animations

What's the point?
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.info 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.
If you were tweening opacity and size in production code,
you’d probably use FadeTransition and SizeTransition
instead.Each tween manages an aspect of the animation. For example:
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, but the constructor for AnimatedWidget
only takes a single Animation object. To solve this problem,
the example creates its own Tween objects and explicitly calculates the
values.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:
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 _opacityTween = Tween(begin: 0.1, end: 1);
static final _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 animations in
Flutter using Tweens, but there are many other classes to explore.
You might investigate the specialized Tween classes,
animations specific to Material Design,
ReverseAnimation,
shared element transitions (also known as Hero animations),
physics simulations and fling() methods.
See the animations landing page
for the latest available documents and examples.

Implicit animations
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.
The following set of resources provide many ways to learn
about implicit animations in Flutter.

Documentation

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.Learn about Animation Basics with Implicit AnimationsLearn about building Custom Implicit Animations with TweenAnimationBuilder

The Boring Show
Watch the Boring Show to follow Google Engineers build apps
from scratch in Flutter. The following episode covers
using implicit animations in a news aggregator app.Learn about implicitly animating the Hacker News 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:Learn about the AnimatedOpacity Flutter WidgetLearn about the AnimatedPadding Flutter WidgetLearn about the AnimatedPositioned Flutter WidgetLearn about the AnimatedSwitcher Flutter Widget

Animate the properties of a 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
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.
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.
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.
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());
});
},
child: const Icon(Icons.play_arrow),
)

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 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());
});
},
child: const Icon(Icons.play_arrow),
),
),
);
}
}

Fade a widget in and out
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
First, create something to fade in and out. For this example,
draw a green box on screen.
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 button 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 VSCode include
the stful snippet to quickly generate this code.
// The StatefulWidget's job is to take data and create a State class.
// In this case, the widget takes a title, and creates a _MyHomePageState.
class MyHomePage extends StatefulWidget {
final String title;
const MyHomePage({
super.key,
required this.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.For more information on working with user input,
see the Gestures section of the cookbook.
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:
AnimatedOpacity(
// If the widget is visible, animate to 0.0 (invisible).
// If the widget is hidden, animate to 1.0 (fully visible).
opacity: _visible ? 1.0 : 0.0,
duration: 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
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 is visible, animate to 0.0 (invisible).
// If the widget is hidden, animate to 1.0 (fully visible).
opacity: _visible ? 1.0 : 0.0,
duration: 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),
),
);
}
}

Hero animations

What you'll learn
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.Examples: This guide provides examples of each hero animation style at
the following links.New to Flutter?
This page assumes you know how to create a layout
using Flutter’s widgets. For more information, see
Building Layouts in Flutter.Terminology:
A Route describes a page or screen in a Flutter app.You can create this animation 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 hero animationsA 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 hero animationsIn 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
or radial hero animations,
read basic structure of a hero animation
to learn how to structure hero animation code,
and behind the scenes to understand
how Flutter performs a hero animation.

Basic structure of a hero animation

What's the point?
Terminology:
If the concept of tweens or tweening is new to you,
see 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:Flutter calculates the tween that animates the Hero’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 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, specified in Hero’s
createRectTween property.
By default, Flutter uses an instance of
MaterialRectArcTween, which animates the
rectangle’s opposing corners along a curved path.
(See 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
The examples in this guide use the following classes to
implement hero animations:

Standard hero animations

What's the point?
Standard hero animation codeEach of the following examples demonstrates flying an image from one
route to another. This guide describes the first example.

What’s going on?
Flying an image from one route to another is easy 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.Create a new Flutter example and
update it using the files from the hero_animation.To run the example:

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:Key information:

HeroAnimation class
The HeroAnimation class creates the source and destination
PhotoHeroes, and sets up the transition.Here’s the code:Key information:

Radial hero animations

What's the point?
Flying a hero from one route to another as it transforms
from a circular shape to a rectangular shape is a slick
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, see Radial transformation
in the Material motion spec.This animation might seem complex (and it is), but you can customize the
provided example to your needs. The heavy lifting is done for you.Radial hero animation codeEach of the following examples demonstrates a radial hero animation.
This guide describes the first example.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.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.Create a new Flutter example and
update it using the files from the
radial_hero_animation GitHub directory.To run the example:

Photo class
The Photo class builds the widget tree that holds the image:Key information:

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:Key information: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:The hero’s flight path still follows an arc,
but the image’s aspect ratio remains constant.

Animate a page route transition
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
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).info 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”.
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,1) to Offset(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:
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.AnimatedWidget Return a SlideTransition
with the Animation and the child widget:
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:
var curve = Curves.ease;
var curveTween = CurveTween(curve: curve);
This new Tween still produces values from 0 to 1. In the next step, it will be
combined the Tween from step 2.

5. Combine the two Tweens
To combine the tweens,
use chain():
const begin = Offset(0.0, 1.0);
const end = Offset.zero;
const curve = Curves.ease;
var 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:
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:Another way to create an Animation with an easing curve is to use a
CurvedAnimation:
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
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;
var 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'),
),
);
}
}

Animate a widget using a 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:

Step 1: Set up an animation controller
Start with a stateful widget called DraggableCard:
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.info Note
Extending SingleTickerProviderStateMixin allows the state object to be a
TickerProvider for the AnimationController. For more information, see the
documentation for TickerProvider.

Step 2: Move the widget using gestures
Make the widget move when it’s dragged, and add an Alignment field to the
_DraggableCardState class:Add a GestureDetector that handles the onPanDown, onPanUpdate, and
onPanEnd callbacks. To adjust the alignment, use a MediaQuery 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:

Step 3: Animate the widget
When the widget is released, it should spring back to the center.Add an Animation field and an _runAnimation method. This
method defines a Tween that interpolates between the point the widget was
dragged to, to the point in the center.
void _runAnimation() {
_animation = _controller.drive(
AlignmentTween(
begin: _dragAlignment,
end: Alignment.center,
),
);
_controller.reset();
_controller.forward();
}
Next, update _dragAlignment when the AnimationController produces a
value:Next, make the Align widget use the _dragAlignment field:
child: Align(
alignment: _dragAlignment,
child: Card(
child: widget.child,
),
),
Finally, update the GestureDetector to manage the animation controller:

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:
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:
/// 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: 30,
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:
onPanEnd: (details) {
_runAnimation(details.velocity.pixelsPerSecond, size);
},
info Note
Now that the animation controller uses a simulation it’s duration argument
is no longer required.

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 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: 30,
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.of(context).size;
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,
),
),
);
}
}

Staggered animations

What you'll learn
Terminology:
If the concept of tweens or tweening is new to you, see 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.
Examples
This guide explains the basic_staggered_animation example.
You can also refer to a more complex example,
staggered_pic_selection.The following video demonstrates the animation performed by
basic_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:After running forward, the animation runs in reverse.New to Flutter?
This page assumes you know how to create a layout using Flutter’s
widgets. For more information, see Building Layouts in Flutter.

Basic structure of a staggered animation

What's the point?
The following diagram shows the Intervals used in the
basic_staggered_animation example.
You might notice the following characteristics:To set up the animation: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. See Curves for
other available pre-defined animation curves.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().

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 Tweens,
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 in the screen.Full code for basic_staggered_animation’s main.dart

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 Tweens’ current values.
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().

Stateful widget: StaggerDemo
The stateful widget, StaggerDemo, creates the AnimationController
(the one who rules them all), 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 in the screen.
The animation runs forward, then backward.

Create a 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:

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.
class Menu extends StatefulWidget {
const Menu({super.key});
@override
State createState() => _MenuState();
}
class _MenuState extends State {
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 _buildListItems() {
final listItems = [];
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
Control of the animation timing requires an
AnimationController.Add the SingleTickerProviderStateMixin
to the MenuState class. Then, declare and
instantiate an AnimationController.
class _MenuState extends State 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.
class _MenuState extends State 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 _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.
class _MenuState extends State with SingleTickerProviderStateMixin {
final List _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
The staggered animation plays as soon as the menu becomes visible.Start the animation in initState().
@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.
List _buildListItems() {
final listItems = [];
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.
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
import 'package:flutter/material.dart';
void main() {
runApp(
const MaterialApp(
home: ExampleStaggeredAnimations(),
debugShowCheckedModeBanner: false,
),
);
}
class ExampleStaggeredAnimations extends StatefulWidget {
const ExampleStaggeredAnimations({
super.key,
});
@override
State createState() =>
_ExampleStaggeredAnimationsState();
}
class _ExampleStaggeredAnimationsState extends State
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 createState() => _MenuState();
}
class _MenuState extends State 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 _animationDuration = _initialDelayTime +
(_staggerTime * _menuTitles.length) +
_buttonDelayTime +
_buttonTime;
late AnimationController _staggeredController;
final List _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 _buildListItems() {
final listItems = [];
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,
),
),
),
),
),
);
}
}

Animations API overview
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 there are two widgets
that help widgets rebuild when animations change value:
AnimatedWidget and AnimatedBuilder.
The first, AnimatedWidget, is most useful for
stateless animated widgets. To use AnimatedWidget,
simply subclass it and implement the build function.
The second, AnimatedBuilder, is useful for more complex widgets
that wish to include an animation as part of a larger build function.
To use AnimatedBuilder, simply construct the widget
and pass it a builder function.

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.

Animation­Controller
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.

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 just 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: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.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 actually 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 synchronised 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 synchronised. If you
start three tickers at different times between two ticks, they will all
nonetheless be synchronised 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.Animatable classes are stateless and immutable.
Tweens
The Tween abstract 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).Tween classes are stateless and immutable.
Composing animatables
Passing an Animatable (the parent) to an Animatable’s
chain() method 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 class (the
parent) and a couple of Curve classes (the forward and reverse
curves) as input, and uses the value of the parent as input to the
curves to determine its output. CurvedAnimation is immutable and
stateless.The ReverseAnimation subclass takes an
Animation class 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 class 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 give itself life.
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 simple case (using forward() or reverse()), the animation controller simply does 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, but
does not stop.When using animateTo(), the animation controller does a linear
interpolation over the given duration from the current value to the
given target. If no duration is given to the method, the default
duration of the controller and the range described by the controller’s
lower bound and upper bound is used to determine the velocity of the
animation.When using fling(), a Force is used to create a specific
simulation which is then used to drive the controller.When using animateWith(), the given simulation is used to drive the
controller.These methods all return the future that the Ticker provides and
which will resolve when the controller next stops or changes
simulation.
Attaching animatables to animations
Passing an Animation (the new parent) to an Animatable’s
animate() method creates a new Animation subclass that acts like
the Animatable but is driven from the given parent.

Accessibility
Ensuring 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 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:Details of these features are discussed below.

Inspecting accessibility support
In addition to testing for these specific topics,
we recommend using automated accessibility scanners:

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.

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.

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:To learn how to find and customize Android’s
accessibility features, view the following video.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:Desktop browsers: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: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).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.Check out the following video demo to see Victor Tsaran,
using 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.
Note that MaterialApp.locale and Localizations.override
don’t affect which voice the screen reader uses.
Usually, the screen reader uses the system voice
except where you explicitly set it with TextSpan.locale.

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:

Building with accessibility in mind
Ensuring 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.

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 example shows how to use the Guideline API on Name Generator.
You created this app as part of the
Write your first Flutter app codelab.
Each button on the app’s main screen serves as a tappable target
with text represented in 18 point.
final SemanticsHandle handle = tester.ensureSemantics();
await tester.pumpWidget(MyApp());
// 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();
You can add Guideline API tests
in test/widget_test.dart of your app directory, or as a separate test
file (such as test/a11y_test.dart in the case of the Name Generator).

Testing accessibility on 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: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 release checklist
Here is a non-exhaustive list of things to consider as you prepare your
app for release.

Learn more
To learn more about Flutter and accessibility, check out
the following articles written by community members:

Internationalizing Flutter apps

What you'll learn
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.

Setting up an internation­alized app: the Flutter_localizations package
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.
As of December 2023, this package supports 115 languages
and language variants.To begin, start by creating a new Flutter application
in a directory of your choice with the flutter create command.To use flutter_localizations,
add the package as a dependency to your pubspec.yaml file,
as well as the intl package:This creates a pubspec.yml file with the following entries:
dependencies:
flutter:
sdk: flutter
flutter_localizations:
sdk: flutter
intl: any
Then import the flutter_localizations library and specify
localizationsDelegates and supportedLocales for
your MaterialApp or CupertinoApp:
import 'package:flutter_localizations/flutter_localizations.dart';
return const MaterialApp(
title: 'Localizations Sample App',
localizationsDelegates: [
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: [
Locale('en'), // English
Locale('es'), // Spanish
],
home: MyHomePage(),
);
After introducing the flutter_localizations package
and adding the previous code,
the Material and Cupertino
packages should now be correctly localized in
one of the 115 supported locales.
Widgets should be adapted to the localized messages,
along with correct left-to-right or right-to-left layout.Try switching the target platform’s locale to
Spanish (es) and the messages should be localized.Apps based on WidgetsApp are similar except that the
GlobalMaterialLocalizations.delegate isn’t needed.The full Locale.fromSubtags constructor is preferred
as it supports scriptCode, 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. GlobalWidgetsLocalizations.delegate
defines the default text direction,
either left-to-right or right-to-left, for the widgets library.More information about these app properties, the types they
depend on, and how internationalized Flutter apps are typically
structured, is covered in this page.

Overriding 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:
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.

Adding your own localized messages
After adding the flutter_localizations package,
you can configure localization.
To add localized text to your application,
complete the following instructions:Add the intl package as a dependency, pulling
in the version pinned by flutter_localizations:Open the pubspec.yaml file and enable the generate flag.
This flag is found in the flutter section in the pubspec file.
# The following section is specific to Flutter.
flutter:
generate: true # Add this line
Add a new yaml file to the root directory of the Flutter project.
Name this file l10n.yaml and include following content:
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:In ${FLUTTER_PROJECT}/lib/l10n,
add the app_en.arb template file. For example:
{
"helloWorld": "Hello World!",
"@helloWorld": {
"description": "The conventional newborn programmer greeting"
}
}
Add another bundle file called app_es.arb in the same directory.
In this file, add the Spanish translation of the same message.
{
"helloWorld": "¡Hola Mundo!"
}
Now, run flutter pub get or flutter run and codegen takes place automatically.
You should find generated files in
${FLUTTER_PROJECT}/.dart_tool/flutter_gen/gen_l10n.
Alternatively, you can also run flutter gen-l10n to
generate the same files without running the app.Add the import statement on app_localizations.dart and
AppLocalizations.delegate
in your call to the constructor for MaterialApp:
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
return const MaterialApp(
title: 'Localizations Sample App',
localizationsDelegates: [
AppLocalizations.delegate, // Add this line
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: [
Locale('en'), // English
Locale('es'), // Spanish
],
home: MyHomePage(),
);
The AppLocalizations class also provides auto-generated
localizationsDelegates and supportedLocales lists.
You can use these instead of providing them manually.
const MaterialApp(
title: 'Localizations Sample App',
localizationsDelegates: AppLocalizations.localizationsDelegates,
supportedLocales: AppLocalizations.supportedLocales,
);
Once the Material app has started,
you can use AppLocalizations anywhere in your app:
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),
),
info 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:
return MaterialApp(
onGenerateTitle: (context) => DemoLocalizations.of(context).title,

Placeholders, plurals, and selects
lightbulb 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.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: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:
"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:
// 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: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”:
"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:
// 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:The next example defines a message that
selects a pronoun based on gender:
"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:
// 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: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 follow text is converted
to a Dart String:The resulting string is as follows:

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
class in the intl package to format
numbers based on the locale and the desired format.The int, double, and number types can use any of the
following NumberFormat constructors: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.arg file:
"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: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”.

Localizing for iOS: Updating the iOS app bundle
Typically, iOS applications define key application metadata,
including supported locales, in an Info.plist file
that is built into the application bundle.
To configure the locales supported by your app,
use the following instructions:Open your project’s ios/Runner.xcworkspace Xcode file.In the Project Navigator, open the Info.plist file
under the Runner project’s Runner folder.Select the Information Property List item.
Then select Add Item from the Editor menu,
and select Localizations from the pop-up menu.Select and expand the newly-created Localizations item.
For each locale your application supports,
add a new item and select the locale you wish to add
from the pop-up menu in the Value field.
This list should be consistent with the languages listed
in the supportedLocales parameter.Once all supported locales have been added, save the file.

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:
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.

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 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():
Locale myLocale = Localizations.localeOf(context);

Specifying the app’s supported­Locales parameter
Although the flutter_localizations library currently supports
115 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:
MaterialApp(
localeResolutionCallback: (
locale,
supportedLocales,
) {
return locale;
},
);

Configuring the l10n.yaml file
The l10n.yaml file allows you to configure the gen-l10n tool
to specify the following:For a full list of options, either run flutter gen-l10n --help
at the command line or refer to the following table:

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 LocalizationsDelegates.
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():This particular Localizations.of() expression is used frequently,
so the MaterialLocalizations class provides a convenient shorthand:

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 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.
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
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
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:
@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:
@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:
const nnLocaleDatePatterns = {
'd': 'd.',
'E': 'ccc',
'EEEE': 'cccc',
'LLL': 'LLL',
// ...
}
const 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:
class _NnMaterialLocalizationsDelegate
extends LocalizationsDelegate {
const _NnMaterialLocalizationsDelegate();
@override
bool isSupported(Locale locale) => locale.languageCode == 'nn';
@override
Future load(Locale locale) async {
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:
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
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:
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.
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;
}

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.With the app’s root directory as the current directory,
generate l10n/intl_messages.arb from 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.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 of the messages files:Windows doesn’t support file name wildcarding.
Instead, list the .arb files that were generated by the
intl_translation:extract_to_arb command.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.If Dart’s intl package is new to you,
check out Using the Dart intl tools.

State management

Topics

State management
info 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.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.

Start thinking declaratively
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 get started guide.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.

Differentiate between ephemeral state 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.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.
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: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.

Simple app state management
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.Even if you get the above code to work,
you would then have to deal
with the following in the MyCart widget: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.
// 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.
// 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:
@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: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
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:
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:
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.
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:
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.
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.
return Consumer(
builder: (context, cart, child) => Stack(
children: [
// Use SomeExpensiveWidget here, without rebuilding every time.
if (child != null) 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.
// DON'T DO THIS
return Consumer(
builder: (context, cart, child) {
return HumongousWidget(
// ...
child: AnotherMonstrousWidget(
// ...
child: Text('Total price: ${cart.totalPrice}'),
),
);
},
);
Instead:
// 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.
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.

Putting it all together
You can check out the example covered in this article.
If you want something simpler,
see what the simple Counter app looks like when
built with provider.By following along with these articles, you’ve greatly
improved your ability to create state-based applications.
Try building an application with provider yourself to
master these skills.

List of state management approaches
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 at the following links,
many of which have been contributed by the Flutter community:

General overview
Things to review before selecting an approach.

Provider

Riverpod
Riverpod works in a similar fashion to Provider.
It offers compile safety and testing without depending on the Flutter SDK.

setState
The low-level approach to use for widget-specific, ephemeral state.

ValueNotifier & InheritedNotifier
An approach using only Flutter provided tooling to update state and notify the UI of changes.

InheritedWidget & InheritedModel
The low-level approach used to communicate between ancestors and children
in the widget tree. This is what provider and many other approaches
use under the hood.The following instructor-led video workshop covers how to
use InheritedWidget:Other useful docs include:

June
A lightweight and modern state management library that focuses on providing
a pattern similar to Flutter’s built-in state management.

Redux
A state container approach familiar to many web developers.

Fish-Redux
Fish Redux is an assembled flutter application framework
based on Redux state management.
It is suitable for building medium and large applications.

BLoC / Rx
A family of stream/observable based patterns.

GetIt
A service locator based state management approach that
doesn’t need a BuildContext.info Note
To learn more, watch this short Package of the Week video on the GetIt package:

MobX
A popular library based on observables and reactions.

Flutter Commands
Reactive state management that uses the Command Pattern
and is based on ValueNotifiers. Best in combination with
GetIt, but can be used with Provider or other
locators too.

Binder
A state management package that uses InheritedWidget
at its core. Inspired in part by recoil.
This package promotes the separation of concerns.

GetX
A simplified reactive state management solution.

states_rebuilder
An approach that combines state management with a
dependency injection solution and an integrated router.
For more information, see the following info:

Triple Pattern (Segmented State Pattern)
Triple is a pattern for state management that uses Streams or ValueNotifier.
This mechanism (nicknamed triple because the stream always uses three
values: Error, Loading, and State), is based on the
Segmented State pattern.For more information, refer to the following resources:

solidart
A simple but powerful state management solution inspired by SolidJS.

flutter_reactive_value
The flutter_reactive_value library might offer the least complex solution for state
management in Flutter. It might help Flutter newcomers add reactivity to their UI,
without the complexity of the mechanisms described before.
The flutter_reactive_value library defines the reactiveValue(BuildContext)
extension method on ValueNotifier. This extension allows a Widget to
fetch the current value of the ValueNotifier and
subscribe the Widget to changes in the value of the ValueNotifier.
If the value of the ValueNotifier changes, Widget rebuilds.

Networking

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 in the Android
manifest (AndroidManifest.xml):

macOS
macOS apps must allow network access in the relevant *.entitlements files.Learn more about 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.

Fetch data from the internet
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.info 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
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:Import the http package.
import 'package:http/http.dart' as http;
If you are deploying to Android, edit your AndroidManifest.xml file to
add the Internet permission.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.

2. Make a network request
This recipe covers how to fetch a sample album from the
JSONPlaceholder using the http.get() method.
Future fetchAlbum() {
return http.get(Uri.parse('https://jsonplaceholder.typicode.com/albums/1'));
}
The http.get() method returns a Future that contains a Response.

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.
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:
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');
}
}
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.
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: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.
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:

Complete example
import 'dart:async';
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
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');
}
}
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();
},
),
),
),
);
}
}

Make 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.
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.
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.'),
};
}
}

Send data to the internet
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
To add the http package as a dependency,
run flutter pub add:Import the http package.
import 'package:http/http.dart' as http;
If you develop for android,
add the following permission inside the manifest tag
in the AndroidManifest.xml file located at android/app/src/main.

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:
import 'dart:convert';
Use the http.post() method to send the encoded data:
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.

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.
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:
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.
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: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.
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
import 'dart:async';
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
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();
},
);
}
}

Update data over the internet
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
To add the http package as a dependency,
run flutter pub add:Import the http package.
import 'package:http/http.dart' as http;

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.
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.

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.
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:
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.

4. 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.
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.

5. 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.
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: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.
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
import 'dart:async';
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
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();
},
),
),
),
);
}
}

Delete data on the internet
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
To add the http package as a dependency,
run flutter pub add:Import the http package.
import 'package:http/http.dart' as http;

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.
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.

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.
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.
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 parse the JSON. After deleting,
// you'll get an empty JSON `{}` response.
// Don't return `null`, otherwise `snapshot.hasData`
// will always return false on `FutureBuilder`.
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 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
import 'dart:async';
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
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 parse the JSON. After deleting,
// you'll get an empty JSON `{}` response.
// Don't return `null`, otherwise `snapshot.hasData`
// will always return false on `FutureBuilder`.
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 delete 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 {
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();
},
),
),
),
);
}
}

Communicate with WebSockets
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 sponsored by Lob.com.
The server sends back the same message you send to it.
This recipe uses the following steps:

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:
final channel = WebSocketChannel.connect(
Uri.parse('wss://echo.websocket.events'),
);

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.
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.
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:
channel.sink.close();

Complete example
import 'package:flutter/material.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 _channel = WebSocketChannel.connect(
Uri.parse('wss://echo.websocket.events'),
);
@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();
}
}

Serialization

Topics

JSON and serialization
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.info
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: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.lightbulb 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.
{
"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.
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: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 ints instead of Strings,
the app won’t compile, instead of crashing at runtime.user.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.
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.
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.info
The cookbook contains a more comprehensive worked example of using
JSON model classes, 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.info
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: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
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: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:

Running the code generation utility
When creating json_serializable classes the first time,
you’ll get errors similar to what is shown in the image below.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.
final userMap = jsonDecode(jsonString) as Map;
final user = User.fromJson(userMap);
The same goes for encoding. The calling API is the same as before.
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:
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:
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:All looks fine now, but if you do a print() on the user object:
Address address = Address('My st.', 'New York');
User user = User('John', address);
print(user.toJson());
The result is:When what you probably want is output like the following:To make this work, pass explicitToJson: true in the @JsonSerializable()
annotation over the class declaration. The User class now looks as follows:
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:

Parse JSON in the background
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.
On Android, this means scheduling work on a different thread.
In Flutter, you can use a separate Isolate.
This recipe uses the following steps:

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:

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.
Future fetchPhotos(http.Client client) async {
return client.get(Uri.parse('https://jsonplaceholder.typicode.com/photos'));
}
info 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.
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>:
// A function that converts a response body into a List.
List parsePhotos(String responseBody) {
final parsed =
(jsonDecode(responseBody) as List).cast>();
return parsed.map((json) => Photo.fromJson(json)).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.
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.

Complete example
import 'dart:async';
import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
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((json) => Photo.fromJson(json)).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 StatelessWidget {
const MyHomePage({super.key, required this.title});
final String title;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(title),
),
body: FutureBuilder>(
future: fetchPhotos(http.Client()),
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);
},
);
}
}

Persistence

Topics

Store key-value data on disk
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.This recipe uses the following steps:info 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:

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.
// 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.
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.
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:

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.
SharedPreferences.setMockInitialValues({
'counter': 2,
});

Complete example
import 'package:flutter/material.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),
),
);
}
}

Read and write 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:To learn more, watch this Package of the Week video
on the path_provider package:info 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.

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:This example stores information in the documents directory.
You can find the path to the documents directory as follows:
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.
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.
Future writeCounter(int counter) async {
final file = await _localFile;
// Write 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.
Future readCounter() async {
try {
final file = await _localFile;
// Read the file
final contents = await file.readAsString();
return int.parse(contents);
} catch (e) {
// If encountering an error, return 0
return 0;
}
}

Complete example
import 'dart:async';
import 'dart:io';
import 'package:flutter/material.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 file
final contents = await file.readAsString();
return int.parse(contents);
} catch (e) {
// If encountering an error, return 0
return 0;
}
}
Future writeCounter(int counter) async {
final file = await _localFile;
// Write 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),
),
);
}
}

Persist data with SQLite
info Note
This guide uses the sqflite package.
This package only supports apps that run on
macOS, iOS, or Android.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.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
To work with SQLite databases, import the sqflite and
path packages.To add the packages as a dependency,
run flutter pub add:Make sure to import the packages in the file you’ll be working in.
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.
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:info 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 {}.
// 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.For more information about the available Datatypes that can be stored in a
SQLite database, see the official SQLite Datatypes documentation.
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:
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}';
}
}
// 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,
);
}
// 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:
// 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),
];
}
// 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:
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],
);
}
// 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 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.
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:
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}';
}
}

Firebase

Introduction
Firebase is a Backend-as-a-Service (BaaS) app development platform
that provides hosted backend services such as a realtime database,
cloud storage, authentication, crash reporting, machine learning,
remote configuration, and hosting for your static files.

Flutter and Firebase resources
Firebase supports Flutter. To learn more,
check out the following resources.

Documentation

Blog Posts
Use Firebase to host your Flutter app on the web

Tutorials
Get to know Firebase for Flutter

Flutter and Firebase community resources
The Flutter community created the following useful resources.

Blog Posts
Building chat app with Flutter and Firebase

Videos

Google APIs
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.info
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
codelab and the
Get Started with Firebase Authentication on Flutter docs.

Overview
To use Google APIs, follow these steps:

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-users
YouTube data, authenticate the user with
youtubeReadonlyScope.
/// Provides the `YouTubeApi` class.
import 'package:googleapis/youtube/v3.dart';

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.

3. Authenticate the user with the required scopes
Use the google_sign_in package to
authenticate users with their Google identity.
Configure signin for each platform you want to support.
/// Provides the `GoogleSignIn` class
import 'package:google_sign_in/google_sign_in.dart';
When instantiating the GoogleSignIn class,
provide the desired scopes as discussed
in the previous section.
final _googleSignIn = GoogleSignIn(
scopes: [YouTubeApi.youtubeReadonlyScope],
);
Follow the instructions provided by
package:google_sign_in
to allow a user to authenticate.Once authenticated,
you must obtain an authenticated HTTP client.

4. Obtain an authenticated HTTP client
The extension_google_sign_in_as_googleapis_auth
package provides an extension method on GoogleSignIn
called authenticatedClient.
import 'package:extension_google_sign_in_as_googleapis_auth/extension_google_sign_in_as_googleapis_auth.dart';
Add a listener to onCurrentUserChanged
and when the event value isn’t null,
you can create an authenticated client.
var httpClient = (await _googleSignIn.authenticatedClient())!;
This Client instance includes the necessary
credentials when invoking Google API classes.

5. Create and use the desired API class
Use the API to create the desired API type and call methods.
For instance:
var youTubeApi = YouTubeApi(httpClient);
var favorites = await youTubeApi.playlistItems.list(
['snippet'],
playlistId: 'LL', // Liked List
);

More information
You might want to check out the following:

Supported deployment platforms
As of Flutter 3.19.3,
Flutter supports deploying apps the following combinations of
hardware architectures and operating system versions.
These combinations are called platforms.Flutter supports platforms in three tiers:Based on these tiers, Flutter supports deploying to the following platforms.Android supports IA32 (x86) in software emulation only.

Desktop support for Flutter
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.info 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:

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: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:Other available flags: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
than 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.
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:To launch your application from the command line,
enter one of the following commands from the top
of the package:info 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:

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: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:

Samples and codelabs
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.

Writing custom platform-specific code
This guide describes how to write custom platform-specific code.
Some platform-specific functionality is available
through existing packages;
see using packages.info Note
The information in this page is valid for most platforms,
but platform-specific code for the web generally uses
JS interoperability or the dart:html library instead.Flutter uses a flexible system that allows you to call
platform-specific APIs in a language that works directly
with those APIs:Flutter’s builtin platform-specific API support
doesn’t rely on code generation,
but rather on a flexible message passing style.
Alternatively, you can use the Pigeon
package for sending structured typesafe messages
with code generation:The Flutter portion of the app sends 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.info Note
This guide addresses using the platform channel mechanism
if you need to use the platform’s APIs in a non-Dart language.
But 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.

Architectural overview: platform channels
Messages are passed between the client (UI)
and host (platform) using platform
channels as illustrated in this diagram:Messages and responses are passed asynchronously,
to ensure the user interface remains responsive.info 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.On the client side, MethodChannel enables sending
messages that correspond to method calls. On the platform side,
MethodChannel on Android (MethodChannelAndroid) and
FlutterMethodChannel on iOS (MethodChanneliOS)
enable receiving method calls and sending back a
result. These classes allow you to develop a platform plugin
with very little ‘boilerplate’ code.info Note
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.

Platform channel data types support and codecs
The standard platform channels use a standard message codec that supports
efficient binary serialization of simple JSON-like values, such as booleans,
numbers, Strings, byte buffers, and Lists and Maps of these
(see StandardMessageCodec for details).
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:

Example: Calling platform-specific code using platform channels
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),
but the platform channel code
is still written in the same way.info 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
Start by creating a new app: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:

Step 2: Create the Flutter platform 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.
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.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.
// 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.
@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:Start Android StudioSelect the menu item File > Open…Navigate to the directory holding your Flutter app,
and select the android folder inside it. Click OK.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.
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:
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:
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:
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler {
call, result ->
// This method is invoked on the main thread.
// TODO
}
And replace with the following:
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:Start Android StudioSelect the menu item File > Open…Navigate to the directory holding your Flutter app,
and select the android folder inside it. Click OK.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.
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:
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:
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:
(call, result) -> {
// This method is invoked on the main thread.
// TODO
}
And replace with the following:
(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:Start Xcode.Select the menu item File > Open….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:Expand Runner > Runner in the Project navigator.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:
@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
let controller : FlutterViewController = window?.rootViewController as! FlutterViewController
let batteryChannel = FlutterMethodChannel(name: "samples.flutter.dev/battery",
binaryMessenger: controller.binaryMessenger)
batteryChannel.setMethodCallHandler({
(call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in
// This method is invoked on the UI thread.
// Handle battery messages.
})
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}
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:
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.
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:Start Xcode.Select the menu item File > Open….Navigate to the directory holding your Flutter app,
and select the ios folder inside it. Click OK.Make sure the Xcode projects builds without errors.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.
#import
#import "GeneratedPluginRegistrant.h"
@implementation AppDelegate
- (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions {
FlutterViewController* controller = (FlutterViewController*)self.window.rootViewController;
FlutterMethodChannel* batteryChannel = [FlutterMethodChannel
methodChannelWithName:@"samples.flutter.dev/battery"
binaryMessenger:controller.binaryMessenger];
[batteryChannel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) {
// This method is invoked on the UI thread.
// TODO
}];
[GeneratedPluginRegistrant registerWithRegistry:self];
return [super application:application didFinishLaunchingWithOptions:launchOptions];
}
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:
- (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.
__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:Run flutter build windows in your project directory once to generate
the Visual Studio solution file.Start Visual Studio.Select Open a project or solution.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:Expand batterylevel > Source Files in the Solution Explorer.Open the file flutter_window.cpp.First, add the necessary includes to the top of the file, just
after #include "flutter_window.h":
#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:
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:
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:
channel.SetMethodCallHandler(
[](const flutter::MethodCall<>& call,
std::unique_ptr> result) {
// TODO
});
And replace with the following:
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:Start Xcode.Select the menu item File > Open….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:Expand Runner > Runner in the Project navigator.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:
import IOKit.ps
Create a FlutterMethodChannel tied to the channel name
samples.flutter.dev/battery in the awakeFromNib method:
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:
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.
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: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.Launch Visual Studio Code.Open the linux directory inside your project.Choose Yes in the prompt asking: Would you like to configure project "linux"?.
This enables C++ autocomplete.Open the file my_application.cc.First, add the necessary includes to the top of the file, just
after #include #include
#include
Add an FlMethodChannel to the _MyApplication struct:
struct _MyApplication {
GtkApplication parent_instance;
char** dart_entrypoint_arguments;
FlMethodChannel* battery_channel;
};
Make sure to clean it up in my_application_dispose:
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:
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:
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 = (UpDevice*)(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(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:
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’.

Typesafe platform channels using Pigeon
The previous example uses MethodChannel
to communicate between the host and client,
which isn’t typesafe. Calling and receiving
messages depends on the host and client declaring
the same arguments and datatypes in order for messages to work.
You can use the Pigeon package as
an alternative to MethodChannel
to generate code that sends messages in a
structured, typesafe manner.With Pigeon, the messaging protocol is defined
in a subset of Dart that then generates messaging
code for Android, iOS, macOS, or Windows. You can find a more complete
example and more information on the pigeon
page on pub.dev.Using Pigeon eliminates the need to match
strings between host and client
for the names and datatypes 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.
Supported languages are Objective-C, Java, Kotlin, C++,
and Swift (with Objective-C interop).

Pigeon example
Pigeon file:
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);
}
Dart usage:
import 'generated_pigeon.dart';
Future onClick() async {
SearchRequest request = SearchRequest(query: 'test');
Api api = SomeApi();
SearchReply reply = await api.search(request);
print('reply: ${reply.result}');
}

Separate platform-specific code from UI code
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 for details.

Publish platform-specific code as a package
To share your platform-specific code with other developers
in the Flutter ecosystem, see publishing packages.

Custom channels and codecs
Besides the above mentioned MethodChannel,
you can also use the more basic
BasicMessageChannel, which supports basic,
asynchronous message passing using a custom message codec.
You can also use the specialized BinaryCodec,
StringCodec, and JSONMessageCodec
classes, or create your own codec.You might also check out an example of a custom codec
in the cloud_firestore plugin,
which is able to serialize and deserialize many more
types than the default types.

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.info Note
On Android, the platform’s main thread is sometimes
called the “main thread”, but it is technically defined
as the UI thread. Annotate methods that need
to be run on the UI thread with @UiThread.
On iOS, this thread is officially
referred to as the main thread.

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.

Executing channel handlers on background threads
In order for a channel’s platform side handler to
execute on a background thread, you must use the
Task Queue API. Currently this feature is only
supported on iOS and Android.In Java:In Kotlin:In Swift:info Note
In release 2.10, the Task Queue API is only available on the master channel
for iOS.In Objective-C:info Note
In release 2.10, the Task Queue API is only available on the master channel
for iOS.

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.In Java:In Kotlin:

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 on the main dispatch queue:In Objective-C:In Swift:

Automatic platform adaptations
info Note
As of the Flutter 3.16 release, Material 3
replaces Material 2 as the default theme
on all Flutter apps that use Material.

Adaptation philosophy
In general, two cases of platform adaptiveness exist: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.info
Preliminary guides addressing case 2
are being added to the UI components section.
You can request additional guides by commenting on issue #8427.

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:

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.

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.

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.

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.

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.

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
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.

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.

Top app bar and navigation bar
Since Android 12, the default UI for top app
bars follows the design guidelines defined in Material 3.
On iOS, an equivalent component called “Navigation Bars”
is defined in Apple’s Human Interface Guidelines (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.But, because app bars are displayed alongside
other content in your page, it’s only recommended to adapt the styling
so long as its 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.

Bottom navigation bars
Since Android 12, the default UI for bottom navigation
bars follow the design guidelines defined in Material 3.
On iOS, an equivalent component called “Tab Bars”
is defined in Apple’s Human Interface Guidelines (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.

Text fields
Since Android 12, text fields follow the
Material 3 (M3) design guidelines.
On iOS, Apple’s Human Interface Guidelines (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.To learn more about adapting text fields, check out
the GitHub discussion on text fields.
You can leave feedback or ask questions in the discussion.

Alert dialog
Since Android 12, the default UI of alert dialogs
(also known as a “basic dialog”) follows the design guidelines
defined in Material 3 (M3).
On iOS, an equivalent component called “alert” is defined in Apple’s
Human Interface Guidelines (HIG).Since alert dialogs are often tightly integrated with the operating system,
their design generally needs to follow the platform conventions.
This is especially important when a dialog is used to request user input
about security, privacy, and destructive operations (e.g., deleting files
permanently). As an exception, a branded alert dialog design can be used on
non-critical user flows to highlight specific information or messages.To implement platform-specific alert dialogs,
you can use Flutter’s AlertDialog widget on Android
and the CupertinoAlertDialog widget on iOS. Below is a code snippet you can
adapt to show a platform-specific alert dialog.To learn more about adapting alert dialogs, check out
the GitHub discussion on dialog adaptations.
You can leave feedback or ask questions in the discussion.

Android

Topics

Add Android devtools for Flutter
To choose the guide to add Android Studio to your Flutter configuration,
click the Getting Started path you followed.

Adding a splash screen to your Android app
Splash screens (also known as launch screens) provide
a simple initial experience while your Android app loads.
They set the stage for your application,
while allowing time for the app engine
to load and your app to initialize.

Overview
warning Warning
If you are experiencing a crash from implementing a splash screen, you
might need to migrate your code. See detailed instructions in the
Deprecated Splash Screen API Migration guide.In Android, there are two separate screens that you can control:
a launch screen shown while your Android app initializes,
and a splash screen that displays while the Flutter experience
initializes.info Note
As of Flutter 2.5, the launch and splash screens have been
consolidated—Flutter now only implements the Android launch screen,
which is displayed until the framework draws the first frame.
This launch screen can act as both an Android launch screen and an
Android splash screen via customization, and thus, is referred to
as both terms. For example of such customization, check out the
Android splash screen sample app.If, prior to 2.5, you used flutter create to create an app,
and you run the app on 2.5 or later, the app might crash.
For more info, see the Deprecated Splash Screen API Migration guide.info 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.

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.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
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.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.

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.The Android app now displays the desired launch screen
while the app initializes.

Android 12
To configure your launch screen on Android 12,
check out Android Splash Screens.As of Android 12, you must use the new splash screen
API in your styles.xml file.
Consider creating an alternate resource file for Android 12 and higher.
Also make sure that your background image is in line with
the icon guidelines;
check out Android Splash Screens for more details.Make sure that
io.flutter.embedding.android.SplashScreenDrawable is
not set in your manifest, and that provideSplashScreen
is not implemented, as these APIs are deprecated.
Doing so causes the Android launch screen to fade smoothly
into the Flutter when the
app is launched and the app might crash.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:
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);
}
}
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)
}
}
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
Android splash screen sample app.

Binding to native Android code using dart:ffi
Flutter mobile and desktop apps can use the
dart:ffi library to call native C APIs.
FFI stands for foreign function interface.
Other terms for similar functionality include
native interface and language bindings.info Note
This page describes using the dart:ffi library
in Android apps. For information on iOS, see
Binding to native iOS code using dart:ffi.
For information in macOS, see
Binding to native macOS code using dart:ffi.
This feature is not yet supported for web plugins.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 Android 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 on both Android and iOS.
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 vs 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. On Android, a dynamically
linked library is distributed as a set of .so (ELF)
files, one for each architecture.A dynamically linked library can be loaded into
Dart via DynamicLibrary.open.API documentation is available from the Dart dev channel:
Dart API reference documentation.On Android, only dynamic libraries are supported
(because the main executable is the JVM,
which we don’t link to statically).

Create an FFI plugin
To create an FFI plugin called “native_add”,
do the following:info 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 will create 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)).On Android, the native_add/android/build.gradle links the code.The native code is invoked from dart in lib/native_add_bindings_generated.dart.The bindings are generated with package:ffigen.

Other use cases

Platform library
To link against a platform library,
use the following instructions:Load the library using DynamicLibrary.open.
For example, to load OpenGL ES (v3):You might need to update the Android manifest
file of the app or plugin if indicated by
the documentation.
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
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).
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:

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 device into a
temporary location and then loading.
APKs are additionally packed in transit—that’s
why you should be looking at download size.Flutter APKs by default don’t follow these guidelines
and compress libflutter.so and libapp.so—this
leads to smaller APK size but larger on device size.Shared objects from third parties can change this default
setting with android:extractNativeLibs="true" in their
AndroidManifest.xml and stop the compression of libflutter.so,
libapp.so, and any user-added shared objects.
To re-enable compression, override the setting in
your_app_name/android/app/src/main/AndroidManifest.xml
in the following way.

Other Resources
To learn more about C interoperability, check out these videos:

Hosting native Android views in your Flutter app with 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.info 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,
see Hosting native iOS views.Flutter supports two modes:
Hybrid composition and virtual displays.Which one to use depends on the use case.
Let’s take a look:Hybrid composition
appends the native android.view.View to the view hierarchy.
Therefore, keyboard handling, and accessibility work out of the box.
Prior to Android 10, this mode might significantly
reduce the frame throughput (FPS) of the Flutter UI.
For more context, see Performance.Virtual displays
render the android.view.View instance to a texture,
so it’s not embedded within the Android Activity’s view hierarchy.
Certain platform interactions such as keyboard handling
and accessibility features might not work.To create a platform view on Android,
use the following steps:

On the Dart side
On the Dart side, create a Widget
and add one of the following build implementations.
Hybrid composition
In your Dart file,
for example native_view_example.dart,
use the following instructions:Add the following imports:
import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart';
Implement a build() method:
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, see the API docs for:
Virtual display
In your Dart file,
for example native_view_example.dart,
use the following instructions:Add the following imports:
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
Implement a build() method:
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, see the API docs for:

On the platform side
On the platform side, use the standard
io.flutter.plugin.platform package
in either Java or Kotlin: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):Create a factory class that creates an instance of the
NativeView created earlier
(for example, NativeViewFactory.kt):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):For plugin registration,
modify the plugin’s main class
(for example, PlatformViewPlugin.kt):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):Create a factory class that creates an
instance of the NativeView created earlier
(for example, NativeViewFactory.java):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):For plugin registration,
modify the plugin’s main file
(for example, PlatformViewPlugin.java):For more information, see the API docs for:Finally, modify your build.gradle file
to require one of the minimal Android SDK versions:
Manual view invalidation
Certain Android Views do not invalidate themselves when their content changes.
Some example views include SurfaceView and SurfaceTexture.
When your Platform View includes these views you are required to
manually invalidate the view after they have been drawn to
(or more specifically: after the swap chain is flipped).
Manual view invalidation is done by calling invalidate on the View
or one of its parent views.

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 the main platform thread is rarely blocked.While a platform view is rendered with hybrid composition,
the Flutter UI is composed from the platform thread,
which competes with other tasks like handling OS or plugin messages.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.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.For complex cases, there are some techniques that
can be used to mitigate these 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.For more information, see:

Restore state on Android
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.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.

Overview
You can enable state restoration with just a few tasks:Define a restorationId or a restorationScopeId
for all widgets that support it,
such as TextField and ScrollView.
This automatically enables built-in state restoration
for those widgets.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.If you use any Navigator API (like push, pushNamed, and so on)
migrate to the API that has “restorable” in the name
(restorablePush, resstorablePushNamed, and so on)
to restore the navigation stack.Other considerations:Providing a restorationId 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
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.The VeggieSeasons example (listed under “Other resources” below)
implements navigation with the go_router package.
Setting the restorationId
values occur in the lib/screens classes.

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 Warning
Don’t forget to reenable
storing state on your device once you are
finished with testing!

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.You might want to check out packages on pub.dev that
perform state restoration, such as statePersistence.

Targeting ChromeOS with Android
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: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)To run these from the command line, use the following command:Sample output for this command might look like:

iOS

Topics

Add iOS devtools for Flutter
To choose the guide to add iOS devtools to your Flutter configuration,
click the Getting Started path you followed.

Leveraging Apple's System APIs and 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 or MapKit.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.
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 need,
you can create your own or use platform channels directly in your project.
To learn more, see 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
Supports both Google Play Store on Android and Apple App Store on iOS. ↩Adds Google Pay payments on Android and Apple Pay payments on iOS. ↩Uses Firebase Cloud Messaging
and integrates with APNs. ↩Includes sensors like accelerometer, gyroscope, etc. ↩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. ↩ ↩2Uses the OpenWeatherMap API.
Other packages exist that can pull from different weather APIs. ↩

Adding 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.All apps submitted to the Apple App Store
must provide a launch screen
with an Xcode storyboard.
Customize the launch screen
The default Flutter template includes an Xcode
storyboard named LaunchScreen.storyboard
that can be customized 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.

Adding an iOS App Clip target
error 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.warning 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.

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.1Click 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.2Select the App Clip type for your new target.2.3Enter 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.4In the following dialog,
activate the new scheme for the new target.2.5Back in the project settings, open the Build Phases tab.
Drag Embedded App Clips to above Thin Binary.

Step 3 - Remove unneeded files
3.1In the Project Navigator, in the newly created App Clip group,
delete everything except Info.plist and
.entitlements.lightbulb 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.2If 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.1Back 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.2In the App Clip group’s Info.plist file, set:

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.
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.

Step 6 - Add App Clip associated domains
This is a standard step for App Clip development.
See the official Apple documentation.6.1Open the .entitlements file.
Add an Associated Domains Array type.
Add a row to the array with appclips:.6.2The 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.3Back 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.1For the Swift target,
set the Objective-C Bridging Header
build setting to Runner/Runner-Bridging-Header.hIn other words,
the same as the main app target’s build settings.7.2Now 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: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.3Press the + sign and select New Run Script Phase again.
Leave it as the last phase.This time, add: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.1Open the Podfile for your Flutter project
or add-to-app host project.For full-Flutter apps, replace the following section:with:At the top of the file,
also uncomment platform :ios, '12.0' and set the
version to the lowest of the two target’s iOS
Deployment Target.For add-to-app, add to:with:8.2From the command line,
enter your Flutter project directory
and then install the pod:

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.

Debugging, hot reload
Unfortunately flutter attach cannot auto-discover
the Flutter session in an App Clip due to
networking permission restrictions.In order to debug your App Clip and use functionalities
like hot reload, you must look for the Observatory URI
from the console output in Xcode after running.You must then copy and paste it back into the
flutter attach command to connect.For example:

Adding iOS app extensions
iOS app extensions allow you to expand functionality
outside your app. Your app could appear as a home screen widget,
or you can make portions of your app available within other apps.To learn more about app extensions, check out
Apple’s documentation.info Note
If you experience a build error when building an
iOS app that includes an app extension, be
aware that there is an open bug. The workaround
involves changing the order of the build process.
For more information, check out
Issue #9690 and Issue #135056.

How do you add an app extension to your Flutter app?
To add an app extension to your Flutter app,
add the extension point target to your Xcode project.In Xcode, select File -> New -> Target from the menu bar.To learn how to add a home screen widget to your iOS device,
check out the
Adding a Home Screen Widget to your Flutter app
codelab.

How do Flutter apps interact with App Extensions?
Flutter apps interact with 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.

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
or search pub.dev.

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.info Note
You must be signed in to your Apple Developer account.To add a target to an App Group:Choose which App Group you want to add the target from
one of two options: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.

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.

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.

Creating app extension UIs with Flutter
Some app extensions display a user interface.For example, share extensions allow users to conveniently
share content with other apps,
such as sharing a picture to create
a new post on a social media app.As of the 3.16 release, you can build
Flutter UI for an app extension,
though you must use an extension-safe
Flutter.xcframework and embed the
FlutterViewController as described in
the following section.info Note
Due to the memory limitations of app extensions,
use Flutter to build an app extension UI for extension
types that have memory limits larger than 100MB.
For example, Share extensions have a 120MB memory limit.In addition, Flutter uses extra memory in debug mode.
Therefore, Flutter doesn’t fully support running
app extensions in debug mode on physical devices
when used to build extension UI; it might run out of memory.
As an alternative,
use an iOS simulator to test your extension in debug mode.Locate the extension-safe Flutter.xcframework file,
at /bin/cache/artifacts/engine/ios/extension_safe/Flutter.xcframework.Drag and drop the Flutter.xcframework file into your
share extension’s frameworks and libraries list.
Make sure the embed column says “Embed & Sign”.Open the Flutter app project settings in Xcode
to share build configurations.(Optional) Replace any storyboard files with an extension class, if needed.Embed the FlutterViewController as described in
Adding a Flutter Screen. For example, you can display a
specific route in your Flutter app within a share extension.

Test extensions
Testing extensions on simulators and physical devices
have slightly different procedures.

Test on a simulator

Test on a physical device
You can use the following procedure or the
Testing on simulators instructions
to test on physical devices.

Tutorials
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
codelab.

Binding to native iOS code using dart:ffi
Flutter mobile and desktop apps can use the
dart:ffi library to call native C APIs.
FFI stands for foreign function interface.
Other terms for similar functionality include
native interface and language bindings.info Note
This page describes using the dart:ffi library
in iOS apps. For information on Android, see
Binding to native Android code using dart:ffi.
For information in macOS, see
Binding to native macOS code using dart:ffi.
This feature is not yet supported for web plugins.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 iOS 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 on iOS.
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 vs 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. On iOS, the dynamically linked
library is distributed as a .framework folder.A dynamically linked library can be loaded into
Dart using DynamicLibrary.open.API documentation is available from the Dart dev channel:
Dart API reference documentation.

Create an FFI plugin
To create an FFI plugin called “native_add”,
do the following:info 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 will create 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)).On iOS, the native_add/ios/native_add.podspec links the code.The native code is invoked from dart in lib/native_add_bindings_generated.dart.The bindings are generated with package:ffigen.

Other use cases

iOS and macOS
Dynamically linked libraries are automatically loaded by
the dynamic linker 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
To link against a platform library,
use the following instructions:
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
To link directly to source code,
use the following instructions:Add the following prefix to the
exported symbol declarations to ensure they
are visible to Dart:C/C++/Objective-CSwift
Compiled (dynamic) library
To link to a compiled dynamic library,
use the following instructions:
Open-source third-party library
To create a Flutter plugin that includes both
C/C++/Objective-C and Dart code,
use the following instructions:The native code is then statically linked into
the application binary of any app that uses
this plugin.
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:warning 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.

Stripping iOS symbols
When creating a release archive (IPA),
the symbols are stripped by Xcode.

Other Resources
To learn more about C interoperability, check out these videos:

Hosting native iOS views in your Flutter app with 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.info 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.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:Add the following imports:
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
Implement a build() method:
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.

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, FLNativeView.swift:Finally, register the platform view.
This can be done in an app or a plugin.For app registration,
modify the App’s AppDelegate.swift:For plugin registration,
modify the plugin’s main file
(for example, FLPlugin.swift):In Objective-C, add the headers for the factory and the platform view.
For example, as shown in FLNativeView.h: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:Finally, register the platform view.
This can be done in an app or a plugin.For app registration,
modify the App’s AppDelegate.m:For plugin registration,
modify the main plugin file
(for example, FLPlugin.m):For more information, see the API docs for:

Putting it together
When implementing the build() method in Dart,
you can use defaultTargetPlatform
to detect the platform, and decide which widget to use:
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.
default:
throw UnsupportedError('Unsupported platform view');
}
}

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 the main platform thread is rarely blocked.When a platform view is rendered with hybrid composition,
the Flutter UI is composed from the platform thread.
The platform thread competes with other tasks
like handling OS or plugin messages.When an iOS PlatformView is on screen, the screen refresh rate is
capped at 80fps to avoid rendering janks.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.

iOS debugging
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.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.

Restore state on 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 kill 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
and the VeggieSeasons code sample.

Linux

Topics

Add Linux devtools for Flutter
To choose the guide to add Linux devtools to your Flutter configuration,
click the Getting Started path you followed.

Building Linux apps with Flutter
This page discusses considerations unique to building
Linux apps with Flutter, including shell integration
and preparation of apps for distribution.

Integrating with Linux
The Linux programming interface,
comprising library functions and system calls,
is designed around the C language and ABI.
Fortunately, Dart provides dart:ffi,
which is designed to enable Dart programs
to efficiently call into C libraries.
FFI 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.Many apps will 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
with a focus on enabling Dart and Flutter on Linux,
including support for desktop notifications,
dbus, network management, and Bluetooth.More generally, many other packages support Linux,
including common packages such as url_launcher,
shared_preferences, file_selector, and
path_provider.

Preparing Linux apps for distribution
The executable binary can be found in your project under
build/linux//bundle/. Alongside your
executable binary in the bundle directory there are
two directories:In addition to these files, your application also
relies on various operating system libraries that
it’s been compiled against.
You can see the full list by running ldd
against your application. For example,
assuming you have a Flutter desktop application
called linux_desktop_test, you could inspect
the system libraries it depends upon as follows:To wrap up this application for distribution
you need to include everything in the bundle directory,
and make sure the Linux system you are installing
it on has all of the system libraries required.
This could be as simple as:For information on publishing a Linux application
to the Snap Store, see
Build and release a Linux application to the Snap Store.

macOS

Topics

Add macOS devtools for Flutter
To choose the guide to add macOS devtools to your Flutter configuration,
click the Getting Started path you followed.

Building macOS apps 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.

Building macOS apps
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.
As of macOS 10.14.5, you need to notarize
your macOS application before distributing
it outside of 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.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
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.

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: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_chooser 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: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.

Hardened Runtime
If you choose to distribute your application outside
of the App Store, you need to notarize your application
for compatibility with macOS 10.15+.
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.

Binding to native macOS code using dart:ffi
Flutter mobile and desktop apps can use the
dart:ffi library to call native C APIs.
FFI stands for foreign function interface.
Other terms for similar functionality include
native interface and language bindings.info Note
This page describes using the dart:ffi library
in macOS desktop apps.
For information on Android, see
Binding to native Android code using dart:ffi.
For information on iOS, see
Binding to native iOS code using dart:ffi.
This feature is not yet supported for web plugins.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 macOS 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 on macOS.
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 vs 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. On macOS, the dynamically linked
library is distributed as a .framework folder.A dynamically linked library can be loaded into
Dart using DynamicLibrary.open.API documentation is available from the Dart dev channel:
Dart API reference documentation.

Create an FFI plugin
If you already have a plugin, skip this step.To create a plugin called “native_add”,
do the following:info 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 will create 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)).On iOS, the native_add/macos/native_add.podspec links the code.The native code is invoked from dart in lib/native_add_bindings_generated.dart.The bindings are generated with package:ffigen.

Other use cases

iOS and macOS
Dynamically linked libraries are automatically loaded by
the dynamic linker 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
To link against a platform library,
use the following instructions:
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
To link directly to source code,
use the following instructions:Add the following prefix to the
exported symbol declarations to ensure they
are visible to Dart:C/C++/Objective-CSwift
Compiled (dynamic) library
To link to a compiled dynamic library,
use the following instructions:
Compiled (dynamic) library (macOS)
To add a closed source library to a
Flutter macOS Desktop app,
use the following instructions:

Other Resources
To learn more about C interoperability, check out these videos:

Web support for Flutter
Flutter’s web support delivers the same experiences on the web as on mobile.
Building on the portability of Dart, the power of the web platform and the
flexibility of the Flutter framework, you can now build apps for iOS, Android,
and the browser from the same codebase. You can compile existing Flutter code
written in Dart into a web experience because it is exactly the same Flutter
framework and web is just another device target for your app.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.While you can do a lot on the web,
Flutter’s web support is most valuable in the
following scenarios: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.For a glimpse into how to migrate your mobile app to web, see
the following video:

Resources
The following resources can help you get started:

Add Web devtools for Flutter
To choose the guide to add Web devtools to your Flutter configuration,
click the Getting Started path you followed.

Building a web application with Flutter
This page covers the following steps for getting started with web support:

Requirements
To create a Flutter app with web support,
you need the following software:For more information, see the web FAQ.

Create a new project with web support
You can use the following steps
to create a new project with web support.

Set up
Run the following commands to use the latest version of the Flutter SDK:warning Warning
Running flutter channel stable replaces your current version of Flutter
with the stable version and can take time if your connection is slow.
After this, running flutter upgrade upgrades your install to the latest
stable. Returning to another channel (beta or master) requires calling
flutter channel explicitly.If Chrome is installed,
the flutter devices command outputs a Chrome device
that opens the Chrome browser with your app running,
and a Web Server that provides the URL serving the app.In your IDE, you should see Chrome (web) in the device pulldown.

Create and run
Creating a new project with web support is no different
than creating a new Flutter project for other platforms.
IDE
Create a new app in your IDE and it automatically
creates iOS, Android, desktop, and web versions of your app.
From the device pulldown, select Chrome (web)
and run your app to see it launch in Chrome.
Command line
To create a new app that includes web support
(in addition to mobile support), run the following commands,
substituting my_app with the name of your project:To serve your app from localhost in Chrome,
enter the following from the top of the package:info Note
If there aren’t any other connected devices,
the -d chrome is optional.The flutter run command launches the application using the
development compiler in a Chrome browser.warning Warning
Hot reload is not supported in a web browser
Currently, Flutter supports hot restart,
but not hot reload in a web browser.

Build
Run the following command to generate a release build:If you receive a not supported error, run the following command:A release build uses dart2js
(instead of the development compiler)
to produce a single JavaScript file main.dart.js.
You can create a release build using release mode
(flutter run --release) or by using flutter build web.
This populates a build/web directory
with built files, including an assets directory,
which need to be served together.You can also include --web-renderer html or --web-renderer canvaskit to
select between the HTML or CanvasKit renderers, respectively. For more
information, see Web renderers.To learn more, see
Build and release a web app.

Add web support to an existing app
To add web support to an existing project
created using a previous version of Flutter,
run the following command
from your project’s top-level directory:If you receive a not supported error, run the following command:

Web FAQ

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: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 does not align with what search
engines need to properly index. For web content that is static or document-like,
we recommend using HTML—just like we do on flutter.dev,
dart.dev, and pub.dev. You should also
consider separating your primary application experience—created in Flutter—from
your landing page, marketing content, and help content—created using
search-engine optimized HTML.

How do I create an app that also runs on the web?
See building a web app with Flutter.

Does hot reload work with a web app?
No, but you can use hot restart. Hot restart is a fast way of seeing your
changes without having to relaunch your web app and wait for it to compile and
load. This works similarly to the hot reload feature for Flutter mobile
development. The only difference is that hot reload remembers your state and hot
restart doesn’t.

How do I restart the app running in the browser?
You can either use the browser’s refresh button,
or you can enter “R” in the console where
“flutter run -d chrome” is running.

Which web browsers are supported by Flutter?
Flutter web apps can run on the following browsers: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, see the documentation for conditional imports
on dart.dev.

Does Flutter web support concurrency?
Dart’s concurrency support via isolates
is not currently supported in Flutter web.Flutter web apps can potentially work around this
by using web workers,
although no such support is built in.

How do I embed a Flutter web app in a web page?
You can embed a Flutter web app,
as you would embed other content,
in an iframe tag of an HTML file.
In the following example, replace “URL”
with the location of your hosted HTML page:If you encounter problems, please file an issue.

How do I debug a web app?
Use Flutter DevTools for the following tasks:Use Chrome DevTools for the following tasks:

How do I test a web app?
Use widget tests or integration tests. To learn more about
running integration tests in a browser, see the Integration testing page.

How do I deploy a web app?
See Preparing a web app for release.

Does Platform.is work on the web?
Not currently.

Web renderers
When running and building apps for the web, you can choose between two different
renderers. This page describes both renderers and how to choose the best one for
your needs. The two renderers are:

Command line options
The --web-renderer command line option takes one of three values, auto,
html, or canvaskit.This flag can be used with the run or build subcommands. For example:This flag is ignored when a non-browser (mobile or desktop) device
target is selected.

Runtime configuration
To override the web renderer at runtime:The web renderer can’t be changed after the Flutter engine startup process
begins in main.dart.js.info Note
As of Flutter 3.7.0, setting a window.flutterWebRenderer
(an approach used in previous releases) displays a
deprecation notice in the JS console. For more information,
check out Customizing web app initialization.

Choosing which option to use
Choose the auto option (default) if you are optimizing for download size on
mobile browsers and optimizing for performance on desktop browsers.Choose the html option if you are optimizing download size over performance on
both desktop and mobile browsers.Choose the canvaskit option if you are prioritizing performance and
pixel-perfect consistency on both desktop and mobile browsers.

Examples
Run in Chrome using the default renderer option (auto):Build your app in release mode, using the default (auto) option:Build your app in release mode, using just the CanvasKit renderer:Run your app in profile mode using the HTML renderer:

Customizing web app initialization
You can customize how a Flutter app is initialized on the web
using the _flutter.loader JavaScript API provided by flutter.js.
This API can be used to display a loading indicator in CSS,
prevent the app from loading based on a condition,
or wait until the user presses a button before showing the app.The initialization process is split into the following stages:This page shows how to customize the behavior
at each stage of the initialization process.

Getting started
By default, the index.html file
generated by the flutter create command
contains a script tag
that calls loadEntrypoint from the flutter.js file:info Note
In Flutter 2.10 or earlier,
this script doesn’t support customization.
To upgrade your index.html file to the latest version,
see Upgrading an older project.The loadEntrypoint function calls the onEntrypointLoaded callback
once the Service Worker is initialized, and the main.dart.js entrypoint
has been downloaded and run by the browser. Flutter also calls
onEntrypointLoaded on every hot restart during development.The onEntrypointLoaded callback receives an engine initializer object as
its only parameter. Use the engine initializer to set the run-time
configuration, and start the Flutter Web engine.The initializeEngine() function returns a Promise
that resolves with an app runner object. The app runner has a
single method, runApp(), that runs the Flutter app.

Customizing web app initialization
In this section,
learn how to customize each stage of your app’s initialization.

Loading the entrypoint
The loadEntrypoint method accepts these parameters:The serviceWorker JavaScript object accepts the following properties:

Initializing the engine
As of Flutter 3.7.0, you can use the initializeEngine method to
configure several run-time options of the Flutter web engine through a
plain JavaScript object.You can add any of the following optional parameters:info Note
Some of the parameters described above might have been overridden
in previous releases by using properties in the window object.
That approach is still supported, but displays a deprecation
notice in the JS console, as of Flutter 3.7.0.
Engine configuration example
The initializeEngine method lets you pass any of the configuration
parameters described above to your Flutter app.Consider the following example.Your Flutter app should target an HTML element with id="flutter_app" and
use the canvaskit renderer. The resulting JavaScript code would resemble
the following:For a more detailed explanation of each parameter, take a look at the
“Runtime parameters” documentation section of the configuration.dart
file of the web engine.
Skipping this step
Instead of calling initializeEngine() on the engine initializer (and then
runApp() on the application runner), you can call autoStart() to
initialize the engine with its default configuration, and then start the app
immediately after the initialization is complete:

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:For a more practical example using CSS animations,
see the initialization code for the Flutter Gallery.

Upgrading an older project
If your project was created in Flutter 2.10 or earlier,
you can create a new index.html file
with the latest initialization template by running
flutter create as follows.First, remove the files from your /web directory.Then, from your project directory, run the following:

Display images on the web
The web supports the standard Image widget 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
This section summarizes the technologies available
across Flutter and the web,
on which the solutions below are based on.

Images in Flutter
Flutter offers the Image widget as well as the low-level
dart:ui/Image class for rendering images.
The Image widget has enough functionality for most use-cases.
The dart:ui/Image class can be used in
advanced situations where fine-grained control
of the image is needed.

Images on the web
The web offers several methods for displaying images.
Below are some of the common ones: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 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)
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 or 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.When using , , or ,
the browser automatically blocks access to pixels
when it knows that an image is coming from another site
and the CORS policy disallows access to data.WebGL requires access to the image data in order
to be able to render the image. Therefore,
images to be rendered using WebGL must only come from servers
that have a CORS policy configured to work with
the domain that serves your application.

Flutter renderers on the web
Flutter offers a choice of two renderers on the web:Because the HTML renderer uses the
element it can display images from
arbitrary sources. However,
this places the following limitations on what you
can do with them:The CanvasKit renderer implements Flutter’s image API fully.
However, it requires access to image pixels to do so,
and is therefore subject to the CORS policy.

Solutions

In-memory, asset, and same-origin network images
If the app has the bytes of the encoded image in memory,
provided as an asset, 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, Image.asset, and Image.network
in both HTML and CanvasKit modes.

Cross-origin images
The HTML renderer can load cross-origin images
without extra configuration.CanvasKit requires that the app gets the bytes of the encoded image.
There are several ways to do this, discussed below.
Host your 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 Access-Control-Allow-Origin
header in the firebase.json file.
Lack control over the image server? Use a CORS proxy.
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:
Use in a platform view.
Flutter supports embedding HTML inside the app using
HtmlElementView. Use it to create an
element to render the image from another domain.
However, do keep in mind that this comes with the
limitations explained in Flutter renderers on the web.

Windows

Topics

Add Windows devtools for Flutter
To choose the guide to add Visual Studio to your Flutter configuration,
click the Getting Started path you followed.

Building Windows apps 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.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.

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.

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: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: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:Run flutter build windows to create the build\ directory.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.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.To generate the necessary dependencies,
run Build > Build SolutionYou can also press/
Ctrl + Shift + B.To run the Windows app from Visual Studio, go to Debug > Start Debugging.You can also press F5.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:

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.
For an example of using the msix package
from a Flutter desktop app,
see the Desktop Photo Search sample.
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.

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: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:At this point if desired it would be relatively simple to
add this folder to a Windows installer such as Inno Setup, WiX, etc.

Using packages
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.What is the difference between a package
and a plugin? A plugin is a type of
package—the full designation is plugin package,
which is generally shortened to plugin.Existing packages enable many use cases—for example,
making network requests (http),
navigation/route handling (go_router),
integration with device APIs
(url_launcher and battery_plus),
and using third-party platform SDKs like Firebase
(FlutterFire).To write a new package, see developing packages.
To add assets, images, or fonts,
whether stored in files or packages,
see Adding assets and images.

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.

Adding a package dependency to an app
To add the package, css_colors, to an app:

Adding a package dependency to an app using flutter pub add
To add the package, css_colors, to an app:

Removing a package dependency to an app using flutter pub remove
To remove the package, css_colors, to an app:The Installing tab,
available on any package page on pub.dev,
is a handy reference for these steps.For a complete example,
see the css_colors example below.

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.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: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:CocoaPods doesn’t currently offer dependency
override functionality.

Developing new packages
If no package exists for your specific use case,
you can write a custom package.

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.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.This syntax means the same as the one noted in the first bullet.To learn more, check out the package versioning guide.

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.

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:Finally, use the ref argument to pin the dependency to a
specific git commit, branch, or tag. For more details, see
Package dependencies.

Examples
The following examples walk through the necessary steps for
using packages.

Example: Using the css_colors package
The css_colors package
defines color constants for CSS colors, so use the constants
wherever the Flutter framework expects the Color type.To use this package:Create a new project called cssdemo.Open pubspec.yaml, and add the css-colors dependency:Run flutter pub get in the terminal,
or click Get Packages in VS Code.Open lib/main.dart and replace its full contents with:
import 'package:css_colors/css_colors.dart';
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(
home: DemoPage(),
);
}
}
class DemoPage extends StatelessWidget {
const DemoPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(body: Container(color: CSSColors.orange));
}
}

Example: Using the url_launcher package to launch the browser
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:Create a new project called launchdemo.Open pubspec.yaml, and add the url_launcher dependency:Run flutter pub get in the terminal,
or click Get Packages get in VS Code.Open lib/main.dart and replace its full contents with the
following:
import 'package:flutter/material.dart';
import 'package:path/path.dart' as p;
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(p.toUri('https://flutter.dev'));
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: ElevatedButton(
onPressed: launchURL,
child: const Text('Show Flutter homepage'),
),
),
);
}
}
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.

Developing packages & plugins

Package introduction
Packages enable the creation of modular code that can be shared easily.
A minimal package consists of the following:info Note
For a list of dos and don’ts when writing an effective plugin,
see the Medium article by Mehmet Fidanboylu,
Writing a good plugin.

Package types
Packages can contain more than one kind of content: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 Medium article by Harry Terkelsen,
How to Write a Flutter Web Plugin, Part 1.

Developing Dart packages
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:This creates a package project in the hello
folder with the following content:

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
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.

Federated plugins
Federated plugins are a way of splitting support for
different platforms into separate packages.
So, a 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 packages:
Endorsed federated plugin
Ideally, when adding a platform implementation to
a 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 file. So, the developer
must include both the foobar dependency and
the foobar_windows dependency in order to achieve
full functionality.For more information on federated plugins,
why they are useful, and how they are
implemented, see the Medium article by Harry Terkelsen,
How To Write a Flutter Web Plugin, Part 2.

Specifying a plugin’s supported 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: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:
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:
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: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: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:

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.Use the -a option to specify the language for android
or the -i option to specify the language for ios.
Please choose one of the following:This creates a plugin project in the hello folder
with the following specialized content: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. For example:

Step 2: Implement the 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.Then use the following steps: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).Then use the following steps: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 hello/darwin/Classes instead.)You can run the example app by pressing the run (▶) button.
Add CocoaPod dependencies
Use the following instructions to add HelloPod with the version 0.0.1:Specify dependency at the end of ios/hello.podspec:For private pods, refer to Private CocoaPods to ensure repo access:Installing the pluginThe pod should appear in the installation summary.
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: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).Then use the following steps: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: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: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.info 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: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:
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: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.

Developing FFI plugin packages
If you want to develop a package that calls into native APIs using
Dart’s FFI, you need to develop an FFI plugin package.Both FFI plugin packages and (non-FFI) plugin packages support
bundling native code, but FFI plugin packages do not support
method channels and do include method channel registration code.
If you want to implement a plugin that uses both method channels
and FFI, use a (non-FFI) plugin. You can chose per platform to
use an FFI or (non-FFI) plugin.FFI plugin packages were introduced in Flutter 3.0, if you’re
targeting older Flutter versions, you can use a (non-FFI) plugin.

Step 1: Create the package
To create a starter FFI plugin package,
use the --template=plugin_ffi flag with flutter create:This creates an FFI plugin project in the hello
folder with the following specialized content:lib: The Dart code that defines the API of the plugin,
and which calls into the native code using dart:ffi.src: The native source code, and a CMakeLists.txt
file for building that source code into a dynamic library.platform folders (android, ios, windows, etc.): The
build files for building and bundling the native code
library with the platform application.

Step 2: Building and bundling native code
The pubspec.yaml specifies FFI plugins as follows:This configuration invokes the native build
for the various target platforms and bundles
the binaries in Flutter applications using these FFI plugins.This can be combined with dartPluginClass,
such as when FFI is used for the
implementation of one platform in a federated plugin:A plugin can have both FFI and method channels:The native build systems that are invoked by FFI
(and method channels) plugins are:

Step 3: 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.Regenerate the bindings by running the following:

Step 4: 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:

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.If you wish to generate API documentation locally on
your development machine, use the following commands:Change directory to the location of your package:Tell the documentation tool where the
Flutter SDK is located (change the following commands to reflect
where you placed it):Run the dart doc tool
(included as part of the Flutter SDK), as follows: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:Here is another example of a well-organized license file:Here is an example of a poorly-organized license file:Another example of a poorly-organized license file:

Publishing your package
lightbulb 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:Next, run the publish command in dry-run mode
to see if everything passes analysis:The next step is publishing to pub.dev,
but be sure that you are ready because
publishing is forever:For more details on publishing, see the
publishing docs on dart.dev.

Handling package interdependencies
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: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: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.podspec: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, see the
CocoaPods Documentation on them.

Web
All web dependencies are handled by the pubspec.yaml
file like any other Dart package.

Flutter Favorite program
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.info 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:

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: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:

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:

Flutter Favorites
You can see the complete list of
Flutter Favorite packages on pub.dev.

Testing Flutter apps
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.info Note
For hands-on practice of testing Flutter apps, see the
How to test a Flutter app codelab.Automated testing falls into a few categories: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.

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.
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.info 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.
Recipes
An introduction to unit testingMock dependencies using Mockito

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
An introduction to widget testingFind widgetsHandle scrollingTap, drag, and enter text

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.For more information on how to write integration tests, see the integration
testing page.
Recipes
An introduction to integration testingPerformance 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:

An introduction to unit testing
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: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:

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:

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.
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.
// 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.
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.IntelliJVSCode

Run tests in a terminal
To run the all tests from the terminal,
run the following command from the root of the project:To run all tests you put into one group,
run the following command from the root of the project:This example uses the group created in section 5.To learn more about unit tests, you can execute this command:

Mock dependencies using Mockito
Sometimes, unit tests might depend on classes that fetch data from live
web services or databases. This is inconvenient for a few reasons: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: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:

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:The function should now look like this:
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([http.Client]) to the main
function to generate a MockClient class with mockito.The generated MockClient class implements the http.Client class.
This allows you to pass the MockClient 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.
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.
@GenerateMocks([http.Client])
void main() {
}
Next, generate the mocks running the following command:

4. Write a test for each condition
The fetchAlbum() function does one of two things:Therefore, you want to test these two conditions.
Use the MockClient class to return an “Ok” response
for the success test, and an error response for the unsuccessful test.
Test these conditions using the when() function provided by
Mockito:
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.
@GenerateMocks([http.Client])
void main() {
group('fetchAlbum', () {
test('returns an Album if the http call completes successfully', () async {
final client = MockClient();
// 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 = MockClient();
// 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.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
import 'dart:async';
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
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
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.
@GenerateMocks([http.Client])
void main() {
group('fetchAlbum', () {
test('returns an Album if the http call completes successfully', () async {
final client = MockClient();
// 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 = MockClient();
// 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.

An introduction to widget testing
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: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
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.

2. Create a widget to test
Next, create a widget for testing. For this recipe,
create a widget that displays a title and message.
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.
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.
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.info 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.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.
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.
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.

Complete example
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.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 widgets
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
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.
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.
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.
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
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.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);
});
}

Handle 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 via integration 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
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 integration tests.
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(
// 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.

// 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/material.dart';
import 'package:flutter_test/flutter_test.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:

Tap, drag, and enter text
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.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
For this example,
create a basic todo app that tests three features: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:
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:
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');
});
info Note
This recipe builds upon previous widget testing recipes.
To learn the core concepts of widget testing,
see the following recipes:

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:
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:
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
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.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),
),
),
);
}
}

An introduction to integration testing
Unit tests and widget tests are handy for testing individual classes,
functions, or widgets. However, they generally don’t test how
individual pieces work together as a whole, or capture the performance
of an application running on a real device. These tasks are performed
with integration tests.Integration tests are written using the integration_test package, provided
by the SDK.In this recipe, learn how to test a counter app. It demonstrates
how to set up integration tests, how to verify specific text is displayed
by the app, how to tap specific widgets, and how to run integration tests.This recipe uses the following steps:

1. Create an app to test
First, create an app for testing. In this example,
test the counter app produced by the flutter create
command. This app allows a user to tap on a button
to increase a counter.
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: '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),
),
);
}
}

2. Add the integration_test dependency
Next, use the integration_test and flutter_test packages
to write integration tests. Add these dependencies to the dev_dependencies
section of the app’s pubspec.yaml file.

3. Create the test files
Create a new directory, integration_test, with an empty app_test.dart file:

4. Write the integration test
Now you can write tests. This involves three steps:
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'package:introduction/main.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 Key('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);
});
});
}

5. Run the integration test
The process of running the integration tests varies depending on the platform
you are testing against. You can test against a mobile platform or the web.
5a. Mobile
To test on a real iOS / Android device, first connect the device and run the
following command from the root of the project:Or, you can specify the directory to run all integration tests:This command runs the app and integration tests on the target device. For more
information, see the Integration testing page.
5b. Web
To get started testing in a web browser, Download ChromeDriver.Next, create a new directory named test_driver containing a new file
named integration_test.dart:
import 'package:integration_test/integration_test_driver.dart';
Future main() => integrationDriver();
Launch chromedriver as follows:From the root of the project, run the following command:For a headless testing experience, you can also run flutter drive
with web-server as the target device identifier as follows:

Integration testing
This page describes how to use the integration_test package to run
integration tests. Tests written using this package have the following
properties:info Note
The integration_test package is part of the Flutter SDK itself.
To use it, make sure that you update your app’s pubspec file
to include this package as one of your dev_dependencies.
For an example, see the Project setup section below.

Overview
Unit tests, widget tests, and integration testsThere are three types of tests that Flutter supports.
A unit test verifies the behavior of a method or class.
A widget test verifies the behavior of Flutter widgets
without running the app itself. An integration test (also
called end-to-end testing or GUI testing) runs the full app.Hosts and targetsDuring development, you are probably writing the code
on a desktop computer, called the host machine,
and running the app on a mobile device, browser,
or desktop application, called the target device.
(If you are using a web
browser or desktop application,
the host machine is also the target device.)integration_testTests written with the integration_test package can:Migrating from flutter_driverExisting projects using flutter_driver can be migrated to
integration_test by following the Migrating from flutter_drive
guide.

Project setup
Add integration_test and flutter_test to your pubspec.yaml file:In your project, create a new directory
integration_test with a new file, _test.dart:
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:how_to/main.dart';
import 'package:integration_test/integration_test.dart';
void main() {
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 Key('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('0'), findsNothing);
expect(find.text('1'), findsOneWidget);
});
}
info Note
Only use testWidgets to declare your tests.
Otherwise, Flutter could report errors incorrectly.If you are looking for more examples, take a look at the testing_app of
the samples repository.

Directory structure
See also:

Running using the flutter command
These tests can be launched with the
flutter test command, where :
is the optional device ID or pattern displayed
in the output of the flutter devices command:This runs the tests in foo_test.dart. To run all tests in this directory on
the default device, run:

Running in a browser
Download and install ChromeDriver
and run it on port 4444:To run tests with flutter drive, create a new directory containing a new file,
test_driver/integration_test.dart:
import 'package:integration_test/integration_test_driver.dart';
Future main() => integrationDriver();
Then add IntegrationTestWidgetsFlutterBinding.ensureInitialized() in your
integration_test/_test.dart file:
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:how_to/main.dart';
import 'package:integration_test/integration_test.dart';
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized(); // NEW
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 Key('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('0'), findsNothing);
expect(find.text('1'), findsOneWidget);
});
}
In a separate process, run flutter_drive:To learn more, see the
Running Flutter driver tests with web wiki page.

Testing on Firebase Test Lab
You can use the Firebase Test Lab with both Android
and iOS targets.

Android setup
Follow the instructions in the Android Device Testing
section of the README.

iOS setup
Follow the instructions in the iOS Device Testing
section of the README.

Test Lab project setup
Go to the Firebase Console,
and create a new project if you don’t have one
already. Then navigate to Quality > Test Lab:

Uploading an Android APK
Create an APK using Gradle:Where _test.dart is the file created in the
Project Setup section.info Note
To use --dart-define with gradlew, you must base64 encode
all parameters, and pass them to gradle in a comma separated list:Drag the “debug” APK from
/build/app/outputs/apk/debug
into the Android Robo Test target on the web page.
This starts a Robo test and allows you to run
other tests:Click Run a test,
select the Instrumentation test type and drag
the following two files:If a failure occurs,
you can view the output by selecting the red icon:

Uploading an Android APK from the command line
See the Firebase Test Lab section of the README
for instructions on uploading the APKs from the command line.

Uploading Xcode tests
See the Firebase TestLab iOS instructions
for details on how to upload the .zip file
to the Firebase TestLab section of the Firebase Console.

Uploading Xcode tests from the command line
See the iOS Device Testing section in the README
for instructions on how to upload the .zip file
from the command line.

Performance 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.info Note
Recording performance timelines isn’t supported on web.
For performance profiling on web, see
Debugging performance for web appsThis recipe uses the following steps:

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.
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:To capture the results, create a file named perf_driver.dart
in the test_driver folder and add the following code:
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: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.info 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:
Summary example

Complete example
integration_test/scrolling_test.dart
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'package:scrolling/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
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,
);
}
},
);
}

Testing plugins
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.info 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.

Types of plugin tests
To see examples of each of these types of tests, you can
create a new plugin from the plugin template
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 can’t interact with native UI,
such as native dialogs or the contents of platform views.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.

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.

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 JUnitIf 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:iOS and macOS XCTestIf 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:For iOS tests, you might need to first open
Runner.xcworkspace in Xcode to configure code signing.Linux GoogleTestTo run the tests from the command line,
use the following command in the example directory,
replacing “my_plugin” with your plugin project name:If you built the example app in release mode rather than
debug, replace “debug” with “release”.Windows GoogleTestIf 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: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
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 integration
tests—for example if they require interacting with
native UI or mocking device state—consider 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.

Plugins in Flutter tests
info 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.Almost all Flutter plugins have two parts: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.
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:info 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.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:

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:

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: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
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: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.

Debugging Flutter apps
There’s a wide variety of tools and features to help debug
Flutter applications. Here are some of the available tools:

Other resources
You might find the following docs useful:

Debug Flutter apps from code
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.info Note
If you are looking for a way to use GDB to remotely debug the
Flutter engine running within an Android app process,
check out flutter_gdb.

Add logging to your application
info Note
You can view logs in DevTools’ Logging view
or in your system console. This sections
shows how to set up your logging statements.You have two options for logging for your application.Import dart:io and invoking methods on
stderr and stdout. For example:
stderr.writeln('print me');
If you output too much at once, then Android might discard some log lines.
To avoid this outcome,
use debugPrint() from Flutter’s foundation library.
This wrapper around print throttles the output to avoid the Android kernel
dropping output.You can also log your app using the dart:developer log() function.
This allows you to include greater granularity and more information
in the logging output.
Example 1
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
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: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
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.info 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.lightbulb 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.

Print the widget tree
To dump the state of the Widgets library,
call the debugDumpApp() function.
Example 4: Call debugDumpApp()
import 'package:flutter/material.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: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.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:If you write your own widgets, override the
debugFillProperties() 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:
Example 5: Call debugDumpRenderTree()
import 'package:flutter/material.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.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().
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()
import 'package:flutter/material.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'),
),
),
);
}
}
The RepaintBoundary widget creates:A RenderRepaintBoundary RenderObject in the render tree
as shown in the Example 5 results.A new layer in the layer tree as shown in the Example 6
results.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: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()
import 'package:flutter/material.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'),
),
),
);
}
}

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:
Example 8: Call debugDumpSemanticsTree()
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.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))),
),
),
);
}
}

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 1To 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:
//add import to rendering library
import 'package:flutter/rendering.dart';
void main() {
debugPaintSizeEnabled = true;
runApp(const MyApp());
}
When enabled, Flutter displays the following changes to your app: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.
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.

Debug animation issues
info 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
info 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 of flags and one function from the
rendering library for debugging performance issues.To set these flags either: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
info 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
like Android does with systrace,
use dart:developer Timeline utilities.Wrap the code you want to measure in Timeline methods.
import 'dart:developer';
void main() {
Timeline.startSync('interesting function');
// iWonderHowLongThisTakes();
Timeline.finishSync();
}
To ensure that the runtime performance characteristics closely match that
of your final product, run your app in profile mode.

Add performance overlay
info 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
import 'package:flutter/material.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.

Use a native language debugger
info 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.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.Create a basic Flutter app.Open the lib\main.dart file in the Flutter app using
VS Code.Click the bug icon
().
This opens the following panes in VS Code:The first time you run the debugger takes the longest.Test the debugger.a. In main.dart, click on this line: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:

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.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.

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.Open the lib/main.dart file using your preferred IDE.Replace the contents of main.dart with the following code.
// 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),
],
),
),
);
}
}
To add the url_launcher package as a dependency,
run flutter pub add:To check what changed with the codebase:In Linux or macOS, run this find command.In Windows, run this command in the command prompt.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.info Note
If you want to use the GNU Project Debugger to debug the
Flutter engine running within an Android app process,
check out flutter_gdb.
Build the Android version of the Flutter app in the Terminal
To generate the needed Android platform dependencies,
run the flutter build command.
Start debugging with VS Code first
If you use VS Code to debug most of your code, start with this section.To open the Flutter app directory, go to
File >
Open Folder… and choose the my_app directory.Open the lib/main.dart file.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.Type flutter select.Click the Flutter: Select Device command.Choose your target device.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:
Attach to the Flutter process in Android Studio
Click the Attach debugger to Android process button.
()lightbulb Tip
If this button doesn’t appear in the Projects menu bar, verify that
you opened Flutter application project but not a Flutter plugin.The process dialog displays one entry for each connected device.
Select show all processes to display available processes for each
device.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.Locate the tab for Android Debugger in the Debug pane.In the Project pane, expand
my_app_android >
android >
app >
src >
main >
java >
io.flutter plugins.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
If you use Android Studio to debug most of your code, start with this section.To open the Flutter app directory, go to
File >
Open… and choose the my_app directory.Open the lib/main.dart file.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.From that same menu, select the virtual Android device.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.Click the Attach debugger to Android process button.
()lightbulb Tip
If this button doesn’t appear in the Projects menu bar, verify that
you opened Flutter application project but not a Flutter plugin.The process dialog displays one entry for each connected device.
Select show all processes to display available processes for each
device.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.Locate the tab for Android Debugger in the Debug pane.In the Project pane, expand
my_app_android >
android >
app >
src >
main >
java >
io.flutter plugins.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.
Build the iOS version of the Flutter app in the Terminal
To generate the needed iOS platform dependencies,
run the flutter build command.
Start debugging with VS Code first
If you use VS Code to debug most of your code, start with this section.
Start the Dart debugger in VS Code
To open the Flutter app directory, go to
File >
Open Folder… and choose the my_app directory.Open the lib/main.dart file.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.Type flutter select.Click the Flutter: Select Device command.Choose your target device.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:
Attach to the Flutter process in Xcode
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
If you use Xcode to debug most of your code, start with this section.
Start the Xcode debugger
Open ios/Runner.xcworkspace from your Flutter app directory.Select the correct device using the Scheme menu in the toolbar.If you have no preference, choose iPhone Pro 14.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:Copy the Dart VM service URI.
Attach to the Dart VM in VS Code
To open the command palette, go to
View >
Command Palette…You can also press Cmd + Shift + P.Type debug.Click the Debug: Attach to Flutter on Device command.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.
Build the macOS version of the Flutter app in the Terminal
To generate the needed macOS platform dependencies,
run the flutter build command.
Start debugging with VS Code first

Start the debugger in VS Code
To open the Flutter app directory, go to
File >
Open Folder… and choose the my_app directory.Open the lib/main.dart file.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.Type flutter select.Click the Flutter: Select Device command.Choose your target device.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:
Attach to the Flutter process in Xcode
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

Start the debugger in Xcode
Open macos/Runner.xcworkspace from your Flutter app directory.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:Copy the Dart VM service URI.
Attach to the Dart VM in VS Code
To open the command palette, go to View > Command Palette…You can also press Cmd + Shift + P.Type debug.Click the Debug: Attach to Flutter on Device command.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.
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.
Start debugging with VS Code first
If you use VS Code to debug most of your code, start with this section.
Start the debugger in VS Code
To open the Flutter app directory, go to
File >
Open Folder… and choose the my_app directory.Open the lib/main.dart file.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.Type flutter select.Click the Flutter: Select Device command.Choose your target device.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:
Attach to the Flutter process in Visual Studio
To open the project solution file, go to
File >
Open >
Project/Solution…You can also press Ctrl + Shift + O.Choose the build/windows/my_app.sln file in your Flutter app directory.Go to Debug > Attach to Process.You can also press Ctrl + Alt + P.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
To open the project solution file, go to
File >
Open >
Project/Solution…You can also press Ctrl + Shift + O.Choose the build/windows/my_app.sln file in your Flutter app directory.Set my_app as the startup project.
In the Solution Explorer, right-click on my_app and select
Set as Startup Project.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:Copy the Dart VM service URI.
Attach to the Dart VM in VS Code
To open the command palette, go to
View >
Command Palette…You can also press Cmd + Shift + P.Type debug.Click the Debug: Attach to Flutter on Device command.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

Android
You can find the following debugging resources on
developer.android.com.

iOS and macOS
You can find the following debugging resources on
developer.apple.com.

Windows
You can find debugging resources on Microsoft Learn.

Flutter's build modes
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:The rest of the page details these modes.

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:Debug mode for a web app means that: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.info Note

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:Release mode for a web app means that: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:Profile mode for a web app means that:Your IDE supports this mode. Android Studio, for example,
provides a Run > Profile… menu option.
The command flutter run --profile compiles to profile mode.info Note
Use the DevTools suite to profile your app’s performance.For more information on the build modes, see
Flutter’s build modes.

Common Flutter 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.

‘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: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:
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 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 overviewIn 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:
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
on the Flexible widget.Further information:The resources linked below provide further information about this error.

‘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: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’
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: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.
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.
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.

‘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: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.
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:
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: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.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: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:
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.
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:

References
To learn more about how to debug errors,
especially layout errors in Flutter,
check out the following resources:

Handling errors in Flutter
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.info 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:
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
void main() {
FlutterError.onError = (details) {
FlutterError.presentError(details);
if (kReleaseMode) exit(1);
};
runApp(const MyApp());
}
// rest of `flutter create` code...
info 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.
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:
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.
import 'package:flutter/material.dart';
import 'dart:ui';
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:
import 'package:flutter/material.dart';
import 'dart:ui';
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');
},
);
}
}

Report errors to a service
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
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:

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:

3. Initialize the Sentry SDK
Initialize the SDK to capture different unhandled errors automatically:
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:

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:
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.

Performance
Flutter performance basicsinfo 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.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.To improve performance, you first need metrics: some measurable numbers to
verify the problems and improvements.
In the 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,
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 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”, “perf: memory”,
“perf: app size”, “perf: 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

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

Impeller rendering engine

What is Impeller?
Impeller provides a new rendering runtime for Flutter.
The Flutter team’s believes this solves Flutter’s
early-onset jank issue.
Impeller precompiles a smaller, simpler set of shaders
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.Introducing Impeller - Flutter’s new rendering engineImpeller has the following objectives:

Availability
Where can you use Impeller?

iOS
Flutter enables Impeller by default on iOS.To disable Impeller on iOS when debugging,
pass --no-enable-impeller to the flutter run command.To disable Impeller on iOS when deploying your app,
add the following tags under the top-level tag in your
app’s Info.plist file.The team continues to improve iOS support.
If you encounter performance or fidelity issues
with Impeller on iOS, file an issue in the GitHub tracker.
Prefix the issue title with [Impeller] and
include a small reproducible test case.

macOS
Impeller is available for macOS in preview as of
the Flutter 3.13 stable release.
It continues to be in preview as of the 3.19 release.To enable Impeller on macOS when debugging,
pass --enable-impeller to the flutter run command.To enable Impeller on macOS when deploying your app,
add the following tags under the top-level tag in your
app’s Info.plist file.

Android
As of Flutter 3.16, Impeller is available behind
a flag on Android devices that support Vulkan.
It continues to be in preview as of the 3.19 release.Does your device support Vulkan?
You can determine whether your Android device
supports Vulkan at checking for Vulkan support.You can try Impeller on Vulkan-capable Android devices
by passing --enable-impeller to flutter run:Or, you can add the following setting to your project’s
AndroidManifest.xml file under the tag:

Bugs and issues
For the full list of Impeller’s known bugs
and missing features,
the most up-to-date information is on the
Impeller project board on GitHub.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.
Prefix the issue title with [Impeller] and
include a small reproducible test case.Please include the following information when
submitting an issue for Impeller:

Architecture
To learn more details about Impeller’s design and architecture,
check out the README.md file in the source tree.

Additional Information

Performance best practices
info Note
To learn how to use the Performance View
(part of Flutter DevTools)
for debugging performance issues,
see Using the Performance view.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.info 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: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:For more information, check out:

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.
Minimizing calls to saveLayer
Can you avoid calls to saveLayer?
It might require rethinking of how you
create your visual effects: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:

Minimize use of opacity and clipping
Opacity is another expensive operation, as is clipping.
Here are some tips you might find to be useful:

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:
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 Cards.
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:To dive even deeper into how layout works,
check out the layout and rendering
section in the Flutter architectural overview.

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?If you are wondering why 60fps leads to a smooth visual experience,
check out the video Why 60fps?

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:

Measuring your app's size
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
(as used in Test drive and
Write your first Flutter app),
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: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: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: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 Warning
As stated in the checking 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:This build is different from a standard release build in two ways.In addition to analyzing a single build, two builds can also be diffed by
loading two *-code-size-analysis_*.json files into DevTools. See
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 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 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 flutter pub global run devtools, selecting
Open app size tool and uploading the JSON file.For further information on using the DevTools app size tool, see
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:

Deferred components

Introduction
Flutter has the capability to build apps that can
download additional Dart code and assets at runtime.
This allows apps to reduce install apk size and download
features and assets when needed by the user.We refer to each uniquely downloadable bundle of Dart
libraries and assets as a “deferred component”.
To load these components, use Dart’s deferred imports.
They can be compiled into split AOT and JavaScript shared libraries.info Note
Flutter supports deferred, or “lazy”, loading on Android and the web.
The implementations differ.
Android’s dynamic feature modules deliver the
deferred components packaged as Android modules.
The web creates these components as separate *.js files.
Deferred code doesn’t impact other platforms,
which continue to build as normal with all deferred
components and assets included at initial install time.Though you can defer loading modules,
you must build the entire app and upload that app as a single
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 app
in release or profile mode.
Debug mode treats all deferred components as regular imports.
The components are present at launch and load immediately.
This allows debug builds to hot reload.For a deeper dive into the technical details of
how this feature works, see Deferred Components
on the Flutter wiki.

How to set your 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
Add Play Core to the Android app’s
build.gradle dependencies.
In android/app/build.gradle add the following: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: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:Call SplitCompat.install(this);
in the attachBaseContext() method:Declare SplitCompatApplication as the application
subclass and add the Flutter compatibility code from
FlutterApplication to your application class: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:Opt into deferred components by adding
the deferred-components entry to the app’s pubspec.yaml
under the flutter entry: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 does not 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.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
import 'package:flutter/material.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,
);
}
}
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.
import 'package:flutter/material.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: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.info 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.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 cannot be performed
until gen_snapshot completes and produces a final set
of loading units.info 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 do not 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:/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 does not validate
the contents of these files. If a file does not 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:The gen_snapshot validator won’t run until the prebuild
validator passes.For 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.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: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:To assign a loading unit to a deferred component,
add any Dart lib in the loading unit into the
libraries section of the feature module.
Keep the following guidelines in mind:Loading units should not 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.Assets can also be included by adding
an assets section in the deferred component configuration: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 libs 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.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: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 does not 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 lib would
load properly because it is always present in the base,
and the lib would not 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: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 .apks 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 .apks file: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 lib and
assets is downloaded by the Flutter engine using the
Play store’s delivery feature.

Improving rendering performance
info Note
To learn how to use the Performance View
(part of Flutter DevTools)
for debugging performance issues,
see Using the Performance view.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:For more information on evaluating performance
including information on common pitfalls,
see the following docs:

Mobile-only advice
Do you see noticeable jank on your mobile app, but only on
the first run of an animation? If so, see
Reduce shader animation jank on mobile.

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:

Flutter performance profiling
info Note
To learn how to use the Performance View
(part of Flutter DevTools)
for debugging performance issues,
see Using the Performance view.
What you'll learn
It’s been said that “a fast app is great,
but a smooth app is even better.”
If your app isn’t rendering smoothly,
how do you fix it? Where do you begin?
This guide shows you where to start,
steps to take, and tools that can help.info Note

Diagnosing performance problems
To diagnose an app with performance problems, you’ll enable
the performance overlay to look at the UI and raster threads.
Before you begin, make sure that you’re running in
profile mode, and that you’re not using an emulator.
For best results, you might choose the slowest device that
your users might use.

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.
Why you should run on a real device:

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.info Note
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):From the command line, use the --profile flag: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.

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.

The 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.

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.
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.

Flutter’s 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.For links to more information and videos,
see The Framework architecture on the
GitHub wiki, and the community article,
The Layer Cake.

Displaying the performance overlay
You can toggle display of the performance overlay as follows:
Using the Flutter inspector
The easiest way to enable the PerformanceOverlay widget is
from the Flutter inspector, which is available in the
Inspector view in DevTools. Simply click the
Performance Overlay button to toggle the overlay
on your running app.
From the 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.

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.

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.
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. You can check whether
your scene is using saveLayer with the
PerformanceOverlayLayer.checkerboardOffscreenLayers switch.Once the switch is enabled, run the app and look for any images
that are outlined with a flickering box. The box flickers from
frame to frame if a new frame is being rendered. 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.info 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:
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.You can see which images are being cached by enabling the
PerformanceOverlayLayer.checkerboardRasterCacheImages switch.Run the app and look for images rendered with a randomly colored
checkerboard, indicating that the image is cached.
As you interact with the scene, the checkerboarded images
should remain constant—you don’t want to see flickering,
which would indicate that the cached image is being re-cached.In most cases, you want to see checkerboards on static images,
but not on non-static images. If a static image isn’t cached,
you can cache it by placing it into a RepaintBoundary
widget. Though the engine might still ignore a repaint
boundary if it thinks the image isn’t complex enough.

Viewing the widget rebuild profiler
The Flutter framework is designed to make it hard to create
applications that are not 60fps and smooth. Often, if you have jank,
it’s because there is a simple bug causing more of the UI to be
rebuilt each frame than required. The Widget rebuild profiler
helps you debug and fix performance problems due to these sorts
of bugs.You can view the widget rebuilt counts for the current screen and
frame in the Flutter plugin for Android Studio and IntelliJ.
For details on how to do this, see Show performance data

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 to track the following:Tracking these benchmarks allows you to be informed when a
regression is introduced that adversely affects performance.For more information, check out Integration testing.

Other resources
The following resources provide more information on using
Flutter’s tools and debugging in Flutter:

Debugging performance for web apps
info 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.You can also emit your own timeline events using the dart:developer
Timeline and TimelineTask APIs for further performance analysis.

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.

Instructions

Shader compilation jank
info Note
To learn how to use the Performance View
(part of Flutter DevTools)
for debugging performance issues,
see Using the Performance view.If the animations on your mobile app appear to be janky,
but only on the first run,
this is likely due to shader compilation.
Flutter’s long term solution to
shader compilation jank is Impeller,
which is in the stable release for iOS
and in preview behind a flag on Android.While we work on making Impeller fully production ready,
you can mitigate shader compilation jank by bundling
precompiled shaders with an iOS app.
Unfortunately, this approach doesn’t work well on Android
due to precompiled shaders being device or GPU-specific.
The Android hardware ecosystem is large enough that the
GPU-specific precompiled shaders bundled with an application
will work on only a small subset of devices,
and will likely make jank worse on the other devices,
or even create rendering errors.Also, note that we aren’t planning to make
improvements to the developer experience for creating
precompiled shaders described below. Instead,
we are focusing our energies on the more robust
solution to this problem that Impeller offers.

What is shader compilation jank?
A shader is a piece of code that runs on a
GPU (graphics processing unit).
When the Skia graphics backend that Flutter uses for rendering
sees a new sequence of draw commands for the first time,
it sometimes generates and compiles a
custom GPU shader for that sequence of commands.
This allows that sequence and potentially similar sequences
to render as fast as possible.Unfortunately, Skia’s shader generation and compilation
happens in sequence with the frame workload.
The compilation could cost up to a few hundred milliseconds
whereas a smooth frame needs to be drawn within 16 milliseconds
for a 60 fps (frame-per-second) display.
Therefore, a compilation could cause tens of frames
to be missed, and drop the fps from 60 to 6.
This is compilation jank.
After the compilation is complete,
the animation should be smooth.On the other hand, Impeller generates and compiles all
necessary shaders when we build the Flutter Engine.
Therefore apps running on Impeller already have
all the shaders they need, and the shaders can be used
without introducing jank into animations.Definitive evidence for the presence of shader compilation jank
is to set GrGLProgramBuilder::finalize in the tracing
with --trace-skia enabled.
The following screenshot shows an example timeline tracing.

What do we mean by “first run”?
On iOS, “first run” means that the user might see
jank when an animation first occurs every time
the user opens the app from scratch.

How to use SkSL warmup
Flutter provides command line tools
for app developers to collect shaders that might be needed
for end-users in the SkSL (Skia Shader Language) format.
The SkSL shaders can then be packaged into the app,
and get warmed up (pre-compiled) when an end-user first
opens the app, thereby reducing the compilation
jank in later animations.
Use the following instructions to collect
and package the SkSL shaders:Run the app with --cache-sksl turned on
to capture shaders in SkSL:If the same app has been previously run
without --cache-sksl, then the
--purge-persistent-cache flag might be needed:This flag removes older non-SkSL shader caches that
could interfere with SkSL shader capturing.
It also purges the SkSL shaders so use it only on the first
--cache-sksl run.Play with the app to trigger as many animations
as needed; particularly those with compilation jank.Press M at the command line of flutter run to
write the captured SkSL shaders into a file named something like
flutter_01.sksl.json.
For best results,
capture SkSL shaders on an actual iOS device.
A shader captured on a simulator isn’t likely to work correctly
on actual hardware.Build the app with SkSL warm-up using the following,
as appropriate:If it’s built for a driver test like test_driver/app.dart,
make sure to also specify --target=test_driver/app.dart
(for example, flutter build ios --bundle-sksl-path
flutter_01.sksl.json --target=test_driver/app.dart).Test the newly built app.Alternatively, you can write some integration tests to
automate the first three steps using a single command.
For example:With such integration tests,
you can easily and reliably get the
new SkSLs when the app code changes,
or when Flutter upgrades.
Such tests can also be used to verify the performance change
before and after the SkSL warm-up.
Even better, you can put those tests into a
CI (continuous integration) system so the
SkSLs are generated and tested automatically over the lifetime of an app.info Note
The integration_test package is now the recommended way
to write integration tests. Refer to the
Integration testing
page for details.Take the original version of Flutter Gallery as an example.
The CI system is set up to generate SkSLs for every Flutter commit,
and verifies the performance, in the transitions_perf_test.dart test.
For more details,
check out the flutter_gallery_sksl_warmup__transition_perf and
flutter_gallery_sksl_warmup__transition_perf_e2e_ios32 tasks.The worst frame rasterization time is a useful metric from
such integration tests to indicate the severity of shader
compilation jank.
For instance,
the steps above reduce Flutter gallery’s shader compilation
jank and speeds up its worst frame rasterization time on a
Moto G4 from ~90 ms to ~40 ms. On iPhone 4s,
it’s reduced from ~300 ms to ~80 ms. That leads to the visual
difference as illustrated in the beginning of this article.

Performance metrics
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:

Concurrency and isolates
All Dart code runs in isolates,
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 of the Dart
documentation.

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:

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.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.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.
// 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.

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: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.

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 StreamConroller is called a SendPort,
and you can “add” messages with the send() method.
ReceivePorts 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.

Using platform plugins in isolates
As of Flutter 3.7, you can use platform plugins in background isolates.
This opens many possibilities 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.
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.

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:

Performance FAQ
This page collects some frequently asked questions
about evaluating and debugging Flutter’s performance.

More thoughts about 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?
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: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.

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: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).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.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.Make performance metrics monitor a population that is as wide as possible,
so no one is left behind.

Obfuscate Dart code

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.Flutter’s code obfuscation works
only on a release build.

Limitations
Note that obfuscating your code does not
encrypt resources nor does it protect against
reverse engineering.
It only renames symbols with more obscure names.info
It is a poor security practice to
store secrets in an app.

Supported targets
The following build targets
support the obfuscation process
described on this page:info
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.

Obfuscate your app
To obfuscate your app, use the flutter build command
in release mode
with the --obfuscate and --split-debug-info options.
The --split-debug-info option specifies the directory
where Flutter outputs debug files.
In the case of obfuscation, it outputs a symbol map.
For example:Once you’ve obfuscated your binary, save
the symbols file. You need this if you later
want to de-obfuscate a stack trace.lightbulb Tip
The --split-debug-info option can also be used without --obfuscate
to extract Dart program symbols, reducing code size.
To learn more about app size, see Measuring your app’s size.For detailed information on these flags, run
the help command for your specific target, for example:If these flags are not listed in the output,
run flutter --version to check your version of Flutter.

Read an obfuscated stack trace
To debug a stack trace created by an obfuscated app,
use the following steps to make it human readable:Find the matching symbols file.
For example, a crash from an Android arm64
device would need app.android-arm64.symbols.Provide both the stack trace (stored in a file)
and the symbols file to the flutter symbolize command.
For example:For more information on the symbolize command,
run flutter symbolize -h.

Read an obfuscated name
To make the name that an app obfuscated human readable,
use the following steps:To save the name obfuscation map at app build time,
use --extra-gen-snapshot-options=--save-obfuscation-map=/.
For example:To recover the name, use the generated obfuscation map.
The obfuscation map is a flat JSON array with pairs of
original names and obfuscated names. For example,
["MaterialApp", "ex", "Scaffold", "ey"], where ex
is the obfuscated name of MaterialApp.

Caveat
Be aware of the following when coding an app that will
eventually be an obfuscated binary.
expect(foo.runtimeType.toString(), equals('Foo'));

Create flavors of a Flutter app

What are flavors
Have you ever wondered how to set up different environments in your Flutter app?
Flavors (known as build configurations in iOS and macOS), allow you (the developer) to
create separate environments for your app using the same code base.
For example, you might have one flavor for your full-fledged production app,
another as a limited “free” app, another for testing experimental features, and so on.Say you want to make both free and paid versions of your Flutter app.
You can use flavors to set up both app versions
without writing two separate apps.
For example, the free version of the app has basic functionality and ads.
In contrast, the paid version has basic app functionality, extra features,
different styles for paid users, and no ads.You also might use flavors for feature development.
If you’ve built a new feature and want to try it out,
you could set up a flavor to test it out.
Your production code remains unaffected
until you’re ready to deploy your new feature.Flavors let you define compile-time configurations
and set parameters that are read at runtime to customize
your app’s behavior.This document guides you through setting up Flutter flavors for iOS, macOS, and Android.

Environment set up
Prerequisites:To set up flavors in iOS and macOS, you’ll define build configurations in Xcode.

Creating flavors in iOS and macOS
Open your project in Xcode.Select Product > Scheme > New Scheme from the menu to
add a new Scheme.Duplicate the build configurations to differentiate between the
default configurations that are already available and the new configurations
for the free scheme.info Note
Your configurations should be based on your Debug.xconfig or Release.xcconfig
file, not the Pods-Runner.xcconfigs. You can check this by expanding the configuration names.To match the free flavor, add -free
at the end of each new configuration name.Change the free scheme to match the build configurations already created.

Using flavors in iOS and macOS
Now that you’ve set up your free flavor,
you can, for example, add different product bundle identifiers per flavor.
A bundle identifier uniquely identifies your application.
In this example, we set the Debug-free value to equal
com.flavor-test.free.Change the app bundle identifier to differentiate between schemes.
In Product Bundle Identifier, append .free to each -free scheme value.In the Build Settings, set the Product Name value to match each flavor.
For example, add Debug Free.Add the display name to Info.plist. Update the Bundle Display Name
value to $(PRODUCT_NAME).Now you have set up your flavor by making a free scheme
in Xcode and setting the build configurations for that scheme.For more information, skip to the Launching your app flavors
section at the end of this document.

Plugin configurations
If your app uses a Flutter plugin, you need to update
ios/Podfile (if developing for iOS) and macos/Podfile (if developing for macOS).

Using flavors in Android
Setting up flavors in Android can be done in your project’s
build.gradle file.Inside your Flutter project,
navigate to android/app/build.gradle.Create a flavorDimension to group your added product flavors.
Gradle doesn’t combine product flavors that share the same dimension.Add a productFlavors object with the desired flavors along
with values for dimension, resValue,
and applicationId or applicationIdSuffix.

Setting up launch configurations
Next, add a launch.json file; this allows you to run the command
flutter run --flavor [environment name].In VSCode, set up the launch configurations as follows:You can now run the terminal command
flutter run --flavor free or you can set up a run
configuration in your IDE.

Launching your app flavors
For examples of build flavors for iOS, macOS, and Android,
check out the integration test samples in the Flutter repo.

Retrieving your app’s flavor at runtime
From your Dart code, you can use the appFlavor API to determine what
flavor your app was built with.

Conditionally bundling assets based on flavor
If you aren’t familiar with how to add assets to your app, see
Adding assets and images.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 building for that flavor.
This prevents your app bundle size from being bloated by unused assets.Here is an example:In this example, files within the assets/common/ directory will always be bundled
when app is built during flutter run or flutter build. Files within the
assets/free/ directory are bundled only when the --flavor option is set
to free. Similarly, files within the assets/premium directory are
bundled only if --flavor is set to premium.

More information
For more information on creating and using flavors, check out
the following resources:

Packages
For packages that support creating flavors, check out the following:

Build and release an Android app
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 Google Play Store,
this page can help. Before publishing,
you might want to put some finishing touches on your app.
This page covers the following topics:info Note
Throughout this page, [project] refers to
the directory that your application is in. While following
these instructions, substitute [project] with
your app’s directory.

Adding a launcher icon
When a new Flutter app is created, it has a default launcher icon.
To customize this icon, you might want to check out the
flutter_launcher_icons package.Alternatively, you can do it manually using the following steps:Review the Material Design product
icons guidelines for icon design.In the [project]/android/app/src/main/res/ directory,
place your icon files in folders named using
configuration qualifiers.
The default mipmap- folders demonstrate the correct
naming convention.In AndroidManifest.xml, update the
application tag’s android:icon
attribute to reference icons from the previous
step (for example,

Enabling Material Components
If your app uses Platform Views, you might want to enable
Material Components by following the steps described in the
Getting Started guide for Android.For example:To find out the latest version, visit Google Maven.

Sign the app
To publish on the Play Store, you need to
sign your app with a digital certificate.Android uses two signing keys: upload and app signing.To create your app signing key, use Play App Signing
as described in the official Play Store documentation.To sign your app, use the following instructions.

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:Run the following command at the command line:On macOS or Linux, use the following command:On Windows, use the following command in PowerShell: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!info NoteThe keytool command might not be in your path—it’s
part of Java, which is installed as part of
Android Studio. For the concrete path,
run flutter doctor -v and locate the path printed after
‘Java binary at:’. Then use that fully qualified path
replacing java (at the end) with keytool.
If your path includes space-separated names,
such as Program Files, use platform-appropriate
notation for the names. For example, on Mac/Linux
use Program\ Files, and on Windows use
"Program Files".The -storetype JKS tag is only required for Java 9
or newer. As of the Java 9 release,
the keystore type defaults to PKS12.

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 (< >).
They indicate that the text serves as a placeholder for your values.The storeFile might be located at
/Users//upload-keystore.jks on macOS
or C:\\Users\\\\upload-keystore.jks on Windows.warning Warning
Keep the key.properties file private;
don’t check it into public source control.

Configure signing in gradle
Configure gradle to use your upload key when building your app in release mode
by editing the [project]/android/app/build.gradle file.Add the keystore information from your properties file before the android block:Load the key.properties file into the keystoreProperties object.Find the buildTypes block:And replace it with the following signing configuration info:Release builds of your app will now be signed automatically.info Note
You might need to run flutter clean after changing the gradle file.
This prevents cached builds from affecting the signing process.For more information on signing your app, check out
Sign your app on developer.android.com.

Shrinking your code with R8
R8 is the new code shrinker from Google, and 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.info Note
Obfuscation and minification can considerably extend compile time
of the Android application.

Enabling 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 does not 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 device:When prompted, enter y.
The Flutter tool enables multidex support and retries the build:info Note
Multidex support is natively included when targeting
Android SDK 21 or later. However, we don’t recommend
targeting API 21+ purely to resolve the multidex issue
as this might inadvertently exclude users running older devices.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 must be specified to include:Also, include any other classes used in app startup.
For more detailed guidance on adding multidex support manually,
check out the official Android documentation.

Reviewing the app manifest
Review the default App Manifest file, AndroidManifest.xml.
This file is located in [project]/android/app/src/main.
Verify the following values:

Reviewing the Gradle build configuration
Review the default Gradle build file
(build.gradle, located in [project]/android/app),
to verify that the values are correct.
Under the defaultConfig block

Under the android block
For more information, check out the module-level build
section in the Gradle build file.

Building the app for release
You have two possible release formats when publishing to
the Play Store.info Note
The Google Play Store prefers the app bundle format.
For more information, check out
About Android App Bundles.

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
to make it more difficult to reverse engineer. Obfuscating
your code involves adding a couple flags to your build command,
and maintaining additional files to de-obfuscate stack traces.From the command line: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 (ARM 32-bit), arm64-v8a
(ARM 64-bit), and x86-64 (x86 64-bit).

Test the app bundle
An app bundle can be tested in multiple ways.
This section describes two.
Offline using the bundle tool

Online using Google Play

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
to make it more difficult to reverse engineer. Obfuscating
your code involves adding a couple flags to your build command.From the command line:Enter cd [project].Run flutter build apk --split-per-abi.
(The flutter build command defaults to --release.)This command results in three APK files: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 are not applicable to their device’s architecture.

Install an APK on a device
Follow these steps to install the APK on a connected Android device.From the command line:

Publishing to the Google Play Store
For detailed instructions on publishing your app to the Google Play Store,
check out the Google Play launch documentation.

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+1The 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 Android, build-name is used as versionName while
build-number used as versionCode. For more information,
check out Version your app 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
Here are some commonly asked questions about deployment for
Android apps.

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?
A fat APK 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 using the
--split-per-abi flag.

What are the supported target architectures?
When building your application in release mode,
Flutter apps can be compiled for armeabi-v7a (ARM 32-bit),
arm64-v8a (ARM 64-bit), and x86-64 (x86 64-bit).
Flutter supports building for x86 Android through ARM emulation.

How do I sign the app bundle created by flutter build appbundle?
See Signing the app.

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.

Build and release an iOS app
This guide provides a step-by-step walkthrough of releasing a
Flutter app to the App Store 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.To publish your app to the App Store,
you must first enroll in the Apple Developer Program.
You can read more about the various membership options in Apple’s
Choosing a Membership guide.

Video overview
For those who prefer video over text,
the following video covers the same material as this guide.Release an iOS app with Flutter in 7 steps

Register your app on App Store Connect
Manage your app’s life cycle on
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 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:

Create an application record on App Store Connect
Register your app on App Store Connect:For a detailed overview, see
Add an app to your account.

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.Navigate to your target’s settings in Xcode:Verify the most important settings.In the Identity section of the General tab:In the Signing & Capabilities tab:In the Deployment section of the Build Settings tab: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.

Updating the app’s deployment version
If you changed Deployment Target in your Xcode project,
open ios/Flutter/AppframeworkInfo.plist in your Flutter app
and update the MinimumOSVersion value to match.

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:

Add a launch image
Similar to the app icon,
you can also replace the placeholder launch image:

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: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:

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 by
adding the option --export-method ad-hoc,
--export-method development or --export-method enterprise.info 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 by either:Install and open the Apple Transport macOS app.
Drag and drop the build/ios/ipa/*.ipa app bundle into the app.Or upload the app bundle from the command line by running:Run man altool for details about how to authenticate with the App Store Connect API key.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.info 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.
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.

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 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.Install the Codemagic CLI tools:You’ll need to generate an App Store Connect 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.You need to export or create an iOS Distribution certificate to code sign and package a build archive.If you have existing certificates, you can export the
private keys by executing the following command for each certificate:Or you can create a new private key by executing the following command:Later, you can have CLI tools automatically create a new iOS Distribution from the private key.Set up a new temporary keychain to be used for code signing:Restore Login Keychain!
After running keychain initialize you must run the following:keychain use-loginThis sets your login keychain as the default to avoid potential
authentication issues with apps on your machine.Fetch the code signing files from App Store Connect: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.Now add the fetched certificates to your keychain:Update the Xcode project settings to use fetched code signing profiles:Install Flutter dependencies:Install CocoaPods dependencies:Build the Flutter the iOS project:Note that export_options.plist is the output of the xcode-project use-profiles command.
Publish the app to App Store Connect:As mentioned earlier, don’t forget to set your login keychain
as the default to avoid authentication issues
with apps on your machine: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.For more details, see Distribute an app using 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: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.

Troubleshooting
The Distribute your app guide provides a
detailed overview of the process of releasing an app to the App Store.

Build and release a macOS app
This guide provides a step-by-step walkthrough of releasing a
Flutter app to the App Store.

Preliminaries
Before beginning the process of releasing your app,
ensure that it meets
Apple’s App Review Guidelines.In order to publish your app to the App Store,
you must first enroll in the
Apple Developer Program.
You can read more about the various
membership options in Apple’s
Choosing a Membership guide.

Register your app on App Store Connect
Manage your app’s life cycle on
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 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:

Create an application record on App Store Connect
Register your app on App Store Connect:For a detailed overview,
see Add an app to your account.

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.Navigate to your target’s settings in Xcode:Verify the most important settings.In the Identity section:In the Deployment info section:In the Signing & Capabilities section: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.

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+1The 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:

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:Finally, create a build archive and upload it to App Store Connect: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.

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 executed in a terminal
in the Flutter project directory.Install the Codemagic CLI tools:You’ll need to generate an App Store Connect 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.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, you can export the
private keys by executing the following command for each certificate:Or you can create a new private key by executing the following command: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.Fetch the code signing files from App Store Connect:Where cert_key is either your exported Mac App Distribution certificate private key
or a new private key which automatically generates a new certificate.If you do not have a Mac Installer Distribution certificate,
you can create a new certificate by executing the following:Use cert_key of the private key you created earlier.Fetch the Mac Installer Distribution certificates:Set up a new temporary keychain to be used for code signing:Restore Login Keychain!
After running keychain initialize you must run the following:keychain use-loginThis sets your login keychain as the default to avoid potential
authentication issues with apps on your machine.Now add the fetched certificates to your keychain:Update the Xcode project settings to use fetched code signing profiles:Install Flutter dependencies:Install CocoaPods dependencies:Build the Flutter macOS project:Package the app:Publish the packaged app to App Store Connect:As mentioned earlier, don’t forget to set your login keychain
as the default to avoid authentication issues
with apps on your machine:

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.

Distribute to registered devices
See distribution guide
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: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.

Troubleshooting
The Distribute your app guide provides a
detailed overview of the process of releasing an app to the App Store.

Build 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,
this page can help.

Prerequisites
To build and publish to the Snap Store, you need the
following components:

Set up the build environment
Use the following instructions to set up your build environment.

Install snapcraft
At the command line, run the following:

Install LXD
To install LXD, use the following command: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.On the first run, LXD might not be able to connect to its 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:

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: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.

Grade, confinement, and base
This section defines how the snap is built.

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.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.

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.

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: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:To use the LXD container backend:

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:

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:

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 resources
You can learn more from the following links on the
snapcraft.io site:

Build and release a Windows desktop app
One convenient approach to distributing Windows apps
is the Microsoft Store.
This guide provides a step-by-step walkthrough
of packaging and deploying a Flutter app in this way.info 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.

Preliminaries
Before beginning the process of releasing
a Flutter Windows desktop app to the Microsoft Store,
first confirm that it satisfies Microsoft Store Policies.Also, you must join the
Microsoft Partner Network 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.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
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:After manually packaging the application,
manually submit it to the
Microsoft Partner Center.
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 uses the
msix pub package to package
Flutter Windows desktop applications.For Flutter applications, use either the
Codemagic Workflow Editor
or codemagic.yaml
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;
so, Codemagic requires
associating the Azure Active Directory
and Partner Center accounts.
GitHub Actions CI/CD
GitHub Actions can use the
Microsoft Dev Store CLI
to package applications into an MSIX and publish them to the Microsoft Store.
The 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, 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.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.
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 .
and
msstore publish
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

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.info 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.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: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.info Note
Flutter projects created before Flutter 3.3
need to be updated to set the executable’s version
information. For more information,
refer to the version migration guide.

Add app icons
To update the icon of a Flutter Windows
desktop application before packaging use the
following instructions:When packaging with the msix pub 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
is a tool included in the
Windows Software Development Kit (SDK).To validate the application:The report might contain important warnings and information,
even if the certification passes.

Build 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
Build the app for deployment using the
flutter build web command.
You can also choose which renderer to use
by using the --web-renderer option (See Web renderers).
This generates the app, including the assets,
and places the files into the /build/web
directory of the project.The release build of a simple app has the
following structure:info Note
The canvaskit directory and its contents are only present when the
CanvasKit renderer is selected—not when the HTML renderer is selected.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.

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:

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 the Firebase CLI:

Initialize Firebase
Enable the web frameworks preview to the Firebase framework-aware CLI:In an empty directory or an existing Flutter project, run the initialization
command:Answer yes when asked if you want to use a web framework.If you’re in an empty directory,
you’ll be asked to choose your web framework. Choose Flutter Web.Choose your hosting source directory; this could be an existing flutter app.Select a region to host your files.Choose whether to set up automatic builds and deploys with GitHub.Deploy the app to Firebase Hosting: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.

Choosing a web renderer
By default, the flutter build and flutter run commands
use the auto choice for the web renderer. This means that
your app runs with the HTML renderer on mobile browsers and
CanvasKit on desktop browsers. We recommend this combination
to optimize for the characteristics of each platform.For more information, see Web renderers.

Minification
Minification is handled for you when you
create a release build.

Embedding a Flutter app into an HTML page

hostElement
Added in Flutter 3.10
You can embed a Flutter web app into
any HTML element of your web page, with flutter.js and the hostElement
engine initialization parameter.To tell Flutter web in which element to render, use the hostElement parameter of the initializeEngine
function:To learn more, check out Customizing web app initialization.

Iframe
You can embed a Flutter web app,
as you would embed other content,
in an iframe tag of an HTML file.
In the following example, replace “URL”
with the location of your HTML page:

PWA Support
As of release 1.20, the Flutter template for web apps includes support
for the core features needed for an installable, offline-capable PWA app.
Flutter-based PWAs can be installed in the same way as any other web-based
PWA; the settings signaling that your Flutter app is a PWA are provided by
manifest.json, which is produced by flutter create in the web directory.PWA support remains a work in progress,
so please give us feedback if you see something that doesn’t look right.

Continuous delivery with Flutter
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

Integrating fastlane with existing workflows
You can use fastlane with the following tooling: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. On iOS, follow the
fastlane iOS beta deployment guide.
You can specify the archive path to avoid rebuilding the project. For example:You’re now ready to perform deployments locally or migrate the deployment
process to a continuous integration (CI) system.

Running deployment locally

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.

Xcode Cloud
Xcode Cloud is a continuous integration and delivery service for building,
testing, and distributing apps and frameworks for Apple platforms.

Requirements

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.info 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.
#!/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.

Workflow configuration
An Xcode Cloud workflow defines the steps performed in the CI/CD process
when your workflow is triggered.info 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:Choose Product > Xcode Cloud > Create Workflow to open the
Create Workflow sheet.Select the product (app) that the workflow should be attached to, then click
the Next button.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:

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.

Add Flutter to an existing app

Add-to-app
It’s sometimes not practical to rewrite your entire application in
Flutter all at once. For those situations,
Flutter can be integrated into your existing
application piecemeal, as a library or module.
That module can then be imported into your Android or iOS
(currently supported platforms) app to render a part of your
app’s UI in Flutter. Or, just to run shared Dart logic.In a few steps, you can bring the productivity and the expressiveness of
Flutter into your own app.The add-to-app feature supports integrating multiple instances of any screen size.
This can help scenarios such as a hybrid navigation stack with mixed
native and Flutter screens, or a page with multiple partial-screen Flutter
views.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.

Supported features

Add to Android applications

Add to iOS applications
See our add-to-app GitHub Samples repository
for sample projects in Android and iOS that import
a Flutter module for UI.

Get started
To get started, see our project integration guide for
Android and iOS:

API usage
After Flutter is integrated into your project,
see our API usage guides at the following links:

Limitations

Add Flutter to Android

Topics

Integrate a Flutter module into your 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 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:
android {
//...
defaultConfig {
ndk {
// Filter for architectures supported by Flutter.
abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86_64'
}
}
}
The Flutter engine has an x86 and 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
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.Android Studio supports add-to-app flows on Android Studio 2022.2 or later
with the Flutter plugin for IntelliJ.
To build your app, the Android Studio plugin configures your
Android project to add your Flutter module as a dependency.Open your Android project in Android Studio.Go to File > New > New Project….
The New Project dialog displays.Click Flutter.If asked to provide your Flutter SDK path, do so and click Next.Complete the configuration of your Flutter module.If you have an existing project:If you need to create a new Flutter project:Click Finish.lightbulb 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
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
Let’s assume that you have an existing Android app at
some/path/MyApp, and that you want your Flutter
project as a sibling:This creates a some/path/flutter_module/ Flutter module project
with some Dart code to get you started and an .android/
hidden subfolder. The .android folder contains an
Android project that can both help you run a barebones
standalone version of your Flutter module via flutter run
and it’s also a wrapper that helps bootstrap the Flutter
module an embeddable Android library.info Note
Add custom Android code to your own existing
application’s project or a plugin,
not to the module in .android/.
Changes made in your module’s .android/
directory won’t appear in your existing Android
project using the module.Do not source control the .android/ directory
since it’s autogenerated. Before building the
module on a new machine, run flutter pub get
in the flutter_module directory first to regenerate
the .android/ directory before building the
Android project using the Flutter module.info 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 11 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.
android {
//...
compileOptions {
sourceCompatibility 11 // The minimum value
targetCompatibility 11 // 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.Remove the repositories block in all of your app’s build.gradle files.Add the dependencyResolutionManagement displayed in this step to the
settings.gradle file.

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.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.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)
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: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: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 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 Android project’s app/build.gradle file.
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")
}
The profileImplementation ID is a custom configuration to be
implemented in the app/build.gradle file of a host project.
configurations {
getByName("profileImplementation") {
}
}
include(":app")
dependencyResolutionManagement {
repositories {
maven(url = "https://storage.googleapis.com/download.flutter.io")
maven(url = "some/path/flutter_module_project/build/host/outputs/repo")
}
}
error 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.lightbulb 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
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.lightbulb 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.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
// Include the host app project.
include ':app' // assumed existing content
setBinding(new Binding([gradle: this])) // new
evaluate(new File( // new
settingsDir.parentFile, // new
'flutter_module/.android/include_flutter.groovy' // new
)) // 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.Introduce an implementation dependency on the Flutter
module from your app:
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 guide.

Add a Flutter screen to an 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: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.info Note
Make sure to use the following import:
myButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
startActivity(
FlutterActivity.createDefaultIntent(currentActivity)
);
}
});
myButton.setOnClickListener {
startActivity(
FlutterActivity.createDefaultIntent(this)
)
}
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.
myButton.addOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
startActivity(
FlutterActivity
.withNewEngine()
.initialRoute("/my_route")
.build(currentActivity)
);
}
});
myButton.setOnClickListener {
startActivity(
FlutterActivity
.withNewEngine()
.initialRoute("/my_route")
.build(this)
)
}
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:
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);
}
}
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)
}
}
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.info 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:
myButton.addOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
startActivity(
FlutterActivity
.withCachedEngine("my_engine_id")
.build(currentActivity)
);
}
});
myButton.setOnClickListener {
startActivity(
FlutterActivity
.withCachedEngine("my_engine_id")
.build(this)
)
}
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.info 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().info 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.info 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:
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);
}
}
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)
}
}
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 Activitys and Fragments 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
FlutterActivitys 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 Activitys that render
with a translucent background. Create or update an Android theme with the
following property:Then, apply the translucent theme to your FlutterActivity.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:
// 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)
);
// 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)
);
You now have a FlutterActivity with a transparent background.info 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.

Add a Flutter Fragment to an 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: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:
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();
}
}
}
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()
}
}
}
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:
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);
}
}
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)
}
}
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 next.

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.
// 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);
FlutterFragment.withCachedEngine("my_engine_id").build();
// 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)
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:
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);
}
}
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)
}
}
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 Activitys and Fragments 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 FlutterFragments, with different
FlutterEngines. 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:
// With a new FlutterEngine.
FlutterFragment flutterFragment = FlutterFragment.withNewEngine()
.initialRoute("myInitialRoute/")
.build();
// With a new FlutterEngine.
val flutterFragment = FlutterFragment.withNewEngine()
.initialRoute("myInitialRoute/")
.build()
info 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
FlutterFragments 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:
FlutterFragment flutterFragment = FlutterFragment.withNewEngine()
.dartEntrypoint("mySpecialEntrypoint")
.build();
val 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.info 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,
SurfaceViews 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:
// 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();
// 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()
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.info 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 Views, which means it appears
above all other Views. 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 Views 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:
// 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();
// 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()

The relationship between FlutterFragment and its Activity
Some apps choose to use Fragments 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, Fragments 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:
// 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();
// 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()
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.info Note
Some plugins might expect or require an Activity reference.
Ensure that none of your plugins require an Activity
before you disable access.

Add a Flutter View to an Android app
warning Warning
Integrating via a FlutterView
is advanced usage and requires manually creating custom, application specific
bindings.Integrating via a FlutterView
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)
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.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
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
and the FlutterEngine
present in the FlutterActivityAndFragmentDelegate
in your own application’s code. The connections made in the FlutterActivityAndFragmentDelegate
are done automatically when using a FlutterActivity
or a FlutterFragment,
but since the FlutterView
in this case is being added to an Activity or Fragment in your application,
you must recreate the connections manually. Otherwise, the FlutterView
will not render anything or have other missing functionalities.A sample FlutterViewEngine
class shows one such possible implementation of an application-specific
connection between an Activity, a FlutterView
and a FlutterEngine.

APIs to implement
The absolute minimum implementation needed for Flutter to draw anything at all
is to:The reverse detachFromFlutterEngine and other lifecycle methods on the LifecycleChannel
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
demo class or in the FlutterActivityAndFragmentDelegate
to ensure a correct functioning of other features such as clipboards, system
UI overlay, plugins etc.

Manage plugins and dependencies in add-to-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: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.info 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:
…
dependencies {
…
implementation 'com.crashlytics.sdk.android:crashlytics:2.10.1'
…
}
…
And your Flutter module also depends on
firebase_crashlytics via 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:
…
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:
…
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.

Add Flutter to iOS

Topics

Integrate a Flutter module into your iOS project
Flutter UI components can be incrementally added into your existing iOS
application as embedded frameworks. There are a few ways to embed Flutter
in your existing application.Use the CocoaPods dependency manager and installed Flutter SDK.
In this case, the flutter_module is compiled from
the source each time the app is built. (Recommended.)Create frameworks for the Flutter engine, your compiled Dart code,
and all Flutter plugins. Here, you manually embed the frameworks,
and update your existing application’s build settings in Xcode.
This can be useful for teams that don’t want to require every developer
to have the Flutter SDK and Cocoapods installed locally.Create frameworks for your compiled Dart code,
and all Flutter plugins. Use CocoaPods for the Flutter engine.
With this option, embed the frameworks for your application
and the plugins in Xcode, but distribute the
Flutter engine as a CocoaPods podspec.
This is similar to the second option, but it provides
an alternative to distributing the large Flutter.xcframework.For examples using an app built with UIKit,
see the iOS directories in the add_to_app code samples.
For an example using SwiftUI, see the iOS directory in News Feed App.

System requirements
Your development environment must meet the
macOS system requirements for Flutter
with Xcode installed.
Flutter supports iOS 12 and later.
Additionally, you will need CocoaPods
version 1.10 or later.

Create a Flutter module
To embed Flutter into your existing application,
using any of the methods mentioned above,
first create a Flutter module.From the command line, run:A Flutter module project is created at some/path/my_flutter/.
If you are using the first method mentioned above,
the module should be created 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 --debug or flutter build ios.
You can also run the module in
Android Studio/IntelliJ or VS Code with
the Flutter and Dart plugins. This project contains a
single-view example version of your module before it’s
embedded in your existing application,
which is useful for incrementally
testing the Flutter-only parts of your code.

Module organization
The my_flutter module directory structure is similar to a
normal Flutter application:Add your Dart code to the lib/ directory.Add Flutter dependencies to my_flutter/pubspec.yaml,
including Flutter packages and plugins.The .ios/ hidden subfolder contains an Xcode workspace where
you can run a standalone version of your module.
It is a wrapper project to bootstrap your Flutter code,
and contains helper scripts to facilitate building frameworks or
embedding the module into your existing application with CocoaPods.info 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.Do not source control the .ios/ directory since it’s autogenerated.
Before building the module on a new machine, run flutter pub get
in the my_flutter directory first to regenerate the .ios/
directory before building iOS project using the Flutter module.

Embed the Flutter module in your existing application
After you have developed your Flutter module,
you can embed it using the methods described at the top of the page.info Note
You can run in Debug mode on a simulator or a real device,
and Release on a real device. Learn more about
Flutter’s build modesTo leverage Flutter debugging functionality
such as hot reload, see Debugging your add-to-app module.Using Flutter increases your app size.

Option A - Embed with CocoaPods and the Flutter SDK
This method requires every developer working on your
project to have a locally installed version of the Flutter SDK.
The Flutter module is compiled from source each time the app is built.
Simply build your application in Xcode to automatically
run the script to embed your Dart and plugin code.
This allows rapid iteration with the most up-to-date
version of your Flutter module without running additional
commands outside of Xcode.The following example assumes that your existing
application and the Flutter module are in sibling
directories. If you have a different directory structure,
you might need to adjust the relative paths.If your existing application (MyApp) doesn’t
already have a Podfile, run pod init in the
MyApp directory to create one.
You can find more details on using
CocoaPods in the CocoaPods getting started guide.Add the following lines to your Podfile:
flutter_application_path = '../my_flutter'
load File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb')
For each Podfile target that needs to
embed Flutter, call install_all_flutter_pods(flutter_application_path).
target 'MyApp' do
install_all_flutter_pods(flutter_application_path)
end
In the Podfile’s post_install block, call flutter_post_install(installer).
post_install do |installer|
flutter_post_install(installer) if defined?(flutter_post_install)
end
info Note
The flutter_post_install method (added in Flutter 3.1.0),
adds build settings to support native Apple Silicon arm64 iOS simulators.
Include the if defined?(flutter_post_install) check to ensure your Podfile
is valid if you are running on older versions of Flutter that don’t have this method.Run pod install.info Note
When you change the Flutter plugin dependencies in
my_flutter/pubspec.yaml, run flutter pub get
in your Flutter module directory to refresh the list
of plugins read by the podhelper.rb script.
Then, run pod install again from
your application at some/path/MyApp.The podhelper.rb script embeds your plugins,
Flutter.framework, and App.framework into your project.Your app’s Debug and Release build configurations embed
the Debug or Release build modes of Flutter, respectively.
Add a Profile build configuration
to your app to test in profile mode.lightbulb Tip
Flutter.framework is the bundle for the Flutter engine,
and App.framework is the compiled Dart code for this project.Open MyApp.xcworkspace in Xcode.
You can now build the project using ⌘B.

Option B - Embed frameworks in Xcode
Alternatively, you can generate the necessary frameworks
and embed them in your application by manually editing
your existing Xcode project. You might do this if members of your
team can’t locally install Flutter SDK and CocoaPods,
or if you don’t want to use CocoaPods
as a dependency manager in your existing applications.
You must run flutter build ios-framework
every time you make code changes in your Flutter module.The following example assumes that you want to generate the
frameworks to some/path/MyApp/Flutter/.warning Warning
Always use Flutter.xcframework and App.xcframework
from the same directory. Mixing .xcframework imports
from different directories (such as Profile/Flutter.xcframework
with Debug/App.xcframework) causes runtime crashes.Link and embed the generated frameworks into your existing
application in Xcode. There are multiple ways to do
this—use the method that is best for your project.
Link on the frameworks
For example, you can drag the frameworks from
some/path/MyApp/Flutter/Release/ in Finder
into your target’s Build
Settings > Build Phases > Link Binary With Libraries.In the target’s build settings, add $(PROJECT_DIR)/Flutter/Release/
to the Framework Search Paths (FRAMEWORK_SEARCH_PATHS).lightbulb Tip
To use the simulator, you will need to
embed the Debug version of the Flutter frameworks in your
Debug build configuration. To do this
you can use $(PROJECT_DIR)/Flutter/$(CONFIGURATION)
in the Framework Search Paths (FRAMEWORK_SEARCH_PATHS)
build setting. This embeds the Release frameworks in the Release configuration,
and the Debug frameworks in the Debug Configuration.You must also open MyApp.xcodeproj/project.pbxproj (from Finder)
and replace path = Flutter/Release/example.xcframework;
with path = "Flutter/$(CONFIGURATION)/example.xcframework";
for all added frameworks. (Note the added ".)
Embed the frameworks
The generated dynamic frameworks must be embedded
into your app to be loaded at runtime.error Important
Plugins might produce static or dynamic frameworks.
Static frameworks should be linked on, but never embedded.
If you embed a static framework into your application,
your application is not publishable to the App Store
and fails with a
Found an unexpected Mach-O header code archive error.After linking the frameworks, you should see them in the
Frameworks, Libraries, and Embedded Content
section of your target’s General settings.
To embed the dynamic frameworks
select Embed & Sign.They will then appear under Embed Frameworks within
Build Phases as follows:You should now be able to build the project in Xcode using ⌘B.

Option C - Embed application and plugin frameworks in Xcode and Flutter framework with CocoaPods
Alternatively, instead of distributing the large Flutter.xcframework
to other developers, machines, or continuous integration systems,
you can instead generate Flutter as CocoaPods podspec by adding
the flag --cocoapods. This produces a Flutter.podspec
instead of an engine Flutter.xcframework.
The App.xcframework and plugin frameworks are generated
as described in Option B.To generate the Flutter.podspec and frameworks, run the following
from the command line in the root of your Flutter module:Host apps using CocoaPods can add Flutter to their Podfile:
pod 'Flutter', :podspec => 'some/path/MyApp/Flutter/[build mode]/Flutter.podspec'
info 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 the generated App.xcframework,
FlutterPluginRegistrant.xcframework,
and any plugin frameworks into your existing application
as described in Option B.

Local Network Privacy Permissions
On iOS 14 and higher, enable the Dart multicast DNS
service in the Debug version of your app
to add debugging functionalities such as hot-reload and
DevTools via flutter attach.warning Warning
This service must not be enabled in the Release
version of your app, or you might experience App Store rejections.One way to do this is to maintain a separate copy of your app’s Info.plist per
build configuration. The following instructions assume
the default Debug and Release.
Adjust the names as needed depending on your app’s build configurations.Rename your app’s Info.plist to Info-Debug.plist.
Make a copy of it called Info-Release.plist and add it to your Xcode project.In Info-Debug.plist only add the key NSBonjourServices
and set the value to an array with the string _dartVmService._tcp.
Note Xcode will display this as “Bonjour services”.Optionally, add the key NSLocalNetworkUsageDescription set to your
desired customized permission dialog text.In your target’s build settings, change the Info.plist File
(INFOPLIST_FILE) setting path from path/to/Info.plist to path/to/Info-$(CONFIGURATION).plist.This will resolve to the path Info-Debug.plist in Debug and
Info-Release.plist in Release.Alternatively, you can explicitly set the Debug path to Info-Debug.plist
and the Release path to Info-Release.plist.If the Info-Release.plist copy is in your target’s Build Settings > Build Phases > Copy Bundle
Resources build phase, remove it.The first Flutter screen loaded by your Debug app will now prompt
for local network permission. The permission can also be allowed by enabling
Settings > Privacy > Local Network > Your App.

Apple Silicon (arm64 Macs)
On an Apple Silicon (M1) Mac, 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 and you must exclude arm64 from the simulator
architectures in your host app.In your host app target, find the Excluded Architectures (EXCLUDED_ARCHS) build setting.
Click the right arrow disclosure indicator icon to expand the available build configurations.
Hover over Debug and click the plus icon. Change Any SDK to Any iOS Simulator SDK.
Add arm64 to the build settings value.When done correctly, Xcode will add "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64; to your project.pbxproj file.Repeat for any iOS unit test targets.

Development
You can now add a Flutter screen to your existing application.

Add a Flutter screen to an 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, you start a
FlutterEngine and a FlutterViewController.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.lightbulb Tip
It’s generally recommended to pre-warm a long-lived
FlutterEngine for your application because: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 ObservableObject.
We then pass this FlutterEngine into a ContentView using the
environmentObject() property.
import SwiftUI
import Flutter
// The following library connects plugins with iOS platform code to this app.
import FlutterPluginRegistrant
class FlutterDependencies: ObservableObject {
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 using EnvironmentObject.
@StateObject var flutterDependencies = FlutterDependencies()
var body: some Scene {
WindowGroup {
ContentView().environmentObject(flutterDependencies)
}
}
}
As an example, we demonstrate creating a
FlutterEngine, exposed as a property, on app startup in
the app delegate.
import UIKit
import Flutter
// The following library connects plugins with iOS platform code to this app.
import FlutterPluginRegistrant
@UIApplicationMain
class AppDelegate: FlutterAppDelegate { // More on the FlutterAppDelegate.
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);
}
}
In this example, we create a FlutterEngine
object inside a SwiftUI ObservableObject.
We then pass this FlutterEngine into a
ContentView using the environmentObject() property.
@import UIKit;
@import Flutter;
@interface AppDelegate : FlutterAppDelegate // More on the FlutterAppDelegate below.
@property (nonatomic,strong) FlutterEngine *flutterEngine;
@end
// 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
Button hooked to present a FlutterViewController.
The FlutterViewController constructor takes the pre-warmed
FlutterEngine as an argument. FlutterEngine is passed in
as an EnvironmentObject via flutterDependencies.
import SwiftUI
import Flutter
struct ContentView: View {
// Flutter dependencies are passed in an EnvironmentObject.
@EnvironmentObject var flutterDependencies: FlutterDependencies
// Button is created to call the showFlutter function when pressed.
var body: some View {
Button("Show Flutter!") {
showFlutter()
}
}
func showFlutter() {
// Get the RootViewController.
guard
let windowScene = UIApplication.shared.connectedScenes
.first(where: { $0.activationState == .foregroundActive && $0 is UIWindowScene }) as? UIWindowScene,
let window = windowScene.windows.first(where: \.isKeyWindow),
let rootViewController = window.rootViewController
else { return }
// Create the FlutterViewController.
let flutterViewController = FlutterViewController(
engine: flutterDependencies.flutterEngine,
nibName: nil,
bundle: nil)
flutterViewController.modalPresentationStyle = .overCurrentContext
flutterViewController.isViewOpaque = false
rootViewController.present(flutterViewController, animated: true)
}
}
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.
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)
}
}
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.
@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.info 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.
// Existing code omitted.
func showFlutter() {
let flutterViewController = FlutterViewController(project: nil, nibName: nil, bundle: nil)
present(flutterViewController, animated: true, completion: nil)
}
// 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 the FlutterAppDelegate
Letting your application’s UIApplicationDelegate subclass
FlutterAppDelegate is recommended but not required.The FlutterAppDelegate performs functions such as:

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 that conforms to the ObservableObject protocol as follows:Then, in your view, the AppDelegateis accessible as an EnvironmentObject.

If you can’t directly make FlutterAppDelegate a subclass
If your app delegate can’t directly make FlutterAppDelegate a subclass,
make your app delegate implement the FlutterAppLifeCycleProvider
protocol in order to make sure your plugins receive the necessary callbacks.
Otherwise, plugins that depend on these events might have undefined behavior.For instance:
import Foundation
import Flutter
class AppDelegate: UIResponder, UIApplicationDelegate, FlutterAppLifeCycleProvider, ObservableObject {
private let lifecycleDelegate = FlutterPluginAppLifeCycleDelegate()
let flutterEngine = FlutterEngine(name: "flutter_nps_engine")
override func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
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)
}
}
@import Flutter;
@import UIKit;
@import FlutterPluginRegistrant;
@interface AppDelegate : UIResponder
@property (strong, nonatomic) UIWindow *window;
@property (nonatomic,strong) FlutterEngine *flutterEngine;
@end
The implementation should delegate mostly to a
FlutterPluginAppLifeCycleDelegate:
@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

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.info Note
Dart entrypoint functions other than main()
must be annotated with the following in order to
not be tree-shaken away when compiling:

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:

Route
Starting in Flutter version 1.22, an initial route can be set for your Flutter
WidgetsApp when constructing the FlutterEngine or the
FlutterViewController.This code sets your dart:ui’s window.defaultRouteName
to "/onboarding" instead of "/".Alternatively, to construct a FlutterViewController directly without pre-warming
a FlutterEngine:lightbulb 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.

Debug your add-to-app 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.However, Flutter is now powering the UI in places where you’re showing a
FlutterActivity or FlutterViewController.

Debugging
You might be used to having your suite of favorite Flutter debugging tools
available to you automatically 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.These functionalities are provided by the flutter attach mechanism.
flutter attach can be initiated through different pathways,
such as through the SDK’s CLI tools,
through VS Code or IntelliJ/Android Studio.flutter attach can connect as soon as you run your FlutterEngine, and
remains attached until your FlutterEngine is disposed. But you can invoke
flutter attach before starting your engine. flutter attach waits for
the next available Dart VM that is hosted by your engine.
Terminal
Run flutter attach or flutter attach -d deviceId to attach from the terminal.
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.
Start debugging with VS Code first
If you use VS Code to debug most of your code, start with this section.
Start the Dart debugger in VS Code
To open the Flutter app directory, go to
File >
Open Folder… and choose the my_app directory.Open the lib/main.dart file.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.Type flutter select.Click the Flutter: Select Device command.Choose your target device.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:
Attach to the Flutter process in Xcode
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
If you use Xcode to debug most of your code, start with this section.
Start the Xcode debugger
Open ios/Runner.xcworkspace from your Flutter app directory.Select the correct device using the Scheme menu in the toolbar.If you have no preference, choose iPhone Pro 14.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:Copy the Dart VM service URI.
Attach to the Dart VM in VS Code
To open the command palette, go to
View >
Command Palette…You can also press Cmd + Shift + P.Type debug.Click the Debug: Attach to Flutter on Device command.In the Paste an VM Service URI box, paste the URI you copied
from Xcode and press Enter.You can also create a .vscode/launch.json file in your Flutter module project.
This enables you to attach using the Run > Start Debugging command or F5:
IntelliJ / Android Studio
Select the device on which the Flutter module runs so flutter attach filters for the right start signals.

Wireless debugging
You can debug your app wirelessly on an iOS or Android device
using flutter attach.
iOS
On iOS, you must follow the steps below:Ensure that your device is wirelessly connected to Xcode
as described in the iOS setup guide.Open Xcode > Product > Scheme > Edit SchemeSelect the Arguments tabAdd either --vm-service-host=0.0.0.0 for IPv4,
or --vm-service-host=::0 for IPv6 as a launch argumentYou can determine if you’re on an IPv6 network by opening your Mac’s
Settings > Wi-Fi > Details (of the network you’re connected to) > TCP/IP
and check to see if there is an IPv6 address section.
Android
Ensure that your device is wirelessly connected to Android Studio
as described in the Android setup guide.

Multiple Flutter screens or views

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: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 FlutterEngines, 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.FlutterEngines spawned from FlutterEngineGroup can be used to
connect to UI classes like FlutterActivity or FlutterViewController
in the same way as normally constructed cached FlutterEngines.The first FlutterEngine spawned from the FlutterEngineGroup doesn’t need
to continue surviving in order for subsequent FlutterEngines 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 FlutterEngines 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
FlutterEngines but does remove the ability to spawn additional
FlutterEngines 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.

Load sequence, performance, and memory
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
and iOS 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 is constructed because the
JNI connectors need to reference the Flutter C++ library.
On iOS, this happens when the
FlutterEngine 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 for the first time on
Android and when running a Dart entrypoint
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.

Memory and latency
Showing a Flutter UI has a non-trivial latency cost.
This cost can be lessened by starting the Flutter engine
ahead of time.The most relevant choice for add-to-app scenarios is for you
to decide when to pre-load a FlutterEngine
(that is, to load the Flutter library, start the Dart VM,
and run entrypoint in an isolate), and what the memory and latency
cost is of that pre-warm. You also need to know how the pre-warm
affects the memory and latency cost of rendering a first Flutter
frame when the UI component is subsequently attached
to that FlutterEngine.As of Flutter v1.10.3, and testing on a low-end 2015 class device
in release-AOT mode, pre-warming the FlutterEngine costs:A Flutter UI can be attached during the pre-warm.
The remaining time is joined to the time-to-first-frame latency.Memory-wise, a cost sample (variable,
depending on the use case) could be:Latency-wise,
a cost sample (variable, depending on the use case) could be:The FlutterEngine should be pre-warmed late enough to delay the
memory consumption needed but early enough to avoid combining the
Flutter engine start-up time with the first frame latency of
showing Flutter.The exact timing depends on the app’s structure and heuristics.
An example would be to load the Flutter engine in the screen
before the screen is drawn by Flutter.Given an engine pre-warm, the first frame cost on UI attach is:Memory-wise, the cost is primarily the graphical memory buffer used for
rendering and is dependent on the screen size.Latency-wise, the cost is primarily waiting for the OS callback to provide
Flutter with a rendering surface and compiling the remaining shader programs
that are not pre-emptively predictable. This is a one-time cost.When the Flutter UI component is released, the UI-related memory is freed.
This doesn’t affect the Flutter state, which lives in the FlutterEngine
(unless the FlutterEngine is also released).For performance details on creating more than one FlutterEngine,
see multiple Flutters.

Android Studio and IntelliJ

Installation and setup
Follow the Set up an editor
instructions to install the Dart and Flutter plugins.

Updating the plugins
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:

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:In IntelliJ:
Setting 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:Click Open.error 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:

Running and debugging
info Note
You can debug your app in a few ways.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:

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.info 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.

Run app without breakpoints

Run app with breakpoints

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
info 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.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 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: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.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.The build() function for an AnimatedBuilder draws a subtree that
does not need to be animated, causing unnecessary rebuilds of static
objects.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: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:The Flutter plugin includes the following templates:You can also define custom templates in Settings > Editor > Live Templates.

Keyboard shortcuts
Hot reloadOn 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 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: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
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.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: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
To enable editing of Android code in IntelliJ IDEA, you need to configure the
location of the Android SDK:

Tips and tricks

Troubleshooting

Known issues and feedback
Important known issues that might impact your experience are documented
in the Flutter plugin README file.All known bugs are tracked in the issue trackers:We welcome feedback, both on bugs/issues and feature requests.
Prior to filing new issues:When filing new issues, include the output of flutter doctor.

Visual Studio Code

Installation and setup
Follow the Set up an editor instructions to
install the Dart and Flutter extensions
(also called plugins).

Updating the extension
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:

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:Go to View >
Command Palette….You can also press Ctrl / Cmd +
Shift + P.

Opening a project from existing source code
To open an existing Flutter project:Go to File > Open.You can also press Ctrl / Cmd + O

Editing code and viewing issues
The Flutter extension performs code analysis.
The code analysis can:Navigate to type declarationsFind type usages.View all current source code problems.

Running and debugging
info Note
You can debug your app in a couple of ways.The instructions below describe features available in VS Code.
For information on using launching 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):
info NoteThe 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.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.

Run app without breakpoints
Go to Run > Start Without Debugging.You can also press Ctrl + F5.

Run app with breakpoints
Click Run > Start Debugging.
You can also press F5.
The status bar turns orange to show you are in a debug session.

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.Open the launch.json file in VS Code.If you don’t have a launch.json file:Go to View > Run.You can also press Ctrl / Cmd +
Shift + DThe Run and Debug panel displays.Click create a launch.json file.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: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.

Debugging external libraries
By default, debugging an external library is disabled
in the Flutter extension. To enable:

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: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.

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:You can also define custom snippets by executing
Configure User Snippets from the Command Palette.

Keyboard shortcuts
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: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.

Troubleshooting

Known issues and feedback
All known bugs are tracked in the issue tracker:
Dart and Flutter extensions GitHub issue tracker.
We welcome feedback,
both on bugs/issues and feature requests.Prior to filing new issues:When filing new issues, include flutter doctor output.

DevTools

What is DevTools?
DevTools is a suite of performance and debugging tools
for Dart and Flutter.For a video introduction to DevTools, check out
the following deep dive and use case walkthrough:Dive in to DevTools

What can I do with DevTools?
Here are some of the things you can do with DevTools:We expect you to use DevTools in conjunction with
your existing IDE or command-line based development workflow.

How to start DevTools
See the VS Code, Android Studio/IntelliJ, or
command line pages for instructions on how to start DevTools.

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.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.

Providing feedback
Please give DevTools a try, provide feedback, and file issues
in the DevTools issue tracker. Thanks!

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.

DevTools

What is DevTools?
DevTools is a suite of performance and debugging tools
for Dart and Flutter.For a video introduction to DevTools, check out
the following deep dive and use case walkthrough:Dive in to DevTools

What can I do with DevTools?
Here are some of the things you can do with DevTools:We expect you to use DevTools in conjunction with
your existing IDE or command-line based development workflow.

How to start DevTools
See the VS Code, Android Studio/IntelliJ, or
command line pages for instructions on how to start DevTools.

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.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.

Providing feedback
Please give DevTools a try, provide feedback, and file issues
in the DevTools issue tracker. Thanks!

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.

Install and run DevTools from Android Studio

Install the Flutter plugin
Install 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
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:

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,
DevTools is installed (if it isn’t already), 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.

Install and run DevTools from VS Code

Install 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
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):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
here or in the
VS Code settings editor.
Some recommendation settings for Dart/Flutter in VS Code can be found
here.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).

Install and run DevTools from the command line
To run Dart DevTools from the CLI, you must have dart on your path. Then
you can run the following command to launch DevTools:To upgrade DevTools, upgrade your Dart SDK. If a newer Dart SDK
includes a newer version of DevTools, dart devtools will automatically
launch this version. If which dart points to the Dart SDK included in
your Flutter SDK, then DevTools will be upgraded when you upgrade your
Flutter SDK to a newer version.When you run DevTools from the command line, you should see output that
looks something like:

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: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: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:You can manually connect DevTools to a new app instance
by copying the Observatory link you got from running your app,
such as http://127.0.0.1:52129/QjqebSY4lQ8=/
and pasting it into the connect dialog:

Using the Flutter inspector
info Note
The inspector works with all Flutter applications.

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:

Get started
To debug a layout issue, run the app in debug mode and
open the inspector by clicking the Flutter Inspector
tab on the DevTools toolbar.info Note
You can still access the Flutter inspector directly from
Android Studio/IntelliJ, but you might prefer the
more spacious view when running it from DevTools
in a browser.

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.

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 Layout Explorer
The Flutter Layout Explorer helps you to better understand
Flutter layouts.For an overview of what you can do with this tool, see
the Flutter Explorer video:You might also find the following step-by-step article useful:

Using the Layout Explorer
From the Flutter Inspector, select a widget. The Layout Explorer
supports both flex layouts and fixed size layouts, and has
specific tooling for both kinds.
Flex layouts
When you select a flex widget (for example, Row, Column, Flex)
or a direct child of a flex widget, the flex layout tool will
appear in the Layout Explorer.The Layout Explorer 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.Clicking a widget in the layout 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 Layout 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
Layout 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
Supported values:
crossAxisAlignment
Supported values:
FlexParentData.flex
Layout 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
Layout Explorer supports the two different types of
FlexFit: loose and tight.
Fixed size layouts
When you select a fixed size widget that is not a child
of a flex widget, fixed size layout information will appear
in the Layout Explorer. You can see size, constraint, and padding
information for both the selected widget and its nearest upstream
RenderObject.

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

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:
import 'package:flutter/scheduler.dart';
void setSlowAnimations() {
timeDilation = 5.0;
}
This slows the animations by 5x.
See also
The following links provide more info.The following screen recordings show before and after slowing an animation.

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:
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:
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 is shown with a semi-transparent blue background:
Scroll views
Widgets with scrolling contents (such as list views) are shown with green arrows:
Clipping
Clipping, for example when using the ClipRect widget, are shown
with a dashed pink line with a scissors icon:
Spacers
Spacer widgets are shown with a grey background,
such as this SizedBox without a child:

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 widget can be used to adjust baselines.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:
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.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.Here the progress indicator causes its container to repaint:
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:
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:
import 'package:flutter/rendering.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: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: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:
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:
void showOversizedImages() {
debugInvertOversizedImages = true;
}

More information
You can learn more at the following link:

Details Tree
Select the Widget Details Tree tab to display the details tree for the
selected widget. From here, you can gather useful information about a
widget’s properties, render object, and children.

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.

Inspector settings

Enable hover inspection
Hovering over any widget displays its properties and values.Toggling this value enables or disables the hover inspection functionality.

Package directories
By default, DevTools limits the widgets displayed in the widget tree
to those from the project’s root directory, and those from Flutter. This
filtering only applies to the widgets in the Inspector Widget Tree (left side
of the Inspector) – not the Widget Details Tree (right side of the Inspector
in the same tab view as the Layout Explorer). In the Widget Details Tree, you
will be able to see all widgets in the tree from all packages.In order to show other widgets, a parent directory of theirs must be added to the Package Directories.For example, consider the following directory structure: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
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.

Using the Performance view
info 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.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: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.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 tab
or the Timeline events tab.
(As of DevTools 2.23.1, the Raster stats
is a standalone feature without data per frame).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
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.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.

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: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.

Raster stats tab
info Note
For best results, this tool should be used with
the Impeller rendering engine. When using Skia,
the raster stats reported might be inconsistent
due to the timing of when shaders are compiled.If you have Flutter frames that are janking with
slow raster thread times, this tool might be able
to help you diagnose the source of the slow performance.
To generate raster stats:If you see an expensive layer, find the Dart code in your app
that is producing this layer and investigate further.
You can make changes to your code, hot reload,
and take new snapshots to see if the performance of a layer
was improved by your change.

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.
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:info Note
Frame times might be negatively affected when these options are enabled.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.Watch this video for an example of tracking widget builds

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

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

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.

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.

Using the CPU profiler view
info 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.info 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 ring buffer, and then displays those
CPU samples in the profiler views.

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 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:Table element (self time)

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 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:

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
The flame chart view is a graphical representation of
the 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
DevTools sets a default 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
When viewing a CPU profile, you can filter the data by
library, method name, or UserTag.

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
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.
Also, learn how to analyze CPU usage when the app
uses isolates for parallel computing.

Using the Memory view
The memory view provides insights into details
of the application’s memory allocation and
tools to detect and debug specific issues.info 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.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.

Reasons to use the memory view
Use the memory view for preemptive memory optimization or when
your application experiences one of the following conditions:

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).

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:

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: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: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.
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:
How to fix leak prone code?
The following code is not leak prone,
because:
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,
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
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
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.
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:

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
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
Use the Trace Instances tab to investigate what methods
allocate memory for a set of classes during feature execution:
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
For more information, check out the following resources:

Using the Debug 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.info Note
This page is up to date for DevTools 2.23.0.The Debug console is available from the Inspector,
Debugger, and Memory views.

Watch application output
The console shows the application’s standard output (stdout):

Explore inspected widgets
If you click a widget on the Inspector screen,
the variable for this widget displays in the Console:

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
To drop a variable to the console from a heap snapshot,
do the following:The Console screen displays both live and static
inbound and outbound references, as well as field values:

Using the Network View
info Note
The network view works with all Flutter and Dart applications.

What is it?
The network view allows you to inspect HTTP, HTTPS, and web socket traffic from
your Dart or Flutter application.

How to use it
Network traffic should be recording by default when you open the Network page.
If it is not, click the Resume button in the upper left to
begin polling.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.

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:Any text that is not paired with an available filter key will be queried against
all categories (method, uri, status, type).Example filter queries:

Other resources
HTTP and HTTPs requests are also surfaced in the 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.
The tutorial also uses the view to identify network activity that
causes poor app performance.

Using the debugger
info 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.info Note
The debugger works with all Flutter and Dart applications.
If you are looking for a way to use GDB to remotely debug the
Flutter engine running within an Android app process,
check out flutter_gdb.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
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.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.

Other resources
For more information on debugging and profiling, see the
Debugging page.

Using the Logging view
info 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:

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.

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.

Using the 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.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 installation instructions).

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
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 below for information on
generating size files.

Treemap and table
The treemap and table show the hierarchical data for your app’s size.
Using 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
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.
Using 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.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.In this example, package:a dominates package:d,
so the dominator tree for this data would look like: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.
Using 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: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.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?
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.

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 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 Using 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: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.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. Various strategies
to reduce an app’s size are also discussed.

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.

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.Extension enablement states are stored in a devtools_options.yaml file in the
root of the user’s project (similar to analysis_options.yaml). 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, check out
Dart and Flutter DevTools extensions, a free article on Medium.

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.The Dart and Flutter SDKs include DevTools.
To check your current version of DevTools,
run the following on your command line:
Release notes

Flutter SDK overview
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: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 and the pubspec file
info Note
This page is primarily aimed at folks who write
Flutter apps. If you write packages or plugins,
(perhaps you want to create a federated plugin),
you should check out the
Developing packages and plugins page.Every Flutter project includes a pubspec.yaml file,
often referred to as the pubspec.
A basic pubspec is generated when you create
a new Flutter project. It’s located at the top
of the project tree and contains metadata about
the project that the Dart and Flutter tooling
needs to know. The pubspec is written in
YAML, which is human readable, but be aware
that white space (tabs v spaces) matters.The pubspec file specifies dependencies
that the project requires, such as particular packages
(and their versions), fonts, or image files.
It also specifies other requirements, such as
dependencies on developer packages (like
testing or mocking packages), or particular
constraints on the version of the Flutter SDK.Fields common to both Dart and Flutter projects
are described in the pubspec file on dart.dev.
This page lists Flutter-specific fields
that are only valid for a Flutter project.info Note
The first time you build your project, it
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.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.Here is an example of a Flutter project pubspec file.
The Flutter only fields are highlighted.

Assets
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).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,
see the resolution aware section of the
Assets and images page.
For information on adding assets from package
dependencies, see the
asset images in package dependencies
section in the same page.

Fonts
As shown in the above example,
each entry in the fonts section should have a
family key with the font family name,
and a fonts key with a list specifying the
asset and other descriptors for the font.For examples of using fonts
see the Use a custom font and
Export fonts from a package recipes in the
Flutter cookbook.

More information
For more information on packages, plugins,
and pubspec files, see the following:

Flutter fix
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.lightbulb 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.
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.
A sample code action in VS Code

Applying project-wide fixes
dart fix Decoding FlutterTo 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:To apply all changes in bulk, run the
following command:For more information on Flutter deprecations, see
Deprecation lifetime in Flutter, a free article
on Flutter’s Medium publication.

Code formatting
While your code might follow any preferred style—in our
experience—teams of developers might find it more productive to: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
Editor 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
Install the Dart plugin (see
Editor setup)
to get automatic formatting of code in Android Studio and IntelliJ.
To format your code in the current source code window: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
To correct code formatting in the command line interface (CLI),
run the dart format command:

Using trailing commas
Flutter code often involves building fairly deep tree-shaped data structures,
for example in a build method. To get good automatic formatting,
we recommend you adopt the optional trailing commas.
The guideline for adding a trailing comma is simple: Always
add a trailing comma at the end of a parameter list in
functions, methods, and constructors where you care about
keeping the formatting you crafted.
This helps the automatic formatter to insert an appropriate
amount of line breaks for Flutter-style code.Here is an example of automatically formatted code with trailing commas:And the same code automatically formatted code without trailing commas:

Flutter 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.Flutter is a cross-platform UI toolkit that is designed to allow code reuse
across operating systems such as iOS and Android, 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:

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, 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 may
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 (through Impeller on iOS and coming to Android,
and Skia on other platforms) text layout,
file and network I/O, accessibility support,
plugin architecture, and a Dart runtime
and compile toolchain.The engine is exposed to the Flutter framework through
dart: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:The Flutter framework is relatively small; many higher-level features that
developers might use are implemented as packages, including platform plugins
like camera and
webview, as well as platform-agnostic
features like characters,
http, and
animations that build upon the core Dart and
Flutter libraries. Some of these packages come from the broader ecosystem,
covering services like in-app
payments, Apple
authentication, and
animations.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 AppFramework (source code)Engine (source code)Embedder (source code)Runner

Reactive user interfaces
On the surface, Flutter is a reactive, declarative UI framework,
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,
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 via the controller, and then the model pushes the new state to the view
via 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: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.

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:
import 'package:flutter/material.dart';
import 'package:flutter/services.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 of both the
iOS Toggle
control
and the one for the
Android equivalent.This approach provides several benefits:

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,
Animations and Tweens, cover most of the design space. In the rendering
layer, RenderObjects 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
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, 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,
ConstrainedBox,
Align,
Padding,
DecoratedBox, and
Transform 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() 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 of some
text and
various
buttons. As needed,
the framework recursively asks each widget to build until the tree is entirely
described by concrete renderable
objects. 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 widgets1, 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.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 naïve 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.However, if the unique characteristics of a widget need 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, and
(because the widget itself is immutable) they store mutable state in a separate
class that subclasses State.
StatefulWidgets 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()
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: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:Whenever one of the ExamWidget or GradeWidget objects needs data from
StudentState, it can now access it with a command such as: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. InheritedWidgets 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 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:
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, 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 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 may 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 Skia (or, in the future, Impeller) for rendering.
Flutter also embeds its own copy of Skia as part of the engine,
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.info Note
Flutter 3.10 set Impeller as the default
rendering engine on iOS. It’s in preview
for Android behind a flag.

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
Consider this code fragment that demonstrates a widget hierarchy:
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
for Container, you can see that if the color is not null, it inserts a
ColoredBox representing the color:Correspondingly, the Image and Text widgets might insert child widgets such
as RawImage and RichText during the build process. The eventual widget
hierarchy may therefore be deeper than what the code represents, as in this
case2:This explains why, when you examine the tree through a debug tool such as the
Flutter inspector, part of the
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:RenderObjectElements 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, 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). 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.
RenderObjects are primitives:
RenderParagraph
renders text,
RenderImage renders
an image, and
RenderTransform
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()
method.The box constraint model is very powerful as a way to layout objects in O(n)
time: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 widget,
the child object can examine the passed-down constraints and use those to
determine how it will use them, for example:
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 topic.The root of all RenderObjects 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 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.

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)
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 that supports remoting
Flutter sessions through a VNC-style framebuffer or this worked example for
Raspberry Pi.Each platform has its own set of APIs and constraints. Some brief
platform-specific notes:

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).The following is a short platform channel example of a Dart call to a receiving
event handler in Kotlin (Android) or Swift (iOS):
// Dart side
const channel = MethodChannel('foo');
final greeting = await channel.invokeMethod('bar', 'world') as String;
print(greeting);
Further examples of using platform channels, including examples for desktop
platforms, can be found in the flutter/packages
repository. There are also thousands of plugins
already available for Flutter that cover many common
scenarios, ranging from Firebase to ads to device hardware like camera and
Bluetooth.

Foreign Function Interface
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 package
serves an equivalent 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:
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
);
}

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
and UiKitView)
that let you embed this kind of content on each platform. Platform views can be
integrated with other Flutter content3. Each of
these widgets acts as an intermediary to the underlying operating system. For
example, on Android, AndroidView serves three primary functions: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 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.

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.
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 provides a reimplementation of the
engine on top of standard browser APIs.
We currently have two options for
rendering Flutter content on the web: HTML and WebGL.
In HTML mode, Flutter uses HTML, CSS, Canvas, and SVG.
To render to WebGL, Flutter uses a version of Skia
compiled to WebAssembly called
CanvasKit.
While HTML mode offers the best code size characteristics,
CanvasKit provides the fastest path to the
browser’s graphics stack,
and offers somewhat higher graphical fidelity with the
native mobile targets4.The web version of the architectural layer diagram is as follows: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.
It’s also worthy to note that Dart has very few language semantic differences
across all 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, a compiler that supports
incremental compilation and therefore allows hot restart (although not currently
hot reload) for apps. Conversely, when you are ready to create a production app
for the web, dart2js, Dart’s
highly-optimized production JavaScript compiler is used, packaging the Flutter
core and framework along with your application into a minified source file that
can be deployed to any web server. Code can be offered in a single file or split
into multiple files through deferred imports.

Further information
For those interested in more information about the internals of Flutter, the
Inside Flutter whitepaper
provides a useful guide to the framework’s design philosophy.Footnotes: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.4 One example is shadows, which have to be
approximated with DOM-equivalent primitives at the cost of some fidelity.

Inside Flutter
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 again1
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
twice2 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 system3.
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
clean4.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 InheritedWidgets 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 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, Animations 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 grid6.

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 sanity 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 paradigm7 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
end8,
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
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.

Understanding constraints
info Note
If you are experiencing specific layout errors,
you might check out Common Flutter 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:Flutter layout can’t really be understood without knowing
this rule, so Flutter developers should learn it early on.In more detail: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: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 section.
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 red = Colors.red;
const green = Colors.green;
const blue = Colors.blue;
const 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.
Example 1
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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.info 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
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.info 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
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:If you revisit 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,
the Center allows the red Container to be smaller,
but not bigger than the screen.
Unbounded constraints
info 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.
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 GlasbergMarcelo originally published this content as
Flutter: The Advanced Layout Rule Even Beginners Must Know
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.info Note
To better understand how Flutter implements layout
constraints, check out the following 5-minute video:Decoding Flutter: Unbounded height and width

Hot reload
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 running Dart Virtual Machine (VM).
After the VM 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.

How to perform a hot reload
To hot reload a Flutter app:If you’re working in an IDE/editor that supports Flutter’s IDE tools,
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: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.What is the difference between hot reload, hot restart,
and full restart?Flutter web currently supports hot restart but not
hot reload.
Controls for run, run debug, hot reload, and hot restart in Android StudioA 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: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:
enum Color {
red,
green,
blue,
}
After the change:
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:
class A {
T? i;
}
After the change:
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.
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:
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:
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:
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:
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:
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:
const foo = 1;
const bar = foo; // Convert foo to a const...
void onClick() {
print(foo);
print(bar);
}
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 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:
import 'package:flutter/material.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:
import 'package:flutter/widgets.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: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.

FAQ

Introduction
This page collects some common questions asked about
Flutter. You might also check out the following
specialized FAQs:

What is Flutter?
Flutter is Google’s portable UI toolkit for crafting beautiful,
natively compiled applications for mobile, web,
and desktop from a single codebase.
Flutter works with existing code,
is used by developers and organizations around
the world, and is free and open source.

Who is Flutter for?
For users, Flutter makes beautiful apps come to life.For developers, Flutter lowers the bar to entry for building apps.
It speeds app development and reduces the cost and complexity
of app production across platforms.For designers, Flutter provides a canvas for
high-end user experiences. Fast Company described
Flutter as one of the top design ideas of the decade for
its ability to turn concepts into production code
without the compromises imposed by typical frameworks.
It also acts as a productive prototyping tool
with drag-and-drop tools like FlutterFlow
and web-based IDEs like Zapp!.For engineering managers and businesses,
Flutter allows the unification of app
developers into a single mobile, web,
and desktop app team, building branded
apps for multiple platforms out of a single codebase.
Flutter speeds feature development and synchronizes
release schedules across the entire customer base.

How much development experience do I need to use Flutter?
Flutter is approachable to programmers familiar with
object-oriented concepts (classes, methods, variables,
etc) and imperative programming concepts (loops,
conditionals, etc).We have seen people with very little programming
experience learn and use Flutter for prototyping
and app development.

What kinds of apps can I build with Flutter?
Flutter is designed to support mobile apps that run
on both Android and iOS, as well as interactive apps
that you want to run on your web pages or on the desktop.Apps that need to deliver highly branded designs
are particularly well suited for Flutter.
However, you can also create pixel-perfect experiences
that match the Android and iOS design languages with Flutter.Flutter’s package ecosystem supports a wide
variety of hardware (such as camera, GPS, network,
and storage) and services (such as payments, cloud
storage, authentication, and ads).

Who makes Flutter?
Flutter is an open source project,
with contributions from Google and other
companies and individuals.

Who uses Flutter?
Developers inside and outside of Google use
Flutter to build beautiful natively-compiled
apps for iOS and Android. To learn about some
of these apps, visit the showcase.

What makes Flutter unique?
Flutter is different than most other options
for building mobile apps because it doesn’t rely
on web browser technology nor the set of widgets
that ship with each device. Instead, Flutter uses
its own high-performance rendering engine to draw widgets.In addition, Flutter is different because it only
has a thin layer of C/C++ code. Flutter implements
most of its system (compositing, gestures, animation,
framework, widgets, etc) in Dart (a modern,
concise, object-oriented language) that developers
can easily approach read, change, replace, or remove.
This gives developers tremendous control over the system,
as well as significantly lowers the bar to approachability
for the majority of the system.

Should I build my next production app with Flutter?
Flutter 1 was launched on Dec 4th, 2018,
Flutter 2 on March 3rd, 2021, and
Flutter 3 on May 10th, 2023.
As of May 2023, over one million apps have shipped using
Flutter to many hundreds of millions of devices.
Check out some sample apps in the showcase.Flutter ships updates on a roughly-quarterly
cadence that improve stability and performance
and address commonly-requested user features.

What does Flutter provide?

What is inside the Flutter SDK?
Flutter includes:

Does Flutter work with any editors or IDEs?
We provide plugins for VS Code,
Android Studio, and IntelliJ IDEA.
See editor configuration for setup details,
and VS Code and Android Studio/IntelliJ
for tips on how to use the plugins.Alternatively, you can use the flutter command
from a terminal, along with one
of the many editors that support editing Dart.

Does Flutter come with a framework?
Yes! Flutter ships with a modern react-style framework.
Flutter’s framework is designed to be layered and
customizable (and optional). Developers can choose to
use only parts of the framework, or even replace
upper layers of the framework entirely.

Does Flutter come with widgets?
Yes! Flutter ships with a set of
high-quality Material Design and Cupertino
(iOS-style) widgets, layouts, and themes.
Of course, these widgets are only a starting point.
Flutter is designed to make it easy to create your own
widgets, or customize the existing widgets.

Does Flutter support Material Design?
Yes! The Flutter and Material teams collaborate closely,
and Material is fully supported. For more information,
check out the Material 2 and Material 3 widgets
in the widget catalog.

Does Flutter come with a testing framework?
Yes, Flutter provides APIs for writing unit and
integration tests. Learn more about testing with Flutter.We use our own testing capabilities to test our SDK,
and we measure our test coverage on every commit.

Does Flutter come with debugging tools?
Yes, Flutter comes with Flutter DevTools (also
called Dart DevTools). For more information, see
Debugging with Flutter and the Flutter DevTools docs.

Does Flutter come with a dependency injection framework?
We don’t ship with an opinionated solution,
but there are a variety of packages that offer
dependency injection and service location,
such as injectable, get_it, kiwi, and riverpod.

Technology

What technology is Flutter built with?
Flutter is built with C, C++, Dart,
Skia (a 2D rendering engine),
and Impeller (the default rendering engine on iOS).
See this architecture diagram
for a better picture of the main components.
For a more detailed description of the layered architecture
of Flutter, read the architectural overview.

How does Flutter run my code on Android?
The engine’s C and C++ code are compiled with Android’s NDK.
The Dart code (both the SDK’s and yours)
are ahead-of-time (AOT) compiled into native, ARM, and x86
libraries. Those libraries are included in a “runner”
Android project, and the whole thing is built into an .apk.
When launched, the app loads the Flutter library.
Any rendering, input, or event handling, and so on,
is delegated to the compiled Flutter and app code.
This is similar to the way many game engines work.During debug mode, Flutter uses a virtual machine (VM)
to run its code in order to enable stateful hot reload,
a feature that lets you make changes to your running code
without recompilation. You’ll see a “debug” banner in
the top right-hand corner of your app when running in this mode,
to remind you that performance is not characteristic of
the finished release app.

How does Flutter run my code on iOS?
The engine’s C and C++ code are compiled with LLVM.
The Dart code (both the SDK’s and yours)
are ahead-of-time (AOT) compiled into a native, ARM library.
That library is included in a “runner” iOS project,
and the whole thing is built into an .ipa.
When launched, the app loads the Flutter library.
Any rendering, input or event handling, and so on,
are delegated to the compiled Flutter and app code.
This is similar to the way many game engines work.During debug mode, Flutter uses a virtual machine (VM)
to run its code in order to enable stateful hot reload,
a feature that lets you make changes to your
running code without recompilation. You’ll see
a “debug” banner in the top right-hand corner of
your app when running in this mode, to remind you that
performance is not characteristic of the finished release app.

Does Flutter use my operating system’s built-in platform widgets?
No. Instead, Flutter provides a set of widgets
(including Material Design and Cupertino (iOS-styled) widgets),
managed and rendered by Flutter’s framework and engine.
You can browse a catalog of Flutter’s widgets.We believe that the end result is higher quality apps.
If we reused the built-in platform widgets,
the quality and performance of Flutter apps would be limited
by the flexibility and quality of those widgets.In Android, for example, there’s a hard-coded set
of gestures and fixed rules for disambiguating them.
In Flutter, you can write your own gesture recognizer
that is a first class participant in the gesture system.
Moreover, two widgets authored by different people can
coordinate to disambiguate gestures.Modern app design trends point towards designers and
users wanting more motion-rich UIs and brand-first designs.
In order to achieve that level of customized, beautiful design,
Flutter is architectured to drive pixels instead
of the built-in widgets.By using the same renderer, framework, and set of widgets,
it’s easier to publish for multiple platforms from the same
codebase, without having to do careful and costly planning
to align different feature sets and API characteristics.By using a single language, a single framework,
and a single set of libraries for all of your code
(regardless if your UI is different for each platform or not),
we also aim to help lower app development and maintenance costs.

What happens when my mobile OS updates and introduces new widgets?
The Flutter team watches the adoption and demand for new mobile
widgets from iOS and Android, and aims to work with the community
to build support for new widgets. This work might come in the form
of lower-level framework features, new composable widgets,
or new widget implementations.Flutter’s layered architecture is designed to support numerous
widget libraries, and we encourage and support the community in
building and maintaining widget libraries.

What happens when my mobile OS updates and introduces new platform capabilities?
Flutter’s interop and plugin system is designed to allow
developers to access new mobile OS features and capabilities
immediately. Developers don’t have to wait for the Flutter team
to expose the new mobile OS capability.

What operating systems can I use to build a Flutter app?
Flutter supports development using Linux, macOS, ChromeOS,
and Windows.

What language is Flutter written in?
Dart, a fast-growing modern language optimized
for client apps. The underlying graphics framework
and the Dart virtual machine are implemented in C/C++.

Why did Flutter choose to use Dart?
During the initial development phase,
the Flutter team looked at a lot of
languages and runtimes, and ultimately
adopted Dart for the framework and widgets.
Flutter used four primary dimensions for evaluation,
and considered the needs of framework authors,
developers, and end users. We found many languages
met some requirements, but Dart scored highly on
all of our evaluation dimensions and met all our
requirements and criteria.Dart runtimes and compilers support the combination of
two critical features for Flutter: a JIT-based fast
development cycle that allows for shape changing and
stateful hot reloads in a language with types,
plus an Ahead-of-Time compiler that emits efficient
ARM code for fast startup and predictable performance of
production deployments.In addition, we have the opportunity to work closely
with the Dart community, which is actively investing
resources in improving Dart for use in Flutter. For
example, when we adopted Dart,
the language didn’t have an ahead-of-time
toolchain for producing native binaries,
which is instrumental in achieving predictable,
high performance, but now the language does because the Dart team
built it for Flutter. Similarly, the Dart VM has
previously been optimized for throughput but the
team is now optimizing the VM for latency, which is more
important for Flutter’s workload.Dart scores highly for us on the following primary criteria:

Can Flutter run any Dart code?
Flutter can run Dart code that doesn’t directly or
transitively import dart:mirrors or dart:html.

How big is the Flutter engine?
In March 2021, we measured the download size of a
minimal Flutter app (no Material Components,
just a single Center widget, built with flutter build
apk --split-per-abi), bundled and compressed as a release APK,
to be approximately 4.3 MB for ARM32, and 4.8 MB for ARM64.On ARM32, the core engine is approximately 3.4 MB
(compressed), the framework + app code is approximately
765 KB (compressed), the LICENSE file is 58 KB
(compressed), and necessary Java code (classes.dex)
is 120 KB (compressed).In ARM64, the core engine is approximately 4.0 MB
(compressed), the framework + app code is approximately
659 KB (compressed), the LICENSE file is 58 KB
(compressed), and necessary Java code (classes.dex)
is 120 KB (compressed).These numbers were measured using apkanalyzer,
which is also built into Android Studio.On iOS, a release IPA of the same app has a download
size of 10.9 MB on an iPhone X, as reported by Apple’s
App Store Connect. The IPA is larger than the APK mainly
because Apple encrypts binaries within the IPA, making the
compression less efficient (see the
iOS App Store Specific Considerations
section of Apple’s QA1795).info Note
The release engine binary used to include LLVM IR (bitcode).
However, Apple deprecated bitcode in Xcode 14 and removed support,
so it has been removed from the Flutter 3.7 release.Of course, we recommend that you measure your own app.
To do that, see Measuring your app’s size.

How does Flutter define a pixel?
Flutter uses logical pixels,
and often refers to them merely as “pixels”.
Flutter’s devicePixelRatio expresses the ratio
between physical pixels and logical CSS pixels.

Capabilities

What kind of app performance can I expect?
You can expect excellent performance. Flutter is
designed to help developers easily achieve a constant 60fps.
Flutter apps run using natively compiled code—no
interpreters are involved.
This means that Flutter apps start quickly.

What kind of developer cycles can I expect? How long between edit and refresh?
Flutter implements a hot reload developer cycle. You can expect
sub-second reload times, on a device or an emulator/simulator.Flutter’s hot reload is stateful so the app state
is retained after a reload. This means you can quickly iterate
on a screen deeply nested in your app, without starting
from the home screen after every reload.

How is hot reload different from hot restart?
Hot reload works by injecting updated source code files
into the running Dart VM (Virtual Machine). This doesn’t
only add new classes, but also adds methods and fields
to existing classes, and changes existing functions.
Hot restart resets the state to the app’s initial state.For more information, see Hot reload.

Where can I deploy my Flutter app?
You can compile and deploy your Flutter app to iOS, Android,
web, and desktop.

What devices and OS versions does Flutter run on?
We support and test running Flutter on a variety
of low-end to high-end platforms. For a detailed list
of the platforms on which we test, see
the list of supported platforms.Flutter supports building ahead-of-time (AOT) compiled libraries
for x86_64, armeabi-v7a, and arm64-v8a.Apps built for ARMv7 or ARM64 run fine (using ARM emulation)
on many x86 Android devices.We support developing Flutter apps on a range of platforms.
See the system requirements listed under each
development operating system.

Does Flutter run on the web?
Yes, web support is available in the stable channel.
For more details, check out the web instructions.

Can I use Flutter to build desktop apps?
Yes, desktop support is in stable for Windows,
macOS, and Linux.

Can I use Flutter inside of my existing native app?
Yes, learn more in the add-to-app section of our website.

Can I access platform services and APIs like sensors and local storage?
Yes. Flutter gives developers out-of-the-box access to some
platform-specific services and APIs from the operating system.
However, we want to avoid the “lowest common denominator” problem
with most cross-platform APIs, so we don’t intend to build
cross-platform APIs for all native services and APIs.A number of platform services and APIs have
ready-made packages available on pub.dev.
Using an existing package is easy.Finally, we encourage developers to use Flutter’s
asynchronous message passing system to create your
own integrations with platform and third-party APIs.
Developers can expose as much or as little of the
platform APIs as they need, and build layers of
abstractions that are a best fit for their project.

Can I extend and customize the bundled widgets?
Absolutely. Flutter’s widget system was designed
to be easily customizable.Rather than having each widget provide a large number of parameters,
Flutter embraces composition. Widgets are built out of smaller
widgets that you can reuse and combine in novel ways to make
custom widgets. For example, rather than subclassing a generic
button widget, ElevatedButton combines a Material widget with a
GestureDetector widget. The Material widget provides the visual
design and the GestureDetector widget provides the
interaction design.To create a button with a custom visual design, you can combine
widgets that implement your visual design with a GestureDetector,
which provides the interaction design. For example,
CupertinoButton follows this approach and combines a
GestureDetector with several other widgets that implement its
visual design.Composition gives you maximum control over the visual and
interaction design of your widgets while also allowing a
large amount of code reuse. In the framework, we’ve decomposed
complex widgets to pieces that separately implement
the visual, interaction, and motion design. You can remix
these widgets however you like to make your own custom
widgets that have full range of expression.

Why would I want to share layout code across iOS and Android?
You can choose to implement different app layouts for
iOS and Android. Developers are free to check the mobile OS
at runtime and render different layouts,
though we find this practice to be rare.More and more, we see mobile app layouts and designs evolving
to be more brand-driven and unified across platforms.
This implies a strong motivation to share layout and UI
code across iOS and Android.The brand identity and customization of the app’s
aesthetic design is now becoming more important than
strictly adhering to traditional platform aesthetics.
For example, app designs often require custom fonts, colors,
shapes, motion, and more in order to clearly convey their
brand identity.We also see common layout patterns deployed across
iOS and Android. For example, the “bottom nav bar”
pattern can now be naturally found across iOS and Android.
There seems to be a convergence of design ideas
across mobile platforms.

Can I interop with my mobile platform’s default programming language?
Yes, Flutter supports calling into the platform,
including integrating with Java or Kotlin code on Android,
and ObjectiveC or Swift code on iOS.
This is enabled by a flexible message passing style
where a Flutter app might send and receive messages
to the mobile platform using a BasicMessageChannel.Learn more about accessing platform and third-party services
in Flutter with platform channels.Here is an example project that shows how to use a
platform channel to access battery state information on
iOS and Android.

Does Flutter come with a reflection / mirrors system?
No. Dart includes dart:mirrors,
which provides type reflection. But since
Flutter apps are pre-compiled for production,
and binary size is always a concern with mobile apps,
this library is unavailable for Flutter apps.Using static analysis we can strip out anything that isn’t
used (“tree shaking”). If you import a huge Dart library
but only use a self-contained two-line method,
then you only pay the cost of the two-line method,
even if that Dart library itself imports dozens and
dozens of other libraries. This guarantee is only secure
if Dart can identify the code path at compile time.
To date, we’ve found other approaches for specific needs
that offer a better trade-off, such as code generation.

How do I do international­ization (i18n), localization (l10n), and accessibility (a11y) in Flutter?
Learn more about i18n and l10n in the
internationalization tutorial.Learn more about a11y in the
accessibility documentation.

How do I write parallel and/or concurrent apps for Flutter?
Flutter supports isolates. Isolates are separate heaps in
Flutter’s VM, and they are able to run in parallel
(usually implemented as separate threads). Isolates
communicate by sending and receiving asynchronous messages.Check out an example of using isolates with Flutter.

Can I run Dart code in the background of a Flutter app?
Yes, you can run Dart code in a background process on both
iOS and Android. For more information, see the free Medium article
Executing Dart in the Background with Flutter Plugins and Geofencing.

Can I use JSON/XML/protobuffers (and so on) with Flutter?
Absolutely. There are libraries on
pub.dev for JSON, XML, protobufs,
and many other utilities and formats.For a detailed writeup on using JSON with Flutter,
check out the JSON tutorial.

Can I build 3D (OpenGL) apps with Flutter?
Today we don’t support 3D using OpenGL ES or similar.
We have long-term plans to expose an optimized 3D API,
but right now we’re focused on 2D.

Why is my APK or IPA so big?
Usually, assets including images, sound files, fonts, and so on,
are the bulk of an APK or IPA. Various tools in the
Android and iOS ecosystems can help you understand
what’s inside of your APK or IPA.Also, be sure to create a release build
of your APK or IPA with the Flutter tools.
A release build is usually much smaller
than a debug build.Learn more about creating a
release build of your Android app,
and creating a release build of your iOS app.
Also, check out Measuring your app’s size.

Do Flutter apps run on Chromebooks?
We have seen Flutter apps run on some Chromebooks.
We are tracking issues related to running Flutter on
Chromebooks.

Is Flutter ABI compatible?
Flutter and Dart don’t offer application binary interface (ABI)
compatibility. Offering ABI compatibility is not a current
goal for Flutter or Dart.

Framework

Why is the build() method on State, rather than StatefulWidget?
Putting a Widget build(BuildContext context) method on State
rather putting a Widget build(BuildContext context, State state)
method on StatefulWidget gives developers more flexibility when
subclassing StatefulWidget. You can read a more
detailed discussion on the API docs for State.build.

Where is Flutter’s markup language? Why doesn’t Flutter have a markup syntax?
Flutter UIs are built with an imperative, object-oriented
language (Dart, the same language used to build Flutter’s
framework). Flutter doesn’t ship with a declarative markup.We found that UIs dynamically built with code allow for
more flexibility. For example, we have found it difficult
for a rigid markup system to express and produce
customized widgets with bespoke behaviors.We have also found that our “code-first” approach better allows
for features like hot reload and dynamic environment adaptations.It’s possible to create a custom language that is then
converted to widgets on the fly. Because build methods
are “just code”, they can do anything,
including interpreting markup and turning it into widgets.

My app has a Debug banner/ribbon in the upper right. Why am I seeing that?
By default, the flutter run command uses the
debug build configuration.The debug configuration runs your Dart code in a VM (Virtual Machine)
enabling a fast development cycle with hot reload
(release builds are compiled using the standard Android
and iOS toolchains).The debug configuration also checks all asserts, which helps
you catch errors early during development, but imposes a
runtime cost. The “Debug” banner indicates that these checks
are enabled. You can run your app without these checks by
using either the --profile or --release flag to flutter run.If your IDE uses the Flutter plugin,
you can launch the app in profile or release mode.
For VS Code, use the Run > Start debugging
or Run > Run without debugging menu entries.
For IntelliJ, use the menu entries
Run > Flutter Run in Profile Mode or Release Mode.

What programming paradigm does Flutter’s framework use?
Flutter is a multi-paradigm programming environment.
Many programming techniques developed over the past few decades
are used in Flutter. We use each one where we believe
the strengths of the technique make it particularly well-suited.
In no particular order:

Project

Where can I get support?
If you think you’ve encountered a bug, file it in our
issue tracker. You might also use
Stack Overflow for “HOWTO” type questions.
For discussions, join our mailing list at
flutter-dev@googlegroups.com or seek us out on Discord.For more information, see our Community page.

How do I get involved?
Flutter is open source, and we encourage you to contribute.
You can start by simply filing issues for feature requests
and bugs in our issue tracker.We recommend that you join our mailing list at
flutter-dev@googlegroups.com and let us know how you’re
using Flutter and what you’d like to do with it.If you’re interested in contributing code, you can start
by reading our Contributing guide, and check out our
list of easy starter issues.Finally, you can connect with helpful Flutter communities.
For more information, see the Community page.

Is Flutter open source?
Yes, Flutter is open source technology.
You can find the project on GitHub.

Which software license(s) apply to Flutter and its dependencies?
Flutter includes two components: an engine that ships as a
dynamically linked binary, and the Dart framework as a separate
binary that the engine loads. The engine uses multiple software
components with many dependencies; view the complete list
in its license file.The framework is entirely self-contained and requires
only one license.In addition, any Dart packages you use might have their
own license requirements.

How can I determine the licenses my Flutter application needs to show?
There’s an API to find the list of licenses you need to show:If your application has a Drawer, add an
AboutListTile.If your application doesn’t have a Drawer but does use the
Material Components library, call either showAboutDialog
or showLicensePage.For a more custom approach, you can get the raw licenses from the
LicenseRegistry.

Who works on Flutter?
We all do! Flutter is an open source project.
Currently, the bulk of the development is done
by engineers at Google. If you’re excited about Flutter,
we encourage you to join the community and
contribute to Flutter!

What are Flutter’s guiding principles?
We believe the following:We are focused on three things:

Will Apple reject my Flutter app?
We can’t speak for Apple, but their App Store contains
many apps built with framework technologies such as Flutter.
Indeed, Flutter uses the same fundamental architectural
model as Unity, the engine that powers many of the
most popular games on the Apple store.Apple has frequently featured well-designed apps
that are built with Flutter,
including Hamilton and Reflectly.As with any app submitted to the Apple store,
apps built with Flutter should follow Apple’s
guidelines for App Store submission.

Books about Flutter
Here’s a collection of books about Flutter,
in alphabetical order.
If you find another one that we should add,
file an issue and (feel free to)
submit a PR (sample) to add it yourself.Also, check the Flutter version that the book
was written under. Anything published before
Flutter 3.10/Dart 3 (May 2023),
won’t reflect the latest version of Dart and
might not include null safety;
anything published before Flutter 3.16 (November 2023)
won’t reflect that Material 3 is now
Flutter’s default theme.
See the what’s new
page to view Flutter’s latest release.
The following sections have more information about each book.

Beginning App Development with Flutter
by Rap PayneEasy to understand starter book. Currently the best-selling and highest rated Flutter book on Amazon.
Beginning Flutter: A Hands On Guide to App Development
by Marco L. NapoliBuild your first app in Flutter - no experience necessary.
Flutter Apps Development: Build Cross-Platform Flutter Apps with Trust
by Mouaz M. Al-ShahmehThis book teaches what you need to know to build your first Flutter app. You will learn about the basics of Flutter (widgets, state management, and navigation), as well as how to build a variety of different app types (games, social media apps, and e-commerce apps). By the end of this book, you will be able to build beautiful, high-performance mobile apps using Flutter.
Flutter Apprentice
by Michael Katz, Kevin David Moore, Vincent Ngo, and Vincenzo GuzziBuild for both iOS and Android With Flutter! With Flutter and Flutter Apprentice, you can achieve the dream of building fast applications, faster.
Flutter Complete Reference 2.0
by Alberto MiolaThe book’s first eight chapters are dedicated to Dart 3.0 and all its features. Over 500 pages are about the Flutter framework: widgets basics, state management, animations, navigation, and more.
Flutter: Développez vos applications mobiles multiplateformes avec Dart
by Julien TrillardCe livre sur Flutter s’adresse aux développeurs, initiés comme plus aguerris, qui souhaitent disposer des connaissances nécessaires pour créer de A à Z des applications mobiles multiplateformes avec le framework de Google
Flutter for Beginners
by Thomas Bailey and Alessandro BiessekAn introductory guide to building cross-platform mobile applications with Flutter 2.5 and Dart.
Flutter in Action
by Eric WindmillFlutter in Action teaches you to build professional-quality mobile applications using the Flutter SDK and the Dart programming language.
Flutter Libraries We Love
by Codemagic“Flutter libraries We Love” focuses on 11 different categories of Flutter libraries. Each category lists available libraries as well as a highlighted library that is covered in more detail – including pros and cons, developer’s perspective, and real-life code examples. The code snippets of all 11 highlighted libraries support Flutter 2 with sound null safety.
Flutter Succinctly
by Ed FreitasApp UI in Flutter-from Zero to Hero.
Google Flutter Mobile Development Quick Start Guide
by Prajyot Mainkar and Salvatore GirodanoA fast-paced guide to get you started with cross-platform mobile application development with Google Flutter
Learn Google Flutter Fast
by Mark ClowLearn Google Flutter by example. Over 65 example mini-apps.
Managing State in Flutter Pragmatically
by Waleed ArshadExplore popular state management techniques in Flutter.
Practical Flutter
by Frank ZammettiImprove your Mobile Development with Google’s latest open source SDK.
Pragmatic Flutter
by Priyanka TyagiBuilding Cross-Platform Mobile Apps for Android, iOS, Web & Desktop
Programming Flutter
by Carmine ZaccagninoNative, Cross-Platform Apps the Easy Way. Doesn’t require previous Dart knowledge.
Flutter Architectures
by Atul VashishtWrite code with a good architecture

Videos
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 livestream
Flutter Forward playlist

Get ready for Flutter Forward
Flutter Forward is happening on Jan 25th, 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

Learning to Fly
info 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.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!Building my first Flutter app | Learning to Fly
Learning to Fly playlistHere are some of the series offered on the
flutterdev YouTube channel.
For more information, check out all
of the Flutter channel’s playlists.

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.Introducing Decoding Flutter
Decoding Flutter playlist

Flutter widget of the week
Do you have 60 seconds?
Each one-minute video highlights a Flutter widget.Introducing widget of the week
Flutter widget of the week playlist

Flutter package of the week
If you have another minute (or two),
each of these videos highlights a Flutter package.flutter_slidable package
Flutter package of the week playlist

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.Introducing the Boring Flutter show
The Boring Flutter show playlist

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 Engage 2021
Flutter Engage 2021 was a day-long event that
officially launched Flutter 2.Check out the Flutter Engage 2021 highlights reel.Watch recordings of the sessions on the Flutter YouTube channel.Keynote
Flutter Engage 2021 playlistWatch recordings of the sessions offered by the Flutter community.Flutter Engage community talks playlist

Flutter in focus
Five-to-ten minute tutorials (more or less) on using Flutter.Introducing Flutter in focus
Flutter in focus playlist

Conference talks
Here are a some Flutter talks given at various conferences.Conference talks playlist

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

Online courses
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 listed alphabetically.
To include your course, submit a PR:

Bootstrap into Dart
New to the Dart language?
We compiled our favorite resources to
help you quickly learn Dart.
Many people have reported that
Dart is easy and fun to learn.
We hope these resources make Dart easy for
you to learn, too.Want to learn more and perhaps contribute?
Check out the Dart community.

Create useful bug reports
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

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 the command in verbose mode
Follow these steps only if your issue is related to the
flutter tool.

Provide the most recent logs

Provide the crash report

Issues: flutter/flutter
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and
privacy statement. We’ll occasionally send you account related emails.
Already on GitHub?
Sign in
to your account

Issues list

Who is Dash?
This is Dash:Dash is the mascot for the Dart language and the Flutter framework.info Note
You can now make your own digital Dash using
Dashatar, a Flutter app for web and mobile!

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.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.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?
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. Introducing Dash at the January 2018 Dart Conference.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 swagSince 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
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 event
in Brooklyn, New York, on December 11, 2019.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.

flutter: The Flutter command-line tool
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:To run pub commands using the flutter tool:To view all commands that flutter supports:To get the current version of the Flutter SDK, including its framework, engine,
and tools:

flutter commands
The following table shows which commands you can use with the flutter tool:For additional help on any of the commands, enter flutter help
or follow the links in the More information column.
You can also get details on pub commands — for example,
flutter help pub outdated.
: This is an alphabetical list of nearly every widget that is bundled with Flutter.
AbsorbPointer: 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 at itself. It still consumes space during layout and paints its child as usual. It just prevents its children from being the target of located events, because it returns true from RenderBox.hitTest.
AlertDialog: Hovering containers that prompt app users to provide more data or make a decision.
Align: A widget that aligns its child within itself and optionally sizes itself based on the child's size.
AnimatedAlign: Animated transition that moves the child's position over a given duration whenever the given alignment changes.
AnimatedBuilder: A general-purpose widget for building animations. AnimatedBuilder is useful for more complex widgets that wish to include animation as part of a larger build function To use AnimatedBuilder, construct the widget and pass it a builder function.
AnimatedContainer: A container that gradually changes its values over a period of time.
AnimatedCrossFade: A widget that cross-fades between two given children and animates itself between their sizes.
AnimatedDefaultTextStyle: Animated version of DefaultTextStyle which automatically transitions the default text style (the text style to apply to descendant Text widgets without explicit style) over a given duration whenever the given style changes.
AnimatedList: A scrolling container that animates items when they are inserted or removed.
AnimatedListState: The state for a scrolling container that animates items when they are inserted or removed.
AnimatedModalBarrier: A widget that prevents the user from interacting with widgets behind itself.
AnimatedOpacity: Animated version of Opacity which automatically transitions the child's opacity over a given duration whenever the given opacity changes.
AnimatedPhysicalModel: Animated version of PhysicalModel.
AnimatedPositioned: Animated version of Positioned which automatically transitions the child's position over a given duration whenever the given position changes.
AnimatedSize: Animated widget that automatically transitions its size over a given duration whenever the given child's size changes.
AnimatedWidget: A widget that rebuilds when the given Listenable changes value.
AppBar: Container that displays content and actions at the top of a screen.
AspectRatio: A widget that attempts to size the child to a specific aspect ratio.
AssetBundle: 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 can be transparently loaded over a network (e.g., from a NetworkAssetBundle) or from the local file system without blocking the application's user interface.Applications have a rootBundle, which contains the resources that were packaged with the application when it was built.
Autocomplete: A widget for helping the user make a selection by entering some text and choosing from among a list of options.
BackdropFilter: A widget that applies a filter to the existing painted content and then paints a child. The filter will be applied to all the area within its parent or ancestor widget's clip. If there's no clip, the filter will be applied to the full screen.The results of the filter will be blended back into the background using the blendMode parameter. The only value for blendMode that is supported on all platforms is BlendMode.srcOver which works well for most scenes.
Badge: Icon-like block that conveys dynamic content such as counts or status. It can include labels or numbers.
Baseline: Container that positions its child according to the child's baseline.
Bottom app bar: Container that displays navigation and key actions at the bottom of a screen.
Bottom sheet: Containers that anchor supplementary content to the bottom of the screen.
BottomNavigationBar: Container that includes tools to explore and switch between top-level views in a single tap.
BottomSheet: Bottom sheets slide up from the bottom of the screen to reveal more content. You can call showBottomSheet() to implement a persistent bottom sheet or A modal bottom sheet The BottomSheet widget itself is rarely used directly. Instead, prefer to create a persistent bottom sheet with ScaffoldState.showBottomSheet or Scaffold.bottomSheet, and a modal bottom sheet with showModalBottomSheet.
Card: Container for short, related pieces of content displayed in a box with rounded corners and a drop shadow.
Center: Alignment block that centers its child within itself.
Checkbox: Form control that app users can set or clear to select one or more options from a set.
Chip: Small blocks that simplify entering information, making selections, filtering content, or triggering actions.
CircularProgressIndicator: Circular progress indicator that spins to indicate a busy application.
ClipOval: A widget that clips its child using an oval.
ClipPath: A widget that clips its child using a path.
ClipRect: A widget that clips its child using a rectangle.
Column: Layout a list of child widgets in the vertical direction.
Common buttons: Clickable blocks that start an action, such as sending an email, sharing a document, or liking a comment.
ConstrainedBox: A widget that imposes additional constraints on its child.
Container: A convenience widget that combines common painting, positioning, and sizing widgets.
CupertinoActionSheet: An iOS-style modal bottom action sheet to choose an option among many.
CupertinoActivityIndicator: An iOS-style activity indicator. Displays a circular 'spinner'.
CupertinoAlertDialog: An iOS-style alert dialog.
CupertinoButton: An iOS-style button.
CupertinoContextMenu: An iOS-style full-screen modal route that opens when the child is long-pressed. Used to display relevant actions for your content.
CupertinoDatePicker: An iOS-style date or date and time picker.
CupertinoDialogAction: A button typically used in a CupertinoAlertDialog.
CupertinoFullscreenDialogTransition: An iOS-style transition used for summoning fullscreen dialogs.
CupertinoListSection: Container that uses the iOS style to display a scrollable view.
CupertinoListTile: A block that uses the iOS style to create a row in a list.
CupertinoNavigationBar: Container at the top of a screen that uses the iOS style. Many developers use this with `CupertinoPageScaffold`.
CupertinoPageScaffold: Basic iOS style page layout structure. Positions a navigation bar and content on a background.
CupertinoPageTransition: Provides an iOS-style page transition animation.
CupertinoPicker: An iOS-style picker control. Used to select an item in a short list.
CupertinoPopupSurface: Rounded rectangle surface that looks like an iOS popup surface, such as an alert dialog or action sheet.
CupertinoScrollbar: An iOS-style scrollbar that indicates which portion of a scrollable widget is currently visible.
CupertinoSearchTextField: An iOS-style search field.
CupertinoSegmentedControl: An iOS-style segmented control. Used to select mutually exclusive options in a horizontal list.
CupertinoSlider: Used to select from a range of values.
CupertinoSlidingSegmentedControl: An iOS-13-style segmented control. Used to select mutually exclusive options in a horizontal list.
CupertinoSliverNavigationBar: An iOS-styled navigation bar with iOS-11-style large titles using slivers.
CupertinoSwitch: An iOS-style switch. Used to toggle the on/off state of a single setting.
CupertinoTabBar: An iOS-style bottom tab bar. Typically used with CupertinoTabScaffold.
CupertinoTabScaffold: Tabbed iOS app structure. Positions a tab bar on top of tabs of content.
CupertinoTabView: Root content of a tab that supports parallel navigation between tabs. Typically used with CupertinoTabScaffold.
CupertinoTextField: An iOS-style text field.
CupertinoTimerPicker: An iOS-style countdown timer picker.
CustomMultiChildLayout: A widget that uses a delegate to size and position multiple children.
CustomPaint: A widget that provides a canvas on which to draw during the paint phase.
CustomScrollView: A ScrollView that creates custom scroll effects using slivers.
CustomSingleChildLayout: A widget that defers the layout of its single child to a delegate.
DataTable: Data tables display sets of raw data. They usually appear in desktop enterprise products. The DataTable widget implements this component.
DatePicker: Calendar interface used to select a date or a range of dates.
DecoratedBox: A widget that paints a Decoration either before or after its child paints.
DecoratedBoxTransition: Animated version of a DecoratedBox that animates the different properties of its Decoration.
DefaultTextStyle: The text style to apply to descendant Text widgets without explicit style.
Dismissible: 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 out of view. Following the slide animation, if resizeDuration is non-null, the Dismissible widget animates its height (or width, whichever is perpendicular to the dismiss direction) to zero over the resizeDuration.
Divider: Thin line that groups content in lists and containers.
DragTarget: 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 is asked whether it will accept the data the draggable is carrying. If the user does drop the draggable on top of the drag target (and the drag target has indicated that it will accept the draggable's data), then the drag target is asked to accept the draggable's data.
Draggable: 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 widget that tracks the user's finger across the screen. If the user lifts their finger while on top of a DragTarget, that target is given the opportunity to accept the data carried by the draggable.The ignoringFeedbackPointer defaults to true, which means that the feedback widget ignores the pointer during hit testing.
Similarly, ignoringFeedbackSemantics defaults to true, and the feedback also ignores semantics when building the semantics tree.On multitouch devices, multiple drags can occur simultaneously because there can be multiple pointers in contact with the device at once.: To limit the number of simultaneous drags, use the maxSimultaneousDrags property. The default is to allow an unlimited number of simultaneous drags.
This widget displays child when zero drags are under way. If childWhenDragging is non-null, this widget instead displays childWhenDragging when one or more drags are underway. Otherwise, this widget always displays child.
DraggableScrollableSheet: A container for a Scrollable that responds to drag gestures by resizing the scrollable until a limit is reached, and then scrolling.
Drawer: A Material Design panel that slides in horizontally from the edge of a Scaffold to show navigation links in an application.
DropdownButton: Shows the currently selected item and an arrow that opens a menu for selecting another item.
ElevatedButton: A Material Design elevated button. A filled button whose material elevates when pressed.
ExcludeSemantics: 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 only be confusing
Expanded: A widget that expands a child of a Row, Column, or Flex.
ExpansionPanel: Expansion panels contain creation flows and allow lightweight editing of an element. The ExpansionPanel widget implements this component.
Extended FloatingActionButton: Clickable block that triggers an action. These wider blocks can fit a text label and provide a larger target area.
FadeTransition: Animates the opacity of a widget.
FittedBox: Scales and positions its child within itself according to fit.
FloatingActionButton: Clickable block containing an icon that keeps a key action always in reach.
Flow: A widget that implements the flow layout algorithm.
FlutterLogo: The Flutter logo, in widget form. This widget respects the IconTheme.
Form: An optional container for grouping together multiple form field widgets (e.g. TextField widgets).
FormField: 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 UI
FractionalTranslation: A widget that applies a translation expressed as a fraction of the box's size before painting its child.
FractionallySizedBox: A widget that sizes its child to a fraction of the total available space. For more details about the layout algorithm, see RenderFractionallySizedOverflowBox.
FutureBuilder: Widget that builds itself based on the latest snapshot of interaction with a Future.
GestureDetector: 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 that child for its sizing behavior. If it does not have a child, it grows to fit the parent instead.
GridView: A grid list consists of a repeated pattern of cells arrayed in a vertical and horizontal layout. The GridView widget implements this component.
Hero: A widget that marks its child as being a candidate for hero animations.
Icon: A Material Design icon.
IconButton: Clickable icons to prompt app users to take supplementary actions.
IgnorePointer: 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 space during layout and paints its child as usual. It just cannot be the target of located events, because it returns false from RenderBox.hitTest.
Image: A widget that displays an image.
ImplicitlyAnimatedWidget: An abstract class for building widgets that animate changes to their properties.
IndexedStack: A Stack that shows a single child from a list of children.
InteractiveViewer: A widget that enables pan and zoom interactions with its child.
IntrinsicHeight: A widget that sizes its child to the child's intrinsic height.
IntrinsicWidth: A widget that sizes its child to the child's intrinsic width.
KeyboardListener: A widget that calls a callback whenever the user presses or releases a key on a keyboard.
LayoutBuilder: Builds a widget tree that can depend on the parent widget's size.
LimitedBox: A box that limits its size only when it's unconstrained.
LinearProgressIndicator: Vertical line that changes color as an ongoing process, such as loading an app or submitting a form, completes.
ListBody: A widget that arranges its children sequentially along a given axis, forcing them to the dimension of the parent in the other axis.
ListTile: A single fixed-height row that typically contains some text as well as a leading or trailing icon.
ListView: 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 In the cross axis, the children are required to fill the ListView.
LongPressDraggable: Makes its child draggable starting from long press.
MaterialApp: A convenience widget that wraps a number of widgets that are commonly required for applications implementing Material Design.
MediaQuery: Establishes a subtree in which media queries resolve to the given data.
Menu: Container that displays a list of choices on a temporary surface.
MergeSemantics: A widget that merges the semantics of its descendants.
Navigation rail: Persistent container on the leading edge of tablet and desktop screens to navigate to parts of an app.
NavigationBar: Persistent container that enables switching between primary destinations in an app.
NavigationDrawer: Container that slides from the leading edge of the app to navigate to other sections in an app.
Navigator: 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 in order to display their logical history using an Overlay with
the most recently visited pages visually on top of the older pages. Using this pattern lets the navigator visually transition from one page to another by moving the widgets around in the overlay.
Similarly, the navigator can be used to show a dialog by positioning the dialog widget above the current page.
NestedScrollView: A scrolling view inside of which can be nested other scrolling views, with their scroll positions being intrinsically linked.
NotificationListener: A widget that listens for Notifications bubbling up the tree.
Offstage: 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 testing, and without taking any room in the parent.Offstage children are still active: they can receive focus and have keyboard input directed to them.
Opacity: A widget that makes its child partially transparent.
OutlinedButton: A Material Design outlined button, essentially a TextButton with an outlined border.
OverflowBox: A widget that imposes different constraints on its child than it gets from its parent, possibly allowing the child to overflow the parent.
Padding: A widget that insets its child by the given padding.
PageView: A scrollable list that works page by page.
Placeholder: A widget that draws a box that represents where other widgets will one day be added.
PopupMenuButton: Displays a menu when pressed and calls onSelected when the menu is dismissed because an item was selected.
PositionedTransition: 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 lifetimeof the animation.Only works if it's the child of a Stack.
Radio: Form control that app users can set or clear to select only one option from a set.
RawImage: A widget that displays a dart:ui.Image directly.
RefreshIndicator: A Material Design pull-to-refresh wrapper for scrollables.
ReorderableListView: A list whose items the user can interactively reorder by dragging.
RichText: 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 has an associated style that is used for that subtree. The text might break across multiple lines or might all be displayed on the same line depending on the layout constraints.
RotatedBox: A widget that rotates its child by a integral number of quarter turns.
RotationTransition: Animates the rotation of a widget.
Row: Layout a list of child widgets in the horizontal direction.
Scaffold: Implements the basic Material Design visual layout structure. This class provides APIs for showing drawers, snack bars, and bottom sheets.
ScaleTransition: Animates the scale of transformed widget.
ScrollConfiguration: Controls how Scrollable widgets behave in a subtree.
Scrollable: 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 displays the children, is constructed.
Scrollbar: A Material Design scrollbar. A scrollbar indicates which portion of a Scrollable widget is actually visible.
SegmentedButton: Single or multiple selected clickable blocks to help people select options, switch views, or sort elements.
Semantics: 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 analysis software to determine the meaning of the application.
SimpleDialog: Simple dialogs can provide additional details or actions about a list item. For example they can display avatars icons clarifying subtext or orthogonal actions
SingleChildScrollView: 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 visible, for example a clock face in a time picker, but you need to make sure it can be scrolled if the container gets too small in one axis (the scroll direction).
SizeTransition: Animates its own size and clips and aligns the child.
SizedBox: A box with a specified size. If given a child, this widget forces its child to have a specific width and/or height .
SizedOverflowBox: A widget that is a specific size but passes its original constraints through to its child, which will probably overflow.
SlideTransition: Animates the position of a widget relative to its normal position.
Slider: Form control that enables selecting a range of values.
SliverAppBar: A material design app bar that integrates with a CustomScrollView.
SliverChildBuilderDelegate: A delegate that supplies children for slivers using a builder callback.
SliverChildListDelegate: A delegate that supplies children for slivers using an explicit list.
SliverFixedExtentList: A sliver that places multiple box children with the same main axis extent in a linear array.
SliverGrid: A sliver that places multiple box children in a two dimensional arrangement.
SliverList: A sliver that places multiple box children in a linear array along the main axis.
SliverPadding: A sliver that applies padding on each side of another sliver.
SliverPersistentHeader: A sliver whose size varies when the sliver is scrolled to the edge of the viewport opposite the sliver's GrowthDirection.
SliverToBoxAdapter: A sliver that contains a single box widget.
SnackBar: Brief messages about app processes that display at the bottom of the screen.
Stack: 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 a gradient and a button attached to the bottom.
Stepper: A Material Design stepper widget that displays progress through a sequence of steps.
StreamBuilder: Widget that builds itself based on the latest snapshot of interaction with a Stream.
Switch: Toggle control that changes the state of a single item to on or off.
TabBar: Layered containers that organize content across different screens, data sets, and other interactions.
TabBarView: A page view that displays the widget which corresponds to the currently selected tab. Typically used in conjunction with a TabBar.
TabController: Coordinates tab selection between a TabBar and a TabBarView.
TabPageSelector: Displays a row of small circular indicators, one per tab. The selected tab's indicator is highlighted. Often used in conjunction with a TabBarView.
Table: Displays child widgets in rows and columns.
Text: A run of text with a single style.
TextButton: A Material Design text button. A simple flat button without a border outline.
TextField: Box into which app users can enter text. They appear in forms and dialogs.
Theme: Applies a theme to descendant widgets. A theme describes the colors and typographic choices of an application.
TimePicker: Clock interface used to select and set a specific time.
Tooltip: 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 provide a message which will be shown when the widget is long pressed.
Transform: A widget that applies a transformation before painting its child.
WidgetsApp: A convenience class that wraps a number of widgets that are commonly required for an application.
Wrap: A widget that displays its children in multiple horizontal or vertical runs.