ListView.builder's index doesn't reset when calling setState. Found a weird fix for it. Is there a better solution? Why is this happening? - flutter

I have this listview.builder which is supposed to show some orders from an array of Order objects based on their status.
It looks kinda like this:
The List works just fine until, for some reason, when I scroll down and the viewport can only display the order with index (the one in the listview builder function) 5 and then press another category like "New", setState() is called and the whole thing rebuilds, but the builder's index starts at 5 and the listview.builder doesn't build anything from 0 - 4. I've printed the index in the builder and caught this bug, but I still don't understand why this is happening.
This is what my listview.builder code looks like:
ListView.builder(
controller: _scrollController,
shrinkWrap: true,
itemBuilder: (BuildContext context, int index) {
print("INDEX: $index");
return _showOrder(index);
},
itemCount: orders.length,
),
This is the code for _showOrder():
Widget _showOrder(int i) {
String _currOrderStatus = orders[i].orderStatus;
/// _selectedOrderStatus is just a String which changes depending on the selected category of orders.
/// e.g. "New" or "Past"
return _currOrderStatus == _selectedOrderStatus
? ShowMyOrderWidget()
: SizedBox(
/// AND FOR SOME REALLY WEIRD REASON, THIS FIXES THE PROBLEM
/// It works with any height, as long as it's not 0, but if I have a lot of them, then the
/// layout gets spaced out and messy. With such a low number, this is highly unlikely, but
/// still seems like a stupid fix.
/// Why does this work? Why is this happening? Is there a better way to fix it?
height: 0.000000001,
);
}
And I just call setState() in the onPressed() function of those buttons on top of the screen.

Changing the items inside the ListView doesn't reset scroll position.
Since you're already assigning a ScrollController to your ListView, try calling "jumpTo(0.0)" to reset it's scroll position before calling setState().
_scrollController.jumpTo(0.0);

Related

FLUTTER: PageView.builder onPage property - can i change so the index is triggered upon a FULL page swipe instead of 0.50

Using PageView.builder in Flutter, I have made a basic quiz with random repopulating questions, and some animation that I want to trigger every time a new page comes into view.
Also, of relevance, the user can only proceed to the next page once they have answered a question on each page. (Hence to control swiping, I created a ternary operator on the physics property where the onScroll boolean is set to false when the onPage method is triggered, (and changed back later by the user to true when they answer a question by pressing a button)).
So, everything is working well, except one annoying sideeffect:
The onPageChange property is "Called whenever the page in the center of the viewport changes." and the animation set for the new page, will play on the previous page as it dissapears from view, as the onState method needed for the onScroll boolean is triggered half way during the scroll causing a rebuild.
A simple fix would be if I could change the onPageChange property to trigger only once a FULL page swipe has been completed...but I cant work out how to achieve this..?
Thank you!
#override
Widget build(BuildContext context) {
return Material(
child: PageView.builder(
onPageChanged: (int i) {
print('page changed! + i');
setState(() {_canScroll = false;});
},
physics: _canScroll
? AlwaysScrollableScrollPhysics()
: NeverScrollableScrollPhysics(),
pageSnapping: true,
itemBuilder: (context, index) {
_index = index;
return WordCardWidget(
word1: _word1,
word2: _word2,
answer: answer,
animationType: _cardAnimType,
buttonSelected: buttonSelected,
);
},
));
}
}
Try using mounted property before triggering setState().
This way it will ensure widget is completely mounted before changing the state.
Leaving this up, in case it helps any future readers.
I got this to work by using itemCount property and incrementing with a local variable, hence I dont need to use the setState method on the onPage property. The users can progress through each page only after attemping to answer a question which increases the itemCount by one.
(I have amended my original post where I wrongly thought this didnt work).
Here is the relevant working code snippet for reference :)
child: PageView.builder(
itemCount: itemCount,
onPageChanged: (int i) {
print('page changed! + i');
},

Dynamic ListView of stateful widgets not working

Dears,
it could be just a mistake of mine but ...it's driving me crazy ;)
I have a ListView inside my statefulwidget:
Expanded(
child: ListView.builder(
padding: EdgeInsets.all(10.0),
itemCount: searchableFoodList.length,
itemBuilder: (BuildContext context, int index) {
print('4 - SFL - ${searchableFoodList[index].id} - ${searchableFoodList[index].name}');
return
FoodItem(
id: searchableFoodList[index].id,
baseUnit: searchableFoodList[index].baseUnit,
baseUnitS: searchableFoodList[index].baseUnitS,
baseVal: searchableFoodList[index].baseVal,
baseValCal: searchableFoodList[index].baseValCal,
mediumVal: searchableFoodList[index].mediumVal,
mediumValCal: searchableFoodList[index].mediumValCal,
name: searchableFoodList[index].name,
note: searchableFoodList[index].note
);
},
),
searchableFoodList is modified (according user selection) inside a setState().
Let say that we have a 3 FoodItem(s), so my searchableFoodList is [FoodItem-1, FoodItem-2, FoodItem-3] and everything is working fine.
If I select a single item in my list, let say "FoodItem-2", the searchableFoodList becomes [FoodItem-2] and the list displayed contains (correctly) just one item but it is FoodItem-1.
Note that I inserted a print ...and it prints "FoodItem-2"
I guess that the problem is that it is considered the "original list" and it is changed only the length of the list but the list itself is not re-generated.
I have no more ideas ...any suggestions to solve this problem?
P.S.
FoodItem is stateful widget as well (an animated one). I did something similar with using stateless widget and I didn't have any problem

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.

Why Flutter ListTile onTap seems cached?

I have two listview screen on my app. User can navigate between them using BottomNavigationBar control.
On listview.builder function, I return something like this
return ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (context, index) {
return ListTile(
title: Text('...'),
subtitle: Text('...'),
onTap: () {
...
}
)
});
I found onTap handler seems mixed between those 2 listviews.
When I open first list view, flutter serve the correct onTap,but when I switch to second listview, flutter still serving the first listview onTap.
Seems the onTap is cached by flutter (title & subtitle seems okay). Any idea?
Sample source code: https://github.com/jazarja/flutter_app
The problem is in your compararing transition values. Because you are first reversing the animation transition of current view.
In your botton_nav.dart, change this:
return aValue.compareTo(bValue);
to this:
return bValue.compareTo(aValue);
Yes the problem is that the animations haven't completed by the time the comparison is being done so the widget at the top of the stack is always 1 tab selection behind. You actually don't need to build a stack at all, just replace Center(child: _buildTransitionStack()) here with _navigationViews[_currentIndex].transition(_type, context) and it should work.