Fix "setState() or markNeedsBuild() called during build" error triggered from deactivate(). Use when: (1) Error occurs during widget disposal or navigation away, (2) Stack trace shows deactivate -> some notifier -> ValueListenableBuilder._valueChanged, (3) Video/audio player pause() or stop() calls in deactivate() cause the error. The fix is deferring state-modifying operations to addPostFrameCallback.
Scanned 9/3/2026
Install to Claude Code
npx -y skills add divinevideo/divine-mobile --skill flutter-deactivate-setstate-during-build --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Flutter Deactivate Setstate During Build?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/divinevideo-flutter-deactivate-setstate-during-build-divine-mobile)More formats (shields.io, HTML) on the badges page.
---
name: flutter-deactivate-setstate-during-build
description: |
Fix "setState() or markNeedsBuild() called during build" error triggered from deactivate().
Use when: (1) Error occurs during widget disposal or navigation away, (2) Stack trace shows
deactivate -> some notifier -> ValueListenableBuilder._valueChanged, (3) Video/audio player
pause() or stop() calls in deactivate() cause the error. The fix is deferring state-modifying
operations to addPostFrameCallback.
author: Claude Code
version: 1.0.0
date: 2025-01-27
---
# Flutter: setState During Build from deactivate()
## Problem
When navigating away from a screen, calling methods like `VideoPlayerController.pause()` in
`deactivate()` triggers `setState() or markNeedsBuild() called during build` because the
controller notifies its listeners synchronously during widget tree teardown.
## Context / Trigger Conditions
- Error: `setState() or markNeedsBuild() called during build`
- Stack trace includes:
- `YourWidget.deactivate`
- `VideoPlayerController.pause` (or similar)
- `ChangeNotifier.notifyListeners`
- `ValueListenableBuilder._valueChanged`
- Widget using `ValueListenableBuilder<VideoPlayerValue>` or similar
- Trying to pause/stop media playback when navigating away
## Solution
Defer the state-modifying operation to after the current frame:
```dart
@override
void deactivate() {
// Capture controller reference while ref/context is still valid
final controller = _getController();
// Defer pause to after current frame
if (controller != null && controller.value.isPlaying) {
WidgetsBinding.instance.addPostFrameCallback((_) {
if (controller.value.isPlaying) {
controller.pause();
}
});
}
super.deactivate();
}
```
## Key Insights
1. **Why deactivate() is problematic**: It's called during the widget tree teardown phase,
which is part of the build process. Any synchronous notification to listeners will
trigger rebuilds during build.
2. **Why dispose() doesn't have this issue**: By `dispose()`, the widget is fully unmounted
and listeners are typically already removed.
3. **Capture references early**: Get controller/state references before the async gap
since `ref`, `context`, or other widget state may become invalid.
4. **Guard against double-execution**: Check `isPlaying` again in the callback since
state may have changed.
## Verification
- Navigate away from the screen multiple times
- No error in console
- Video/audio properly pauses when leaving
## Example: Full Implementation
```dart
class _FullscreenVideoScreenState extends ConsumerState<FullscreenVideoScreen> {
@override
void deactivate() {
_schedulePauseCurrentVideo();
super.deactivate();
}
void _schedulePauseCurrentVideo() {
final controller = _getCurrentController();
if (controller == null || !controller.value.isInitialized) {
return;
}
// Defer to avoid setState during build
WidgetsBinding.instance.addPostFrameCallback((_) {
if (controller.value.isPlaying) {
controller.pause();
}
});
}
}
```
## Notes
- This pattern applies to any `ChangeNotifier` that updates synchronously
- Also relevant for: `AnimationController.stop()`, `TextEditingController` updates
- If you need the pause to happen immediately (rare), consider using
`controller.pause()` without notifying, though this breaks the observer pattern
- The same issue can occur in `didUpdateWidget` when old controllers are disposed
## Related Patterns
- Use `deactivate()` for cleanup that needs `ref`/`context` (like unsubscribing)
- Use `dispose()` for cleanup that doesn't (like disposing controllers you own)
- Never call `setState()` in `deactivate()` or `dispose()`
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!