1

I'm new to Flutter and have some performance concerns. For my app, I have created a custom sidebar menu, inspired by custom sidebar menu

For this purpose, I created a stateful top-level widget that acts as the parent screen. It contains a Stack widget with the navigation screen on the button, and a content screen on top. The user should be able to open/close the menu in two ways:

  1. By pressing the hamburger menu icon at the top left of the content screen (either when it is fully opened, or moved to the side as in the first pic)
  2. By swiping right when the menu is open, and left when the menu is closed.

To satisfy point 2, I added a GestureDetector on the parent screen, such that the swipes are detected in the entire screen, which animates the content screen to the side/back in full view. To satisfy point 1, I pass an onPress callBack to the content screen (which passes it to the hamburger iconButton), which also does the top level animation. However, reading the documentation (stateful performance considerations), it seems that such a top-level stateful widget can be harmful for performance, as the rebuild passes down. I can't make my content screen a const widget (which is a proposed solution) because of the callback. This is obviously suboptimal, since in the content screen, only the icon has an animated change when the menu opens (the icon changes from a hamburger to an arrow).

How can I minimize the number of rerenders in the subtree? Is there a way to pass the screen as a const widget, even though it has a callback? Or is the current approach satisfactory?

The code, as I have it currently, is as follows:

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

  @override
  _ParentScreenState createState() => _ParentScreenState();
}

class _ParentScreenState extends State<ParentScreen> {
  bool isMenuOpen = false;
  double xOffset = 0;
  double yOffset = 0;
  double rotationAngle = 0;
  double scaleFactor = 1;

  double toRadians(double degrees) => degrees * math.pi / 180.0;

  void animateMenu() {
    setState(() {
      ...
      isMenuOpen = !isMenuOpen;
    });
  }

  @override
  Widget build(BuildContext context) {
    return SafeArea(
      // Detect user swipe to navigate between the screens
      child: GestureDetector(
        onHorizontalDragEnd: (DragEndDetails details) {
          if (details.primaryVelocity != null) {
            if (details.primaryVelocity! > 0) {
              // Right swipe, close menu if open
              if (isMenuOpen) animateMenu();
            } else if (details.primaryVelocity! < 0) {
              // Left swipe, open menu if closed
              if (!isMenuOpen) animateMenu();
            }
          }
        },
        child: Scaffold(
          body: Stack(
            children: <Widget>[
              const DrawerScreen(), // Screen with navigation information
              AnimatedContainer(
                transform: Matrix4.translationValues(xOffset, yOffset, 0)
                  ..scale(scaleFactor)
                  ..rotateZ(toRadians(rotationAngle)),
                duration: const Duration(milliseconds: 300),
                child: HomeScreen( // Screen with custom content 
                  onMenuPress: animateMenu,
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

1 Answers1

1

Well, you don't need to make the parent widget a stateful widget.

First make the actual menu including it's animation it's on widget which draws over everything else.. (Similar to what you said in a Stack widget).

Then create a object (typically called a BLoC in flutter-world) which lives outside the widget tree - either a ChangeNotifier or a Stream and inject it into the stateless widgets (easiest is by using the provider package, but you can also use an InheritedWidget.

When you want to show the menu you would just change the state of this external object which will notify the menu widget to expand.

Herbert Poul
  • 4,512
  • 2
  • 31
  • 48