'How to fix Flutter 'Text Editing Controller used after being disposed' error in Stepper widget
I'm using a Stepper with five text form fields, one in the first step, two in the second step and two in the third step. I have a different form keys for each of the three steps. I have a suffix icon button that I use as a way to clear the text from the text form field. I'm wondering if that is causing my problem. Or maybe I should make each text form field a Stateful widget which I saw as a suggestion while searching SO. I'm new to programming so I can't figure out what to do. When I use the suffix icon to clear the text in the first step everything works fine. I can add text again and then proceed to the next step. Once I get to the second step or the third step the first time that I try to use the suffix icon to clear the text in the text field I get the error message:
A TextEditingController was used after being disposed.
As long as I don't use the suffix icon I can go through all three steps and upload all the data accurately to Firebase. I have tried to remove as much of the code from below as possible to make it easier to follow. In the code below I only show one TextEditingController, one Validator function, one listener in InitState, one method that is used to clear the text from the text field, one controller being disposed of but in the app there are five of each with the four deleted being identical to what is being shown. If I need to show the full code I will be happy to do so. I also removed the method used to add the data to Firestore as that works perfectly as long as I don't use the suffix icon to clear any data during the three steps. Any help will be greatly appreciated. Thank you.
class AddTriviaGamePage extends StatefulWidget {
const AddTriviaGamePage({
Key? key,
required this.triviaGameID,
}) : super(key: key);
final String triviaGameID;
@override
_AddTriviaGamePageState createState() => _AddTriviaGamePageState();
}
class _AddTriviaGamePageState extends State<AddTriviaGamePage> {
final _questionFormKey = GlobalKey<FormState>();
final _answerGroupOneFormKey = GlobalKey<FormState>();
final _answerGroupTwoFormKey = GlobalKey<FormState>();
FirestoreMethods firestoreMethods = FirestoreMethods();
bool _isLoading = false;
final TextEditingController _triviaGameQuestionController =
TextEditingController();
int currentStep = 0;
@override
void initState() {
super.initState();
_triviaGameQuestionController.addListener(() {
setState(() {});
});
}
List<Step> getTriviaGameSteps() => [
Step(
content: AddTriviaGamePageQuestionTextField(
formOneKey: _questionFormKey,
suffixIcon: _triviaGameQuestionController.text.isEmpty
? const EmptyContainer()
: ClearTextFieldIconButton(
onPressed: _clearTriviaGameQuestionTextField,
),
triviaGameQuestionController: _triviaGameQuestionController,
validator: _triviaGameQuestionValidator,
),
isActive: currentStep >= 0,
state: currentStep > 0 ? StepState.complete : StepState.indexed,
title: const Text(StepperString.questions),
),
Step(
content: AddTriviaGamePageAnswerGroupOneContainer(
answerGroupOneFormKey: _answerGroupOneFormKey,
suffixIconAnswerOne: _triviaGameAnswerOneController.text.isEmpty
? const EmptyContainer()
: ClearTextFieldIconButton(
onPressed: _clearTriviaGameAnswerOneTextField,
),
suffixIconAnswerTwo: _triviaGameAnswerTwoController.text.isEmpty
? const EmptyContainer()
: ClearTextFieldIconButton(
onPressed: _clearTriviaGameAnswerTwoTextField,
),
triviaGameAnswerOneController: _triviaGameAnswerOneController,
triviaGameAnswerTwoController: _triviaGameAnswerTwoController,
validatorAnswerOne: _triviaGameAnswerOneValidator,
validatorAnswerTwo: _triviaGameAnswerTwoValidator,
),
isActive: currentStep >= 1,
state: currentStep > 1 ? StepState.complete : StepState.indexed,
title: const Text(StepperString.options),
),
Step(
content: AddTriviaGamePageAnswerGroupTwoContainer(
answerGroupTwoFormKey: _answerGroupTwoFormKey,
suffixIconAnswerThree: _triviaGameAnswerThreeController.text.isEmpty
? const EmptyContainer()
: ClearTextFieldIconButton(
onPressed: _clearTriviaGameAnswerThreeTextField,
),
suffixIconAnswerFour: _triviaGameAnswerFourController.text.isEmpty
? const EmptyContainer()
: ClearTextFieldIconButton(
onPressed: _clearTriviaAnswerOptionFourTextField,
),
triviaGameAnswerThreeController: _triviaGameAnswerThreeController,
triviaGameAnswerFourController: _triviaGameAnswerFourController,
validatorAnswerThree: _triviaGameAnswerThreeValidator,
validatorAnswerFour: _triviaGameAnswerFourValidator,
),
isActive: currentStep >= 2,
title: const Text(StepperString.options),
),
];
String? _triviaGameQuestionValidator(value) {
if (value == null || value.isEmpty) {
return ValidatorString.triviaGameQuestionRequired;
}
return null;
}
void _clearTriviaGameQuestionTextField() {
setState(() {
_triviaGameQuestionController.clear();
});
}
@override
void dispose() {
_triviaGameQuestionController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return AdaptiveLayoutScaffold(
appBar: const AddTriviaGamePageAppBar(),
body: _isLoading
? const AdaptiveCircularProgressCenter()
: Stepper(
controlsBuilder:
(BuildContext context, ControlsDetails controls) {
final isLastStep =
currentStep == getTriviaGameSteps().length - 1;
return AddTriviaGamePageButtons(
children: [
if (currentStep != 0)
StepperCancelButton(
onPressed: controls.onStepCancel,
),
const SizedBox(
width: 12.0,
),
StepperNextAndConfirmButton(
isLastStep: isLastStep,
onPressed: controls.onStepContinue,
),
],
);
},
currentStep: currentStep,
onStepCancel: () {
currentStep == 0 ? null : setState(() => currentStep -= 1);
},
onStepContinue: () {
final isLastStep =
currentStep == getTriviaGameSteps().length - 1;
if (isLastStep) {
uploadQuestionDataToFirestore();
} else {
setState(() => currentStep += 1);
}
},
onStepTapped: (step) => setState(() => currentStep = step),
steps: getTriviaGameSteps(),
type: StepperType.horizontal,
),
);
}
}
Solution 1:[1]
The issue is that you need to initialize your text editing controller in initState and then dispose of it in dispose. You are initializing it just once and then disposing of it perhaps multiple times in dispose().
Here is an example:
class MyWidget extends StatefulWidget {
const MyWidget({Key? key}) : super(key: key);
@override
_MyWidgetState createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
late final TextEditingController _controller;
@override
void initState() {
super.initState();
_controller = TextEditingController();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Container();
}
}
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | Vandad Nahavandipoor |
