Chapter 5 - Challenge 1: Add a scroll listener - Too Soon?

I understand that a challenge is meant to stretch one’s abilities, but it feel like Chapter 5’s Challenge 1: Add a scroll listener comes too soon.

We haven’t seen initState() yet, except in a very high-level mention in Chapter 4. Actual usage of initState() doesn’t appear until Chapter 6.

Same with using the ScollController. We haven’t been introduced to them yet.

Telling readers to go read the Flutter docs doesn’t help, either, as those have no examples to help put things in context. (And, personally, are thoroughly confusing)

Yes, there is a balance trying to be struck between absolute neophyte coders and experienced developers with this book, and that is a challenge itself.

I think that at least introducing the concept before challenging the concept would be more helpful, though.

Despite work through Dart Apprentice first, I’m still trying to understand how a lot of this is supposed to be structured, so I can only imagine how absolutely fresh coders may be struggling with this.

I appreciate all the work that has gone in to this book, especially the numbered breakdowns after most of the code blocks to explain what’s happening. I’m really hoping I can finally get a handle on this promising language as a result.

Further to this, I had to “cheat” and look at the Appendix for the “step by step answers”…and still got it wrong.

Maybe it’s just me, but when working “step by step”, I work top to bottom through the code. So, following the steps in order, my code (copied from the appendix after using the IDE to convert the Stateless Widget into a Stateful Widget) looked like this:

class ExploreScreen extends StatefulWidget {
  ExploreScreen({Key? key}) : super(key: key);

  @override
  State<ExploreScreen> createState() => _ExploreScreenState();

}

class _ExploreScreenState extends State<ExploreScreen> {
  final mockService = MockFooderlichService();
  late ScrollController _controller;

  void _scrollListener() {
    // 1
    if (_controller.offset >= _controller.position.maxScrollExtent &&
        !_controller.position.outOfRange) {
      print('i am at the bottom!');
    }
    // 2
    if (_controller.offset <= _controller.position.minScrollExtent &&
        !_controller.position.outOfRange) {
      print('i am at the top!');
    }
  }

@override
  void initState() {
    super.initState();
    // 1
    _controller = ScrollController();
    // 2
    _controller.addListener(_scrollListener);
  }

  @override
  void dispose() {
    _controller.removeListener(_scrollListener);
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
   ...

This initially threw an error about the _controller not being initialized.

So, I “cheated” further and looked at the final code from the book resources. And found this:

class ExploreScreen extends StatefulWidget {
  const ExploreScreen({Key? key}) : super(key: key);
  @override
  _ExploreScreenState createState() => _ExploreScreenState();
}

class _ExploreScreenState extends State<ExploreScreen> {
  final mockService = MockFooderlichService();

  late ScrollController _controller;

  @override
  void initState() {
    super.initState();
    _controller = ScrollController();
    _controller.addListener(_scrollListener);
  }

  void _scrollListener() {
    if (_controller.offset >= _controller.position.maxScrollExtent &&
        !_controller.position.outOfRange) {
      print('reached the bottom');
    }
    if (_controller.offset <= _controller.position.minScrollExtent &&
        !_controller.position.outOfRange) {
      print('reached the top!');
    }
  }

  @override
  void dispose() {
    _controller.removeListener(_scrollListener);
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
   ...

Notice how the initState and _scrollListener sections are swapped?

While trying to recreate the error for this post, I could not, so now I am wondering if I was hit by a hot reload vs hot restart issue, but I wanted to note this a) in case someone else has the same issue, and b) to suggest this section be reworked slightly to clarify the order in which the code should be added if the order does, in fact, matter.

And, phrasing matters. In the Appendix description especially, but possibly in other areas, using phrasing like:

“Within the ExploreScreen's parent ListView, all you have to do is set the scroll controller…”
(bolding added by me for emphasis)

Can make inexperienced readers feel like they should already know this.

I know the intent is to show how (theoretically) easy this is, but it can come across differently for folks who aren’t conversant with the topic yet.

(As the resident geek and former tech support person, I have been told this myself, which is why I mention it. I have now been on the receiving end.)

Hopefully all of this helps someone.

@dionv thank you! will keep this in mind for future updates.

1 Like