Sitemap

Advanced Flutter Performance: DevTools, Profiling, Shader Compilation and App Size Optimization

6 min readSep 3, 2026

--

Performance optimization should not be based on assumptions.

A screen may feel slow because of:

  • Excessive widget rebuilds
  • CPU-heavy code
  • Expensive painting
  • Image decoding
  • Memory pressure
  • Shader compilation
  • Large native libraries

The only reliable way to understand the problem is to profile the application.

This article covers the advanced tools and techniques Flutter developers should use before shipping a production application.

Debug Mode Is Not Performance Mode

Before profiling, understand Flutter build modes.

Debug Mode

Designed for development.

Includes:

  • Assertions
  • Debugging information
  • Development tooling

Do not use it for performance benchmarking.

Profile Mode

Designed for performance analysis.

Run:

flutter run --profile

This mode provides profiling information while offering performance closer to production.

Release Mode

Designed for production.

Run:

flutter run --release

Use this to test the final user experience.

Flutter DevTools

Flutter DevTools provides several tools for investigating application behavior.

Important areas include:

  • Performance
  • CPU Profiler
  • Memory
  • Inspector
  • Network analysis

The exact availability can vary depending on your Flutter and tooling versions.

Investigating Jank

A janky animation or scroll usually means that a frame required more time than the available frame budget.

Use the Performance view to inspect slow frames.

Look for:

  • Long UI work
  • Long rasterization
  • Expensive events
  • Repeated frame spikes

The goal is to determine:

Is the bottleneck on the UI side, the raster side, or elsewhere?

UI Thread vs Raster Work

Simplified:

UI Work

Responsible for work such as:

  • Dart execution
  • Widget building
  • Layout
  • Application logic

Raster Work

Responsible for:

  • Painting
  • Compositing
  • Rendering graphics

If UI work is slow, investigate:

  • Large rebuilds
  • Expensive synchronous operations
  • Complex calculations

If raster work is slow, investigate:

  • Large images
  • Blur
  • Shadows
  • Complex clipping
  • Expensive effects

Use the CPU Profiler

The CPU profiler helps identify where CPU time is being spent.

Look for methods that:

  • Consume significant time
  • Execute repeatedly
  • Appear during slow interactions

Example problem:

@override
Widget build(BuildContext context) {
return Text(
calculateComplexValue(data),
);
}

If profiling shows this calculation is expensive, cache it or move it outside frequent rebuilds.

Profile Memory

Use the Memory view to investigate:

  • Allocation growth
  • Large objects
  • Retained objects
  • Memory spikes

A useful workflow:

Baseline
↓
Perform action
↓
Close screen
↓
Repeat
↓
Compare memory

If objects continue growing unexpectedly, inspect what is retaining them.

Track Widget Rebuilds

During development, debugging options can help identify frequent rebuilds.

For example:

debugPrintRebuildDirtyWidgets = true;

This should be used only for investigation.

If a large screen rebuilds when a tiny value changes, consider isolating the changing state.

Performance Overlay

Flutter provides a performance overlay for visual frame information.

This can help you quickly see when frames exceed the rendering budget.

Use performance tools for diagnosis rather than leaving debugging overlays enabled in production.

Add Custom Performance Events

For expensive application operations, record meaningful timing.

For example:

final stopwatch = Stopwatch()..start();
await repository.loadDashboard();debugPrint(
'Dashboard load: '
'${stopwatch.elapsedMilliseconds}ms',
);

For more advanced analysis, instrument important workflows so that you can correlate user actions with performance events.

Examples:

  • App startup
  • Login
  • Dashboard loading
  • Database migration
  • Large file processing

Understanding Shader Compilation Jank

Some graphics effects require shaders.

The first time a shader is needed, compilation work can potentially cause a visible frame hitch on some devices.

This can happen during:

  • First-time animations
  • Complex transitions
  • Graphics-heavy effects

The exact behavior depends on platform, graphics backend, device, and Flutter engine behavior.

The important point is:

Test animations on real devices and investigate first-use stutters.

Reduce First-Use Animation Stutter

When you notice jank that happens primarily the first time an animation runs:

  1. Reproduce it in Profile or Release mode.
  2. Test on the affected physical device.
  3. Record performance data.
  4. Identify whether shader compilation or another rendering bottleneck is involved.
  5. Use Flutter’s current shader warm-up or tooling guidance where supported for your target platforms and engine configuration.

Do not blindly apply shader-caching commands from old tutorials because Flutter tooling and rendering backends evolve.

Always verify against the Flutter version you are using.

Analyze Application Size

Before reducing application size, analyze what is actually making it large.

For release builds, Flutter provides app-size analysis options.

For example:

flutter build apk --analyze-size

You can also analyze App Bundles depending on your build and deployment workflow.

Look for:

  • Large assets
  • Large images
  • Fonts
  • Native libraries
  • Unused dependencies

Prefer Android App Bundles for Play Distribution

For Google Play distribution, Android App Bundles allow Google Play to generate optimized APKs for supported device configurations.

This can reduce the amount users need to download compared with distributing one universal APK containing resources for every supported device architecture.

Split APKs by ABI When Needed

For direct APK distribution, architecture-specific builds can reduce individual APK sizes.

For example, Flutter supports target ABI splitting in relevant Android build workflows.

Get Nisarg Ratani’s stories in your inbox

Join Medium for free to get updates from this writer.

The principle is:

Universal APK
= Multiple architectures
= Larger download

Compared with:

Architecture-specific APK
= Only required native code
= Smaller download

Choose the distribution strategy that fits your release process.

Remove Unused Assets

Audit your application assets.

Look for:

  • Old images
  • Duplicate assets
  • Unused videos
  • Large JSON files
  • Unused fonts

Do not assume assets are harmless simply because they are stored in the repository.

If included in the application package, they contribute to size.

Optimize Images Before Adding Them

Do not use a 10 MB image when a compressed 300 KB image provides the same visual result.

Before adding an image:

  • Resize it
  • Compress it
  • Choose the appropriate format
  • Remove unnecessary metadata when relevant

This can significantly reduce application size.

Remove Unused Dependencies

Every dependency should justify its existence.

Periodically review:

dependencies:

Ask:

  • Is this package still used?
  • Can Flutter provide this functionality directly?
  • Is the package pulling in large native dependencies?

A package that is no longer needed should be removed.

Tree Shaking

Flutter can remove unused code in supported scenarios.

However, tree shaking cannot solve everything.

For example:

  • Large assets are still assets
  • Native libraries may still contribute to size
  • Dynamically referenced resources may require special handling

Do not assume unused-looking resources are automatically removed.

Icon Tree Shaking

When using supported icon fonts and static icon references, Flutter can reduce unused icon data.

However, unusual dynamic icon usage can reduce the effectiveness of tree shaking.

Prefer predictable, static icon usage when possible.

Font Optimization

Custom fonts can increase application size.

Review:

  • Number of font families
  • Number of weights
  • Number of styles

Instead of including every weight:

Thin
ExtraLight
Light
Regular
Medium
SemiBold
Bold
ExtraBold
Black

Include only what your design actually uses.

Native Libraries Can Be Large

Some Flutter plugins include native code.

Common size contributors include:

  • Machine learning SDKs
  • Video processing
  • Maps
  • Large databases
  • Camera libraries

Use application size analysis before removing or replacing anything.

R8 and Code Shrinking

Android release builds can use code shrinking and optimization tools depending on your build configuration and dependencies.

Before enabling aggressive shrinking:

  1. Test the release build thoroughly.
  2. Verify reflection or dynamically referenced classes.
  3. Add appropriate keep rules when necessary.

Incorrect shrinking configuration can cause release-only crashes.

Debug Symbols and Native Code

Native debugging information can increase build artifacts.

Depending on your distribution and crash-reporting setup, configure symbol handling so you can:

  • Keep production downloads reasonably small
  • Preserve symbols required for crash analysis

Always verify your crash-reporting workflow after changing symbol configuration.

APK Size Optimization Checklist

Assets

  • Remove unused assets
  • Compress large images
  • Use appropriate image formats
  • Remove duplicate files

Dependencies

  • Remove unused packages
  • Review large native dependencies
  • Prefer lighter alternatives only when they provide equivalent functionality

Fonts

  • Remove unused font families
  • Include only required font weights

Android

  • Analyze release size
  • Prefer App Bundles for Play distribution
  • Consider ABI-specific APKs for direct distribution
  • Review code shrinking configuration
  • Verify native symbol handling

A Practical Flutter Performance Workflow

Use this process whenever you encounter a performance issue.

Step 1: Reproduce

Create a reliable workflow.

Open screen
↓
Scroll
↓
Tap button
↓
Animation stutters

Step 2: Profile

Use:

flutter run --profile

Inspect the relevant tools.

Step 3: Identify the Bottleneck

Determine whether the problem is:

  • CPU
  • Rebuilds
  • Layout
  • Painting
  • Rasterization
  • Images
  • Memory
  • Network
  • Application startup

Step 4: Optimize

Make one focused change.

Examples:

Large image
→ Resize image
Expensive calculation in build()
→ Cache result
Huge list
→ Lazy builder

Step 5: Measure Again

Never assume the optimization worked.

Measure before and after.

Sometimes an optimization:

  • Does nothing
  • Moves the bottleneck
  • Increases memory usage
  • Makes the code unnecessarily complex

Final Production Performance Checklist

Before releasing a Flutter application:

  • Test in Profile mode
  • Test the final build in Release mode
  • Test cold startup
  • Test long scrolling lists
  • Test large images
  • Test animations
  • Test on lower-end devices
  • Check for memory growth
  • Dispose controllers and subscriptions
  • Profile slow screens
  • Investigate dropped frames
  • Analyze application size
  • Remove unused assets
  • Remove unused dependencies
  • Verify release shrinking and symbol handling
  • Test real user workflows

Final Thoughts

Flutter performance optimization is not a collection of tricks.

It is an engineering process.

Measure
↓
Profile
↓
Identify
↓
Optimize
↓
Measure again

The best-performing Flutter applications are not necessarily the ones with the most optimization code.

They are the applications where developers understand:

  • What is expensive
  • Why it is expensive
  • How to measure it
  • Whether the optimization actually improves the user experience

Master this workflow, and you will be able to optimize not just one Flutter application — but every application you build.

--

--

Nisarg Ratani
Nisarg Ratani

Written by Nisarg Ratani

Flutter & Node.js developer with 6+ years of experience. Published few articles on Medium, sharing practical insights on Flutter, Dart, and Node.js.