Martin Niederl
Martin Niederl

Reputation: 759

How to navigate in a bottom navigation bar from another page?

I am using a normal bottom navigation bar with a local state containing the index of the currently selected page. When tapping on one of the nav items the tapped page is displayed:

class _HomePageState extends State<HomePage> {
    int _selectedIndex = 1;

    final _widgetOptions = [
        Text('Index 0'),
        Overview(),
        Details(),
        Text('Index 3'),
    ];

    @override
    Widget build(BuildContext context) {
        return Scaffold(
            body: _widgetOptions.elementAt(_selectedIndex),
            bottomNavigationBar: BottomNavigationBar(
                ...
                currentIndex: _selectedIndex,
                onTap: onTap: (index) => _selectedIndex = index,
            ),
        );
    }
}

How to proceed if you want to navigate from inside one page to another and update the selected index of the bottom navigation bar?

Example: Page 2 is an overview list which contains among other things the ID of the list items. If you tap an item in the list, it should be navigated to the details page (3 in bottom nav bar) displaying details for the selected item and therefore the ID of the selected item should be passed.

Navigator.of(context)... can't be used, because the individual pages are items of the nav bar and therefore not routes.

Upvotes: 2

Views: 7299

Answers (3)

shau
shau

Reputation: 153

You can use Provider to manage the index of the BottomNavigationBar. Make a class to handle the indexPage and consume it in your widgets using the Consumer. Don't forget to register the ChangeNotifierProvider in the

ChangeNotifierProvider<NavigationModel> (create: (context) => NavigationModel(),),

class NavigationModel extends ChangeNotifier {
  int _pageIndex = 0;
  int get page => _pageIndex;

  changePage(int i) {
    _pageIndex = i;
    notifyListeners();
  }
}

Upvotes: 2

Marcos Boaventura
Marcos Boaventura

Reputation: 4741

You will need a better state management way. I advise you to use BLoC pattern to manage navigation changes in this widget. I will put a simplified example here about how to do this with some comments and external reference to improvements.

// An enum to identify navigation index
enum Navigation { TEXT, OVERVIEW, DETAILS, OTHER_PAGE}

class NavigationBloc {
  //BehaviorSubject is from rxdart package
  final BehaviorSubject<Navigation> _navigationController 
    = BehaviorSubject.seeded(Navigation.TEXT);
  // seeded with inital page value. I'am assuming PAGE_ONE value as initial page.

  //exposing stream that notify us when navigation index has changed
  Observable<Navigation> get currentNavigationIndex => _navigationController.stream;

  // method to change your navigation index
  // when we call this method it sends data to stream and his listener
  // will be notified about it.
  void changeNavigationIndex(final Navigation option) => _navigationController.sink.add(option);

 void dispose() => _navigationController?.close();
}

This bloc class expose a stream output currentNavigationIndex. HomeScreen will be the listener of this output which provides info about what widget must be created and displayed on Scaffold widget body. Note that the stream starts with an initial value that is Navigation.TEXT.

Some changes are required in your HomePage. Now we're using StreamBuilder widget to create and provide a widget to body property. In some words StreamBuilder is listening a stream output from bloc and when some data is received which will be a Navigation enum value we decide what widget should be showed on body.

class _HomePageState extends State<HomePage> {
  final NavigationBloc bloc = new NavigationBloc();
  int  _selectedIndex = 0;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: StreamBuilder<Navigation>(
        stream: bloc.currentNavigationIndex,
        builder: (context, snapshot){
          _selectedIndex = snapshot.data.index;

          switch(snapshot.data){
            case Navigation.TEXT:
              return Text('Index 0');

            case Navigation.OVERVIEW:
              // Here a thing... as you wanna change the page in another widget
             // you pass the bloc to this new widget for it's capable to change
             // navigation values as you desire.
              return Overview(bloc: bloc);
              //... other options bellow
            case Navigation.DETAILS:
              return Details(/* ... */);
          }
        },
      ),

      bottomNavigationBar: BottomNavigationBar(
      //...
      currentIndex: _selectedIndex,
      onTap: (index) => bloc.changeNavigationIndex(Navigation.values[index]),
      ),
    );
  }
  @override
  void dispose(){
    bloc.dispose();// don't  forgot this.
    super.dispoe();
  }
}

Since you wanna change the body widget of HomePage when you click in a specific items that are in other widget, Overview by example, then you need pass the bloc to this new widget and when you click in item you put new data into Stream that the body will be refreshed. Note that this way of send a BLoC instance to another widget is not a better way. I advise you take a look at InheritedWidget pattern. I do here in a simple way in order to not write a bigger answer which already is...

class Overview extends StatelessWidget {
  final NavigationBloc bloc;

  Overview({this.bloc});

  @override
  Widget build(BuildContext context) {
    return YourWidgetsTree(
      //...
      // assuming that when you tap on an specific item yout go to Details page.
      InSomeWidget(
        onTap: () => bloc.changeNavigationIndex(Navigation.DETAILS);
      ),
    );
  }
}

I know so much code it's a complex way to do this but it's the way. setState calls will not solve all your needs. Take a look at this article It's one of the best and the bigger ones that talks about everything that I wrote here with minimum details.

Good Luck!

Upvotes: 5

Infusion Analysts
Infusion Analysts

Reputation: 489

make some change like..

  int _currentIndex = 0;

 void onTabTapped(int index) {
setState(() {
  _currentIndex = index;
});
 }


  body: _widgetOptions[_currentIndex],

tab click..

onTap: onTabTapped,

Upvotes: -3

Related Questions