How to assign TextEditingController to dynamicaly created textfield in model and retrieve data from TextEditingController in flutter - flutter

What I actually want is to create text fields against each index of my list and display this list of text fields in expansion tile with Title 'index' of list and children are Textfields which user will create dynamically on button click so I Created list of text fields dynamically and now want to assign TextEditingController to each of textfields and retirive data
This is my Model I created
class MyModel {
List<Widget> widgets;
bool isShow = false;
MyModel({ required this.widgets, required this.isShow});
}
This is my Controller
class DummyController extends ChangeNotifier {
List<MyModel> myModelList = [];
addTextFieldToDay(var index) {
myModelList[index].widgets.add(TextField());
notifyListeners();
}
}
And here is how I am calling above function and displaying textfields
ListView.builder(
itemCount: vm.myModelList.length,
itemBuilder: ((context, index) {
return ExpansionTile(
title: Text('Day ${index + 1}'),
children: [
IconButton(
onPressed: () {
vm.addTextFieldToDay(index);
},
icon: Icon(Icons.add)),
Container(
color: Colors.white,
height: 150,
child: ListView(children:vm.myModelList[index].widgets)
),
I tried all previously asked question solutions: But the problem i faced in this is i don't know how to get value of specific index controller
i.e
List<TextEditingController> controllers = [];
TextEditingController controller = TextEditingController();
controllers.add(controller);

It here:
class Home extends StatefulWidget {
#override
State<Home> createState() => _HomeState();
}
class _HomeState extends State<Home> {
List<Widget> listData = [];
List<TextEditingController> listDataCTL = [];
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('AppBar'),
),
body: Column(
children: [
TextButton(
onPressed: () {
var ctl = TextEditingController(text: "index");
var textF = TextField(
controller: ctl,
);
listData.add(textF);
listDataCTL.add(ctl);
setState(() {});
},
child: Text("Add")),
Expanded(
child: ListView.builder(
itemCount: listData.length + 1,
itemExtent: 50,
itemBuilder: (context, index) {
if (index == 0) {
return GestureDetector(
onTap: () {
print(listDataCTL.first.text);
},
child: Container(
child: Text('Header'),
),
);
}
return Container(
child: listData[index - 1],
);
},
),
),
],
));
}
}

Here is how achieve this I remove model class and create List of dynamic type and added days to my list with key value pair
class DummyController extends ChangeNotifier {
List days = [];
int addedDay =0;
List finalMap = [];
addDaysToList(){
addedDay++;
days.add({'day$addedDay':<TextEditingController>[]});
finalMap.add({'day$addedDay':<String>[]});
notifyListeners();
}
addTextFieldsToList(var index){
days[index]['day${index+1}'].add(TextEditingController());
notifyListeners();
}}
And this is what i do in my view
Container(
child: SizedBox(
height: 500,
child: ListView.builder(
itemCount: vm.days.length,
itemBuilder: ((context, index) {
return ExpansionTile(
title: Text('Day ${index + 1}'),
children: [
IconButton(
onPressed: () {
vm.addTextFieldsToList(index);
},
icon: Icon(Icons.add)),
Container(
color: Colors.grey[300],
height: 150,
child: ListView.builder(
itemCount: vm.days[index['day${index+1}'].length,
itemBuilder: ((context, mindex) {
return TextField(
controller: vm.days[index]['day${index+1}'][mindex],);
})) ),],);
})),),),

Related

how to make click with selection on ListTile?

I have US states displayed on the screen. They are displayed using a ListView. I need to make it so that when you click on one of the states, a checkmark appears. Now in the trailing I added an icon, but when you click on one state, a checkmark appears on all. How can this be implemented?
class _AddStatePageState extends State<AddStatePage> {
static const List<String> _usaStates = [
'Alabama',
'Alaska',
'Arizona',
'Arkansas',
...
];
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: const AppBarWithSearch(
appBarTitle: 'Add State',
),
body: Padding(
padding: const EdgeInsets.only(top: 24),
child: ListView.separated(
itemCount: _usaStates.length,
itemBuilder: (context, index) {
return ListTile(
trailing: Image.asset(
Assets.assetsCheckmark,
width: 13,
height: 10,
),
title: Text(
_usaStates[index],
),
);
},
separatorBuilder: (context, index) {
return const Divider();
},
),
),
);
}
}
Something along these lines:
class _AddStatePageState extends State<AddStatePage> {
static const List<String> _usaStates = [
'Alabama',
'Alaska',
'Arizona',
'Arkansas',
...
];
static const List<bool> _usaStatesSelected = [false, false, true, ...];
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: const AppBarWithSearch(
appBarTitle: 'Add State',
),
body: Padding(
padding: const EdgeInsets.only(top: 24),
child: ListView.separated(
itemCount: _usaStates.length,
itemBuilder: (context, index) {
return ListTile(
onTap: () {
setState(() {
for(var i = 0; i < _usaStatesSelected.length; i++) {
_usaStatesSelected[i] = false;
}
_usaStatesSelected[index] = true;
});
},
trailing:
_usaStatesSelected[index] == false
? SizedBox.shrink()
: Image.asset(
Assets.assetsCheckmark,
width: 13,
height: 10,
),
title: Text(
_usaStates[index],
),
);
},
separatorBuilder: (context, index) {
return const Divider();
},
),
),
);
}
}
ListTile provide onTap method, you can use it. To show selected item, create a variable that will holds the selected index on state class.
int? _selectedIndex;
and ListTile will be
return ListTile(
onTap: () {
_selectedIndex=index;
setState(() {});
},
trailing:
_selectedIndex==index ? Icon(Icons.check) : null,
Replace Icon(Icons.check) with your image.

Passing data to another screen with Flutter Provider

I'm trying to pass the data to another screen using Provider, but it seems I'm always passing on the same data unless I sort the List and then pass the different data (meaning I'm probably switching the index by sorting the list so that is why it's passing different data now). In short, I call the API, populate the list, setting up the provider too for the next page, and on click I list out the the information from the previous screen, but the problem is I display the same item always unless I sort the list. Here is the code:
Calling the API and displaying the list:
var posts = <RideData>[];
var streamController = StreamController<List<RideData>>();
#override
void initState() {
_getRideStreamList();
super.initState();
}
_getRideStreamList() async {
await Future.delayed(Duration(seconds: 3));
var _vehicleStreamData = await APICalls.instance.getRides();
var provider = Provider.of<RideStore>(context, listen: false);
posts = await _vehicleStreamData
.map<RideData>((e) => RideData.fromJson(e))
.toList();
streamController.add(posts);
provider.setRideList(posts, notify: false);
}
bool isSwitched = true;
void toggleSwitch(bool value) {
if (isSwitched == false) {
posts.sort((k1, k2) => k1.rideId.compareTo(k2.rideId));
} else {
posts.sort((k1, k2) => k2.rideId.compareTo(k1.rideId));
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
body: SafeArea(
child: SingleChildScrollView(
scrollDirection: Axis.vertical,
child: Column(
children: [
TextButton(
child: Text('sort ascending'),
onPressed: () {
setState(() {
toggleSwitch(isSwitched = !isSwitched);
});
}),
Container(
height: 1000,
child: StreamBuilder<List<RideData>>(
initialData: posts,
stream: streamController.stream,
builder: (context, snapshot) {
return ListView.builder(
shrinkWrap: true,
itemCount: snapshot.data.length,
itemBuilder: (context, index) {
return Column(
children: [
Row(
children: [
Padding(
padding: const EdgeInsets.only(left: 15.0),
child: Text(
'Ride #${snapshot.data[index].rideId}',
),
),
FlatButton(
textColor: Colors.blue[700],
minWidth: 0,
child: Text('View'),
onPressed: () {
// here is where I pass the data to the RideInfo screen
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => RideInfo(
rideId: snapshot
.data[index].rideId,
)));
},
),
],
),
Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Text(
'${snapshot.data[index].pickupTime}',
),
Text(
'${snapshot.data[index].jobArrived}',
),
],
),
],
);
},
);
}),
),
],
),
),
),
);
}
After pressing the View button and passing the data to another screen (RideInfo):
class RideInfo extends StatefulWidget {
static const String id = 'ride_info_screen';
String rideId;
RideInfo({#required this.rideId});
#override
_RideInfoState createState() => _RideInfoState();
}
class _RideInfoState extends State<RideInfo> {
String rideID = '';
#override
void initState() {
super.initState();
setState(() {
rideID = widget.rideId;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.white,
title: Text(
'Ride #$rideID',
),
),
body: SafeArea(
child: SingleChildScrollView(
child: Consumer<RideStore>(
builder: (context, rideStore, child) {
return Column(
children: [
ListView.builder(
shrinkWrap: true,
itemCount: 1,
itemBuilder: (context, index) {
RideData rides = rideStore.getRideByIndex(index);
return Column(
children: [
Expanded(
flex: 2,
child: Column(
children: [
Text(
"PICK UP",
),
// here I display the pickUpTime but it is always the same and I wanted to display the time based on the ID
Text(
'${rides.pickupTime}AM',
),
],
),
),
],
);
}),
],
);
},
),
),
),
);
}
}
The data (pickUpTime in this case) doesn't change when I press to see the View of a single item, but like I said, when I change the order of the list with the sort method, then I get the different data.
Here is the Provider model:
class RideStore extends ChangeNotifier {
List<RideData> _rideList = [];
List<RideData> get rideList => _rideList;
setRideList(List<RideData> list, {bool notify = true}) {
_rideList = list;
if (notify) notifyListeners();
}
RideData getRideByIndex(int index) => _rideList[index];
int get rideListLength => _rideList.length;
}
How do I display the correct information based on the ID from the List that I pressed and passed in the Ride Info screen so it doesn't give back always the same data? Thanks in advance for the help!
The offending code is in RideInfo:
ListView.builder(
shrinkWrap: true,
itemCount: 1,
itemBuilder: (context, index) {
RideData rides = rideStore.getRideByIndex(index);
The index is always 1, so you are always showing the first RideData. There are various options to fix it, e.g. pass the index, or even pass the RideData, to the RideInfo constructor:
class RideInfo extends StatefulWidget {
static const String id = 'ride_info_screen';
String rideId;
final int index;
RideInfo({#required this.rideId, #required this.index, Key key})
: super(key: key) {
and:
RideData rides = rideStore.getRideByIndex(widget.index);
I have some additional comments on the code. Firstly, the ListView is serving no purpose in RideInfo, so remove it.
Secondly, there is no need to construct the streamController and to use StreamBuilder in the parent form. Your list is available in the RideStore. So your parent form could have:
Widget build(BuildContext context) {
var data = Provider.of<RideStore>(context).rideList;
...
Container(
height: 1000,
child:
// StreamBuilder<List<RideData>>(
// initialData: posts,
// stream: streamController.stream,
// builder: (context, snapshot) {
// return
ListView.builder(
shrinkWrap: true,
itemCount: data.length,
I hope these comments help.
Edit:
It is simple to edit your code to use FutureBuilder. Firstly, make _getRideStreamList return the data it read:
_getRideStreamList() async {
...
return posts;
}
Remove the call to _getRideStreamList in initState and wrap the ListView in the FutureBuilder that invokes _getRideStreamList:
Container(
height: 1000,
child: FutureBuilder(
future: _getRideStreamList(),
builder: (ctx, snapshot) {
if (!snapshot.hasData) {
return Center(child: CircularProgressIndicator());
} else {
var data = snapshot.data;
return ListView.builder(
...
);
}
},
),
),
This displays the CircularProgressIndicator while waiting for the data.
Note that this is a quick hack - you do not want to read the data everytime that the widget rebuilds. So _getRideStreamList could check if the data has already been read and just return it rather than rereading.

Flutter: How to save user's input from a ListView.builder of TextFields

I'm trying to save user's input from a ListView.builder of TextFields in a list of objects when he presses a button. Screnshot
I would like that when the user presses the button save, all the hello words were storaged as the text property of my object.
That is my code:
class Home extends StatelessWidget {
#override
Widget build(BuildContext context) {
ListData listData = Provider.of<ListData>(context, listen: false);
return Scaffold(
appBar: AppBar(
title: Text('Add Textfields'),
actions: <Widget>[
FlatButton(
color: Colors.red,
onPressed: () {},
child: Text('Save'),
),
],
),
body: ListView(
children: <Widget>[
Padding(
padding: EdgeInsets.symmetric(horizontal: 50),
child: TextFieldBuilder(),
),
SizedBox(height: 50),
FlatButton(
onPressed: () {
listData.addTextField();
},
child: Text(
'Add',
style: TextStyle(
letterSpacing: 4,
fontWeight: FontWeight.w900,
fontSize: 16,
),
),
),
],
),
);
}
}
The Builderclass:
class TextFieldBuilder extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Consumer<ListData>(
builder: (context, listData, child) {
return ListView.builder(
itemCount: listData.textFieldList.length,
itemBuilder: (context, index) {
return TextFieldWidget(index: index);
},
scrollDirection: Axis.vertical,
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
);
},
);
}
}
TextFieldWidget:
class TextFieldWidget extends StatelessWidget {
final int index;
TextFieldWidget({this.index});
#override
Widget build(BuildContext context) {
return TextField(
textAlign: TextAlign.center,
);
}
}
And the list of objects:
class TextFieldData {
String text;
TextFieldData({this.text});
}
class ListData extends ChangeNotifier {
List<TextFieldData> textFieldList = [];
void addTextField() {
textFieldList.add(TextFieldData());
notifyListeners();
}
}
I finally got the solution:
First creating a List<TextEditingController> and after a for loop in the 'Save' button. Here is the code:
class Home extends StatelessWidget {
#override
Widget build(BuildContext context) {
ListData listData = Provider.of<ListData>(context, listen: false);
return Scaffold(
appBar: AppBar(
title: Text('Add Textfields'),
actions: <Widget>[
FlatButton(
color: Colors.red,
onPressed: () {
for (int i = 0; i < listData.textFieldList.length; i++) {
listData.textFieldList[i].text = listData.controllers[i].text; // for loop
}
},
child: Text('Save'),
),
],
),
body: ListView(
children: <Widget>[
Padding(
padding: EdgeInsets.symmetric(horizontal: 50),
child: TextFieldBuilder(),
),
SizedBox(height: 50),
FlatButton(
onPressed: () {
listData.addTextField();
},
child: Text(
'Add',
style: TextStyle(
letterSpacing: 4,
fontWeight: FontWeight.w900,
fontSize: 16,
),
),
),
],
),
);
}
}
The Builder class:
class TextFieldBuilder extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Consumer<ListData>(
builder: (context, listData, child) {
return ListView.builder(
itemCount: listData.textFieldList.length,
itemBuilder: (context, index) {
//adds a controller to the controller list
listData.controllers.add(new TextEditingController());
//added the controller to the widget
return TextFieldWidget(controller: listData.controllers[index],);
},
scrollDirection: Axis.vertical,
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
);
},
);
}
}
TextFieldWidget:
//set the controller inside the widget
class TextFieldWidget extends StatelessWidget {
final TextEditingController controller;
TextFieldWidget({this.controller});
#override
Widget build(BuildContext context) {
return TextField(
textAlign: TextAlign.center,
controller: controller,
);
}
}
And the list of objects and TextEditingControllers:
class TextFieldData {
String text;
TextFieldData({this.text});
}
class ListData extends ChangeNotifier {
List<TextFieldData> textFieldList = [];
List<TextEditingController> controllers = new List(); //list of controllers
void addTextField() {
textFieldList.add(TextFieldData());
notifyListeners();
}
}

Dynamic ListView height inside another ListView

I need to make a dynamic ListView height inside another ListView. None of the answers here I have come to didn't really answer it. I've made a simple example of what I'm trying to do so you can simply copy and paste it to try it and play with it. I've got problem with sub ListView where I need to make it grow or shrink based on number of items in it (problem commented in program)
class Test extends StatefulWidget {
#override
_TestState createState() => _TestState();
}
List<List<bool>> subList = [
[true, true],
[true]
];
class _TestState extends State<Test> {
#override
Widget build(BuildContext context) {
return ListView.separated(
itemBuilder: (BuildContext context, int index) {
return Column(
children: <Widget>[
ListTile(
trailing: IconButton(
icon: Icon(Icons.add),
onPressed: () {
setState(() {
subList[index].add(true);
});
},
),
title: Text(
'item $index',
style: TextStyle(fontWeight: FontWeight.w600),
),
),
Container(
height: 100, // <--- this needs to be dynamic
child: ListView.builder(
physics: NeverScrollableScrollPhysics(),
itemBuilder: (BuildContext context, int subIndex) {
return TestRow(
text: 'sub$subIndex',
onRemove: () {
setState(() {
subList[index].removeAt(subIndex);
});
});
},
itemCount: subList[index].length,
),
)
],
);
},
separatorBuilder: (BuildContext context, int index) => Divider(),
itemCount: subList.length);
}
}
class TestRow extends StatelessWidget {
final String text;
final Function onRemove;
const TestRow({this.onRemove, this.text});
#override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.symmetric(horizontal: 20),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Text(text),
IconButton(
icon: Icon(Icons.remove),
onPressed: onRemove,
)
],
),
);
}
}
BTW I managed to make a workaround by changing height of container (commented part) to height: 50.0 * subList[index].length where 50 is height of sub title. I'm still looking for a proper way of doing it where I wouldn't need to hardcode height of the tile and calculate it
Here is video of the project with workaround how it should work
Try setting the shrinkWrap property to true and remove the container
ListView.builder(
shrinkWrap: true, //<-- Here
physics: NeverScrollableScrollPhysics(),
itemBuilder: (BuildContext context, int subIndex) {
return TestRow(
text: 'sub$subIndex',
onRemove: () {
setState(() {
subList[index].removeAt(subIndex);
});
});
},
itemCount: subList[index].length,
)
Output:

Error in the code, BuildContext in Flutter

I am making an app with different build methods so that I can make a list of items to save them on another screen when the "love heart" button is tapped. But I am getting errors in the code. I am following the Flutter Codelabs app tutorial part 2.
My code:
import 'package:aioapp2/lists.dart';
import 'package:flutter/material.dart';
class _FavoriteListState extends State<FavoriteList> {
final _suggestions = [];
final Set<Widget> _saved = Set<Widget>();
Widget _buildList() {
return ListView.builder(
itemCount: 53,
itemBuilder: (BuildContext context, int index) {
return _buildRow(_suggestions[index]);
},
);
}
Widget _buildRow(Widget website){
final bool alreadySaved = _saved.contains(website);
return Card(
child: Container(
child: ListTile(
contentPadding: EdgeInsets.symmetric(horizontal: 20, vertical: 10),
subtitle: Row(
children: <Widget>[
// Image.asset('lib/images/${images[index]}'),
],
),
),
),
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: _buildList(),
);
}
}
class FavoriteList extends StatefulWidget {
#override
_FavoriteListState createState() => _FavoriteListState();
}
The error that I'm facing is in the Image.asset() line. On typing the following line its showing red line under the "index". But it shouldn't and that's the problem! Any help?
You are attempting to reference the variable index from within the _buildRow method, but that variable doesn't exist there. Take a look at this excerpt from your code
Widget _buildList() {
return ListView.builder(
itemCount: 53,
itemBuilder: (BuildContext context, int index) { // <-- index declared here
return _buildRow(_suggestions[index]);
},
);
}
Widget _buildRow(Widget website){
final bool alreadySaved = _saved.contains(website);
return Card(
child: Container(
child: ListTile(
contentPadding: EdgeInsets.symmetric(horizontal: 20, vertical: 10),
subtitle: Row(
children: <Widget>[
// Image.asset('lib/images/${images[index]}'), // <-- index has not been declared here
],
),
),
),
);
}
This is an example of scope. The variable index is defined in the builder method within _buildList. That means the variable only exists there. You can't access it outside that method, so when you try to access it within _buildRow, you get an error.
If you want to pass the value of index to the _buildRow, you need to pass it as an argument to the _buildRow method:
Widget _buildList() {
return ListView.builder(
itemCount: 53,
itemBuilder: (BuildContext context, int index) { // <-- index declared here
return _buildRow(_suggestions[index], index); // Passing it as an argument
},
);
}
Widget _buildRow(Widget website, int index) {
final bool alreadySaved = _saved.contains(website);
return Card(
child: Container(
child: ListTile(
contentPadding: EdgeInsets.symmetric(horizontal: 20, vertical: 10),
subtitle: Row(
children: <Widget>[
Image.asset('lib/images/${images[index]}'), // <-- index has been declared in the parameter list so everything is ok here
],
),
),
),
);
}
(Your code doesn't declare images anywhere either, but I'm assuming you declare it elsewhere in code that you didn't share since you aren't reporting the error there.)
Try like this a simple workaround:
import 'package:flutter/material.dart';
void main() => runApp(
MaterialApp(
home: FavoriteList(),
)
);
class _FavoriteListState extends State<FavoriteList> {
final _suggestions = [{
'image' : 'ic_play.png'
},
{
'image' : 'ic_play.png'
},
{
'image' : 'ic_play.png'
},
{
'image' : 'ic_play.png'
}
];
final Set<Widget> _saved = Set<Widget>();
Widget _buildList() {
return ListView.builder(
itemCount: _suggestions.length,
itemBuilder: (BuildContext context, int index) {
return _buildRow(_suggestions[index]);
},
);
}
Widget _buildRow(dynamic website){
return Card(
child: Container(
child: ListTile(
contentPadding: EdgeInsets.symmetric(horizontal: 20, vertical: 10),
subtitle: Row(
children: <Widget>[
Image.asset('assets/images/${website['image']}'),
],
),
),
),
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: _buildList(),
);
}
}
class FavoriteList extends StatefulWidget {
#override
_FavoriteListState createState() => _FavoriteListState();
}
Shouldnt
Image.asset('lib/images/${images[index]}'),
be like
Image.asset('lib/images/${index}.png'), // Provide you have 0.png, 1.png .... in lib folder
Or you should had an String array names images like
String[] images=["1.png","2.png"];
=====
I cant see any variable images which you want to reference in the commented code.