Flutter: Prevent `build` being called when `Refresher` is used

Viewed 339

I am using the pull_to_refresh package.

I am having a Stack() with two elements. One of them is the Refresher(). When I pull down on my screen, activating the refreshing animation, the build method is called constantly. The problem is that my second Widget in my Stack is quite complex to build and takes some time. I want to prevent having it build all the time when triggering the Refresher-Animation. Is this possible?

My simplified code would look like this:

@override
  Widget build(BuildContext context) {
    return Scaffold(
        key: _scaffoldKey,
        body: Stack(children: <Widget>[
          SafeArea(
              child: Column(children: [
            Expanded(
              child: Container(
                  margin: EdgeInsets.all(0),
                  width: 100.w,
                  constraints: const BoxConstraints.expand(),
                  child: SizedBox(
                              width: 100.w,
                              child: Refresher( refresher stuff )
                              )
                          )
                      )
                  )
              ),
              SecondItem()
          )
      )
 }

Somehow the build method of SecondItem is called all the time. Not the build method of the whole scaffold.

2 Answers

If Your second Item dont want to get refresh, then add it as a separeate class like,

 Expanded(
              child: Container(
                  margin: EdgeInsets.all(0),
                  width: 100.w,
                  constraints: const BoxConstraints.expand(),
                  child: SizedBox(
                              width: 100.w,
                              child: Refresher( refresher stuff )
                              )
                          )
                      )
                  )
              ),
              SecondItem()
          )


class SecondItem extends StatefulWidget {
  @override
  _SecondItemState createState() => _SecondItemState();
}

class _SecondItemState extends State<SecondItem> {
  int counter = 0;
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Text("Your Second Widget"),
    );
  }
}

Now your SecondItem() will not get refresh when you refresh your FirstItem()

Since I wasn't really able to replicate the problem, I build a working structure that implements refreshing.

First the main widget, in my case MyHomePage. This widget implements the Scaffold and Stack with FirstWidget and SecondWidget as children.

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

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Stack(
        children: const <Widget>[
          FirstWidget(),
          SecondWidget(),
        ],
      ),
    );
  }
}

FirstWidget is a statefull widget with a counter in the state. It implements the refresher with a specific controller. Once the refresh is triggered, it calls set state and updates the counter within his state. That should trigger only his build again and not any other. I implemented a Text to show the counter value increasing at each refresh, and a Print to expose the build.

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

  @override
  State<FirstWidget> createState() => _FirstWidgetState();
}

class _FirstWidgetState extends State<FirstWidget> {
  late int _counter;
  late RefreshController _refreshController;

  @override
  void initState() {
    _counter = 1;
    _refreshController = RefreshController(initialRefresh: false);
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    print('First widget built');
    return SafeArea(
      child: Column(
        children: [
          Container(
            margin: const EdgeInsets.all(0),
            width: double.infinity,
            height: 500,
            color: Colors.red,
            child: SmartRefresher(
              controller: _refreshController,
              onRefresh: () async {
                setState(() {
                  _counter++;
                });
                await Future.delayed(const Duration(milliseconds: 1000));
                _refreshController.refreshCompleted();
              },
            ),
          ),
          Text("Counter: $_counter"),
        ],
      ),
    );
  }
}

Last we got the SecondWidget which is a another simple widget with a print statement. In case of build it writes on the console. When the FirstWidget refresh, the second doesn't build becouse his state has not changed.

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

  @override
  State<SecondWidget> createState() => _SecondWidgetState();
}

class _SecondWidgetState extends State<SecondWidget> {
  @override
  Widget build(BuildContext context) {
    print('Second widget built');
    return const Center(child: Text('Second here!'));
  }
}

Possible cause of your problem.

It could be that when refreshing, you actually are updating the state of a parent widget that, on cascade, causes the re build of your second widget. If state is handled correctly, and your second widget doesn't depends on your first widget state, the refresh should not rebuild the second.

Related