Flutter sudah terkenal cepat karena rendering engine Skia/Impeller yang bypass native UI. Tapi aplikasi kompleks tetap bisa jank (drop frame) kalau gak diperhatikan. Hari ini kita bahas teknik optimasi performa yang wajib diketahui developer Flutter level expert.
1. Pahami Frame Budget
Target 60 FPS = 16.67 ms per frame. Target 120 FPS (device modern) = 8.33 ms. Semua work (layout, paint, compositing, Dart execution) harus selesai dalam budget ini.
Gunakan Performance Overlay untuk profiling:
// main.dart
MaterialApp(
showPerformanceOverlay: true, // Aktifkan di debug
// ...
);
Atau via DevTools: Flutter Inspector β Performance Overlay.
2. Const Constructor & Const Widgets
Gunakan const sebisa mungkin. Flutter bisa reuse widget instance, skip rebuild, dan skip diffing.
// β Bad: Rebuild setiap frame
Widget build(BuildContext context) {
return Container(
color: Colors.blue,
child: Text('Hello'),
);
}
// β
Good: Const constructor
const MyWidget = Container(
color: Colors.blue,
child: Text('Hello'),
);
// β
Good: Const di tree
Widget build(BuildContext context) {
return const Column(
children: [
Text('Static'),
SizedBox(height: 16),
Text('Tetap const'),
],
);
}
Aturan: Kalau widget & semua children immutable β const. Kalau ada setState / Provider / Bloc yang berubah β gak bisa const.
3. ListView.builder vs ListView
ListView bikin semua children sekaligus (O(n) memory). ListView.builder lazy-build hanya item yg visible (O(1) memory).
// β
Good untuk list panjang
ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) => ItemWidget(item: items[index]),
);
// β
Tambahan: cacheExtent untuk preload sebentar sebelum visible
ListView.builder(
cacheExtent: 500, // pixel
itemCount: items.length,
itemBuilder: (context, index) => ItemWidget(item: items[index]),
);
4. RepaintBoundary β Isolasi Repaint
Widget yang sering berubah (animasi, scroll) jangan bikin parent repaint. Bungkus dengan RepaintBoundary.
// Parent gak repaint saat child berubah
RepaintBoundary(
child: AnimatedContainer(
duration: Duration(milliseconds: 300),
color: isActive ? Colors.red : Colors.blue,
child: const Text('Animasi'),
),
);
Tip: ListView children otomatis dapet RepaintBoundary (via RenderSliverList). Tapi custom scroll / complex tree butuh manual.
5. Opacity & Clipping β Mahal
Opacity dan ClipRect/ClipRRect trigger offscreen layer (saveLayer) β GPU cost tinggi.
// β Bad: Opacity di widget besar
Opacity(
opacity: 0.5,
child: LargeComplexWidget(),
);
// β
Good: Ubah color/alpha langsung
Container(
color: Colors.blue.withOpacity(0.5),
child: LargeComplexWidget(),
);
// β
Good: FadeInImage / AnimatedOpacity (sudah optimized)
AnimatedOpacity(
opacity: visible ? 1.0 : 0.0,
duration: Duration(milliseconds: 300),
child: ChildWidget(),
);
6. Image Caching & Resize
Gambar besar di-resize di runtime = CPU & memory boros. Gunakan cacheWidth/cacheHeight atau resizeImage.
// β
Resize di decode (memory efficient)
Image.network(
url,
cacheWidth: 300, // decode ke 300px width
cacheHeight: 200,
);
// β
Atau pakai resizeImage untuk asset
Image.asset(
'assets/large.png',
width: 100,
height: 100,
// Flutter auto downscale kalau width/height < intrinsic
);
Pro tip: Gunakan precacheImage di initState untuk preload hero image.
@override
void initState() {
super.initState();
precacheImage(NetworkImage(heroUrl), context);
}
7. Build Methods Ringan
Jangan taruh logic berat di build. Pindah ke compute (isolate) atau Future.microtask.
// β Bad: Heavy computation di build
Widget build(BuildContext context) {
final sorted = heavySort(items); // Block UI thread
return ListView.builder(...);
}
// β
Good: Compute di isolate
Future<List<Item>> _sortInBackground(List<Item> items) async {
return compute(heavySort, items);
}
Widget build(BuildContext context) {
return FutureBuilder(
future: _sortInBackground(items),
builder: (context, snapshot) {
if (!snapshot.hasData) return const CircularProgressIndicator();
return ListView.builder(...);
},
);
}
8. Keys β Preserve State
Pakai Key yang stabil (bukan UniqueKey di list) agar Flutter reuse element tree.
// β Bad: Key berubah tiap rebuild
ListView.builder(
itemBuilder: (_, i) => ItemWidget(key: UniqueKey(), item: items[i]),
);
// β
Good: Key berdasarkan ID stabil
ListView.builder(
itemBuilder: (_, i) => ItemWidget(key: ValueKey(items[i].id), item: items[i]),
);
9. Profile Mode β Bukan Debug/Release
Debug mode: asserts, checks, unoptimized. Release: full optimize tapi gak bisa profiling. Profile mode = optimized + profiling tools.
flutter run --profile
# atau
flutter build apk --profile
Buka DevTools β Timeline β rekam scroll/interaksi β cari red bars (frame > 16ms).
10. Impeller & Shader Compilation
Flutter 3.16+ pakai Impeller (iOS default, Android opt-in). Impeller compile shader di build time (AOT), gak di runtime β zero jank pertama kali.
# android/app/build.gradle (Android)
flutter {
// ...
experimental-impeller = true
}
Warm-up shader (pre-Impeller / fallback):
// main.dart
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Future.wait([
// Prewarm routes
precacheImage(AssetImage('assets/bg.png'), context),
// ...
]);
runApp(MyApp());
}
Checklist Cepat Sebelum Release
| Area | Check |
|---|---|
| Const widgets | β
Semua static widget const |
| Listλ·° | β
ListView.builder + cacheExtent |
| Repaint | β
RepaintBoundary di animasi/scroll |
| Opacity | β
Hindari Opacity widget besar |
| Images | β
cacheWidth/cacheHeight / precacheImage |
| Build | β
Logic berat ke compute / isolate |
| Keys | β
ValueKey(id) stabil |
| Profiling | β
Test di --profile mode, DevTools Timeline |
| Impeller | β
Aktifkan di Android (experimental-impeller) |
Kesimpulan
Performa Flutter bukan sihir β bisa diukur & dioptimasi. Pola paling impact:
- Const everywhere β skip rebuild & diff
- Lazy lists β
ListView.builder - Isolate heavy work β
compute() - Profile mode + DevTools Timeline β temukan bottleneck real, bukan dugaan
Coba sendiri! Share ke sosial media dan tag @ahsai001