How do you handle performance optimization - app size, rendering speed, and load time?
#performance
Answer
Overview
Flutter performance optimization spans three key areas: app size, rendering speed, and load time. Each requires a different approach.
1. Rendering Speed (60/120fps)
Use const Widgets
dart// ✅ const widget is cached — never rebuilt const Text('Static Label') const SizedBox(height: 16) const Icon(Icons.star) // ❌ Non-const widget rebuilds unnecessarily Text('Static Label') // Rebuilt on every setState
Avoid Unnecessary Rebuilds
dart// ❌ Rebuilds entire tree on counter change class MyWidget extends StatefulWidget { Widget build(BuildContext context) { return Column( children: [ ExpensiveHeader(), // Rebuilt even though counter doesn't affect it Text('$_counter'), ], ); } } // ✅ Only rebuild what changed Column( children: [ const ExpensiveHeader(), // const — never rebuilt Consumer<CounterModel>( builder: (context, model, child) => Text('${model.count}'), ), ], )
Use ListView.builder (Not ListView) — Lazy Loading
text
ListView.builderdart// ❌ Eager loading — builds ALL 1000 items at once, even off-screen ListView( children: items.map((e) => ItemWidget(e)).toList(), // 1000 widgets created immediately ) // ✅ Lazy loading — builds only ~10-15 visible items at a time ListView.builder( itemCount: items.length, itemBuilder: (context, index) { // Called ONLY when item[index] is about to become visible return ItemWidget(items[index]); }, )
How lazy loading works in ListView.builder:
- Viewport renders only the visible items + a small buffer
- As user scrolls down, new items are built on demand
- Items that scroll off-screen are disposed to free memory
- is called just-in-time — never for off-screen itemstext
itemBuilder
Lazy Loading with Pagination (Infinite Scroll)
For loading data from an API as the user scrolls, combine
text
ListView.buildertext
ScrollControllerdartclass ProductListScreen extends StatefulWidget { State<ProductListScreen> createState() => _ProductListScreenState(); } class _ProductListScreenState extends State<ProductListScreen> { final ScrollController _scrollController = ScrollController(); final List<Product> _products = []; bool _isLoading = false; bool _hasMore = true; int _page = 1; void initState() { super.initState(); _fetchProducts(); // Load first page _scrollController.addListener(_onScroll); } void _onScroll() { // Trigger fetch when user is 200px from the bottom final atBottom = _scrollController.position.pixels >= _scrollController.position.maxScrollExtent - 200; if (atBottom && !_isLoading && _hasMore) { _fetchProducts(); } } Future<void> _fetchProducts() async { setState(() => _isLoading = true); final newProducts = await ProductApi.fetchPage(page: _page); setState(() { _products.addAll(newProducts); _isLoading = false; _hasMore = newProducts.isNotEmpty; _page++; }); } void dispose() { _scrollController.dispose(); // Always dispose to prevent memory leaks super.dispose(); } Widget build(BuildContext context) { return ListView.builder( controller: _scrollController, itemCount: _products.length + (_hasMore ? 1 : 0), itemBuilder: (context, index) { // Last item — show loading spinner while fetching next page if (index == _products.length) { return const Center( child: Padding( padding: EdgeInsets.all(16), child: CircularProgressIndicator(), ), ); } return ProductCard(_products[index]); }, ); } }
Avoid Expensive Operations in build()
dart// ❌ Sorting inside build — runs every rebuild Widget build(BuildContext context) { final sorted = items.sort(...); // Expensive! return ListView.builder(...); } // ✅ Pre-compute and cache final _sortedItems = [...items]..sort(...); // Computed once Widget build(BuildContext context) { return ListView.builder(itemCount: _sortedItems.length, ...); }
Use RepaintBoundary for Complex Widgets
dartRepaintBoundary( child: ComplexAnimatedChart(), // Only this layer repaints, not the whole tree )
2. App Size Reduction
bash# Build with size analysis flutter build apk --analyze-size flutter build ios --analyze-size # Use --split-per-abi to reduce APK size flutter build apk --split-per-abi # Produces: arm64-v8a, armeabi-v7a, x86_64 APKs separately
Remove Unused Assets
yaml# pubspec.yaml — only include what you use flutter: assets: - assets/images/logo.png # ✅ Only specific files # - assets/images/ # ❌ Avoid including entire folders
Use WebP images instead of PNG/JPG
dart// Use WebP format — 25-35% smaller than PNG Image.asset('assets/images/photo.webp')
Defer unused packages / tree shake
Dart automatically tree-shakes unused code in release builds. Avoid heavy packages for simple tasks.
3. Load Time Optimization
Defer Heavy Initialization
dartvoid main() async { WidgetsFlutterBinding.ensureInitialized(); runApp(MyApp()); // Show UI immediately // Defer non-critical initializations WidgetsBinding.instance.addPostFrameCallback((_) async { await FirebaseAnalytics.init(); await RemoteConfig.fetchAndActivate(); }); }
Cache Network Images
dart// ✅ Use cached_network_image — avoids re-downloading CachedNetworkImage( imageUrl: url, placeholder: (context, url) => Shimmer(), errorWidget: (context, url, error) => Icon(Icons.error), )
Use Isolate for Heavy Parsing
dart// Parse large JSON in background Future<List<Product>> loadProducts(String jsonStr) async { return await compute(_parseProducts, jsonStr); } List<Product> _parseProducts(String json) { return (jsonDecode(json) as List).map((e) => Product.fromJson(e)).toList(); }
Performance Checklist
| Optimization | Impact |
|---|---|
text | ⭐⭐⭐ High |
text | ⭐⭐⭐ High |
| Image caching | ⭐⭐⭐ High |
text | ⭐⭐ Medium |
text | ⭐⭐ Medium |
| Avoid build() logic | ⭐⭐ Medium |
| Isolate for heavy work | ⭐⭐ Medium |
| Defer initialization | ⭐⭐ Medium |
| Use WebP images | ⭐ Low-Medium |
Profiling Tools
bash# Profile mode — runs with production performance but debug tools available flutter run --profile # Open DevTools for frame timings, rebuild counts, memory flutter pub global run devtools
Golden Rules: Use
wherever possible. Never do heavy work intextconst. Usetextbuild()for lists. Cache images. Defer non-critical initialization.textListView.builder