ListView: fetching details with FutureBuilder results in glitchy behavior - flutter

I have a ListView and I am using ListView.builder to lazily populate the list when children get visible.
However, the items also need to render additional content which I want to fetch lazily.
To do that, I am constructing an http-get-future in the child-builder and pass it to a FutureBuilder that renders the additional content as soon as the Future completes.
However, even if I cache the http-get-futures, the whole list feels glitchy since the items are rebuild when they get visible again and the FutureBuilder renders always one waiting-frame without any data (“because there is no way to synchronously check if a Future already completed” — as the Flutter docs say).
What should I change?

I found a solution:
Instead of directly using a Future in the FutureBuilder, I put a ValueNotifier in the cache that is set inside of the Future‘s then function and a replace the FutureBuilder by a ListenableBuilder.
The ListenableBuilder does not have the issue with the empty frame...

I just resolved by using "reverse: true"
ListView.builder(
reverse: true,
...
)

Related

My ValueNotifier is not Working properly, why?

I have a GestureDetector inside a ListView and a CustomFilter widget in my Scaffold.
When I filter or change me showList in CustomFiter Widget it seems to be updated because I print the correct value from my CustomValueNotifier.
When it comes to my parent widget which is supposed to show my showList inside the GestureDetector the value is not updated at all and is at all times an empty list.
So the problem seems to be between the connection of my parent widget which hold both the filter widget, which upadtes the list, and my CustomValueNotifier, which takes the updated value but as I said before does not provide it at the widget.
Right now I also do the onnection with getter and setter so in the code im goin to show the showList will be taken the correct value from the getShowList() method. The problem is it doesnt change when the filter is being activated, on change of each filter, but after that when i press somewhere inside the gesturedetector again.
I have tried Change Notifier,Value Notifier, Provider does not seem to be what i can use in this example, setState but of course it updates on click in gestureDetector later

toggle todos checkbox trigger build function everytime

for RiverPod official todos sample, system triggers build function whenever I toggle checkbox item, is it a correct behavior ? Won't it cause performance issue? thanks.
As I know, state management has one purpose to avoid rendering widgets so often. Please correct me if any.
if you are watching your provider inside your build method then yes it rebuilds your entire build method. but if your want your the check box to only rebuild when necessary then its either (1) separates that into another class or (2) wraps that check box with Consumer Widget. something like:
Consumer(
builder(_, ref, __){
final checkboxProvider = ref.watch(yourCheckboxProvider);
return CheckBox();
}
)

ListView start from bottom of list

I'm building a chat app and I want to achieve a ListView() that loads up like its reverse property is set to true i.e showing the end of the ListView on build. The reason I don't want to set the reverse property to true is because, if the items in the ListView do not fill up the screen, they are aligned to the bottom (cannot use shrinkWrap because of performance), and also because it reverses the order of the items and to show new elements at the bottom of the list, I'd need to use a spread operator and I'm worried about performance since the Map from which I render from can grow significantly.
I have also tried using the ScrollController's jumpTo method inside a SchedulerBinding.instance!.addPostFrameCallback but the user can see the scrolling and
it also causes it to jump like it's attempting to scroll beyond what is contained in the ListView. I have also used ScrollablePositionedList and set the index to which I want to scroll, but this jumps also.
Is there any way I can achieve my aim without any drawbacks visually or performance-wise?
You have the correct idea: in the initState, jump to the end. The end of the list can be obtained from position.maxScrollExtent. When done correctly, this does not create any visual or performance drawbacks. For example:
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
_controller.jumpTo(_controller.position.maxScrollExtent);
});
}
The _controller should be the ScrollController passed to the list.

Flutter chat list update rebuild efficiency

I'm new to Flutter and I'm trying to build a chat application and I've watched several tutorials. To view the chat messages list/history, almost every tutorial is doing something like this: (I'm shortening the code to get to the point)
List<Widget> messages = api.listOfMessages();
return Column(children: messages);
Now every time there's a new message, messages is updated and the column is re-built. I gotta say the word "rebuild" sounds an expensive procedure to me. Say 2 users have been chatting 500 lines. Now every time a new message is coming, 500 lines are getting rebuilt over an over.
I thought about putting an empty widget at the end of the list. So when a new message arrives, I just insert it to that empty widget and have that to rebuild only:
List<Widget> messages = api.listOfMessages();
return Column(children: [...messages, EmptyWidgetForNewMessage()];
But that looks like a hack and will cause a lot of nested widgets, because every new message must also insert another EmptyWidget etc...
How can I avoid rebuilding previous messages and only insert the new one to the view? (or rebuilding the entire list is not that big a deal?)
You can use sliver widget to build the messages that are visible in viewport(or within cacheExtent).
Like ListView.build, ListView.separated, from the doc of ListView.build:
if the list view's children are
created in advance, or all at once when the [ListView] itself is created,
it is more efficient to use the [ListView] constructor. Even more efficient, however, is to create the instances on demand using this constructor's itemBuilder callback.
Also, we will not fetch all messages from server at once. Instead we will fetch them in batches with query like ?page=1&size=20.
Note: There is a known issue with this widget, avoid using shrinkWrap: true if possible. See this issue
couple of improvements that you could target
Use ListView builder constructor instead of the column, only the children widget that are currently visible in the screen will be rendered whereas in Column widget all of its children will be rendered. Also ListView should be your preferred widget, because the Column widget is not scrollable, and there are chances of overflow exception, if the message list length is huge.
Use const constructor for all type of widgets returned by api.listOfMessages(), this will allow the compiler to reuse any rendered widget, meaning every time when a state change happens(in you case arrival of a new message) the entire tree is not re-rendered, the renderer will have the luxury of re using previously built message widget.
These two suggestions should take care of any performance bottlenecks, in short we would be rendering only the visible children widgets with ListView and we will be re reusing already rendered widget with the help of const constructor.

Put ListView in/out of Expanded based on how big the ListView is going to be

I have a ListView that might be big / small, depends on result of API call. What I want to do is if the result is too big, put this ListView inside Expanded.
The client could be anything, phone or tab, all with different sizes, both horizontal and vertical orientation supported.
How do I detect the size of the ListView before it gets built?
if (MyListView.height > this.container.height) { // ListView isn't built yet, so how can I know the size?
return Expanded(child: MyListView);
}
return MyListView();
// MyListView is just a ListView inside a Container
API calls should be made inside initState function so that you don't hit the endpoint with each rebuild. (which can happen many times) You can create an empty future object in your statefull widget and in the initState function assign your api call to the future object. In build method add a FutureBuilder widget to control the state of the api call. (loading, errors? etc.) Once you get the data just check the length of the list and depending on that you can decide what to do:
data.length < 20 ? MyListView() : Expanded (child: MyListView())