Popular Searches
Popular Course Categories
Popular Courses

Stateful Widgets

Flutter Fundamentals

Stateful Widgets in Flutter

StatefulWidget is one of the most important concepts in Flutter for building interactive and dynamic user interfaces. A StatefulWidget is used when the appearance or data of a widget needs to change during its lifetime, such as when a user taps a button, enters text, selects a checkbox, moves a slider, or when data changes over time.

A StatefulWidget works together with a separate State object. The StatefulWidget itself is immutable, while the State object stores the mutable data that can change during the widget's lifetime.

For structured Flutter learning, visit JustAcademy Flutter Training and Register for a Course Demo.


1. What is a StatefulWidget?

A StatefulWidget is a Flutter widget that can maintain mutable state. When its state changes, Flutter can rebuild the relevant part of the user interface to display the updated information.

Examples of situations where StatefulWidget can be useful include:

  • A counter that increases when a button is pressed
  • A checkbox that can be checked or unchecked
  • A switch that can be turned on or off
  • A slider whose value changes
  • A form with changing input values
  • A loading indicator that changes after an operation completes
  • A widget that displays changing data
  • A selected tab or menu item
  • Animations and timers

Official reference: Flutter StatefulWidget API Documentation.


2. StatefulWidget vs StatelessWidget

Feature StatelessWidget StatefulWidget
Mutable internal state Not required Supported through State
State class Not required Required
setState() Not available Available in State
Dynamic UI Can rebuild from new configuration Can rebuild when internal state changes
Typical use Static or configuration-driven UI Interactive and changing UI
Example Text, Icon, static card Counter, Checkbox, interactive form

3. Basic Structure of StatefulWidget

A StatefulWidget normally consists of two classes:

  1. A class that extends StatefulWidget.
  2. A class that extends 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');
  }
}

Explanation

  • MyWidget is the StatefulWidget.
  • _MyWidgetState stores the mutable state.
  • createState() creates and returns the State object.
  • The build() method is implemented inside the State class.

4. Why Are There Two Classes?

Flutter separates the immutable widget configuration from mutable state.

The StatefulWidget describes what the widget is configured to do, while the State object stores information that can change.

StatefulWidget
      |
      | createState()
      v
State Object
      |
      | build()
      v
Widget Tree
      |
      | setState()
      v
Rebuild UI

This separation allows Flutter to preserve state while widget configurations are rebuilt or replaced when appropriate.


5. Creating a Simple StatefulWidget

Let's create a basic StatefulWidget.

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

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

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

class _MyAppState extends State {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text('StatefulWidget Example'),
        ),
        body: const Center(
          child: Text('Hello Flutter'),
        ),
      ),
    );
  }
}

Although this example does not yet contain changing state, it demonstrates the basic structure of a StatefulWidget.


6. Understanding State

State is information that can change during the lifetime of a widget and can affect what the widget displays.

For example:

int counter = 0;

If the counter changes from 0 to 1, the UI may need to display the new value.

Official reference: Flutter State API Documentation.


7. Creating a Counter Application

The counter is one of the easiest ways to understand StatefulWidget.

import 'package:flutter/material.dart';

void main() {
  runApp(const CounterApp());
}

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

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

class _CounterAppState extends State {
  int counter = 0;

  void incrementCounter() {
    setState(() {
      counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Counter App'),
        ),
        body: Center(
          child: Text(
            '$counter',
            style: const TextStyle(
              fontSize: 40,
              fontWeight: FontWeight.bold,
            ),
          ),
        ),
        floatingActionButton: FloatingActionButton(
          onPressed: incrementCounter,
          child: const Icon(Icons.add),
        ),
      ),
    );
  }
}

How It Works

  1. The initial value of counter is 0.
  2. The user presses the floating action button.
  3. incrementCounter() is called.
  4. setState() changes the counter.
  5. Flutter schedules the State object for rebuilding.
  6. The build() method runs again.
  7. The new counter value is displayed.

8. What is setState()?

setState() tells Flutter that the internal state of a State object has changed and that the framework should schedule the widget for rebuilding.

setState(() {
  counter++;
});

The change to the state should be made inside the callback passed to setState().

Official reference: Flutter setState API Documentation.


9. Why is setState() Important?

Consider the following code:

counter++;

The variable may change in memory, but Flutter is not automatically notified that the change should affect the UI.

The preferred approach is:

setState(() {
  counter++;
});

This tells Flutter that the state changed and the affected State object should be rebuilt.


10. setState() Example with Increment and Decrement

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

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

class _CounterPageState extends State {
  int count = 0;

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

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text(
              '$count',
              style: const TextStyle(fontSize: 40),
            ),
            Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                ElevatedButton(
                  onPressed: decrement,
                  child: const Text('-'),
                ),
                const SizedBox(width: 10),
                ElevatedButton(
                  onPressed: increment,
                  child: const Text('+'),
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }
}

11. StatefulWidget Lifecycle

A StatefulWidget has a lifecycle that describes how its State object is created, initialized, rebuilt, updated, and eventually removed.

A simplified lifecycle is:

createState()
     |
     v
mounted
     |
     v
initState()
     |
     v
didChangeDependencies()
     |
     v
build()
     |
     v
setState()
     |
     v
build() again
     |
     v
didUpdateWidget()
     |
     v
dispose()

12. createState()

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

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

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

Flutter calls createState() when the StatefulWidget is inserted into the widget tree and needs its State object.


13. initState()

initState() is called once when the State object is initialized.

It is commonly used for one-time initialization, such as:

  • Initializing controllers
  • Starting timers
  • Initializing animation controllers
  • Setting initial values
  • Subscribing to services or listeners
class Example extends StatefulWidget {
  const Example({super.key});

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

class _ExampleState extends State {
  @override
  void initState() {
    super.initState();

    print('State initialized');
  }

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

When overriding initState(), call super.initState().


14. didChangeDependencies()

didChangeDependencies() is called when a dependency used by the State changes. It is commonly associated with inherited information provided through the widget tree.

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

  print('Dependencies changed');
}

Use it when initialization or updates depend on inherited widgets or similar dependencies.


15. build()

The build() method describes the user interface.

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

The build method can run many times, so it should primarily describe the UI and avoid unnecessary expensive work.


16. didUpdateWidget()

didUpdateWidget() is called when the parent rebuilds and provides a new widget configuration while Flutter keeps the existing State object.

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

  if (oldWidget.title != widget.title) {
    print('Title changed');
  }
}

This can be useful when the State needs to respond to changes in the StatefulWidget's constructor properties.


17. dispose()

dispose() is called when the State object is permanently removed from the widget tree.

It is commonly used for cleanup such as:

  • Canceling timers
  • Disposing TextEditingController
  • Disposing AnimationController
  • Removing listeners
  • Canceling subscriptions
@override
void dispose() {
  controller.dispose();
  super.dispose();
}

Flutter documentation recommends performing cleanup in dispose() for resources owned by the State object.


18. mounted Property

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

if (!mounted) {
  return;
}

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

This can be important after asynchronous operations if the widget might have been removed from the tree before the operation finishes.

Where possible, it is preferable to cancel work that should no longer continue instead of relying only on a mounted check.


19. StatefulWidget with Boolean State

A Boolean variable is useful for UI such as switches, visibility controls, and favorite buttons.

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

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

class _ToggleWidgetState extends State {
  bool isEnabled = false;

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

20. StatefulWidget with Checkbox

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

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

class _TermsCheckboxState extends State {
  bool accepted = false;

  @override
  Widget build(BuildContext context) {
    return CheckboxListTile(
      title: const Text('I accept the terms'),
      value: accepted,
      onChanged: (value) {
        setState(() {
          accepted = value ?? false;
        });
      },
    );
  }
}

21. StatefulWidget with Slider

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

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

class _VolumeSliderState extends State {
  double volume = 50;

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Text('Volume: ${volume.toInt()}'),
        Slider(
          value: volume,
          min: 0,
          max: 100,
          onChanged: (value) {
            setState(() {
              volume = value;
            });
          },
        ),
      ],
    );
  }
}

22. StatefulWidget with TextField

Text input is another common use case for stateful behavior.

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;
            });
          },
          decoration: const InputDecoration(
            labelText: 'Enter your name',
          ),
        ),
        const SizedBox(height: 20),
        Text('Hello $name'),
      ],
    );
  }
}

23. TextEditingController

For more advanced text input handling, Flutter provides TextEditingController.

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

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

class _LoginFormState extends State {
  final TextEditingController emailController =
      TextEditingController();

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

  @override
  Widget build(BuildContext context) {
    return TextField(
      controller: emailController,
      decoration: const InputDecoration(
        labelText: 'Email',
      ),
    );
  }
}

The controller is disposed in dispose() because it is owned by the State object.


24. Show and Hide UI Dynamically

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

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

class _PasswordFieldState extends State {
  bool obscurePassword = true;

  @override
  Widget build(BuildContext context) {
    return TextField(
      obscureText: obscurePassword,
      decoration: InputDecoration(
        labelText: 'Password',
        suffixIcon: IconButton(
          icon: Icon(
            obscurePassword
                ? Icons.visibility
                : Icons.visibility_off,
          ),
          onPressed: () {
            setState(() {
              obscurePassword = !obscurePassword;
            });
          },
        ),
      ),
    );
  }
}

25. Favorite Button Example

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;
        });
      },
    );
  }
}

26. Loading State

StatefulWidget is commonly used to represent loading, success, and error states.

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: loadData,
          child: const Text('Load Data'),
        ),
      ],
    );
  }
}

Notice that the asynchronous operation is performed outside the setState() callback. Only the synchronous state changes are placed inside setState().


27. Important Rule: Do Not Make setState() Async

The callback passed to setState() should not be asynchronous.

Incorrect:

setState(() async {
  await loadData();
  isLoading = false;
});

Correct:

await loadData();

if (!mounted) {
  return;
}

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

The Flutter API specifies that the callback supplied to setState() executes synchronously and must not return a Future.


28. Multiple State Variables

A StatefulWidget can maintain multiple related state variables.

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

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

class _UserSettingsState extends State {
  bool notifications = true;
  bool darkMode = false;
  double fontSize = 16;

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        SwitchListTile(
          title: const Text('Notifications'),
          value: notifications,
          onChanged: (value) {
            setState(() {
              notifications = value;
            });
          },
        ),
        SwitchListTile(
          title: const Text('Dark Mode'),
          value: darkMode,
          onChanged: (value) {
            setState(() {
              darkMode = value;
            });
          },
        ),
        Slider(
          value: fontSize,
          min: 12,
          max: 30,
          onChanged: (value) {
            setState(() {
              fontSize = value;
            });
          },
        ),
      ],
    );
  }
}

29. Conditional Rendering

State can determine which widgets should appear.

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

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

class _LoginStatusState extends State {
  bool loggedIn = false;

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Text(
          loggedIn
              ? 'Welcome Back!'
              : 'Please Login',
        ),
        ElevatedButton(
          onPressed: () {
            setState(() {
              loggedIn = !loggedIn;
            });
          },
          child: Text(
            loggedIn ? 'Logout' : 'Login',
          ),
        ),
      ],
    );
  }
}

30. Managing State in a Parent Widget

State does not always need to be stored inside the widget that displays it. Flutter allows a parent widget to manage state and pass values and callbacks to child widgets.

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

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

class _ParentWidgetState extends State {
  int count = 0;

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Text('Count: $count'),
        ChildButton(
          onPressed: () {
            setState(() {
              count++;
            });
          },
        ),
      ],
    );
  }
}

class ChildButton extends StatelessWidget {
  final VoidCallback onPressed;

  const ChildButton({
    super.key,
    required this.onPressed,
  });

  @override
  Widget build(BuildContext context) {
    return ElevatedButton(
      onPressed: onPressed,
      child: const Text('Increase'),
    );
  }
}

Here, the parent owns the state while the child only receives a callback.


31. StatefulWidget with Timer

A timer can be used to demonstrate state that changes over time.

import 'dart:async';

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

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

class _TimerWidgetState 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',
      style: const TextStyle(fontSize: 24),
    );
  }
}

The timer is started in initState() and canceled in dispose().


32. StatefulWidget with Form Validation

Forms often need state to track input and validation results.

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

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

class _LoginFormState extends State {
  final GlobalKey formKey =
      GlobalKey();

  @override
  Widget build(BuildContext context) {
    return Form(
      key: formKey,
      child: Column(
        children: [
          TextFormField(
            decoration: const InputDecoration(
              labelText: 'Email',
            ),
            validator: (value) {
              if (value == null || value.isEmpty) {
                return 'Please enter your email';
              }
              return null;
            },
          ),
          ElevatedButton(
            onPressed: () {
              if (formKey.currentState!.validate()) {
                print('Form is valid');
              }
            },
            child: const Text('Submit'),
          ),
        ],
      ),
    );
  }
}

33. StatefulWidget with AnimationController

Animations commonly require lifecycle management and are therefore often implemented in a State object.

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

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

class _FadeExampleState
    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 FadeTransition(
      opacity: controller,
      child: const FlutterLogo(size: 100),
    );
  }
}

34. StatefulWidget and Constructor Parameters

The StatefulWidget class can receive immutable configuration through constructor parameters.

class GreetingWidget extends StatefulWidget {
  final String name;

  const GreetingWidget({
    super.key,
    required this.name,
  });

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

class _GreetingWidgetState
    extends State {
  @override
  Widget build(BuildContext context) {
    return Text(
      'Hello ${widget.name}',
    );
  }
}

Inside the State class, the current StatefulWidget configuration is available through the widget property.


35. Understanding the widget Property

Inside a State class, widget refers to the current StatefulWidget configuration.

class UserWidget extends StatefulWidget {
  final String username;

  const UserWidget({
    super.key,
    required this.username,
  });

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

class _UserWidgetState extends State {
  @override
  Widget build(BuildContext context) {
    return Text(
      'User: ${widget.username}',
    );
  }
}

The username belongs to the widget configuration, while mutable fields such as counters or selected values belong in the State object.


36. StatefulWidget and didUpdateWidget()

Suppose a parent changes the properties of a StatefulWidget while the State object remains associated with the same location in the tree.

class ProfileWidget extends StatefulWidget {
  final String name;

  const ProfileWidget({
    super.key,
    required this.name,
  });

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

class _ProfileWidgetState
    extends State {

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

    if (oldWidget.name != widget.name) {
      print('Name changed');
    }
  }

  @override
  Widget build(BuildContext context) {
    return Text(widget.name);
  }
}

37. Common Mistakes with StatefulWidget

Mistake 1: Forgetting setState()

count++;

Changing state directly without notifying Flutter may leave the visible UI unchanged.

Use:

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

Mistake 2: Making setState() Async

setState(() async {
  await someFunction();
});

Do not make the callback passed to setState asynchronous.

Mistake 3: Forgetting dispose()

Controllers, timers, subscriptions, and other resources that require cleanup should be cleaned up in dispose().

Mistake 4: Calling setState() After dispose

Calling setState after the State object has been disposed is an error. Properly cancel asynchronous work or check mounted when appropriate.

Mistake 5: Performing Expensive Work in build()

The build method can run frequently. Avoid unnecessarily expensive operations inside it.


38. Best Practices for StatefulWidget

  • Keep the State object focused on state and UI behavior.
  • Use setState() only when the state change should affect the UI.
  • Keep the callback passed to setState() synchronous.
  • Use final for immutable widget configuration.
  • Use const constructors where appropriate.
  • Initialize resources in initState().
  • Clean up resources in dispose().
  • Avoid expensive calculations inside build().
  • Keep widgets small and reusable.
  • Lift state to a parent when multiple widgets need access to the same state.
  • Cancel timers, listeners, and subscriptions when they are no longer needed.
  • Use appropriate state-management solutions as application complexity grows.

39. When Should You Use StatefulWidget?

Use StatefulWidget when the UI or data associated with a component needs to change over time and that state is appropriately owned by the component.

Common Examples

  • Counter
  • Checkbox
  • Switch
  • Slider
  • Text input
  • Form validation
  • Selected tabs
  • Expandable sections
  • Loading indicators
  • Timers
  • Animations
  • Temporary UI state

40. When Should You Use StatelessWidget Instead?

If a component only needs to display information based on its configuration and surrounding context, a StatelessWidget may be more appropriate.

class UserName extends StatelessWidget {
  final String name;

  const UserName({
    super.key,
    required this.name,
  });

  @override
  Widget build(BuildContext context) {
    return Text(name);
  }
}

If the component itself needs to modify and maintain a value over time, StatefulWidget may be appropriate.


41. Complete Practical Example

The following example combines StatefulWidget, setState(), text input, a counter, and conditional UI.

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: const InteractivePage(),
    );
  }
}

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

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

class _InteractivePageState
    extends State {
  int counter = 0;
  bool isFavorite = false;
  String name = '';

  void incrementCounter() {
    setState(() {
      counter++;
    });
  }

  void toggleFavorite() {
    setState(() {
      isFavorite = !isFavorite;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Interactive Flutter App'),
      ),
      body: Padding(
        padding: const EdgeInsets.all(20),
        child: Column(
          children: [
            TextField(
              onChanged: (value) {
                setState(() {
                  name = value;
                });
              },
              decoration: const InputDecoration(
                labelText: 'Enter your name',
                border: OutlineInputBorder(),
              ),
            ),
            const SizedBox(height: 20),
            Text(
              name.isEmpty
                  ? 'Hello!'
                  : 'Hello, $name!',
              style: const TextStyle(
                fontSize: 24,
                fontWeight: FontWeight.bold,
              ),
            ),
            const SizedBox(height: 20),
            Text(
              'Counter: $counter',
              style: const TextStyle(fontSize: 22),
            ),
            ElevatedButton(
              onPressed: incrementCounter,
              child: const Text('Increase'),
            ),
            IconButton(
              onPressed: toggleFavorite,
              icon: Icon(
                isFavorite
                    ? Icons.favorite
                    : Icons.favorite_border,
                size: 40,
              ),
            ),
          ],
        ),
      ),
    );
  }
}

42. Widget Tree of the Practical Example

MaterialApp
└── Scaffold
    ├── AppBar
    │   └── Text
    └── Body
        └── Column
            ├── TextField
            ├── Text
            ├── Text
            ├── ElevatedButton
            └── IconButton

The InteractivePage StatefulWidget manages the values that can change:

  • counter
  • isFavorite
  • name

43. StatefulWidget in Real-World Applications

StatefulWidgets are used throughout real Flutter applications for temporary and interactive UI state.

E-Commerce Application

  • Selected product quantity
  • Favorite status
  • Selected product options
  • Cart item controls
  • Loading states

Social Media Application

  • Like button
  • Follow button
  • Comment input
  • Expandable posts
  • Selected tabs

Education Application

  • Quiz answers
  • Progress indicators
  • Selected options
  • Timer-based tests
  • Form inputs

Booking Application

  • Date selection
  • Time selection
  • Guest count
  • Filter selection
  • Booking progress

44. Interview Questions

Q1. What is StatefulWidget?

StatefulWidget is a Flutter widget used when the widget needs mutable state that can change during its lifetime.

Q2. How many classes are normally required for a StatefulWidget?

A typical StatefulWidget implementation contains two classes: a class extending StatefulWidget and a State class extending State.

Q3. What is createState()?

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

Q4. Where is mutable state stored?

Mutable state is normally stored in the State object rather than directly in the StatefulWidget.

Q5. What does setState() do?

It notifies Flutter that internal state has changed and schedules the State object to rebuild.

Q6. Can setState() be async?

No. The callback passed to setState() must execute synchronously and must not return a Future.

Q7. What is initState() used for?

It is used for one-time initialization of the State object.

Q8. What is dispose() used for?

It is used to clean up resources such as controllers, timers, listeners, and subscriptions.

Q9. What is mounted?

mounted indicates whether the State object is currently associated with a BuildContext in the widget tree.

Q10. What happens if state changes without setState()?

The internal value can change, but Flutter may not schedule the necessary rebuild, so the visible UI may not reflect the new value.


45. Practice Exercise

Create a Student Dashboard using StatefulWidget.

Requirements

  1. Create a StatefulWidget named StudentDashboard.
  2. Add a student name variable.
  3. Add a counter for completed lessons.
  4. Add a Boolean variable for course completion.
  5. Add a button to increase completed lessons.
  6. Add a switch for course completion.
  7. Display a different message based on completion status.
  8. Add a TextField to update the student name.
  9. Use setState() for all UI-changing state.
  10. Keep the UI organized using Column, Row, Card, and Text widgets.

Expected State Variables

String studentName = '';
int completedLessons = 0;
bool courseCompleted = false;

46. Quick Revision

Concept Purpose
StatefulWidget Creates a widget that can work with mutable state
State Stores data that can change during the widget's lifetime
createState() Creates the State object
initState() Performs one-time initialization
build() Builds the user interface
setState() Notifies Flutter about state changes and schedules a rebuild
didChangeDependencies() Responds to dependency changes
didUpdateWidget() Responds to updated widget configuration
mounted Indicates whether State is currently mounted
dispose() Cleans up resources before State is permanently removed

47. Key Takeaways

  • StatefulWidget is used for dynamic and interactive UI.
  • A StatefulWidget normally works with a separate State object.
  • The StatefulWidget itself remains immutable.
  • Mutable values are normally stored in the State object.
  • createState() creates the State object.
  • build() describes the current UI.
  • setState() tells Flutter that state has changed and the UI may need rebuilding.
  • The callback passed to setState() must not be asynchronous.
  • initState() is useful for one-time initialization.
  • dispose() is important for cleaning up resources.
  • mounted can help determine whether the State is still active in the widget tree.
  • StatefulWidget is commonly used for counters, forms, switches, sliders, timers, animations, and interactive components.
  • When state is shared between multiple widgets, consider managing that state at an appropriate parent or with a suitable state-management approach.

48. Official Learning Resources


Conclusion

StatefulWidget is a fundamental Flutter concept for creating dynamic and interactive applications. Unlike a StatelessWidget, a StatefulWidget works with a separate State object that can hold mutable data. When that data changes, setState() can be used to notify Flutter so the relevant UI is rebuilt.

Understanding StatefulWidget, State, setState(), widget lifecycle, mounted, and dispose() provides a strong foundation for building interactive Flutter applications such as forms, dashboards, shopping apps, social applications, educational applications, and booking systems.

For structured Flutter training, explore JustAcademy Flutter Training and Register for a Course Demo.

whatsapp