Example of Stateful Widget in Flutter

Flutter provides developers with two main types of widgets: Stateless Widgets and Stateful Widgets. While Stateless Widgets are perfect for static UI that never changes, most real-world applications need dynamic and interactive UI that responds to user actions, data updates, and animations.

That’s where Stateful Widgets come in.

In this article, we will go deep into an example of a Stateful Widget in Flutter—the CounterApp—and break down every detail about how it works, its lifecycle, why we use it, and best practices.


Introduction to Stateful Widgets

A Stateful Widget is a widget that:

  • Can store mutable state (data that can change over time).
  • Automatically rebuilds the UI when its state changes.
  • Uses the setState() method to notify Flutter that the widget tree needs to be updated.

In contrast to Stateless Widgets, Stateful Widgets are dynamic and are perfect for handling interactions such as:

  • Button clicks
  • Form inputs
  • Page navigation
  • Animations
  • Real-time data updates

Structure of a Stateful Widget

A Stateful Widget consists of two classes:

  1. The Widget Class
    • Extends StatefulWidget.
    • Immutable itself, but linked to a State object.
    • Defines the createState() method, which returns the State object.
  2. The State Class
    • Extends State<T> where T is the Stateful Widget.
    • Holds the mutable state.
    • Contains the build() method to define the UI.
    • Uses setState() to trigger UI rebuilds when state changes.

Example: CounterApp in Flutter

Let’s look at a simple but powerful example of a Stateful Widget—a counter app that increases the count when a button is pressed.

class CounterApp extends StatefulWidget {
  @override
  _CounterAppState createState() => _CounterAppState();
}

class _CounterAppState extends State<CounterApp> {
  int count = 0;

  @override
  Widget build(BuildContext context) {
return Column(
  children: &#91;
    Text("Count: $count"),
    ElevatedButton(
      onPressed: () =&gt; setState(() =&gt; count++),
      child: Text("Increment"),
    )
  ],
);
} }

Breaking Down the Code

1. Declaring the Stateful Widget

class CounterApp extends StatefulWidget {
  @override
  _CounterAppState createState() => _CounterAppState();
}
  • CounterApp extends StatefulWidget.
  • This class is immutable, just like a Stateless Widget.
  • But unlike Stateless Widgets, it is connected to a State object that stores the actual data (count).
  • The createState() method returns _CounterAppState, which manages the widget’s state.

2. Declaring the State Class

class _CounterAppState extends State<CounterApp> {
  int count = 0;
  • _CounterAppState extends State<CounterApp>.
  • This is where the mutable state (count) is declared.
  • int count = 0; initializes the counter at zero.

3. The build() Method

@override
Widget build(BuildContext context) {
  return Column(
children: &#91;
  Text("Count: $count"),
  ElevatedButton(
    onPressed: () =&gt; setState(() =&gt; count++),
    child: Text("Increment"),
  )
],
); }
  • The build() method is called whenever the widget is created or rebuilt.
  • It returns a UI tree that shows:
    • A Text widget displaying the current value of count.
    • An ElevatedButton that increments the counter when pressed.

4. The setState() Method

onPressed: () => setState(() => count++),
  • setState() is crucial in Stateful Widgets.
  • It notifies Flutter that the state (count) has changed.
  • Flutter then rebuilds the widget tree to update the UI.
  • Without setState(), the UI would not refresh.

Lifecycle of CounterApp

To fully understand this example, let’s map out the lifecycle of the Stateful Widget:

  1. Constructor CalledCounterApp is created.
  2. createState() Called – Links CounterApp with _CounterAppState.
  3. initState() (optional) – Runs once before build (not used here).
  4. build() Called – Displays Count: 0 and the button.
  5. User Presses Button – Triggers setState().
  6. State Updatescount increments (count++).
  7. build() Recalled – UI rebuilt showing updated count.
  8. Widget Removed – If the CounterApp is removed, dispose() is called (if implemented).

How It Works in Practice

Imagine running the app:

  1. At first, the screen shows: Count: 0 [Increment Button]
  2. When you press Increment, the onPressed callback runs:
    • setState(() => count++); increments count by 1.
    • Flutter calls build() again.
  3. Now the UI updates to: Count: 1 [Increment Button]
  4. Each button press increases the count and triggers a rebuild.

Why Use Stateful Widgets?

Stateful Widgets are essential because they allow Flutter apps to:

  • Respond to user interactions.
  • Manage dynamic data.
  • Handle real-time updates from APIs.
  • Support animations and transitions.

In short: If your UI changes, you need a Stateful Widget.


Real-World Use Cases of Stateful Widgets

The CounterApp is a simple demo, but Stateful Widgets are used everywhere:

  1. Forms – Capturing user input like name, email, and password.
  2. Shopping Carts – Adding/removing items dynamically.
  3. Music Players – Play, pause, skip, and update progress bar.
  4. Social Media Apps – Liking posts, adding comments, refreshing feeds.
  5. Games – Updating scores, player positions, and animations.
  6. Data Fetching – Displaying loading spinners while fetching API data.

Advantages of Stateful Widgets

  1. Dynamic UI – Perfect for apps where the UI changes.
  2. Easy State Management – Built-in setState() makes updates straightforward.
  3. Encapsulation – Each Stateful Widget manages its own state.
  4. Flexibility – Works for both small and large interactive components.

Limitations of Stateful Widgets

  1. Rebuild Overhead – Too many rebuilds can hurt performance.
  2. Not Suitable for Complex Apps Alone – For large apps, advanced state management (Provider, Riverpod, BLoC) may be required.
  3. State Tied to Widget Lifecycle – If the widget is destroyed, its state is lost.

Best Practices for Stateful Widgets

  1. Keep State Local – Only store state inside a Stateful Widget if it belongs to that widget.
  2. Minimize setState() Calls – Only wrap the smallest part of UI that needs rebuilding.
  3. Use const Widgets – For static parts of the UI to avoid unnecessary rebuilds.
  4. Break Down UI – Use smaller reusable widgets inside your Stateful Widget.
  5. Plan State Management – For large-scale apps, consider advanced solutions like Provider, Riverpod, or BLoC.

Example: Improved CounterApp with Styling

Let’s improve the CounterApp by adding better design:

import 'package:flutter/material.dart';

class CounterApp extends StatefulWidget {
  @override
  _CounterAppState createState() => _CounterAppState();
}

class _CounterAppState extends State<CounterApp> {
  int count = 0;

  @override
  Widget build(BuildContext context) {
return Scaffold(
  appBar: AppBar(title: Text("Counter App Example")),
  body: Center(
    child: Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: &#91;
        Text(
          "Count: $count",
          style: TextStyle(fontSize: 28, fontWeight: FontWeight.bold),
        ),
        SizedBox(height: 20),
        ElevatedButton(
          onPressed: () =&gt; setState(() =&gt; count++),
          child: Text("Increment"),
        ),
      ],
    ),
  ),
);
} }

Features of This Version:

  • Uses Scaffold for structure.
  • Adds an AppBar.
  • Centers content with Center widget.
  • Improves UI with SizedBox and styling.

Debugging Stateful Widgets

When working with Stateful Widgets, debugging rebuilds is important. You can:

  • Use print() inside build() to track rebuilds.
  • Use Flutter DevTools to monitor widget rebuilds.
  • Use const for widgets that don’t need to rebuild.

When to Choose Stateful vs Stateless

ScenarioWidget Type
Static text, icons, imagesStateless
UI changes on button clickStateful
Fetching and displaying API dataStateful
Navigation structure (like routes)Stateless
User forms and inputsStateful

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *