Skip to content

Understanding Stateful and Stateless Widgets in Flutter

Overview

In Flutter, everything is a widget. Widgets are the building blocks of a Flutter app's user interface. They can be broadly categorized into two types: Stateful and Stateless.

Stateless Widgets

A Stateless widget is immutable, meaning its properties cannot change—once created, it cannot be altered. Stateless widgets are typically used for static content.

Stateful Widgets

A Stateful widget is dynamic and can change during the app's lifecycle. It can update its state based on user interactions or other factors, and it redraws the widget whenever the state changes.

Real-World Analogies

Stateless Widget: A Poster

Imagine a poster on a wall. The poster is static; it doesn't change. You can look at it, read it, but it stays the same unless you replace it with a new poster. This is similar to a Stateless widget.

Stateful Widget: A Whiteboard

Now, think of a whiteboard. You can write on it, erase it, and rewrite on it multiple times. It changes based on your interactions. This is similar to a Stateful widget.

Examples in Flutter

Stateless Widget Example: Static Greeting

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: Text('Stateless Widget Example')),
        body: Center(child: GreetingWidget()),
      ),
    );
  }
}

class GreetingWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Text('Hello, World!');
  }
}

In the example above:

  • GreetingWidget is a Stateless widget that displays a static text "Hello, World!"

Stateful Widget Example: Interactive Counter

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: Text('Stateful Widget Example')),
        body: Center(child: CounterWidget()),
      ),
    );
  }
}

class CounterWidget extends StatefulWidget {
  @override
  _CounterWidgetState createState() => _CounterWidgetState();
}

class _CounterWidgetState extends State<CounterWidget> {
  int _counter = 0;

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        Text('Counter: $_counter'),
        ElevatedButton(
          onPressed: _incrementCounter,
          child: Text('Increment'),
        ),
      ],
    );
  }
}

In the example above:

  • CounterWidget is a Stateful widget that displays a counter and a button
  • _CounterWidgetState holds the state for the widget, including the _counter variable
  • When the button is pressed, _incrementCounter is called, which updates the state and causes the widget to redraw with the new counter value

Key Differences

Stateless Widget

  • Immutable: Properties cannot change once the widget is built
  • Use Cases: Static content like text, icons, and images
  • Lifecycle: Build method is called once

Stateful Widget

  • Mutable: Properties can change over time based on user interactions or other events
  • Use Cases: Interactive content like forms, animations, and any content that can change
  • Lifecycle: Build method can be called multiple times as the state changes

Practical Applications

Stateless Widget Use Case: Displaying User Profile

A profile page that displays static information like the user's name and profile picture.

class UserProfile extends StatelessWidget {
  final String name;
  final String profilePicture;

  UserProfile({required this.name, required this.profilePicture});

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Image.network(profilePicture),
        Text(name),
      ],
    );
  }
}