Question #109EasyFlutter Basics

What is StatefulBuilder and it’s use case (example setState)

#state

Answer

Overview

StatefulBuilder is a widget that allows you to rebuild part of the widget tree with its own

text
setState
, without rebuilding the entire parent widget. It's useful for isolated state changes in dialogs, bottom sheets, or nested widgets.


Problem Without StatefulBuilder

text
setState
in a StatefulWidget rebuilds the entire widget.

dart
class MyPage extends StatefulWidget {
  
  _MyPageState createState() => _MyPageState();
}

class _MyPageState extends State<MyPage> {
  bool isChecked = false;

  
  Widget build(BuildContext context) {
    return Scaffold(
      body: Column(
        children: [
          Text('Expensive widget that rebuilds unnecessarily'),
          ExpensiveList(), // ❌ Rebuilds even though it shouldn't
          Checkbox(
            value: isChecked,
            onChanged: (value) {
              setState(() {
                isChecked = value!;
              });
              // ❌ Entire widget tree rebuilds
            },
          ),
        ],
      ),
    );
  }
}

Solution: StatefulBuilder

Rebuild only the StatefulBuilder widget, not the parent.

dart
class MyPage extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return Scaffold(
      body: Column(
        children: [
          Text('This does NOT rebuild'),
          ExpensiveList(), // ✅ Does NOT rebuild
          StatefulBuilder(
            builder: (context, setState) {
              bool isChecked = false;

              return Checkbox(
                value: isChecked,
                onChanged: (value) {
                  setState(() {
                    isChecked = value!;
                  });
                  // ✅ Only StatefulBuilder rebuilds
                },
              );
            },
          ),
        ],
      ),
    );
  }
}

Common Use Cases

1. Dialogs with State

dart
void showCustomDialog(BuildContext context) {
  bool agreedToTerms = false;

  showDialog(
    context: context,
    builder: (context) {
      return AlertDialog(
        title: Text('Terms and Conditions'),
        content: StatefulBuilder(
          builder: (context, setState) {
            return Column(
              mainAxisSize: MainAxisSize.min,
              children: [
                Text('Please agree to the terms'),
                CheckboxListTile(
                  title: Text('I agree'),
                  value: agreedToTerms,
                  onChanged: (value) {
                    setState(() {
                      agreedToTerms = value!;
                    });
                    // ✅ Only dialog content rebuilds
                  },
                ),
              ],
            );
          },
        ),
        actions: [
          TextButton(
            onPressed: agreedToTerms
                ? () => Navigator.pop(context)
                : null,
            child: Text('Continue'),
          ),
        ],
      );
    },
  );
}

2. Bottom Sheets

dart
void showFilterSheet(BuildContext context) {
  int selectedCategory = 0;

  showModalBottomSheet(
    context: context,
    builder: (context) {
      return StatefulBuilder(
        builder: (context, setState) {
          return Column(
            children: [
              Text('Filter by Category'),
              RadioListTile(
                title: Text('Electronics'),
                value: 0,
                groupValue: selectedCategory,
                onChanged: (value) {
                  setState(() {
                    selectedCategory = value!;
                  });
                },
              ),
              RadioListTile(
                title: Text('Clothing'),
                value: 1,
                groupValue: selectedCategory,
                onChanged: (value) {
                  setState(() {
                    selectedCategory = value!;
                  });
                },
              ),
              ElevatedButton(
                onPressed: () {
                  Navigator.pop(context, selectedCategory);
                },
                child: Text('Apply Filter'),
              ),
            ],
          );
        },
      );
    },
  );
}

3. Nested Widgets with Isolated State

dart
class ProductCard extends StatelessWidget {
  final Product product;

  const ProductCard({required this.product});

  
  Widget build(BuildContext context) {
    return Card(
      child: Column(
        children: [
          Image.network(product.imageUrl),
          Text(product.name),
          StatefulBuilder(
            builder: (context, setState) {
              int quantity = 1;

              return Row(
                children: [
                  IconButton(
                    icon: Icon(Icons.remove),
                    onPressed: () {
                      setState(() {
                        if (quantity > 1) quantity--;
                      });
                    },
                  ),
                  Text('$quantity'),
                  IconButton(
                    icon: Icon(Icons.add),
                    onPressed: () {
                      setState(() {
                        quantity++;
                      });
                    },
                  ),
                  ElevatedButton(
                    onPressed: () {
                      addToCart(product, quantity);
                    },
                    child: Text('Add to Cart'),
                  ),
                ],
              );
            },
          ),
        ],
      ),
    );
  }
}

Complete Example: Multi-Step Form in Dialog

dart
void showMultiStepDialog(BuildContext context) {
  int currentStep = 0;
  String name = '';
  String email = '';

  showDialog(
    context: context,
    builder: (context) {
      return AlertDialog(
        title: Text('Registration'),
        content: StatefulBuilder(
          builder: (context, setState) {
            return Column(
              mainAxisSize: MainAxisSize.min,
              children: [
                if (currentStep == 0) ...[
                  Text('Step 1: Enter Name'),
                  TextField(
                    onChanged: (value) => name = value,
                    decoration: InputDecoration(labelText: 'Name'),
                  ),
                ] else if (currentStep == 1) ...[
                  Text('Step 2: Enter Email'),
                  TextField(
                    onChanged: (value) => email = value,
                    decoration: InputDecoration(labelText: 'Email'),
                  ),
                ] else ...[
                  Text('Step 3: Confirm'),
                  Text('Name: $name'),
                  Text('Email: $email'),
                ],
              ],
            );
          },
        ),
        actions: [
          if (currentStep > 0)
            TextButton(
              onPressed: () {
                setState(() {
                  currentStep--;
                });
              },
              child: Text('Back'),
            ),
          TextButton(
            onPressed: () {
              if (currentStep < 2) {
                setState(() {
                  currentStep++;
                });
              } else {
                Navigator.pop(context);
                submitForm(name, email);
              }
            },
            child: Text(currentStep < 2 ? 'Next' : 'Submit'),
          ),
        ],
      );
    },
  );
}

StatefulBuilder vs StatefulWidget

FeatureStatefulBuilderStatefulWidget
Use caseIsolated state changesFull widget state
BoilerplateLow (inline)High (2 classes)
State lifetimeScoped to builderEntire widget lifecycle
Best forDialogs, bottom sheetsPages, complex widgets

Alternative: ValueNotifier

For simple state, use

text
ValueNotifier
+
text
ValueListenableBuilder
.

dart
void showDialogWithValueNotifier(BuildContext context) {
  final agreedToTerms = ValueNotifier<bool>(false);

  showDialog(
    context: context,
    builder: (context) {
      return AlertDialog(
        title: Text('Terms'),
        content: ValueListenableBuilder<bool>(
          valueListenable: agreedToTerms,
          builder: (context, value, child) {
            return CheckboxListTile(
              title: Text('I agree'),
              value: value,
              onChanged: (newValue) {
                agreedToTerms.value = newValue!;
              },
            );
          },
        ),
      );
    },
  );
}

Best Practices

dart
// ✅ Use StatefulBuilder for isolated state
StatefulBuilder(
  builder: (context, setState) {
    return Checkbox(value: isChecked, onChanged: (val) => setState(() {}));
  },
)

// ✅ Use for dialogs/bottom sheets with interactive widgets
showDialog(
  builder: (context) => AlertDialog(
    content: StatefulBuilder(...),
  ),
)

// ❌ Don't overuse for complex state (use BLoC, Riverpod instead)
// ❌ Don't nest multiple StatefulBuilders (hard to maintain)

Summary

Use CaseSolution
Dialog with checkboxStatefulBuilder
Bottom sheet with radio buttonsStatefulBuilder
Nested widget with local stateStatefulBuilder
Full page stateStatefulWidget
Global stateBLoC, Riverpod, Provider

When to use: Isolated state changes in dialogs, bottom sheets, or nested widgets without rebuilding the parent.

Learn more: StatefulBuilder Documentation