Agent skill

Dart Lifecycle Disposed Flag Overload

by divinevideo in divinevideo/divine-mobile

Fix Dart/Flutter services where calling start() after stop() is a silent no-op because stop() sets a disposed (or similar) flag that start()'s guard short-circuits on.

MPL-2.0Auto-check passedMobile

Install Dart Lifecycle Disposed Flag Overload

skills CLI
$ npx skills add divinevideo/divine-mobile --skill dart-lifecycle-disposed-flag-overload -a claude-code

Project install by default; add -g for ~/.claude/skills/.

GitHub CLI
$ gh skill install divinevideo/divine-mobile dart-lifecycle-disposed-flag-overload --agent claude-code

Project scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).

Manual copy
$ git clone --depth 1 https://github.com/divinevideo/divine-mobile.git skills-src && mkdir -p .claude/skills && cp -r skills-src/.agents/skills/dart-lifecycle-disposed-flag-overload .claude/skills/dart-lifecycle-disposed-flag-overload && rm -rf skills-src

Use ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.

Claude Code skills documentation · loads skills from .claude/skills/

Facts

Skill name
dart-lifecycle-disposed-flag-overload
GitHub stars
266
Token cost
~2k tokens
SKILL.md length
658 words
Files
1
Skills in repo
103
Repo updated
First seen
Licence
MPL-2.0

At a glance

Fix Dart/Flutter services where calling start() after stop() is a silent no-op because stop() sets a disposed (or similar) flag that start()'s guard short-circuits on.

  • Works in 2 steps: "This instance is permanently torn down"… → "Currently not listening, but could be…
  • A repository/service/controller has startListening/stopListening
  • SKILL.md covers Problem, Context / Trigger Conditions, Solution and Verification, plus 3 more sections
  • Instructions only: no scripts, shell commands, URLs or credentials in SKILL.md

What it does

Dart Lifecycle Disposed Flag Overload is an agent skill from divinevideo/divine-mobile. Fix Dart/Flutter services where calling start() after stop() is a silent no-op because stop() sets a disposed (or similar) flag that start()'s guard short-circuits on. Use when: (1) A repository/service/controller has startListening/stopListening, subscribe/unsubscribe, open/close, or similar lifecycle methods, (2) Re-opening the service after closing it appears to do nothing — no subscription, no events, no error, (3) Unit tests that only exercise a single mount/open/start cycle pass while the real app breaks on…

Its SKILL.md is about 2k tokens, which your agent loads only when the skill is triggered. It is a single SKILL.md file with no bundled scripts.

It sits in Mobile, covering Cross-platform mobile apps and Unit testing. It works with Dart and Flutter. The licence is MPL-2.0.

When your agent uses it

  • A repository/service/controller has startListening/stopListening
  • Subscribe/unsubscribe
  • Similar lifecycle methods
  • Re-opening the service after closing it appears to do nothing — no subscription

Example prompts

  • “in the middle of tearing down this instance forever”
  • “currently stopped, can be re-started”
  • “/dart-lifecycle-disposed-flag-overload”

Workflow steps

2 steps, taken from the first numbered list in SKILL.md.

  1. "This instance is permanently torn down" (e.g. user switched accounts, object
  2. "Currently not listening, but could be re-started" (e.g. user navigated away

What it can do on your machine

Read from SKILL.md and the folder at commit c3d6f7e. It shows what the files ask for, not the result of running them.

  • Tool permissions

    Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.

    From allowed-tools in the SKILL.md frontmatter.

  • Runs code

    No scripts in the folder and no shell commands in SKILL.md (its code samples are dart).

    From the folder's file list and the shell code blocks in SKILL.md.

  • Network

    Links to these hosts (documentation or services it may open):

    • api.dart.dev
    • api.flutter.dev

    From URLs in SKILL.md, links to its own repository left out.

  • Credentials

    Names no API keys, tokens, secrets or passwords.

    From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.

Context cost

Dart Lifecycle Disposed Flag Overload loads about 2k tokens when it runs. Until then it costs about 225 tokens; SKILL.md has 658 words of instructions outside code blocks.

Always · name and description, kept in context so the agent knows when to use it
~225
When it runs · the whole SKILL.md, loaded when a task matches
~2k

Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.

Safety

Auto-check passed

The automated check found no risky patterns in SKILL.md.

Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.

SKILL.md

The full file from divinevideo/divine-mobile at commit c3d6f7e, republished under its MPL-2.0 licence (© divinevideo). 658 words, ~1,955 tokens.

Download SKILL.mdSave it as .claude/skills/dart-lifecycle-disposed-flag-overload/SKILL.md (or your agent's skills folder).
name
dart-lifecycle-disposed-flag-overload
description
Fix Dart/Flutter services where calling start() after stop() is a silent no-op because stop() sets a _disposed (or similar) flag that start()'s guard short-circuits on. Use when: (1) A repository/service/controller has startListening/stopListening, subscribe/unsubscribe, open/close, or similar lifecycle methods, (2) Re-opening the service after closing it appears to do nothing — no subscription, no events, no error, (3) Unit tests that only exercise a single mount/open/start cycle pass while the real app breaks on the second visit to a screen, (4) A boolean flag is used both for "in the middle of tearing down this instance forever" AND for "currently stopped, can be re-started". Common in Riverpod/Bloc-driven screens that wire startListening() in initState and stopListening() in dispose — the second time the user visits the screen, nothing happens.
author
Claude Code
version
1.0.0
date
2026-04-05

Dart Lifecycle _disposed Flag Overload

Problem

A Dart/Flutter class with start/stop lifecycle methods sets a "disposed"/"stopped" boolean inside stop(), but the guard clause in start() short-circuits whenever that boolean is true. After a stop→start cycle, start() silently returns without doing anything. The class has two distinct concerns conflated into one flag:

  1. "This instance is permanently torn down" (e.g. user switched accounts, object is being discarded) — should prevent any further work.
  2. "Currently not listening, but could be re-started" (e.g. user navigated away from the inbox screen and may come back) — must allow future start() calls.

When those concerns share a single flag, the second concern silently breaks the first.

Context / Trigger Conditions

  • A class (repository, service, controller, bloc, cubit, notifier) has methods like:
    • startListening() / stopListening()
    • subscribe() / unsubscribe()
    • connect() / disconnect()
    • open() / close()
  • stop() includes a line like _disposed = true; or _stopped = true;
  • start() begins with a guard like:
    dart
    if (_subscription != null || _disposed || !isInitialized) return;
  • Symptom: the feature works on first open, breaks on every subsequent open
  • Unit tests that mock the dependencies pass because they only exercise one cycle OR because the mock doesn't model the real instance's internal state
  • Manual QA finds that leaving and returning to a screen breaks the feature silently (no error thrown, no log emitted, no visible indication)

Solution

Separate the two concerns. Reserve the permanent-teardown flag for the code path that actually tears the instance down for good (typically a _resetState() called on user-switch or full logout), and do NOT set it inside stop().

Before (broken)
dart
class MyRepository {
  bool _disposed = false;
  StreamSubscription<Event>? _subscription;

  void startListening() {
    if (_subscription != null || _disposed || !isInitialized) return;
    _subscription = _client.subscribe(...).listen(...);
  }

  Future<void> stopListening() async {
    _disposed = true;                    // ← THE BUG
    await _subscription?.cancel();
    _subscription = null;
  }

  void _resetState() {
    _disposed = true;
    // ... wipe credentials ...
    _disposed = false;
  }
}

After stopListening(), _disposed == true forever until _resetState() is called (which only happens on user switch). Any subsequent startListening() hits the guard and returns silently.

After (fixed)
dart
class MyRepository {
  bool _disposed = false;
  StreamSubscription<Event>? _subscription;

  void startListening() {
    // Guard still checks _disposed for the permanent-teardown case — that
    // window is only open during _resetState()'s synchronous body.
    if (_subscription != null || _disposed || !isInitialized) return;
    _subscription = _client.subscribe(...).listen(...);
  }

  Future<void> stopListening() async {
    // Do NOT set _disposed here — _disposed is reserved for _resetState()
    // (permanent teardown, e.g. user switch). Setting it would make a
    // subsequent startListening() call a silent no-op and break re-open
    // flows like "user leaves the screen and comes back later".
    await _subscription?.cancel();
    _subscription = null;
  }

  void _resetState() {
    _disposed = true;
    // ... wipe credentials, cancel subscription, etc. ...
    _disposed = false;
  }
}

The _subscription != null half of the guard is still sufficient to make startListening() idempotent against double-calls within a single listening lifetime.

Verification

  1. Add a regression test that exercises start → stop → start and asserts the start work happened twice:

    dart
    test('startListening after stopListening re-opens the subscription', () async {
      final repo = createRepository();
      repo.initialize(...);
    
      repo.startListening();
      await repo.stopListening();
      repo.startListening();
    
      // Both opens must hit the client.
      verify(() => mockClient.subscribe(any(), ...)).called(2);
    
      await repo.stopListening();
    });
  2. Manual QA: visit the screen that drives the lifecycle, back out of it, visit it again. The feature should work on the second visit identically to the first.

  3. Run the existing test for the permanent-teardown path (e.g. user switch / _resetState()) and confirm it still passes. The fix should not affect that path.

Show full SKILL.md (293 more words)Show less

Example

From divine-mobile (PR #2769, April 2026): DmRepository drove NIP-17 gift-wrap subscription lifecycle from the inbox screen's initState/dispose. On the second visit to the inbox, DMs silently stopped arriving. Root cause: stopListening() had _disposed = true; as its first line. Fix: delete that line, leave an explanatory comment, add a regression test that asserts mockNostrClient.subscribe was called twice after an open → close → open cycle. Commit bd1420eb3 fix(dm): allow startListening() to succeed after stopListening().

Notes

  • Why mocks hide this bug: unit tests that mock the dependency (e.g. a mock NostrClient) only verify that the repository calls subscribe() once when startListening() is called. They don't exercise the real state machine across multiple cycles unless the test explicitly cycles start→stop→start and verifies the second start also called subscribe. Add that cycle to your lifecycle test suite preemptively.
  • Alternative name for the flag: if you need two flags because both concerns genuinely exist, name them for their actual meaning: _permanentlyDisposed (or _torn_down) vs _isListening (or _started). A single bool with an overloaded meaning is the root smell.
  • Riverpod/Bloc lifecycle binding: this bug is especially common when a screen wires startListening() in initState and stopListening() in dispose and the user can leave and return to the screen. If that flow is new, always add a "visit twice" test to your widget test for that screen.
  • Watch for asymmetric reconnect paths: onDone callbacks on cancelled streams may also read the flag and decide whether to schedule a reconnect. After separating the flags, audit every read of the old flag to confirm the new semantics still match the callsite's intent.

References

© divinevideo, MPL-2.0. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file

Files

Just SKILL.md in .agents/skills/dart-lifecycle-disposed-flag-overload of divinevideo/divine-mobile.

Open the folder on GitHubat commit c3d6f7e

Compare with similar skills

Dart Lifecycle Disposed Flag Overload next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.

Dart Lifecycle Disposed Flag Overload compared with similar skills
SkillStarsUsed inTokensAuto-checkLicenceRepo updated
Dart Lifecycle Disposed Flag Overload this skilldivinevideo/divine-mobile266—~2kAutomated safety check: PassMPL-2.0
BlocVeryGoodOpenSource/vgv-ai-flutter-plugin170—~2kAutomated safety check: PassMIT
TestingVeryGoodOpenSource/vgv-ai-flutter-plugin170—~3.2kAutomated safety check: PassMIT
Flutter TestingMADTeacher/mad-agents-skills110—~1.5kAutomated safety check: PassMIT
Testingevanca/flutter-ai-rules650—~1.1kAutomated safety check: PassMIT
Native Testing Strategybladeofgod/flutter-ai-harness116—~345Automated safety check: PassMIT

Similar skills

  • Bloc

    VeryGoodOpenSource/vgv-ai-flutter-plugin

    Best practices for Bloc state management in Flutter/Dart, covering Cubit versus Bloc, event and state naming, sealed classes with Equatable, the Page/View split with BlocProvider, BlocBuilder…

    170 GitHub stars~2k tokensUpdated 4 days ago
    MobileAuto-check passed
  • Testing

    VeryGoodOpenSource/vgv-ai-flutter-plugin

    Best practices for Dart unit tests, Flutter widget tests, and golden file tests, covering group and test naming, setUp lifecycle and isolation, mocking with package:mocktail, and the shared pumpApp…

    170 GitHub stars~3.2k tokensUpdated 4 days ago
    MobileAuto-check passed
  • Flutter Testing

    MADTeacher/mad-agents-skills

    Write, fix, review, debug, and validate Flutter tests for apps, packages, and plugins.

    110 GitHub stars~1.5k tokensUpdated 5 mo ago
    Testing & QAAuto-check passed
  • Testing

    evanca/flutter-ai-rules

    A skill your agent uses when writing or reviewing Flutter/Dart tests (unit, widget, golden), fixing flaky tests, adding coverage, or choosing between unit and widget tests.

    650 GitHub stars~1.1k tokensUpdated 26 days ago
    Testing & QAAuto-check passed
  • Native Testing Strategy

    bladeofgod/flutter-ai-harness

    适用:设计或审查 Kotlin/Swift 原生模块、Bridge Adapter、Host 编译、模拟器/设备和系统能力验证。不适用:纯 Dart/Flutter 测试或用 Fake 代替相机和权限真机验证。触发词:JUnit、XCTest、Robolectric、instrumented test、Swift Testing、Framework Fake、Gradle…

    116 GitHub stars~345 tokensUpdated 1 mo ago
    MobileAuto-check passed
  • Flutter Tester

    FNOSP/FlyNarwhal

    A skill your agent uses when creating, writing, fixing, or reviewing tests in a Flutter project.

    509 GitHub stars~1.4k tokensUpdated 2 days ago
    MobileAuto-check passed

More from divinevideo/divine-mobile

All 103 skills in this repo
  • Fix ArgoCD ExternalSecret deployment failing with "namespace X is not permitted in project Y".

    266 GitHub stars~931 tokensUpdated yesterday
    Auto-check passed
  • Art Direct

    divinevideo/divine-mobile

    Art direction for any content — reads text, PDF, Word, HTML, PPT, then proposes 2-3 creative directions with photography style, mood, and visual language.

    266 GitHub stars~4.8k tokensUpdated yesterday
    Auto-check passed
  • Async Await Null Race Condition

    divinevideo/divine-mobile

    Fix "Null check operator used on a null value" errors when an object is set to null during an async await.

    266 GitHub stars~881 tokensUpdated yesterday
    Auto-check passed
  • AWS V4 Signing Custom Headers Gcs

    divinevideo/divine-mobile

    Add custom metadata headers (x-amz-meta-) to AWS v4 signed requests for GCS S3-compatible API.

    266 GitHub stars~1k tokensUpdated yesterday
    Auto-check passed
  • Bash Herestring Newline Secrets

    divinevideo/divine-mobile

    Fix password/secret authentication failures caused by trailing newlines when creating Google Cloud secrets (or similar) with bash here-strings.

    266 GitHub stars~791 tokensUpdated yesterday
    Auto-check passed
  • Fix silent video/media processing failures caused by URL extraction code that filters on file extensions (.mp4, .webm, .webp).

    266 GitHub stars~1.1k tokensUpdated yesterday
    Auto-check passed

Works with

Questions about Dart Lifecycle Disposed Flag Overload

What does Dart Lifecycle Disposed Flag Overload do?

Fix Dart/Flutter services where calling start() after stop() is a silent no-op because stop() sets a disposed (or similar) flag that start()'s guard short-circuits on. Dart Lifecycle Disposed Flag Overload is an agent skill from divinevideo/divine-mobile. Fix Dart/Flutter services where calling start() after stop() is a silent no-op because stop() sets a disposed (or similar) flag that start()'s guard short-circuits on.

When should I use Dart Lifecycle Disposed Flag Overload?

Dart Lifecycle Disposed Flag Overload fits situations like: A repository/service/controller has startListening/stopListening; subscribe/unsubscribe; similar lifecycle methods; re-opening the service after closing it appears to do nothing — no subscription.

How do I install Dart Lifecycle Disposed Flag Overload in Claude Code?

Run `npx skills add divinevideo/divine-mobile --skill dart-lifecycle-disposed-flag-overload -a claude-code`. Or copy the skill folder (.agents/skills/dart-lifecycle-disposed-flag-overload in divinevideo/divine-mobile) into .claude/skills/dart-lifecycle-disposed-flag-overload in your project. Claude Code loads it when a task matches its description.

How do I install Dart Lifecycle Disposed Flag Overload in Codex?

Run `npx skills add divinevideo/divine-mobile --skill dart-lifecycle-disposed-flag-overload -a codex`. Or copy the skill folder (.agents/skills/dart-lifecycle-disposed-flag-overload in divinevideo/divine-mobile) into .agents/skills/dart-lifecycle-disposed-flag-overload in your project. Codex loads it when a task matches its description.

Can I use Dart Lifecycle Disposed Flag Overload in Cursor, Gemini CLI or GitHub Copilot?

Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add divinevideo/divine-mobile --skill dart-lifecycle-disposed-flag-overload -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/dart-lifecycle-disposed-flag-overload, .gemini/skills/dart-lifecycle-disposed-flag-overload, .github/skills/dart-lifecycle-disposed-flag-overload and .opencode/skills/dart-lifecycle-disposed-flag-overload in your project.

What does Dart Lifecycle Disposed Flag Overload need to run?

SKILL.md names no scripts, command-line tools or credentials: Dart Lifecycle Disposed Flag Overload is instructions for the agent only.

Does Dart Lifecycle Disposed Flag Overload access the network?

SKILL.md names 2 domains. As links in the text: api.dart.dev and api.flutter.dev. This is read from the text; nothing was executed.

Is Dart Lifecycle Disposed Flag Overload safe to install?

Our automated static check of SKILL.md found no risky patterns, such as piping downloads into a shell, reading credential files or hidden Unicode. It is not a guarantee. Review the folder before installing.

What licence does Dart Lifecycle Disposed Flag Overload use?

Dart Lifecycle Disposed Flag Overload is published under the MPL-2.0 licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.

How many tokens does Dart Lifecycle Disposed Flag Overload use?

About 2k tokens (SKILL.md is roughly 7.8k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full.

What are the alternatives to Dart Lifecycle Disposed Flag Overload?

Skills that share tags, products or a category with Dart Lifecycle Disposed Flag Overload: Bloc (VeryGoodOpenSource/vgv-ai-flutter-plugin, 170 stars), Testing (VeryGoodOpenSource/vgv-ai-flutter-plugin, 170 stars), Flutter Testing (MADTeacher/mad-agents-skills, 110 stars) and Testing (evanca/flutter-ai-rules, 650 stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.

Who maintains Dart Lifecycle Disposed Flag Overload?

divinevideo (a GitHub organization) maintains it in divinevideo/divine-mobile, which has 266 GitHub stars. The repository holds 103 skills in this directory. The repository was last updated on October 10, 2026.

Source: divinevideo/divine-mobile on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.