Popular Searches
Popular Course Categories
Popular Courses

Stateless Widgets

Flutter Fundamentals

Stateful Widgets in Flutter – Detailed Notes

A StatefulWidget is a Flutter widget whose associated state can change during the lifetime of the widget. Examples include counters, checkboxes, switches, sliders, forms, text fields, loading indicators, and other interactive UI components.

A StatefulWidget itself is immutable. Its mutable data is stored in a separate State object. When that state changes, setState() tells Flutter to rebuild the relevant UI. :contentReference[oaicite:0]{index=0}

Related Flutter Training: JustAcademy Flutter Training | Register for Flutter Course Demo


1. What is a StatefulWidget?

A StatefulWidget is a widget used when part of the user interface needs to change dynamically during the lifetime of the widget.

For example, consider a counter application:

Count: 0

When the user presses a button:

Count: 1

Pressing it again changes the value:

Count: 2

Because the displayed value changes, a StatefulWidget is appropriate for managing this changing state.


2. What is State?

State is information that can change while a widget is being used. Flutter defines state as information that can be read synchronously when the widget is built and may change during the widget's lifetime. :contentReference[oaicite:1]{index=1}

Examples of state include:

  • Counter value
  • Whether a checkbox is selected
  • Whether a switch is enabled
  • Selected tab
  • Selected dropdown value
  • Current slider value
  • Loading status
  • Form input values
  • Whether a password is visible
  • Whether a menu is expanded

3. StatefulWidget vs StatelessWidget

Feature StatelessWidget StatefulWidget
State Does not hold mutable local state Can manage mutable state
State class Not required Requires a separate State class
Dynamic UI Usually depends on immutable configuration and context Can change in response to state changes
setState() Not available Available in the State class
createState() Not required Required
Common examples Text, Icon, simple layouts Checkbox, Slider, TextField, interactive custom widgets

4. Why Do We Need Stateful Widgets?

Applications frequently need to respond to user actions or changing data.

For example:

User taps button
        ↓
State changes
        ↓
setState()
        ↓
build() runs again
        ↓
Updated UI

Without a mechanism for state changes, interactive interfaces would be difficult to build.

Flutter's documentation recommends StatefulWidget when a widget's appearance or data needs to change during its lifetime. :contentReference[oaicite:2]{index=2}


5. Basic Structure of a StatefulWidget

A StatefulWidget normally consists of two classes:

  1. A class extending StatefulWidget.
  2. A corresponding class extending State.
class MyWidget extends StatefulWidget {
  const MyWidget({super.key});

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

class _MyWidgetState extends State {
  @override
  Widget build(BuildContext context) {
    return const Text('Hello');
  }
}

Flutter's framework calls createState() to create the mutable state associated with the widget's location in the tree. :contentReference[oaicite:3]{index=3}


6. Understanding the Two Classes

StatefulWidget Class

class MyWidget extends StatefulWidget {
  const MyWidget({super.key});

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

The StatefulWidget class represents the widget configuration. Its fields are normally immutable.

State Class

class _MyWidgetState extends State {
  int count = 0;

  @override
  Widget build(BuildContext context) {
    return Text('$count');
  }
}

The State object contains mutable values and the build() method that describes the UI.


7. StatefulWidget Architecture

StatefulWidget
      ↓
createState()
      ↓
State Object
      ↓
build()
      ↓
Widget Tree
      ↓
UI

When the state changes:

State changes
      ↓
setState()
      ↓
Flutter schedules rebuild
      ↓
build()
      ↓
Updated Widget Tree
      ↓
Updated UI

8. Simple StatefulWidget Example

import 'package:flutter/material.dart';

class CounterPage extends StatefulWidget {
  const CounterPage({super.key});

  @override
  State createState() => _CounterPageState();
}

class _CounterPageState extends State {
  int count = 0;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Counter'),
      ),
      body: Center(
        child: Text(
          '$count',
          style: const TextStyle(fontSize: 40),
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          setState(() {
            count++;
          });
        },
        child: const Icon(Icons.add),
      ),
    );
  }
}

9. Understanding the Counter Example

The variable:

int count = 0;

stores the current state.

The button changes it:

setState(() {
  count++;
});

The UI reads the value:

Text('$count')

When setState() is called, Flutter schedules the State object for rebuilding, allowing the updated value to appear in the UI. :contentReference[oaicite:4]{index=4}


10. What is setState()?

setState() is a method of the State class. It tells Flutter that internal state has changed and that the affected widget subtree should be rebuilt. :contentReference[oaicite:5]{index=5}

Basic syntax:

setState(() {
  variable = newValue;
});

Example:

setState(() {
  count++;
});

11. Why is setState() Important?

Changing a state variable directly does not by itself tell Flutter that the UI needs to rebuild.

For example:

count++;

The variable may change, but Flutter is not explicitly notified that the change should be reflected in the UI.

Correct approach:

setState(() {
  count++;
});

Flutter's official documentation explains that changing state without calling setState() may leave the displayed UI unchanged because the framework was not notified to schedule a rebuild. :contentReference[oaicite:6]{index=6}


12. StatefulWidget Lifecycle

A StatefulWidget has an associated State object with a lifecycle.

A simplified lifecycle is:

createState()
    ↓
mounted
    ↓
initState()
    ↓
didChangeDependencies()
    ↓
build()
    ↓
setState()
    ↓
build()
    ↓
didUpdateWidget()
    ↓
build()
    ↓
dispose()

The exact callbacks depend on what happens to the widget in the tree, but understanding the major lifecycle methods is important for Flutter development. :contentReference[oaicite:7]{index=7}


13. createState()

createState() creates the State object associated with the StatefulWidget.

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

Flutter calls this method when it needs a State object for that widget at a particular location in the widget tree. :contentReference[oaicite:8]{index=8}


14. initState()

initState() is called when the State object is initialized. It is commonly used for one-time initialization.

@override
void initState() {
  super.initState();

  // Initialization code
}

Typical uses include:

  • Initializing controllers
  • Starting certain one-time operations
  • Setting initial values
  • Subscribing to services or listeners

When overriding initState(), call super.initState(). :contentReference[oaicite:9]{index=9}


15. build()

The build() method describes the UI for the current state.

@override
Widget build(BuildContext context) {
  return Scaffold(
    body: Center(
      child: Text('Hello Flutter'),
    ),
  );
}

When state changes and setState() is used, Flutter can call build() again to reflect the new state. :contentReference[oaicite:10]{index=10}


16. dispose()

dispose() is called when the State object is permanently removed from the widget tree. It is commonly used to clean up resources.

@override
void dispose() {
  controller.dispose();
  super.dispose();
}

Typical cleanup tasks include:

  • Disposing controllers
  • Canceling timers
  • Removing listeners
  • Closing resources
  • Unsubscribing from services

Flutter's documentation recommends cleanup in dispose() for resources such as timers and subscriptions. :contentReference[oaicite:11]{index=11}


17. Complete Lifecycle Example

class ExamplePage extends StatefulWidget {
  const ExamplePage({super.key});

  @override
  State createState() => _ExamplePageState();
}

class _ExamplePageState extends State {
  @override
  void initState() {
    super.initState();
    print('initState called');
  }

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

    return const Scaffold(
      body: Center(
        child: Text('Hello Flutter'),
      ),
    );
  }

  @override
  void dispose() {
    print('dispose called');
    super.dispose();
  }
}

18. StatefulWidget with Boolean State

A Boolean variable is useful for representing two states such as true/false, enabled/disabled, or visible/hidden.

class ToggleExample extends StatefulWidget {
  const ToggleExample({super.key});

  @override
  State createState() => _ToggleExampleState();
}

class _ToggleExampleState extends State {
  bool isActive = false;

  @override
  Widget build(BuildContext context) {
    return Switch(
      value: isActive,
      onChanged: (value) {
        setState(() {
          isActive = value;
        });
      },
    );
  }
}

19. StatefulWidget with Checkbox

class CheckboxExample extends StatefulWidget {
  const CheckboxExample({super.key});

  @override
  State createState() => _CheckboxExampleState();
}

class _CheckboxExampleState extends State {
  bool accepted = false;

  @override
  Widget build(BuildContext context) {
    return Checkbox(
      value: accepted,
      onChanged: (value) {
        setState(() {
          accepted = value ?? false;
        });
      },
    );
  }
}

The checkbox state is stored in accepted.


20. StatefulWidget with Slider

class SliderExample extends StatefulWidget {
  const SliderExample({super.key});

  @override
  State createState() => _SliderExampleState();
}

class _SliderExampleState extends State {
  double value = 50;

  @override
  Widget build(BuildContext context) {
    return Slider(
      value: value,
      min: 0,
      max: 100,
      onChanged: (newValue) {
        setState(() {
          value = newValue;
        });
      },
    );
  }
}

21. StatefulWidget with TextField

Text input often involves changing values during user interaction.

class NameInput extends StatefulWidget {
  const NameInput({super.key});

  @override
  State createState() => _NameInputState();
}

class _NameInputState extends State {
  String name = '';

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        TextField(
          onChanged: (value) {
            setState(() {
              name = value;
            });
          },
        ),
        Text('Hello $name'),
      ],
    );
  }
}

Each time the text changes, the state can be updated and the UI can rebuild.


22. StatefulWidget for Show and Hide UI

class VisibilityExample extends StatefulWidget {
  const VisibilityExample({super.key});

  @override
  State createState() => _VisibilityExampleState();
}

class _VisibilityExampleState extends State {
  bool showMessage = true;

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        if (showMessage)
          const Text('Welcome to Flutter'),
        ElevatedButton(
          onPressed: () {
            setState(() {
              showMessage = !showMessage;
            });
          },
          child: const Text('Toggle'),
        ),
      ],
    );
  }
}

23. StatefulWidget for Dynamic Color

class ColorExample extends StatefulWidget {
  const ColorExample({super.key});

  @override
  State createState() => _ColorExampleState();
}

class _ColorExampleState extends State {
  Color boxColor = Colors.blue;

  void changeColor() {
    setState(() {
      boxColor = boxColor == Colors.blue
          ? Colors.green
          : Colors.blue;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Container(
          width: 150,
          height: 150,
          color: boxColor,
        ),
        ElevatedButton(
          onPressed: changeColor,
          child: const Text('Change Color'),
        ),
      ],
    );
  }
}

24. StatefulWidget for Favorite Button

class FavoriteButton extends StatefulWidget {
  const FavoriteButton({super.key});

  @override
  State createState() => _FavoriteButtonState();
}

class _FavoriteButtonState extends State {
  bool isFavorite = false;

  @override
  Widget build(BuildContext context) {
    return IconButton(
      icon: Icon(
        isFavorite
            ? Icons.favorite
            : Icons.favorite_border,
      ),
      onPressed: () {
        setState(() {
          isFavorite = !isFavorite;
        });
      },
    );
  }
}

25. StatefulWidget for Tab Selection

class TabExample extends StatefulWidget {
  const TabExample({super.key});

  @override
  State createState() => _TabExampleState();
}

class _TabExampleState extends State {
  int selectedIndex = 0;

  @override
  Widget build(BuildContext context) {
    return Row(
      children: [
        ElevatedButton(
          onPressed: () {
            setState(() {
              selectedIndex = 0;
            });
          },
          child: Text(
            selectedIndex == 0 ? 'Selected' : 'Tab 1',
          ),
        ),
        ElevatedButton(
          onPressed: () {
            setState(() {
              selectedIndex = 1;
            });
          },
          child: Text(
            selectedIndex == 1 ? 'Selected' : 'Tab 2',
          ),
        ),
      ],
    );
  }
}

26. Multiple State Variables

A StatefulWidget can manage multiple state variables.

class ProfileSettings extends StatefulWidget {
  const ProfileSettings({super.key});

  @override
  State createState() => _ProfileSettingsState();
}

class _ProfileSettingsState extends State {
  bool notifications = true;
  bool darkMode = false;
  String username = 'Manish';

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Text(username),
        Switch(
          value: notifications,
          onChanged: (value) {
            setState(() {
              notifications = value;
            });
          },
        ),
        Switch(
          value: darkMode,
          onChanged: (value) {
            setState(() {
              darkMode = value;
            });
          },
        ),
      ],
    );
  }
}

27. StatefulWidget and Conditional UI

State can determine which widget should be displayed.

bool isLoggedIn = true;

@override
Widget build(BuildContext context) {
  return isLoggedIn
      ? const Text('Welcome User')
      : const Text('Please Login');
}

The state can be changed using:

setState(() {
  isLoggedIn = !isLoggedIn;
});

28. StatefulWidget and Loading State

A common real-world use of StatefulWidget is displaying a loading indicator while an operation is running.

class LoadingExample extends StatefulWidget {
  const LoadingExample({super.key});

  @override
  State createState() => _LoadingExampleState();
}

class _LoadingExampleState extends State {
  bool isLoading = false;

  Future loadData() async {
    setState(() {
      isLoading = true;
    });

    await Future.delayed(
      const Duration(seconds: 2),
    );

    if (!mounted) return;

    setState(() {
      isLoading = false;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        if (isLoading)
          const CircularProgressIndicator()
        else
          const Text('Data Loaded'),
        ElevatedButton(
          onPressed: isLoading ? null : loadData,
          child: const Text('Load'),
        ),
      ],
    );
  }
}

When asynchronous work finishes, make sure the State object is still mounted before calling setState(). Flutter also recommends canceling work that could trigger updates after disposal when possible. :contentReference[oaicite:12]{index=12}


29. StatefulWidget and Form Validation

Stateful widgets can be used to manage form-related UI state.

class LoginForm extends StatefulWidget {
  const LoginForm({super.key});

  @override
  State createState() => _LoginFormState();
}

class _LoginFormState extends State {
  String email = '';
  String password = '';

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        TextField(
          onChanged: (value) {
            setState(() {
              email = value;
            });
          },
        ),
        TextField(
          obscureText: true,
          onChanged: (value) {
            setState(() {
              password = value;
            });
          },
        ),
        ElevatedButton(
          onPressed: email.isNotEmpty && password.isNotEmpty
              ? () {}
              : null,
          child: const Text('Login'),
        ),
      ],
    );
  }
}

30. StatefulWidget and Parent-Child State Management

Sometimes a parent widget should manage state used by one or more child widgets.

ParentWidget
├── ChildWidget
├── ChildWidget
└── ChildWidget

The parent can maintain shared state and pass values to children through constructors.

ChildWidget(
  isActive: isActive,
  onChanged: updateState,
)

This approach is commonly called lifting state up: the state is moved to the lowest common ancestor that needs to coordinate the relevant widgets. Flutter's interactivity documentation describes parent-managed state as one common state-management approach. :contentReference[oaicite:13]{index=13}


31. StatefulWidget and Callback Communication

A child widget can notify a parent using a callback.

class ChildWidget extends StatelessWidget {
  const ChildWidget({
    required this.onChanged,
    super.key,
  });

  final ValueChanged onChanged;

  @override
  Widget build(BuildContext context) {
    return Switch(
      value: true,
      onChanged: onChanged,
    );
  }
}

Parent:

ChildWidget(
  onChanged: (value) {
    setState(() {
      isActive = value;
    });
  },
)

32. StatefulWidget and const

The StatefulWidget class itself can have a const constructor when its configuration permits it.

class CounterPage extends StatefulWidget {
  const CounterPage({super.key});

  @override
  State createState() => _CounterPageState();
}

The mutable values belong in the State object rather than the widget configuration.


33. StatefulWidget Does Not Mean Every Variable Must Be State

Not every variable inside a State class needs to change.

Only values whose changes should affect the widget's behavior or appearance need to be treated as mutable state.

class Example extends StatefulWidget {
  const Example({super.key});

  @override
  State createState() => _ExampleState();
}

class _ExampleState extends State {
  int count = 0;
  final String title = 'Counter';

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Text(title),
        Text('$count'),
      ],
    );
  }
}

34. StatefulWidget and Widget Properties

Suppose a parent provides a title to a StatefulWidget.

class ProfilePage extends StatefulWidget {
  const ProfilePage({
    required this.title,
    super.key,
  });

  final String title;

  @override
  State createState() => _ProfilePageState();
}

The State object can access the current widget configuration using the widget property.

class _ProfilePageState extends State {
  @override
  Widget build(BuildContext context) {
    return Text(widget.title);
  }
}

When the parent rebuilds with a new configuration for the same location, Flutter can update the State object's widget reference while retaining the State object when appropriate. :contentReference[oaicite:14]{index=14}


35. didUpdateWidget()

didUpdateWidget() can be overridden when a StatefulWidget receives a new widget configuration while its State object is retained.

@override
void didUpdateWidget(covariant ProfilePage oldWidget) {
  super.didUpdateWidget(oldWidget);

  if (oldWidget.title != widget.title) {
    // React to changed configuration.
  }
}

This is useful when the State object needs to respond to changes in widget properties. The State lifecycle includes this callback as part of updates to an existing State object. :contentReference[oaicite:15]{index=15}


36. didChangeDependencies()

didChangeDependencies() is called when an inherited dependency used by the State changes.

@override
void didChangeDependencies() {
  super.didChangeDependencies();

  // Respond to dependency changes.
}

It is also called after initState(). :contentReference[oaicite:16]{index=16}


37. mounted Property

The mounted property indicates whether the State object is currently associated with an element in the widget tree.

This is particularly useful after asynchronous operations:

Future loadData() async {
  await Future.delayed(
    const Duration(seconds: 2),
  );

  if (!mounted) return;

  setState(() {
    // Update state.
  });
}

Calling setState() after the State object has been disposed is an error. :contentReference[oaicite:17]{index=17}


38. setState() Should Not Be Async

The callback passed to setState() should perform the synchronous state update and should not itself be async.

Incorrect:

setState(() async {
  await loadData();
  count++;
});

Better:

await loadData();

if (!mounted) return;

setState(() {
  count++;
});

The Flutter API documentation explicitly states that the callback passed to setState() must not return a Future. :contentReference[oaicite:18]{index=18}


39. Common Mistake: Forgetting setState()

Incorrect:

void increment() {
  count++;
}

Correct:

void increment() {
  setState(() {
    count++;
  });
}

40. Common Mistake: Calling setState() Inside build()

Avoid calling setState() directly from build().

Incorrect:

@override
Widget build(BuildContext context) {
  setState(() {
    count++;
  });

  return Text('$count');
}

This can create unnecessary rebuild cycles and should be avoided.


41. Common Mistake: Heavy Work Inside setState()

Only the actual state mutation should generally be placed inside setState().

Instead of:

setState(() {
  performExpensiveCalculation();
  count++;
  saveDataToDisk();
});

Prefer:

performExpensiveCalculation();

setState(() {
  count++;
});

saveDataToDisk();

Flutter's API documentation recommends keeping setState() focused on the state change rather than unrelated computation or side effects. :contentReference[oaicite:19]{index=19}


42. Common Mistake: Calling setState() After dispose()

Incorrect:

Future loadData() async {
  await Future.delayed(
    const Duration(seconds: 3),
  );

  setState(() {
    dataLoaded = true;
  });
}

If the widget has been removed during the delay, the State object may no longer be mounted.

Safer pattern:

Future loadData() async {
  await Future.delayed(
    const Duration(seconds: 3),
  );

  if (!mounted) return;

  setState(() {
    dataLoaded = true;
  });
}

Whenever possible, cancel work that can trigger updates after disposal rather than relying only on a mounted check. :contentReference[oaicite:20]{index=20}


43. StatefulWidget with Timer

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

class TimerExample extends StatefulWidget {
  const TimerExample({super.key});

  @override
  State createState() => _TimerExampleState();
}

class _TimerExampleState extends State {
  int seconds = 0;
  Timer? timer;

  @override
  void initState() {
    super.initState();

    timer = Timer.periodic(
      const Duration(seconds: 1),
      (_) {
        if (!mounted) return;

        setState(() {
          seconds++;
        });
      },
    );
  }

  @override
  void dispose() {
    timer?.cancel();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Text('$seconds seconds');
  }
}

The timer is started during initialization and canceled during disposal to avoid continuing work after the widget is removed.


44. StatefulWidget with TextEditingController

Controllers are commonly used with input widgets and should be disposed when no longer needed.

class SearchBox extends StatefulWidget {
  const SearchBox({super.key});

  @override
  State createState() => _SearchBoxState();
}

class _SearchBoxState extends State {
  final TextEditingController controller =
      TextEditingController();

  @override
  void dispose() {
    controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return TextField(
      controller: controller,
    );
  }
}

45. StatefulWidget and AnimationController

Some stateful features require controllers that need lifecycle management.

class AnimatedBox extends StatefulWidget {
  const AnimatedBox({super.key});

  @override
  State createState() => _AnimatedBoxState();
}

class _AnimatedBoxState
    extends State
    with SingleTickerProviderStateMixin {
  late AnimationController controller;

  @override
  void initState() {
    super.initState();

    controller = AnimationController(
      vsync: this,
      duration: const Duration(seconds: 1),
    );
  }

  @override
  void dispose() {
    controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return const SizedBox();
  }
}

The key lesson is that resources created by a State object should be cleaned up when the State is disposed.


46. Complete Practical Example

import 'package:flutter/material.dart';

void main() {
  runApp(const MaterialApp(
    home: CounterPage(),
  ));
}

class CounterPage extends StatefulWidget {
  const CounterPage({super.key});

  @override
  State createState() => _CounterPageState();
}

class _CounterPageState extends State {
  int count = 0;
  bool isActive = false;

  void increment() {
    setState(() {
      count++;
    });
  }

  void decrement() {
    setState(() {
      count--;
    });
  }

  void toggleActive() {
    setState(() {
      isActive = !isActive;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Stateful Widget Demo'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text(
              'Count: $count',
              style: const TextStyle(
                fontSize: 28,
              ),
            ),
            const SizedBox(height: 20),
            Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                IconButton(
                  onPressed: decrement,
                  icon: const Icon(Icons.remove),
                ),
                IconButton(
                  onPressed: increment,
                  icon: const Icon(Icons.add),
                ),
              ],
            ),
            const SizedBox(height: 20),
            Text(
              isActive ? 'Active' : 'Inactive',
              style: TextStyle(
                color: isActive
                    ? Colors.green
                    : Colors.red,
              ),
            ),
            Switch(
              value: isActive,
              onChanged: (_) {
                toggleActive();
              },
            ),
          ],
        ),
      ),
    );
  }
}

What This Example Demonstrates

  • Creating a StatefulWidget.
  • Creating a State class.
  • Using mutable state.
  • Updating an integer with setState().
  • Updating Boolean state.
  • Dynamically changing displayed text.
  • Dynamically changing text color.
  • Using a Switch.
  • Using multiple state variables.

47. Widget Tree for the Practical Example

MaterialApp
└── CounterPage
    └── Scaffold
        ├── AppBar
        │   └── Text
        └── Center
            └── Column
                ├── Text
                ├── Row
                │   ├── IconButton
                │   │   └── Icon
                │   └── IconButton
                │       └── Icon
                ├── Text
                └── Switch

48. Real-World Uses of StatefulWidget

Use Case Example State
Counter Current count
Login Form Email, password, validation status
Shopping Cart Item quantity
Favorite Button Favorite or not favorite
Settings Enabled/disabled options
Slider Current slider value
Search Search query
Loading Screen Loading or completed
Timer Elapsed time
Form Input and validation state
Tab Interface Selected tab
Animation Animation progress

49. When Should You Use StatefulWidget?

Use a StatefulWidget when a widget's appearance or data needs to change during its lifetime and that state is appropriate to manage locally in its State object.

Typical situations include:

  • User interactions change the UI.
  • A value changes over time.
  • The widget needs local mutable state.
  • A controller needs to be managed with the widget lifecycle.
  • The UI needs to rebuild after a local state change.

However, not every dynamic application feature requires local StatefulWidget state. Flutter also supports broader state-management approaches for state shared across multiple parts of an application. :contentReference[oaicite:21]{index=21}


50. When Should You Prefer StatelessWidget?

Use a StatelessWidget when the widget's UI can be completely described by its immutable configuration and the current context without maintaining mutable local state.

Example:

class WelcomeText extends StatelessWidget {
  const WelcomeText({super.key});

  @override
  Widget build(BuildContext context) {
    return const Text(
      'Welcome to Flutter',
    );
  }
}

The text does not need to maintain changing local state, so a StatelessWidget is appropriate.


51. StatefulWidget Best Practices

  • Keep the State class focused on the state it actually manages.
  • Use setState() only when a state change should affect the UI.
  • Keep the setState() callback small.
  • Do not perform expensive operations inside setState().
  • Dispose controllers and subscriptions when necessary.
  • Cancel timers and other ongoing work when the State is disposed.
  • Check mounted before updating state after asynchronous work when appropriate.
  • Break large widgets into smaller reusable widgets.
  • Use const constructors where appropriate.
  • Move shared state to an appropriate parent or state-management solution when multiple widgets need the same state.

52. Common Mistakes Summary

Mistake Better Approach
Changing state without setState() Use setState() for UI-affecting local state changes
Calling setState() inside build() Trigger state changes from events or lifecycle logic
Making setState() callback async Await asynchronous work first, then call setState()
Calling setState() after dispose() Cancel work or check mounted
Forgetting to dispose controllers Dispose controllers in dispose()
Putting unrelated work inside setState() Keep only the actual state mutation inside
Using StatefulWidget for everything Use StatelessWidget when local mutable state is unnecessary

53. Interview Questions

Q1. What is a StatefulWidget?

A StatefulWidget is a widget whose associated State object can hold mutable data that may change during the widget's lifetime.

Q2. Is StatefulWidget itself mutable?

No. The StatefulWidget is immutable. Mutable state is stored in its associated State object. :contentReference[oaicite:22]{index=22}

Q3. Why does StatefulWidget require two classes?

The StatefulWidget represents immutable configuration, while the State object stores mutable state and implements the build method.

Q4. What does createState() do?

It creates the State object associated with a StatefulWidget at a particular location in the widget tree. :contentReference[oaicite:23]{index=23}

Q5. What is setState()?

It notifies Flutter that the State object's internal state has changed and that the relevant UI should be rebuilt. :contentReference[oaicite:24]{index=24}

Q6. What happens if you change state without setState()?

The internal value may change, but Flutter may not rebuild the affected UI to display the new value.

Q7. What is initState() used for?

It is commonly used for one-time initialization when the State object is first created.

Q8. What is dispose() used for?

It is used to clean up resources such as controllers, timers, and subscriptions before the State object is permanently removed.

Q9. Can setState() be async?

No. The callback passed to setState() should not return a Future. Perform asynchronous work separately and then synchronously update state inside setState(). :contentReference[oaicite:25]{index=25}

Q10. What is mounted?

mounted indicates whether the State object is currently associated with an active element in the widget tree.


54. Practice Exercise

Create a StatefulWidget called ShoppingCart with the following functionality:

  1. Display a product name.
  2. Display the product price.
  3. Create a quantity variable initialized to 1.
  4. Add a plus button.
  5. Add a minus button.
  6. Update quantity using setState().
  7. Prevent quantity from becoming less than 1.
  8. Calculate the total price dynamically.
  9. Display the total price.

Expected Flow

Initial Quantity = 1
        ↓
User presses +
        ↓
setState()
        ↓
Quantity = 2
        ↓
Total Price Updates
        ↓
UI Rebuilds

55. Quick Revision Table

Concept Meaning
StatefulWidget Widget used when associated state can change over time
State Mutable information associated with a StatefulWidget
createState() Creates the State object
initState() Used for one-time initialization
build() Describes the current UI
setState() Notifies Flutter about a state change and schedules rebuilding
didUpdateWidget() Responds to updated widget configuration
didChangeDependencies() Responds to changes in inherited dependencies
mounted Indicates whether State is currently mounted
dispose() Cleans up resources before State is permanently removed

56. Key Takeaways

  • StatefulWidget is used for UI that can change during its lifetime.
  • A StatefulWidget has a separate State object.
  • The StatefulWidget itself is immutable.
  • Mutable values are generally stored in the State object.
  • createState() creates the associated State object.
  • build() describes the UI for the current state.
  • setState() tells Flutter that a state change may affect the UI.
  • initState() is useful for one-time initialization.
  • dispose() is used for cleanup.
  • Controllers, timers, and subscriptions should be managed according to their lifecycle.
  • After asynchronous work, make sure the State is still mounted before updating it.
  • State can be managed locally or moved to an appropriate parent or broader state-management solution.

57. Learning Resources


Conclusion

Stateful Widgets are essential for building interactive Flutter applications. They allow developers to manage values that change during the lifetime of a widget, such as counters, form inputs, selections, loading states, timers, and interactive controls. The key idea is that the StatefulWidget provides immutable configuration while its associated State object stores mutable data. When that data changes, setState() notifies Flutter so the relevant UI can rebuild. Understanding StatefulWidget, State, createState(), initState(), build(), setState(), mounted, and dispose() provides a strong foundation for developing interactive Flutter applications.

whatsapp