Techniques pratiques pour accélérer les applications Flutter
Les applications Flutter souffrent souvent de saccades dues à des reconstructions inutiles, des opérations sur le thread d'interface et une gestion inefficace des listes. Cet article décompose les erreurs clés et comment les corriger : de la localisation des mises à jour à l'utilisation appropriée des builders et des animations. Ces techniques aident à maintenir 60 FPS même sur des appareils d'entrée de gamme.
Minimiser les reconstructions inutiles de l'interface
Les reconstructions inutiles sont une cause principale de baisse de performance. setState() reconstruit l'intégralité du sous-arbre d'un StatefulWidget, y compris les listes lourdes et les images. Cela entraîne des recalculs de mise en page et des chutes de FPS.
Code problématique :
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++; // reconstruit l'ARBRE ENTIER
});
},
child: Text('Increment'),
),
],
);
}
}
Solution : ValueListenableBuilder met à jour uniquement la partie dépendante de l'interface.
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'),
),
],
);
}
}
De même, StreamBuilder, Selector de provider, ou BlocBuilder fonctionnent. La zone de repeinture est minimisée, réduisant la charge CPU.
Utiliser les constructeurs const
Sans const, chaque reconstruction crée de nouveaux objets widget, sollicitant le GC et le CPU. Dans les listes avec des centaines d'icônes, de textes et de paddings, c'est critique.
Approche optimale :
Widget build(BuildContext context) {
return const Column(
children: [
Text('Hello'),
Icon(Icons.home),
Padding(
padding: EdgeInsets.all(8),
child: Text('World'),
),
],
);
}
Flutter réutilise les objets const, réduisant les allocations. Le gain est notable dans les interfaces complexes avec des milliers de petits widgets.
Déplacer la logique hors de build()
Le tri, le filtrage ou l'analyse dans build() s'exécute à chaque appel—jusqu'à 60 fois par seconde. Pour 60 FPS, seulement 16 ms par image sont disponibles ; les opérations lourdes causent des saccades.
Approche correcte :
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]}'),
);
}
La logique s'exécute une fois dans initState ou la couche métier. build() reste déclaratif.
Listes paresseuses avec ListView.builder
ListView avec children: [...] crée tous les éléments en une fois, même ceux invisibles. Avec 100+ éléments, la mémoire et le rendu initial en souffrent.
Recommandation :
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),
);
},
);
}
Les éléments sont générés au défilement, la mémoire est optimisée et le recyclage fonctionne efficacement.
Clés pour la stabilité des listes
Sans clé, Flutter ne peut pas faire correspondre les éléments lors des mises à jour de liste, causant des reconstructions inutiles et des artefacts visuels.
Critique pour :
- Réorganiser les éléments
- Suppression/insertion
- Animations de transition
ListView.builder(
itemCount: items.length,
itemBuilder: (_, index) {
final item = items[index];
return ListTile(
key: ValueKey(item.id),
title: Text(item.title),
);
},
);
ValueKey(item.id) assure des mises à jour correctes.
Animations sans setState
Timer.periodic avec setState toutes les 16–100 ms surcharge le thread d'interface.
Outil standard :
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 synchronise avec le FPS de l'appareil, mettant à jour un minimum d'interface.
Décomposer en petits widgets
Un énorme StatefulWidget se reconstruit entièrement à chaque changement.
Avantages de la composition :
- Reconstructions localisées
- Réutilisation du code
- Meilleure lisibilité et testabilité
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 en dehors de build()
FutureBuilder avec future: fetchData() dans build() lance des requêtes à chaque reconstruction.
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());
},
);
}
Le Future est créé une fois ; l'interface réagit aux changements.
Points clés à retenir
- Localiser les mises à jour : Utilisez ValueListenableBuilder, AnimatedBuilder au lieu de setState global.
- const partout : Réduit les allocations de 50–70% dans les interfaces complexes.
- Logique dans initState : build() est uniquement pour la description déclarative.
- ListView.builder + clés : Pour des listes dynamiques stables.
- Composition de widgets : Petits StatelessWidgets plutôt que des monolithes.
— Editorial Team
Aucun commentaire pour le moment.