JOHN RYAN: Hi, I'm John from the Flutter team.
In this video, we're going to see different ways
to make your Flutter app faster and more reliable.
The Flutter SDK gives you everything
you need to build modern, fast, and great looking mobile apps.
It's designed to run fast.
But for the smoothest possible experience for your users,
it helps to occasionally take your app
into the shop for a tune-up.
That's why Flutter comes with a collection of developer tools
that lets you see exactly what your app is
doing while it's running.
So you can spot opportunities to make
it faster or more reliable.
And it's not just about performance.
DevTools lets you look under the hood
to fix other things, such as your app's layout, network
requests, and more.
In this section, we'll configure a new project in our IDE,
run our app, and connect to DevTools.
You can create a new Flutter project
in Visual Studio Code using the command palette.
Next, we'll set up our launch configuration file.
I'll open the run debug view from the activity bar.
Then create a launch.json file using the suggested link,
and select Dart & Flutter.
By default, this configures three run modes, debug,
profile, and release.
Debug mode is designed to be used while you're
making changes to your app.
You can hot reload, attach a debugger,
and use all of the DevTools features in this video.
Profile mode lets you measure your app's performance
and still use the performance and memory tools.
Release mode optimizes your app for the real world,
so you can see how it actually runs on a target device
that you're using.
DevTools only supports debug and profile mode, but not
release mode.
Now we're ready to run our app.
Select debug as the run mode from the dropdown
menu in the run and debug view.
Once you've got a device connected
and USB debugging enabled, select your device
in the status bar.
Physical devices offer more accurate timing information
than emulators, so it's a good idea
to collect performance profile data using real devices, not
emulators.
But you can still use DevTools with an emulator in debug mode,
if you prefer.
Run the app using the run button or press F5.
Now we can open DevTools using the command palette.
You can choose to open the full suite of tools
in a web browser, or open a specific panel in the IDE.
If you prefer to use the command line,
you can copy paste this link into your web browser.
Now let's take a tour.
Welcome to DevTools.
Hot reload and hot restart are available in the toolbar.
Hot reload updates your app with any new code,
while preserving your app's state.
Hot restart restarts your Flutter app from the beginning,
without recompiling the native app.
You can also hot reload your app using the debug
toolbar in Visual Studio Code, and the Flutter extension
will perform a hot reload when you save a file.
Flutter inspector has tools for inspecting
your app's widgets, how they're laid out on screen, and more.
To select a widget, use the select widget mode button.
Once selected, you'll see it highlighted in the widget tree
view.
You can use this view to adjust your selection.
You'll also notice that your IDE jumps
to the creation location of a widget
when you select one from the inspector.
You can inspect and modify the layout of your app
in the layout explorer.
For example, adjusting the main axis alignment of this column,
Flutter updates your app right away,
so you can see the effect.
You can also change the layout of the column's children.
Flutter uses a flexible box layout algorithm for rows
and columns called Flexbox.
So if I assign these text widgets a flex value of two
and one, it will make the first widget take up
twice as much vertical space as the second.
You can also change whether the widgets fit
property is tight or loose.
If I set the fit of this text widget to loose,
it'll be allowed to be smaller than the available space.
You can also slow down all the animations
in the app, show guidelines such as render boxes, alignments,
scroll views, clipping, and more.
Show baselines for typography.
Highlight repaints, which paints a border around each layer,
and changes its color whenever the layer is painted.
And highlight oversized images, which
helps you find images that are bigger
than they need to be to be displayed at full resolution.
The Performance View is the place
to go to make sure your app is running at a smooth frame rate.
In order to get accurate timing information,
I'll stop and relaunch the app using profile mode.
The frames chart shows you every frame your app is painting.
You can quickly see when a frame is too slow to hit a smooth 60
frames per second.
When a frame is slow, the rendering engine
will skip frames in order to display the right content
at the right time.
DevTools colors them red, so you can easily spot them
and take a closer look.
Once you've found a janky frame, DevTools automatically
analyzes it for you in the frame analysis tab.
You can see how much time the frame
spent in the build phase, layout phase, and the paint phase.
Other notifications will appear at the top
for other common performance issues.
To see a complete picture of timing events
for a particular frame, select the timeline events tab.
Tracing information for the build phase, layout phase,
and painting phase can be shown through the enhanced tracing
options.
Flow analysis is also displayed using orange arrows.
DevTools uses a trace viewer called Perfetto,
so you can use the WASD keys to inspect the timeline events
and press question mark to see more keyboard shortcuts.
The CPU Profiler collects CPU samples
to help you get a sense of the functions your app is spending
time in.
To collect a profile, press record, use the app for a bit,
and then press stop.
You can also select, load all CPU samples,
to load the profile of all available CPU
information up to this point.
The bottom up tab helps you identify expensive functions,
like this one here.
The total time includes any functions
that are called by this function,
and the self time is only the time spent in that function.
You can hover over a function to reveal
tooltips for more context.
The call tree tab helps you identify expensive code paths.
The call tree tab is top down.
This is helpful if you want to get detailed information
about where a function is spending
the majority of its time.
The method table shows the call graph for individual functions.
For example, this Fibonacci function is recursive,
so it calls itself and is also a caller for itself,
except for the original function that called it.
The flame chart is like the call tree tab, but more visual.
In any of these views, we can find
the same expensive function by filtering out core, Dart
& Flutter libraries.
The memory view is helpful for diagnosing whether your app is
using too much memory.
Dart objects are stored in memory
on the heap, which is managed by the Dart Virtual Machine.
Objects are removed from the heap during garbage collection
if they're unreachable.
In other words, if there's no retaining path to the root
object.
The profile memory tab shows the number
of instances of each object type,
and the space these objects are consuming in the heap.
For example, this app is storing a lot of objects
called MyObject in memory.
The diff snapshots tab can compare
two different snapshots.
You can also explore a single heap snapshot in this view
without diffing it with another.
The retaining path shows the references
for the shortest path from the root object
to the current object.
If there is a retaining path, then the object
will be retained and will stay in memory
after the next garbage collection cycle.
You can also inspect these objects
in the console using the context menu.
The trace instances tab lets you see
how many allocations have occurred for the selected
class.
Search for the name of the class and select
it to start tracking.
Any allocations from this point forward will be tracked,
and can be explored in the panel on the right, which
shows the methods where instances of the selected class
were allocated.
There's also a memory chart that shows your app's memory
usage over time, and a button to trigger garbage collection.
The debugger tab includes a full source level debugger.
You can set breakpoints, step through code,
pause on exceptions, evaluate variables that are in scope,
and other common debugging tasks.
It can even show code coverage and the time
spent in each function.
The total time includes the time it
takes to run methods called by our function,
and the self time reports only the time
spent in that function.
You can also use Visual Studio Codes debugger, if you prefer.
The Network tab shows each network request
made by your app.
You can use this view to find out
where your app is using too much network bandwidth.
The logging tab shows all the logs produced by your app.
You can display your own log messages
using print statements, or the Dart Developer Library if you
want to label it as a certain kind of log
or include more information, such as a log level.
The app size tool lets you analyze a snapshot of your app,
so you can improve the size of your app.
To start analyzing, build your app using the analyze size flag
and select the JSON file to view it in the app size tab.
The boxes at the top are a tree map,
which shows the size of each Dart package, library,
or class.
You can drill down into specific parts of your app
to see what's taking up the most space.
You can also compare two snapshots
to see why your app size changed.
Remember, Dart uses a tree shaking compiler,
so the size only increases when you call new code.
So adding a package to your pub spec
won't increase your app size until you start using it.
And that completes our tour.
Now let's look at some slow Flutter apps
and use DevTools to diagnose and fix performance problems.
Here's an app that runs an animation,
but whenever I tap the floating action button,
the animation stutters.
The frame analysis tab shows that the build
phase took longer than usual.
Usually, it's pretty fast.
Let's open up the timeline events chart.
By default, details about the build phase are hidden.
This helps keep the timing information accurate,
but doesn't help us debug a slow build phase.
To get more detailed timeline of events
during the build phase, select the enhanced tracing dropdown
and choose track widget builds.
Now we can see which widgets are spending the most
time in the build phase.
If we look at the widget, we can see
it's performing an expensive operation in its build method.
Here's another example that displays
a horizontal scroll view of cards.
The height of the list is calculated
based on the largest card.
Opening the performance tab and selecting the slow frames
reveals a warning about intrinsic passes.
Intrinsic height and intrinsic width widgets
are slow, because they need to lay out
all the widgets in the subtree to determine the size.
To see exactly what's going on, enable track layouts
and open the timeline events view.
The layout phase is the longest, with most
of the time spent in the speculative layout pass
that the intrinsic height widget performs.
To fix it, we can use a constrained box
to limit the height of the row of cards.
This isn't exactly the same behavior as before,
since it may not be large enough for the largest card,
but it doesn't spend as much time calculating the layout.
In this case, this is the trade-off between functionality
and performance.
But there are often cases where intrinsic height
or intrinsic width are useful, and don't impact performance
very much.
That's why we're measuring it in DevTools.
For more information about intrinsic layout calculations,
check out the decoding Flutter video on intrinsic widgets
in the description.
The paint phase takes all the render objects for your app
and groups their painting instructions
into layers before sending them off to the raster thread.
Rasterization is the last step of the rendering pipeline,
where the painting instructions get executed.
This happens on the raster thread.
You can see how long each layer in your app
took to rasterize by going to the raster stats
tab in the performance view and taking a snapshot.
This view will provide the most accurate data
when you use it with Impeller, Flutter's latest rendering
engine.
You can use debug mode to quickly make changes and figure
out what's making a particular layer expensive to rasterize.
Sometimes an animation can cause a layer
to be repainted more often than you intended.
If it's expensive to paint and composite that layer,
it's a good idea to move the animation to a separate layer
to be painted separately.
To see which layers are being repainted,
use the highlight repaints tool.
This draws a colorful border around each layer
that changes color when the layer is repainted.
For example, this app displays a circular progress indicator,
which is composited into the same layer
as a widget that's expensive to paint.
The highlight repaints tool changes color on each frame
while the animation is running.
You can tell the framework to put the circular progress
indicator on a separate layer by wrapping it
with a repaint boundary.
In Visual Studio Code, I can type command dot or control
dot on windows and select, wrap with widget,
and type repaint boundary.
That's better.
Putting the animation on a separate layer
saved our expensive widget from being rasterized
while the animation's running.
There are a lot more tactics you can
use to make your app faster.
Check out the Performance Best Practices
page for more information.
As we've seen, DevTools is a great tool
for improving your app's performance, so go ahead
and take it for a test drive.
To learn more about DevTools and all things Flutter,
head to flutter.dev.
[MUSIC PLAYING]