Search code examples
flutterbottomnavigationview

Flutter - How to change the text in bottom navigation from child widget?


I started Flutter recently and my app required bottom navigation. I have created bottom navigation and manage to access the child widget based on the tab selected.

Under the child widget there is drop down selection where I can change the bottom navigation text in one of the tabs for different selections.

I have tried a few days but still could not figure out how the child widget can change the text.

I have tried callback but cannot get it work. I have tried navigation.push - material page route but it rebuild the whole widget and my selection gone. I have also tried to use GlobalKey or Sharedpreference to capture my selection so that when it rebuild, it will use back the stored selection but I couldn't get it work.

I only wish to change the bottom navigation text in one of the text from child widget drop down selection.

Which is the best method to achieve this?


Solution

  • I would recommend you try to use the bloc pattern with a StreamBuilder. I have an example below. Regardless, in the example there is a stateful widget, a bloc, and a data class. Try to understand this code and modify it to your needs.

    import 'package:flutter/material.dart';
    import 'dart:async';
    
    class StreamScaffold extends StatefulWidget {
      @override
      _StreamScaffoldState createState() => _StreamScaffoldState();
    }
    
    class _StreamScaffoldState extends State<StreamScaffold> {
      ScaffoldDataBloc bloc;
    
      @override
      void initState() {
        super.initState();
        bloc = ScaffoldDataBloc();
      }
    
      @override
      Widget build(BuildContext context) {
        return StreamBuilder<ScaffoldDataState>(
            stream: bloc.stream, // The stream we want to listen to.
            initialData: bloc.initial(), // The initial data the stream provides.
            builder: (context, snapshot) {
              ScaffoldDataState state = snapshot.data;
    
              Widget page;
              if (state.index == 0) {
                // TODO separate this into its own widget, this is messy.
                page = Center(
                  child: Column(
                      mainAxisAlignment: MainAxisAlignment.center,
                      children: <Widget>[
                        RaisedButton(
                          onPressed: () => bloc.updateText(state,"Sales"),
                            child: Text("Set text to Sales")
    
                        ),
                        RaisedButton(
                          onPressed: () => bloc.updateText(state, "Purchases"),
                          child: Text("Set text to Purchases"),
                        )
                      ]),
                );
              }
    
              if (state.index == 1) {
                // TODO separate this into its own widget, this is messy.
                page = Center(
                    child: Column(
                        mainAxisAlignment: MainAxisAlignment.center,
                        children: <Widget>[
                      RaisedButton(
                        onPressed: () => bloc.updateText(state, "Stock"),
                        child: Text("Set text to Stock"),
                      ),
                      RaisedButton(
                        onPressed: () => bloc.updateText(state, "Budget"),
                        child: Text("Set text to Budget"),
                      )
                    ]));
              }
    
              return Scaffold(
                body: page,
                bottomNavigationBar: BottomNavigationBar(
                    currentIndex: state.index,
                    onTap: (int) => bloc.updateIndex(state, int),
                    items: [
                      BottomNavigationBarItem(
                          icon: Icon(Icons.play_arrow),
                          // Obtain the text from the state
                          title: Text(state.variableText)),
                      BottomNavigationBarItem(
                          icon: Icon(Icons.play_arrow), title: Text("Test")),
                    ]),
              );
            });
      }
    
      @override
      void dispose() {
        super.dispose();
        bloc.dispose();
      }
    }
    
    // A data class to hold the required data.
    class ScaffoldDataState {
      int index;
      String variableText;
    
      ScaffoldDataState({this.index = 0, this.variableText = "Hello"});
    }
    
    // A bloc to handle updates of the state.
    class ScaffoldDataBloc {
      StreamController<ScaffoldDataState> scaffoldDataStateController = StreamController<ScaffoldDataState>();
      Sink get updateScaffoldDataState => scaffoldDataStateController.sink;
      Stream<ScaffoldDataState> get stream => scaffoldDataStateController.stream;
    
      ScaffoldDataBloc();
    
      ScaffoldDataState initial() {
        return ScaffoldDataState();
      }
    
      void dispose() {
        scaffoldDataStateController.close();
      }
    
      // Needs to be called every time a change should happen in the UI
      // Add updated states into the Sink to get the Stream to update.
      void _update(ScaffoldDataState state) {
        updateScaffoldDataState.add(state);
      }
    
      // Specific methods for updating the different fields in the state object
      void updateText(ScaffoldDataState state, String text) {
        state.variableText = text;
        _update(state);
      }
    
      void updateIndex(ScaffoldDataState state, int index) {
        state.index = index;
        _update(state);
      }
    }
    

    Hope it helps!

    Additional Questions from comment:

    The easiest solution would be to simply pass the bloc as a parameter to the widget. Create a new dart file in your project, create a StatelessWidget there, create the code for the page in the build method. Note: it would make sense for you to separate the bloc into its own file along with the data class.

    import 'package:flutter/material.dart';
    
    // Import the file where the bloc and data class is located
    // You have to have a similar import in the parent widget. 
    // Your dart files should be located in the lib folder, hit ctrl+space for 
    // suggestions while writing an import, or alt+enter on a unimported class.
    import 'package:playground/scaffold_in_stream_builder.dart'; 
    
    
    class ChildPage extends StatelessWidget {
      final ScaffoldDataBloc bloc;
      final ScaffoldDataState state;
    
      const ChildPage({Key key, this.bloc, this.state}) : super(key: key);
    
      @override
      Widget build(BuildContext context) {
        return Container(); // TODO replace with your page
      }
    }
    

    However, if the these child widgets get their own children in separate files it would be better to use a InheritedWidget instead, with the bloc and state. This avoids "passing state down". See this article on inherited widgets