4

I am learning flutter and I am working with tabBars and I am having an issue with saving the state. I have put a small working example of my issue below. Basically, there is a button and a stateful counter. When I click the button, I see the text field update correctly. But, when I switch to a different tab and come back, the text field is back to zero.

I have found if i move the following line outside of _CounterState so its defined at the top level of the file, then, it works correctly. When I switch tabs, the counter stays at the correct count when I switch back

int _counter = 0;

I don't feel like this is the appropriate way to do this and all of the examples I have seen have the variable inside of the class. Can anyone give me any insights? Why would it reset if it is inside the class? Am I supposed to keep it outside the class? Below is the simplified full example.

import 'package:flutter/material.dart';

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

class TabBarDemo extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      home: new DefaultTabController(
        length: 3,
        child: new Scaffold(
          appBar: new AppBar(
            bottom: new TabBar(
              tabs: [
                new Tab(icon: new Icon(Icons.directions_car)),
                new Tab(icon: new Icon(Icons.directions_transit)),
                new Tab(icon: new Icon(Icons.directions_bike)),
              ],
            ),
            title: new Text('Tabs Demo'),
          ),
          body: new TabBarView(
            children: [
              new Counter(),
              new Icon(Icons.directions_transit),
              new Icon(Icons.directions_bike),
            ],
          ),
        ),
      ),
    );
  }
}

class Counter extends StatefulWidget {
  @override
  _CounterState createState() => new _CounterState();
}

class _CounterState extends State<Counter> {
  int _counter = 0;

  void _increment() {
    setState(() {
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return new Row(
      children: <Widget>[
        new RaisedButton(
          onPressed: _increment,
          child: new Text('Increment'),
        ),
        new Text('Count: $_counter'),
      ],
    );
  }
}

Below is the example with the counter moved outside of the class

import 'package:flutter/material.dart';

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

class TabBarDemo extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      home: new DefaultTabController(
        length: 3,
        child: new Scaffold(
          appBar: new AppBar(
            bottom: new TabBar(
              tabs: [
                new Tab(icon: new Icon(Icons.directions_car)),
                new Tab(icon: new Icon(Icons.directions_transit)),
                new Tab(icon: new Icon(Icons.directions_bike)),
              ],
            ),
            title: new Text('Tabs Demo'),
          ),
          body: new TabBarView(
            children: [
              new Counter(),
              new Icon(Icons.directions_transit),
              new Icon(Icons.directions_bike),
            ],
          ),
        ),
      ),
    );
  }
}

class Counter extends StatefulWidget {
  @override
  _CounterState createState() => new _CounterState();
}

int _counter = 0; //<-- MOVED OUTSIDE THE _CounterState CLASS
class _CounterState extends State<Counter> {


  void _increment() {
    setState(() {
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return new Row(
      children: <Widget>[
        new RaisedButton(
          onPressed: _increment,
          child: new Text('Increment'),
        ),
        new Text('Count: $_counter'),
      ],
    );
  }
}
casmang
  • 375
  • 1
  • 6
  • 17
  • Possible duplicate of [Flutter: Default Tab Bar Controller does not maintain state after swipe](https://stackoverflow.com/questions/50979157/flutter-default-tab-bar-controller-does-not-maintain-state-after-swipe) – Johnykutty Apr 24 '19 at 09:26

3 Answers3

12

As _CounterState widget is built everytime you go to the given TabView you'll need to put _counter variable in the state configuration class (Counter).

class Counter extends StatefulWidget {
  int _counter = 0;
  @override
  _CounterState createState() => new _CounterState();
}

class _CounterState extends State<Counter> {
  void _increment() {
    setState(() {
      widget._counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return new Row(
      children: <Widget>[
        new RaisedButton(
          onPressed: _increment,
          child: new Text('Increment'),
        ),
        new Text('Count: ${widget._counter}'),
      ],
    );
  }
}
Zroq
  • 8,002
  • 3
  • 26
  • 37
  • It's interesting that I typically see examples with the variables in the _CounterState widget. How did you know that you had to use `widget` before the counter as in `widget._counter++`? I don't think I would have known that. – casmang Jun 15 '18 at 21:36
  • 1
    When accessing variables from the state controlling class you use the `widget` variable. – Zroq Jun 15 '18 at 21:44
  • 2
    wow! saved my day... but as a side note, now `StatefulWidget` class marked as immutable so it's better you don't define a not final variable in it – Mahdi Zakizadeh Mar 27 '20 at 19:35
4

As I used one solution AutomaticKeepAliveClientMixin

You need to use this mixin with your state class of StateFullWidget.

you need to pass true to wantKeepAlive getter method.

class SampleWidget extends StatefulWidget {
  @override
  _SampleWidgetState createState() => _SampleWidgetState();
}

class _SampleWidgetState extends State<SampleWidget> with AutomaticKeepAliveClientMixin{
  @override
  Widget build(BuildContext context) {
    super.build(context);
    return Container();
  }

  @override
  // TODO: implement wantKeepAlive
  bool get wantKeepAlive => true;
}

This will save your state and stop your widget to recreate again. I have used it with Tabbar and PageView and it's working fine.

0

put the variable in that statefulwidget and then call it every time as "widget.variable_name"