Why dart objects are modified when the associated variables are modified? - flutter

I want to display markers on a Flutter Map ('package:flutter_map/flutter_map.dart')
But all map markers take the same parameters when I add one.
When the user clicks on the map, a popup opens with fields to define the attributes of the marker.
These fields values are associated to class variables.
Is it because the Marker builder uses the pointers of the primitive variables and not their values?
Or it's because Dart understands primitive types as objects?
import 'package:flutter/material.dart';
import 'package:flutter_map/flutter_map.dart';
import 'package:latlong2/latlong.dart';
import 'package:positioned_tap_detector_2/positioned_tap_detector_2.dart';
///
/// Widget that allows display intervention
///
class DisplayIntervention extends StatefulWidget {
const DisplayIntervention({Key? key}) : super(key: key);
#override
_DisplayInterventionState createState() => _DisplayInterventionState();
}
class _DisplayInterventionState extends State<DisplayIntervention> {
// Initialize map controller
late final MapController mapController;
// Size of the left panel
final int leftPaneProportion = 20;
// Map settings
List<Marker> map_markers = [];
List<Map> availableColors = [
{'name': 'Red', 'value': Colors.red},
{'name': 'Green', 'value': Colors.green},
{'name': 'Blue', 'value': Colors.blue},
{'name': 'Black', 'value': Colors.black},
];
// Map capture (start capture many points)
bool mapCapture = false;
// History of taps
List<LatLng> tapHistory = [];
// ---- END DRAWER SECTION ----- //
// ---- START NEW MARKER SECTION ----- //
final _markerFormKey = GlobalKey<FormState>();
String _markerLabelController = "";
IconData _markerTypeController = Icons.directions_car;
int _markerRotationController = 0;
double _markerSizeController = 30.0;
Color _markerColorController = Colors.red;
List<Map> availableVehicles = [
{'name': 'Car', 'value': Icons.directions_car},
{'name': 'Truck', 'value': Icons.local_shipping},
];
// ---- END NEW MARKER SECTION ----- //
#override
void initState() {
super.initState();
mapController = MapController();
}
void _handleTap(TapPosition tapPosition, LatLng latlng) {
tapHistory.add(latlng);
openMarkerPopup();
}
void computeMarker(){
print("Add new marker");
map_markers.add(
Marker(
width: _markerSizeController,
height: _markerSizeController,
point: tapHistory.last,
builder: (ctx) =>
Container(
child: Icon(_markerTypeController, color: _markerColorController, size: _markerSizeController),
),
));
setState(() {
map_markers;
});
}
openMarkerPopup(){
showDialog(context: context, builder: (BuildContext context) {
return AlertDialog(
scrollable: true,
title: Text('Add marker'),
content: Padding(
padding: const EdgeInsets.all(8.0),
child: Form(
key: _markerFormKey,
child: Column(
children: <Widget>[
TextFormField(
initialValue: _markerLabelController,
onChanged: (value) {
_markerLabelController = value;
},
decoration: const InputDecoration(
labelText: 'Label',
icon: Icon(Icons.abc_rounded),
),
),
DropdownButtonFormField(
decoration: const InputDecoration(
icon: Icon(Icons.border_color),
),
value: _markerTypeController,
items: availableVehicles.map((map) {
return DropdownMenuItem(
child: Text(map['name']),
value: map['value'],
);
}).toList(),
onChanged: (value) {
setState(() {
if (value != null){
_markerTypeController = value as IconData;
}
});
},
),
DropdownButtonFormField(
decoration: const InputDecoration(
icon: Icon(Icons.brush_rounded),
),
value: _markerColorController,
items: availableColors.map((map) {
return DropdownMenuItem(
child: Text(map['name']),
value: map['value'],
);
}).toList(),
onChanged: (value) {
setState(() {
if (value != null){
_markerColorController = value as Color;
}
});
},
),
TextFormField(
initialValue: _markerSizeController.toString(),
keyboardType: TextInputType.number,
onChanged: (value) {
_markerSizeController = double.parse(value);
},
decoration: const InputDecoration(
labelText: 'Size',
icon: Icon(Icons.expand_more),
),
),
TextFormField(
initialValue: _markerRotationController.toString(),
keyboardType: TextInputType.number,
onChanged: (value) {
_markerRotationController = int.parse(value);
},
decoration: const InputDecoration(
labelText: 'Angle',
icon: Icon(Icons.zoom_in_rounded),
),
)
],
),
),
),
actions: [
ElevatedButton(
child: Text("Back"),
onPressed: () {
Navigator.of(context).pop();
}),
ElevatedButton(
child: Text("Valid"),
onPressed: () {
computeMarker();
Navigator.of(context).pop();
}),
],
);
});
}
#override
Widget build(BuildContext context) {
return Flex(
direction: Axis.horizontal,
children: [
Flexible(
flex: leftPaneProportion,
child: Container(
color: Colors.white,
child: Scaffold(
resizeToAvoidBottomInset: true,
body: ListView(
padding: EdgeInsets.only(
bottom: MediaQuery.of(context).viewInsets.bottom),
children: const <Widget>[
Card(
child: ListTile(
title: Text("Debug"),
trailing: Icon(
Icons.arrow_circle_right_outlined))),
],
))),
),
Flexible(
flex: 100 - leftPaneProportion,
child: FlutterMap(
mapController: mapController,
options: MapOptions(
center: LatLng(48.117266, -1.6777926),
zoom: 10,
onTap: _handleTap
),
layers: [
MarkerLayerOptions(
markers: map_markers
),
],
children: <Widget>[
TileLayerWidget(
options: TileLayerOptions(
urlTemplate:
'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
subdomains: ['a', 'b', 'c'],
),
),
],
),
),
],
);
}
}

The problem came from the marker builder
By giving directly the global variable the builder uses the pointer.
While by passing the value via an intermediate variable the problem is solved.

Related

Flutter : Right overflowed by 70 pixels

i m new to flutter and am trying to make a Responsive app , i have a textfield and i wanted to add a dropdown list next to it it s working but it shows a error " right overflowed by 150 pixels" even tho i m using the Expanded widget in the child . the error is in both dropdownlists
thank you for ur help in advance
import '../Classes/demandes.dart';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import '../data/menu_item.dart';
import '../Classes/menu.dart';
import '../data/logout.dart';
class Demandes extends StatefulWidget {
#override
_demande createState() => _demande();
}
class _demande extends State<Demandes> {
String dropdownValue = 'Assets';
String dropdownValue1 = 'Locations';
Future<Demandes?> submitData(String description, ASSETNUM, location,
DESCRIPTION_LONGDESCRIPTION) async {
var respone = await http.post(
Uri.parse(
'http://9080/maxrest/rest/mbo/sr/?_lid=azizl&_lpwd=max12345m&_format=json'),
body: {
"description": description,
"ASSETNUM": ASSETNUM,
"location": location,
"DESCRIPTION_LONGDESCRIPTION": DESCRIPTION_LONGDESCRIPTION,
});
var data = respone.body;
print(data);
if (respone.statusCode == 201) {
dynamic responseString = respone.body;
azizFromJson(responseString);
ScaffoldMessenger.of(context)
.showSnackBar(SnackBar(content: Text("Demande de service Cree")));
} else
return null;
ScaffoldMessenger.of(context)
.showSnackBar(SnackBar(content: Text("error")));
return null;
}
late Demandes? _demandes;
TextEditingController descriptionController = TextEditingController();
TextEditingController ASSETNUMController = TextEditingController();
TextEditingController locationController = TextEditingController();
TextEditingController DESCRIPTION_LONGDESCRIPTIONController =
TextEditingController();
Widget DescriptionTextField() {
return TextField(
decoration: InputDecoration(
// labelText: "Description",
border: OutlineInputBorder(borderRadius: BorderRadius.circular(10)),
),
controller: descriptionController,
);
}
Widget AssetTextField() {
return TextField(
decoration: InputDecoration(
// labelText: "Asset",
border: OutlineInputBorder(),
),
controller: ASSETNUMController,
);
}
Widget DeatialsTextField() {
return TextField(
decoration: InputDecoration(
// labelText: "Details",
border: OutlineInputBorder(),
),
maxLines: 10,
controller: DESCRIPTION_LONGDESCRIPTIONController,
);
}
Widget LocationTextField() {
return TextField(
decoration: InputDecoration(
// labelText: "Location",
border: OutlineInputBorder(),
),
controller: locationController,
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Creation Demande de service'),
actions: [
PopupMenuButton<MenuItem>(
onSelected: (item) => onSelected(context, item),
itemBuilder: (context) =>
[...MenuItems.itemsFirst.map(buildItem).toList()],
)
],
),
body: ListView(
padding: EdgeInsets.all(8),
// ignore: prefer_const_literals_to_create_immutables
children: [
ListTile(
title: Text("Description"),
),
DescriptionTextField(),
ListTile(
title: Text("Details"),
),
DeatialsTextField(),
ListTile(title: Text("Asset")),
Row(
children: <Widget>[
Expanded(
child: AssetTextField(),
),
const SizedBox(
width: 20,
),
Expanded(
child: DropdownButton<String>(
value: dropdownValue,
icon: const Icon(Icons.arrow_drop_down),
elevation: 16,
style: const TextStyle(color: Colors.blue),
underline: Container(
height: 2,
color: Color.fromARGB(255, 0, 140, 255),
),
onChanged: (String? newValue) {
setState(() {
dropdownValue = newValue!;
});
},
items: <String>[
'Assets',
'100014 moteur 3',
'100027 Système de freinage 1',
'12500 Overhead Crane #2',
'13110 Feeder System',
'13120 Bottom Sealing System',
'13130 Stripper System',
'13140 Conveyor System- Pkg. Dept.',
'13141 Elevator Rails And Drainpan Assembly',
'13142 Carton Escapement Assembly #2',
'13143 Chain Wash Assembly',
].map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
)),
],
),
ListTile(
title: Text("Location"),
),
Row(
children: <Widget>[
Expanded(
child: LocationTextField(),
),
const SizedBox(
width: 20,
),
Expanded(
child: DropdownButton<String>(
value: dropdownValue1,
icon: const Icon(Icons.arrow_drop_down),
elevation: 16,
style: const TextStyle(color: Colors.blue),
underline: Container(
height: 2,
color: Color.fromARGB(255, 0, 140, 255),
),
onChanged: (String? newValue) {
setState(() {
dropdownValue1 = newValue!;
});
},
items: <String>[
'Locations',
'100014 moteur 3',
'10002 7 Système de freinage 1',
'12500 Overhead Crane #2',
'13110 Feeder System',
'13120 Bottom Sealing System',
'13130 Stripper System',
'13140 Conveyor System- Pkg. Dept.',
'13141 Elevator Rails And Drainpan Assembly',
'13142 Carton Escapement Assembly #2',
'13143 Chain Wash Assembly',
].map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
)),
],
),
const SizedBox(
width: 20,
),
Padding(padding: EdgeInsets.all(10)),
ElevatedButton(
onPressed: (() async {
String description = descriptionController.text;
String ASSETNUM = ASSETNUMController.text;
String location = locationController.text;
String DESCRIPTION_LONGDESCRIPTION =
DESCRIPTION_LONGDESCRIPTIONController.text;
Demandes? data = await submitData(description, ASSETNUM,
location, DESCRIPTION_LONGDESCRIPTION);
setState(() {
_demandes = data;
});
}),
child: Text("submit "),
)
],
));
}
}
Ok the only thing that you have to write is this :
Expanded(
child: DropdownButton<String>(
value: dropdownValue,
isExpanded: true, // this
[...]
),
),
Like the documentation say :
Set the dropdown's inner contents to horizontally fill its parent.
By default this button's inner width is the minimum size of its
contents. If [isExpanded] is true, the inner width is expanded to fill
its surrounding container.

Is there any way I can reset the dynamic fields I added into a form to their previous state if user doesn't make any changes (Presses back)?

I'm trying to create a dynamic form so I used the idea of using a listview builder to create it. I was able to successfully create it but I faced that I cannot discard changes made to the form by popping it off after editing it. The two textFormField Job name and rate per hour were able to discard changes as they were using onsaved but on the checkbox I can't do that as it has onChanged which wraps setstate to change its state.
You can take a look at the video at this link to see how it functions as of now - https://vimeo.com/523847256
As you can see that it is retaining the data even after popping the page and coming back which I don't want it to. I'm looking for a way to prevent that and make the form the same as before if the user didn't press save.
I have tried to reassign the variables() in onpressed of back button but that didn't work. I also tried push replacement to the same page to reset it but that also didn't work. I think the cuprit here is the sublist and the initialValueTextFormField and initialValueCheckbox which are used declared under ListView.builder but I don't know how to fix that without affecting the dynamic list functionality.
class EditJobPage extends StatefulWidget {
const EditJobPage({Key key, this.job}) : super(key: key);
final Job job;
static Future<void> show(BuildContext context, {Job job}) async {
await Navigator.of(context, rootNavigator: true).pushNamed(
AppRoutes.editJobPage,
arguments: job,
);
}
#override
_EditJobPageState createState() => _EditJobPageState();
}
class _EditJobPageState extends State<EditJobPage> {
final _formKey = GlobalKey<FormState>();
String _name;
int _ratePerHour;
List<dynamic> _subList = [];
Set newSet = Set('', false);
#override
void initState() {
super.initState();
if (widget.job != null) {
_name = widget.job?.name;
_ratePerHour = widget.job?.ratePerHour;
_subList = widget.job?.subList;
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
elevation: 2.0,
title: Text(widget.job == null ? 'New Job' : 'Edit Job'),
leading: IconButton(
icon: Icon(Icons.clear),
onPressed: () {
Navigator.of(context).pop();
},
),
actions: <Widget>[
FlatButton(
child: const Text(
'Save',
style: TextStyle(fontSize: 18, color: Colors.white),
),
onPressed: () => _submit(),
),
],
),
body: _buildContents(),
backgroundColor: Colors.grey[200],
);
}
Widget _buildContents() {
return SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Card(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: _buildForm(),
),
),
),
);
}
Widget _buildForm() {
return Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: _buildFormChildren(),
),
);
}
List<Widget> _buildFormChildren() {
print(_subList);
return [
TextFormField(
decoration: const InputDecoration(labelText: 'Job name'),
keyboardAppearance: Brightness.light,
initialValue: _name,
validator: (value) =>
(value ?? '').isNotEmpty ? null : 'Name can\'t be empty',
onChanged: (value) {
setState(() {
_name = value;
});
},
),
TextFormField(
decoration: const InputDecoration(labelText: 'Rate per hour'),
keyboardAppearance: Brightness.light,
initialValue: _ratePerHour != null ? '$_ratePerHour' : null,
keyboardType: const TextInputType.numberWithOptions(
signed: false,
decimal: false,
),
onChanged: (value) {
setState(() {
_ratePerHour = int.tryParse(value ?? '') ?? 0;
});
},
),
Column(
children: <Widget>[
ListView.builder(
shrinkWrap: true,
itemCount: _subList?.length ?? 0,
itemBuilder: (context, index) {
String initialValueTextFormField =
_subList[index].subListTitle.toString();
bool initialValueCheckbox = _subList[index].subListStatus;
return Row(
children: [
Checkbox(
value: initialValueCheckbox,
onChanged: (bool newValue) {
setState(
() {
initialValueCheckbox = newValue;
_subList.removeAt(index);
_subList.insert(
index,
Set(initialValueTextFormField,
initialValueCheckbox));
},
);
},
),
Expanded(
child: TextFormField(
minLines: 1,
maxLines: 1,
initialValue: initialValueTextFormField,
autofocus: false,
textAlign: TextAlign.left,
onChanged: (title) {
setState(() {
initialValueTextFormField = title;
_subList.removeAt(index);
_subList.insert(
index,
Set(initialValueTextFormField,
initialValueCheckbox));
});
},
decoration: InputDecoration(
border: UnderlineInputBorder(),
labelStyle: TextStyle(
color: Colors.black,
fontWeight: FontWeight.w600,
),
filled: true,
hintText: 'Write sub List here',
),
),
),
],
);
},
),
TextButton(
onPressed: () {
setState(() {
_subList.add(newSet);
});
},
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Icon(Icons.add),
Text('Add Sub Lists'),
],
),
),
],
),
];
}
void _submit() {
final isValid = _formKey.currentState.validate();
if (!isValid) {
return;
} else {
final database = context.read<FirestoreDatabase>(databaseProvider);
final id = widget.job?.id ?? documentIdFromCurrentDate();
final job = Job(
id: id,
name: _name ?? '',
ratePerHour: _ratePerHour ?? 0,
subList: _subList);
database.setJob(job);
Navigator.of(context).pop();
}
}
}
And this is the link to the full repository of the whole flutter app in case you want to look at any other part:- https://github.com/brightseagit/dynamic_forms . Thank you.
Note - This is the edited code of this repo - https://github.com/bizz84/starter_architecture_flutter_firebase.
When assigning the list we need to use _subList = List.from(widget.job.subList) instead of _subList = widget.job.subList.
Otherwise, the changes made in _subList will also be made in job.subList .

Flutter: How to keep list data inside table even after switching screens back and forth?

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.

Can't build new widget on pressing button at run time in FormBuilder

I am trying to build a new widget every time a button is pressed.
I am using object of Global key to change the current state of Form as other local keys doesn't provide me the capability to change the state of FormBuilder
final _formKey = GlobalKey<FormBuilderState>();
Problem is when I try to create object of GlobalKey(_formKey) under listTile widget that I 'm trying to build on runtime, form builderr text fields don't work i.e appear and disappear instantly! But when create _formKey oustside the listTile widget under stateful widget, many other errors appears
i.e setState() called after dispose(), Global key used for multiple widgets etc.
Should I use here local keys i.e value,object or unique key? But they aren't providing me to change the current state of form builder!
Check my code:
class _addMenuState extends State<addMenu> {
var _price = TextEditingController();
var _itemName = TextEditingController();
var _desc = TextEditingController();
File _image;
String itemImageUrl;
List<menu> items = [];
final _formKey = GlobalKey<FormBuilderState>();
Future getImageFromGallery() async {
var image = await ImagePicker.pickImage(source: ImageSource.gallery);
setState(() {
_image = image;
print('Image Path $_image');
});
}
Future getImageFromCamera() async {
var image = await ImagePicker.pickImage(source: ImageSource.camera);
setState(() {
_image = image;
print('Image Path $_image');
});
}
Future uploadItemOfShop(BuildContext context) async {
FirebaseStorage storage = FirebaseStorage.instance;
Reference ref = storage.ref().child("${this.widget.rr.name}'s ${_itemName.text} Price ${_price.text}" + DateTime.now().toString());
if(_image.toString()==''){
Flushbar(
title: "Menu Item Image is empty",
message: "Please Add some Image first",
backgroundColor: Colors.red,
boxShadows: [BoxShadow(color: Colors.red[800], offset: Offset(0.0, 2.0), blurRadius: 3.0,)],
duration: Duration(seconds: 3),
)..show(context);
}else{
UploadTask uploadTask = ref.putFile(_image);
uploadTask.then((res) async {
itemImageUrl = await res.ref.getDownloadURL();
});
}
/* setState(() {
print("Logo uploaded");
Scaffold.of(context).showSnackBar(SnackBar(
content: Text('Your Restaurnat Logo has Uploaded'),
duration: Duration(seconds: 3),
));
});*/
}
Widget Divido() {
return Divider(
thickness: 5,
color: Colors.black45,
height: 2,
);
}
Widget listTile(int i) {
final _formKey = GlobalKey<FormBuilderState>();
return SingleChildScrollView(
child: ListTile(
title: //Code not needed here for this question as I haven't used key in it
subtitle: FormBuilder(
key: _formKey,
child: Column(
children: [
FormBuilderTextField(
controller: _itemName,
keyboardType: TextInputType.text,
name: 'item_name',
decoration: InputDecoration(labelText: 'Enter Item name'),
validator: FormBuilderValidators.compose([
FormBuilderValidators.required(context),
]),
),
FormBuilderTextField(
controller: _price,
name: 'price',
keyboardType: TextInputType.number,
decoration: InputDecoration(labelText: 'Enter Price'),
validator: FormBuilderValidators.compose([
FormBuilderValidators.required(context),
]),
),
FormBuilderTextField(
maxLength: 150,
controller: _desc,
keyboardType: TextInputType.multiline,
maxLines: null,
name: 'desc',
decoration: InputDecoration(
labelText: 'Description',
),
validator: FormBuilderValidators.compose(
[
FormBuilderValidators.required(context),
],
),
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
RaisedButton(
child: Text(
"Save",
style: TextStyle(color: Colors.white, fontSize: 16),
),
color: Colors.red,
onPressed: () {
debugPrint("null");
menu item = menu(
_itemName.text, _price.text, _desc.text, itemImageUrl);
items.add(item);
setState(() {
_count = _count + 1;
});
},
),
RaisedButton(
child: Text(
"Reset",
style: TextStyle(color: Colors.white, fontSize: 16),
),
color: Colors.grey,
onPressed: () {
_formKey.currentState.reset();
},
)
],
),
Divido()
],
),
),
selectedTileColor: Colors.red.shade300,
),
);
}
int _count = 1;
bool _showAnotherWidget = false;
#override
Widget build(BuildContext context) {
List<Widget> children = List.generate(_count, (int i) => listTile(i));
return Scaffold(
appBar: AppBar(
title: Text("Add Menu for ${this.widget.rr.name} Shop"),
),
body: SingleChildScrollView(
child: Container(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: children
),
),
)
);
}
}

how to remove widget based on it's index in flutter

I've question about how to close the appropriate widget based on the close button index. here in this image, you can see the output so here I am adding some widget using add button which located inside app bar now i've added close button inside the container when the user pressed the close button it will remove that container.
Here is the image of output :
Here is the code i've tried
class BspUnlicensedSignupPage extends StatefulWidget {
static const String routeName = "/bspUnlicensedSignup";
final BspSignupCommonModel bspSignupCommonModel;
BspUnlicensedSignupPage({
Key key,
#required this.bspSignupCommonModel,
}) : super(key: key);
#override
_BspUnlicensedSignupPageState createState() =>
_BspUnlicensedSignupPageState();
}
class _BspUnlicensedSignupPageState extends State<BspUnlicensedSignupPage> {
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
List<Object> images = List<Object>();
Future<File> _imageFile;
bool autovalidate = false;
bool informationislegitimate = false;
DateTime expirydate1 = DateTime.now();
DateTime expirydate2 = DateTime.now();
final format = DateFormat("yyyy-MM-dd");
final format2 = DateFormat("yyyy-MM-dd");
String type2 = 'Passport';
List<String> _type = <String>[
'',
'Passport',
'Driving License',
'Voter ID card',
'Ration Card',
'Aadhar',
'Other Id',
];
String type = 'Passport';
var _myWidgets = List<Widget>();
int _index = 3;
final Map<int, String> identification1Values = Map();
final Map<int, String> documentValues = Map();
final Map<int, DateTime> expiryDateValues = Map();
final Map<int, String> issuingAuthority = Map();
final Map<int, String> identificationPicturesValues = Map();
final List<TextEditingController> _documentControllers = List();
final List<TextEditingController> _issuingauthoritytype = List();
final List<TextEditingController> _expiryDate = List();
final List<TextEditingController> _issuingauthority = List();
final List<List<Object>> _identificationpictures = List();
#override
void initState() {
super.initState();
setState(() {
images.add("Add Image");
images.add("Add Image");
images.add("Add Image");
images.add("Add Image");
images.add("Add Image");
});
}
void _add() {
int keyValue = _index;
_myWidgets = List.from(_myWidgets)
..add(Column(
key: Key("$keyValue"),
children: <Widget>[
SizedBox(height: 10),
Container(
// padding: EdgeInsets.fromLTRB(18,5,18,18),
padding: EdgeInsets.all(15),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(10),
boxShadow: [
BoxShadow(
color: Colors.black12,
blurRadius: 15,
),
],
),
child: Column(
children: <Widget>[
Stack(
children: <Widget>[
Align(
alignment: Alignment.topRight,
child: GestureDetector(
child: Icon(Icons.close),
onTap: () {
print("CLose pressed");
_myWidgets.removeAt(_index);
},
),
),
SizedBox(
height: 10,
),
Column(
children: <Widget>[
SizedBox(
height: 20,
),
_buildidentificationtype1(keyValue),
_builddocumentnumber1(keyValue),
_builddate(keyValue),
_buildissuingauthority1(keyValue),
_buildidentificationpictures(keyValue),
],
),
],
)
],
),
)
],
));
setState(() => ++_index);
}
bool isClicked = false;
Widget _buildidentificationtype1(int keyValue) {
TextEditingController controller = TextEditingController();
_issuingauthoritytype.add(controller);
return FormBuilder(
autovalidate: autovalidate,
child: FormBuilderCustomField(
attribute: "Business type",
validators: [FormBuilderValidators.required()],
formField: FormField(
builder: (FormFieldState<dynamic> field) {
return InputDecorator(
decoration: InputDecoration(
prefixIcon: Icon(Icons.location_on),
labelText: "Business type",
errorText: field.errorText,
),
isEmpty: type == '',
child: new DropdownButtonHideUnderline(
child: new DropdownButton(
value: type,
isDense: true,
onChanged: (String newValue) {
setState(() {
type = controller.text = newValue;
field.didChange(newValue);
});
},
items: _type.map(
(String value) {
return new DropdownMenuItem(
value: value,
child: new Text(value),
);
},
).toList(),
),
),
);
},
)),
);
}
Widget _builddocumentnumber1(int keyValue) {
TextEditingController controller = TextEditingController();
_documentControllers.add(controller);
return new TudoTextWidget(
controller: controller,
prefixIcon: Icon(FontAwesomeIcons.idCard),
labelText: "Document Number",
validator: Validators().validateLicenseno,
onSaved: (val) {
setState(() {
documentValues[keyValue] = val;
});
// _licenseno = val;
},
);
}
Widget _builddate(int keyValue) {
TextEditingController controller = TextEditingController();
_expiryDate.add(controller);
return DateTimeField(
format: format,
autocorrect: true,
autovalidate: autovalidate,
controller: controller,
readOnly: true,
decoration: InputDecoration(
labelText: "Expiry Date",
hintText: "Expiry Date",
prefixIcon: Icon(
FontAwesomeIcons.calendar,
size: 24,
)),
onShowPicker: (context, currentValue) {
return showDatePicker(
context: context,
firstDate: DateTime(1900),
initialDate: currentValue ?? DateTime.now(),
lastDate: DateTime.now());
},
);
}
Widget _buildissuingauthority1(int keyValue) {
TextEditingController controller = TextEditingController();
_issuingauthority.add(controller);
return new TudoTextWidget(
prefixIcon: Icon(FontAwesomeIcons.idCard),
labelText: "Issuing Authority",
validator: (val) => Validators.validateName(val, "Issuing Authority"),
onSaved: (val) {
setState(() {
issuingAuthority[keyValue] = val;
});
},
controller: controller,
);
}
Widget _buildidentificationpictures(int keyValue) {
return GridView.count(
physics: NeverScrollableScrollPhysics(),
shrinkWrap: true,
crossAxisCount: 5,
childAspectRatio: 1,
children: List.generate(images.length, (index) {
if (images[index] is ImageUploadModel) {
ImageUploadModel uploadModel = images[index];
return Card(
clipBehavior: Clip.antiAlias,
child: Stack(
children: <Widget>[
Image.file(
uploadModel.imageFile,
width: 300,
height: 300,
),
Positioned(
right: 5,
top: 5,
child: InkWell(
child: Icon(
Icons.remove_circle,
size: 20,
color: Colors.red,
),
onTap: () {
setState(() {
images.replaceRange(index, index + 1, ['Add Image']);
});
},
),
),
],
),
);
} else {
return Card(
child: IconButton(
icon: Icon(Icons.add),
onPressed: () {
_onAddImageClick(index);
},
),
);
}
}),
);
}
#override
Widget build(BuildContext context) {
final appBar = AppBar(
title: Text("BSP Unlicensed Details"),
leading: IconButton(
icon: Icon(Icons.arrow_back_ios),
onPressed: () {
NavigationHelper.navigatetoBack(context);
},
),
actions: <Widget>[IconButton(icon: Icon(Icons.add), onPressed: _add)],
centerTitle: true,
);
final bottomNavigationBar = Container(
color: Colors.transparent,
height: 56,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
new FlatButton.icon(
icon: Icon(Icons.close),
label: Text('Clear'),
color: Colors.redAccent,
textColor: Colors.black,
padding: EdgeInsets.symmetric(vertical: 10, horizontal: 30),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(7),
),
onPressed: () {},
),
new FlatButton.icon(
icon: Icon(FontAwesomeIcons.arrowCircleRight),
label: Text('Next'),
color: colorStyles["primary"],
padding: EdgeInsets.symmetric(vertical: 10, horizontal: 30),
textColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(7),
),
onPressed: () async {
setState(() {
autovalidate = !autovalidate;
});
if (_formKey.currentState.validate()) {
BspSignupCommonModel model = widget.bspSignupCommonModel;
for (var i = 0; i < _myWidgets.length; i++) {
String document = _documentControllers[i].text;
String issuingAuthorityType = _issuingauthoritytype[i].text;
String expiryDate = _expiryDate[i].text;
String issuingAuthority = _issuingauthority[i].text;
// String picture = _identificationpictures[i].text;
print('Document: $document');
print('IssuingAuthorityType: $issuingAuthorityType');
print('ExpiryDate: $expiryDate');
print('IssuingAuthority: $issuingAuthority');
print('Picture: ${_identificationpictures.length}');
print(_myWidgets.length);
List<Licensed> listOfLicenses = new List<Licensed>();
Licensed licensed = new Licensed(
bspLicenseNumber: document,
bspAuthority: issuingAuthority,
bspExpiryDate: expiryDate,
bspIssuing: issuingAuthorityType);
licensed.bspLicenseNumber = _documentControllers[i].text;
licensed.bspExpiryDate = _expiryDate[i].text;
licensed.bspIssuing = _issuingauthoritytype[i].text;
licensed.bspAuthority = _issuingauthority[i].text;
listOfLicenses.add(licensed);
model.unlicensed = listOfLicenses;
}
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => BspLicensedSignupTermsPage(
bspSignupCommonModel: model)));
}
}),
],
),
);
return new Scaffold(
appBar: appBar,
bottomNavigationBar: bottomNavigationBar,
body: Container(
height: double.infinity,
width: double.infinity,
child: Form(
autovalidate: autovalidate,
key: _formKey,
child: Stack(
children: <Widget>[
Column(
children: <Widget>[
Expanded(
child: SizedBox(
child: ListView(
padding: const EdgeInsets.all(18.0),
children: _myWidgets,
),
),
),
],
)
],
)),
),
);
}
}
Edit: It seems that you will need to use a Map instead of a List because if you remove an item the indexes could change.
You could do something like this: int keyValue = ++_lastKey;where _lastKey would be a variable that you increment every time you add an item and it will work as unique identifier
Then instead of _myWidgets.removeAt(keyValue); call this _myWidgetsMap.remove(keyValue) inside a setState(), like this:
setState(() {
_myWidgetsMap.remove(keyValue);
});
And you should call the code inside _add() inside a setState()
But I recommend you to build your widgets according to a list of information, instead of a list of widgets. This way when you change the information, the widgets would adapt correctly.
Maybe you can try to draw or not draw the widget if the button is tapped or not, you can create a var in the code to handle the state of the widget and when you tap the close button change the value of the var, something like this:
bool isClosed = false;
!isClosed?MyWidget():Container()
and in the onTap() of the close button you need to include this:
setState(() {
isClosed = true;
});
I hope this help you
class _MyHomePageState extends State<MyHomePage> {
bool isClosed = false;
void Closed() {
setState(() {
isClosed = true;
});
print("CLick");
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: !isClosed?Center(
child: Container(
width: 200,
height: 200,
decoration: BoxDecoration(
color: Colors.blueAccent,
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
GestureDetector(
child: Container(
child: Icon(Icons.close),
),
onTap: (){
Closed();
},
),
Text(
'Close Here by Tap',
),
],
),
),
):Container(),
);
}
}
this code works for me
I know I'm late to the party but someone else may find this helpful.
I was faced with exactly the same problem. And I'm proud to say that after a good one hour, I found a solution!
To explain it simply:-
Use a Map<String, Widget> instead of a List (as some have rightly suggested already)
Use UniqueKey().toString() to generate a new key as you dynamically build a widget each time and add this <key, value> pair to the Map
Here's the code snippet
Map<String, MyCard> theCards = {
'0': MyCard(
isInitialCard: true,
)
};
void removeMyCard(String cIndex) {
setState(() {
print(cIndex);
substrateCards.remove(cIndex);
});
}
void addMyCard() {
setState(() {
String id = UniqueKey().toString();
theCards.putIfAbsent(
id,
() => MyCard(
onClose: () {
removeMyCard(id);
},
));
print(id);
});
}
Then, in the ListView.builder, build from the Map, like so:-
ListView.builder(
shrinkWrap: true,
itemCount: theCards.length,
itemBuilder: (BuildContext context, int index) {
String key = theCards.keys.elementAt(index);
return theCards[key];
}),
It will work, whether you delete from the middle, end or wherever.