skip to Main Content

I have several custom Widgets in my Flutter app that edit the variables that I send and I need the new value.

I have tried this in two ways, but I don’t know which one is the best.

I have done it via sending a callback that gets me the new value:

class ClosedDaysSelector extends StatefulWidget {
  final Function(Map<String, bool>)? onChanged;

  const ClosedDaysSelector({Key? key, this.onChanged}) : super(key: key);
  @override
  State<ClosedDaysSelector> createState() => _ClosedDaysSelectorState();
}

class _ClosedDaysSelectorState extends State<ClosedDaysSelector> {
  List<String> weekdays = ['L', 'M', 'X', 'J', 'V', 'S', 'D'];
  List<bool> values = List.filled(7, false);

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        const Text('Días de cierre'),
        Row(
          children: [
            for (int i = 0; i < weekdays.length; i++)
              Expanded(
                child: MyCheckboxListTile(
                  title: weekdays[i],
                  value: values[i],
                  onChanged: (bool? value) {
                    setState(() {
                      values[i] = value!;
                    });
                    if (widget.onChanged != null) {
                      widget.onChanged!(toJson());
                    }
                  },
                ),
              ),
          ],
        ),
      ],
    );
  }

  Map<String, bool> toJson() {
    final jsonMap = <String, bool>{};
    for (int i = 0; i < weekdays.length; i++) {
      jsonMap[weekdays[i]] = values[i];
    }
    return jsonMap;
  }
}

Also I have done it with text editing controllers, and this is the more similar way to pass by reference that I have found:

class DropdownZonasWidget extends StatefulWidget {
  final TextEditingController dropdownValueController;
  final Function(String) onDropdownValueChanged;
  final bool enabled;

  const DropdownZonasWidget({Key? key, required this.dropdownValueController, required this.onDropdownValueChanged, required this.enabled}) : super(key: key);

  @override
  State<DropdownZonasWidget> createState() => _DropdownZonasState();
}

class _DropdownZonasState extends State<DropdownZonasWidget> {
  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
      child: FutureBuilder(
          future: MyDatabase.instance.get_zonas(),
          builder: (BuildContext context, AsyncSnapshot<List<Zona>> snapshot) {
            if (snapshot.hasData) {
              List<Zona> salas = snapshot.data!;
              List<DropdownMenuItem<String>> defaultValue = [
                const DropdownMenuItem<String>(
                  value: '',
                  child: Text(""),
                )
              ];
              if(snapshot.data!.any((item) => item.zona == '#') && widget.dropdownValueController.text == '')
              {
                widget.dropdownValueController.text = '#';
              }

              return Padding(
                  padding: const EdgeInsets.only(right: 10),
                  child: IgnorePointer(
                    ignoring: widget.enabled,
                    child: DropdownButtonFormField<String>(
                        value: widget.dropdownValueController.text,
                        style: const TextStyle(color: Colors.black),
                        onChanged: (String? newValue) {
                          widget.dropdownValueController.text = newValue ?? '';
                          widget.onDropdownValueChanged(newValue ?? '');
                        },
                        items: defaultValue +
                            salas.map<DropdownMenuItem<String>>((Zona value) {
                              return DropdownMenuItem<String>(
                                value: value.zona.toString(),
                                child: Text(
                                  value.zona.toString(),
                                  style: const TextStyle(color: Colors.black, fontSize: 16, fontWeight: FontWeight.w600),
                                ),
                              );
                            }).toList(),
                        hint: const Text(
                          "Zonas",
                          style: TextStyle(color: Colors.black, fontSize: 16, fontWeight: FontWeight.w600),
                        ),
                        validator: (value) {
                          if (value == null)
                          {
                            return "Por favor asigna una zona";
                          }
                          else
                          {
                            return null;
                          }
                        }
                    ),
                  )
              );
            } else {
              return const Text("Sin zonas");
            }
          }),
    );
  }
}

2

Answers


  1. In Dart (the language Flutter is written in), all variables are essentially passed by reference because everything is an object. However, Dart doesn’t have the same concept of "reference" like in languages such as C++ or C#. Instead, when you pass a variable to a function or widget, you’re passing a reference to the object the variable points to.

    However, the catch is that primitive types (like int, double, bool) are immutable. So, even though you’re passing their reference, changing them inside a function won’t affect their original value. If you need to modify such values inside a custom widget and reflect those changes outside, you’d typically use a callback or a state management solution.

    Here’s how you might approach this:

    1. Using Callbacks:

      If you want to send a variable to a widget and get updates when it changes, you can use a callback.

      class CustomWidget extends StatelessWidget {
        final int value;
        final ValueChanged<int> onValueChanged;
      
        CustomWidget({required this.value, required this.onValueChanged});
      
        @override
        Widget build(BuildContext context) {
          return ElevatedButton(
            onPressed: () => onValueChanged(value + 1),
            child: Text('Increment'),
          );
        }
      }
      
      // Usage
      int myValue = 5;
      
      CustomWidget(
        value: myValue,
        onValueChanged: (newValue) {
          setState(() {
            myValue = newValue;
          });
        },
      )
      
    2. Using State Management:

      There are various state management solutions in Flutter, such as Provider, Riverpod, Bloc, etc. Using them, you can effectively "pass by reference" by accessing and mutating shared state across widgets.

      Here’s a simple example using Provider:

      class ValueNotifier extends ChangeNotifier {
        int _value = 0;
      
        int get value => _value;
      
        set value(int newValue) {
          _value = newValue;
          notifyListeners();
        }
      }
      
      // In the main widget
      final valueNotifier = ValueNotifier();
      
      // Provide the valueNotifier to descendants
      ChangeNotifierProvider<ValueNotifier>(
        create: (_) => valueNotifier,
        child: CustomWidget(),
      )
      
      // In the CustomWidget
      final valueNotifier = Provider.of<ValueNotifier>(context);
      // You can now directly set or get the value using valueNotifier.value
      
    3. Using GlobalKey:

      While not recommended for simple cases due to its more verbose nature, a GlobalKey can be used to access the state of a stateful widget from outside.

      class CustomWidget extends StatefulWidget {
        final GlobalKey<CustomWidgetState> key;
      
        CustomWidget(this.key) : super(key: key);
      
        @override
        CustomWidgetState createState() => CustomWidgetState();
      }
      
      class CustomWidgetState extends State<CustomWidget> {
        int value = 0;
      
        void increment() {
          setState(() {
            value++;
          });
        }
      
        @override
        Widget build(BuildContext context) {
          return Text('$value');
        }
      }
      
      // Usage
      final key = GlobalKey<CustomWidgetState>();
      
      CustomWidget(key)
      
      // Accessing the value or incrementing it from outside
      print(key.currentState!.value);
      key.currentState!.increment();
      

    Among these approaches, callbacks and state management solutions are most commonly used, while GlobalKey is typically reserved for more complex cases or when you need direct access to a widget’s state from outside. Choose the approach that best fits the complexity and requirements of your application.

    Login or Signup to reply.
  2. Both approaches are valid, however, I suppose using Callbacks is more common for Flutter applications among developers.

    You can also use state management solutions like Bloc or Provider, and Inherited widgets to achieve the same result.

    class ClosedDaysBlocProvider extends InheritedWidget {
      final ClosedDaysBloc bloc;
    
      ClosedDaysBlocProvider({Key? key, required this.bloc, required Widget child})
          : super(key: key, child: child);
    
      static ClosedDaysBlocProvider of(BuildContext context) {
        return context.dependOnInheritedWidgetOfExactType<ClosedDaysBlocProvider>()!;
      }
    
      @override
      bool updateShouldNotify(covariant InheritedWidget oldWidget) {
        return true;
      }
    }
    
    class ClosedDaysBloc {
      final BehaviorSubject<Map<String, bool>> _closedDaysController =
          BehaviorSubject<Map<String, bool>>();
    
      Stream<Map<String, bool>> get closedDaysStream => _closedDaysController.stream;
    
      ClosedDaysBloc() {
        _closedDaysController.sink.add({
          'L': false,
          'M': false,
          'X': false,
          'J': false,
          'V': false,
          'S': false,
          'D': false,
        });
      }
    
      void toggleDay(String day, bool value) {
        final currentDays = _closedDaysController.value;
        currentDays[day] = value;
        _closedDaysController.sink.add(currentDays);
      }
    
      void dispose() {
        _closedDaysController.close();
      }
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search