Hongseok Yoon
Hongseok Yoon

Reputation: 3318

How to prevent rebuild StatelessWidget children of PageView

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?

Upvotes: 8

Views: 6752

Answers (3)

Abdelazeem Kuratem
Abdelazeem Kuratem

Reputation: 1706

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.

Upvotes: 20

Randal Schwartz
Randal Schwartz

Reputation: 44056

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.

Upvotes: 1

mafanwei
mafanwei

Reputation: 61

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.

Upvotes: -4

Related Questions