I have a simplified flutter control, think of a row of 'radio' buttons or a menu bar. The parent passes in a list of 'captions' for each button and a callback. The control then hits the callback passing the index of the button tapped. The issue is, the 'buttons' are created dynamically and the quantity may vary by the parent. When I set the callback for the onTap function in GestureDetector, it will always hit the callback with the last value of the parameter (idx) in the loop. So if there are 4 buttons, the doCallback is always called with a 4, no matter which button is tapped. It appears like doCallback is being called with a reference to idx, rather than the value of idx. Is there a way to make each button send it's own index to the callback?
class CtrlRadioSelector extends StatelessWidget {
CtrlRadioSelector({Key? key, required this.captions, required this.onTapItem})
: super(key: key);
final List<String> captions;
final ValueSetter<int> onTapItem;
@override
Widget build(BuildContext context) {
List<Widget> selectorItems = [];
int idx = 0;
for (var caption in captions) {
selectorItems.add(Expanded(
flex: 10,
child: GestureDetector(
onTap: () => doCallback(idx),
child: Text(caption,
textAlign: TextAlign.center,
style: TextStyle(fontSize: 18)))));
idx ;
}
return Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: selectorItems);
}
void doCallback(int idx) {
onTapItem(idx);
}
}
CodePudding user response:
This is the correct way to create a dynamic row with buttons, where the actual index of the children is preserved:
import 'package:flutter/material.dart';
class CtrlRadioSelector extends StatelessWidget {
const CtrlRadioSelector({Key? key, required this.captions, required this.onTapItem})
: super(key: key);
final List<String> captions;
final ValueSetter<int> onTapItem;
@override
Widget build(BuildContext context) {
return Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: List.generate( //equivalent to your code using for loop and a list
captions.length, //if length of captions is 4, it'll iterate 4 times
(idx) {
return Expanded(
flex: 10,
child: GestureDetector(
onTap: () => doCallback(
idx), //value of idx is the actual index of the button
child: Text(captions[idx],
textAlign: TextAlign.center,
style: const TextStyle(fontSize: 18))));
}));
}
void doCallback(int idx) {
onTapItem(idx);
}
}
CodePudding user response:
One fix would be use a for
loop that iterates with an index, which you need anyway:
for (var idx = 0; idx < captions.length; i = 1) {
selectorItems.add(Expanded(
flex: 10,
child: GestureDetector(
onTap: () => doCallback(idx),
child: Text(captions[idx],
textAlign: TextAlign.center,
style: TextStyle(fontSize: 18)))));
}
This is because Dart specifically makes closures capture a for
-loop's index (and not the values of all in-scope variables). Per the Dart Language Tour:
Closures inside of Dart’s
for
loops capture the value of the index, avoiding a common pitfall found in JavaScript. For example, consider:var callbacks = []; for (var i = 0; i < 2; i ) { callbacks.add(() => print(i)); } callbacks.forEach((c) => c());
The output is 0 and then 1, as expected. In contrast, the example would print 2 and then 2 in JavaScript.