How can I add validation to Radio Button in Flutter? I know there's a package called flutter_form_builder but I don't want to use it. Is there any way to add validation to the radio button? I would like it to validate it using formkey and I can't post code because the whole form is dynamic and I don't have permission to post the code online so any help is appreciated. Can I make a custom radio button?
I know it is a bit late. Just use FormBuilder, for example.
or if you use it inside Form(), this is an example:
FormField(
builder: (FormFieldState<bool> state) {
return Padding(
padding: EdgeInsets.all(10),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Where is occurrence happened?'),
state.hasError
? Text(
state.errorText,
style: TextStyle(color: Colors.red),
)
: Container(),
RadioListTile(
...
onChanged: (SomeValueType value) {
...
state.setValue(true);
},
),
RadioListTile(
...
),
],
));
},
validator: (value) {
if (value != true) {
return 'Please choose location';
}
return null;
},
)
in that code, the validator will run when you call FormState.validate(), and then show the ErrorText.
Related
So I'm relatively new to flutter and I've been trying to dynamically add Sections(TextFormFields) that are represented in a form that has Form.Helper as its child and in the process to get the saveAndValidate method to work i had to use a GlobalKey to be able to access the currentState of its so i can validate and save user input and such, but whenever i try add another Section to the screen it display this error massage
════════ Exception caught by widgets library ═══════════════════════════════════
Multiple widgets used the same GlobalKey.
════════════════════════════════════════════════════════════════════════════════
here is the code I wrote and I'd appreciate any help in solving this error please.
#1- the code for the model I used:
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
class AddCourse with ChangeNotifier {
String? sectionName;
List<String>? sections;
List<dynamic>? addVids;
AddCourse({this.sectionName, this.sections, this.addVids});
/*where we save our values later to push them to firbase/database*/
Map<String, dynamic> toJson() {
final Map<String, dynamic> sectionData = <String, dynamic>{};
sectionData['Section #'] =
sections; // where current section number is saved and is stored dynamicly and updates as user adds more or less sections.
sectionData['Section Name'] =
sectionName; // where the input of the textformfield is saved and to be later pushed to the database and also is stored in a list so it can hold multiple section names as such.
return sectionData;
}
/* this is another model data for a functionality thats not implemented yet*/
Map<dynamic, dynamic> toJson2() {
final Map<dynamic, dynamic> vidData = <dynamic, dynamic>{};
vidData['Videos #'] = addVids;
return vidData;
}
}
#2 this the code for the form I created
import 'package:flutter/material.dart';
import 'package:snippet_coder_utils/FormHelper.dart';
import '../provider/course_add_model.dart';
class CourseCardBody extends StatefulWidget {
const CourseCardBody({
Key? key,
}) : super(key: key);
#override
State<CourseCardBody> createState() => _CourseCardBodyState();
}
class _CourseCardBodyState extends State<CourseCardBody> {
/* this is where i set up my global key that has the type of GlobalKey<FormState>*/
/*State associated with a [Form] widget. such as textformfields/forms/textfields..etc// the use of the (FormState) is to be able to Access the Functions "save"/"validate"/"reset" as to use them with forms/textformfields that you want to validate thier input or save it*/
GlobalKey<FormState> globalkey = GlobalKey();
AddCourse coursesModel = AddCourse();
#override
void initState() {
super.initState();
coursesModel.sections = List<String>.empty(growable: true);
coursesModel.sections?.add("");
// adds empty sections to the list of sections when the add button is used
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Add Courses'),
centerTitle: true,
),
body: ListView.separated(
shrinkWrap: true,
physics: const ScrollPhysics(),
itemBuilder: ((context, index) => Column(
children: [
_uiWidget(index),
Center(
// the submit button here needs some work to only be show once but for now sorry for this annoying button.
child: FormHelper.submitButton('Save', () {
if (validateAndSave()) {
print(coursesModel.toJson());
}
}),
),
],
)),
separatorBuilder: ((context, index) => const Divider()),
itemCount: coursesModel.sections!.length,
),
);
}
Widget _uiWidget(index) {
/* this form here is the parent of form fields/Formhelper widgets as seen below*/
return Form(
/* -- note here--
if we use a UniqueKey()
instead of our globalkey
here and comment the ValidateAndSave() function here
the form will work in terms of adding and removing sections
but we won't be able to either
save content/input of the user in the fields or
either validate
them so that sucks. */
/*this form is where global key is first used*/
key: globalkey,
child: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_sectionsContainer(index),
Row(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Flexible(
flex: 1,
fit: FlexFit.loose,
child: FormHelper.inputFieldWidgetWithLabel(
context,
'Add Section$index',
'',
'Section Title',
(onValidate) {
if (onValidate.isEmpty) {
return 'section ${index + 1} name cant be empty';
}
return null;
},
(onSavedVal) {
coursesModel.sections![index++] = index.toString();
onSavedVal = index;
},
onChange: (onChangedval) {
coursesModel.sectionName = onChangedval;
},
initialValue: coursesModel.sectionName ?? "",
borderColor: Colors.black,
borderFocusColor: Colors.black,
fontSize: 14,
labelFontSize: 14,
validationColor: Colors.redAccent,
),
),
Visibility(
visible: index == coursesModel.sections!.length - 1,
child: IconButton(
onPressed: () {
addEmailControl();
},
icon: const Icon(
Icons.add_circle,
color: Colors.greenAccent,
),
),
),
Visibility(
visible: index > 0,
child: SizedBox(
width: 35,
child: IconButton(
onPressed: () {
removeEmailControl(index);
},
icon: const Icon(
Icons.remove_circle,
color: Colors.redAccent,
),
),
),
),
],
),
],
),
),
);
}
Widget _sectionsContainer(index) {
/* the widget used to create the current section displayed on the top left of each textformfields*/
return Column(
children: [
Padding(
padding: const EdgeInsets.all(10),
child: Text(
'Section ${index + 1}',
textAlign: TextAlign.left,
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.bold,
),
),
),
],
);
}
void addEmailControl() {
setState(() {
coursesModel.sections!.add('');
});
}
void removeEmailControl(index) {
setState(() {
if (coursesModel.sections!.length > 1) {
coursesModel.sections!.removeAt(index);
}
});
}
bool validateAndSave() {
/* we're especially using the <FormState> that is provided by the Globalkey to be able access the currentState of widget/form that has the global key in order to either validate or save the textformfields input or both in the same time*/
// validate each form
if (globalkey.currentState!.validate()) {
// If all data are correct then save data to out variables
// save each form
globalkey.currentState!.save();
return true;
} else {
return false;
}
}
}
I'm trying my best to figure it out on my own as I want to know how to solve this problem properly and where did I go wrong, and any help is very much appreciated thank you!
I suggest to create List<GlobalKey> variable. When you dynamically add or delete sub forms, you add or remove list items accordingly. It is impossible to use same GlobalKey for multiple widgets. So you need to create separate GlobalKeys for each form.
You may create a file of Global variables that may be shared across multiple files to ensure you are using a single instance.
Example globals.dart file
GlobalKey<SomeState> myGlobalKey = GlobalKey<SomeState>();
Example of implementation inside main.dart (or whatever file)
import './[path-to-globals]/globals.dart' // enter the appropriate path for your project
... // some code
Form(
key: myGlobalKey,
... // code
)
... // maybe more code
What is the difference between TextField and TextFormField in flutter? Both look the same. Which one should i use? Which one do you recommend? I use TextField like this:
const TextField(
obscureText: true,
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText: 'Password',
),
)
A TextFormField mainly has the validator argument, while TextField doesn't: it must return null if the input is valid, and it must return a String if there's some error (usually the String itself contains information about the error). This argument allows you to validate the user input in a very easy way, provided that you include the form fields (there are others, in addition to TextFormField) in a Form widget and that you apply a Key to the Form.
If you do everything correctly, you could just invoke formKey.currentState!.validate() and automatically Flutter will invoke the validator argument for each form field that you added to the Form. If everything checks, the validate will return true, and you can proceed with your program logic. Otherwise, it will return false and it will show the String returned by the validator near the form field that contained incorrect data.
This example is taken from the Flutter cookbook on forms:
[...]
final _formKey = GlobalKey<FormState>();
#override
Widget build(BuildContext context) {
// Build a Form widget using the _formKey created above.
return Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
TextFormField(
// The validator receives the text that the user has entered.
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter some text';
}
return null;
},
),
Padding(
padding: const EdgeInsets.symmetric(vertical: 16.0),
child: ElevatedButton(
onPressed: () {
// Validate returns true if the form is valid, or false otherwise.
if (_formKey.currentState!.validate()) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Processing Data')),
);
}
},
child: const Text('Submit'),
),
),
],
),
);
}
}
You use TextFormField when using Form widget. Combined with Form widget you can make more complex forms + validation for whole form.
TextField is basically a TextFormField but you don't have to include it into the Form widget and validation would work a bit differently.
i am getting data by document id but i get this error:
Bad state: cannot get a field on a DocumentSnapshotPlatform which does not exist
and it's working, i can get the data from firebase by document id but it's giving the error in debug console.
I'm getting data with StreamBuilder:
StreamBuilder(
stream: _databaseService.productCollection.doc(docID).snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return Center(
child: CircularProgressIndicator(
valueColor:
new AlwaysStoppedAnimation<Color>(Colorsx.mainColor),
),
);
}
var document1 = snapshot.data;
return Container(
decoration: BoxDecoration(
color: Colorsx.mainColor,
borderRadius: radius,
),
// color: Colorsx.mainColor,
child: Column(
children: [
Expanded(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Chip(
label: LabelText1("Ürün Adı: "),
backgroundColor: Colorsx.mainColor,
),
Chip(
shadowColor: Colorsx.mainColor2,
elevation: 24,
label: LabelText1(document1["productName"] ?? ""),
backgroundColor: Colorsx.mainColor2,
),
],
),
),
],
),
);
},
),
i couldn't find what is the problem in here but according to my researches, it' related with the map .
is there any idea?
It looks like _databaseService.productCollection.doc(docID) may not point to an existing document at some point while this code runs. If you then call document1["productName"] on it as you do, it'll raise the error you see.
So you need to decide what to render when this situation happens (even if only briefly). For example, you could just make the CircularProgressIndicator stay on the screen until a document is available:
if (!snapshot.hasData || !snapshot.data.exists) {
if you're using cloud_functions ^2 and were using 1 you might be running into this issue, only way to deal with the error is with a try catch function
getSnapshot(DocumentSnapshot snapshot, String key) {
try {
return snapshot.get(key);
} catch (error) {
return null;
}
}
then just call it
Status.fromSnapShot(DocumentSnapshot snapshot)
: assert(snapshot.exists != false),
name = getSnapshot(snapshot, 'name'),
before it was looking like this
Status.fromSnapShot(DocumentSnapshot snapshot)
: assert(snapshot.exists != false),
name = snapshot.data()['name'],
I am working on a login system, where i authenticate user by OTP ,Here i want to disable the Resend OTP button for 30 seconds every time the user clicks it and show the time remaining
if you want to have a live counter for showing the user the seconds past you should use stream builder
StreamBuilder(
stream: _timerStream.stream,
builder: (BuildContext ctx,
AsyncSnapshot snapshot) {
return SizedBox(
width: 300,
height: 30,
child:RaisedButton(
textColor: Theme.of(context)
.accentColor,
child: Center(
child:
snapshot.data == 0 ?
Text('send code again')
: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(' button will be enable after ${snapshot.hasData ? snapshot.data.toString() : 30} seconds '),
],)
),
onPressed: snapshot.data == 0 ? () {
// your sending code method
_timerStream.sink.add(30);
activeCounter();
} : null,
)
);
},
)
you can find complete code on dartpad.dev with this link
Declare boolean onPressedValue variable with true,
Add Condition in onPressed Parameter.
bool onPressedValue=true;
RaisedButton(
child: Text('OTP'),
onPressed: onPressedValue==true?(){
setState((){
onPressedValue=false;
});
Timer(Duration(seconds: 30),(){
setState((){
onPressedValue=true;
});
});
}:null)
You can try this
Declare a variable call like this globally
bool shouldButtonEnabled=true;
then on click of send OTP button call this method while you other stuff like sending OTP call this method after it
_disabledButton(){
shouldButtonEnabled=false;
Timer(
Duration(seconds: 30),
() => shouldButtonEnabled=true);
}
and when check this bool on resend OTP button like this
onPressed: () {
if(shouldButtonEnabled){
//do your work here
}
}
I have a FLUTTER problem that I couldn't solve.
Scenario:
1. Implement a QR reader application.
2. The app, read the QR code
3. When you read the QR code, you redirect me to a user's detail page
Problem:
I want to edit that person's data, that's why place a TexFormField, valid fields, but when I call
FUTURE function to send the parameters by post, transforming the body in a JSON so that my server detects it, the button DOES NOTHING.
This is My code
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Container(
child : Text("Escanea el codigo QR ", style: TextStyle(fontSize: 25.0),)
),
),
floatingActionButton: FloatingActionButton(
onPressed: obtenerValorQR,
child: Icon(Icons.settings_overscan,),
backgroundColor:Color(0xFF56AB2F)
),
floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
);
}
---------------------------LOGIC -------------------------
Future obtenerValorQR()
async{
_scantemp= await FlutterBarcodeScanner.scanBarcode("#004297", "salir", true);
setState(() {
value=_scantemp;
});
if (value == null) {
Navigator.pushNamed(context, QrPageRoute);
} else {
Navigator.pushNamed(context, HomePageRoute, arguments: value);
}
}
2. App read QR code
Widget _infoPerfilUsuario(BuildContext context , index ){
return Container(
height: 120.0,
child: Card(
child: Padding(
padding: const EdgeInsets.all(15.0),
child: ListTile(
leading: CircleAvatar(backgroundImage:
NetworkImage(widget.usuarios[index].urlFoto), radius: 30.0,),
title: Text("Nombre: ${widget.usuarios[index].nombres}"),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text("Apellidos: ${widget.usuarios[index].apellidos}"),
Text("Zona: ${widget.usuarios[index].territorio}")
],
),
),
)
),
);
}
QR DETAIL
4. I WANT TO OTHER PARAMETERS IN DETAILPAGE FOR EXAMPLE " PESO" BUT TH RAISED BUTTON DONT COMPILE THE CODE
Code where I send the "peso" parameter that I implement, but does not do what I am looking for.
widget _botonesAcciones(BuildContext context , int index ){
return Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
RaisedButton(child: Text("SAVE "), color: Colors.green,
onPressed: () {
final form = formKey.currentState;
if(form.validate()) {
_sendData( context , index );
Navigator.pushNamed(context, QrPageRoute);
}
}
),
],
);
}
I IMPLEMENT THIS FUNCTION IF THE FIELD IS VALIDATED, I just want the data to be sent, I don't want the response body returned, just send the data to my DataBase
Future <void> _sendData (BuildContext context , int index ) async {
final url = Uri.https( _url,'/searchdata.php');
await http.post(url,
body: json.encode({
"id" : "${widget.usuarios[index].idUsuarioMobile}",
"peso" : peso
}),
);
}
Something is wrong?
I think my mistake is in the sendData () function
Hi the solucion is simple:
void _sendData(BuildContext context , int index ) {
var url = Uri.https( _url,'/updatePuntos.php');
http.post(url,
body: json.encode({
"id" : "${widget.usuarios[index].idUsuarioMobile}",
"peso" : peso
}),
);
Looking for me econtre, the answer to my question, was something as simple as returning a void method and sending the data to the server. You should use,
body: json.encode
it will make your life easier.