Errata for Flutter Apprentice 3rd Edition

Creating this topic to catch any typos and bugs in the 3rd Edition of Flutter Apprentice.

Chapters 3 and 4
Fooderlich App

home.dart

bottomNavigationBar: BottomNavigationBar(
  selectedItemColor: Theme.of(context).textSelectionTheme.selectionColor,
  ...

selectedItemColor is NOT needed here, since it is already defined in fooderlich_theme.dart

So the code 100% works correctly (with the defined color from our theme file) without assigning this property for the BottomNavigationBar.

Also, textSelectionTheme.selectionColor doesn’t even make sense here, and the only reason why the code still works correctly is because
Theme.of(context).textSelectionTheme.selectionColor evaluates to null, so selectedItemColor actually gets the value from our theme file which is:
Colors.green

Kindest Regards,
Márton Urbán

Hello, I’m not sure whether a 4th version is expected as my previous errata hasn’t been evaluated yet, but I have another note/question for chapters 3 and 4.
In all 3 cards, you use BoxConstraints.expand:

return Center(
  child: Container(
    padding: const EdgeInsets.all(16),
    constraints: const BoxConstraints.expand(
      width: 350,
      height: 450,
    ),

But in this case where no min and max are given, why not simply use the Container’s width and height like this:

return Center(
  child: Container(
    padding: const EdgeInsets.all(16),
    width: 350,
    height: 450,

I tested both and both seem to give the exact same results (for all 3 cards) on phone and browser as well (even during resizing).

Is there any difference in terms of outcome between the two codes for the fooderlich example?

Chapter 6 is all about state management. Why is the class “Home” a Stateful Widget here? You are using Consumer for the whole build (Scaffold), and the app will still work perfectly converted into a Stateless Widget like this:

class Home extends StatelessWidget {
  const Home({super.key});

  static List<Widget> pages = <Widget>[
    ExploreScreen(),
    RecipesScreen(),
    const GroceryScreen(),
  ];

  @override
  Widget build(BuildContext context) {
    return Consumer<TabManager>(
      builder: (context, tabManProvider, child) {
        return Scaffold(
          appBar: AppBar(
            title: Text(
              'Fooderlich',
              style: Theme.of(context).textTheme.titleLarge,
            ),
          ),
          body: IndexedStack(
            index: tabManProvider.selectedTab,
            children: pages,
          ),
          bottomNavigationBar: BottomNavigationBar(
            selectedItemColor:
                Theme.of(context).textSelectionTheme.selectionColor,
            currentIndex: tabManProvider.selectedTab,
            onTap: (index) {
              tabManProvider.goToTab(index);
            },
            items: const [
              BottomNavigationBarItem(
                icon: Icon(Icons.explore),
                label: 'Explore',
              ),
              BottomNavigationBarItem(
                icon: Icon(Icons.book),
                label: 'Recipes',
              ),
              BottomNavigationBarItem(
                icon: Icon(Icons.list),
                label: 'To Buy',
              ),
            ],
          ),
        );
      },
    );
  }
}

I think this can confuse beginners. Using Consumer makes it possible to use a Stateless Widget in this example, and I feel like this would be kind of important to discuss in the chapter. I myself thought at first that this needs to be a Stateful widget, but it turns out, it is completely unnecessary here.

Thank you for your insight.
Márton

Is this book still being updated?

CHAPTER 6
gorcery_item_screen.dart

_currentSliderValue is declared as int, yet in the code toInt() is used twice:

Text(
  _currentSliderValue.toInt().toString(),
  style: GoogleFonts.lato(fontSize: 18.0),
),
label: _currentSliderValue.toInt().toString(),

Kindest Regards,
Márton Urbán

I already opened a separate thread about this as I’m not sure the authors check the erratas here.

In my opinion, Chapter 7 Fooderlich doesn’t actually need to be a StatefulWidget. It works perfectly converted into a StatelessWidget. I think that is because Consumer takes care of the state changes.

Yes, we update the book quite a bit, but it does take time to work on new versions

So the only reply you are addressing is the one that is not about an actual errata/bug in a thread called “Errata”.

I’m not sure I’ll continue reporting issues if they get ignored, although there are a lot of issues in this book. In a book that is supposed to teach beginners. Yet the authors don’t seem to understand basics like when a widget needs to be stateful and when it doesn’t (the book has at least three instances where the authors claim a specific widget needs to be stateful when in fact it doesn’t). And these specific cases are not a question of different coding habits, they are clear examples where Consumer takes care of the state changes, so the widget itself does not need to be Stateful. In some of the examples you specifically wrote in the book that you are changing the widget from stateless to stateful when it doesn’t need to be… How do you think that affects your apprentices’ learning progress?

There are so many other minor bugs in the book, I start to wonder how good programmers the authors actually are if none of them noticed…
And you know, the actual apprentices will not notice those, but they will believe it is right, and thus perhaps learn bad coding habits.
And all these bugs are not flutter version specific, so it’s not about outdated source codes. But you know, for beginners, minor inconsistencies can really affect the learning process in a major way. The better I get at Flutter, the more I start to realize that your book is in fact not a good resource for beginners.

We read all the errata reported. Some authors can choose not to reply immediately on the forums, but we do track and take into account all the suggestions.

If you like, feel free to point out all the misuses of StatefulWidgets you spotted. I am sure the authors will appreciate.

Just to set some expectations, we update the book periodically but not any time an issue is reported. We collect them for a bit and then we put all the fixes in a new release (paying attention to not introduce new issues).

Thanks for all the errata you reported so far.

We are working on an update but it takes time. I understand the frustration of filing an issue and not seeing an immediate effect, but a book like this is a big project to tackle.

Thanks for the comments. Definitely want to use stateless widgets whenever possible. (One of the reasons I didn’t directly comment on this is that they were not my chapters). Since we’ll be working on a new update, we’ll take into account your suggestions.

Chapter 7
main.dart

I have already pointed out why Fooderlich doesn’t need to be a Stateful widget here, but whether it is or not, why is router initialized under the Consumer here?

child: Consumer<ProfileManager>(
        builder: (context, profileManager, child) {
          ThemeData theme;
          if (profileManager.darkMode) {
            theme = FooderlichTheme.dark();
          } else {
            theme = FooderlichTheme.light();
          }

          final router = _appRouter.router;

          return MaterialApp.router(

The router gets reinitialized every time the theme is changed by the user.
Why not initizalize it here with the rest?

class FooderlichState extends State<Fooderlich> {
  late final _groceryManager = GroceryManager();
  late final _profileManager = ProfileManager();
  late final _appRouter = AppRouter(
    widget.appStateManager,
    _profileManager,
    _groceryManager,
  );
  late final router = _appRouter.router;

That way router doesn’t get reinitialized unnecessarily every time the user switches the theme.

1 Like

Chapter 19, implementing Firebase Authentication:

The proposed login() method has e.code exceptions copied from the signup() method. Per documentation they should probably be:

      if (e.code == 'user-not-found') {
        log('No user found for that email.');
      } else if (e.code == 'wrong-password') {
        log('Wrong password provided for that user.');
      }

Thanks!

Chapter 19 starter project:

in file message_list.dart
// TODO: Add _buildListItem
should be
// TODO: add _buildList

Thanks for spotting these!

Cheers.

1 Like

This topic was automatically closed after 16 minutes. New replies are no longer allowed.