70

I have a series of stateful widgets in a PageView managed by a PageController. I am using pageController.jumpToPage(index) to switch pages. When switching pages all state appears to be lost in the widget, as if it was recreated from scratch. I have tried using keepPage: true in the PageController but that doesn't seem to have any effect. Is this the intended behavior of a PageView or am I doing something wrong? Any suggestions appreciated, thanks!

Matt Hall
  • 2,569
  • 2
  • 23
  • 33

3 Answers3

159

use with AutomaticKeepAliveClientMixin to your SubPage.

then @override bool get wantKeepAlive => true; here is a sample

import 'package:flutter/material.dart';

void main() => runApp(new MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'Flutter Demo',
      theme: new ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: new MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => new _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return DefaultTabController(
      length: 4,
      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)),
              new Tab(
                icon: new Icon(Icons.airplanemode_active),
              )
            ],
          ),
        ),
        body: new TabBarView(children: [
          new OnePage(color: Colors.black,),
          new OnePage(color: Colors.green,),
          new OnePage(color: Colors.red,),
          new OnePage(color: Colors.blue,),
        ]),
      ),
    );
  }
}

class OnePage extends StatefulWidget {
  final Color color;

  const OnePage({Key key, this.color}) : super(key: key);

  @override
  _OnePageState createState() => new _OnePageState();
}

class _OnePageState extends State<OnePage> with AutomaticKeepAliveClientMixin<OnePage> {
  @override
  Widget build(BuildContext context) {
    super.build(context);
    return new SizedBox.expand(
      child: new ListView.builder(
        itemCount: 100,
        itemBuilder: (context, index) {
          return new Padding(
            padding: const EdgeInsets.all(10.0),
            child: new Text(
              '$index',
              style: new TextStyle(color: widget.color),
            ),
          );
        },
      ),
    );
  }

  @override
  bool get wantKeepAlive => true;
}
JokerHYC
  • 1,709
  • 2
  • 10
  • 3
  • 4
    Not working when you have this: https://stackoverflow.com/questions/59693451/flutter-googlemap-in-a-pageview-build-only-once – lacas Jan 11 '20 at 10:12
49

Write custom widget:

import 'package:flutter/material.dart';

class KeepAlivePage extends StatefulWidget {
  KeepAlivePage({
    Key key,
    @required this.child,
  }) : super(key: key);

  final Widget child;

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

class _KeepAlivePageState extends State<KeepAlivePage>
    with AutomaticKeepAliveClientMixin {
  @override
  Widget build(BuildContext context) {
    /// Dont't forget this
    super.build(context);

    return widget.child;
  }

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

And use in pageview:

import 'package:flutter/material.dart';
import 'keep_alive_page.dart';
class PageViewDemo extends StatefulWidget {
  const PageViewDemo({Key key}) : super(key: key);

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

class _PageViewDemoState extends State<PageViewDemo> {
  @override
  Widget build(BuildContext context) {
    return PageView(
      children: [
        KeepAlivePage(child:Page1()),
        KeepAlivePage(child: Page2()),
        KeepAlivePage(child:Page3()),
      ],
    );
  }
}
O Thạnh Ldt
  • 1,103
  • 10
  • 11
  • super.build(context), that is the key that i was missing, thank you. – Kaki Master Of Time Dec 27 '20 at 16:58
  • 1
    i like this approach more, it reduces the complexity in the actual page widgets. you can also use a stateless widgets. thank you! – rasitayaz Nov 10 '22 at 11:33
  • 1
    Saved my life!!! – Nicks Jun 23 '23 at 07:09
  • Thank you so much! There were a few minor changes I had to make which I suspect was due to having a newer version of Flutter (please excuse the ////////// below as there are limited formatting options in comments). In the custom widget (keep_alive_page.dart), an error appeared using your code, so I changed: ////////// Key key, @required this.child ====> Key? key, required this.child ////////// The other change was made in my main.dart file: const PageViewDemo({Key key}) ====> const PageViewDemo({Key? key}) – Nick Dev Jul 19 '23 at 06:34
35

keepPage: true is the default behavior; it means that the PageController will remember what page it's on if it is destroyed and recreated. This isn't what you want.

Instead, pass a page-specific PageStorageKey to the constructor of the pages. This helps Flutter give a unique storage bucket to your page. Then in your State that you want to have restored to its previous condition, you can use PageStorage.of(context) to get the storage bucket, which you can read values from in initState and write values to when they change. You can see an example in ExpansionTile.

Collin Jackson
  • 110,240
  • 31
  • 221
  • 152
  • This seems odd that this would be required. So I should be saving the scroll position of every widget in a PageView, or a navigation stack if one exists and stuff like that? Is there a way to just have the widget left as-is when I page away? I should mention that one of the widgets in my PageView does _not_ lose it's state when you page away, so it could just be that I'm doing something dumb overall. – Matt Hall Aug 30 '17 at 02:38
  • Scroll positions are maintained internally by the framework, see the usage of `PageStorage` in the [`ScrollPosition` class](https://github.com/flutter/flutter/blob/6655074b370d39150d2ed28f67851e3bce7013ab/packages/flutter/lib/src/widgets/scroll_position.dart#L303). If other state is being maintained, it may be because it's being stored at a higher level of the tree. – Collin Jackson Aug 30 '17 at 03:44
  • 1
    Thank you, setting a PageStorage key for the widget containing the ListView seems to have done the trick in terms of saving scroll position. I must admit that it's still very confusing to me why manually storing portions of state is necessary when the rest of state handlig is so amazingly automatic. I guess I don't understand why the state object that the StatefulWidget creates isn't just stored and then reused automatically. – Matt Hall Aug 30 '17 at 14:35
  • PageViews can have potentially unlimited pages, so Flutter needs some mechanism for cleaning up the offscreen pages. – Collin Jackson Aug 30 '17 at 15:12
  • 1
    I understand PageView can have multiple pages, just like ViewPager but ViewPager provides you with the function where you can define that how many pages you want to keep in the memory. I believe we need something similar in PageView. This is overkill, especially when I am downloading the data from the backend soI need to put more checks so it doesn't fetch the data everytime. – Varundroid Jun 15 '18 at 04:39
  • @Varundroid that´s why you should implement a Repository Pattern. – 最白目 Jul 17 '18 at 13:35
  • Any help on this? https://stackoverflow.com/questions/59693451/flutter-googlemap-in-a-pageview-build-only-once – lacas Jan 11 '20 at 10:12