Flutter : Textfield in ListView.builder - flutter

Hi everyone i have some data at Cloud Firestore and a ListView.builder to load the data in it. In this ListView.builder i have Network.Image, ListTile to show the title and subtitle and another ListTile which has a Textfield to add some comment and an iconbutton. My goal is to get the text of the textfield and show as a alertDialog when the button clicked. Until now i added a controller to the textfield but whenever i type anything in the textfield it changes all the textfields. I have tried creating a List to specify a unique controller so i can stop the all of the changes in textfields but i have failed. Is there any way i can do this. All this textfields and iconbuttons must be unique so when i clicked the iconbutton i need to see the text of the typed textfield.

Try using
List<TextEditingController> _controllers = new List();
//try below in null-safe
//List<TextEditingController>? _controllers = [];
And inside your ListView.builder add a new controller for each item.
ListView.builder(
itemBuilder: (context,index){
_controllers.add(new TextEditingController());
//try below in null-safe
//_controllers!.add(TextEditingController());
//Your code here
}),
To access the controller's text you will need the index value
_controllers[index].text
Make sure to dispose of all textControllers in the end.

You might want to consider making a new type of custom Widget for each row.
You can leverage a
TextEditingController (where you call the corresponding controller
on click or the
TextField's onChanged callback (where you store the new value for
the corresponding item on change
In both cases you have a somewhat nasty list of either text controllers or String values.
Remember, ListView.builder is only going to build the items that are in or near the viewport (as you scroll).
the builder is called only for those children that are actually visible
That means that you can have the same row built multiple times (
Consider using a custom widget for each row (extend StatefulWidget)
This will alleviate the coordination involved with all the roles and it will push any resulting state further down the tree to the leaf nodes
If using a TextEditingController:
you only have one controller to worry
call _textController.dispose() in the widget's dispose() method
If using a onChanged callback (available on TextField not TextFormField)
create a String variable as state in the custom widget inputValue
handle the null cases
read from this when the button is tapped
It looks like the TextEditingController may be easiest, but I wanted to mention both options for your consideration.

ListView.builder(
shrinkWrap: true,
physics: ScrollPhysics(),
itemCount: list.length,
itemBuilder: (BuildContext context, int index) {
_controllers.add(new TextEditingController());
return Container(
child: TextField(
textAlign: TextAlign.start,
controller: _controllers[index],
autofocus: false,
keyboardType: TextInputType.text,),))

Related

Button built from ListView.builder seems to call the same function for all buttons built from the ListView

Basically, I have two nested ListView. Builders inside a third ListView builder. The two nested listview builders build two rows of custom buttons; the contents of the second row of buttons depend on the selected button in the two rows. The first row works entirely as intended, but the second row causes an issue.
Whenever a button from the second row is pressed, it affects the state of all the other buttons (and the data those lists are built on), as if calling the same function on all elements on the list. The strangest thing, though, is that it only seems to call PART of that function:
void toggleSubcategoryPanelButton(int panelNum, int subCategoryIndex) {
App()
.panelNames[panelNum]
.panelSettings
.categories[
App().panelNames[panelNum].panelSettings.activeCategoryIndex]
.activeSubcategory =
App()
.panelNames[panelNum]
.panelSettings
.categories[
App().panelNames[panelNum].panelSettings.activeCategoryIndex]
.subcategories[subCategoryIndex];
rerollName(panelNum);
saveSettingstoPrefs();
notifyListeners();
}
App() is a singleton that holds pretty much all the data for this lightweight app. The purpose of this function is to set a new index for activeSubcategory for only the 'panel' from which the function was called (given via panelNum). Instead, the function sets activeSubcategory of to the same index for all of the elements of panelNames in their categories at the current activeCategoryIndex.
Then, rerollName() seems to only be called for the panelNum provided.
The problem can be seen here. The top row's selection is set correctly.
[![Different function][1]][1]
You can (hopefully) see in this gif that the application correctly identifies the panelNum as it is displayed in the terminal (index of 1).
The ListView in question:
SizedBox(
height: 30.0,
child: ListView.builder(
shrinkWrap: true,
scrollDirection: Axis.horizontal,
physics:
const NeverScrollableScrollPhysics(),
itemCount: widget
.panelSettings
.categories[widget.panelSettings
.activeCategoryIndex]
.subcategories
.length,
itemBuilder: ((context, index) {
return Consumer(
builder: (context, value, child) {
return PanelButtonToggleable(
icon: widget
.panelSettings
.categories[widget
.panelSettings
.activeCategoryIndex]
.subcategories[index]
.icon,
tooltip: widget
.panelSettings
.categories[widget
.panelSettings
.activeCategoryIndex]
.subcategories[index]
.getName(),
buttonBehavior: () =>
_onToggleSubcategoryClick(
index),
toggled: widget
.panelSettings
.categories[widget
.panelSettings
.activeCategoryIndex]
.subcategories[index]
.getName() ==
widget
.panelSettings
.categories[widget
.panelSettings
.activeCategoryIndex]
.activeSubcategory
.getName(),
);
},
);
}),
),
),
https://github.com/trevclaridge/Name-Generator-Extension
[1]: https://i.stack.imgur.com/Z2tg8.gif
I discovered that my issue actually had nothing to do with the ListViews at all. Rather the objects pointed to by widget.panelSettings.categories were actually the same instance of a List. Thus, assigning the entry for activeSubcategory was assigning it for all the objects that pointed to the categories list.
Confirm this in your own projects using Dart's identical method. Link here.
To solve this, I created a new class Categories with an empty constructor to hold the list, so every initialization of the list was from a new object.

How to refresh ListView after Sorting in Flutter?

I have a ListView which contains my customers. This ListView currently is a Stateless Widget. After sorting the list of customers inside the parent, I need to refresh the list.
parent where the List is displayed (Parent is stateful):
MyLieferListe(
bestellungen.anzahlPositionen,
bestellungen.kunde,
notifyParent: refresh,
),
MyLieferListe (currently Stateless):
SingleChildScrollView(
child: ListView.builder(
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
itemBuilder: (context, index) {
return GestureDetector(
onTap: () {
getBeleg();
this.notifyParent(kunden[index]);
},
child: MyBestellungText(
kunden[index].kundenbezeichnung,
kunden[index].kundenNr,
"${kunden[index].strasse}, ${kunden[index].plz} ${kunden[index].ort}",
kunden[index].tourHinweis),
);
},
itemCount: kunden.length,
),
),
How to refresh the ListView of the Child which is displayed inside of the parent?
Firstly you have to make your list stateful because on changing the list. You have to call setState.
A bad solution would be to use Navigator.pushReplacement.
A good solution would be to make your list a stateful widget.
A state change triggers a rebuild in Flutter, so in order to ensure your widget rebuilds after you sort the list, you should call setState
// a state variable
List<Kunde> kunden;
onSortButtonClick() {
List<Kunde> newKunden = kunden.sort(...);
// store to the state
setState((){
kunden = newKunden;
});
}
build(WidgetContext context) {
return ListView.builder(/* use kunden, from state, in here */)
}
As you mention, your parent is already stateful. You should move bestellungen.kunde to the state variables (set it first in initState and use setState every time you change it), then it should automatically rebuild widget if you update kunden via setState.
Alternatively, you could make the child widget stateful and put kunden in the state of that widget. Theoretically, that should be slightly better since you then only rebuild the child, not the parent, but it realistically shouldn't make much difference in this case.

How do i monitor which ListView item is selected in Flutter?

In my listView i have the itemBuilder value set to a custom widget
Widget programsRowWidget(BuildContext context, List<MembershipPrograms> programs) {
return Expanded(
child: ListView.builder(
scrollDirection: Axis.horizontal,
shrinkWrap: true,
itemCount: programs.length,
itemBuilder: (context, index) {
return UsersProgramListItem(program: programs[index]);
}
),
);
In that widget the root element is a Card, whose child is an InkWell. I want to use it's onTap property to control if the item is selected or not. How do i monitor which item in the listView is selected from the listView in the parent widget shown above, and how do i allow only one item to be selected?
How do i monitor which item in the listView is selected from the listView in the parent widget shown above
You can use bool flags such as isSelected and isSelectable, which is passed to a widget in a builder function. The main aspect there is to separate logic of selection from ListView.builder to upper widget or to object of your element.
How do i allow only one item to be selected?
Use flags. When one item is selected - the flag of selectable for others can be set to false.

What is the difference between ListView and ListView.builder and can we use Listview.builder to create and validate and submit forms?

What is the difference between Listview.builder and Listview?
Can we use ListView.builder to submit forms?
I am using the Listview.builder now to create forms.
From official docs:
https://api.flutter.dev/flutter/widgets/ListView/ListView.html
ListView: Creates a scrollable, linear array of widgets from an
explicit List.
This constructor is appropriate for list views with a small number of
children because constructing the List requires doing work for every
child that could possibly be displayed in the list view instead of
just those children that are actually visible.
https://api.flutter.dev/flutter/widgets/ListView/ListView.builder.html
ListView.builder Creates a scrollable, linear array of widgets that
are created on demand. This constructor is appropriate for list views
with a large (or infinite) number of children because the builder is
called only for those children that are actually visible.
Basically, builder constructor create a lazy list. When user is scrolling down the list, Flutter builds widgets "on demand".
Default ListView constructor build the whole list at once.
In your case, default construct works fine, because you already now how many widgets should put on Column().
The main difference between ListView and ListView.builder
The ListView constructor requires us to create all items at once. This is good when list items are less and all will be seen on the screen, but if not then for long list items its not the good practice.
whereas
the ListView.Builder constructor will create items as they are scrolled onto the screen like on-demand. This is the best practice for development for List widget where items will only render only when items are going seen on the screen.
ListView.builder() builds widgets as required(when they can be seen). This process is also called "lazily rendering widgets".
To see the difference between each one go visit ListView Class.
And sure, you can create Forms with ListView.builder(), but I've found some problems trying it.
I can't put it into any ListView(), either Column(), to put them if there's any more items than just the Form().
I couldn't even add a Button at the of the ListView.builder() even using a conditional to put it when the last index is reached. Because of that, you have to use textInputAction: TextInputAction.done to perform some kind of action at onFieldSubmitted:
The best way to get the Fields data I've found was to add them all into an array when the onSaved:method is called, and I don't think that's a good way to go (maybe it is).
With that being said, that's what I used to make it work:
body: Form(
key: _key,
child: Container(
child: ListView.builder(
itemCount: 5,
itemBuilder: (context, index) {
return TextFormField(
textInputAction: TextInputAction.done,
validator: (text) {
if (text.isEmpty) {
return "The text is empty";
}
},
onFieldSubmitted: (text) {
_onSaved();
},
onSaved: (text) {
form.add(text);
},
);
},
),
),
),
void _onSaved() {
if (_key.currentState.validate()) {
_key.currentState.save();
print(form);
}
}
And the result:
I/flutter ( 7106): [fjjxjx, hxjxjcj, jxjxjfj, jfjfj, jxjxj]
This answer for those who are coming from native android background.
ListView is like listview in android
ListView.builder is recyclerView in android
In case you don't know recyclerView only render those view which are visible in screen and reuse them again and again to save resources.
RecyclerView when there is alot of data.

How to dynamically add Children to Scaffold Widget

Let's say, I have a chat screen that looks like this.
Now, when the user clicks the "Press when ready" button, the method fetchNewQuestion() is called.
My intention is that this will make a HTTP request, and display the result using
_buildUsersReply(httpResponse);
But, the problem is that this return must be made inside the current scaffold's widget as a child under the existing children, so that it is built at the bottom with the previous ones still there. The result would be like this:
You can find my complete code here.
Is this possible to be done pro-grammatically? Or do I have to change the concept of how I do this?
[Update, I now understand that my approach above is wrong and I have to use a listview builder. CurrentStatus below shows my progress towards achieving that goal.]
Current status:
I have built a list of Widgets:
List<Widget> chatScreenWidgets = [];
And on setState, I am updating that with a new Widget using this:
setState(() { chatScreenWidgets.add(_buildUsersReply("I think there were 35 humans and one horse.")); });
Now at this point, I am not sure how to pass the widget inside the scaffold. I have written some code that does not work. For instance, I tried this:
Code in the image below and in the gist here:
Just for future reference, here is what I really needed to do:
1. Create a list of widgets
List<Widget> chatScreenWidgets = [];
2. Inside my method, I needed to use a setState in order to add elements to that list. Every widget I add to this will be displayed on ths Scaffold.
`setState(() {
chatScreenWidgets.add(_buildUsersReply("Some Text"));
});`
3. And then, load that inside my Scaffold, I used an itemBuilder in order to return a list of widgets to my ListView. I already had that ListView (where I was manually adding children). Now this just returns them through the setState method inside my business logic method (in this case, fetchNewQuestion()).
body: Stack(
children: <Widget>[
Padding(
padding: EdgeInsets.only(bottom: 0),
child: new ListView.builder(
physics: BouncingScrollPhysics(),
padding: EdgeInsets.symmetric(horizontal: 25),
itemCount: chatScreenWidgets.length,
itemBuilder: (BuildContext context, int itemCount) {
return chatScreenWidgets[itemCount];
}
),
),
],
),
);`
I hope this helps future flutter engineers!
forget about the scaffold the idea is about what you really want to change, lets say it is
a list and your getting the data from an array if you update the array, then the list will update,if it is another type widgets then you can handle it in a different way i will edit this answer if you clarify what each part does in your widget as i cant read your full code.
first you have to create an object with two attributes one is the type of the row(if it is a user replay or other messages) and the second attribute is the string or the text.
now create a global list in the listview class from the above object, so you get the data from the user or even as a news and you create a new object from the above class and add your data to it and add it to the list.
item builder returns a widget so according to the the widget that you return the row will be set , so according to the data in the object call one of your functions that return the views like _buildUsersReply(string text).
if you have any other question you can ask :) if this what you need please mark it as the answer.