Skip to content
Back to skills

Flutter Deactivate Setstate During Build

ASecurity

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.

  • 265 stars
  • 0 votes
  • 0 copies
  • 6 views
  • Added May 27, 2026
content-media

Works with

  • claude code

Security analysis

A100/100

Scanned May 27, 2026

npx -y skills add divinevideo/divine-mobile --skill flutter-deactivate-setstate-during-build --agent claude-code

Installs 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 with every re-scan.

Security grade badge for Flutter Deactivate Setstate During Build
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/divinevideo-flutter-deactivate-setstate-during-build/badge)](https://www.skillsdirectory.com/skills/divinevideo-flutter-deactivate-setstate-during-build)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
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()`

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…