What is setState() in flutter and when to use it?
Tags: flutter, mobile, beginners, tutorial
Published: 2021-06-20 12:05:14

When working on frontend applications, a common use case is "Updating the screen's UI dynamically". `setState` is one of the ways to accomplish this in flutter.  

## Overview

- [Tech Terms](#tech-terms)
- [What is a State Object in flutter](#state)
  - [Fun Fact](#fun-fact-1)
- [Example code](#example-code)
- [When to use setState](#setsate-use)
- [Going one step ahead](#more)  
- [Summary](#summary)
- [Final Note](#final-note)
  
## Tech Terms <a id="tech-terms"></a>

These are some common terms used in flutter. The concepts apply to other frameworks also, although each framework has its own technical term for them.

* Widget: Any UI component on the screen is called a Widget in flutter. A Widget can have its own Widgets, like a tree structure.

* StatefulWidget: A Widget that can change dynamically. Generally used when we want to modify something on the screen's UI.

## What is a State Object in flutter? <a id="state"></a>

`setState` is called inside a State class. Let's understand this in detail.  
State is simply the information of a StatefulWidget. Every [StatefulWidget](https://api.flutter.dev/flutter/widgets/StatefulWidget-class.html) has a State Object. This State Object keeps a track of the variables and functions that we define inside a StatefulWidget. 

> State Object is actually managed by corresponding Element Object of the Widget, but for this blog, we will only focus on the Widget part. If you don't know what [Element](https://api.flutter.dev/flutter/widgets/Element-class.html) Object is, I'll encourage you to read about it. It's not required to know for this blog though.

```dart
class MyWidget extends StatefulWidget { // immutable Widget
  @override
  _MyWidgetState createState() => _MyWidgetState();
  // creating State Object of MyWidget
}

class _MyWidgetState extends State<MyWidget> { // State Object
  @override
  Widget build(BuildContext context) {
    return Container();
  }
}
```

Since StatefulWidget itself is immutable (cannot be modified), we use [State Object](https://api.flutter.dev/flutter/widgets/State-class.html) to modify the UI.  
We tell this State Object to update our screen's UI using a function called `setState()`.

#### Function Definition
```dart
void setState(VoidCallback fn) {
  ...
}
```

This `setState()` takes a function as it's parameter.  
`VoidCallback` is just a fancy way of saying: `void Function()`

```dart
typedef VoidCallback = void Function();
```

### Example

We'll create a simple counter app (I know it's very common but bear with me). 
- This will have a variable `counter` initialized with `0`.
- We will display this `counter` on the screen inside the `Text` Widget.
- Next, we'll have a button that increases this `counter`'s value by `1`.
- The UI should be updated with the new value of `counter`.

#### Let's see the steps written above one by one.

###### When the Widget is created, the value of the counter is 0

```dart
int counter = 0;
```

###### Displaying value of counter on screen

```dart
Widget build(BuildContext context){
  return 
       ...
       Text(`counter value: $counter`)
       ...
}
```

###### When we click on a button, we increment the value of the counter

```dart
onTap: () {

  // passing an anonymous function to setState
  // that increments counter's value by 1
  counter++;
},
```
###### Update the UI

```dart
onTap: () {

  // passing an anonymous function to setState
  // that increments counter's value by 1
  // and update the UI
  setState(() {
    counter++;
  });
},
```

#### #Fun Fact : <a id="fun-fact-1"></a> 
We can update our variables first and then call the `setState()` function, since `setState` just informs the underlying framework that 
> "update this Widget's UI in next frame" (`marks it dirty`).

The underlying framework will use the last value that is defined before calling the `setState` function. 

##### This is the same as above

```dart
onTap: () {
  counter++;
  setState(() {});
},
```

## Example Code <a id="example-code"></a>

```dart
import 'package:flutter/material.dart';

class MyWidget extends StatefulWidget {   // widget class

  const MyWidget({Key? key}) : super(key: key);

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

class _MyWidgetState extends State<MyWidget> {  // state class
  int counter = 0; // initializing counter

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        crossAxisAlignment: CrossAxisAlignment.stretch,
        children: [

          // displaing the counter
          Text(
            'counter value: $counter',
            textAlign: TextAlign.center,
          ),
          TextButton(
            onPressed: () {

              //incrementing counter
              setState(() {
                counter++;
              });
            },
            child: const Text('Tap here to increment counter'),
          )
        ],
      ),
    );
  }
}
```

## When to use setState() ? <a id="setsate-use"></a>

*When we want to change the UI of the screen.*

We don't need to call `setState` every time we change a variable. We call `setState` only when we want the change in a variable to reflect on the UI of the screen.

For instance, say you have a form containing a text field and a button to submit it.

```dart
import 'package:flutter/material.dart';

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

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

class _MyFormState extends State<MyForm> {
  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        
        // text field
        Form(
          child: TextFormField(),
        ),

        // submit button
        OutlinedButton(
          onPressed: () {},
          child: const Text('Submit'),
        ),
      ],
    );
  }
}
```

User types in the text field and clicks on submit button. Then we display that text field's text below the submit button.

```dart
import 'package:flutter/material.dart';

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

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

class _MyFormState extends State<MyForm> {
  // we'll use this to save the text user types
  String userText = "";

  // this will keep track of submit button's tapped action/event
  // and display the userText below submit button
  bool hasSubmitted = false;

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Form(
          child: TextFormField(

            // triggered when we save the form
            onSaved: (value) {
              // store the text-field's value into
              // the userText variable
            },
          ),
        ),
        OutlinedButton(

          // this is triggered whenever we click on button  
          onPressed: () {
            // validate and save the form
            // display the text below the button
          },
          child: const Text('Submit'),
        ),

        // this will display the userText only
        // if user has clicked on submit button
        if (hasSubmitted) Text(userText)
      ],
    );
  }
}
```

Steps:

1. User types in the text-field
1. User clicks submit button
1. `onPressed` function of submit button is triggered
1. Inside `onPressed` function:
    1. Validate and save the form
    1. This will trigger the `onSaved` function in the `TextFormField`
    1. Inside `onSaved` function:
        1. Store the text field's value in the `userText` variable
    1. Update `hasSubmitted` variable with `true`

Implementation:

```dart
import 'package:flutter/material.dart';

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

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

class _MyFormState extends State<MyForm> {
  String userText = "";

  bool hasSubmitted = false;

  // for getting access to form
  final _formKey = GlobalKey<FormState>();

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Form(
          // attaching key to form
          key: _formKey,
          child: TextFormField(
            onSaved: (value) {
              /// updating userText variable
              if (value != null) userText = value;
            },
          ),
        ),
        OutlinedButton(
          onPressed: () {
            // validating form
            if (!_formKey.currentState!.validate()) {
              return;
            }

            // saving form
            _formKey.currentState!.save();

            // updating hasSubmitted
            hasSubmitted = true;
          },
          child: const Text('Submit'),
        ),
        if (hasSubmitted) Text(userText)
      ],
    );
  }
}
```

There's one small step left.
When you run this program, you'll notice that nothing happens when we click on submit button.  
Here `setState` comes to the rescue!

Now the question is where should we call it?  
There are two places in the program where we are updating variables.
- inside `onSaved` function
- insided `onPressed` function

So either one of these or both places should be the answer.

Let's ask one question to ourselves.    
"On which variable update, do I want to update the UI of the screen?"  
Is it `userText` inside `onSaved` function   
or  
`hasSubmitted` inside `onPressed` function?  

You got it right!  
It's inside the `onPressed` function after the `hasSubmitted` variable has been updated.

```dart
...
onPressed: () {
  // validating form
  if (!_formKey.currentState!.validate()) {
    return;
  }

  // saving form
  _formKey.currentState!.save();

  // updating hasSubmitted
  hasSubmitted = true;

  setState(() {});
},
...
```

Again, this is same as below:

```dart
onPressed: () {
  // validating form
  if (!_formKey.currentState!.validate()) {
    return;
  }

  // saving form
  _formKey.currentState!.save();

  setState(() {
    // updating hasSubmitted
    hasSubmitted = true;
  });
},
```

Why use `setState` here?  
In our logic, we used `hasSubmitted` variable as a condition to show the `userText`. So only after updating `hasSubmitted` value, does the UI show our desired result.

## Going one step ahead <a id="more"></a>:

* What happens when you use the `setState` inside the `onSaved` function only?

```dart
...
onSaved: (value) {
      /// updating userText variable
      if (value != null) userText = value;
      setState(() {});
    },
  ),
),
OutlinedButton(
  onPressed: () {
// validating form
    if (!_formKey.currentState!.validate()) {
      return;
    }

// saving form
    _formKey.currentState!.save();

// updating hasSubmitted
    hasSubmitted = true;
  },
...
```

It works here also. Surprise!!  
But why? This goes against everything we've read so far in this blog.  

So here's what happens.  
When we call `setState`, the Widget inside we called it is marked as `dirty`.  
Now whenever the framework actually rebuilds the UI of the screen, it will take into account all the latest values of the respective variables and paint the pixels on the screen.

> This happens 60 times per second usually, which is the frame per second ([fps](https://flutter.dev/docs/perf/rendering/ui-performance#:~:text=Flutter%20aims%20to%20provide%2060,UI%20doesn't%20render%20smoothly)) rate of flutter. That means approximately every 16ms (1000/60 ms).

If there is any other change until the next frame renders, those changes will also be reflected on the screen's UI.

The change in `hasSubmitted` variable falls under such case.

How do we verify it?  
Let's add print statements and see exactly when does the UI actually rebuild.

```dart
import 'package:flutter/material.dart';

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

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

class _MyFormState extends State<MyForm> {
  String userText = "";

  bool hasSubmitted = false;

  // for getting access to form
  final _formKey = GlobalKey<FormState>();

  @override
  Widget build(BuildContext context) {
    print('Widget build called');

    return Column(
      children: [
        Form(
          // attaching key to form
          key: _formKey,
          child: TextFormField(
            onSaved: (value) {
              print('inside save');

              if (value != null) userText = value;

              print('hasSubmitted value before setState: $hasSubmitted');

              setState(() {});

              print('hasSubmitted value after setState: $hasSubmitted');
            },
          ),
        ),
        OutlinedButton(
          onPressed: () async {
            print('button clicked: ->');

            // validating form
            if (!_formKey.currentState!.validate()) {
              return;
            }

            print('before calling save');

            // saving form
            _formKey.currentState!.save();

            print('after calling save');

            print('hasSubmitted value after calling save: $hasSubmitted');

            // updating hasSubmitted
            hasSubmitted = true;

            print(
                'hasSubmitted value after updating hasSubmitted: $hasSubmitted');
          },
          child: const Text('Submit'),
        ),
        if (hasSubmitted) Text(userText)
      ],
    );
  }
}
```

With these print statements, we can see the order in which the flutter framework updates the UI.  
Let's write something in the text field and click on submit button.

Inside console:

```dart
Widget build called
button clicked: ->
before calling save
inside save
hasSubmitted value before setState: false
hasSubmitted value after setState: false
after calling save
hasSubmitted value after calling save: false
hasSubmitted value after updating hasSubmitted: true
Widget build called
```

Clearly, the Widget is built after the `hasSubmitted` is set to `true`.

So the major question is: "Why can't we use `setState` inside `onSaved` function instead of inside `onPressed` function, it seems to work fine."

Because this method doesn't work for all the use cases. And it is also logically wrong.
When you'll come back to refactor the code (like for adding a new feature), things might not work as expected. Debugging the code will also be difficult since the problem is in the old code.  
Let's see an example of such a use case. 

> Okay, we're almost done. This is the most important part. We've been building up to this point since the starting of this blog.

Let's go back to the steps of this example and add one more thing to it.
* After saving the form, say we want to make an API call to send the data that the user typed to a server. For simplicity, let's say we're going to do some computation with the `userText` data which will take a minimum of 20ms (this can be any duration of your choice).

This is a practical example. Usually, we do communicate with our application's backend server.

Does our desired result still happen?

```dart
onPressed: () async {
  print('button clicked: ->');

  // validating form
  if (!_formKey.currentState!.validate()) {
    return;
  }

  print('before calling save');

  // saving form
  _formKey.currentState!.save();

  print('after calling save');

  print('hasSubmitted value after calling save: $hasSubmitted');

  /// some computation that takes 20ms
  await Future.delayed(const Duration(milliseconds: 20), () {});

  // updating hasSubmitted after 20ms
  hasSubmitted = true;

  print(
      'hasSubmitted value after updating hasSubmitted: $hasSubmitted');
},
```

See inside `onPressed` function. We're `awating` a `Future` and then updating the `hasSubmitted` value.   
> If you don't know what await, async and Future means, then just think that we're basically saying to program, "Hey flutter framework, we're gonna do some task that'll probably take some time. Please do it in the next iteration."
I'll encourage you to read about [asynchronous programming](https://dart.dev/codelabs/async-await). This concept is not exclusive to dart.  
    
##### #Note: We can also use a [Timer](https://api.dart.dev/stable/2.12.0/dart-async/Timer-class.html) to get the same effect, but here we'll use [Future](https://api.dart.dev/stable/2.13.3/dart-async/Future-class.html). 

Inside console:

```dart
Widget build called
button clicked: ->
before calling save
inside save
hasSubmitted value before setState: false
hasSubmitted value after setState: false
after calling save
hasSubmitted value after calling save: false
Widget build called
hasSubmitted value after updating hasSubmitted: true
```

Now, we don't see the UI updates on the screen. And according to our print statements order, `hasSubmitted` variable is updated after the Widget has been rebuilt.

Reason?
> Warning: This includes some advanced topics. I'll try to explain it as simply as possible.

There is a [queue](https://en.wikipedia.org/wiki/Queue_%28abstract_data_type%29) of microtasks. One by one all the tasks are performed by dart. When we use `await`, we tell dart to first complete the current task (waiting for 20 ms), then move on to the next one in the queue.  
So although some tasks (like rendering of the screen and waiting for 20 ms) are being performed concurrently, the tasks below the `await` keyword (updating `hasSubmitted` variable) will not be performed till the current task is completed.

So, when the framework actually rendered the `dirty` Widget(MyForm), `hasSubmitted` variable's value was not updated. Hence we don't see our typed text below the submit button.

Something to search about:   
There is also an `event queue`. When we don't wait for the Future to complete but want to continue on to the next microtask, the task in Future is added to `event queue`.

Want to experiment more?  
Try changing the duration to 0 seconds.

The UI still doesn't update as desired.

> If you wanna dig deep into why after encountering the `await` keyword, does the code below it doesn't run synchronously even if the duration is 0 seconds, then read about the [event loop](https://dart.cn/articles/archive/event-loop) in dart. There are other resources also that you can easily find on the internet. This concept is not exclusive to dart.

With this new use case (using a Future), our desired output is not achieved. Below is the short summary.

```dart
   /// Approach 1: this is good (recommended)
   setState((){
     hasSubmitted = true;
   });

   ...

   /// Approach 2: this is also good
   hasSubmitted = true;
   setState((){});

   ...

   /// Approach 3: this is not good
   setState((){});
   hasSubmitted = true;  
```

* What happens when we use `setState` inside both the functions?

This case is dangerous.   
Since our desired result is achieved, we overlook the one extra call to `setState` inside the `onSaved` function.

I hope now you got an idea of when and when not to use `setState`.

***

Since this was a very simple example with only two variables to think of, it's easy to implement our logic into code.

When the program becomes bigger and complicated, keeping track of updating the variables and UI becomes cumbersome.  
Then we use a mix of StatefulWidget's `setState` and/or some other [state management](https://flutter.dev/docs/development/data-and-backend/state-mgmt/options) solution.

I'll encourage you to read flutter official [docs](https://flutter.dev/docs/development/ui/interactive) and build apps to get a grip on this.

<!-- Add a section: What we learned (Kind of a summary)! -->

## Summary <a id="summary"></a>
- `setState` is a way to dynamically change the UI.
- We call it inside the State Object class of the StatefulWidget.
- Calling `setState` marks the corresponding Widget `dirty`. When flutter builds the next frame (approx. every 16ms), it renders the Widget according to the latest values of the State Object.
- Where we call `setState` matters a lot.
- There are other state management solutions also.

## Final Note <a id="final-note"></a>
Thank you for reading this article. If you enjoyed it, consider sharing it with other people.  
If you find any mistakes, please let me know.  
Feel free to share your opinions below.