I am working on an Array of Textformfields. The Array can be more than 50. I want to submit array of values to the server. The issue is when I execute validator it will be validating the first 8 in the array after that no error message. That too disappears when scrolling. I posted my code below:
void validateAndSave() {
final form = _formKey.currentState;
if (form.validate()) {
_formKey.currentState.save();
print('Form is valid');
}
else {
// _formKey.currentState.
print('form is invalid');
}
}
Widget singleItemList(int index) {
Item item = itemList[index];
return Container(
width: MediaQuery.of(context).size.width,
decoration: BoxDecoration(
color: Colors.white,
),
child:
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child:Padding(
padding: const EdgeInsets.fromLTRB(20, 10, 10, 20),
child: new TextFormField(
controller: _controller[index],
keyboardType: TextInputType.number,
//onSubmitted: (text) {
// takeNumber(text, item.id);
//},
validator: (value) {
if (value.isEmpty) return 'value is required';
},
decoration: new InputDecoration(
border: new OutlineInputBorder(
borderSide: new BorderSide(color: Colors.indigo)),
labelText: itemList[index].id,
suffixStyle: const TextStyle(color: Colors.red)
),
),
),
),
],
),
);
}
Please help me. Thanks in advance.
Regards,
Sathish
Have you considered replacing ListView.builder with Column?
Like this:
child: SingleChildScrollView(
child: Column(
children: List.generate(itemList.length, (index) {
if (itemList.isEmpty) {
return CircularProgressIndicator();
} else {
return singleItemList(index);
}
})
)
)
From ListView documentation:
Destruction
When a child is scrolled out of view, the associated element subtree, states and render objects are destroyed. A new child at the same position in the list will be lazily recreated along with new elements, states and render objects when it is scrolled back.
Related
Im trying to use a dropDownButton instead of a dropDownButtonFormField because i run thru alot of bugs that i see in the dropDownButtonFormField widget. One problem is that in dropDownButtonFormField the clickable area to open the items is only over the hint text and not a cm under it, witch in my case created bad ux. The second problem is that in the dropDownButtonFormField, if an item is a long text and the user presses it when it displays on the main part of the dropdown, the text doesnt create a new line so the user can see the text, the same thing doesnt happen to the dropDownButton widget. Im using dropDownButtonFormField only to use the validator. This is with using DropDownButton Widget.
And this is using the dropDownButtonFormField Widget while having a long text.
Center locationDropDown() {
return Center(
child: Container(
margin: const EdgeInsets.all(10),
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4),
decoration: BoxDecoration(
color: Colors.grey.shade200,
borderRadius: BorderRadius.circular(12),
),
child: DropdownButtonFormField<String>(
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please select a location';
}
return null;
},
decoration: const InputDecoration(
enabledBorder: UnderlineInputBorder(
borderSide: BorderSide(color: Colors.transparent),
),
),
value: chooseLocation,
hint: const Text('Select a location'),
isExpanded: true,
items: workingData!.map((some) {
return DropdownMenuItem(
child: Text(
some.name + ' (${some.location})',
),
value: some.id,
);
}).toList(),
onChanged: (String? displayedValue) {
setState(
() {
chooseLocation = displayedValue!;
},
);
},
),
),
);
}
isDense: false,
Add isDense value false in DropdownButtonFormField.
I have a DropDownButtonFormField I need to check this. If the dropdown value is not selected by the user then assign the initial value when submitting.
Custom DropDown
Container myDropDownContainer(String initialVal, List<String> listItems,
String text, Function myFunc, Function validate) {
return Container(
margin: const EdgeInsets.all(8),
child: Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start,
children: [
SizedBox(
width: 120,
child: Text(
text,
style: kTextStyle,
),
),
const SizedBox(
width: 20,
),
Expanded(
child: Container(
height: 50,
decoration: BoxDecoration(
color: Colors.orangeAccent,
borderRadius: BorderRadius.circular(5)),
child: DropdownButtonFormField<String>(
autovalidateMode: AutovalidateMode.always,
//menuMaxHeight: 300,
validator: (value) {
if(value!.isEmpty) {
return "485s4a8sd4as85";
}
} ,
decoration: const InputDecoration(border: InputBorder.none),
isExpanded: true,
onTap: () => myFunc,
//borderRadius: BorderRadius.circular(5),
value: initialVal,
icon: const Icon(
Icons.arrow_downward,
color: Colors.black38,
),
iconSize: 24,
elevation: 16,
dropdownColor: Colors.deepOrange,
style: kTextStyle.copyWith(color: Colors.black),
onChanged: (val) => myFunc(val),
items: listItems.map<DropdownMenuItem<String>>((String? val) {
return DropdownMenuItem(
//TODO: Set default values
value: val,
child: Text(
val,
style: kTextStyle.copyWith(color: Colors.black),
),
);
}).toList(),
),
),
)
],
),
);
}
This is my onChanged property that assigns the selected value by the user. I added some explanations about what I am trying to do.
String _valueCinsiyet = "Diğer"; // initial value
void onChangedCinsiyet(String? newVal) {
setState(() {
if(newVal==null) {
_formData.setCinsiyet(_valueCinsiyet);
/*
'if newVal is null' means that if the value is not selected by the user
then set the initialValue( _valueCinsiyet)
*/
} else {
/*
if newVal is not null then assign the newVal( which means the selected value)
into my initialValue, then set the data to use it on different pages. What is missing?
*/
_valueCinsiyet = newVal;
_formData.setCinsiyet(_valueCinsiyet);
}
});
}
You can use nullable data to track DropdownButtonFormField changes. Being nullable you can check if it is null or not, no need to anything extra on onChanged: just assign new value usual way.
On state before build method: String? value; // value to keep track
child: DropdownButtonFormField<String>(
value: value,
onChanged: (val) {
setState(() {
value = val;
});
},
Now onSaved/submit button you can pass value by checking null, simple way is
value?? "default Value". In your case, it is value??Diğer
I have a GridView.builder widget displaying all of the users of the app from the list searchedUsers and I am trying to implement searching. The TextFormField widget's onChanged property is defined to trigger the searchName(String searchQuery) function. I made the searchName function print out the searchedUsers list and I can see from the log that it is updated everytime I type something in the TextFormField but the GridView is always empty. Please see the code below. Any help would be greatly appreciated.
I have added comments to the code to explain everything.
class UserGrid extends StatefulWidget {
#override
_UserGridState createState() => _UserGridState();
}
class _UserGridState extends State<UserGrid> {
#override
Widget build(BuildContext context) {
//The list of userModels from FireStore
final userModels = Provider.of<List<UserModel>>(context) ?? [];
//The list that will contain users whose names match the value in TextFormField
List<UserModel> searchedUsers = [];
//The function that updates the searchedUsers list. I wrote setState whenever I changed the
//list because I thought it would update the UI but it doesn't
void searchName (String searchQuery){
for (var i in userModels) {
if (i.name.toLowerCase().contains(searchQuery.toLowerCase())) {
setState(() {
searchedUsers.add(i);
print(searchedUsers);
});
}
}
if (searchedUsers == null) {
setState(() {
searchedUsers = userModels;
});
}
}
return Column(
children: [
SizedBox(height: 20,),
Padding(
padding: EdgeInsets.all(10),
child: Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: new BorderRadius.circular(1000.0),
),
child: Padding(
padding: EdgeInsets.only(left: 15, right: 15, top: 5),
child: TextFormField(
onChanged: (val) {
searchName(val);
},
decoration: InputDecoration(
focusColor: Colors.white,
icon: Icon(Icons.search),
border: InputBorder.none,
labelText: 'For- eller efternavn',
))))),
SizedBox(height: 40,),
GridView.builder(
itemCount: searchedUsers.length,
shrinkWrap: true,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
),
itemBuilder: (context, index) {
return userModelTile(userModel: searchedUsers[index]);
},
),
],
);
}
}
I figured out the problem. The list searchedUsers need to be defined before the build function.
i want to change the indexvalue (pictogramindex) of one page when we click nextbutton on another screen.I will explain briefly , I have 2 screens in my scenario the first screen contains an image and it's name , a textfield and nextbutton (i have provided a dummy data contains a list of image and it's names) the logic behind this is , when we complete the textfield box and click next button(after validate) the textfield value checks with the correctvalue which i was given in the dummy data and show it's synonym which also provided. when we click the next button we will go to another page which contains the correct answer(passed from first page) and a textfield in this the user can write about the correct answer ( validated) when click next button in this page (till this my applicationworks perfectly) i want to load the first page with it's index updated (+1) which i initialised as 0 (var pictogramindex=0). But in my case when coming back to first page the index is not updating it will automatically stores the initialised value. what i want is i want to update index on the first page when i click next button in the Second page .
my source code of first screen is shown here
class Pictogramscreen extends StatefulWidget {
final int length;
const Pictogramscreen({Key key, this.length}) : super(key: key);
#override
_PictogramscreenState createState() => _PictogramscreenState();
}
class _PictogramscreenState extends State<Pictogramscreen> {
#override
final _Key = GlobalKey<FormState>();
Color defaultcolor = Colors.blue[50];
Color trueColor = Colors.green;
Color falseColor = Colors.red;
Widget defcorrect = Text('');
var pictogramindex = 0;
TextEditingController usertitleInput = TextEditingController();
nextPictogram() {
setState(() {
pictogramindex++;
});
}
fillColor() {
setState(() {
usertitleInput.text == pictdata[pictogramindex]['pictcorrectword']
? defaultcolor = trueColor
: defaultcolor = falseColor;
});
}
correctText() {
setState(() {
usertitleInput.text == pictdata[pictogramindex]['pictcorrectword']
? defcorrect = Text(pictdata[pictogramindex]['pictsynonym'])
: defcorrect = Text(pictdata[pictogramindex]['pictcorrectword']);
});
}
reset() {
setState(() {
defaultcolor = Colors.blue[50];
defcorrect = Text('');
usertitleInput.clear();
});
}
void description(BuildContext ctx) {
Navigator.of(context).pushNamed('/user-description', arguments: {
'id': pictdata[pictogramindex]['pictid'],
'word': pictdata[pictogramindex]['pictcorrectword']
});
}
Widget build(BuildContext context) {
int length = pictdata.length;
return Scaffold(
body: pictogramindex < pictdata.length
? ListView(
children: <Widget>[
Container(
margin: EdgeInsets.only(top: 20),
padding: EdgeInsets.all(15),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Card(
margin: EdgeInsets.only(top: 20),
child: Image.network(
pictdata[pictogramindex]['pictimg']),
),
SizedBox(
height: 10,
),
Text(
pictdata[pictogramindex]['pictword'],
style: TextStyle(
fontSize: 25,
),
),
SizedBox(
height: 10,
),
//Card(
//color: Colors.blue,
// child: TextField(
// decoration: InputDecoration.collapsed(
// hintText: 'type here'),
//textAlign: TextAlign.center,
// onSubmitted: (value) {
// usertitleInput = value;
// print(usertitleInput);
// },
// ),
//),
Form(
key: _Key,
child: TextFormField(
controller: usertitleInput,
validator: (usertitleInput) {
if (usertitleInput.isEmpty) {
return 'Answer cannot be empty';
} else {
return null;
}
},
textAlign: TextAlign.center,
decoration: InputDecoration(
enabledBorder: OutlineInputBorder(
borderSide:
BorderSide(color: Colors.blueAccent),
borderRadius: BorderRadius.all(
Radius.circular(15),
)),
labelText: 'Type your Answer',
filled: true,
fillColor: defaultcolor,
),
onFieldSubmitted: (value) {
usertitleInput.text = value;
fillColor();
correctText();
print(usertitleInput.text);
}),
),
SizedBox(
height: 10,
),
defcorrect,
SizedBox(
height: 10,
),
RaisedButton(
onPressed: () {
if (_Key.currentState.validate()) {
description(context);
// nextPictogram();
reset();
}
//
//if (_Key.currentState.validate() == correctText()) {
// nextPictogram;
// }
},
child: Text('Next'),
)
],
),
),
],
)
: Center(
child: Text('completed'),
));
}
}
my source code of the second screen is show here
class Userinputscreen extends StatefulWidget {
final String id;
final String word;
const Userinputscreen({Key key, this.id, this.word}) : super(key: key);
#override
_UserinputscreenState createState() => _UserinputscreenState();
}
class _UserinputscreenState extends State<Userinputscreen> {
final _Keey = GlobalKey<FormState>();
TextEditingController userdescription = TextEditingController();
var pictogramindex;
void nextpict(BuildContext context) {
Navigator.of(context).pushNamed('/main-screen');
}
// void nextpict(BuildContext context, int index) {
// Navigator.push(
// context,
// MaterialPageRoute(
// builder: (ctx) => Pictogramscreen(
// index: i = 0,
// )));
// }
#override
Widget build(BuildContext context) {
final routeArgs =
ModalRoute.of(context).settings.arguments as Map<String, String>;
final correctWord = routeArgs['word'];
return MaterialApp(
home: Scaffold(
body: ListView(children: <Widget>[
Padding(
padding: EdgeInsets.only(top: 50),
child: Center(
child: Container(
padding: EdgeInsets.all(20),
margin: EdgeInsets.only(top: 100),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
correctWord,
style: TextStyle(fontSize: 26),
),
SizedBox(
height: 10,
),
Form(
key: _Keey,
child: TextFormField(
controller: userdescription,
validator: (userdescription) {
if (userdescription.isEmpty) {
return 'Answer cannot be empty';
} else {
return null;
}
},
textAlign: TextAlign.center,
decoration: InputDecoration(
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(color: Colors.blueAccent),
borderRadius: BorderRadius.all(
Radius.circular(15),
)),
labelText: 'Type your Answer',
filled: true,
),
onFieldSubmitted: (value) {
userdescription.text = value;
print(userdescription.text);
}),
),
SizedBox(
height: 10,
),
RaisedButton(
onPressed: () {
if (_Keey.currentState.validate()) {
nextpict(context);
}
},
child: Text('Next'),
)
],
),
),
),
),
])),
);
}
}
If I get it right, you basically want to tell the initial page that it's state is updated(the index) elsewhere. You basically need to make your app "reactive".
As is said in Google Developers Tutorial:
One of the advantages of Flutter is that it uses reactive views, which you can take to the next level by also applying reactive principles to your app’s data model.
Use some sort of state management. You need to choose from and use either Bloc, InheritedWidget and InheritedModel, Provider(ScopedModel), or the like.
Check this article on flutter about state management, or this for a complete list of approaches
I have a widget that adding some decoration to out of the widget as the code below.I also wanted to change decoration of the widgets like removing underline in TextFormField widget.
class FormRowWidget extends StatelessWidget {
final Widget inputWidget;
FormRowWidget({this.title, this.inputWidget});
#override
Widget build(BuildContext context) {
var isTextField = inputWidget is TextFormField;
return Container(
padding: EdgeInsets.symmetric(horizontal: 8.0,vertical: 8.0),
decoration: BoxDecoration(
shape: BoxShape.rectangle,
color: Colors.white,
boxShadow: [
new BoxShadow(
color: Colors.black,
blurRadius: 10,
)
],
),
child: inputWidget// I want to change input widgets' properties.
])
}
}
I am using this widget like the code below.
children: <Widget>[
FormRowWidget(
inputWidget: TextFormField(
decoration: InputDecoration(border: InputBorder.none),
controller: usernameController,
validator: (value) {
if (value.length < 4) return "Username err!";
},
onSaved: (value) {
print(value);
},
),
),
FormRowWidget(
inputWidget: TextFormField(
decoration: InputDecoration(border: InputBorder.none),
controller: receiverFirmController,
),
)]
The thing I wonder is what is the best way of overriding inputWidget in my FormRowWidget.
After all, I want to access and change the settings of the widget like the below.Is it possible to handle it?
var customTextWidget = inputWidget as TextFormField;
customTextWidget.decoration