I am looking for an example of how to handle forms and validation in best practice with GetX? Is there any good example of that or can someone show me an example of how we best can do this?
However, experienced Flutter devs do not recommend GetX. Do not use GetX.
GetX is a powerful and lightweight solution provided by Flutter to manage states and their updation. It provides: High-performance state management. Intelligent dependency injection.
Setting up a form to validateStart by creating a new Flutter project in either of VS Code or Android Studio. Replace the Flutter default counter application in main. dart with your own stateful widget. The formKey handles the state of the form, validation, and saving.
Form Validation is an important part of every application. In the flutter application, there are many ways to validate form such as using a TextEditingController. But handling text controller for every Input can be messy in big applications.
First, create a Form . The Form widget acts as a container for grouping and validating multiple form fields. When creating the form, provide a GlobalKey . This uniquely identifies the Form , and allows validation of the form in a later step.
But it's fun to play around with how GetX can be used to perform validation. Two widgets of interest that rebuild based on Observable value changes: onChanged: fx.usernameChanged doesn't cause rebuilds.
In form, the input is validated in your submit function (the function which is called once the user has entered every detail, But the condition is applied in each TextFormField itself with a widget name validator as shown in the below given example. Want a more fast-paced & competitive environment to learn the fundamentals of Android?
Here's an example of how you could use GetX's observables to dynamically update form fields & submit button.
I make no claim that this is a best practice. I'm sure there's better ways of accomplishing the same. But it's fun to play around with how GetX can be used to perform validation.
Two widgets of interest that rebuild based on Observable value changes:
errorText
changes & will rebuild this widgetonChanged: fx.usernameChanged
doesn't cause rebuilds. This calls a function in the controller usernameChanged(String val)
when form field input changes.username
observable with a new value.onChanged: (val) => fx.username.value = val
onPressed
function can change between null
and a functionnull
disables the button (only way to do so in Flutter)class FormObxPage extends StatelessWidget {
const FormObxPage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
FormX fx = Get.put(FormX()); // controller
return Scaffold(
appBar: AppBar(
title: const Text('Form Validation'),
),
body: SafeArea(
child: Container(
alignment: Alignment.center,
margin: const EdgeInsets.symmetric(horizontal: 5),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Obx(
() {
print('rebuild TextFormField ${fx.errorText.value}');
return TextFormField(
onChanged: fx.usernameChanged, // controller func
decoration: InputDecoration(
labelText: 'Username',
errorText: fx.errorText.value // obs
)
);
},
),
Obx(
() => ElevatedButton(
child: const Text('Submit'),
onPressed: fx.submitFunc.value, // obs
),
)
],
),
),
),
);
}
}
Explanation / breakdown below
class FormX extends GetxController {
RxString username = RxString('');
RxnString errorText = RxnString(null);
Rxn<Function()> submitFunc = Rxn<Function()>(null);
@override
void onInit() {
super.onInit();
debounce<String>(username, validations, time: const Duration(milliseconds: 500));
}
void validations(String val) async {
errorText.value = null; // reset validation errors to nothing
submitFunc.value = null; // disable submit while validating
if (val.isNotEmpty) {
if (lengthOK(val) && await available(val)) {
print('All validations passed, enable submit btn...');
submitFunc.value = submitFunction();
errorText.value = null;
}
}
}
bool lengthOK(String val, {int minLen = 5}) {
if (val.length < minLen) {
errorText.value = 'min. 5 chars';
return false;
}
return true;
}
Future<bool> available(String val) async {
print('Query availability of: $val');
await Future.delayed(
const Duration(seconds: 1),
() => print('Available query returned')
);
if (val == "Sylvester") {
errorText.value = 'Name Taken';
return false;
}
return true;
}
void usernameChanged(String val) {
username.value = val;
}
Future<bool> Function() submitFunction() {
return () async {
print('Make database call to create ${username.value} account');
await Future.delayed(const Duration(seconds: 1), () => print('User account created'));
return true;
};
}
}
Starting with the three observables...
RxString username = RxString('');
RxnString errorText = RxnString(null);
Rxn<Function()> submitFunc = Rxn<Function()>(null);
username
will hold whatever was last input into the TextFormField.
errorText
is instantiated with null
initial value so the username field is not "invalid" to begin with. If not null (even empty string), TextFormField will be rendered red to signify invalid input. When a non-valid input is in the field, we'll show an error message. (min. 5 chars
in example:)
submitFunc
is an observable for holding a submit button function or null
, since functions in Dart are actually objects, this is fine. The null
value initial assignment will disable the button.
The debounce
worker calls the validations
function 500ms after changes to the username
observable end.
validations
will receive username.value
as its argument.
More on workers.
Inside validations
function we put any types of validation we want to run: minimum length, bad characters, name already taken, names we personally dislike due to childhood bullies, etc.
For added realism, the available()
function is async
. Commonly this would query a database to check username availability so in this example, there's a fake 1 second delay before returning this validation check.
submitFunction()
returns a function which will replace the null value in submitFunc
observable when we're satisfied the form has valid inputs and we allow the user to proceed.
A little more realistic, we'd prob. expect some return value from the submit button function, so we could have the button function return a future bool:
Future<bool> Function() submitFunction() {
return () async {
print('Make database call to create ${username.value} account');
await Future.delayed(Duration(seconds: 1), () => print('User account created'));
return true;
};
}
GetX is not the solution for everything but it has some few utility methods which can help you achieve what you want. For example you can use a validator
along with SnackBar
for final check. Here is a code snippet that might help you understand the basics.
TextFormField(
controller: emailController,
autovalidateMode: AutovalidateMode.onUserInteraction,
validator: (value) {
if (!GetUtils.isEmail(value))
return "Email is not valid";
else
return null;
},
),
GetUtils
has few handy methods for quick validations and you will have to explore each method to see if it fits your need.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With