I've been working on refactoring my Flutter code to make it cleaner and more organized. I had a stateful widget(screen) with text form fields and a button. When the button is pressed, the data from the text form fields is converted into an object and added to a list. After that, I want to clear the form fields to receive new data.
Here's what I've done so far:
I converted my screen to a stateless widget. I moved the form fields to a separate stateful widget in another file. I kept the button in the original screen widget.
I've also created a controller for the form fields(it has globalkeys and text-controllers). **However, there's one issue that's bothering me: ** When I press the button and the data becomes an object added to the list, I want the form fields to clear (essentially reset) so that they are empty for new data entry. I've tried a few methods, but nothing seems to clear the form fields. Additionally, if it does clear them, it triggers the validators (I used clear on the text controllers, so it's as if the user deleted the text, which activates the validators[onUserInterction]).
Is there a way to clear the form fields without triggering the validators, or perhaps a better approach to achieve this functionality? Any help would be appreciated!
Here is my code so far:
This is the controlle, essantialy you should focus on the emptyAll():
import 'package:flutter/cupertino.dart';
class FieldsController
{
final exerciseName = TextEditingController();
final numberOfSets = TextEditingController();
final numberOfReps = TextEditingController();
final exerciseRPE = TextEditingController();
final exerciseNotes = TextEditingController();
final GlobalKey<FormState> nameKey = GlobalKey<FormState>();
final GlobalKey<FormState> repsKey = GlobalKey<FormState>();
final GlobalKey<FormState> setsKey = GlobalKey<FormState>();
final GlobalKey<FormState> rpeKey = GlobalKey<FormState>();
void emptyAll() {
nameKey.currentState?.reset();
repsKey.currentState?.reset();
setsKey.currentState?.reset();
rpeKey.currentState?.reset();}
///when checking if all the fields are good to go over all of them
bool validatorAllTheFormKeys(){
bool setsValid = setsKey.currentState?.validate() ?? false ;
bool nameValid = nameKey.currentState?.validate() ?? false;
bool rpeValid = rpeKey.currentState?.validate() ?? false;
bool repsValid = repsKey.currentState?.validate() ?? false;
if (!setsValid || !nameValid || !rpeValid || !repsValid) {
return false;
} else {
return true;
}
}
FieldsController();
}
The statless screen(I have edited some things out for clarity):
// ignore_for_file: prefer_const_constructors, use_build_context_synchronously, non_constant_identifier_names, avoid_print, curly_braces_in_flow_control_structures
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:provider/provider.dart';
import 'package:titan_coach_app/exercise.dart';
import 'package:titan_coach_app/providers/workout_list_provider.dart';
import 'package:titan_coach_app/widgets/build_exercise_forms.dart';
import 'package:titan_coach_app/widgets/sign_out_firebase_bt.dart';
List<Exercise> trainingListCreate = <Exercise>[];
late WorkoutProvider workoutListAddPr;
BuildExercisesForms fieldForms = BuildExercisesForms();
class TrainingCreateScreen extends StatelessWidget {
late BuildContext context;
TrainingCreateScreen({super.key});
@override
Widget build(BuildContext context) {
this.context = context;
workoutListAddPr = Provider.of<WorkoutProvider>(context);
return Scaffold(
appBar: AppBar(
title: Text(user?.email ?? 'No email connected'),
backgroundColor: Colors.amber,
actions: [
buildSeeingAllTheExercises()
],
),
body: AnnotatedRegion<SystemUiOverlayStyle>(
value: SystemUiOverlayStyle.dark,
child: Stack(
children: [
Container(
height: double.infinity,
width: double.infinity,
decoration: BoxDecoration(
color: Color(0xff333333),
),
child: SingleChildScrollView(
padding: EdgeInsets.symmetric(vertical: 50),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
fieldForms,
buildExerciseBt(),
],
),
),
),
],
),
),
);
}
Widget buildExerciseBt() {
return FloatingActionButton(
onPressed: () {
Exercise? exerciseAdd = fieldForms.createAnExercise();
print(exerciseAdd);
if(indexOfEditExercise == null){
if(fieldForms.fieldsController.validatorAllTheFormKeys())
{
workoutListAddPr.addExercise(exerciseAdd!);
fieldForms.fieldsController.emptyAll();
}
}
print(workoutListAddPr.toString());
},
backgroundColor: Colors.amber,
child: Icon(Icons.add),
);
}
}
Here is the formfields Widget:
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:titan_coach_app/exercise.dart';
import 'package:titan_coach_app/functions/validators_form.dart';
import '../controllers/field_form_exercise.dart';
class BuildExercisesForms extends StatefulWidget{
/// used when cleaning the forms to disable the checking
FieldsController fieldsController = FieldsController();
BuildExercisesForms({super.key});
Exercise createAnExercise() {
if (fieldsController.validatorAllTheFormKeys()) {
return Exercise(
fieldsController.exerciseName.text,
int.tryParse(fieldsController.numberOfSets.text) ?? 0,
int.tryParse(fieldsController.numberOfReps.text) ?? 0,
int.tryParse(fieldsController.exerciseRPE.text) ?? 0,
fieldsController.exerciseNotes.text.isEmpty ? '' : fieldsController.exerciseNotes.text);
}
return Exercise(
"null",
int.tryParse(fieldsController.numberOfSets.text) ?? 0,
int.tryParse(fieldsController.numberOfReps.text) ?? 0,
int.tryParse(fieldsController.exerciseRPE.text) ?? 0,
fieldsController.exerciseNotes.text.isEmpty ? '' : fieldsController.exerciseNotes.text);;
}
@override
State<StatefulWidget> createState() => _BuildExercisesForms();
}
class _BuildExercisesForms extends State<BuildExercisesForms>{
var formValidatorDisabler = AutovalidateMode.onUserInteraction;
@override
Widget build(BuildContext context) {
return Container(
margin: const EdgeInsets.symmetric(horizontal: 40, vertical: 10),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Form(
autovalidateMode: formValidatorDisabler,
key: widget.fieldsController.nameKey,
child: TextFormField(
controller: widget.fieldsController.exerciseName,
keyboardType: TextInputType.text,
validator: (val) {
if (val!.isEmpty) {
return 'Required*';
} else {
return null;
}
},
decoration: const InputDecoration(
filled: true,
fillColor: Colors.white,
labelText: 'Exercise',
alignLabelWithHint: true,
labelStyle: TextStyle(
color: Colors.amber,
fontSize: 23,
fontWeight: FontWeight.w700,
letterSpacing: 1.5,
),
border: OutlineInputBorder(borderSide: BorderSide.none),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(color: Colors.amber, width: 3),
),
hintStyle: TextStyle(
color: Colors.white,
),
),
),
),
const SizedBox(height: 15),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Expanded(
child: Form(
autovalidateMode: formValidatorDisabler,
key: widget.fieldsController.setsKey,
child: TextFormField(
controller: widget.fieldsController.numberOfSets,
keyboardType: TextInputType.number,
validator: (val) => ValidatorForms().numberValidator(val!),
decoration: const InputDecoration(
filled: true,
fillColor: Colors.white,
labelText: 'Sets',
alignLabelWithHint: true,
labelStyle: TextStyle(
color: Colors.amber,
fontSize: 18,
fontWeight: FontWeight.w500,
),
border: OutlineInputBorder(borderSide: BorderSide.none),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(color: Colors.amber, width: 3),
),
hintStyle: TextStyle(
color: Colors.white,
),
),
),
),
),
///rest of the form fields....
),
),
),
],
),
);;
}
}
I tried to reset and clear the form fields with the globalkeys and it didn't clear them. And if it did it activated the validator(onUserInteraction), I need some how to disable the validator with set state from the outside, or resting the forms with the globalkeys.