8

How do i create a multi page view in flutter where the pages correspond to tabs in a bottomnavigationbar such that the widgets corresponding to the pages are built only once and on demand.

For eg., consider a simple facebook app kind of UI with two tabs - the feed and the notification with the following behavior:

  1. Both feed and notifications are list of items fetched over network.
  2. Assuming the original tab is feed, the notifications should be fetched only when user clicks on the notification tab
  3. If a user scrolls in the feed, and clicks on notification icon and then clicks again on feed icon, the scroll position should be remembered.

If i use a TabBarView, it rebuilds the widgets every time the tab is changed, so the scroll position is not retained.

Collin Jackson
  • 97,818
  • 28
  • 207
  • 143
aptik
  • 563
  • 1
  • 4
  • 8
  • about BottomNavigationBar, see this https://stackoverflow.com/questions/45235570/how-to-use-bottomnavigationbar-with-navigator/45992604#45992604 If you want TabBarView, use PageStorageKey to your inner pages. – najeira Sep 05 '17 at 03:09

2 Answers2

20

To give the pages of the TabBarView a unique scroll position storage container, use a PageStorageKey.

video

import 'package:flutter/material.dart';

void main() {
  runApp(new MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      home: new MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  @override
  State createState() => new MyHomePageState();
}

class MyHomePageState extends State<MyHomePage> with TickerProviderStateMixin {
  TabController _controller;
  int _tab = 0;

  @override
  void initState() {
    _controller = new TabController(length: 2, vsync: this);
  }

  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(title: new Text('Example App')),
      body: new TabBarView(
        controller: _controller,
        children: <Widget>[
          new ListView.builder(
            key: new PageStorageKey('feed'),
            itemBuilder: (BuildContext context, int index) {
              return new ListTile(
                title: new Text('Feed Item $index'),
              );
            },
          ),
          new ListView.builder(
            key: new PageStorageKey('notifications'),
            itemBuilder: (BuildContext context, int index) {
              return new ListTile(
                title: new Text('Notification $index'),
              );
            },
          ),
        ],
      ),
      bottomNavigationBar: new BottomNavigationBar(
        onTap: (int value) {
          _controller.animateTo(value);
          setState(() {
            _tab = value;
          });
        },
        currentIndex: _tab,
        items: <BottomNavigationBarItem>[
          new BottomNavigationBarItem(
            icon: new Icon(Icons.home),
            title: new Text('Home'),
          ),
          new BottomNavigationBarItem(
            icon: new Icon(Icons.notifications),
            title: new Text('Notifications'),
          ),
        ],
      ),
    );
  }
}
Collin Jackson
  • 97,818
  • 28
  • 207
  • 143
2

enter image description here

The Complete Example

First make a class MyBottomBarDemo

class MyBottomBarDemo extends StatefulWidget {
  @override
  _MyBottomBarDemoState createState() => new _MyBottomBarDemoState();
}

class _MyBottomBarDemoState extends State<MyBottomBarDemo> {
  int _pageIndex = 0;
  PageController _pageController;

  List<Widget> tabPages = [
    Screen1(),
    Screen2(),
  ];

  @override
  void initState(){
    _pageController = PageController(initialPage: _pageIndex);
    super.initState();
  }

  @override
  void dispose() {
    super.dispose();
    _pageController.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Multi tab / page view", style: TextStyle(color: Colors.white)),
        backgroundColor: Colors.deepPurple,
      ),
      bottomNavigationBar: BottomNavigationBar(
        currentIndex: _pageIndex,
        onTap: onTabTapped,
        backgroundColor: Colors.white,
        items: <BottomNavigationBarItem>[
          BottomNavigationBarItem( icon: Icon(Icons.home), title: Text("Home")),
          BottomNavigationBarItem(icon: Icon(Icons.mail), title: Text("Messages")),
        ],

      ),
      body: PageView(
        children: tabPages,
        onPageChanged: onPageChanged,
        controller: _pageController,
      ),
      drawer: SlideDrawer(),
    );
  }
  void onPageChanged(int page) {
    setState(() {
      this._pageIndex = page;
    });
  }

  void onTabTapped(int index) {
    this._pageController.animateToPage(index,duration: const Duration(milliseconds: 500),curve: Curves.easeInOut);
  }
}

Then create a your pagers screens

class Screen1(), extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.yellow,
      child: StreamBuilder(
        stream: getHomeDataList(param).asStream(), // This is your function of get data from network
        builder: (BuildContext context, AsyncSnapshot snapshot) {
          switch (snapshot.connectionState) {
            case ConnectionState.waiting: {
                return LoadingIndicatorView();
              }
            case ConnectionState.active: {
                break;
              }
            case ConnectionState.done: {
                if (snapshot.hasData) {
                  if (snapshot.data != null) {
                    if (snapshot.data.length > 0) {
                      return Container(
                        color: offBlueColor,
                        child: Scrollbar(
                          child: ListView.builder(
                              key: PageStorageKey('homeList'),
                              padding: EdgeInsets.all(5),
                              itemCount: snapshot.data.length,
                              itemBuilder: (context, index) {
                                return ListTile(
                                  title: Text('Home Page index $index')
                                );;
                              }),
                        ),
                      );
                    } else {
                      return Text("No data found");
                    }
                  } else {
                    // display error message data is null.
                    return Text("No data found");
                  }
                } else if (snapshot.hasError) {
                  return Text(snapshot.error.toString());
                } else {
                  return Text("Something went wrong");
                }
                break;
              }
            case ConnectionState.none:{
                break;
              }
          }
          return Container();
        },
      ),
    );
  }
}
Paresh Mangukiya
  • 37,512
  • 17
  • 201
  • 182