beamer icon indicating copy to clipboard operation
beamer copied to clipboard

How to beam to tab in BottomNavigationBar?

Open vixez opened this issue 2 years ago • 4 comments

Hello,

I would like to beam to a specific tab, but for some reason I can't get it to work. This does not do anything: Beamer.of(context) .beamToNamed(kHomeRecognitionTabRoute); It only logs in the main delegate as routes: /home/recognition

  • kHomeRoute: /home
  • kHomeRecognitionTabRoute: /home/recognition

This is my main delegate

final routerDelegate = BeamerDelegate(
  initialPath: kSplashRoute,
  routeListener: (RouteInformation routeInformation, BeamerDelegate delegate) {
    Logger().info(
      routeInformation.location ?? '',
      category: 'routes',
    );
  },
  transitionDelegate: const NoAnimationTransitionDelegate(),
  locationBuilder: (routeInformation, _) {
    if (routeInformation.location == null) {
      return NotFound();
    }

    switch (routeInformation.location) {
      case kSplashRoute:
        return SplashLocation(routeInformation);
      case kLoginDomainRoute:
        return LoginLocation(routeInformation);
    }

    if (routeInformation.location!.startsWith(kHomeRoute)) {
      return HomeLocation(routeInformation);
    }

    return NotFound();
  },
);

HomeLocation

class HomeLocation extends BeamLocation<BeamState> {
  HomeLocation(RouteInformation routeInformation) : super(routeInformation);

  @override
  List<String> get pathPatterns => [
        kHomeRoute,
      ];

  @override
  List<BeamPage> buildPages(BuildContext context, BeamState state) => [
        const BeamPage(
          key: ValueKey('home'),
          title: 'Home',
          type: BeamPageType.scaleTransition,
          child: HomeScreen(),
        )
      ];
}

HomeScreen

final beamerHomeKey = GlobalKey<BeamerState>();

final routerDelegate = BeamerDelegate(
  locationBuilder: RoutesLocationBuilder(
    routes: {
      kHomeRoute: (context, state, data) => const HomeTabs(),
      kHomeNotificationsRoute: (context, state, data) => const BeamPage(
            child: NotificationsScreen(),
            type: BeamPageType.slideRightTransition,
          ),
    },
  ),
);

class HomeScreen extends ConsumerStatefulWidget {
  const HomeScreen({super.key});

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

class HomeScreenState extends ConsumerState<HomeScreen> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: const CustomAppBar(),
      body: Beamer(
        key: beamerHomeKey,
        routerDelegate: routerDelegate,
      ),
    );
  }
}

HomeTabs

final _routerDelegates = [
  BeamerDelegate(
    initialPath: kHomeHomeTabRoute,
    routeListener:
        (RouteInformation routeInformation, BeamerDelegate delegate) {
      Logger().info(
        routeInformation.location ?? '',
        category: 'routes_home_tabs',
      );
    },
    locationBuilder: (routeInformation, _) {
      return TabHomeLocation(routeInformation);
    },
  ),
  BeamerDelegate(
    initialPath: kHomeRecognitionTabRoute,
    locationBuilder: (routeInformation, _) {
      return TabRecognitionLocation(routeInformation);
    },
  ),
  BeamerDelegate(
    initialPath: kHomeIncentivesTabRoute,
    locationBuilder: (routeInformation, _) {
      return TabIncentivesLocation(routeInformation);
    },
  ),
  BeamerDelegate(
    initialPath: kHomeMoreTabRoute,
    locationBuilder: (routeInformation, _) {
      return TabMoreLocation(routeInformation);
    },
  ),
];

class HomeTabs extends ConsumerStatefulWidget {
  const HomeTabs({Key? key}) : super(key: key);

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

class HomeState extends ConsumerState<HomeTabs> {
  int currentIndex = 0;

  @override
  Widget build(BuildContext context) {
    var tr = ref.watch(localizationProvider).translations;

    return Scaffold(
      body: IndexedStack(
        index: currentIndex,
        children: [
          Beamer(
            routerDelegate: _routerDelegates[0],
          ),
          Beamer(
            routerDelegate: _routerDelegates[1],
          ),
          Beamer(
            routerDelegate: _routerDelegates[2],
          ),
          Beamer(
            routerDelegate: _routerDelegates[3],
          ),
        ],
      ),
      bottomNavigationBar: BottomNavigationBar(
        currentIndex: currentIndex,
        items: [
          BottomNavigationBarItem(
            label: tr['global.home'],
            icon: const Icon(
              Icons.home,
            ),
          ),
          BottomNavigationBarItem(
            label: tr['global.recognitions'],
            icon: const Icon(
              Icons.handshake,
            ),
          ),
          BottomNavigationBarItem(
            label: tr['global.gifts'],
            icon: const Icon(
              Icons.card_giftcard,
            ),
          ),
          BottomNavigationBarItem(
            label: tr['global.more'],
            icon: const Icon(
              Icons.person,
            ),
          ),
        ],
        onTap: (index) {
          if (index != currentIndex) {
            setState(() => currentIndex = index);
            _routerDelegates[currentIndex].update(rebuild: false);
          }
        },
      ),
    );
  }
}

What am I doing wrong so I can navigate to the tab from anywhere?

Thanks!

vixez avatar Nov 03 '22 16:11 vixez

I had to do the same, I ended up using Bloc (Already used for other stuff in my app) I created a NavigationBloc, with a ChangeTabEvent, and listening for this event in the HomeTabs Class using BlocListener. Then you can call the same logic you got in your onTap Method

alanlanglois avatar Feb 03 '23 11:02 alanlanglois

@vixez Hi! Did you manage to find the answer?

MadGeorge avatar May 14 '23 19:05 MadGeorge

I had to do the same, I ended up using Bloc (Already used for other stuff in my app) I created a NavigationBloc, with a ChangeTabEvent, and listening for this event in the HomeTabs Class using BlocListener. Then you can call the same logic you got in your onTap Method

Hello @alanlanglois

I'm also using Bloc in my app, but I'm struggling with nested routes and beamer key placement. Could you provide a code example of your implementation?

Retr0sec7 avatar Sep 09 '23 04:09 Retr0sec7

Sure, It's a lot of file, I'll make it short if you have any struggle with my explaination let me know. I have a NavigationBloc (Using Freezed to minimize the boilerplate):

  • a NavigationEvent : const factory NavigationEvent.openSection(SectionName sectionId) = _OpenSectionEvent;
  • a NavigationState : const factory NavigationState.openSection(SectionName sectionId) = _OpenSection;
  • the NavigationBloc itself just pass the event to a state:
on<NavigationEvent>((event, emit) {
     event.when(
       openSection: (sectionId, subSectionId, directSub) {
         emit(const NavigationState.initial());
         emit(NavigationState.openSection(sectionId, subSectionId, directSub));
       }
   });

And then you have your navigation UI component :

Widget build(BuildContext context) {
   ThemeData theme = Theme.of(context);
    return Scaffold(
       // use an IndexedStack to choose which child to show
       body: IndexedStack(
         index: _currentIndex,
         sizing: StackFit.expand,
         children: [
           // use Beamer widgets as children
           Beamer(
             key: _beamerKey0,
             backButtonDispatcher: BeamerBackButtonDispatcher(
                 delegate: _routerDelegates[0]!,
                 routerDelegate: _routerDelegates[0]!,
           ),
           Beamer(
             key: _beamerKey1,
             routerDelegate: _routerDelegates[1]!,
           ),
           Beamer(
               key: _beamerKey2,
               routerDelegate: _routerDelegates[2]!,
           ),
           Beamer(
             key: _beamerKey3,
             routerDelegate: _routerDelegates[3]!,
           ),
         ],
       ),
       bottomNavigationBar: buildBottomNavigationBar(context, theme));
 }
 

 Widget buildBottomNavigationBar(BuildContext context, ThemeData theme) {
  //There you add your bloc listener
   return BlocListener<NavigationBloc, NavigationState>(
           listener: (context, state) {
             state.whenOrNull(
                 openSection: (sectionName,) {
                   int? sectionId = Const.getSectionId(sectionName); //you could also use int I prefer use const :)
                   (sectionId != null)
                       ? _handleOnTabNav(sectionId, subSection: subSection, directSub: directSub)
                       : null;
                 });
           },
         ),
         child: // your navigation bottom bar
    }
    /// And finally the method that change the tab section and the focus on nested nav
    
    _handleOnTabNav(int index) {
       if (_currentIndex != index && index >= 0 && index < _routerDelegates.length) {
        setState(() {
             _prevIndex = _currentIndex; // for back navigation if I remember well
             _currentIndex = index;
           });
  
       _routerDelegates[index]
     }  
   }
         

alanlanglois avatar Sep 09 '23 08:09 alanlanglois