I'm trying to display an AlertDialog with a ListView of CheckBoxes with the code below:
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text(' ', textAlign: TextAlign.right,),
content: Directionality(
textDirection: TextDirection.rtl,
child: Container(
height: 300.0,
width: 300.0,
child: new ListView.builder(
shrinkWrap: true,
itemCount: dropEntitiesList.length,
itemBuilder: (BuildContext context, int index) {
return new Row(
children: [
new Checkbox(
value: globals.entitiesFilter.contains(dropEntitiesList[index]),
onChanged: (bool newValue) {
setState(() {
dropEntitiesList[index].isClicked = !dropEntitiesList[index].isClicked;
if (dropEntitiesList[index].isClicked){
globals.entitiesFilter.add(dropEntitiesList[index].name);
}else{
globals.entitiesFilter.remove(dropEntitiesList[index].name);
}
});
print(globals.entitiesFilter);
}),
new Text(
dropEntitiesList[index].name,
style: TextStyle(fontSize: 16.0),
),
],
);
}),
),
),
actions: <Widget>[
new FlatButton(
child: new Text('انتهيت'),
onPressed: () {
Navigator.of(context).pop(true);
}),
new FlatButton(
child: new Text('إلغاء'),
onPressed: () {
Navigator.of(context).pop(false);
},
)
],
);
The onChanged's newValue parameter is always true. To see the checked CheckBoxes I need to close the dialog and then open it again, it will not change immediately when clicked .
How can I solve this?
Edit:
As the documentation for showDialog() states, you need to use a StatefulBuilder if the dialog needs to update. So wrap your Directionality widget in a StatefulBuilder:
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text(' ', textAlign: TextAlign.right,),
content: StatefulBuilder(builder: (BuildContext context, StateSetter stateUpdater) {
return Directionality(..rest of code
}
Inside of the onChange() callback then use the StateSetter parameter to update the state:
stateUpdater(() {
dropEntitiesList[index].isClicked = !dropEntitiesList[index].isClicked;
// rest of the code
});
You are mixing some of the lists you use for state. For example, you set the value of the CheckBox based on the row's entity being present in the entitiesFilter list, but this will always be false because in the onChanged() method you update the entitiesFilter list only with the name from dropEntitiesList[index] not the entity itself. You can solve this by using:
value: globals.entitiesFilter.contains(dropEntitiesList[index].name),
(as I said you save only the name in the onChanged() method).
Related
I am implementing a listview with a checkbox. IF the user tap on it, it should change the status from checked to unchecked.
When this happen, I must update the data in Firestore.
What I have implemented does not work as I would like. I have used update and set, but I am getting the same result. You will see that on the pictures bellow. The first picture is the result I want. The second picture is what I am getting. Many thanks for your help.
Widget MyNewBody (BuildContext context,var docID){
return Column(
children: [
Container(
height: MediaQuery.of(context).size.height /1.4,
width: MediaQuery.of(context).size.width,
child:StreamBuilder<DocumentSnapshot>(
stream: FirebaseFirestore.instance
.collection('Users')
.doc(FirebaseAuth.instance.currentUser.uid)
.collection('lists')
.doc(docID)
.snapshots(),
builder: (BuildContext context, AsyncSnapshot<DocumentSnapshot> snapshot) {
if (!snapshot.hasData) {
return Center(
child: CircularProgressIndicator(),
);
}else {
DocumentSnapshot data = snapshot.requireData;
return ListView.builder(
itemCount: data['allItems'].length,
itemBuilder: (context, index) {
return Card(
child:
ListTile(
leading: data['allItems'][index]['itemChecked'] == 'Yes' ? Icon(
Icons.check_box,
color: Colors.blue,) : Icon(
Icons.check_box_outline_blank),
title:
Text(
(data['allItems'][index]['itemName'])),
onTap: () {
String myItemName = data['allItems'][index]['itemName'];
String checked = data['allItems'][index]['itemChecked'];
setState(() {
if (checked == 'Yes') {
checked = 'No';
FirebaseFirestore.instance
.collection('Users')
.doc(FirebaseAuth.instance.currentUser.uid)
.collection('lists')
.doc(docID)
.update(
{
'allItems': [
{'itemChecked': checked},
{'itemName': myItemName},
],
},
);
}
else {
checked = 'Yes';
FirebaseFirestore.instance
.collection('Users')
.doc(FirebaseAuth.instance.currentUser.uid)
.collection('lists')
.doc(docID)
.set({
'allItems': [
{'itemChecked': checked},
{'itemName': myItemName},
]
},
);
}
});
},
));
});
}}))]);
}
I've got a PageViewBuilder which uses a Map<String> of json data containing a String called name and another called tagname. The problem is that the tagname string is actually a comma-separated list of three tags which print out as "boat,grass,toads"; But I'd like to separate this String so that I can make each item a clickable button. IE:
FlatButton( onPressed: () => boat(),
text: "boat",
);
FlatButton( onPressed: () => grass(),
text: "grass",
);
FlatButton( onPressed: () => toads(),
text: "toads",
);
I tried to using Dart's split function like this;
var splitag = tagname.split(",");
var splitag1 = splitag[0];
var splitag2 = splitag[1];
var splitag3 = splitag[2];
But this gives an error if any of the three tag names are void.
So I tried this;
String splitagone = splitag1 ?? "";
String splitagtwo = splitag2 ?? "";
String splitagthree = splitag3 ?? "";
But this still gave me an error. So can anyone suggest a different way to accomplish what I need? Here's the full code;
class SpeakContent {
final String name, tagname;
SpeakContent({
this.name,
this.tagname
});
factory SpeakContent.fromJson(Map<String, dynamic> jsonData) {
return SpeakContent(
name: jsonData['name'],
tagname: jsonData['tagname'],
);
}
}
class StageBuilder extends StatelessWidget {
final List<SpeakContent> speakcrafts;
StageBuilder(this.speakcrafts);
#override
Widget build(context) {
return PageView.builder(
itemCount: speakcrafts.length,
itemBuilder: (context, int currentIndex) {
return createViewItem(speakcrafts[currentIndex], context);
},
);
}
Widget createViewItem(SpeakContent speakcraft, BuildContext context) {
return Column(
children: <Widget>[
Container(
child: Text(
speakcraft.name,
),
),
Container(child:
FlatButton( onPressed: () => boat(),
child: Text('boat'),
),
),
Container(child:
FlatButton( onPressed: () => grass(),
child: Text('grass'),
),
),
Container(child:
FlatButton( onPressed: () => toads(),
child: Text('toads'),
)
),
],
);
}
}
You can do something like this..
Widget createViewItem(SpeakContent speakcraft, BuildContext context) {
List<Widget> columnChildren = [];
Text name = Text(speakcraft.name);
columnChildren.add(name);
speakcraft.tagname.split(',').forEach((String tag) {
FlatButton tagButton = FlatButton(
child: Text(tag),
onPressed: (){},
);
columnChildren.add(tagButton);
});
return Column(
children: columnChildren,
);
}
You can try:
var response = await http.get(
Uri.encodeFull("...."),
headers: {
...
}
);
this.setState(() {
testList = json.decode (response.body) as List;
**testList[0];
testList[1];**
I want to use json array on my flutter project, but it's always an error like this
_TypeError (type 'List<dynamic>' is not a subtype of type 'FutureOr<ProvinsiModel>')
what I expect from this project is that when I click on a widget I mean below it will open a new page on flutter and display a list taken from the json array.
in my code there is no error but just when I click and switch to another page, debugging mode in VSCode suddenly stops and displays an error like the above in a PopUp.
and this is the code that I have
provider
class IndonesianProvinsiProvider with ChangeNotifier {
var api = ApiServices();
ProvinsiModel indonesia;
Future<ProvinsiModel> getIndonesianProvinsiProvider() async {
final response = await api.client.get("${api.baseUrl}/indonesia/");
if (response.statusCode == 200) {
notifyListeners();
final data = jsonDecode(response.body);
return data;
} else {
return null;
}
}
}
page for data that i will use
class ProvinsiPage extends StatelessWidget {
final List<ProvinsiModel> data;
const ProvinsiPage({Key key, this.data}) : super(key: key);
#override
Widget build(BuildContext context) {
return Container(
child: ListView.builder(
itemCount: data.length,
itemBuilder: (context, index) {
return Card(
child: Column(
children: <Widget>[
Card(child: Text(data[index].attributes.provinsi))
],
),
);
},
)
);
}
}
and I call that data with future and future.builder ()
GestureDetector(
onTap: () {
Navigator.push(
context, MaterialPageRoute(
builder: (context) => FutureBuilder (
future: Provider.of<IndonesianProvinsiProvider>(context, listen: false).getIndonesianProvinsiProvider(),
builder: (context, snapshot) {
if (snapshot.data == null) { // Cek jika snapshot tidak menerima data atau null
return Center( // maka
child: CircularProgressIndicator(), // menampilkan loading
);
} else { // maka
return ProvinsiPage( // data dikirim ke class PostinganList
data: snapshot.data,
);
}
}
)
),
);
},
child: IndonesiaStatsWidget (
bgColor: Colors.redAccent,
indo: Provider.of<IndonesianProvider>(context).indonesia,
widgetBgColor: Colors.white,
),
),
for json that I got from the provider requests like this
[
{
"attributes": {
"FID": 11,
"Kode_Provi": 31,
"Provinsi": "DKI Jakarta",
"Kasus_Posi": 6895,
"Kasus_Semb": 1682,
"Kasus_Meni": 509
}
},
{
"attributes": {
"FID": 15,
"Kode_Provi": 35,
"Provinsi": "Jawa Timur",
"Kasus_Posi": 4142,
"Kasus_Semb": 522,
"Kasus_Meni": 320
}
},
....
]
How can I retrieve a data from firebase database with Timer function in flutter app?
P.S.After user request data will be retrieved from firebase database 5 minutes later.
Thank you
So what you are do by calling where('index' isEqualTo: ' 0' is it first finds any documents with a field named index like above then it only returns the ones the has a value this is equal to to the one you provided
My code is just like below but it did not work with your code.#wcyankees424
body: StreamBuilder(
stream: qs.collection('collection').snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
const Text('Loading');
return Center(
child: CircularProgressIndicator(),
);
} else {
return ListView.builder(
itemCount: snapshot.data.documents.length,
itemBuilder: (context, index) {
List<DocumentSnapshot> listedQS = snapshot.data.documents;
var random = new Random();
for (var i = listedQS.length - 1; i > 0; i--) {
var n = random.nextInt(i + 1);
var temp = listedQS[i];
listedQS[i] = listedQS[n];
listedQS[n] = temp;
}
DocumentSnapshot mypost = listedQS[0];
Future.delayed(Duration(minutes: 5), () {
Timer.periodic(Duration(minutes: 5), (Timer t) {
setState(() {});
});
});
okay put this in the stateful widget with you StreamBuilder this worked on my emulator. Let me know if it works for you.
Future<void> addEntry(Brew newBrew) async {
//value you want saved are stored in newBrew and passed in
Map<String, Object> entryData = {
'name': newBrew.name,
'strength': newBrew.strength,
'sugars': newBrew.sugars,
'index': newBrew.index,
};
await Firestore.instance.collection('//collection name').add(entryData);
}
Future<Brew> getEntries(Brew newBrew) async {
QuerySnapshot snapshot = await Firestore.instance
.collection('//Collection name')
.where('index', isGreaterThanOrEqualTo: Random().nextInt('//this number should be higher than the number of documents'))
.orderBy('index')
.limit(1)
.getDocuments();
if (snapshot.documents.isNotEmpty) {
Map<String, dynamic> documentData = snapshot.documents[0].data;
return Brew(
strength: documentData['strngth'],
sugars: documentData['sugars'],
name: documentData['name'],
index: documentData['index'],
);
} else {
snapshot = await Firestore.instance
.collection('//Collection name')
.where('index', isGreaterThanOrEqualTo: 0)
.orderBy('index')
.limit(1)
.getDocuments();
Map<String, dynamic> documentData = snapshot.documents[0].data;
return Brew(
strength: documentData['strngth'],
sugars: documentData['sugars'],
name: documentData['name'],
index: documentData['index'],
);
}
}
class Brew {
final String name;
final String sugars;
final int strength;
final int index;
Brew({
this.name,
this.sugars,
this.strength,
this.index,
});
}
you would create and field for every called index which would start at 0 increment by 1 for every entry in your in your database. This might help you
My code is like below how could I show text and image values with Querysnapshot method ? That you've mentioned with brew code! I cant use your code with below code structure...
Your code: #wcyankees424
snapshot = await Firestore.instance
.collection('//Collection name')
.where('index', isGreaterThanOrEqualTo: 0)
.orderBy('index')
.limit(1)
.getDocuments();
my code:
body: StreamBuilder(
stream: qs.collection('collection').limit(1).snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
const Text('Loading');
} else {
return ListView.builder(
itemCount: snapshot.data.documents.length,
itemBuilder: (context, index) {
List<DocumentSnapshot> listedQS =
snapshot.data.documents; //listed documents
var random = new Random(); //dart math
for (var i = listedQS.length - 1; i > 0; i--) {
var n = random.nextInt(i + 1);
var temp = listedQS[i];
listedQS[i] = listedQS[n];
listedQS[n] = temp;
}
DocumentSnapshot mypost = listedQS[0];
return Stack(
children: <Widget>[
Container(
width: MediaQuery.of(context).size.width,
height: 350,
child: Padding(
padding: EdgeInsets.only(top: 8.0, bottom: 8.0),
child: Material(
color: Colors.white,
elevation: 14.0,
shadowColor: Color(0x882196F3),
child: Center(
child: Padding(
padding: EdgeInsets.all(8.0),
child: Column(
children: <Widget>[
Container(
width:
MediaQuery.of(context).size.width,
height: 200,
child: Image.network(
'${mypost['image']}',
fit: BoxFit.fill),
),
SizedBox(height: 10.0),
Text(
'${mypost['title']}',
style: TextStyle(
fontSize: 20.0,
fontWeight: FontWeight.bold),
),
SizedBox(height: 10.0),
Text(
'${mypost['subtitle']}',
style: TextStyle(
fontSize: 16.0,
fontWeight: FontWeight.bold,
color: Colors.blueGrey),
),
],
),
),
),
),
),
),
#override
Widget build(BuildContext context) {
return Scaffold(
body: StreamBuilder(
stream: Firestore.instance.collection('fortunepool').where('index', isGreaterThanOrEqualTo: 0).orderBy('index').limit(1).snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return Container(child: CircularProgressIndicator());
} else {
return ListView.builder(
itemCount: snapshot.data.documents.length,
itemBuilder: (context, index) {
List<DocumentSnapshot> listedQS =
snapshot.data.documents; //listed documents
var random = new Random(); //dart math
for (var i = listedQS.length - 1; i > 0; i--) {
var n = random.nextInt(i + 1);
var temp = listedQS[i];
listedQS[i] = listedQS[n];
listedQS[n] = temp;
}
DocumentSnapshot mypost = listedQS[0];
return Stack(
My code is just like below. I aso updated database as you've indicated above but still white screen
Widget build(BuildContext context) {
return Scaffold(
body: StreamBuilder(
stream: Firestore.instance
.collection('fortunepool')
.where('index', isGreaterThanOrEqualTo: 0)
.orderBy('index')
.limit(1)
.snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return Container(child: CircularProgressIndicator());
} else {
return ListView.builder(
itemCount: snapshot.data.documents.length,
itemBuilder: (context, index) {
List<DocumentSnapshot> listedQS =
snapshot.data.documents; //listed documents
DocumentSnapshot mypost = listedQS[0];
return Stack(
I am building a simple forgot password form for a demo app which consists of one TextFormFields and a FloatingActionButton to submit the data. I have realised that the FloatingActionButton doesn't have disabled boolean state as such, so I wanted to try and replicate it by change the state to _isValid: true/ false depending on the TextFormField validation functions, which I can then put some ternary operators on FloatingActionButton to change the color and the functionality, depending on the state of this widget.
You will be able to see that I have the _autoValidate set to true on mounting of the widget then I try and trigger a UI reload in the _validateForgetEmail function. When I trigger these state changes I get a big UI error saying
══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞═══════════════════════════════════════════════════════════
flutter: The following assertion was thrown building Form-[LabeledGlobalKey<FormState>#0a40e](dirty, state:
flutter: FormState#59216):
flutter: setState() or markNeedsBuild() called during build.
flutter: This ForgotPasswordForm widget cannot be marked as needing to build because the framework is already
flutter: in the process of building widgets. A widget can be marked as needing to be built during the build
flutter: phase only if one of its ancestors is currently building. This exception is allowed because the
flutter: framework builds parent widgets before children, which means a dirty descendant will always be
flutter: built. Otherwise, the framework might not visit this widget during this build phase.
Code is below:
class ForgotPasswordForm extends StatefulWidget {
#override
_ForgotPasswordFormState createState() => _ForgotPasswordFormState();
}
Class _ForgotPasswordFormState extends State<ForgotPasswordForm> {
final _emailController = TextEditingController();
final _formKey = GlobalKey<FormState>();
final bool _autoValidate = true;
bool _isLoading = false;
bool _isValid = false;
String email;
#override
Widget build(BuildContext context) {
// Build a Form widget using the _formKey created above.
return Form(
key: _formKey,
child: _isLoading
? _buildLoadingSpinner(context)
: _buildPasswordForm(context),
autovalidate: _autoValidate,
);
}
Widget _buildLoadingSpinner(BuildContext context) {
return (Center(child: CircularProgressIndicator()));
}
Widget _buildPasswordForm(BuildContext context) {
print('isValid: ' + _isValid.toString());
return Column(
children: <Widget>[
Text(
'Please enter your email address.',
style: TextStyle(fontSize: 14.0),
textAlign: TextAlign.center,
),
Text(
'You will recieve a link to reset your password.',
style: TextStyle(fontSize: 14.0),
textAlign: TextAlign.center,
),
SizedBox(height: 32.0),
TextFormField(
controller: _emailController,
validator: _validateForgetEmail,
keyboardType: TextInputType.emailAddress,
autovalidate: _autoValidate,
style: TextStyle(fontSize: 14.0),
onSaved: (String val) {
email = val;
},
decoration: InputDecoration(
filled: true,
contentPadding: EdgeInsets.symmetric(horizontal: 15, vertical: 8),
labelText: 'Email',
border: InputBorder.none,
labelStyle: TextStyle(fontSize: 14.0, color: Colors.lightBlueAccent),
errorStyle: TextStyle(fontSize: 10.0, height: 0.5),
focusedBorder: UnderlineInputBorder(
borderSide: BorderSide(color: Colors.lightGreenAccent, width: 2.0),
),
),
),
SizedBox(height: 24.0),
FloatingActionButton(
backgroundColor: _isValid ? Colors.lightBlue : Colors.grey,
onPressed: () {
_submitPasswordReset();
},
child: Icon(Icons.arrow_forward_ios, size: 14.0),
)
],
mainAxisAlignment: MainAxisAlignment.center,
);
}
void _submitPasswordReset() async {
if (_formKey.currentState.validate()) {
setState(() {
_isLoading = true;
});
UserPasswordResetRequest newPasswordRequest =
new UserPasswordResetRequest(email: _emailController.text);
http.Response response = await ApiService.queryPost(
'/api/users/password-forgot',
body: newPasswordRequest.toJson());
final int statusCode = response.statusCode;
if (statusCode == 400) {
Scaffold.of(context).showSnackBar(SnackBar(
content: Text('Wrong email or password'),
duration: Duration(seconds: 3),
backgroundColor: Colors.red));
setState(() {
_isLoading = false;
});
}
if (statusCode == 200) {
// setState(() {
// _isLoading = false;
// });
Navigator.push(
context,
MaterialPageRoute(builder: (context) => UserBackToLogin()),
);
}
setState(() {
_isLoading = false;
});
}
}
String _validateForgetEmail(String value) {
String patttern =
r"^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))#((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))$";
RegExp regExp = new RegExp(patttern);
if (value.length == 0) {
return "Email is Required";
} else if (!regExp.hasMatch(value)) {
setState(() {
_isValid = false;
});
return "Must be a valid email address";
}
print('value' + value);
setState(() {
_isValid = true;
});
return null;
}
}
Any insight would be great to see what I am doing wrong - very new to flutter. If you need any more info, then I can provide.
Cheers Sam
You can do it like this:
Split _validateForgetEmail method in two:
String _validateForgetEmail(String value) {
if (value.length == 0) {
return "Email is Required";
} else if (!_isEmailValid(value)) {
return "Must be a valid email address";
}
print('value' + value);
return null;
}
bool _isEmailValid(String value) {
String pattern =
r"^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))#((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))$";
RegExp regExp = new RegExp(pattern);
return regExp.hasMatch(value);
}
Now these methods only validate values without affecting any state.
Listen to _emailController changes
#override
void initState() {
super.initState();
_emailController.addListener(() {
final isEmailValid = _isEmailValid(_emailController.value.text);
if(isEmailValid != _isValid) {
setState(() {
_isValid = isEmailValid;
});
}
});
}
Also don't forget to dispose _emailController
#override
void dispose() {
_emailController.dispose();
super.dispose();
}
Exception explanation:
TextFormField extends FormField class. If autovalidate is turned on, then function passed as validator will be called in FormFieldState.build method to update error text.
So it leads to setState being called from build which is not allowed by framework
A simpler way to achieve this would be to validate in the onChanged callback.
class FormPage extends StatefulWidget {
#override
_FormPageState createState() => _FormPageState();
}
class _FormPageState extends State<FormPage> {
final _formKey = GlobalKey<FormState>();
bool _isValid = false;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Form(
key: _formKey,
onChanged: () {
final isValid = _formKey.currentState.validate();
if (_isValid != isValid) {
setState(() {
_isValid = isValid;
});
}
},
child: Column(
children: <Widget>[
TextFormField(validator: (x) => x.length > 2 ? null : 'Too short'),
],
),
),
floatingActionButton: Opacity(
opacity: _isValid ? 1 : 0.5,
child: FloatingActionButton(
child: Icon(Icons.send),
onPressed: () {},
),
),
);
}
}
Simple use
Form(
key: _key,
autovalidateMode: AutovalidateMode.onUserInteraction,
child: Column(
children: [
//All FormTextFields Here as you went....
],
),
),
Bit late to the party, but you can use setState directly inside the validator by adding a post frame callback in the validator as follows:
validator: (val) {
if (val == '') {
SchedulerBinding.instance.addPostFrameCallback((duration) {
setState(() {
someErrorVariableYouWantToChange = true;
});
});
} else {
SchedulerBinding.instance.addPostFrameCallback((duration) {
setState(() {
someErrorVariableYouWantToChange = false;
});
});
}
return null;
}
This will ensure that setState is called after the build process is complete due to the validator function.