1
头图

The setState function is the most basic way to manage state in a Flutter application. Here are some best practices for keeping your application maintainable.

The StatefulWidget setState function of ---32bd88e24ba11a250d335ceedd99af73--- is a simple way to manage state in a Flutter application. However, when you want your application to work and perform well, you need to avoid several pitfalls. Here are some best practices you should stick to.

What is setState for?

setState is how Flutter emits to rebuild the current widget and its descendants. During the rebuild process, the latest variable values will be used to create the user interface. Let's say a user toggles a switch from on to off. The switch has a backing variable that stores the value, so after the change, it's set to false . The switch itself does not reflect this change until it is rebuilt to the new backing field value.

  • change value
  • call setState()
  • UI has been updated

💡Trick 1: Keep widgets small!

setState triggered a rebuild of the widget you're currently in. If your whole application contains only one widget, then the whole widget will be rebuilt, which will make your application slow. See the example below.

 import 'package:flutter/material.dart';

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

  @override
  State<Home> createState() => _State();
}

class _State extends State<Home> {
  bool _tile1 = false;
  bool _tile2 = false;
  bool _tile3 = false;
  bool _tile4 = false;
  bool _tile5 = false;

  @override
  Widget build(BuildContext context) {
    print("built method Home"); // <-- setState triggers build here!
    return Scaffold(
        appBar: AppBar(title: const Text("Demo")),
        body: Center(
            child: Column(
                crossAxisAlignment: CrossAxisAlignment.center,
                mainAxisAlignment: MainAxisAlignment.center,
                children: <Widget>[
              SwitchListTile(
                  activeColor: Colors.green,
                  inactiveThumbColor: Colors.red,
                  title: Text("Switch is ${_tile1 ? "on" : "off"}"),
                  value: _tile1,
                  onChanged: (_) {
                    setState(() {
                      _tile1 = !_tile1;
                    });
                  }),
              SwitchListTile(
                  activeColor: Colors.green,
                  inactiveThumbColor: Colors.red,
                  title: Text("Switch is ${_tile2 ? "on" : "off"}"),
                  value: _tile2,
                  onChanged: (_) {
                    setState(() {
                      _tile2 = !_tile2;
                    });
                  }),
              SwitchListTile(
                  activeColor: Colors.green,
                  inactiveThumbColor: Colors.red,
                  title: Text("Switch is ${_tile3 ? "on" : "off"}"),
                  value: _tile3,
                  onChanged: (_) {
                    setState(() {
                      _tile3 = !_tile3;
                    });
                  }),
              SwitchListTile(
                  activeColor: Colors.green,
                  inactiveThumbColor: Colors.red,
                  title: Text("Switch is ${_tile4 ? "on" : "off"}"),
                  value: _tile4,
                  onChanged: (_) {
                    setState(() {
                      _tile4 = !_tile4;
                    });
                  }),
              SwitchListTile(
                  activeColor: Colors.green,
                  inactiveThumbColor: Colors.red,
                  title: Text("Switch is ${_tile5 ? "on" : "off"}"),
                  value: _tile5,
                  onChanged: (_) {
                    setState(() {
                      _tile5 = !_tile5;
                    });
                  })
            ])));
  }
}

Here we have 5 SwitchListTile widgets in one Column which are all part of the same widget.

If you toggle any controls, the entire screen is rebuilt. Scaffold , AppBar , Column , ... but just rebuilding the changed widget is enough. Let's look at the next code example:

 import 'package:flutter/material.dart';

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

  @override
  State<Home2> createState() => _State();
}

class _State extends State<Home2> {
  @override
  Widget build(BuildContext context) {
    print("built method Home2");
    return Scaffold(
        appBar: AppBar(title: const Text("Demo")),
        body: Center(
            child: Column(
                crossAxisAlignment: CrossAxisAlignment.center,
                mainAxisAlignment: MainAxisAlignment.center,
                children: const <Widget>[
              Switch(),
              Switch(),
              Switch(),
              Switch(),
              Switch()
            ])));
  }
}

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

  @override
  State<StatefulWidget> createState() => _SwitchState();
}

class _SwitchState extends State<Switch> {
  bool _value = false;

  @override
  Widget build(BuildContext context) {
    print("build method Switch"); // <-- setState triggers build here!
    return SwitchListTile(
        activeColor: Colors.green,
        inactiveThumbColor: Colors.red,
        title: Text("Switch is ${_value ? "on" : "off"}"),
        value: _value,
        onChanged: (_) {
          setState(() {
            _value = !_value;
          });
        });
  }
}

Here we wrap SwitchListTile in a single StatefulWidget . The page looks the same, but if you click any of the switches in this example, only the clicked widget will rebuild.

💡Trick 2: Don't call setState in build method

From Flutter API Documentation

This method is likely to be called every frame and should not have any side effects other than building a widget.

The build method is designed to build the widget tree, so we should keep it that way. Don't do fancy stuff here, it will slow down your application. Calls to setState may trigger additional rebuilds, and in the worst case, you may end up with an exception telling you that a rebuild is currently in progress.

💡Trick 3: Don't call setState in initState method

initState will trigger a rebuild after completion, so there is no need to call setState in this method. This method is intended to initialize state-related properties, such as setting default values or subscribing to a stream. Don't do anything else here!

💡Trick 4: setState() and setState(...) are equal

It's okay to use setState like this

 setState((){
  _text = “Hello”;
});

or like this

 _text = “Hello”;
setState((){});

The result is the same.

💡Trick 5: setState(...) code must be small

Don't do any big calculations inside setState as it will prevent your app from redrawing. See the sample code below:

 setState(() {
  for (var i = 0; i < 10000; i++) print(i);
  _value = true;
});

The widget will rebuild only after the print statement. During this time, your application will not react to user actions, it will perform those actions afterward. Therefore, if the user taps a control multiple times because there is no visual feedback, multiple rebuilds will pile up and make the application slower.

A better approach would be to show a progress indicator while a long running operation is being executed, so the user knows that something is happening and he needs to wait for it to finish.

💡Tip 6: setState(...) code cannot be asynchronous

When running the code

 setState(() async {
  await Future.delayed(const Duration(seconds: 5));
});

You will end up with an exception message like this:

Do the asynchronous operation outside of the method and then call it.

Finish

I hope these insights help you better understand the mechanics of setState in Flutter. Stick with these tips and you'll have fewer problems and a faster application. Source code examples can be found on GitHub .


杭州程序员张张
11.8k 声望6.7k 粉丝

Web/Flutter/独立开发者/铲屎官