8

I've create simple PageView app to test multiple pages.

import 'package:flutter/material.dart';

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

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

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

  final String title;

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

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    final firstPage = FirstPage(key: Key("FirstPage"));
    final secondPage = SecondPage(key: Key("SecondPage"));

    debugPrint("_MyHomePageState.build");
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: PageView(
        children: <Widget>[
          firstPage,
          secondPage,
        ],
      ),
    );
  }
}

class FirstPage extends StatelessWidget {
  FirstPage({Key key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    debugPrint("FirstPage.build");
    return Container(
      child: Center(
        child: Text("First Page"),
      ),
    );
  }
}

class SecondPage extends StatelessWidget {
  SecondPage({Key key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    debugPrint("SecondPage.build");
    return Container(
      child: Center(
        child: Text("Second Page"),
      ),
    );
  }
}

Even thought _MyHomePageState.build has been shown only once, FirstPage.build and SecondPage.build were printed on every page changes.

What I'd like to prevent unnecessary page draw, how can I accomplish this?

Hongseok Yoon
  • 3,148
  • 8
  • 36
  • 51
  • Take a look at this answer https://stackoverflow.com/questions/52249578/how-to-deal-with-unwanted-widget-build ;) – Martyns Aug 29 '19 at 07:02

3 Answers3

17

You can achieve so by using

1. const keyword

  • Make your widgets accept to be const:

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

    @override
    Widget build(BuildContext context) {
      debugPrint("FirstPage.build");
      return Container(
        child: Center(
          child: Text("First Page"),
        ),
      );
    }
    

    }

  • and call it with const keyword:

      return Scaffold(
        appBar: AppBar(
          title: Text(widget.title),
        ),
        body: PageView(
          children: <Widget>[
            const firstPage(),
            const secondPage(),
          ],
        ),
      );
    

2. AutomaticKeepAliveClientMixin

  • Convert your StatelessWidget to StatefullWidget.
class FirstPage extends StatefulWidget {
  FirstPage({Key key}) : super(key: key);

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

class _FirstPageState extends State<FirstPage> {
  @override
  Widget build(BuildContext context) {
    debugPrint("FirstPage.build");
    return Container(
      child: Center(
        child: Text("First Page"),
      ),
    );
  }
}
  • Extends AutomaticKeepAliveClientMixin on StatefullWidget created State.
class _FirstPageState extends State<FirstPage> with AutomaticKeepAliveClientMixin {
  • Call super on the build method.
@override
  Widget build(BuildContext context) {
    super.build(context);
    debugPrint("FirstPage.build");
    return Container(
      child: Center(
        child: Text("First Page"),
      ),
    );
  }
  • Override wantKeepAlive getter with true returned value.
  @override
  bool get wantKeepAlive => true;

And then your widget tree won't dispose of this widget so it won't rebuild over and over.

Code Example:

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

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

class _FirstPageState extends State<FirstPage>
    with AutomaticKeepAliveClientMixin {
  @override
  Widget build(BuildContext context) {
    super.build(context);
    debugPrint("FirstPage.build");
    return Container(
      child: Center(
        child: Text("First Page"),
      ),
    );
  }

  @override
  bool get wantKeepAlive => true;
}

3. MVVM Architecture with any State-management solution you like

It will save your state on ViewModel away from the View, so your UI can rebuild itself anytime it wants with no worries about your State because the ViewModel is still the same.

Abdelazeem Kuratem
  • 1,324
  • 13
  • 20
  • except for the case, when I swipe left on the first page it refreshes all widgets in the PageView – th_lo May 06 '21 at 18:16
  • AutomaticKeepAliveClientMixin solved it for me. I wonder why using a stateless widget didn't work for me. I couldn't use const when declaring an instance as it was dependent on the page index (was using PageVIew.builder), but still, it's stateless... – Johanna Apr 11 '22 at 18:25
1

You should always imagine that your build() methods (for both StatefulWidget and StatelessWidget) are being called 60 times per second, so they should be simple and idempotent. Anything else should be moved into a StatefulWidget initState() and friends.

Randal Schwartz
  • 39,428
  • 4
  • 43
  • 70
  • add log into build() method and you will see it doesn't call 60 times per second, may be once or two times (not per second) –  Aug 29 '19 at 11:08
  • 3
    I said "imagine". And there are optimizations so that it doesn't actually happen (except when there are animations active). But your build() should still be both fast and idempotent (no side effects). – Randal Schwartz Aug 30 '19 at 16:58
-4

It's easy!

pageController can help you.

Just in your _MyHomePageState

Declare final pageController = PageController(keepPage: false);

And in your PageView

PageView(
        controller: pageController,
        children: <Widget>[
          firstPage,
          secondPage,
        ],
      )

Good Luck.

mafanwei
  • 51
  • 4
  • You say:FirstPage.build and SecondPage.build were printed on every page changes. My method can make when you switch to FirstPage, FirstPage.build will run,when you switch to SecondPage, SecondPage.build will run.Is this not enough? – mafanwei Aug 30 '19 at 03:12
  • I've just add PageController to my PageView with controller: PageView(keepPage: false) and it just prints FirstPage.build and SecondPage.build on every page changes as before. – Hongseok Yoon Aug 30 '19 at 04:45
  • You should follow my guide – mafanwei Aug 30 '19 at 13:23