Supposez que vous ayez un “Dialog” comportant des Widgets tels que RadioListTile, DropdownButton… ou n’importe quoi d’autre qui devrait être mis à jour PENDANT que le Dialog reste visible… Comment faire?

Difficulté: Débutant

Contexte

Dernièrement, j’ai dû afficher un Dialog pour permettre à l’utilisateur de sélectionner un élément dans une liste et je voulais afficher une liste de RadioListTile.

Je n’ai eu aucun problème pour afficher le Dialog et la liste, via le code source suivant:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';

class Sample extends StatefulWidget {
  @override
  _SampleState createState() => new _SampleState();
}

class _SampleState extends State<Sample> {
  List<String> countries = <String>['Belgium','France','Italy','Germany','Spain','Portugal'];
  int _selectedCountryIndex = 0;

  @override
  void initState() {
    super.initState();
    _showDialog();
  }

  _buildList(){
    if (countries.length == 0){
      return new Container();
    }

    return new Column(
      children: new List<RadioListTile<int>>.generate(
        countries.length,
        (int index){
          return new RadioListTile<int>(
            value: index,
            groupValue: _selectedCountryIndex,
            title: new Text(countries[index]),
            onChanged: (int value) {
              setState((){
                _selectedCountryIndex = value;
              });
            },
          );
        }
      )
    );
  }

  _showDialog() async{
    await showDialog<String>(
      context: context,
      builder: (BuildContext context){
        return new CupertinoAlertDialog(
          title: new Text('Please select'),
          actions: <Widget>[
            new CupertinoDialogAction(
              isDestructiveAction: true,
              onPressed: (){Navigator.of(context).pop('Cancel');},
              child: new Text('Cancel'),
            ),
            new CupertinoDialogAction(
              isDestructiveAction: true,
              onPressed: (){Navigator.of(context).pop('Accept');},
              child: new Text('Accept'),
            ),
          ],
          content: new SingleChildScrollView(
            child: new Material(
              child: _buildList(),
            ),
          ),
        );
      },
      barrierDismissible: false,
    );
  }

  @override
  Widget build(BuildContext context) {
    return new Container();
  }
}

J’ai été surpris de voir que malgré le setState en lignes #34-36, le RadioListTile sélectionné n’était pas actualisé lorsque l’utilisateur appuyait sur l’un des éléments.

Explication

Après quelques recherches, j’ai réalisé que setState() fait référence au Widget “stateful” dans lequel setState est invoqué.

Dans cet exemple, tout appel à setState() reconstruit la vue du Sample Widget, et non celle du contenu de la boîte de dialogue. Par conséquent, comment faire?

Solution

Une solution très simple consiste à créer un autre Widget avec état(Stateful) qui restitue le contenu de la boîte de dialogue. Ensuite, toute invocation de setState reconstruira le contenu de la boîte de dialogue.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';

class Sample extends StatefulWidget {
  @override
  _SampleState createState() => new _SampleState();
}

class _SampleState extends State<Sample> {
  List<String> countries = <String>['Belgium','France','Italy','Germany','Spain','Portugal'];

  @override
  void initState() {
    super.initState();
    _showDialog();
  }

  _showDialog() async{
    await showDialog<String>(
      context: context,
      builder: (BuildContext context){
        return new CupertinoAlertDialog(
          title: new Text('Please select'),
          actions: <Widget>[
            new CupertinoDialogAction(
              isDestructiveAction: true,
              onPressed: (){Navigator.of(context).pop('Cancel');},
              child: new Text('Cancel'),
            ),
            new CupertinoDialogAction(
              isDestructiveAction: true,
              onPressed: (){Navigator.of(context).pop('Accept');},
              child: new Text('Accept'),
            ),
          ],
          content: new SingleChildScrollView(
            child: new Material(
              child: new MyDialogContent(countries: countries),
            ),
          ),
        );
      },
      barrierDismissible: false,
    );
  }

  @override
  Widget build(BuildContext context) {
    return new Container();
  }
}

class MyDialogContent extends StatefulWidget {
  MyDialogContent({
    Key key,
    this.countries,
  }): super(key: key);

  final List<String> countries;

  @override
  _MyDialogContentState createState() => new _MyDialogContentState();
}

class _MyDialogContentState extends State<MyDialogContent> {
  int _selectedIndex = 0;

  @override
  void initState(){
    super.initState();
  }

  _getContent(){
    if (widget.countries.length == 0){
      return new Container();
    }

    return new Column(
      children: new List<RadioListTile<int>>.generate(
        widget.countries.length,
        (int index){
          return new RadioListTile<int>(
            value: index,
            groupValue: _selectedIndex,
            title: new Text(widget.countries[index]),
            onChanged: (int value) {
              setState((){
                _selectedIndex = value;
              });
            },
          );
        }
      )
    );
  }

  @override
  Widget build(BuildContext context) {
    return _getContent();
  }
}

Conclusion

Parfois, certaines notions de base sont difficiles et setState en fait partie.

Comme la documentation officielle ne l’explique pas encore, je voulais partager cela avec vous.

Restez à l’écoute pour d’autres conseils et bon codage.