Praktické postupy pro zrychlení aplikací ve Flutteru
Aplikace ve Flutteru často trpí zasekáváním kvůli zbytečným přestavbám, operacím v UI vlákně a neoptimální práci se seznamy. V článku jsou rozebrány klíčové chyby a způsoby jejich nápravy: od lokalizace aktualizací až po správné použití builderů a animací. Tyto postupy umožňují udržet 60 FPS i na slabších zařízeních.
Minimalizace zbytečných přestaveb UI
Zbytečné rebuildy jsou hlavní příčinou poklesu výkonu. setState() přestaví celý podstrom StatefulWidgetu, včetně náročných seznamů a obrázků. To vede k výpočtům layoutu a poklesu FPS.
Problémový kód:
class MyScreen extends StatefulWidget {
@override
State<MyScreen> createState() => _MyScreenState();
}
class _MyScreenState extends State<MyScreen> {
int counter = 0;
@override
Widget build(BuildContext context) {
return Column(
children: [
Text('Counter: $counter'),
ElevatedButton(
onPressed: () {
setState(() {
counter++; // přestaví CELÝ strom
});
},
child: Text('Increment'),
),
],
);
}
}
Řešení: ValueListenableBuilder aktualizuje pouze závislou část UI.
class MyScreen extends StatelessWidget {
final ValueNotifier<int> counter = ValueNotifier(0);
@override
Widget build(BuildContext context) {
return Column(
children: [
ValueListenableBuilder<int>(
valueListenable: counter,
builder: (_, value, __) {
return Text('Counter: $value');
},
),
ElevatedButton(
onPressed: () => counter.value++,
child: const Text('Increment'),
),
],
);
}
}
Podobně fungují StreamBuilder, Selector z provideru nebo BlocBuilder. Oblast překreslování se minimalizuje, zátěž CPU klesá.
Použití const konstruktorů
Bez const každý rebuild vytváří nové objekty widgetů, což zatěžuje GC a CPU. V seznamech se stovkami ikon, textů a paddingů je to kritické.
Optimální varianta:
Widget build(BuildContext context) {
return const Column(
children: [
Text('Hello'),
Icon(Icons.home),
Padding(
padding: EdgeInsets.all(8),
child: Text('World'),
),
],
);
}
Flutter znovu používá const objekty, alokace se snižují. Zlepšení je patrné ve složitých UI s tisíci drobných widgetů.
Vynesení logiky z build()
Třídění, filtrování nebo parsování v build() se provádí při každém volání – až 60krát za sekundu. Pro 60 FPS zbývá na snímek 16 ms, náročné operace způsobují trhání.
Správný přístup:
late List<int> filtered;
@override
void initState() {
super.initState();
final sorted = [...items]..sort((a, b) => a.compareTo(b));
filtered = sorted.where((e) => e > 10).toList();
}
@override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: filtered.length,
itemBuilder: (_, i) => Text('${filtered[i]}'),
);
}
Logika se provede jednorázově v initState nebo v obchodní vrstvě. build() zůstává deklarativní.
Líné seznamy s ListView.builder
ListView s children: [...] vytvoří všechny prvky najednou, i ty neviditelné. Při 100+ položkách trpí paměť a první vykreslení.
Doporučení:
Widget build(BuildContext context) {
return ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
final item = items[index];
return ListTile(
title: Text(item.title),
subtitle: Text(item.subtitle),
);
},
);
}
Prvky se generují při posouvání, paměť je optimalizována, recyklace funguje efektivně.
Klíče pro stabilitu seznamů
Bez key Flutter nespáruje prvky při aktualizacích seznamu, což způsobuje zbytečné rebuildy a vizuální artefakty.
Kritické pro:
- Změnu pořadí prvků
- Odstranění/vložení
- Animace přechodů
ListView.builder(
itemCount: items.length,
itemBuilder: (_, index) {
final item = items[index];
return ListTile(
key: ValueKey(item.id),
title: Text(item.title),
);
},
);
ValueKey(item.id) zajišťuje správnou aktualizaci.
Animace bez setState
Timer.periodic se setState každých 16–100 ms přetěžuje UI vlákno.
Standardní nástroj:
late AnimationController controller;
@override
void initState() {
super.initState();
controller = AnimationController(
vsync: this,
duration: Duration(seconds: 2),
)..repeat();
}
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: controller,
builder: (_, __) {
return LinearProgressIndicator(value: controller.value);
},
);
}
AnimationController se synchronizuje s FPS zařízení a aktualizuje minimum UI.
Rozdělení na menší widgety
Jeden obrovský StatefulWidget se přestaví celý při jakékoli změně.
Výhody kompozice:
- Lokalizace rebuildů
- Znovupoužití kódu
- Lepší čitelnost a testy
Widget build(BuildContext context) {
return Column(
children: const [
Header(),
Expanded(child: ItemsList()),
Footer(),
],
);
}
class Header extends StatelessWidget {
const Header();
@override
Widget build(BuildContext context) {
return Text('Header');
}
}
Future mimo build()
FutureBuilder s future: fetchData() v build() spouští požadavky při každém rebuildu.
late Future dataFuture;
@override
void initState() {
super.initState();
dataFuture = fetchData();
}
Widget build(BuildContext context) {
return FutureBuilder(
future: dataFuture,
builder: (_, snapshot) {
if (!snapshot.hasData) return CircularProgressIndicator();
return Text(snapshot.data.toString());
},
);
}
Future se vytvoří jednou, UI reaguje na změny.
Co je důležité
- Lokalizujte aktualizace: ValueListenableBuilder, AnimatedBuilder místo globálního setState.
- const všude: Snižuje alokace o 50–70 % ve složitých UI.
- Logika v initState: build() – pouze deklarativní popis.
- ListView.builder + klíče: Pro stabilní dynamické seznamy.
- Kompozice widgetů: Malé StatelessWidgety místo monolitů.
— Editorial Team
Zatím žádné komentáře.