I have a Form in flutter, containing a column with rows with textFormFields.
Each row has a iconButton at the end for deleting said row. The rows is built with a Consumer.
I have a notifier class containing data that the consumer listens for.
When the delete button is pressed a function in the notifier class is called, responsible for deleting the row with the given index. The same function is printing out the dataset befor deletion and again after deletion. The print looks good, the result is as expected.
The Consumer also rebuilds the dataset with one row less. But the data that is presented in the ui is not as expected.
In the Widget that is responsible for building the rows I print out the data that is put in the initialValue of the textFormField. The print looks good, again as expected. But the ui does not render what the print is showing.
See pictures and code.
Ready for deleting the second row:
After deletion:
Code that deletes the row and prints the data.
void deletePoint({int switchpointIndex, int pointIndex, Point point}) {
for (Switchpoint switchpoint in _settings.switchpoints) {
for (Point point in switchpoint.points) {
print("Tag: ${point.tag}. And Type: ${point.type}");
}
}
_settings.switchpoints[switchpointIndex].points.removeAt(pointIndex);
for (Switchpoint switchpoint in _settings.switchpoints) {
for (Point point in switchpoint.points) {
print("Tag: ${point.tag}. And Type: ${point.type}");
}
}
notifyListeners();
}
Result of the print (as expected):
Tag: a. And Type: a
Tag: b. And Type: b
Tag: c. And Type: c
Tag: a. And Type: a
Tag: c. And Type: c
Code that prints and renders the data in the row Widget:
#override
Widget build(BuildContext context) {
print("PointIndex: $pointIndex, value: ${point.tag} and ${point.type}");
return Row(
...
TextFormField(
...
initialValue: point.tag ?? ''
...
TextFormField(
...
initialValue: point.type ?? ''
...
This also prints the expected:
PointIndex: 0, value: a and a
PointIndex: 1, value: c and c
Anyone who understands whats going on?
-- EDIT --
No TextEditingController.
Form code:
return Container(
padding: EdgeInsets.symmetric(horizontal: 10),
child: Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
height: 50,
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'Switchpoint Layout',
style: TextStyle(
color: Colors.teal,
fontSize: 20,
fontWeight: FontWeight.bold),
),
TextButton.icon(
onPressed: () {
context
.read<SettingsNotifier>()
.addEmptySwitchpoint();
},
icon: Icon(Icons.add),
label: Text('Add Switchpoint')),
],
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 10),
child: Consumer<SettingsNotifier>(
builder: (context, notifier, child) {
List<SwitchpointRow> switchpointRows = [];
int switchpointIndex = 0;
for (Switchpoint switchpoint
in notifier.switchPoints) {
switchpointRows.add(SwitchpointRow(
switchpoint: switchpoint,
switchpointIndex: switchpointIndex,
formKey: _formKey,
));
switchpointIndex++;
}
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: switchpointRows),
],
);
}),
),
Padding(
padding: EdgeInsets.symmetric(vertical: 16.0),
child: ElevatedButton(
onPressed: () async {
print(context
.read<SettingsNotifier>()
.settings
.toJson());
if (_formKey.currentState.validate()) {}
},
child: Text('Submit'),
),
),
],
),
),
);
SwitchpointRow code:
class SwitchpointRow extends StatelessWidget {
const SwitchpointRow({
Key key,
#required this.switchpoint,
#required this.switchpointIndex,
#required this.formKey,
}) : super(key: key);
final int switchpointIndex;
final Switchpoint switchpoint;
final formKey;
List<PointRow> loadPoints(List<Point> points) {
List<PointRow> pointRows = [];
int pointIndex = 0;
for (Point point in points) {
pointRows.add(PointRow(
point: point,
switchpointIndex: switchpointIndex,
pointIndex: pointIndex,
last: points.last == point ? true : false));
pointIndex++;
}
return pointRows;
}
#override
Widget build(BuildContext context) {
return Card(
child: Padding(
padding: const EdgeInsets.only(bottom: 20),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
AppBar(
backgroundColor: Colors.amberAccent,
title: TextFormField(
decoration: InputDecoration(
border: InputBorder.none,
contentPadding: EdgeInsets.fromLTRB(0, 2, 0, 4),
isDense: true,
hintText: 'Switchpoint name',
labelText: 'Switchpoint name',
),
initialValue: switchpoint.name ?? '',
onChanged: (value) {
context
.read<SettingsNotifier>()
.switchPoints[switchpointIndex]
.name = value;
},
validator: (value) {
if (value.isEmpty) {
return 'mandatory';
}
return null;
}),
actions: [
Padding(
padding: const EdgeInsets.only(right: 18.0),
child: TextButton.icon(
onPressed: () {
context.read<SettingsNotifier>().deleteSwitchpoint(
switchpointIndex: switchpointIndex);
},
icon: Icon(Icons.delete),
label: Text('Delete Switchpoint'),
),
),
Padding(
padding: const EdgeInsets.only(right: 8.0),
child: TextButton.icon(
onPressed: () {
context
.read<SettingsNotifier>()
.addPoint(switchpointIndex);
},
icon: Icon(Icons.add),
label: Text('Point'),
),
),
],
),
Padding(
padding: const EdgeInsets.only(left: 50.0),
child: Column(children: loadPoints(switchpoint.points)),
),
],
),
),
);
}
}
PointRow code:
class PointRow extends StatelessWidget {
const PointRow(
{Key key,
#required this.point,
#required this.switchpointIndex,
#required this.pointIndex,
#required this.last})
: super(key: key);
final Point point;
final int pointIndex;
final int switchpointIndex;
final bool last;
#override
Widget build(BuildContext context) {
print("PointIndex: $pointIndex, value: ${point.tag} and ${point.type}");
return Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Expanded(
flex: 3,
child: TextFormField(
decoration: InputDecoration(
contentPadding: EdgeInsets.fromLTRB(0, 2, 0, 4),
isDense: true,
hintText: 'Tag',
labelText: 'Point tag',
),
onChanged: (value) {
context
.read<SettingsNotifier>()
.switchPoints[switchpointIndex]
.points[pointIndex]
.tag = value;
},
initialValue: point.tag ?? '',
validator: (value) {
if (value.isEmpty) {
return 'mandatory';
}
return null;
}),
),
SizedBox(width: 30.0),
Expanded(
flex: 3,
child: TextFormField(
decoration: InputDecoration(
contentPadding: EdgeInsets.fromLTRB(0, 2, 0, 4),
isDense: true,
hintText: 'Type',
labelText: 'Type of point',
),
onChanged: (value) {
context
.read<SettingsNotifier>()
.switchPoints[switchpointIndex]
.points[pointIndex]
.type = value;
},
initialValue: point.type ?? '',
),
),
Expanded(
child: IconButton(
padding: EdgeInsets.all(0.0),
icon: Icon(Icons.delete),
onPressed: () {
context.read<SettingsNotifier>().deletePoint(
switchpointIndex: switchpointIndex,
pointIndex: pointIndex,
point: point);
},
),
)
],
);
}
}
The whole Form picture:
So, after some trial and error the solution had to be a TextEditingController which I wanted to avoid since eventually there will be several fields.
So the solution is a inline controller like this:
TextFormField(
...
controller: TextEditingController()..text = 'the value',
...
);
In this way the UI behaves as I want.
Related
I have a radio buttons created but while on click of the buttons the value is not getting changed.
here is my code.
Widget _buildOmsIDTextField() {
// Group Value for Radio Button.
int id = 1;
List<OptionList> nList = [
OptionList(
padding : const EdgeInsets.fromLTRB(14.0, 180, 25, 50),
index: 1,
option: "Accept",
),
OptionList(
padding : const EdgeInsets.all(14.0),
index: 2,
option: "Reject",
),
];
return Column(
children: <Widget>[
const Padding(
padding : EdgeInsets.all(14.0),
child: Text("Please Select an Option", style: TextStyle(fontSize: 23))
),
SizedBox(
height: 150.0,
child: Column(
children:
nList.map((data) => RadioListTile(
title: Text(data.option),
groupValue: data.index,
value: id,
onChanged: (val) {
setState(() {
radioItemHolder = data.option ;
id = data.index;
});
},
)).toList(),
),
),
],
);
}
Any help would be highly appreciated!
While you are calling _buildOmsIDTextField() inside build method, the int id = 1; will always get 1. Put your group value outside the build method. And use same group value on radio buttons.
// Group Value for Radio Button. I also prefer putting any decleared varable for radio button here
int id = 1;
Widget _buildOmsIDTextField() {...
...
nList.map((data) => RadioListTile(
title: Text(data.option),
groupValue: id,
value: data.option,
}
#override
Widget build(BuildContext context) {
A better way of doing this, providing data type on RadioListTile.
OptionList? selectOption;
List<OptionList> nList = [
OptionList(
padding: const EdgeInsets.fromLTRB(14.0, 180, 25, 50),
index: 1,
option: "Accept",
),
OptionList(
padding: const EdgeInsets.all(14.0),
index: 2,
option: "Reject",
),
];
Widget _buildOmsIDTextField() {
return Column(
children: <Widget>[
const Padding(
padding: EdgeInsets.all(14.0),
child: Text("Please Select an Option",
style: TextStyle(fontSize: 23))),
SizedBox(
height: 150.0,
child: Column(
children: nList
.map((data) => RadioListTile<OptionList>(
title: Text(data.option),
groupValue: selectOption,
value: data,
onChanged: (val) {
setState(() {
selectOption = val;
});
},
))
.toList(),
),
),
],
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: _buildOmsIDTextField(),
);
}
I have a form (second screen) which is used for CRUD. When i add data, it is saved to list view as you can see on the table.
The list view is passed to (first screen) where i can iterate and see the list data with updated content.
However, when i click on go to second screen, the list view data disappears. The given 3 lists are hard coded for testing purpose.
Now, my question is that, How can i keep the data in the table and not disappear, even if i change screen back and forth multiple times. My code is as below: -
**
Main.dart File
**
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
String _userInformation = 'No information yet';
void languageInformation() async {
final language = await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => Episode5(),
),
);
updateLanguageInformation(language);
}
void updateLanguageInformation(List<User> userList) {
for (var i = 0; i <= userList.length; i++) {
for (var name in userList) {
print("Name: " + name.name[i] + " Email: " + name.email[i]);
}
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Testing List View Data From second page to first page"),
),
body: Column(
children: <Widget>[
Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Text(_userInformation),
],
),
SizedBox(
height: 10.0,
),
ElevatedButton(
onPressed: () {
languageInformation();
},
child: Text("Go to Form"),
),
],
),
);
}
}
2. Model.dart File:
class User {
String name;
String email;
User({this.name, this.email});
}
3. Episode5 File
class Episode5 extends StatefulWidget {
#override
_Episode5State createState() => _Episode5State();
}
class _Episode5State extends State<Episode5> {
TextEditingController nameController = TextEditingController();
TextEditingController emailController = TextEditingController();
final form = GlobalKey<FormState>();
static var _focusNode = new FocusNode();
bool update = false;
int currentIndex = 0;
List<User> userList = [
User(name: "a", email: "a"),
User(name: "d", email: "b"),
User(name: "c", email: "c"),
];
#override
Widget build(BuildContext context) {
Widget bodyData() => DataTable(
onSelectAll: (b) {},
sortColumnIndex: 0,
sortAscending: true,
columns: <DataColumn>[
DataColumn(label: Text("Name"), tooltip: "To Display name"),
DataColumn(label: Text("Email"), tooltip: "To Display Email"),
DataColumn(label: Text("Update"), tooltip: "Update data"),
],
rows: userList
.map(
(user) => DataRow(
cells: [
DataCell(
Text(user.name),
),
DataCell(
Text(user.email),
),
DataCell(
IconButton(
onPressed: () {
currentIndex = userList.indexOf(user);
_updateTextControllers(user); // new function here
},
icon: Icon(
Icons.edit,
color: Colors.black,
),
),
),
],
),
)
.toList(),
);
return Scaffold(
appBar: AppBar(
title: Text("Data add to List Table using Form"),
),
body: Container(
child: Column(
children: <Widget>[
bodyData(),
Padding(
padding: EdgeInsets.all(10.0),
child: Form(
key: form,
child: Container(
child: Column(
children: <Widget>[
TextFormField(
controller: nameController,
focusNode: _focusNode,
keyboardType: TextInputType.text,
autocorrect: false,
maxLines: 1,
validator: (value) {
if (value.isEmpty) {
return 'This field is required';
}
return null;
},
decoration: new InputDecoration(
labelText: 'Name',
hintText: 'Name',
labelStyle: new TextStyle(
decorationStyle: TextDecorationStyle.solid),
),
),
SizedBox(
height: 10,
),
TextFormField(
controller: emailController,
keyboardType: TextInputType.text,
autocorrect: false,
maxLines: 1,
validator: (value) {
if (value.isEmpty) {
return 'This field is required';
}
return null;
},
decoration: new InputDecoration(
labelText: 'Email',
hintText: 'Email',
labelStyle: new TextStyle(
decorationStyle: TextDecorationStyle.solid)),
),
SizedBox(
height: 10,
),
Column(
children: <Widget>[
Center(
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
TextButton(
child: Text("Add"),
onPressed: () {
form.currentState.save();
addUserToList(
nameController.text,
emailController.text,
);
},
),
TextButton(
child: Text("Update"),
onPressed: () {
form.currentState.save();
updateForm();
},
),
],
),
Row(
crossAxisAlignment: CrossAxisAlignment.end,
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
ElevatedButton(
child: Text("Save and Exit"),
onPressed: () {
form.currentState.save();
addUserToList(
nameController.text,
emailController.text,
);
Navigator.pop(context, userList);
},
),
],
),
],
),
),
],
),
],
),
),
),
),
],
),
),
);
}
void updateForm() {
setState(() {
User user = User(name: nameController.text, email: emailController.text);
userList[currentIndex] = user;
});
}
void _updateTextControllers(User user) {
setState(() {
nameController.text = user.name;
emailController.text = user.email;
});
}
void addUserToList(name, email) {
setState(() {
userList.add(User(name: name, email: email));
});
}
}
So instead of passing data back and forth between pages, its better to implement a state management solution so that you can access your data from anywhere in the app, without having to manually pass anything.
It can be done with any state management solution, here's how you could do it with GetX.
I took all your variables and methods and put them in a Getx class. Anything in this class will be accessible from anywhere in the app. I got rid of setState because that's no longer how things will be updated.
class FormController extends GetxController {
TextEditingController nameController = TextEditingController();
TextEditingController emailController = TextEditingController();
int currentIndex = 0;
List<User> userList = [
User(name: "a", email: "a"),
User(name: "d", email: "b"),
User(name: "c", email: "c"),
];
void updateForm() {
User user = User(name: nameController.text, email: emailController.text);
userList[currentIndex] = user;
update();
}
void updateTextControllers(User user) {
nameController.text = user.name;
emailController.text = user.email;
update();
}
void addUserToList(name, email) {
userList.add(User(name: name, email: email));
update();
}
void updateLanguageInformation() {
// for (var i = 0; i <= userList.length; i++) { // ** don't need nested for loop here **
for (var user in userList) {
print("Name: " + user.name + " Email: " + user.email);
}
// }
}
}
GetX controller can be initialized anywhere before you try to use it, but lets do it main.
void main() {
Get.put(FormController()); // controller init
runApp(MyApp());
}
Here's your page, we find the controller and now all variables and methods come from that controller.
class Episode5 extends StatefulWidget {
#override
_Episode5State createState() => _Episode5State();
}
class _Episode5State extends State<Episode5> {
final form = GlobalKey<FormState>();
static var _focusNode = new FocusNode();
// finding same instance if initialized controller
final controller = Get.find<FormController>();
#override
Widget build(BuildContext context) {
Widget bodyData() => DataTable(
onSelectAll: (b) {},
sortColumnIndex: 0,
sortAscending: true,
columns: <DataColumn>[
DataColumn(label: Text("Name"), tooltip: "To Display name"),
DataColumn(label: Text("Email"), tooltip: "To Display Email"),
DataColumn(label: Text("Update"), tooltip: "Update data"),
],
rows: controller.userList // accessing list from Getx controller
.map(
(user) => DataRow(
cells: [
DataCell(
Text(user.name),
),
DataCell(
Text(user.email),
),
DataCell(
IconButton(
onPressed: () {
controller.currentIndex =
controller.userList.indexOf(user);
controller.updateTextControllers(user);
},
icon: Icon(
Icons.edit,
color: Colors.black,
),
),
),
],
),
)
.toList(),
);
return Scaffold(
appBar: AppBar(
title: Text("Data add to List Table using Form"),
),
body: Container(
child: Column(
children: <Widget>[
// GetBuilder rebuilds when update() is called
GetBuilder<FormController>(
builder: (controller) => bodyData(),
),
Padding(
padding: EdgeInsets.all(10.0),
child: Form(
key: form,
child: Container(
child: Column(
children: <Widget>[
TextFormField(
controller: controller.nameController,
focusNode: _focusNode,
keyboardType: TextInputType.text,
autocorrect: false,
maxLines: 1,
validator: (value) {
if (value.isEmpty) {
return 'This field is required';
}
return null;
},
decoration: InputDecoration(
labelText: 'Name',
hintText: 'Name',
labelStyle: new TextStyle(
decorationStyle: TextDecorationStyle.solid),
),
),
SizedBox(
height: 10,
),
TextFormField(
controller: controller.emailController,
keyboardType: TextInputType.text,
autocorrect: false,
maxLines: 1,
validator: (value) {
if (value.isEmpty) {
return 'This field is required';
}
return null;
},
decoration: InputDecoration(
labelText: 'Email',
hintText: 'Email',
labelStyle: new TextStyle(
decorationStyle: TextDecorationStyle.solid)),
),
SizedBox(
height: 10,
),
Column(
children: <Widget>[
Center(
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
TextButton(
child: Text("Add"),
onPressed: () {
form.currentState.save();
controller.addUserToList(
controller.nameController.text,
controller.emailController.text,
);
},
),
TextButton(
child: Text("Update"),
onPressed: () {
form.currentState.save();
controller.updateForm();
},
),
],
),
Row(
crossAxisAlignment: CrossAxisAlignment.end,
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
ElevatedButton(
child: Text("Save and Exit"),
onPressed: () {
form.currentState.save();
controller.updateLanguageInformation(); // all this function does is print the list
Navigator.pop(
context); // don't need to pass anything here
},
),
],
),
],
),
),
],
),
],
),
),
),
),
],
),
),
);
}
}
And here's your other page. I just threw in a ListView.builder wrapped in a GetBuilder<FormController> for demo purposes. It can now be stateless.
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Testing List View Data From second page to first page"),
),
body: Column(
children: <Widget>[
Expanded(
child: GetBuilder<FormController>(
builder: (controller) => ListView.builder(
itemCount: controller.userList.length,
itemBuilder: (context, index) => Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Text(controller.userList[index].name),
Text(controller.userList[index].email),
],
),
),
),
),
SizedBox(
height: 10.0,
),
ElevatedButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => Episode5(),
),
);
},
child: Text("Go to Form"),
),
],
),
);
}
}
As your app expands you can create more controller classes and they're all very easily accessible from anywhere. Its a way easier and cleaner way to do things than manually passing data around everywhere.
Need to access a single value when the item is selected in the list.
When the item is clicked, it prints the whole list of objects, instead the value of the selected object.
For example, when user selects Spanish, should print SP instead of the list of objects.
I have a class and a list for the languages, but for this question is not relevant.
This is what I have achieved so far:
List <Object> Dictionary = [
{'Spanish': 'SP'},
{'Italian': 'IT'},
{'German': 'DE'},
{'Arab': 'AR'},
{'Greek': 'GR'},
{'Thai': 'TH'},
{'Chinese': 'CH'},
{'French': 'FR'}
];
List<Language> newDataList;
final finalList = dictionary.values.toList();
onItemChanged(String value) {
setState(() {
newDataList = languages.where((lang) => lang.title.toLowerCase().contains(value.toLowerCase())).toList();
});
}
#override
initState() {
super.initState();
newDataList = [...languages];
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: <Widget>[
Padding(
padding: const EdgeInsets.all(12.0),
child: TextField(
controller: _textController,
decoration: InputDecoration(
icon: Icon(Icons.search),
hintText: 'Type languages',
),
onChanged: onItemChanged,
),
),
Expanded(
child: ListView(
padding: EdgeInsets.all(12.0),
children: newDataList.map((data) {
return Ink(
color: data.selected ? TheBaseColors.lightGreen : Colors.transparent,
child: ListTile(
title: Text(data.title),
onTap: () {
setState(() {
data.selected = !data.selected;
print("${data.title} is now ${data.selected ? "selected" : "not selected"}");
print(Dictionary);
});
}),
);
}).toList(),
),
),
Expanded(
child: Column(children: [
Text('Selected languages :'),
Expanded(
child: ListView(
children: newDataList.where((l) => l.selected).map((l) => Padding(child: Text(l.title), padding: EdgeInsets.only(right: 10))).toList()))
]))
],
),
);
}
}
Tried dictionary.value, dictionary[value]
Instead of List<Object> you want to use Map<String, String> for this.
Map<String, String> dictionary = {
'Spanish': 'SP',
'Italian': 'IT',
'German': 'DE',
'Arab': 'AR',
'Greek': 'GR',
'Thai': 'TH',
'Chinese': 'CH',
'French': 'FR'
};
If you use this you can easily do dictionary['Spanish'] and receive 'SP' as value.
Creating a Screen where I want to perform Flutter-FireBase Searching.But Visibility Toggle is not working as desired.
Desired Toggle Behaviour : When clicked on TextForm field , prefix icon and result card should be visible. Upon Clicking the Prefix Icon(Back Arrow) , Result List (Card) and the prefix icon itself should become invisible and TextField should unfocus .
Actual Behaviour : On clicking the prefix icon , Result set and prefix icon don't disappear , Prefix icon remains there and result set becomes invisible but occupies some space beneath the TextFormField
class AddAppointmentWidget extends StatefulWidget {
#override
_AddAppointmentWidgetState createState() => _AddAppointmentWidgetState();
}
class _AddAppointmentWidgetState extends State<AddAppointmentWidget> {
bool searchbartapped = false;
var queryResultSet = [];
var tempSearchStore = [];
// Search Function
initiateSearch(value) {
//body
}
#override
Widget build(BuildContext context) {
return ListView(
children: [
SizedBox(
height: 15,
),
Padding(
padding: const EdgeInsets.all(18.0),
child: Text('Search',
style: TextStyle(fontSize: 35, fontWeight: FontWeight.bold)),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
children: [
Expanded(
flex: 5,
child: TextFormField(
style: TextStyle(color: Color(0xff2a2a2a), fontSize: 18),
keyboardType: TextInputType.name,
onChanged: (value) {
initiateSearch(value);
},
onTap: () {
setState(() {
searchbartapped = true;
});
},
cursorColor: Color(0xff2a2a2a),
cursorWidth: 1.5,
decoration: InputDecoration(
hintText: "Search by Name",
prefixIcon: Visibility(
visible: searchbartapped,
child: IconButton(
icon: Icon(Icons.arrow_back),
color: Colors.black54,
onPressed: () {
setState(() {
searchbartapped = !searchbartapped;
queryResultSet = [];
tempSearchStore = [];
});
FocusScope.of(context).unfocus();
}),
),
)),
),
],
),
),
Visibility(
visible: searchbartapped,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: ListView(
padding: EdgeInsets.all(5.0),
primary: false,
shrinkWrap: true,
children: tempSearchStore.map((element) {
print(element['name']);
return buildResult(context, element);
}).toList()),
),
),
],
);
}
}
Note The buildResult widget is working perfectly fine.
Problem is only with the visibilty toggle
The issue: When you tap the prefixIcon:
onPressed is called, setting searchbartapped to false which is what you want.
The onTap method of your TextFormField is also called (since prefixIcon is inside it), setting searchbartapped to true.
So what you want is to prevent the second event from happening. I tried to prevent the notification from bubbling up the tree but I couldn't. So what I ended up doing is a bit more manual but works just as well.
Solution: Add a variable (for example hideSearchTapped) which is set to true when the prefixIcon is called. Then when the onTap method of your TextFormField is called, check this variable:
If hideSearchTapped is true, set it to false
Else change searchbartapped as you did
Here is a working example:
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
void main() async {
runApp(
MaterialApp(
home: Scaffold(
body: new AddAppointmentWidget(),
),
),
);
}
class AddAppointmentWidget extends StatefulWidget {
#override
_AddAppointmentWidgetState createState() => _AddAppointmentWidgetState();
}
class _AddAppointmentWidgetState extends State<AddAppointmentWidget> {
bool searchbartapped = false;
bool hideSearchTapped = false;
var queryResultSet = [];
var tempSearchStore = [];
// Search Function
initiateSearch(value) {
//body
}
#override
Widget build(BuildContext context) {
return ListView(
children: [
SizedBox(
height: 15,
),
Padding(
padding: const EdgeInsets.all(18.0),
child: Text('Search', style: TextStyle(fontSize: 35, fontWeight: FontWeight.bold)),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
children: [
Expanded(
flex: 5,
child: TextFormField(
style: TextStyle(color: Color(0xff2a2a2a), fontSize: 18),
keyboardType: TextInputType.name,
onChanged: (value) {
initiateSearch(value);
},
onTap: () {
setState(() {
if (hideSearchTapped) {
hideSearchTapped = false;
} else {
searchbartapped = true;
}
});
},
cursorColor: Color(0xff2a2a2a),
cursorWidth: 1.5,
decoration: InputDecoration(
hintText: "Search by Name",
prefixIcon: Visibility(
visible: searchbartapped,
child: IconButton(
icon: Icon(Icons.arrow_back),
color: Colors.black54,
onPressed: () {
hideSearchTapped = true;
searchbartapped = !searchbartapped;
queryResultSet = [];
tempSearchStore = [];
setState(() {
});
FocusScope.of(context).unfocus();
return true;
}),
),
)),
),
],
),
),
Visibility(
visible: searchbartapped,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: ListView(
padding: EdgeInsets.all(5.0),
primary: false,
shrinkWrap: true,
children: tempSearchStore.map((element) {
print(element['name']);
}).toList()),
),
),
],
);
}
}
Note: you should use lowerCamelCase to name your variable. So searchbartapped would become searchBarTapped.
I followed the flutter documentation here on how to validate multiple TextFormField at once. But in this example all the textformfields are created with same input field i.e, Name. I want that different fields can be used for different inputs such as Name, Password, Email, etc. Can someone help on how to implement the above?
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
Widget build(BuildContext context) {
return Material(
child: Center(
child: Shortcuts(
shortcuts: <LogicalKeySet, Intent>{
// Pressing enter on the field will now move to the next field.
LogicalKeySet(LogicalKeyboardKey.enter): NextFocusIntent(),
},
child: FocusTraversalGroup(
child: Form(
autovalidate: true,
onChanged: () {
Form.of(primaryFocus.context).save();
},
child: Wrap(
children: List<Widget>.generate(5, (int index) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: ConstrainedBox(
constraints: BoxConstraints.tight(Size(200, 50)),
child: TextFormField(
decoration: const InputDecoration(
icon: Icon(Icons.person),
hintText: 'What do people call you?',
labelText: 'Name *',
),
onSaved: (String value) {
// This optional block of code can be used to run
// code when the user saves the form.
},
validator: (String value) {
return value.contains('#') ? 'Do not use the # char.' : null;
},
),
),
);
}),
),
),
),
),
),
);
}
}
Make a class with hintText, labelText etc as fields , make a list of instances of this class and provide it to TextFormField :
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
final List<HintAndLabel> list = const <HintAndLabel>[HintAndLabel(labelText:'Name',hintText:"What do people call you?"),
HintAndLabel(labelText:'label',hintText:"hint"),
HintAndLabel(labelText:'label',hintText:"hint"),
HintAndLabel(labelText:'label',hintText:"hint"),
HintAndLabel(labelText:'label',hintText:"hint")];
#override
Widget build(BuildContext context) {
return Material(
child: Center(
child: Shortcuts(
shortcuts: <LogicalKeySet, Intent>{
// Pressing enter on the field will now move to the next field.
LogicalKeySet(LogicalKeyboardKey.enter): NextFocusIntent(),
},
child: FocusTraversalGroup(
child: Form(
autovalidate: true,
onChanged: () {
Form.of(primaryFocus.context).save();
},
child: Wrap(
children: List<Widget>.generate(5, (int index) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: ConstrainedBox(
constraints: BoxConstraints.tight(Size(200, 50)),
child: TextFormField(
decoration: InputDecoration(
icon: Icon(Icons.person),
hintText: list[index].hintText,
labelText: list[index].labelText,
),
onSaved: (String value) {
// This optional block of code can be used to run
// code when the user saves the form.
},
validator: (String value) {
return value.contains('#') ? 'Do not use the # char.' : null;
},
),
),
);
}),
),
),
),
),
),
);
}
}
class HintAndLabel
{
final String hintText;
final String labelText;
const HintAndLabel({this.hintText,this.labelText});
}