Kodokon kodokon.com

Partager l'état : lifting state up et callbacks

Faites vivre l'état dans l'ancêtre commun et transformez vos widgets enfants en widgets contrôlés qui remontent les changements par callbacks.

8 min · 3 questions

Ouvrir cette leçon dans Kodokon

Situation classique : un sélecteur de quantité et un total affiché ailleurs doivent partager la même donnée. La règle d'or : l'état vit dans l'ancêtre commun le plus proche des widgets qui en dépendent. Le parent détient la donnée, la passe en paramètre vers le bas, et reçoit les changements par un callback qui remonte. C'est le « lifting state up ».

DART
import 'package:flutter/material.dart';

class CartScreen extends StatefulWidget {
  const CartScreen({super.key});

  @override
  State<CartScreen> createState() => _CartScreenState();
}

class _CartScreenState extends State<CartScreen> {
  int _quantity = 1;

  void _updateQuantity(int value) {
    setState(() => _quantity = value);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Panier')),
      body: Column(
        children: [
          QuantitySelector(
            quantity: _quantity,
            onChanged: _updateQuantity,
          ),
          Text('Total : ${_quantity * 20} €'),
        ],
      ),
    );
  }
}
Le parent détient l'état et distribue valeur et callback.

L'enfant devient un widget contrôlé : il est StatelessWidget, ne stocke rien, affiche la valeur reçue et signale chaque intention via onChanged. ValueChanged<int> est simplement un alias de void Function(int). Remarquez le bouton moins désactivé sous la quantité 1 : passer null à onPressed suffit, et cette logique découle de la valeur reçue.

DART
import 'package:flutter/material.dart';

class QuantitySelector extends StatelessWidget {
  const QuantitySelector({
    super.key,
    required this.quantity,
    required this.onChanged,
  });

  final int quantity;
  final ValueChanged<int> onChanged;

  @override
  Widget build(BuildContext context) {
    return Row(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        IconButton(
          icon: const Icon(Icons.remove),
          onPressed: quantity > 1
              ? () => onChanged(quantity - 1)
              : null,
        ),
        Text('$quantity'),
        IconButton(
          icon: const Icon(Icons.add),
          onPressed: () => onChanged(quantity + 1),
        ),
      ],
    );
  }
}
L'enfant affiche la valeur reçue et remonte les intentions.

Vous utilisez ce contrat en permanence sans le savoir : Checkbox, Slider, Switch sont tous des widgets contrôlés (value + onChanged). Quand l'arbre se creuse et que faire transiter les callbacks sur cinq niveaux devient pénible, il sera temps de découvrir les solutions de gestion d'état - c'est l'objet du module suivant.

Quiz de validation

Vérifiez que vous avez bien retenu les points clés de cette leçon.

  1. Où placer un état partagé par deux widgets frères ?
    • Dans chacun des deux widgets
    • Dans l'ancêtre commun le plus proche
    • Dans une variable globale
    • Dans le MaterialApp
  2. Comment un widget enfant demande-t-il une modification de l'état ?
    • Il appelle setState directement sur son parent
    • Il modifie la propriété reçue en paramètre
    • Il invoque le callback fourni par le parent
  3. Qu'est-ce qu'un widget contrôlé ?
    • Un widget qui reçoit sa valeur et remonte les changements via un callback, sans état propre
    • Un widget qui utilise obligatoirement setState
    • Un widget couvert par des tests unitaires
    • Un widget déclaré const