Search code examples
flutterdartrecursionnavigator

Flutter: Navigator.push recursion and keep appbar and bottom navigation bar


I want to show dynamic listview according to a tree hierarchy.

enter image description here

What I planned was this : draw listview according to a List with Maps in it, and when I tap on one item, draw the same listview but with the children's List (to form a tree).

This is the json file

[{
   "label":"All",
   "children":[
      {
         "label":"test1",
         "children":[
            {
               "label":"test1-1"
            }
         ]
      },
      {
         "label":"test2"
      },
      {
         "label":"test3"
      }
   ]
}]

So, the first page shows one listTile with "All". If i click on "All", then now there are 3 listTile with "test1", "test2", "test3".

I tried Navigator.push( ...()=>Scaffold(body: MyPage())) but of course because it is a new Scaffold, I can't see the bottom navigation bar.

I tried to do it like Navigator.push(...()=>CurrentClass()) but then flutter says that I don't have a Material to draw on.

How can I fix this?

return ListView.separated(
            shrinkWrap: true,
            itemBuilder: (context, index) {
              print(_children[index]["children"]);
              List<Map<String, dynamic>> _childMaps =
                  _children[index]["children"];

              return ListTile(
                title: Text(_children[index]["label"]),
                onTap: () => Navigator.push(
                  context,
                  PageRouteBuilder(
                    pageBuilder: (context, _, __) => Scaffold(
                      appBar: AppBar(
                        title: Text(_children[index]["label"]),
                      ),
                      body: TempScreen(
                        childMaps: _childMaps,
                      ),
                    ),
                    transitionDuration: Duration.zero,
                    reverseTransitionDuration: Duration.zero,
                  ),
                ),
              );
            },
            separatorBuilder: (_, __) => Divider(),
            itemCount: _children.length,
          )

Solution

  • You can try this way, using new MaterialApp which will push new routes on top of that area and preserve the same bottomSheet (navigation) across all of them. Just keep in mind that if you will at some point need to push completely new screen from one of your subScreens it will be a little bit tricky.

    class Test extends StatefulWidget {
      const Test({Key? key}) : super(key: key);
    
      @override
      State<Test> createState() => _TestState();
    }
    
    class _TestState extends State<Test> {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          home: Scaffold(
            // use new MaterialApp to push new (sub)screens on top of that area and preserve the same bottomSheet (navigation)
            body: const MaterialApp(home: Body()),
            bottomSheet: Container(
              height: 60,
              color: Colors.amber,
              width: double.infinity,
              child: Row(
                mainAxisAlignment: MainAxisAlignment.spaceAround,
                children: const [
                  Icon(Icons.home),
                  Icon(Icons.person),
                  Icon(Icons.shop),
                  Icon(Icons.more),
                ],
              ),
            ),
          ),
        );
      }
    }
    
    class Body extends StatelessWidget {
      const Body({
        Key? key,
      }) : super(key: key);
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(title: const Text('Home')),
          body: Container(
            color: Colors.lightBlue.withOpacity(.6),
            child: ListView.builder(
              itemBuilder: (ctx, i) => ListItem(i),
              itemCount: 5,
            ),
          ),
        );
      }
    }
    
    class SubScreen extends StatelessWidget {
      final int index;
      const SubScreen(
        this.index, {
        Key? key,
      }) : super(key: key);
    
      @override
      Widget build(BuildContext context) {
        // just simulating different number of children
        final count = Random().nextInt(7);
        return Scaffold(
          appBar: AppBar(title: Text('item $index')),
          body: Container(
            color: Colors.lightBlue.withOpacity(.6),
            child: ListView.builder(
              itemBuilder: (ctx, i) => ListItem(i),
              itemCount: count,
            ),
          ),
        );
      }
    }
    
    class ListItem extends StatelessWidget {
      final int index;
      const ListItem(
        this.index, {
        Key? key,
      }) : super(key: key);
    
      @override
      Widget build(BuildContext context) {
        return Padding(
          padding: const EdgeInsets.all(5),
          child: ListTile(
            onTap: () => Navigator.of(context).push(
              MaterialPageRoute(builder: (context) => SubScreen(index)),
            ),
            title: Text('item $index'),
            tileColor: Colors.lightGreen,
          ),
        );
      }
    }