I'm creating a dynamic form in which the user adds more group fields. so initially there is no form but an add button. with this button the user can add as many form fields as they need. this from is a group form consisting two TextFormField
and one DropdownButton
.
so lets say the user added 4 group forms and filled each form. but then they changed their minds and wanted to remove the second form. when they do that it removes the last index of the listview, but the value is removed correctly at the selected index. for the textfields i can create a list of controllers and dispose them. but how can i do it for the dropdown?
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const Purchased(),
);
}
}
class Purchased extends StatefulWidget {
const Purchased({Key? key}) : super(key: key);
@override
State<Purchased> createState() => _PurchasedState();
}
class _PurchasedState extends State<Purchased> {
List<UserInfo> list = [];
List<TextEditingController> textControllerList = [];
List<TextEditingController> textControllerList1 = [];
@override
void dispose() {
textControllerList.forEach((element) {
element.dispose();
});
textControllerList1.forEach((element) {
element.dispose();
});
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: FloatingActionButton(
onPressed: () {
/// every time you add new Userinfo, it will generate new FORM in the UI
list.add(UserInfo());
setState(() {}); // dont forget to call setState to update UI
},
child: const Icon(Icons.add),
),
body: Column(
children: [
Expanded(
child: ListView.builder(
shrinkWrap: true,
itemCount: list.length,
itemBuilder: ((context, index) {
return Column(
children: [
const Text('phone'),
Text(list[index].phone),
const Text('email'),
Text(list[index].email),
Text('category'),
Text(list[index].category)
],
);
})),
),
Expanded(
child: ListView.builder(
shrinkWrap: true,
itemCount: list.length,
itemBuilder: ((context, index) {
TextEditingController controller = TextEditingController();
TextEditingController controller1 = TextEditingController();
textControllerList.add(controller);
textControllerList1.add(controller1);
return MyForm(
// dont forget use the key, to make sure every MyForm is has identity. to avoid missed build
textEditingController: textControllerList[index],
textEditingController1: textControllerList1[index],
key: ValueKey(index),
//pass init value so the widget always update with current value
initInfo: list[index],
// every changes here will update your current list value
onChangePhone: (phoneVal) {
if (phoneVal != null) {
setState(() {
list[index].setPhone(phoneVal);
});
}
},
onChangeEmail: (emailVal) {
if (emailVal != null) {
list[index].setEmail(emailVal);
setState(() {});
}
},
onChangeCategory: (categoryVal) {
if (categoryVal != null) {
list[index].setCategory(categoryVal);
setState(() {});
}
},
// every changes here will update your current list value
onremove: () {
list.removeAt(index);
textControllerList.removeAt(index);
textControllerList1.removeAt(index);
setState(() {});
});
})),
)
],
),
);
}
}
class MyForm extends StatefulWidget {
final UserInfo initInfo;
final Function(String?) onChangePhone;
final Function(String?) onChangeEmail;
final Function(String?) onChangeCategory;
final TextEditingController textEditingController;
final TextEditingController textEditingController1;
final VoidCallback? onremove;
const MyForm({
super.key,
required this.initInfo,
required this.onChangePhone,
required this.onChangeEmail,
required this.onChangeCategory,
required this.onremove,
required this.textEditingController,
required this.textEditingController1,
});
@override
State<MyForm> createState() => _MyFormState();
}
class _MyFormState extends State<MyForm> {
List<UserInfo> list = <UserInfo>;
final List<String> category = [
'Manager',
'Reception',
'Sales',
'Service',
];
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.all(12),
child: Column(
children: [
IconButton(
onPressed: widget.onremove,
icon: const Icon(
Icons.remove,
)),
TextFormField(
controller: widget.textEditingController,
onChanged: widget.onChangePhone,
),
TextFormField(
controller: widget.textEditingController1,
onChanged: widget.onChangeEmail,
),
DropdownButton(
isExpanded: true,
hint: const Text(
'Select Category',
style: TextStyle(fontSize: 14),
),
icon: const Icon(
Icons.arrow_drop_down,
color: Colors.black45,
),
iconSize: 30,
items: category
.map((item) => DropdownMenuItem<String>(
value: item,
child: Text(
item,
style: const TextStyle(
fontSize: 14,
),
),
))
.toList(),
onChanged: widget.onChangeCategory)
],
),
);
}
}
class UserInfo {
///define
String _phone = '';
String _email = '';
String _category = '';
/// getter
String get phone => _phone;
String get email => _email;
String get category => _category;
///setter
void setPhone(String phone) {
_phone = phone;
}
void setEmail(String email) {
_email = email;
}
void setCategory(String category) {
_category = category;
}
}
PLEASE any help is appreciated.
CodePudding user response:
you could create a class
class GroupForm extends StatefulWidget{
TextEditingController controller = TextEditingController();
TextEditingController controller1 = TextEditingController();
List category = [];
GroupForm(this.controller ,this.controller1,this.category)
Widget build(){
return Container(
padding: const EdgeInsets.all(12),
child: Column(
children: [
IconButton(
onPressed: widget.onremove,
icon: const Icon(
Icons.remove,
)),
TextFormField(
controller: widget.textEditingController,
onChanged: widget.onChangePhone,
),
TextFormField(
controller: widget.textEditingController1,
onChanged: widget.onChangeEmail,
),
DropdownButton(
isExpanded: true,
hint: const Text(
'Select Category',
style: TextStyle(fontSize: 14),
),
icon: const Icon(
Icons.arrow_drop_down,
color: Colors.black45,
),
iconSize: 30,
items: category
.map((item) => DropdownMenuItem<String>(
value: item,
child: Text(
item,
style: const TextStyle(
fontSize: 14,
),
),
))
.toList(),
onChanged: widget.onChangeCategory)
],
),
);
}
}
and then you could create a List<GroupForm>
to add
and remove
any object.
CodePudding user response:
In order this code to work you are going to need to install the Provider Package. With this solution using provider, you dont need to worry about the controllers.
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MultiProvider(
providers: [
ChangeNotifierProvider(
create: (_) => FormsProvider(),
),
],
child: const Purchased(),
),
);
}
}
class Purchased extends StatefulWidget {
const Purchased({Key? key}) : super(key: key);
@override
State<Purchased> createState() => _PurchasedState();
}
class _PurchasedState extends State<Purchased> {
final List<String> category = [
'Manager',
'Reception',
'Sales',
'Service',
];
@override
Widget build(BuildContext context) {
return Consumer<FormsProvider>(
builder: (context, formsProvider, child) {
List<Form> formsList = formsProvider.listOfForms;
return Scaffold(
floatingActionButton: FloatingActionButton(
onPressed: () {
formsProvider
.addFormToList(DateTime.now().millisecondsSinceEpoch);
},
child: const Icon(Icons.add),
),
body: Column(
children: [
Expanded(
child: ListView.builder(
shrinkWrap: true,
itemCount: formsList.length,
itemBuilder: ((context, index) {
UserInfo formItemInfo = formsList[index].userInfo;
return Column(
children: [
const Text('phone'),
Text(formItemInfo.phone),
const Text('email'),
Text(formItemInfo.email),
const Text('category'),
Text(formItemInfo.category)
],
);
})),
),
Expanded(
child: ListView.builder(
shrinkWrap: true,
itemCount: formsList.length,
itemBuilder: ((context, index) {
Form form = formsList[index];
return Container(
padding: const EdgeInsets.all(12),
child: Column(
children: [
IconButton(
onPressed: () {
formsProvider.removeFormFromList(form);
},
icon: const Icon(
Icons.remove,
),
),
TextFormField(
onChanged: (phoneVal) {
formsProvider.setPhone(form.id, phoneVal);
},
),
TextFormField(
onChanged: (emailVal) {
formsProvider.setEmail(form.id, emailVal);
},
),
DropdownButton(
isExpanded: true,
hint: const Text(
'Select Category',
style: TextStyle(fontSize: 14),
),
icon: const Icon(
Icons.arrow_drop_down,
color: Colors.black45,
),
iconSize: 30,
items: category
.map((item) => DropdownMenuItem<String>(
value: item,
child: Text(
item,
style: const TextStyle(
fontSize: 14,
),
),
))
.toList(),
onChanged: (categoryVal) {
if (categoryVal != null) {
formsProvider.setCategory(
form.id, categoryVal);
}
},
)
],
),
);
})),
)
],
),
);
},
);
}
}
class FormsProvider extends ChangeNotifier {
List<Form> _listOfForms = [];
List<Form> get listOfForms => _listOfForms;
void addFormToList(int id) {
_listOfForms.add(
Form(id: id, userInfo: UserInfo(category: '', email: '', phone: '')));
notifyListeners();
}
void removeFormFromList(Form form) {
_listOfForms.remove(form);
notifyListeners();
}
void setEmail(int idForm, String newEmail) {
_listOfForms.firstWhere((element) => element.id == idForm).userInfo.email =
newEmail;
notifyListeners();
}
void setPhone(int idForm, String newPhone) {
_listOfForms.firstWhere((element) => element.id == idForm).userInfo.phone =
newPhone;
notifyListeners();
}
void setCategory(int idForm, String newCategory) {
_listOfForms
.firstWhere((element) => element.id == idForm)
.userInfo
.category = newCategory;
notifyListeners();
}
}
class Form {
int id;
UserInfo userInfo;
Form({
required this.id,
required this.userInfo,
});
}
class UserInfo {
String phone;
String email;
String category;
UserInfo({
this.email = '',
this.phone = '',
this.category = '',
});
}