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
2import 'package:flutter/cupertino.dart';
3import 'package:flutter/material.dart';
4
5class Sample extends StatefulWidget {
6 const Sample({super.key});
7
8
9 State<Sample> createState() => _SampleState();
10}
11
12class _SampleState extends State<Sample> {
13 final List<String> countries = <String>[
14 'Belgium',
15 'France',
16 'Italy',
17 'Germany',
18 'Spain',
19 'Portugal'
20 ];
21 int _selectedCountryIndex = 0;
22
23
24 void initState() {
25 super.initState();
26 WidgetsBinding.instance.addPostFrameCallback((_) {
27 _showDialog();
28 });
29 }
30
31 _buildList() {
32 if (countries.isEmpty) {
33 return const SizedBox.shrink();
34 }
35
36 return Column(
37 children:
38 List<RadioListTile<int>>.generate(countries.length, (int index) {
39 return RadioListTile<int>(
40 value: index,
41 groupValue: _selectedCountryIndex,
42 title: Text(countries[index]),
43 onChanged: (int? value) {
44 if (mounted) {
45 setState(() {
46 _selectedCountryIndex = value!;
47 });
48 }
49 },
50 );
51 }));
52 }
53
54 _showDialog() async {
55 await showDialog<String>(
56 context: context,
57 builder: (BuildContext context) {
58 return CupertinoAlertDialog(
59 title: const Text('Please select'),
60 actions: <Widget>[
61 CupertinoDialogAction(
62 isDestructiveAction: true,
63 onPressed: () {
64 Navigator.of(context).pop('Cancel');
65 },
66 child: const Text('Cancel'),
67 ),
68 CupertinoDialogAction(
69 isDestructiveAction: true,
70 onPressed: () {
71 Navigator.of(context).pop('Accept');
72 },
73 child: const Text('Accept'),
74 ),
75 ],
76 content: SingleChildScrollView(
77 child: Material(
78 child: _buildList(),
79 ),
80 ),
81 );
82 },
83 barrierDismissible: false,
84 );
85 }
86
87
88 Widget build(BuildContext context) {
89 return Container();
90 }
91}
92
93
J'ai été surpris de voir que malgré le setState en lignes #44-48, 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
2import 'package:flutter/cupertino.dart';
3import 'package:flutter/material.dart';
4
5class Sample extends StatefulWidget {
6 const Sample({super.key});
7
8
9 State<Sample> createState() => _SampleState();
10}
11
12class _SampleState extends State<Sample> {
13 final List<String> countries = <String>[
14 'Belgium',
15 'France',
16 'Italy',
17 'Germany',
18 'Spain',
19 'Portugal'
20 ];
21
22
23 void initState() {
24 super.initState();
25 WidgetsBinding.instance.addPostFrameCallback((_) {
26 _showDialog();
27 });
28 }
29
30 _showDialog() async {
31 await showDialog<String>(
32 context: context,
33 builder: (BuildContext context) {
34 return CupertinoAlertDialog(
35 title: const Text('Please select'),
36 actions: <Widget>[
37 CupertinoDialogAction(
38 isDestructiveAction: true,
39 onPressed: () {
40 Navigator.of(context).pop('Cancel');
41 },
42 child: const Text('Cancel'),
43 ),
44 CupertinoDialogAction(
45 isDestructiveAction: true,
46 onPressed: () {
47 Navigator.of(context).pop('Accept');
48 },
49 child: const Text('Accept'),
50 ),
51 ],
52 content: SingleChildScrollView(
53 child: Material(
54 child: MyDialogContent(countries: countries),
55 ),
56 ),
57 );
58 },
59 barrierDismissible: false,
60 );
61 }
62
63
64 Widget build(BuildContext context) {
65 return Container();
66 }
67}
68
69class MyDialogContent extends StatefulWidget {
70 const MyDialogContent({
71 super.key,
72 required this.countries,
73 });
74
75 final List<String> countries;
76
77
78 State<MyDialogContent> createState() => _MyDialogContentState();
79}
80
81class _MyDialogContentState extends State<MyDialogContent> {
82 int _selectedIndex = 0;
83
84 Widget _getContent() {
85 if (widget.countries.isEmpty) {
86 return const SizedBox.shrink();
87 }
88
89 return Column(
90 children: List<RadioListTile<int>>.generate(
91 widget.countries.length,
92 (int index) {
93 return RadioListTile<int>(
94 value: index,
95 groupValue: _selectedIndex,
96 title: Text(widget.countries[index]),
97 onChanged: (int? value) {
98 if (mounted) {
99 setState(() {
100 _selectedIndex = value!;
101 });
102 }
103 },
104 );
105 },
106 ),
107 );
108 }
109
110
111 Widget build(BuildContext context) {
112 return _getContent();
113 }
114}
115
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.