Search code examples
flutterflutter-state

Flutter change state from related widget class


Lets assume a class "SpecialButton" and its State-Class "SpecialButtonState"

class SpecialButton extends StatefulWidget {
  bool active = false;
  SpecialButton({Key key}) : super(key: key);
  @override
  SpecialButtonState createState() => SpecialButtonState();
}

class SpecialButtonState extends State<SpecialButton> {
  @override
  void initState() {
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Container(
        decoration:
            BoxDecoration(color: this.widget.active ? COLOR_1 : COLOR_2),
        child: null);
  }
}

In the parent widget, I manage a couple of these buttons. Therefore, I want to assign a state to them. The solution I tried was to introduce a flag "active" in the SpecialButton class which I can easily set to either true or false from the parent widget. I can then use this in the build function of the state class to colorize the button. Unfortunately, this does not work completely as it does not update the button immediately (it needs some kind of state update e.g. by hovering over the element).

My second idea was to introduce this flag as a propper state of the SpecialButtonState class

class SpecialButton extends StatefulWidget {
  SpecialButton({Key key}) : super(key: key);
  @override
  SpecialButtonState createState() => SpecialButtonState();
}

class SpecialButtonState extends State<SpecialButton> {
  bool active;

  @override
  void initState() {
    super.initState();
    this.active = false;
  }

  activate() {
    this.setState(() {
      active = true;
    });
  }

  deactivate() {
    this.setState(() {
      active = false;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Container(
        decoration: BoxDecoration(color: this.active ? COLOR_1 : COLOR_2),
        child: null);
  }
}

As far as I understood, this would be the correct way to work with flutter but it seems that I can't access the functions "activate" or "deactivate" from either the SpecialButton Class or the Parent Class containing the widget.

So my question is: How can I (directly or indirectly through functions) modify a State from the corresponding StatefulWidget Class or the Parent Widget containing it?

There are already some similar questions about this on here on Stack Overflow where I could find hints both to use or not to use global keys for such behavior which i found misleading. Also, due to the rapid ongoing development of flutter, they are probably outdated so I ask this (similar) question again in relation to this exact use case.

EDIT: I forgot to mention that it is crucial that this flag will be changed after creation therefore It will be changed multiple times during its livetime. This requires the widget to redraw.


Solution

  • It is not neсessary to use stateful widget for SpecialButton is you case. You can handle active flag with stateless widget and keys. Example code:

    
    class SomeParent extends StatefulWidget {
      const SomeParent({Key key}) : super(key: key);
    
      @override
      State<SomeParent> createState() => SomeParentState();
    }
    
    class SomeParentState extends State<SomeParent> {
      bool _button1IsActive = false;
      bool _button2IsActive = false;
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          body: Column(
            children: [
              SpecialButton(
                key: UniqueKey(),
                active: _button1IsActive,
              ),
              SizedBox(height: 8),
              SpecialButton(
                key: UniqueKey(),
                active: _button2IsActive,
              ),
              SizedBox(height: 16),
              TextButton(
                child: Text('Toggle button 1'),
                onPressed: () {
                  setState(() {
                    _button1IsActive = !_button1IsActive;
                  });
                },
              ),
              SizedBox(height: 8),
              TextButton(
                child: Text('Toggle button 2'),
                onPressed: () {
                  setState(() {
                    _button2IsActive = !_button2IsActive;
                  });
                },
              ),
            ],
          ),
        );
      }
    }
    
    class SpecialButton extends StatelessWidget {
      final bool active;
    
      const SpecialButton({Key key, this.active = false}) : super(key: key);
    
      @override
      Widget build(BuildContext context) {
        return Container(
          height: 40,
          width: 40,
          decoration: BoxDecoration(color: active ? Colors.red : Colors.blue),
        );
      }
    }
    
    

    SomeParent is my fantasy, just for example. Don't know what your parent is. Keys are significant here. They tell widget tree when specific widgets with the same type (such as SpecialButton) should be rebuild.

    Please try this approach, it should work.