74

I have a problem with my BottomNavigationBar in Flutter. I want to keep my page alive if I change the tabs.

here my implementation

BottomNavigation

class Home extends StatefulWidget {
  @override
  State<StatefulWidget> createState() {
    return _HomeState();
  }
}

class _HomeState extends State<Home> {
  int _currentIndex = 0;
  List<Widget> _children;
  final Key keyOne = PageStorageKey("IndexTabWidget");

  @override
  void initState() {
    _children = [
      IndexTabWidget(key: keyOne),
      PlaceholderWidget(Colors.green),
      NewsListWidget(),
      ShopsTabWidget(),
      PlaceholderWidget(Colors.blue),
    ];
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(MyApp.appName),
        textTheme: Theme.of(context).textTheme.apply(
              bodyColor: Colors.black,
              displayColor: Colors.blue,
            ),
      ),
      body: _children[_currentIndex],
      bottomNavigationBar: BottomNavigationBar(
        onTap: onTabTapped,
        key: IHGApp.globalKey,
        fixedColor: Colors.green,
        type: BottomNavigationBarType.fixed,
        currentIndex: _currentIndex,
        items: [
          BottomNavigationBarItem(
            icon: Icon(Icons.home),
            title: Container(height: 0.0),
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.message),
            title: Container(height: 0.0),
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.settings),
            title: Container(height: 0.0),
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.perm_contact_calendar),
            title: Container(height: 0.0),
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.business),
            title: Container(height: 0.0),
          ),
        ],
      ),
    );
  }

  void onTabTapped(int index) {
    setState(() {
      _currentIndex = index;
    });
  }

  Column buildButtonColumn(IconData icon) {
    Color color = Theme.of(context).primaryColor;

    return Column(
      mainAxisSize: MainAxisSize.min,
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        Icon(icon, color: color),
      ],
    );
  }
}

This is my index page (first tab):

class IndexTabWidget extends StatefulWidget {
  IndexTabWidget({Key key}) : super(key: key);

  @override
  State<StatefulWidget> createState() {
    return new IndexTabState();
  }
}

class IndexTabState extends State<IndexTabWidget>
    with AutomaticKeepAliveClientMixin {
  List<News> news = List();
  FirestoreNewsRepo newsFirestore = FirestoreNewsRepo();

  @override
  Widget build(BuildContext context) {
    return Material(
      color: Colors.white,
      child: new Container(
        child: new SingleChildScrollView(
          child: new ConstrainedBox(
            constraints: new BoxConstraints(),
            child: new Column(
              children: <Widget>[
                HeaderWidget(
                  CachedNetworkImageProvider(
                    'https://static1.fashionbeans.com/wp-content/uploads/2018/04/50-barbershop-top-savill.jpg',
                  ),
                  "",
                ),
                AboutUsWidget(),
                Padding(
                  padding: const EdgeInsets.all(16.0),
                  child: SectionTitleWidget(title: StringStorage.salonsTitle),
                ),
                StreamBuilder(
                  stream: newsFirestore.observeNews(),
                  builder: (context, snapshot) {
                    if (!snapshot.hasData) {
                      return CircularProgressIndicator();
                    } else {
                      news = snapshot.data;
                      return Column(
                        children: <Widget>[
                          ShopItemWidget(
                            AssetImage('assets/images/picture.png'),
                            news[0].title,
                            news[0],
                          ),
                          ShopItemWidget(
                            AssetImage('assets/images/picture1.png'),
                            news[1].title,
                            news[1],
                          )
                        ],
                      );
                    }
                  },
                ),
                Padding(
                  padding: const EdgeInsets.only(
                      left: 16.0, right: 16.0, bottom: 16.0),
                  child: SectionTitleWidget(title: StringStorage.galleryTitle),
                ),
                GalleryCategoryCarouselWidget(),
              ],
            ),
          ),
        ),
      ),
    );
  }

  @override
  bool get wantKeepAlive => true;
}

So if I switch from my index tab to any other tab and back to the index tab, the index tab will always rebuild. I debugged it and saw that the build function is always being called on the tab switch.

Could you guys help me out with this issue?

Thank you a lot Albo

NullPointer
  • 1,250
  • 2
  • 12
  • 17
  • 1
    On tab change, the page will get `re-build`. We cannot skip that. Move the network calls and DB related thinks to the state so that it would get computed for each tab change. – Dinesh Balasubramanian Oct 02 '18 at 05:51
  • so everything related to network calls I should place in initState? – NullPointer Oct 02 '18 at 06:05
  • 1
    @DineshBalasubramanian i found out that on tab change also initState is beeing re-called. So this can not be the solution for the problem.... – NullPointer Oct 02 '18 at 08:57
  • 1
    I came across this package: https://pub.dev/packages/persistent_bottom_nav_bar . It keeps state so no rebuild is done. It also handles proper independent and persistent navigation inside the tabs, and it doesn't suffer from loading all tabs at the beginning as `IndexedStack` does. – maganap Oct 24 '21 at 08:59

8 Answers8

165

None of the previous answers worked out for me.

The solution to keep the pages alive when switching the tabs is wrapping your Pages in an IndexedStack.

class Tabbar extends StatefulWidget {
  Tabbar({this.screens});

  static const Tag = "Tabbar";
  final List<Widget> screens;
  @override
  State<StatefulWidget> createState() {
  return _TabbarState();
  }
}

class _TabbarState extends State<Tabbar> {
  int _currentIndex = 0;
  Widget currentScreen;

  @override
  Widget build(BuildContext context) {
    var _l10n = PackedLocalizations.of(context);

    return Scaffold(
  body: IndexedStack(
    index: _currentIndex,
    children: widget.screens,
  ),
  bottomNavigationBar: BottomNavigationBar(
    fixedColor: Colors.black,
    type: BottomNavigationBarType.fixed,
    onTap: onTabTapped,
    currentIndex: _currentIndex,
    items: [
      BottomNavigationBarItem(
        icon: new Icon(Icons.format_list_bulleted),
        title: new Text(_l10n.tripsTitle),
      ),
      BottomNavigationBarItem(
        icon: new Icon(Icons.settings),
        title: new Text(_l10n.settingsTitle),
      )
    ],
  ),
);
  }

  void onTabTapped(int index) {
    setState(() {
      _currentIndex = index;
    });
  }
}
basedgod
  • 2,517
  • 1
  • 17
  • 18
  • @basedgod what is PackedLocalizations referring to in this solution? I have not been able to find any documentation for it. – PJQuakJag Aug 24 '19 at 16:50
  • 1
    @PJQuakJag It's referring to localized strings. – basedgod Aug 30 '19 at 11:42
  • 15
    @basedgod Your solution worked superbly, but the issue is all the screen tabs are building simultaneously even the tabs are not active. So I want to keep the tabs getting built once only after the tabs are active, any suugestion? – TechSatya Jun 01 '20 at 12:27
  • @TechSatya: I am also facing same issue, any solution so far ??? – Abhishek Thapliyal Sep 20 '20 at 16:12
  • @AbhishekThapliyal Nope. I think no such solution provided by the Flutter itself yet. So I had to be Ok with IndexedStack's behavior. – TechSatya Sep 21 '20 at 05:52
  • @AbhishekThapliyal Take a look at this solution which doesn't build all tabs at the beginning: https://stackoverflow.com/a/66404760/2328861. – Tuan van Duong May 06 '21 at 05:58
  • For people ready to sacrifice material NavBar for having the problem fixed "by design" - you can use CupertinoTabScaffold... – parbo May 08 '21 at 20:29
  • @parbo i used CupertinoTabScaffold and same problem, can u share ur code – Fatima ayaa May 10 '21 at 13:15
  • 1
    @Fatimaayaa sorry for late response, I decided to go with CupertinoTabBar inside CupertinoTabScaffold for iOS and PersistentTabView(https://pub.dev/packages/persistent_bottom_nav_bar) for Android. The reason is that I wanted to have the most native feel and look, while still having persistence in Android. Here is the example: https://gist.github.com/bparol/2ebaf11af962115dc598294bf136cb8d – parbo May 23 '21 at 11:43
  • not work, if screen with network request data – Michael Mao Jun 24 '22 at 08:28
12

You need to wrap every root page (the first page you see when you press a bottom navigation item) with a navigator and put them in a Stack.

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

class _HomePageState extends State<HomePage> {
  final int _pageCount = 2;
  int _pageIndex = 0;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: _body(),
      bottomNavigationBar: _bottomNavigationBar(),
    );
  }

  Widget _body() {
    return Stack(
      children: List<Widget>.generate(_pageCount, (int index) {
        return IgnorePointer(
          ignoring: index != _pageIndex,
          child: Opacity(
            opacity: _pageIndex == index ? 1.0 : 0.0,
            child: Navigator(
              onGenerateRoute: (RouteSettings settings) {
                return new MaterialPageRoute(
                  builder: (_) => _page(index),
                  settings: settings,
                );
              },
            ),
          ),
        );
      }),
    );
  }

  Widget _page(int index) {
    switch (index) {
      case 0:
        return Page1();
      case 1:
        return Page2();
    }

    throw "Invalid index $index";
  }

  BottomNavigationBar _bottomNavigationBar() {
    final theme = Theme.of(context);
    return new BottomNavigationBar(
      fixedColor: theme.accentColor,
      currentIndex: _pageIndex,
      items: [
        BottomNavigationBarItem(
          icon: Icon(Icons.list),
          title: Text("Page 1"),
        ),
        BottomNavigationBarItem(
          icon: Icon(Icons.account_circle),
          title: Text("Page 2"),
        ),
      ],
      onTap: (int index) {
        setState(() {
          _pageIndex = index;
        });
      },
    );
  }
}

The pages will be rebuild but you should separate your business logic from you UI anyway. I prefer to use the BLoC pattern but you can also use Redux, ScopedModel or InhertedWidget.

BauerMitFackel
  • 372
  • 5
  • 15
  • 1
    @fvisticot I have not noticed any problems with the performance. – BauerMitFackel Dec 13 '18 at 23:11
  • 1
    unfortunately when using this solution, the navigation to go to another route is not working anymore, Navigator.of(context) .pushNamedAndRemoveUntil('/events', (Route route) => false); – fvisticot Dec 14 '18 at 16:23
  • @fvisticot You should use the root Navigator (https://docs.flutter.io/flutter/widgets/Navigator/of.html) for this. – BauerMitFackel Dec 17 '18 at 12:34
  • But If I have a textInput widget in one of this windows, I am not able to set focus in them , Keyboard is not showing – Harshita Jan 19 '19 at 19:56
  • This solution is great. But I have a problem. If I am navigating from my tabs to another subscreen, I get a second navigation bar displayed. Any chance that I can make this changes only available for the screens of the three tabs (and leaving the sunscreens as they are)? – user3532201 Apr 01 '19 at 04:09
  • @user3532201 This dependes on the widget tree of your subpage. Maybe its better to ask a separate question where you post your code – BauerMitFackel Apr 24 '19 at 09:35
9

Just use an IndexedStack

 IndexedStack(
   index: selectedIndex,
   children: <Widget> [
       ProfileScreen(), 
       MapScreen(), 
       FriendsScreen()
     ],
  )
Ruan
  • 3,969
  • 10
  • 60
  • 87
  • But If I use IndexedStack in my app then when I want to go another screen my BottomMenu goes hide. any solution for that. – pavel Jul 16 '21 at 21:01
3

I'm not sure but CupertinoTabBar would help.
If you don't want it, this video would be great url.

import 'dart:async';
import 'dart:io';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_inapp_purchase/flutter_inapp_purchase.dart';

class HomeScreen extends StatefulWidget {
  @override
  _HomeScreenState createState() => new _HomeScreenState();
}

class _HomeScreenState extends State<HomeScreen> {
  final List<dynamic> pages = [
    new Page1(),
    new Page2(),
    new Page3(),
    new Page4(),
  ];

  int currentIndex = 0;

  @override
  Widget build(BuildContext context) {
    return new WillPopScope(
      onWillPop: () async {
        await Future<bool>.value(true);
      },
      child: new CupertinoTabScaffold(
        tabBar: new CupertinoTabBar(
          iconSize: 35.0,
          onTap: (index) {
            setState(() => currentIndex = index);
          },
          activeColor: currentIndex == 0 ? Colors.white : Colors.black,
          inactiveColor: currentIndex == 0 ? Colors.green : Colors.grey,
          backgroundColor: currentIndex == 0 ? Colors.black : Colors.white,
          currentIndex: currentIndex,
          items: const <BottomNavigationBarItem>[
            BottomNavigationBarItem(
              icon: Icon(Icons.looks_one),
              title: Text(''),
            ),
            BottomNavigationBarItem(
              icon: Icon(Icons.looks_two),
              title: Text(''),
            ),
            BottomNavigationBarItem(
              icon: Icon(Icons.looks_3),
              title: Text(''),
            ),
            BottomNavigationBarItem(
              icon: Icon(Icons.looks_4),
              title: Text(''),
            ),
          ],
        ),
        tabBuilder: (BuildContext context, int index) {
          return new DefaultTextStyle(
            style: const TextStyle(
              fontFamily: '.SF UI Text',
              fontSize: 17.0,
              color: CupertinoColors.black,
            ),
            child: new CupertinoTabView(
              routes: <String, WidgetBuilder>{
                '/Page1': (BuildContext context) => new Page1(),
                '/Page2': (BuildContext context) => new Page2(),
                '/Page3': (BuildContext context) => new Page3(),
                '/Page4': (BuildContext context) => new Page4(),
              },
              builder: (BuildContext context) {
                return pages[currentIndex];
              },
            ),
          );
        },
      ),
    );
  }
}

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

class _Page1State extends State<Page1> {

  String title;

 @override
  void initState() {
    title = 'Page1';
    super.initState();
  }
   @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
          title: new Text(title),
          leading: new IconButton(
            icon: new Icon(Icons.text_fields),
            onPressed: () {
              Navigator.of(context)
                  .push(MaterialPageRoute(builder: (context) => Page13()));
            },
          )),
      body: new Center(
        child: new Text(title),
      ),
    );
  }
}

class Page2 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new Scaffold(
        appBar: new AppBar(
            title: new Text('Page2'),
            leading: new IconButton(
              icon: new Icon(Icons.airline_seat_flat_angled),
              onPressed: () {
                Navigator.of(context)
                    .push(MaterialPageRoute(builder: (context) => Page12()));
              },
            )),
        body: new Center(
          child: Column(
            children: <Widget>[
              CupertinoSlider(
                      value: 25.0,
                      min: 0.0,
                      max: 100.0,
                      onChanged: (double value) {
                        print(value);
                      }
                    ),
            ],
          ),
        ),
    );
  }
}

class Page3 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: new Text('Page3'),
      ),
      body: new Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            new RaisedButton(
                child: new Text('Cupertino'),
                textColor: Colors.white,
                color: Colors.red,
                onPressed: () {
                  List<int> list = List.generate(10, (int i) => i + 1);    
                  list.shuffle();
                  var subList = (list.sublist(0, 5));
                  print(subList);
                  subList.forEach((li) => list.remove(li));
                  print(list);
                }
            ),
            new SizedBox(height: 30.0),
            new RaisedButton(
                child: new Text('Android'),
                textColor: Colors.white,
                color: Colors.lightBlue,
                onPressed: () {
                  var mes = 'message';
                  var messa = 'メッセージ';
                  var input = 'You have a new message';
                  if (input.contains(messa) || input.contains(mes)) {
                    print('object');
                  } else {
                    print('none');
                  }
                }
            ),
          ],
        ),
      ),
    );
  }
}

class Page4 extends StatelessWidget {
  static List<int> ints = [1, 2, 3, 4, 5];

  static _abc() {
    print(ints.last);
  }

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: new Text('Page4'),
      ),
      body: new Center(
          child: new RaisedButton(
        child: new Text('Static', style: new TextStyle(color: Colors.white)),
        color: Colors.lightBlue,
        onPressed: _abc,
      )),
    );
  }
}

class Page12 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: new Text('Page12'),
        actions: <Widget>[
          new FlatButton(
            child: new Text('GO'),
            onPressed: () {
              Navigator.of(context)
                  .push(MaterialPageRoute(builder: (context) => Page13()));
            },
          )
        ],
      ),
      body: new Center(
          child: new RaisedButton(
        child: new Text('Swiper', style: new TextStyle(color: Colors.white)),
        color: Colors.redAccent,
        onPressed: () {},
      )),
    );
  }
}

class Page13 extends StatefulWidget {
  @override
  _Page13State createState() => _Page13State();
}

class _Page13State extends State<Page13> with SingleTickerProviderStateMixin {
 final List<String> _productLists = Platform.isAndroid
      ? [
          'android.test.purchased',
          'point_1000',
          '5000_point',
          'android.test.canceled',
        ]
      : ['com.cooni.point1000', 'com.cooni.point5000'];

  String _platformVersion = 'Unknown';
  List<IAPItem> _items = [];
  List<PurchasedItem> _purchases = [];

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

  Future<void> initPlatformState() async {
    String platformVersion;
    try {
      platformVersion = await FlutterInappPurchase.platformVersion;
    } on PlatformException {
      platformVersion = 'Failed to get platform version.';
    }

    var result = await FlutterInappPurchase.initConnection;
    print('result: $result');

    if (!mounted) return;

    setState(() {
      _platformVersion = platformVersion;
    });

    // refresh items for android
    String msg = await FlutterInappPurchase.consumeAllItems;
    print('consumeAllItems: $msg');
  }

  Future<Null> _buyProduct(IAPItem item) async {
    try {
      PurchasedItem purchased = await FlutterInappPurchase.buyProduct(item.productId);
      print('purchased: ${purchased.toString()}');
    } catch (error) {
      print('$error');
    }
  }

  Future<Null> _getProduct() async {
    List<IAPItem> items = await FlutterInappPurchase.getProducts(_productLists);
    print(items);
    for (var item in items) {
      print('${item.toString()}');
      this._items.add(item);
    }

    setState(() {
      this._items = items;
      this._purchases = [];
    });
  }

  Future<Null> _getPurchases() async {
    List<PurchasedItem> items = await FlutterInappPurchase.getAvailablePurchases();
    for (var item in items) {
      print('${item.toString()}');
      this._purchases.add(item);
    }

    setState(() {
      this._items = [];
      this._purchases = items;
    });
  }

  Future<Null> _getPurchaseHistory() async {
    List<PurchasedItem> items = await FlutterInappPurchase.getPurchaseHistory();
    for (var item in items) {
      print('${item.toString()}');
      this._purchases.add(item);
    }

    setState(() {
      this._items = [];
      this._purchases = items;
    });
  }

  List<Widget> _renderInApps() {
    List<Widget> widgets = this
        ._items
        .map((item) => Container(
              margin: EdgeInsets.symmetric(vertical: 10.0),
              child: Container(
                child: Column(
                  children: <Widget>[
                    Container(
                      margin: EdgeInsets.only(bottom: 5.0),
                      child: Text(
                        item.toString(),
                        style: TextStyle(
                          fontSize: 18.0,
                          color: Colors.black,
                        ),
                      ),
                    ),
                    FlatButton(
                      color: Colors.orange,
                      onPressed: () {
                        print("---------- Buy Item Button Pressed");
                        this._buyProduct(item);
                      },
                      child: Row(
                        children: <Widget>[
                          Expanded(
                            child: Container(
                              height: 48.0,
                              alignment: Alignment(-1.0, 0.0),
                              child: Text('Buy Item'),
                            ),
                          ),
                        ],
                      ),
                    ),
                  ],
                ),
              ),
            ))
        .toList();
    return widgets;
  }

  List<Widget> _renderPurchases() {
    List<Widget> widgets = this
        ._purchases
        .map((item) => Container(
              margin: EdgeInsets.symmetric(vertical: 10.0),
              child: Container(
                child: Column(
                  children: <Widget>[
                    Container(
                      margin: EdgeInsets.only(bottom: 5.0),
                      child: Text(
                        item.toString(),
                        style: TextStyle(
                          fontSize: 18.0,
                          color: Colors.black,
                        ),
                      ),
                    )
                  ],
                ),
              ),
            ))
        .toList();
    return widgets;
  }

  @override
  Widget build(BuildContext context) {
    double screenWidth = MediaQuery.of(context).size.width-20;
    double buttonWidth=(screenWidth/3)-20;

    return new Scaffold(
      appBar: new AppBar(),
      body: Container(
      padding: EdgeInsets.all(10.0),
      child: ListView(
        children: <Widget>[
          Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            mainAxisAlignment: MainAxisAlignment.start,
            children: <Widget>[
              Container(
                child: Text(
                  'Running on: $_platformVersion\n',
                  style: TextStyle(fontSize: 18.0),
                ),
              ),
              Column(
                children: <Widget>[
                  Row(
                    mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                    children: <Widget>[
                      Container(
                        width: buttonWidth,
                        height: 60.0,
                        margin: EdgeInsets.all(7.0),
                        child: FlatButton(
                          color: Colors.amber,
                          padding: EdgeInsets.all(0.0),
                          onPressed: () async {
                            print("---------- Connect Billing Button Pressed");
                            await FlutterInappPurchase.initConnection;
                          },
                          child: Container(
                            padding: EdgeInsets.symmetric(horizontal: 20.0),
                            alignment: Alignment(0.0, 0.0),
                            child: Text(
                              'Connect Billing',
                              style: TextStyle(
                                fontSize: 16.0,
                              ),
                            ),
                          ),
                        ),
                      ),
                      Container(
                        width: buttonWidth,
                        height: 60.0,
                        margin: EdgeInsets.all(7.0),
                        child: FlatButton(
                          color: Colors.amber,
                          padding: EdgeInsets.all(0.0),
                          onPressed: () async {
                            print("---------- End Connection Button Pressed");
                            await FlutterInappPurchase.endConnection;
                            setState(() {
                              this._items = [];
                              this._purchases = [];
                            });
                          },
                          child: Container(
                            padding: EdgeInsets.symmetric(horizontal: 20.0),
                            alignment: Alignment(0.0, 0.0),
                            child: Text(
                              'End Connection',
                              style: TextStyle(
                                fontSize: 16.0,
                              ),
                            ),
                          ),
                        ),
                      ),
                    ],
                  ),
                  Row(
                      mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                      children: <Widget>[
                        Container(
                            width: buttonWidth,
                            height: 60.0,
                            margin: EdgeInsets.all(7.0),
                            child: FlatButton(
                              color: Colors.green,
                              padding: EdgeInsets.all(0.0),
                              onPressed: () {
                                print("---------- Get Items Button Pressed");
                                this._getProduct();
                              },
                              child: Container(
                                padding: EdgeInsets.symmetric(horizontal: 20.0),
                                alignment: Alignment(0.0, 0.0),
                                child: Text(
                                  'Get Items',
                                  style: TextStyle(
                                    fontSize: 16.0,
                                  ),
                                ),
                              ),
                            )),
                        Container(
                            width: buttonWidth,
                            height: 60.0,
                            margin: EdgeInsets.all(7.0),
                            child: FlatButton(
                              color: Colors.green,
                              padding: EdgeInsets.all(0.0),
                              onPressed: () {
                                print(
                                    "---------- Get Purchases Button Pressed");
                                this._getPurchases();
                              },
                              child: Container(
                                padding: EdgeInsets.symmetric(horizontal: 20.0),
                                alignment: Alignment(0.0, 0.0),
                                child: Text(
                                  'Get Purchases',
                                  style: TextStyle(
                                    fontSize: 16.0,
                                  ),
                                ),
                              ),
                            )),
                        Container(
                            width: buttonWidth,
                            height: 60.0,
                            margin: EdgeInsets.all(7.0),
                            child: FlatButton(
                              color: Colors.green,
                              padding: EdgeInsets.all(0.0),
                              onPressed: () {
                                print(
                                    "---------- Get Purchase History Button Pressed");
                                this._getPurchaseHistory();
                              },
                              child: Container(
                                padding: EdgeInsets.symmetric(horizontal: 20.0),
                                alignment: Alignment(0.0, 0.0),
                                child: Text(
                                  'Get Purchase History',
                                  style: TextStyle(
                                    fontSize: 16.0,
                                  ),
                                ),
                              ),
                            )),
                      ]),
                ],
              ),
              Column(
                children: this._renderInApps(),
              ),
              Column(
                children: this._renderPurchases(),
              ),
            ],
          ),
        ],
      ),
    ),
    );
  }
}
Daibaku
  • 11,416
  • 22
  • 71
  • 108
  • Thank you for your answer! I have followed the instruction of the video you mentioned. This helps to retain the state of the page if you scrolled and switched tabs. The problem is still there: The Page/View will be rebuilt after tab switch. I did not test it with CupertinoTabBar but I don't think that the problem is because of the TabBar to be honest. Did you test it with CupertinoTabBar? When you swtich tabs, does the build function start again? – NullPointer Oct 02 '18 at 05:26
  • 1
    I’m not sure it works in your use case but I have 4 tabs and except second tab(which is index 1) I implemented StreamBuilder. So those three tabs are rebuilt every time but second tab is not rebuilt. In second tab, basically I fetch data from internet and display in grid. – Daibaku Oct 02 '18 at 09:14
  • could you please share your code in your answer so I can imagine how you handled this? This is exactly what I need. – NullPointer Oct 02 '18 at 09:20
  • I edited my answer I'm not sure this is correct way to do this. Basically I test everything I want to in my main project in this project. So, those are just demo app but state is remained. If you go to page13 from first tab, and move to tab index3, then go back to first tab, it should be look the same. – Daibaku Oct 02 '18 at 11:57
  • 1
    I use `CupertinoTabBar` and have the same issue as the author. – Tuan van Duong Apr 27 '21 at 13:31
  • @TuanvanDuong did you find how to do it with CupertinoTabBar – Fatima ayaa May 10 '21 at 13:13
  • @Fatimaayaa Yes I found a very good solution from [here](https://stackoverflow.com/a/66404760/2328861). This solution even doesn't use `CupertinoTabBar`. – Tuan van Duong Jun 10 '21 at 09:57
1

Use IndexedStack widget:

class Home extends StatefulWidget {
  const Home({Key? key}) : super(key: key);

  @override
  _HomeState createState() => _HomeState();
}

class _HomeState extends State<Home> {
  int _currentIndex = 0;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: SafeArea(
        child: IndexedStack(
          index: _currentIndex,
          children: const [
            HomePage(),
            SettingsPage(),
          ],
        ),
      ),
      bottomNavigationBar: BottomNavigationBar(
        currentIndex: _currentIndex,
        onTap: (int index) => setState(() => _currentIndex = index),
        items: [
          BottomNavigationBarItem(icon: Icon(Icons.home), label: 'Home'),
          BottomNavigationBarItem(icon: Icon(Icons.settings), label: 'Settings'),
        ],
      ),
    );
  }
}

class HomePage extends StatelessWidget {
  const HomePage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    print('build home');
    return Center(child: Text('Home'));
  }
}

class SettingsPage extends StatelessWidget {
  const SettingsPage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    print('build settings');
    return Center(child: Text('Settings'));
  }
}

Make sure the IndexedStack children widget list is constant. This will prevent the widgets from rebuilding when setState() is called.

IndexedStack(
  index: _currentIndex,
  children: const [
    HomeWidget(),
    SettingsWidget(),
  ],
),

The problem with IndexedStack is that all the widgets will be built at the same time when IndexedStack is initialized. For small widgets (like the example above), it won't be a problem. But for big widgets, you may see some performance issues.

Consider using the lazy_load_indexed_stack package. According to the package:

[LazyLoadIndexedStack widget] builds the required widget only when it is needed, and returns the pre-built widget when it is needed again

Again, make sure the LazyLoadIndexedStack children widgets are constant, otherwise they will keep rebuilding when setState is called.

0

If, you just need to remember the scroll position inside a list, the best option is to simply use a PageStoreKey object for the key property:

  @override
  Widget build(BuildContext context) {
    return Container(
      child: ListView.builder(
        key: PageStorageKey<String>('some-list-key'),
        scrollDirection: Axis.vertical,
        shrinkWrap: true,
        itemCount: items.length,
        itemBuilder: (BuildContext context, int index) {
          return GestureDetector(
            onTap: () => _onElementTapped(index),
            child: makeCard(items[index])
          );
        },
      ),
    );
  }

According to https://docs.flutter.io/flutter/widgets/PageStorageKey-class.html, this should work on ANY scrollable widget.

LucaRoverelli
  • 1,116
  • 1
  • 9
  • 15
  • It only works when the scroll is completed. If you scroll and change the screen before the scroll stops, the scroll position wont be saved. – Gabryel Ferreira Jun 23 '19 at 23:32
-1

if i use the IndexedStack in the body it is loading only the main screen content not every other screen which is present in the bottom nativation bar.

  • 1
    As it’s currently written, your answer is unclear. Please [edit] to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Jun 08 '22 at 15:44
-5

Using IndexedStack with bloc pattern solved everthing.

Pulkit
  • 121
  • 1
  • 2