Unnecessary rebuilds with flutter_hooks

Issue

I’m attempting to use StateNotifier, Provider, flutter_hooks, and functional_widget together.

I’m seeing rebuilds in the widget hierarchy where I am not expecting them. I’ve setup a simple use case below.

My bloc has a method updateName() which is defined as

  void updateName() {
    var name = this.state.userName + "X";
    updateState(this.state.copyWith(userName: name));
  }

As a test, I call this method when one of the MenuItems’ is pressed or when the user taps the Update Name button. My expectation is that when updateName() is pressed and the StateNotifiers state is updated, only the userAccountHeader build function will be called since it is the only one listening to the name via:

var name = useStateValue(bloc, (MenuState s) => s.userName);

However, from my logs I can see that all of the widgets in this tree are being rebuilt.

flutter: menuList.build
flutter: menuListItem.build
flutter: menuListItem.build
flutter: menuListItem.build
flutter: menuListItem.build
flutter: menuListItem.build
flutter: menuListItem.build
flutter: menuListItem.build
flutter: menuListItem.build
flutter: menuListItem.build
flutter: menuListItem.build
flutter: menuListItem.build
flutter: menuListItem.build
flutter: menuListItem.build
flutter: menuListItem.build
flutter: userAccountHeader.build
import 'package:apptree_client/components/menu/menu_bloc.dart';
import 'package:apptree_client/hooks/hooks.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:functional_widget_annotation/functional_widget_annotation.dart';

import 'menu_item_view.dart';
part 'menu.g.dart';

@hwidget
Widget menuDrawer(BuildContext context) {
  var bloc = useMenuBloc();
  return Drawer(child: menuList(context, bloc));
}

@hwidget
Widget userAccountHeader(BuildContext context, MenuBloc bloc) {
  var name = useStateValue(bloc, (MenuState s) => s.userName);

  print("userAccountHeader.build");
  return DrawerHeader(
    child: Column(
      children: [
        Text(name),
        MaterialButton(
          onPressed: () => bloc.updateName(),
          child: Text("Update name"),
        )
      ],
    ),
    decoration: BoxDecoration(
      color: Colors.blue,
    ),
  );
}

@hwidget
Widget menuList(BuildContext context, MenuBloc bloc) {
  var menuItems = useStateValue(bloc, (MenuState s) => s.menuItems);
  print("menuList.build");
  var menuItemsWidgets =
      menuItems.map((item) => menuListItem(context, item, bloc)).toList();

  return ListView(children: [
    userAccountHeader(context, bloc),
    ...menuItemsWidgets,
  ]);
}

@hwidget
Widget menuListItem(BuildContext context, MenuItemView view, MenuBloc bloc) {
  print("menuListItem.build");
  return ListTile(
    title: Text(view.title),
    onTap: () => bloc.updateName(),
  );
}

MenuBloc useMenuBloc() {
  final context = useContext();

  return useMemoized(
        () => MenuBloc(
      config: Provider.of(context, listen: false),
      appSettings: Provider.of(context, listen: false),
    ),
  );
}

R useStateValue<T, R>(StateNotifier<T> notifier, R selector(T value)) {
  // ignore: invalid_use_of_protected_member
  final state = useState<R>(selector(notifier.state));
  useEffect(() {
    return notifier.addListener((s) {
      var newVal = selector(s);
      if (!const DeepCollectionEquality().equals(newVal, state.value)) {
        state.value = newVal;
      }
    });
  }, [notifier]);
  return state.value;
}


Solution

You are using the functional_widget functions directly instead of using the generated widgets.
So instead of each widget having their own context and be able to rebuild separately you created one big widget just like when you only use functions for parts of a widget instead of widget classes.

To solve it make all the widget function calls start with a capital to use the generated widget instead

Answered By – Pieter van Loon

Answer Checked By – Terry (FlutterFixes Volunteer)

Leave a Reply

Your email address will not be published. Required fields are marked *