How to make a persistent side bar with dynamic app bar actions?

I want to make a dashboard, the dashboard has persistent drawer and dynamic main page that changes depending on the selected page from the drawer ,and each page has its own appbar actions. My current approach involves making a central page that has a scaffold and a drawer, with the body being a page view with the pages of the dashboard, I have a dashboard cubit with a method called on page requested that has the index of the page , the dashboard page listens for the changes in the state and displays the requested page. The only issue here is the app bar actions , I load the app bar actions in the dashboard page based on the requested page , this creates an issue because some app bar action widgets (ie a button) needs to trigger a method in the displayed page , my solution to this was a global key but it creates tight coupling and a no rebuilds issues . current implementation : import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:go_router/go_router.dart'; import 'package:my_way_app/Features/Dashboard/presentation/manager/Agencies/my_agency_cubit.dart'; import 'package:my_way_app/Features/Dashboard/presentation/manager/shared/dashboard_cubit.dart'; import 'package:my_way_app/Features/Dashboard/presentation/pages/dashboard_stats_page.dart'; import 'package:my_way_app/Features/Dashboard/presentation/pages/service_settings.dart'; import 'package:my_way_app/Features/Dashboard/presentation/pages/voyages_management.dart'; import 'package:my_way_app/Features/Dashboard/presentation/widgets/app_bars/base_dashboard_app_bar.dart'; import 'package:my_way_app/Features/Dashboard/presentation/widgets/app_bars/voyages_app_bar.dart'; import 'package:my_way_app/Features/Dashboard/presentation/widgets/shared/side_bar/dashboard_side_bar.dart'; import 'package:my_way_app/Features/MyServices/domain/entities/my_service.dart'; import 'package:my_way_app/Shared/Widgets/Buttons/app_bar_check_button.dart'; import 'package:my_way_app/Theme/theme_shortcuts.dart'; class DashboardPage extends StatefulWidget { const DashboardPage({super.key}); @override State createState() => _DashboardPageState(); } class _DashboardPageState extends State { late MyService myService; final PageController pageController = PageController(); final GlobalKey serviceSettingsState = GlobalKey(); late final List pages; late final List sharedDashboardPages; late final List hotelDashboardPages; late final List agencyDashboardPages; @override void initState() { final targetService = context.read().myService; myService = targetService; hotelDashboardPages = []; agencyDashboardPages = [const VoyagesManagement()]; sharedDashboardPages = [ const DashboardStatsPage(), ServiceSettings(key: serviceSettingsState), ]; pages = switch (myService.type) { ServiceType.agency => [ sharedDashboardPages[0], ...agencyDashboardPages, sharedDashboardPages[1], ], ServiceType.hotel => [...sharedDashboardPages, ...hotelDashboardPages], ServiceType.restaurant => throw UnimplementedError(), ServiceType.guide => throw UnimplementedError(), }; super.initState(); } @override void dispose() { pageController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return BlocListener( listener: (context, state) { if (state.state == DashboardCubitStates.pageRequested) { final index = state.pageIndex; pageController.jumpToPage(index); context.pop(); } }, child: Scaffold( appBar: PreferredSize( preferredSize: const Size.fromHeight(kToolbarHeight), child: BlocBuilder( buildWhen: (previous, current) => current.state == DashboardCubitStates.pageRequested, builder: (context, state) { return getAppBar(context, state.url); }, ), ), drawer: const DashboardSideBar(), body: Column( children: [ Expanded( child: SafeArea( child: PageView.builder( physics: const NeverScrollableScrollPhysics(), controller: pageController, itemBuilder: (BuildContext context, int index) { return pages[index]; }, ), ), ), ], ), ), ); } PreferredSizeWidget getAppBar(BuildContext context, String url) { final textTheme = getTextTheme(context); PreferredSizeWidget targetAppBar = const BaseDashboardAppBar( title: 'Dashboard', ); final textStyle = textTheme.bodyMedium?.copyWith( fontWeight: FontWeight.w400, ); if (url.contains('stats')) { targetAppBar = const BaseDashboardAppBar(title: 'Statistics'); } if (url.contains('agencies/v

May 28, 2025 - 08:50
 0

I want to make a dashboard, the dashboard has persistent drawer and dynamic main page that changes depending on the selected page from the drawer ,and each page has its own appbar actions.

My current approach involves making a central page that has a scaffold and a drawer, with the body being a page view with the pages of the dashboard, I have a dashboard cubit with a method called on page requested that has the index of the page , the dashboard page listens for the changes in the state and displays the requested page.

The only issue here is the app bar actions , I load the app bar actions in the dashboard page based on the requested page , this creates an issue because some app bar action widgets (ie a button) needs to trigger a method in the displayed page , my solution to this was a global key but it creates tight coupling and a no rebuilds issues .

current implementation :

import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart';
import 'package:my_way_app/Features/Dashboard/presentation/manager/Agencies/my_agency_cubit.dart';
import 'package:my_way_app/Features/Dashboard/presentation/manager/shared/dashboard_cubit.dart';
import 'package:my_way_app/Features/Dashboard/presentation/pages/dashboard_stats_page.dart';
import 'package:my_way_app/Features/Dashboard/presentation/pages/service_settings.dart';
import 'package:my_way_app/Features/Dashboard/presentation/pages/voyages_management.dart';
import 'package:my_way_app/Features/Dashboard/presentation/widgets/app_bars/base_dashboard_app_bar.dart';
import 'package:my_way_app/Features/Dashboard/presentation/widgets/app_bars/voyages_app_bar.dart';
import 'package:my_way_app/Features/Dashboard/presentation/widgets/shared/side_bar/dashboard_side_bar.dart';
import 'package:my_way_app/Features/MyServices/domain/entities/my_service.dart';
import 'package:my_way_app/Shared/Widgets/Buttons/app_bar_check_button.dart';
import 'package:my_way_app/Theme/theme_shortcuts.dart';

class DashboardPage extends StatefulWidget {
  const DashboardPage({super.key});

  @override
  State createState() => _DashboardPageState();
}

class _DashboardPageState extends State {
  late MyService myService;
  final PageController pageController = PageController();

  final GlobalKey serviceSettingsState = GlobalKey();

  late final List pages;
  late final List sharedDashboardPages;

  late final List hotelDashboardPages;

  late final List agencyDashboardPages;

  @override
  void initState() {
    final targetService = context.read().myService;

    myService = targetService;
    hotelDashboardPages = [];
    agencyDashboardPages = [const VoyagesManagement()];
    sharedDashboardPages = [
      const DashboardStatsPage(),
      ServiceSettings(key: serviceSettingsState),
    ];

    pages = switch (myService.type) {
      ServiceType.agency => [
        sharedDashboardPages[0],
        ...agencyDashboardPages,
        sharedDashboardPages[1],
      ],

      ServiceType.hotel => [...sharedDashboardPages, ...hotelDashboardPages],
      ServiceType.restaurant => throw UnimplementedError(),
      ServiceType.guide => throw UnimplementedError(),
    };

    super.initState();
  }

  @override
  void dispose() {
    pageController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return BlocListener(
      listener: (context, state) {
        if (state.state == DashboardCubitStates.pageRequested) {
          final index = state.pageIndex;
          pageController.jumpToPage(index);
          context.pop();
        }
      },

      child: Scaffold(
        appBar: PreferredSize(
          preferredSize: const Size.fromHeight(kToolbarHeight),
          child: BlocBuilder(
            buildWhen:
                (previous, current) =>
                    current.state == DashboardCubitStates.pageRequested,
            builder: (context, state) {
              return getAppBar(context, state.url);
            },
          ),
        ),
        drawer: const DashboardSideBar(),
        body: Column(
          children: [
            Expanded(
              child: SafeArea(
                child: PageView.builder(
                  physics: const NeverScrollableScrollPhysics(),
                  controller: pageController,
                  itemBuilder: (BuildContext context, int index) {
                    return pages[index];
                  },
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }

  PreferredSizeWidget getAppBar(BuildContext context, String url) {
    final textTheme = getTextTheme(context);

    PreferredSizeWidget targetAppBar = const BaseDashboardAppBar(
      title: 'Dashboard',
    );
    final textStyle = textTheme.bodyMedium?.copyWith(
      fontWeight: FontWeight.w400,
    );

    if (url.contains('stats')) {
      targetAppBar = const BaseDashboardAppBar(title: 'Statistics');
    }
    if (url.contains('agencies/voyages')) {
      targetAppBar = const VoyagesAppBar();
    }
    if (url.contains('/dashboard/settings')) {
      targetAppBar = BaseDashboardAppBar(
        title: 'Settings',
        actions: [
          BlocBuilder(
            builder: (context, state) {
              return AppBarSubmitButton(
                isLoading:
                    state.myAgencyStatus ==
                    MyAgencyCubitStatus.updateAgencyLoading,
                label: 'Save',
                hasIcon: false,
                onTap: () {
                  serviceSettingsState.currentState?.onSubmit();
                },
              );
            },
          ),
        ],
      );
    }

    return targetAppBar;
  }
}

my question is how do I implement a persistent drawer with dynamic main page and app bar actions based on the page selected in the drawer as cleanly as possible ?