Search code examples
flutterdartwidgetfinalstateful

Flutter field must not be final in Stateful widget


Flutter is complaining about the _isSelected because it is not final. The problem is that is must not be final because it is changed on a buttonPressed...

class SelectionButton extends StatefulWidget {
  final String title;
  final Function(String) onSelectionButtonTap;
  bool isSelected;

  SelectionButton({
    required this.title,
    required this.onSelectionButtonTap,
    required this.isSelected,
  });
  @override
  _SelectionButtonState createState() => _SelectionButtonState();
}

class _SelectionButtonState extends State<SelectionButton> {
  @override
  Widget build(BuildContext context) {
    return Expanded(
      child: GestureDetector(
        onTap: () {
          setState(() {
            widget.onSelectionButtonTap(widget.title);
            widget.isSelected = !widget.isSelected;
          });
        },
        child: Container(
          decoration: BoxDecoration(
            color: widget.isSelected ? AppColors.primary : AppColors.white,
            borderRadius: BorderRadius.circular(
              scaleWidth(10),
            ),
          ),
          child: Center(
            child: Text(
              widget.title,
              textAlign: TextAlign.center,
              style: widget.isSelected
                  ? AppTextStyles.h5White
                  : AppTextStyles.h5Light,
            ),
          ),
        ),
      ),
    );
  }
}

This is the warning that Flutter is giving me:

This class (or a class that this class inherits from) is marked as '@immutable', but one or more of its instance fields aren't final: SelectionButton.isSelected

As you can see I am changing widget.isSelected. I am quite new to flutter and don't know why Flutter is complaining here. Everything is working as expected but I guess it is not best practice? How can I fix this issue? What am I missing here?


Solution

  • This is because you should manipulate isSelected in your State class, not your StatefulWidget. You also want to keep the Key parameter on your widgets. Having all variables declared as final allows you to declare the class as const.

    In dart, const means that the object's entire deep state can be determined entirely at compile-time and that the object will be frozen and completely immutable.

    We want to use const whenever possible.

    Here is how you could refactor your widget to shut up the Flutter compiler:

    class SelectionButton extends StatefulWidget {
      final String title;
      final Function(String) onSelectionButtonTap;
      final bool isSelected;
    
      const SelectionButton({
        Key? key,
        required this.title,
        required this.onSelectionButtonTap,
        required this.isSelected,
      }) : super(key: key);
    
      @override
      _SelectionButtonState createState() => _SelectionButtonState();
    }
    
    class _SelectionButtonState extends State<SelectionButton> {
      bool isSelected = false;
    
      @override
      void initState() {
        super.initState();
        isSelected = widget.isSelected;
      }
    
      @override
      Widget build(BuildContext context) {
        return Expanded(
          child: GestureDetector(
            onTap: () {
              setState(() {
                widget.onSelectionButtonTap(widget.title);
                isSelected = !isSelected;
              });
            },
            child: Container(
              decoration: BoxDecoration(
                color: isSelected ? AppColors.primary : AppColors.white,
                borderRadius: BorderRadius.circular(
                  scaleWidth(10),
                ),
              ),
              child: Center(
                child: Text(
                  widget.title,
                  textAlign: TextAlign.center,
                  style: isSelected
                      ? AppTextStyles.h5White
                      : AppTextStyles.h5Light,
                ),
              ),
            ),
          ),
        );
      }
    }