Zpět na domů

Optimalizace Flutter: vyhněte se rebuild'ům

Článek rozebírá 8 běžných chyb optimalizace Flutter aplikací: zbytečné rebuild'y, absence const, logika v build(), nesprávné seznamy. Uvedeny příklady kódu oprav a klíčové postupy pro stabilních 60 FPS.

Flutter bez zmrznutí: 8 klíčových optimalizací
Advertisement 728x90

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:

Google AdInline article slot
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é.

Google AdInline article slot

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í.

Google AdInline article slot

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

Advertisement 728x90

Číst dál