Real-world StatefulWidget lifecycle scenarios: 1) Inside widget if setState is called, which lifecycle method is called - didUpdateWidget or didChangeDependencies? 2) App is running and device theme changes (light/dark mode), which lifecycle method is called? 3) Device orientation changes, which lifecycle method? 4) Stream data updated, which lifecycle method? 5) State management (BLOC/Riverpod) state changes, which lifecycle method?
Answer
Overview
Understanding which StatefulWidget lifecycle methods are called in real-world scenarios is crucial for effective state management, performance optimization, and avoiding common bugs. This question covers 5 practical scenarios that every Flutter developer encounters.
Quick Reference Table
| Scenario | Lifecycle Method Called | Reason |
|---|---|---|
| 1. setState() called | text | setState schedules a rebuild |
| 2. Theme changed (light/dark) | text text | Theme is an InheritedWidget |
| 3. Orientation changed | text text | MediaQuery changes (InheritedWidget) |
| 4. Stream data updated | None automatically | Must call text text |
| 5. State management update (BLOC/Riverpod) | text | Widget rebuilds when watching/listening to state |
Scenario 1: setState() Called Inside Widget
Question
If setState is called inside the widget, will didUpdateWidgetdidChangeDependencies
Answer
Neither! Only
build()Explanation
dartclass CounterWidget extends StatefulWidget { _CounterWidgetState createState() => _CounterWidgetState(); } class _CounterWidgetState extends State<CounterWidget> { int _counter = 0; void initState() { super.initState(); print('1. initState called'); } void didChangeDependencies() { super.didChangeDependencies(); print('2. didChangeDependencies called'); } void didUpdateWidget(CounterWidget oldWidget) { super.didUpdateWidget(oldWidget); print('3. didUpdateWidget called'); } Widget build(BuildContext context) { print('4. build called'); return Column( children: [ Text('Counter: $_counter'), ElevatedButton( onPressed: () { setState(() { _counter++; print('setState called'); }); }, child: Text('Increment'), ), ], ); } }
Output when button is pressed:
textsetState called 4. build called
Why?
- directly schedules a rebuild by callingtext
setState()textbuild() - is only called when the parent passes a new widget configurationtext
didUpdateWidget() - is only called when InheritedWidget dependencies changetext
didChangeDependencies() - Internal state changes (via setState) don't trigger either method
Scenario 2: Device Theme Changed (Light ↔ Dark Mode)
Question
Application is running. If device theme changes from light to dark mode (or vice versa), which lifecycle method is called?
Answer
didChangeDependencies()build()
Explanation
dartclass ThemeAwareWidget extends StatefulWidget { _ThemeAwareWidgetState createState() => _ThemeAwareWidgetState(); } class _ThemeAwareWidgetState extends State<ThemeAwareWidget> { late Brightness _currentBrightness; void initState() { super.initState(); print('1. initState called'); } void didChangeDependencies() { super.didChangeDependencies(); print('2. didChangeDependencies called'); // Theme is accessed here final theme = Theme.of(context); _currentBrightness = theme.brightness; print(' Current theme: ${_currentBrightness == Brightness.dark ? "Dark" : "Light"}'); } Widget build(BuildContext context) { print('3. build called'); final theme = Theme.of(context); return Container( color: theme.brightness == Brightness.dark ? Colors.black : Colors.white, child: Text( 'Current theme: ${theme.brightness == Brightness.dark ? "Dark" : "Light"}', style: TextStyle( color: theme.brightness == Brightness.dark ? Colors.white : Colors.black, ), ), ); } }
Output when theme changes (light → dark):
text2. didChangeDependencies called Current theme: Dark 3. build called
Why?
- is provided bytext
Themewidget, which is an InheritedWidgettextTheme - When system theme changes, Flutter rebuilds the with new ThemeDatatext
MaterialApp - This updates the Theme InheritedWidget
- All widgets that access have theirtext
Theme.of(context)calledtextdidChangeDependencies() - Then is called to rebuild with new themetext
build()
Scenario 3: Device Orientation Changed
Question
If device orientation changes (portrait ↔ landscape), which lifecycle method is called?
Answer
didChangeDependencies()build()
Explanation
dartclass OrientationAwareWidget extends StatefulWidget { _OrientationAwareWidgetState createState() => _OrientationAwareWidgetState(); } class _OrientationAwareWidgetState extends State<OrientationAwareWidget> { Orientation? _previousOrientation; void initState() { super.initState(); print('1. initState called'); } void didChangeDependencies() { super.didChangeDependencies(); print('2. didChangeDependencies called'); final orientation = MediaQuery.of(context).orientation; if (_previousOrientation != null && _previousOrientation != orientation) { print(' Orientation changed: $_previousOrientation → $orientation'); } _previousOrientation = orientation; } Widget build(BuildContext context) { print('3. build called'); final orientation = MediaQuery.of(context).orientation; final size = MediaQuery.of(context).size; return orientation == Orientation.portrait ? _buildPortraitLayout(size) : _buildLandscapeLayout(size); } Widget _buildPortraitLayout(Size size) { return Column( children: [ Text('Portrait Mode'), Text('Width: ${size.width}, Height: ${size.height}'), ], ); } Widget _buildLandscapeLayout(Size size) { return Row( children: [ Text('Landscape Mode'), Text('Width: ${size.width}, Height: ${size.height}'), ], ); } }
Output when rotating device (portrait → landscape):
text2. didChangeDependencies called Orientation changed: Orientation.portrait → Orientation.landscape 3. build called
Why?
- is an InheritedWidget that provides device informationtext
MediaQuery - When orientation changes, screen dimensions change
- Flutter updates MediaQuery with new orientation and size
- All widgets using have theirtext
MediaQuery.of(context)calledtextdidChangeDependencies() - Then is called to rebuild with new layouttext
build()
Important: State persists across orientation changes (unlike Android Activities)
Scenario 4: Stream Data Updated
Question
If you're using a Stream and data is updated in the stream listener, which lifecycle method is called?
Answer
None automatically — You must manually call
setState()build()Explanation
dartimport 'dart:async'; class StreamListenerWidget extends StatefulWidget { _StreamListenerWidgetState createState() => _StreamListenerWidgetState(); } class _StreamListenerWidgetState extends State<StreamListenerWidget> { final _controller = StreamController<int>(); late StreamSubscription<int> _subscription; int _currentValue = 0; void initState() { super.initState(); print('1. initState called'); // Subscribe to stream _subscription = _controller.stream.listen((data) { print(' Stream received data: $data'); // ✅ MUST call setState to rebuild UI setState(() { _currentValue = data; print(' setState called in listener'); }); }); // Simulate stream data _simulateStreamData(); } void _simulateStreamData() async { await Future.delayed(Duration(seconds: 2)); _controller.add(10); await Future.delayed(Duration(seconds: 2)); _controller.add(20); } void didChangeDependencies() { super.didChangeDependencies(); print('2. didChangeDependencies called'); } Widget build(BuildContext context) { print('3. build called'); return Text('Stream value: $_currentValue'); } void dispose() { _subscription.cancel(); _controller.close(); super.dispose(); } }
Output:
text1. initState called 2. didChangeDependencies called 3. build called // After 2 seconds: Stream received data: 10 setState called in listener 3. build called // After 4 seconds: Stream received data: 20 setState called in listener 3. build called
Why?
- Streams are not part of the widget lifecycle
- When stream emits data, the listener callback is executed
- The callback must explicitly call to trigger a rebuildtext
setState() - Without , the UI won't update even thoughtext
setState()changedtext_currentValue
❌ Common Mistake
dart// ❌ WRONG - UI won't update! _subscription = _controller.stream.listen((data) { _currentValue = data; // Changed but no setState! // build() is NOT called! });
✅ Correct Approach
dart// ✅ CORRECT - UI updates _subscription = _controller.stream.listen((data) { setState(() { _currentValue = data; }); // Triggers build() });
Scenario 5: State Management (BLOC/Riverpod) Changes
Question
If you're using state management like BLOC or Riverpod and state changes, which lifecycle method is called?
Answer
build()
Explanation
State management frameworks trigger rebuilds through the Flutter framework, not through StatefulWidget lifecycle methods.
Example 1: BLOC
dartimport 'package:flutter_bloc/flutter_bloc.dart'; class CounterBloc extends Cubit<int> { CounterBloc() : super(0); void increment() => emit(state + 1); } class BlocCounterWidget extends StatefulWidget { _BlocCounterWidgetState createState() => _BlocCounterWidgetState(); } class _BlocCounterWidgetState extends State<BlocCounterWidget> { void initState() { super.initState(); print('1. initState called'); } void didChangeDependencies() { super.didChangeDependencies(); print('2. didChangeDependencies called'); } void didUpdateWidget(BlocCounterWidget oldWidget) { super.didUpdateWidget(oldWidget); print('3. didUpdateWidget called'); } Widget build(BuildContext context) { print('4. build called'); return BlocBuilder<CounterBloc, int>( builder: (context, count) { print(' BlocBuilder rebuilding with state: $count'); return Column( children: [ Text('Count: $count'), ElevatedButton( onPressed: () { context.read<CounterBloc>().increment(); print(' Bloc increment called'); }, child: Text('Increment'), ), ], ); }, ); } }
Output when increment button is pressed:
textBloc increment called 4. build called BlocBuilder rebuilding with state: 1
Why?
- BlocBuilder listens to the BLOC's state stream
- When state changes, BlocBuilder internally calls on its own Statetext
setState() - This triggers a rebuild of the parent widget's methodtext
build() - andtext
didChangeDependencies()are NOT calledtextdidUpdateWidget() - BLOC manages the rebuild mechanism internally
Example 2: Riverpod
dartimport 'package:flutter_riverpod/flutter_riverpod.dart'; final counterProvider = StateProvider<int>((ref) => 0); class RiverpodCounterWidget extends ConsumerStatefulWidget { _RiverpodCounterWidgetState createState() => _RiverpodCounterWidgetState(); } class _RiverpodCounterWidgetState extends ConsumerState<RiverpodCounterWidget> { void initState() { super.initState(); print('1. initState called'); } void didChangeDependencies() { super.didChangeDependencies(); print('2. didChangeDependencies called'); } void didUpdateWidget(RiverpodCounterWidget oldWidget) { super.didUpdateWidget(oldWidget); print('3. didUpdateWidget called'); } Widget build(BuildContext context) { print('4. build called'); final count = ref.watch(counterProvider); print(' Current count from Riverpod: $count'); return Column( children: [ Text('Count: $count'), ElevatedButton( onPressed: () { ref.read(counterProvider.notifier).state++; print(' Riverpod state incremented'); }, child: Text('Increment'), ), ], ); } }
Output when increment button is pressed:
textRiverpod state incremented 4. build called Current count from Riverpod: 1
Why?
- sets up a listener for state changestext
ref.watch(counterProvider) - When state changes, Riverpod internally triggers a rebuild
- Only is called (not didChangeDependencies or didUpdateWidget)text
build() - Riverpod uses InheritedWidget under the hood but manages rebuilds efficiently
Complete Comparison Table
| Scenario | initState | didChangeDependencies | didUpdateWidget | build | Why? |
|---|---|---|---|---|---|
| setState() called | ❌ | ❌ | ❌ | ✅ | setState directly schedules rebuild |
| Theme changed | ❌ | ✅ | ❌ | ✅ | Theme is InheritedWidget |
| Orientation changed | ❌ | ✅ | ❌ | ✅ | MediaQuery is InheritedWidget |
| Stream data updated | ❌ | ❌ | ❌ | ✅* | *Only if setState called in listener |
| BLOC state changed | ❌ | ❌ | ❌ | ✅ | BLOC triggers rebuild internally |
| Riverpod state changed | ❌ | ❌ | ❌ | ✅ | Riverpod triggers rebuild via ref.watch |
When Each Lifecycle Method IS Called
initState()
- ✅ Widget first created
- ❌ Never called again (unless widget is disposed and recreated)
didChangeDependencies()
- ✅ After initState()
- ✅ When InheritedWidget dependencies change (Theme, MediaQuery, Locale, etc.)
- ❌ NOT called for setState
- ❌ NOT called for stream updates
- ❌ NOT called for state management updates
didUpdateWidget()
- ✅ When parent rebuilds and passes new widget configuration
- ❌ NOT called for setState
- ❌ NOT called for theme/orientation changes
- ❌ NOT called for state management updates
build()
- ✅ After initState() and didChangeDependencies()
- ✅ After setState()
- ✅ After didUpdateWidget()
- ✅ After theme/orientation changes (via didChangeDependencies)
- ✅ When parent rebuilds
- ✅ When state management triggers rebuild
Best Practices
1. Use didChangeDependencies for InheritedWidget Changes
dart✅ Good: void didChangeDependencies() { super.didChangeDependencies(); final theme = Theme.of(context); final mediaQuery = MediaQuery.of(context); if (mediaQuery.orientation == Orientation.landscape) { // Handle landscape-specific logic } }
2. Always Call setState for Manual Updates
dart✅ Good: _streamSubscription = stream.listen((data) { setState(() { _data = data; }); }); ❌ Bad: _streamSubscription = stream.listen((data) { _data = data; // UI won't update! });
3. Use State Management for Complex State
dart✅ Good - Use BLOC for complex state: class UserProfileWidget extends StatelessWidget { Widget build(BuildContext context) { return BlocBuilder<UserBloc, UserState>( builder: (context, state) { return state.when( loading: () => CircularProgressIndicator(), loaded: (user) => Text(user.name), error: (error) => Text('Error: $error'), ); }, ); } } ❌ Bad - Manual setState for complex async logic: class UserProfileWidget extends StatefulWidget { ... } class _UserProfileWidgetState extends State<UserProfileWidget> { User? _user; bool _isLoading = false; String? _error; // Lots of manual setState calls, error-prone }
4. Detect Orientation Changes Correctly
dart✅ Good: void didChangeDependencies() { super.didChangeDependencies(); final newOrientation = MediaQuery.of(context).orientation; if (_previousOrientation != newOrientation) { // Orientation actually changed _handleOrientationChange(newOrientation); } _previousOrientation = newOrientation; } ❌ Bad - Using build(): Widget build(BuildContext context) { final orientation = MediaQuery.of(context).orientation; // ❌ This runs on EVERY build, not just orientation changes! _handleOrientationChange(orientation); return ... }
Common Mistakes
Mistake 1: Expecting didChangeDependencies on setState
dart// ❌ WRONG ASSUMPTION setState(() { _counter++; }); // User expects didChangeDependencies to be called - it's NOT!
Mistake 2: Not Calling setState in Stream Listener
dart// ❌ WRONG _subscription = stream.listen((data) { _value = data; // Changed but no rebuild! }); // ✅ CORRECT _subscription = stream.listen((data) { setState(() { _value = data; }); });
Mistake 3: Using build() for Side Effects
dart// ❌ WRONG - build() is called frequently! Widget build(BuildContext context) { _logAnalytics(); // Called multiple times per second during animations! return ... } // ✅ CORRECT - Use didChangeDependencies for one-time effects void didChangeDependencies() { super.didChangeDependencies(); _logAnalytics(); // Called only when dependencies change }
Summary
setState() → Only
is calledtextbuild()
Theme/Orientation Change →
→textdidChangeDependencies()(InheritedWidget changed)textbuild()
Stream Update → Manual
required →textsetState()textbuild()
State Management (BLOC/Riverpod) → Framework triggers
automaticallytextbuild()
didUpdateWidget() → Only when parent passes new widget config