Siddy Hacks
Siddy Hacks

Reputation: 2310

Flutter StatefulWidget: How to Rebuild Widget When State is Updated Externally

I'm encountering an issue with updating a variable within a StatefulWidget in Flutter. Specifically, I have a custom widget CustomErrorFormField which extends StatefulWidget. This widget contains a method setErrorList to update a list variable errorList. However, when setErrorList is called from another class, the build method in CustomErrorFormField does not reflect the updated errorList.

I understand that calling setState should trigger a rebuild of the widget, but I am unsure how to appropriately invoke setState in the StatefulWidget when the variable is updated from an external class. Here's the relevant code snippet for better understanding:

class CustomErrorFormField extends StatefulWidget {
  // Variable declaration
  List<String> errorList = [];

  // Method to update errorList
  void setErrorList(List<String> listOfError) {
    errorList = listOfError;
  }

  @override
  _CustomErrorFormFieldState createState() => _CustomErrorFormFieldState();
}

class _CustomErrorFormFieldState extends State<CustomErrorFormField> {
  @override
  Widget build(BuildContext context) {
    // Trying to print updated errorList, but not reflecting changes
    print(widget.errorList); 
    return ... // UI Code
  }
}

In another class, I'm updating the errorList like this:

// Instance creation and update
CustomErrorFormField nameTextFild = CustomErrorFormField(...);

// Inside some method
setState(() {
  // Updating errorList using setErrorList method
  if (condition) {
    nameTextFild.setErrorList([...]);
  } else {
    nameTextFild.setErrorList([...]);
  }
});

Upvotes: 0

Views: 1248

Answers (1)

Advait
Advait

Reputation: 599

It's not recommended that you change the state of a widget from outside the widget.

What you should do instead is pass the validation logic as a function and let the widget handle the state change.

CustomFormField:

import 'package:flutter/material.dart';

class CustomErrorFormField extends StatefulWidget {
  //Take the validation logic as a parameter.
  final List<String> Function(String value) validator;
  const CustomErrorFormField({required this.validator});

  @override
  _CustomErrorFormFieldState createState() {
    return _CustomErrorFormFieldState();
  }
}

class _CustomErrorFormFieldState extends State<CustomErrorFormField> {
  
  //Keep the state inside the widget itself
  List<String> errorList = [];

  //Update the state from inside the widget
  void setErrorList(List<String> listOfError) {
    setState(() {
      errorList = listOfError;
    });
  }

  @override
  void initState() {
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Form(
      child: TextFormField(
        validator: (String value){
           //Use the validation logic to decide the error.
           setErrorList(widget.validator(value))
          }
        }
      ),
    );
  }
}

I have used TextFormField as an example, you can use any widget that accepts a callback upon change.

If you're making everything from scratch you can attach the validator function to a callback that fires when the text is changed. Usually this is done with the help of a controller.

usage:

final nameTextFild = CustomErrorFormField(
  key: ValueKey(count),
  labelName: "Name",
  iContext: context,
  validator: (String value) {
    if (!value.contains(RegExp(r'[0-9]'))) {
      return [];
    } else {
      return ["Invalid characters, use letters only."];
    }
  },
);

Upvotes: 1

Related Questions