Shreyansh Sharma
Shreyansh Sharma

Reputation: 1844

A TextEditingController was used after being disposed in Flutter

I made two TextEditing Controllers and initialized them. Also, I initialized them in my initState too, so that when my screen is disposed they can be initialized again, then why am I getting this error? The two text controllers are used in various places and the OTPBackButton is the widget which takes us back to the previous screen.

When I am opening this screen for first time, it works fine but when I click on this OTPBackButton (or simply the back button) and come back to this screen again, this error is shown to me.

I am including full widget if it helps -

class MobileInput extends StatefulWidget {
  final bool isOTP;
  final bool isSignup;
  VoidCallback signUpState;
  TwoArgumentEventCallback signInWithOTP;
  Callback sendOTP;
  MobileInput({
    this.isOTP = true,
    required this.sendOTP,
    required this.signInWithOTP,
    required this.signUpState,
    this.isSignup = false,
  });

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

class _MobileInputState extends State<MobileInput> {

  .....
  TextEditingController contactController = TextEditingController();
  TextEditingController otpController = TextEditingController();
  ..... 

  @override
  void initState() {
    ...
    contactController = TextEditingController();
    otpController = TextEditingController();
    super.initState();
  }

  @override
  void dispose() {
    // contactController.dispose();
    //  otpController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {    
    return GestureDetector(
    .........
                  (isOTP)
                      ? Center(
                          child: RichText(
                            textAlign: TextAlign.center,
                            text: TextSpan(
                              style: text.bodyText2!
                                  .apply(color: secondaryTextColor),
                              children: [
                                TextSpan(
                                    text:
                                        'We sent a verification code to your\nphone number '),
                                TextSpan(
                                  text: (contactController.text.isNotEmpty)
                                      ? '(+91) ' +
                                          contactController.text.toString()
                                      : '',
                                  style:
                                      text.bodyText2!.apply(color: brandPurple),
                                ),
                              ],
                            ),
                          ),
                        )
                      : Container(),
                  SizedBox(
                    height: getProportionateScreenWidth(32),
                  ),
                  isOTP
                      ? PinCodeTextField(
                          animationType: AnimationType.scale,
                          focusNode: focusNode,
                          onChanged: (value) {
                            print(otpController.text + ' after input');
                            if (otpController.text.length < 6) {
                              setState(() {
                                isDisabled = true;
                              });
                            }
                          },
                          .....
                          ),
                          onCompleted: (value) {
                            otpController.text = value;
                            setState(() {
                              otpButtonText = "Verify Phone Number";
                              isDisabled = false;
                            });
                          },
                        )
                      : CustomTextField(
                          .....
                          onChanged: (value) {
                            if (isSignup) {
                              if (contactController.text.length == 10) {
                                FocusScope.of(context).unfocus();
                                emailEntered = true;
                                if (checkboxValue) {
                                  setState(() {
                                    isDisabled = false;
                                  });
                                } else {
                                  setState(() {
                                    isDisabled = true;
                                  });
                                }
                              } else {
                                setState(() {
                                  isDisabled = true;
                                });
                              }
                            } else {
                              if (contactController.text.length == 10) {
                                FocusScope.of(context).unfocus();
                                emailEntered = true;
                                setState(() {
                                  isDisabled = false;
                                });
                              } else {
                                setState(() {
                                  isDisabled = true;
                                });
                              }
                            }
                          },
                          ......
                        ),
                  .....
                  ......    

                  isOTP
                      ? Row(
                          mainAxisAlignment: MainAxisAlignment.center,
                          children: [
                            Text(
                              'Didn\'t received?',
                              style: text.bodyText2!.apply(color: Colors.white),
                            ),
                            TextButton(
                              onPressed: () {
                                setState(() {
                                  otpController.clear();
                                  focusNode.requestFocus();
                                  // autoFocus = true;
                                });
                                (otpController.text.isEmpty)
                                    ? widget.sendOTP(contactController.text)
                                    : null;
                              },
                              child: RichText(
                                ........
                        )
                      : Container(),
                  SizedBox(height: getProportionateScreenWidth(20)),
                  isOTP
                      ? LoginCTA(
                          //After input otp
                          onPressed: () async {
                           .....

                            if (emailEntered &&
                                otpController.text.length == 6) {
                              bool res;
                              try {
                                res = await widget.signInWithOTP(
                                    contactController.text, otpController.text);
                              } catch (e) {
                                res = false;
                                print(e);
                              }

                        ..............

                        LoginCTA(
                          //After input mobile number
                          onPressed: () async {
                            if (emailEntered &&
                                contactController.text.length == 10) {
                              widget.sendOTP(contactController.text);
                              setState(() {
                                isOTP = true;
                                isDisabled = true;
                              });
                            } else {
                              ......
                            }
                          },
                          .......
                        ),
                  SizedBox(height: getProportionateScreenWidth(28)),
                  !isOTP
                      ? Row(
                          mainAxisAlignment: MainAxisAlignment.center,
                          children: [
                            Text(
                              (isSignup)
                                  ? 'Already have an account? '
                                  : 'Don\'t have an account? ',
                              style: TextStyle(
                                fontSize: 16,
                                color: Colors.white,
                              ),
                            ),
                            GestureDetector(
                              onTap: () => setState(() {
                                isSignup = !isSignup;
                                if (isSignup) {
                                  if (contactController.text.length == 10) {
                                    FocusScope.of(context).unfocus();
                                    emailEntered = true;
                                    if (checkboxValue) {
                                      setState(() {
                                        isDisabled = false;
                                      });
                                    } else {
                                      setState(() {
                                        isDisabled = true;
                                      });
                                    }
                                  } else {
                                    setState(() {
                                      isDisabled = true;
                                    });
                                  }
                                } else {
                                  if (contactController.text.length == 10) {
                                    FocusScope.of(context).unfocus();
                                    emailEntered = true;
                                    setState(() {
                                      isDisabled = false;
                                    });
                                  } else {
                                    setState(() {
                                      isDisabled = true;
                                    });
                                  }
                                }
                              }),
                              .......
                          ],
                        )
                      : Container(),
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }
}

Upvotes: 1

Views: 5071

Answers (2)

Christopher Ahmed
Christopher Ahmed

Reputation: 51

I had a similar issue. This mostly occur when sharing TextEditingController between classes/widgets.

for example when you pass an external TextEditingController into a Widget but then inside the widget you initialize a TextEditingController that checks if you have an external TextEditingController passed in. And if true you assign the external TextEditingController to the internal TextEditingController and if external is null then you initialize with a fresh TextEditingController.

class MyStatefulWidgetState extends State<MyStatefulWidget> {
 
 ...

 late TextEditingController controller

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

  controller = widget.controller?? TextEditingController();
 }

 @override
 void dispose() {
  controller.dispose(); //this is wrong

  super.dispose();
 }

}

Problem: Most often you call dispose() on the TextEditingController indiscriminately and then sometimes it leads to the error.

What i noticed is that you should only dispose a TextEditingController at the class or widget in which they were originally created to avoid this error. So you should check if you have an external TextEditingController and not dispose it inside the widget/class that is borrowing it, but rather dispose it in the place that it originated from. Only dispose TextEditingController where it was created.

note: Make sure that the parent class/widget of the external TextEditingController is not destroyed before the widget that calls the external TextEditingController.

So the correct way to dispose the above controller would be:

@override
 void dispose() {
  // go and dispose widget.controller in the class it originated from.
  if(widget.controller == null) { 
   controller.dispose();
  }

  super.dispose();
 }

Upvotes: 3

Vandad Nahavandipoor
Vandad Nahavandipoor

Reputation: 1574

You need to mark your TextEditingController instances as late final when you define them and then initialize them in the initState() function and dispose of them in dispose() as I show you here:

class HomePage extends StatefulWidget {
  const HomePage({Key? key}) : super(key: key);

  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  late final TextEditingController _textController;

  @override
  void initState() {
    _textController = TextEditingController();
    super.initState();
  }

  @override
  void dispose() {
    _textController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    throw UnimplementedError();
  }

}

Upvotes: 2

Related Questions