Davin Seju
Davin Seju

Reputation: 99

How to access a widgets state from a stateful widget class Flutter

Sorry if this has been already answered somewhere else but I am new to Flutter. I have a toString method in my widget below that needs to access the state of the widget to output the string. The widget is a card that contains a text field and other text-related operations. To store information on what a user types into the card I need to get all the data into one string which toString returns.

class TextCard extends StatefulWidget {
  _TextCardState cardState = _TextCardState();
  TextCard({String text = ""}) {
    cardState.textController.text = text;

  }
  @override
  _TextCardState createState() => cardState = new _TextCardState();

  String toString({DiagnosticLevel minLevel = DiagnosticLevel.debug}) {
    return delimiter2 +
        "TextCard" +delimiter3 +
        cardState.getText() +
        delimiter3 +
        (cardState.center.toString()) +
        delimiter3 +
        cardState.bold.toString() +
        delimiter3 +
        cardState.italic.toString() +
        delimiter3 +
        cardState.size.toString() +

        delimiter2;
  }
}

The widget also takes in a string value to set the initial value of a text field in the state below

class _TextCardState extends State<TextCard> {
  double size = 18;
  bool bold = false;
  bool italic = false;
  bool center = false;

  var textController = TextEditingController();

  @override
  Widget build(BuildContext context) {
    return Container(
        height: _cardSizeY,
        width: _cardSizeX,
        child: Card(
            elevation: _elevation,
            child: Center(
                child: Column(children: [
              ListTile(leading: Icon(Icons.text_fields)),
              ButtonBar(children: [
                IconButton(
                  icon: Icon(Icons.format_bold),
                  onPressed: () {
                    updateText(size, !bold, italic, center);
                  },
                ),
                IconButton(
                  icon: Icon(Icons.format_italic),
                  onPressed: () {
                    updateText(size, bold, !italic, center);
                  },
                ),
                Slider(
                    value: size,
                    max: 80,
                    min: 1,
                    onChanged: (size) {
                      updateText(size, bold, italic, center);
                    })
              ]),
              TextField(
                  maxLines: null,
                  style: TextStyle(
                      fontWeight: (bold) ? FontWeight.bold : FontWeight.normal,
                      fontStyle: (italic) ? FontStyle.italic : FontStyle.normal,
                      fontSize: size),
                  textAlign: (center) ? TextAlign.center : TextAlign.start,
                  controller: textController,
                  decoration: InputDecoration(
                      border: OutlineInputBorder(
                          borderSide: BorderSide(color: Colors.grey),
                          borderRadius: BorderRadius.all(Radius.circular(10)))))
            ]))));
  }

  void updateText(double size, bool bold, bool italic, bool center) {
    setState(() {
      this.size = size;
      this.bold = bold;
      this.italic = italic;
      this.center = center;
    });
  }

  String getText() {
    return textController.value.text;
  }
}

When I run this code I get the error the create state function returned an old invalid state instance. I have looked into putting the text controller into the _TextCardState() class but I would not be able to change the initial value of the TextField.

Upvotes: 0

Views: 432

Answers (1)

Loren.A
Loren.A

Reputation: 5575

So I see what you are trying to do here but there are better ways to access the value of a textfield from outside of the class.

Instead of access your toString method from outside, which relies on a values from the private state class, I suggest a state management solution that will make this way easier and cleaner. You'll also have easier access to all those variables you need.

What you're doing here is not something that's meant to be done, which is why you're getting those state errors.

_TextCardState cardState = _TextCardState();

Here's a way to do it using GetX.

All your data will live in a GetX Controller class below and will be used in your now stateless TextCard widget.

class Controller extends GetxController {
  var textController = TextEditingController();
  String textfieldString = '';
  double size = 18;
  bool bold = false;
  bool italic = false;
  bool center = false;

  @override
  void onInit() {
    super.onInit();
    // updates the value of textfieldString anytime the user types
    textController.addListener(() {
      textfieldString = textController.text;
      debugPrint(textController.text);
    });
  }

  // this method lives in this class and is accessible from anywhere. The
// only thing not clear is what delimier2 is and where it comes from
// toString is not a good name because that is an overridden method that lives 
// in most Dart classes

   String buildString({DiagnosticLevel minLevel = DiagnosticLevel.debug}) {
    return delimiter2 +
        "TextCard" +delimiter3 +
        textfieldString +
        delimiter3 +
        (center.toString()) +
        delimiter3 +
        bold.toString() +
        delimiter3 +
        italic.toString() +
        delimiter3 +
        size.toString() +

        delimiter2;
  }

// single responsibility methods as opposed to firing one big function 
// multiple times when its only affecting one variable

  void toggleBold() {
    bold = !bold;
    update();
  }

  void toggleItalic() {
    italic = !italic;
    update();
  }

  void toggleCenter() {
    center = !center;
    update();
  }

  void updateSize(double sliderValue) {
    size = sliderValue;
    update();
  }
}

Put this in your main before running your app. Can be done anywhere as long as its before you try and access the controller.

  Get.put(Controller()); 

And here is your TextCard widget

class TextCard extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final controller =
        Get.find<Controller>(); // finding the initalized controller
    return Container(
      height: _cardSizeY,
      width: _cardSizeX,
      child: Card(
        elevation: 20,
        child: Center(
          child: Column(
            children: [
              ListTile(leading: Icon(Icons.text_fields)),
              ButtonBar(children: [
                IconButton(
                  icon: Icon(Icons.format_bold),
                  onPressed: () {
                    controller.toggleBold();
                  },
                ),
                IconButton(
                  icon: Icon(Icons.format_italic),
                  onPressed: () {
                    controller.toggleItalic(); // accessing method via controller
                  },
                ),
                // GetBuilder rebuilds children when value of controller variable changes
                GetBuilder<Controller>(
                  builder: (_) {
                    return Slider(
                        value: controller
                            .size, // accessing size in other class via controller
                        max: 80,
                        min: 1,
                        onChanged: (value) {
                          controller.updateSize(value); 
                        });
                  },
                )
              ]),
              GetBuilder<Controller>(
                builder: (_) {
                  return TextField(
                    maxLines: null,
                    style: TextStyle(
                        fontWeight: (controller.bold)
                            ? FontWeight.bold
                            : FontWeight.normal,
                        fontStyle: (controller.italic)
                            ? FontStyle.italic
                            : FontStyle.normal,
                        fontSize: controller.size),
                    textAlign: (controller.center)
                        ? TextAlign.center
                        : TextAlign.start,
                    controller: controller.textController,
                    decoration: InputDecoration(
                      border: OutlineInputBorder(
                        borderSide: BorderSide(color: Colors.grey),
                        borderRadius: BorderRadius.all(
                          Radius.circular(10),
                        ),
                      ),
                    ),
                  );
                },
              )
            ],
          ),
        ),
      ),
    );
  }
}

So where ever you are in your app where you need that function, find the controller and get your value.

final controller = Get.find<Controller>():
final newString = controller.buildString();

This will be easier and use less memory because TextCard is now stateless.

Upvotes: 1

Related Questions