Reputation: 113
i have used 3 textfields in the code below, i have validated each message and it displays the error message when i click on the raised button but after that when i enter the values it will be still displaying the same error message on the screen can someone please help me.
Here's my code:
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
import 'package:toast/toast.dart';
class AddPromo extends StatefulWidget {
@override
_AddPromoState createState() => _AddPromoState();
}
class _AddPromoState extends State<AddPromo> {
final databaseReference = Firestore.instance;
var priceReductionController = TextEditingController();
var perReductionController = TextEditingController();
var promocodeController = TextEditingController();
bool _validate = false;
bool _validatePrice = false;
bool _validatePerReduction = false;
bool _validatePromoCode = false;
@override
void dispose() {
priceReductionController.dispose();
perReductionController.dispose();
promocodeController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Add promo"),backgroundColor: Colors.indigo,),
body: Column(
children : <Widget>[
Padding(
padding: const EdgeInsets.all(8.0),
child: TextField(
keyboardType: TextInputType.number,
controller: priceReductionController,
decoration: InputDecoration(labelText: "Price Reduction",
errorText: _validatePrice ? 'value cant be empty' : null,
border: new OutlineInputBorder(
borderRadius: const BorderRadius.all(
const Radius.circular(0.0),
),
borderSide: new BorderSide(
color: Colors.indigo,
width: 1.0,
),
)),
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: TextField(
keyboardType: TextInputType.number,
controller: perReductionController,
decoration: new InputDecoration(labelText: "Percentage Reduction",
errorText: _validatePerReduction ? 'value cant be empty' : null,
border: new OutlineInputBorder(
borderSide: new BorderSide(color: Colors.indigo)
)),
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: TextField(
controller: promocodeController,
decoration: InputDecoration(labelText: "Promo code",
errorText: _validatePromoCode ? 'value cant be empty' : null,
border: OutlineInputBorder(
)),
),
),
Spacer(flex:2),
Container(
width: 350,
child: RaisedButton(
color: Colors.indigo,
onPressed: () async {
//perform the validation first
setState(() {
priceReductionController.text.isEmpty
? _validatePrice = true
: _validatePrice = false;
perReductionController.text.isEmpty
? _validatePerReduction = true
: _validatePerReduction = false;
promocodeController.text.isEmpty
? _validatePromoCode = true
: _validatePromoCode = false;
});
//verify that all are fine
if (_validatePrice || _validatePromoCode || _validatePerReduction) {
return;
}
//store the values into the database
DocumentReference ref = await databaseReference.collection("promo_codes")
.add({
'percentage_reduction': perReductionController.text,
'price_reduction': priceReductionController.text,
'promo_code': promocodeController.text,
});
Scaffold.of(context)
.showSnackBar(SnackBar(content: Text("Promo saved.")))
.closed
.then((reason) {
// snackbar is now closed
});
I/OpenGLRenderer( 6519): Initialized EGL, version 1.4
D/OpenGLRenderer( 6519): Swap behavior 2
W/IInputConnectionWrapper( 6519): getTextBeforeCursor on inactive InputConnection
W/IInputConnectionWrapper( 6519): getSelectedText on inactive InputConnection
W/IInputConnectionWrapper( 6519): getTextAfterCursor on inactive InputConnection
W/IInputConnectionWrapper( 6519): beginBatchEdit on inactive InputConnection
W/IInputConnectionWrapper( 6519): endBatchEdit on inactive InputConnection
W/BiChannelGoogleApi( 6519): [FirebaseAuth: ] getGoogleApiForMethod() returned Gms: com.google.firebase.auth.api.internal.zzak@172de21
W/DynamiteModule( 6519): Local module descriptor class for com.google.firebase.auth not found.
I/FirebaseAuth( 6519): [FirebaseAuth:] Loading module via FirebaseOptions.
I/FirebaseAuth( 6519): [FirebaseAuth:] Preparing to create service connection to gms implementation
D/FirebaseAuth( 6519): Notifying id token listeners about user ( 7pkcfF4j1yVLCnMUn9BE0uvjL2s1 ).
I/flutter ( 6519): AsyncSnapshot<String>(ConnectionState.waiting, null, null)
W/DynamiteModule( 6519): Local module descriptor class for providerinstaller not found.
I/DynamiteModule( 6519): Considering local module providerinstaller:0 and remote module providerinstaller:0
W/ProviderInstaller( 6519): Failed to load providerinstaller module: No acceptable module found. Local version is 0 and remote version is 0.
I/dminbookingpag( 6519): The ClassLoaderContext is a special shared library.
I/chatty ( 6519): uid=10296(m20zero.adminbookingpage) AsyncTask #2 identical 1 line
I/dminbookingpag( 6519): The ClassLoaderContext is a special shared library.
V/NativeCrypto( 6519): Registering com/google/android/gms/org/conscrypt/NativeCrypto's 286 native methods...
W/dminbookingpag( 6519): Accessing hidden field Ljava/nio/Buffer;->address:J (light greylist, reflection)
W/dminbookingpag( 6519): Accessing hidden method Ljava/security/spec/ECParameterSpec;->getCurveName()Ljava/lang/String; (light greylist, reflection)
D/NetworkSecurityConfig( 6519): No Network Security Config specified, using platform default
I/ProviderInstaller( 6519): Installed default security provider GmsCore_OpenSSL
W/dminbookingpag( 6519): Accessing hidden field Ljava/net/Socket;->impl:Ljava/net/SocketImpl; (light greylist, reflection)
W/dminbookingpag( 6519): Accessing hidden method Ldalvik/system/CloseGuard;->get()Ldalvik/system/CloseGuard; (light greylist, linking)
W/dminbookingpag( 6519): Accessing hidden method Ldalvik/system/CloseGuard;->open(Ljava/lang/String;)V (light greylist, linking)
W/dminbookingpag( 6519): Accessing hidden field Ljava/io/FileDescriptor;->descriptor:I (light greylist, JNI)
W/dminbookingpag( 6519): Accessing hidden method Ljava/security/spec/ECParameterSpec;->setCurveName(Ljava/lang/String;)V (light greylist, reflection)
W/dminbookingpag( 6519): Accessing hidden method Ldalvik/system/BlockGuard;->getThreadPolicy()Ldalvik/system/BlockGuard$Policy; (light greylist, linking)
W/dminbookingpag( 6519): Accessing hidden method Ldalvik/system/BlockGuard$Policy;->onNetwork()V (light greylist, linking)
I/flutter ( 6519): 21
I/flutter ( 6519): AsyncSnapshot<String>(ConnectionState.done, 21, null)
W/IInputConnectionWrapper( 6519): getTextBeforeCursor on inactive InputConnection
W/IInputConnectionWrapper( 6519): getSelectedText on inactive InputConnection
W/IInputConnectionWrapper( 6519): getTextAfterCursor on inactive InputConnection
W/IInputConnectionWrapper( 6519): requestCursorAnchorInfo on inactive InputConnection
E/flutter ( 6519): [ERROR:flutter/lib/ui/ui_dart_state.cc(157)] Unhandled Exception: Scaffold.of() called with a context that does not contain a Scaffold.
E/flutter ( 6519): No Scaffold ancestor could be found starting from the context that was passed to Scaffold.of(). This usually happens when the context provided is from the same StatefulWidget as that whose build function actually creates the Scaffold widget being sought.
E/flutter ( 6519): There are several ways to avoid this problem. The simplest is to use a Builder to get a context that is "under" the Scaffold. For an example of this, please see the documentation for Scaffold.of():
E/flutter ( 6519): https://api.flutter.dev/flutter/material/Scaffold/of.html
E/flutter ( 6519): A more efficient solution is to split your build function into several widgets. This introduces a new context from which you can obtain the Scaffold. In this solution, you would have an outer widget that creates the Scaffold populated by instances of your new inner widgets, and then in these inner widgets you would use Scaffold.of().
E/flutter ( 6519): A less elegant but more expedient solution is assign a GlobalKey to the Scaffold, then use the key.currentState property to obtain the ScaffoldState rather than using the Scaffold.of() function.
E/flutter ( 6519): The context used was:
E/flutter ( 6519): AddPromo
E/flutter ( 6519): #0 Scaffold.of (package:flutter/src/material/scaffold.dart:1316:5)
E/flutter ( 6519): #1 _AddPromoState.build.<anonymous closure> (package:adminbookingpage/screens/addPromo.dart:119:24)
E/flutter ( 6519): <asynchronous suspension>
E/flutter ( 6519): #2 _InkResponseState._handleTap (package:flutter/src/material/ink_well.dart:706:14)
E/flutter ( 6519): #3 _InkResponseState.build.<anonymous closure> (package:flutter/src/material/ink_well.dart:789:36)
E/flutter ( 6519): #4 GestureRecognizer.invokeCallback (package:flutter/src/gestures/recognizer.dart:182:24)
E/flutter ( 6519): #5 TapGestureRecognizer.handleTapUp (package:flutter/src/gestures/tap.dart:486:11)
E/flutter ( 6519): #6 BaseTapGestureRecognizer._checkUp (package:flutter/src/gestures/tap.dart:264:5)
E/flutter ( 6519): #7 BaseTapGestureRecognizer.handlePrimaryPointer (package:flutter/src/gestures/tap.dart:199:7)
E/flutter ( 6519): #8 PrimaryPointerGestureRecognizer.handleEvent (package:flutter/src/gestures/recognizer.dart:467:9)
E/flutter ( 6519): #9 PointerRouter._dispatch (package:flutter/src/gestures/pointer_router.dart:76:12)
E/flutter ( 6519): #10 PointerRouter._dispatchEventToRoutes.<anonymous closure> (package:flutter/src/gestures/pointer_router.dart:117:9)
E/flutter ( 6519): #11 _LinkedHashMapMixin.forEach (dart:collection-patch/compact_hash.dart:379:8)
E/flutter ( 6519): #12 PointerRouter._dispatchEventToRoutes (package:flutter/src/gestures/pointer_router.dart:115:18)
E/flutter ( 6519): #13 PointerRouter.route (package:flutter/src/gestures/pointer_router.dart:101:7)
E/flutter ( 6519): #14 GestureBinding.handleEvent (package:flutter/src/gestures/binding.dart:218:19)
E/flutter ( 6519): #15 GestureBinding.dispatchEvent (package:flutter/src/gestures/binding.dart:198:22)
E/flutter ( 6519): #16 GestureBinding._handlePointerEvent (package:flutter/src/gestures/binding.dart:156:7)
E/flutter ( 6519): #17 GestureBinding._flushPointerEventQueue (package:flutter/src/gestures/binding.dart:102:7)
E/flutter ( 6519): #18 GestureBinding._handlePointerDataPacket (package:flutter/src/gestures/binding.dart:86:7)
E/flutter ( 6519): #19 _rootRunUnary (dart:async/zone.dart:1138:13)
E/flutter ( 6519): #20 _CustomZone.runUnary (dart:async/zone.dart:1031:19)
E/flutter ( 6519): #21 _CustomZone.runUnaryGuarded (dart:async/zone.dart:933:7)
E/flutter ( 6519): #22 _invoke1 (dart:ui/hooks.dart:273:10)
E/flutter ( 6519): #23 _dispatchPointerDataPacket (dart:ui/hooks.dart:182:5)
Upvotes: 3
Views: 16011
Reputation: 21
Blockquote Form(key: _formKey,autovalidateMode: AutovalidateMode.onUserInteraction)
You should add Autovalidate Mode.onUserInteraction
property into form widget, that allows you to validate the form every time the user changes some values.
Upvotes: 2
Reputation: 11481
Three mistakes are there in the code.
- You cleared the controller object on button click
Don't clear the controller object. We need to keep the controller object value to maintain the text field state on widget rebuild. So, to resolve this, remove the following code snippet from your code
perReductionController.clear();
priceReductionController.clear();
promocodeController.clear();
- You are keeping only one boolean variable to store validated state of all text fields
That means, even after you fill up the first text field, on button click, the first text field will continue showing the error message just because your second text field is still empty. To solve this, you must keep a boolean variable dedicated to each text field.
The steps are explained below,
Add three variables
bool _validatePrice = false;
bool _validatePerReduction = false;
bool _validatePromoCode = false;
replace the validations in each text field with appropriate boolean checks.
For example, for percentage reduction text field, use the following,
errorText: _validatePerReduction ? 'value cant be empty' : null,
replace the following snippet in the button click event function
priceReductionController.text.isEmpty ? _validate = true :_validate = false;
perReductionController.text.isEmpty ? _validate = true :_validate = false;
promocodeController.text.isEmpty ? _validate = true :_validate = false;
with
priceReductionController.text.isEmpty
? _validatePrice = true
: _validatePrice = false;
perReductionController.text.isEmpty
? _validatePerReduction = true
: _validatePerReduction = false;
promocodeController.text.isEmpty
? _validatePromoCode = true
: _validatePromoCode = false;
- You are storing the values into firestore before validating the form.
You should validate the form first and must ensure that everything is fine, then only you may continue to the next step, that is storing it into the database.
So your onPressed function should look like
onPressed: () async {
//perform the validation first
setState(() {
priceReductionController.text.isEmpty
? _validatePrice = true
: _validatePrice = false;
perReductionController.text.isEmpty
? _validatePerReduction = true
: _validatePerReduction = false;
promocodeController.text.isEmpty
? _validatePromoCode = true
: _validatePromoCode = false;
});
//verify that all are fine
if (_validatePrice || _validatePromoCode || _validatePerReduction) {
return;
}
//store the values into the database
DocumentReference ref = await databaseReference.collection("promo_codes")
.add({
'percentage_reduction': perReductionController.text,
'price_reduction': priceReductionController.text,
'promo_code': promocodeController.text,
});
Scaffold.of(context)
.showSnackBar(SnackBar(content: Text("Promo saved.")))
.closed
.then((reason) {
// snackbar is now closed
});
//print(ref.documentID);
},
So, in order to use the snackbar, you must move your scaffold widget into a parent widget as shown below.
return Scaffold(
appBar: AppBar(
title: Text("Add promo"),
backgroundColor: Colors.indigo,
),
body: AddPromo(),
);
Then to show the snack bar, replace your Toast snippet with the following
Scaffold.of(context)
.showSnackBar(SnackBar(content: Text("Promo saved.")))
.closed
.then((reason) {
// snackbar is now closed
});
Upvotes: 1
Reputation: 707
Try with this:
// Replace TextField() -> TextFormField()
// autovalidate: true;
// validate : (String value) {
// // validation rules
// }
Updated code:
class AddPromo extends StatefulWidget {
@override
_AddPromoState createState() => _AddPromoState();
}
class _AddPromoState extends State<AddPromo> {
var priceReductionController = TextEditingController();
var perReductionController = TextEditingController();
var promocodeController = TextEditingController();
bool _validate = false;
bool priceReductionControllerSubmited = false;
bool perReductionControllerSubmited = false;
bool promocodeControllerSubmired = false;
@override
void dispose() {
priceReductionController.dispose();
perReductionController.dispose();
promocodeController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Add promo"),backgroundColor: Colors.indigo,),
body: Column(
children : <Widget>[
Padding(
padding: const EdgeInsets.all(8.0),
child: TextFormField(
keyboardType: TextInputType.number,
controller: priceReductionController,
autovalidate: true,
validator: (String value) {
if(value.isEmpty && !priceReductionControllerSubmited) {
return 'value can\'t be empty';
}
else {
return null;
}
},
decoration: InputDecoration(labelText: "Price Reduction",
errorText: _validate ? 'value cant be empty' : null,
border: new OutlineInputBorder(
borderRadius: const BorderRadius.all(
const Radius.circular(0.0),
),
borderSide: new BorderSide(
color: Colors.indigo,
width: 1.0,
),
)),
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: TextFormField(
keyboardType: TextInputType.number,
autovalidate: true,
validator: (String value) {
if(value.isEmpty && !perReductionControllerSubmited) {
return 'value can\'t be empty';
}
else {
return null;
}
},
controller: perReductionController,
decoration: new InputDecoration(labelText: "Percentage Reduction",
border: new OutlineInputBorder(
borderSide: new BorderSide(color: Colors.indigo)
)),
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: TextFormField(
controller: promocodeController,
autovalidate: true,
validator: (String value) {
if(value.isEmpty && !promocodeControllerSubmired) {
return 'value can\'t be empty';
}
else {
return null;
}
},
decoration: InputDecoration(labelText: "Promo code",
errorText: _validate ? 'value cant be empty' : null,
border: OutlineInputBorder(
)),
),
),
Spacer(flex:2),
Container(
width: 350,
child: RaisedButton(
color: Colors.indigo,
onPressed: () async {
DocumentReference ref = await databaseReference.collection("promo_codes").add({
'percentage_reduction': perReductionController.text,
'price_reduction': priceReductionController.text,
'promo_code': promocodeController.text,
});
// Toast.show("Promo saved", context);
print(ref.documentID);
promocodeController.clear();
perReductionController.clear();
priceReductionController.clear();
setState(() {
priceReductionControllerSubmited = true;
promocodeControllerSubmired =true;
perReductionControllerSubmited =true;
});
}, child: Text("Add Promo", style: TextStyle(color:Colors.white),),),
)
]
),
);
}
}
Upvotes: 0
Reputation: 6776
you can use onFieldSubmitted
of TextFormField
onFieldSubmitted: (value){
_setState(() {
_validate = !priceReductionController.text.isEmpty &&
!perReductionController.text.isEmpty &&
!promocodeController.text.isEmpty;
});
},
But instead of using a single _validate it might be good to use three different boolean states or altogether use Form
https://flutter.dev/docs/cookbook/forms/validation widget and use its validate method. instead of handling by yourself
also dont clear controller
perReductionController.clear();
priceReductionController.clear();
promocodeController.clear();
Upvotes: 1
Reputation: 379
your _validate is always false. kindly check set state method and printing the _validate.
Upvotes: 0