Is there any reason to use a StatefullWidget to implement the solution for the first challenge in Chapter 5?
I have simply used a local variable for ScrollController in the build() method of the ExploreScreenStatelessWidget and it worked perfectly, but as I’m new to Flutter I’m not sure of the tradeoffs of this solution compared to the book solution.
Could anyone give an explanation, please?
this is how my solution look:
class ExploreScreen extends StatelessWidget {
final fooderlichService = MockFooderlichService();
ExploreScreen({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
final scrollController = ScrollController();
scrollController.addListener(() {
final minPos = scrollController.position.minScrollExtent;
final maxPos = scrollController.position.maxScrollExtent;
if (scrollController.offset == minPos) {
print("I'm at the top");
}
if (scrollController.offset == maxPos) {
print("I'm at the bottom");
}
});
return FutureBuilder(
future: fooderlichService.getExploreData(),
builder: (context, AsyncSnapshot<ExploreData> snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
final recipes = snapshot.data?.todayRecipes ?? [];
final posts = snapshot.data?.friendPosts ?? [];
return ListView(
controller: scrollController,
children: [
TodayRecipeListView(recipes: recipes),
FriendPostListView(posts: posts),
],
);
} else {
return const Center(
child: CircularProgressIndicator(),
);
}
},
);
}
}
@alilosoft if you do this make sure you dispose your scrollController.
Placing the scrollController within the build method itself will create a new instance, and add a brand new listener to the scroll controller. So you will have many scroll controllers.
Good idea to only have one, and dispose the controller when the widget is destroyed.
I’m coming from a Java background, and as far as I know in Java the Garbage collector will take care of unrefrenced objects, I don’t know if there is such mechanism in Fluttet/Dart?
You might want to think about whether you need to use a Stateful Widget vs a Stateless Widget for whatever you are building.
If you are using a statefulWidget, then keeping the scrollController in the state would make sense, and you should use the dispose() method to clean up any listeners of the scroll controller. This is recommended practice.
If you use a StatelessWidget you will lose the scroll controller every time the UI gets rebuilt. Stateful widgets keep the state across rebuilds, so it’s also more efficient.
Here’s an exercise for you
Play with the memory view on devtools, maybe you can find out more information about how many scrollController instances you see!
My takeaway (I may be wrong), is that Flutter/Dart GC is optimized for short lived objects, this is why Widgets are created and destroyed frequently without impacting the performance.
In the context of my question, I still prefer using a Stateless Widget to solve the problem of Scroll Controller listener, as it’s a very simple object and let the GC clean up the memory instead of using the arguably complex solution of Stateful Widget and override Lifecyle methods (initState() and destroy()).
One more optimization I did for the shared solution above, is that I moved the ScrollController instantiation out of the build() method, so it wont be created every time the widget get rebuilt.
A widget build method might be called dozens of times a second. Perhaps this isn’t well understood… 60 fps?
Given this, it isn’t correct to needlessly construct and wire up complex objects (and then make them available for destruction) within that method. Incorporating a widget instead allows the Framework to cache and reuse that which doesn’t change between those (potentially many) method calls.
Imagine if your approach was used throughout a widget tree containing thousands of widgets?
Many thanks for the replay @jbadda
Indeed, I wasn’t aware of this fact when I first asked the question, but thankfully I realized my mistake and understood how the build() method works, I changed my solution to something else (hopefully better),
I didn’t want to use a StatefulWidget because I don’t understand why we need to dispose() the controller manually, as it should be cleaned by the GC when the widget is disposed, in the code bellow _controller = MyScrollController(); will not be referenced after ExploreScreen is disposed.
Maybe I miss something, I really want an answer for that question
Well perhaps consider that if you add no listeners to your controller (or create some other custom long lived object referenced to it), you need no dispose method offering you the opportunity to override the default and tidy up things you’ve used or created.
However you do. You add a listener. Where is that listener removed so that the underlying ChangeNotifier might have no listeners attached so it can be garbage collected?
In other word, how can your controller be garbage collected with a listener referenced to it? How about the widget that owns the controllers?
Thanks for the insights, I’m exploring and I hope all this will make sense to me soon
I have just checked the code, and found that class ScrollController extends ChangeNotifier so basically A ScrollController object is a ChangeNotifier object, that have _listeners as member field defined as List<VoidCallback?> _listeners = List<VoidCallback?>.filled(0, null);
As I understand, the ChangeNotifier object (i.e the ScrollController) holds a reference to the list of listeners, and not the inverse, so the listeners will not prevent the object from being garbage collected? am I wrong? is there any thing special about ChangeNotifier objects? like being referenced from the framework?