Reducing App Size: Tree Shaking, Deferred Loading, and Asset Optimization
Reducing App Size in Flutter
A smaller release bundle loads faster, consumes less storage, and reduces drop-off on low-bandwidth networks. Flutter gives you several first-class tools to shrink your APK, AAB, or IPA: tree shaking (dead-code elimination), deferred component loading (splitting the app into on-demand chunks), asset optimization (compressing images and fonts), and the built-in size analysis report from flutter build --analyze-size.
1. Tree Shaking — Dead-Code Elimination
Dart's compiler performs tree shaking automatically when you compile in release mode (flutter build apk --release). Tree shaking analyzes your import graph and removes every class, function, and constant that is never reachable from main(). This is why importing a heavy package but only using one utility from it does not necessarily bloat your bundle — unused symbols are stripped.
- Tree shaking works best when packages export individual files rather than a single barrel file.
- Reflection (mirrors) and
dart:mirrorsdisable tree shaking for the affected code — avoid them in production. - Code generated by
build_runneris fully tree-shakeable as long as references are static.
Verifying Tree Shaking Eliminates Unused Code
// math_utils.dart — a library with two top-level functions
double square(double x) => x * x;
double cube(double x) => x * x * x; // never called anywhere
// main.dart
import 'math_utils.dart';
void main() {
print(square(4)); // only square() is reachable
// cube() is unreachable, so Dart tree-shaking removes it from the bundle
}
2. Deferred Loading (Lazy Imports)
For features that are rarely used — onboarding flows, help screens, admin panels — you can split them into a deferred library. The Dart compiler emits a separate .js or native snapshot for each deferred library, and the runtime downloads or loads it only when the application actually needs it. On Flutter Web this maps directly to JavaScript code splitting; on mobile it is supported via deferred components on Android (Android App Bundles + Play Feature Delivery).
Deferred Import Syntax
import 'package:myapp/features/onboarding/onboarding_screen.dart'
deferred as onboarding;
class HomeScreen extends StatelessWidget {
Future<void> _launchOnboarding(BuildContext context) async {
// Library is loaded from disk / network here, only on first call
await onboarding.loadLibrary();
if (!context.mounted) return;
Navigator.of(context).push(
MaterialPageRoute<void>(
builder: (_) => onboarding.OnboardingScreen(),
),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: ElevatedButton(
onPressed: () => _launchOnboarding(context),
child: const Text('Start Onboarding'),
),
),
);
}
}
loadLibrary() as early as possible — for example, in initState() — so the library is already cached by the time the user taps the button. loadLibrary() is idempotent: calling it multiple times is safe.3. Asset Optimization
Images are frequently the biggest contributor to bundle size. Apply these strategies before shipping:
- Use WebP or AVIF instead of PNG/JPEG. WebP offers 25–35 % smaller files at equivalent visual quality. Convert with
cwebpor Squoosh. - Resize to display resolution. Bundling a 4 K photo that is rendered at 200 × 200 logical pixels wastes roughly 400x the necessary data. Use the closest
1x,2x,3xvariants. - Subset fonts. If you ship a custom font, use a tool like
pyftsubsetto keep only the Unicode ranges your app actually uses. A full Latin + Arabic font can be trimmed from 500 KB to under 50 KB. - Remove unused assets. Assets declared in
pubspec.yamlunderflutter: assets:are always bundled, whether or not they are loaded at runtime.
4. Reading the Size Analysis Report
The --analyze-size flag produces a detailed breakdown of every compiled package and asset in your release bundle. Run it like this:
Generating the Size Analysis Report
# Android App Bundle (recommended for Play Store)
flutter build appbundle --release --analyze-size
# iOS (requires Xcode toolchain)
flutter build ios --release --analyze-size
# Output location printed at the end of the build, e.g.:
# A summary of your APK analysis can be found at:
# /Users/you/.flutter-devtools/apk-code-size-analysis_01.json
# Open in Dart DevTools for an interactive treemap:
dart devtools
# Then open the saved .json file in the "App Size" tab
The report groups symbols by package → library → class → member. The largest nodes are your optimization targets. Common findings include:
- Packages that bring in large transitive dependencies (e.g., Firebase, Google Maps).
- Generated code (JSON serialization, routes) that is larger than expected.
- Unused icon fonts bundled in full (e.g.,
MaterialIcons— Flutter already tree-shakes icon data by default since Flutter 2.3).
--analyze-size flag compiles with additional instrumentation that slightly increases build time. Do not use it in a normal CI pipeline — reserve it for periodic size audits.5. Additional Size-Reduction Techniques
- Build split APKs by ABI:
flutter build apk --split-per-abigenerates separate APKs forarm64-v8a,armeabi-v7a, andx86_64, each containing only the native library for that architecture. - Obfuscate Dart code:
--obfuscate --split-debug-info=./debug-infostrips readable symbol names, reducing snapshot size by 5–15 % and hardening against reverse engineering. - Avoid bundling test assets: Keep test fixtures in
test/, not inassets/— onlyassets/entries inpubspec.yamlare included in the bundle.
Summary
Reducing Flutter app size is a multi-layered discipline. Tree shaking is automatic but relies on static, non-reflective code. Deferred loading lets you split rarely-used features into on-demand chunks. Asset optimization — correct image formats, resolution variants, and font subsetting — often yields the largest absolute savings. Finally, flutter build --analyze-size with Dart DevTools gives you a visual treemap to find and eliminate the biggest contributors to bundle bloat.
--analyze-size to identify the real bottlenecks, apply targeted fixes (deferred imports for large features, WebP for images, ABI splits for native libraries), and re-measure to confirm the improvement.