Flutter – How to change the body widget of parent, while passing a value, from child?

Issue

So, I have a "home" page, that has a scaffold, appbar, body, and bottomNavigationBar.
Body is loaded as widget, from a list, depending on what’s clicked on bottomNavigationBar.
I’d like to be able to press something in the child widget currently loaded in parent body, and change the parent’s body widget to another child widget, while passing a value to the child widget as well.
Think of it like pressing a link in an iframe in a browser, just the iframe changes and you can pass a value like ?id=12.

This is the "home" page:

class HomePage extends StatefulWidget {
  @override
  _HomePageState createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  int _selectedIndex = 0;
  final List<Widget> _children = [
    Page1(),
    Page2(),
    Page3(),
    Page4()
  ];
  void _onItemTapped(int index) {
    setState(() {
      _selectedIndex = index;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
              actions: <Widget>[
                TextButton(
                  onPressed: () {},
                  child: Text("Log Out", style: TextStyle(color: Colors.white)),
                ),
              ],
              backgroundColor: Colors.blue.shade900,
            ),
      body: _children[_selectedIndex],
      bottomNavigationBar: BottomNavigationBar(
              items: const <BottomNavigationBarItem>[
                BottomNavigationBarItem(
                  icon: Icon(Icons.work),
                  label: 'Page 1',
                ),
                BottomNavigationBarItem(
                  icon: Icon(Icons.inventory),
                  label: 'Page 2',
                ),
                BottomNavigationBarItem(
                  icon: Icon(Icons.alt_route),
                  label: 'Page 3',
                ),
                BottomNavigationBarItem(
                  icon: Icon(Icons.article),
                  label: 'Page 4',
                ),
              ],
              currentIndex: _selectedIndex,
              onTap: _onItemTapped,
              type: BottomNavigationBarType.fixed,
            ),
    );
  }
}

And this is the child widget that would be defined by _children[_selectedIndex] by pressing one of the buttons in the bottomNavigationBar

class Page1 extends StatefulWidget {
  @override
  _Page1State createState() => _Page1State();
}

class _Page1State extends State<Page1> {

  @override
  Widget build(BuildContext context) {
    return Text("Click Here",onPressed:(){
    //objective here is to open Page5 in the body of HomePage(not in _children list of homepage, and pass it some value (ie. id 12))
            },
        ),
    );
  }
}

To exemplify expectation, this is what I’d like in Page5 (which should be loaded in the body of HomePage

class Page5 extends StatefulWidget {
  @override
  _Page5State createState() => _Page5State();
}

class _Page5State extends State<Page5> {
  int _variableFromPage1;
  @override
  Widget build(BuildContext context) {
    return Text(_variableFromPage1);
  }
}

Can anyone advise the best way of doing that?

Solution

You can define custom callback function like the following code sample.

class HomePage extends StatefulWidget {      
  @override
  _HomePageState createState() => _HomePageState();
}

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

  List<Widget> _children;

  void _onItemTapped(int index) {
    setState(() {
      _selectedIndex = index;
    });
  }

  @override
  void initState() {
    super.initState();

    _children = [
      Page1(
        callback: (value) {
          setState(() {
            _selectedID = value;
          });
        },
      ),
      Page2(),
    ];
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: (_selectedIndex == 0 && _selectedID > 0)
          ? Page5(id: _selectedID)
          : _children[_selectedIndex],
      bottomNavigationBar: BottomNavigationBar(
        items: const <BottomNavigationBarItem>[
          BottomNavigationBarItem(
            icon: Icon(Icons.work),
            label: 'Page 1',
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.inventory),
            label: 'Page 2',
          ),
        ],
        currentIndex: _selectedIndex,
        onTap: _onItemTapped,
        type: BottomNavigationBarType.fixed,
      ),
    );
  }
}

typedef IntCallback(int value);

class Page1 extends StatelessWidget {
  Page1({Key key, @required this.callback}) : super(key: key);

  final IntCallback callback;

  @override
  Widget build(BuildContext context) {
    return Center(
      child: InkWell(
        onTap: () {
          callback(12);
        },
        child: Text("Page1 ::: id >>> 12"),
      ),
    );
  }
}

class Page2 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Center(
      child: Text("Page2"),
    );
  }
}

class Page5 extends StatelessWidget {
  Page5({Key key, @required this.id}) : super(key: key);
  
  final int id;

  @override
  Widget build(BuildContext context) {
    return Center(child: Text("Page5 ::: id $id"));
  }
}

Answered By – loyal

Answer Checked By – Senaida (FlutterFixes Volunteer)

Leave a Reply

Your email address will not be published. Required fields are marked *