Home > Net >  How to use a variable parameter in a Flutter callback?
How to use a variable parameter in a Flutter callback?

Time:12-18

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.

  • Related