Zurück zur Startseite

Flutter-Optimierung: Rebuilds vermeiden

Der Artikel analysiert 8 gängige Flutter-App-Optimierungsfehler: zusätzliche Rebuilds, Fehlen von const, Logik in build(), falsche Listen. Code-Beispiele für Korrekturen und Schlüsselpraktiken für stabile 60 FPS werden bereitgestellt.

Flutter ohne Freezes: 8 Schlüsseloptimierungen
Advertisement 728x90

Praktische Techniken zur Beschleunigung von Flutter-Apps

Flutter-Apps leiden oft unter Rucklern aufgrund unnötiger Neuerstellungen, Operationen auf dem UI-Thread und ineffizienter Listenverarbeitung. Dieser Artikel erläutert häufige Fehler und deren Behebung: von lokalisierten Updates bis zum richtigen Einsatz von Buildern und Animationen. Diese Techniken helfen, selbst auf Einsteigergeräten 60 FPS zu halten.

Minimierung unnötiger UI-Neuerstellungen

Unnötige Neuerstellungen sind eine Hauptursache für Performance-Einbrüche. setState() erstellt den gesamten Unterbaum eines StatefulWidget neu, inklusive aufwändiger Listen und Bilder. Dies führt zu Layout-Neuberechnungen und FPS-Einbrüchen.

Problematischer Code:

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++; // erstellt den GESAMTEN Baum neu
            });
          },
          child: Text('Increment'),
        ),
      ],
    );
  }
}

Lösung: ValueListenableBuilder aktualisiert nur den abhängigen Teil der 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'),
        ),
      ],
    );
  }
}

Ebenso funktionieren StreamBuilder, Selector von provider oder BlocBuilder. Der Neuzeichnungsbereich wird minimiert, was die CPU-Last reduziert.

Verwendung von const-Konstruktoren

Ohne const erstellt jede Neuerstellung neue Widget-Objekte, was GC und CPU belastet. In Listen mit Hunderten von Icons, Texten und Paddings ist dies kritisch.

Google AdInline article slot

Optimaler Ansatz:

Widget build(BuildContext context) {
  return const Column(
    children: [
      Text('Hello'),
      Icon(Icons.home),
      Padding(
        padding: EdgeInsets.all(8),
        child: Text('World'),
      ),
    ],
  );
}

Flutter wiederverwendet const-Objekte, was Allokationen reduziert. Der Gewinn ist in komplexen UIs mit Tausenden kleiner Widgets spürbar.

Logik aus build() auslagern

Sortieren, Filtern oder Parsen in build() läuft bei jedem Aufruf – bis zu 60 Mal pro Sekunde. Für 60 FPS stehen nur 16 ms pro Frame zur Verfügung; schwere Operationen verursachen Ruckler.

Google AdInline article slot

Korrekter Ansatz:

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]}'),
  );
}

Logik läuft einmal in initState oder der Business-Layer. build() bleibt deklarativ.

Lazy-Listen mit ListView.builder

ListView mit children: [...] erstellt alle Elemente auf einmal, auch unsichtbare. Bei 100+ Elementen leiden Speicher und initiales Rendering.

Empfehlung:

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),
      );
    },
  );
}

Elemente werden beim Scrollen generiert, Speicher ist optimiert und Recycling funktioniert effizient.

Keys für Listenstabilität

Ohne Key kann Flutter Elemente bei Listenupdates nicht zuordnen, was unnötige Neuerstellungen und visuelle Artefakte verursacht.

Kritisch für:

  • Neuordnung von Elementen
  • Löschen/Einfügen
  • Übergangsanimationen
ListView.builder(
  itemCount: items.length,
  itemBuilder: (_, index) {
    final item = items[index];
    return ListTile(
      key: ValueKey(item.id),
      title: Text(item.title),
    );
  },
);

ValueKey(item.id) stellt korrekte Updates sicher.

Animationen ohne setState

Timer.periodic mit setState alle 16–100 ms überlastet den UI-Thread.

Standardwerkzeug:

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 synchronisiert mit der FPS des Geräts und aktualisiert minimale UI.

Aufteilung in kleine Widgets

Ein riesiges StatefulWidget wird bei jeder Änderung komplett neu erstellt.

Vorteile der Komposition:

  • Lokalisierte Neuerstellungen
  • Code-Wiederverwendung
  • Bessere Lesbarkeit und Testbarkeit
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 außerhalb von build()

FutureBuilder mit future: fetchData() in build() startet Anfragen bei jeder Neuerstellung.

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 wird einmal erstellt; UI reagiert auf Änderungen.

Wichtige Erkenntnisse

  • Updates lokalisieren: Verwenden Sie ValueListenableBuilder, AnimatedBuilder statt globalem setState.
  • const überall: Reduziert Allokationen um 50–70 % in komplexen UIs.
  • Logik in initState: build() dient nur der deklarativen Beschreibung.
  • ListView.builder + Keys: Für stabile dynamische Listen.
  • Widget-Komposition: Kleine StatelessWidgets statt Monolithen.

— Editorial Team

Advertisement 728x90

Weiterlesen