Reputation: 1844
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
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
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