sposnjak
sposnjak

Reputation: 1499

How to set currentIndex in BottomNavigationBar in Flutter from BLOC?

I would like to use a StatelessWidget with BottomNavigationBar which I would control from a BLOC. I can hook up the body of the Scaffold and onTap of the BottomNavigationBar to the BLOC (see the code). But I do not understand how one can set the currentIndex of BottomNavigationBar from BLOC (from Observable).

Is there any good solution or do I need to use StatefulWidget as in https://stackoverflow.com/a/53019841/936780 which is similar to my example.

The BLOC code:

class Bloc { 
  final _currentTabSink = PublishSubject<int>();
  final _currentTabIndex = BehaviorSubject<int>();

  Observable<int> get currentTabIndex => _currentTabIndex.stream;
  Function(int) get setTabIndex => _currentTabSink.sink.add;

  Bloc() {
    _currentTabSink.stream.pipe(_currentTabIndex);
  }

  dispose() {
    _currentTabSink.close();
    _currentTabIndex.close();
  }
}

Widget code:

class MyWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final bloc = Provider.of(context);

    final List<Widget> _children = [
      AaWidget(),
      BbWidget(),
      CcWidget(),
      DdWidget(),
      EeWidget()
    ];

    return Scaffold(
        appBar: AppBar(
          title: Text(Localizations.of(context).appName),
        ),
        body: setBody(_children, bloc), // hook to BLOC
        bottomNavigationBar: BottomNavigationBar(
          currentIndex: 1, // ?? how to hook up to BLOC ??
          onTap: (index) {
            bloc.setTabIndex(index); // hook to BLOC
            },
          items: [
            addAa(context),
            addBb(context),
            addCc(context),
            addDd(context),
            addEE(context),
          ],
        ));
  }

  Widget setBody(List<Widget> children, Bloc bloc) {
    return StreamBuilder(
      stream: bloc.currentTabIndex,
      builder: (context, AsyncSnapshot<int> snapshot) {
        if (snapshot.hasData) {
          return children[snapshot.data];
        }
      },
    );
  }

  ...
}

Upvotes: 3

Views: 2871

Answers (1)

Jordan Davies
Jordan Davies

Reputation: 10871

I think you would need to wrap your Scaffold in a StreamBuilder with an initial data set and then you can access the snapshot data. Something like this (I had to make some changes to your code so it would build for me)

class MyWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final bloc = Bloc();

    final List<Widget> _children = [
      Container(color: Colors.blue, child: Center(child: Text("1"))),
      Container(color: Colors.red, child: Center(child: Text("2"))),
      Container(color: Colors.green, child: Center(child: Text("3"))),
      Container(color: Colors.yellow, child: Center(child: Text("4"))),
      Container(color: Colors.orange, child: Center(child: Text("5"))),
    ];

    return StreamBuilder<int>(
      stream: bloc.currentTabIndex,
      initialData: 0,
      builder: (BuildContext context, AsyncSnapshot<int> snapshot) {
        if (!snapshot.hasData) {
          return Container();
        }

        return Scaffold(
          appBar: AppBar(
            title: Text("Test"),
          ),
          body: _children[snapshot.data],
          bottomNavigationBar: BottomNavigationBar(
            currentIndex: snapshot.data,
            onTap: (index) {
              bloc.setTabIndex(index);
            },
            items: _children
                .map((child) => BottomNavigationBarItem(
                    icon: Icon(Icons.add),
                    title: Text("Test"),
                    backgroundColor: Colors.black))
                .toList(),
          ),
        );
      },
    );
  }
}

This works for me, the only downside here is that you will be rebuilding the AppBar unnecessarily every time the index changes.

Upvotes: 2

Related Questions