dangi_doctor 0.3.1
dangi_doctor: ^0.3.1 copied to clipboard
Flutter app health CLI — crawls every screen, analyses widget trees, measures performance, detects bugs, and generates integration tests.
Dangi Doctor 🩺 #
Your Flutter app's personal physician.
Dangi Doctor connects to your live Flutter app, crawls every screen automatically, finds real bugs through static analysis, measures performance, and generates Flutter integration tests — all without you writing a single line of test code.
What's new in v0.3.0 #
- The weekly knowledge updater works again — all the Flutter doc sources had moved and were silently 404ing; they're repaired, the updater now fails loudly instead of shipping placeholders, and each release bundles current knowledge.
- Generated tests compile — the interaction and performance tests referenced a helper that was never emitted; that's fixed, along with import paths, dynamic keys, and Cupertino-app support.
- Crawler correctness (verified on a live Android device) — accurate screen naming through pushed route stacks, navigation that returns home without exiting the app, whole-word dialog-button matching, and coordinate re-resolution after returning from a child screen. It also no longer kills unrelated
flutter run/adb sessions on your machine. - Real performance numbers — frames read from
Flutter.Frameevents, actual memory usage, and a jank budget that follows the device's real refresh rate (120 Hz → 8.3 ms, not a hardcoded 16 ms). - Faster, safer AI diagnosis — cross-screen prompt caching (~90% cheaper input), real retries/timeouts, current model defaults, and the Gemini key no longer travelling in the request URL.
- Proper CLI —
--help,--version,--project,--vm-url,--device,--no-ai; honest exit codes; safe to run in CI.
What it does #
1. Live app crawling #
Connects to your running Flutter app via the Dart VM service. Walks every reachable screen by tapping navigation triggers (bottom nav, buttons, drawers). No emulator required — works on real physical devices.
2. Static analysis — detects real bugs before they reach production #
Scans your lib/ source code for patterns that cause crashes at runtime:
| Bug type | What it catches |
|---|---|
late_field_double_init |
late field assigned in a method called from didChangeDependencies() without a guard → LateInitializationError |
setState_after_dispose |
setState() in async method without if (mounted) → "setState called after dispose" |
stream_subscription_leak |
StreamSubscription field with no .cancel() in dispose() → memory leak |
build_side_effects |
setState() or await directly inside build() → infinite rebuild loop |
3. Generated integration tests — with exact fix instructions #
For every screen and every detected bug, Dangi Doctor writes Flutter integration tests directly into your project at integration_test/dangi_doctor/. Each failing test tells you:
- Exact file and line number
- Plain-English explanation of the bug
- Copy-pasteable fix code
BUG: pages/SplashScreen/splashScreenPage.dart:74 —
late field `_appLinks` double-init in `initDeepLinks()`
━━━ BUG DETECTED ━━━
File: pages/SplashScreen/splashScreenPage.dart:74
Problem:
Late field `_appLinks` is assigned in `initDeepLinks()` which is called
from `didChangeDependencies()` without an initialization guard...
Fix:
bool _initDeepLinksCalled = false;
@override
void didChangeDependencies() {
super.didChangeDependencies();
if (!_initDeepLinksCalled) {
_initDeepLinksCalled = true;
initDeepLinks();
}
}
━━━━━━━━━━━━━━━━━━━
4. HTML health report #
After every diagnosis, Dangi Doctor opens a health report in your browser automatically.
- Overall health score (0–100)
- Per-screen performance grade (A–F) with build time, jank %, memory
- All issues listed with severity (error / warning / info)
- Static analysis bugs table with file locations and fix code
- Saved to
.dangi_doctor/report_<timestamp>.html
5. AI diagnosis (optional) #
If you have a Claude, OpenAI, Gemini, Groq, or Ollama API key, Dangi Doctor gives each screen a written diagnosis — what's wrong, why it matters, and a prioritised fix list.
The AI is powered by a 3-layer knowledge system that stays current automatically:
- Layer 1 — Flutter official knowledge: widget lifecycle, performance rules, state management error patterns for Provider / BLoC / Riverpod / GetX, common Flutter errors by category, and the last 5 Flutter version changelogs. Updated weekly by pulling directly from the Flutter GitHub repo.
- Layer 2 — Community anti-patterns: most common mistakes found across thousands of real production Flutter apps.
- Layer 3 — Your project: auto-detected on first run. State management, dependencies, folder structure, naming conventions, largest files. Stored at
.dangi_doctor/project.json.
Requirements #
- Dart SDK ≥ 3.0
- Flutter project with a physical Android device or emulator
adbinstalled (Android Debug Bridge)
Installation #
dart pub global activate dangi_doctor
Or add to your Flutter project's dev dependencies:
dev_dependencies:
dangi_doctor: ^0.3.0
Usage #
From the root of your Flutter project:
cd /path/to/your/flutter/app
dangi_doctor
Dangi Doctor auto-detects your project from the current directory. If you need to point it at a different path, use the DANGI_PROJECT env var.
On first run, Dangi Doctor will ask how to connect:
┌─────────────────────────────────────────────┐
│ How do you want to connect? │
│ │
│ 1. Launch app now (Dangi Doctor runs it) │
│ 2. App already running — paste VM URL │
└─────────────────────────────────────────────┘
Choose 1 to let Dangi Doctor launch your app, or 2 to paste the VM service URL from a running flutter run --debug session.
Command-line options #
| Flag | Description |
|---|---|
--project <path> |
Flutter project path (overrides DANGI_PROJECT and cwd auto-detection) |
--vm-url <ws://…> |
VM service URL of an already-running app (skips the connect prompt) |
--device <id> |
ADB device id for widget taps (auto-detected if omitted) |
--no-ai |
Crawler + static analysis only; skip AI diagnosis |
--version |
Print the version and exit |
--help |
Print usage and exit |
Fully non-interactive runs are supported for CI — pass --project, --vm-url,
and --no-ai and Dangi Doctor never prompts; a crash exits non-zero.
Run generated tests #
flutter test integration_test/dangi_doctor/<screen>_smoke_test.dart \
-d <device_id>
With auth token (for apps requiring login):
flutter test integration_test/dangi_doctor/<screen>_smoke_test.dart \
--dart-define=TEST_TOKEN=your_token \
-d <device_id>
AI diagnosis — free options #
You do not need a paid subscription to get AI diagnosis.
| Provider | Cost | How to get a key |
|---|---|---|
| Groq | Free tier (14,400 req/day) | Sign up at console.groq.com — no credit card |
| Ollama | Free, runs locally | Install from ollama.com, run ollama pull llama3.1 |
| Claude | Paid | console.anthropic.com |
| OpenAI | Paid | platform.openai.com |
| Gemini | Paid | aistudio.google.com |
If no API key is set, Dangi Doctor asks at runtime which provider to use. Choose Groq for the quickest free setup, or Ollama for fully offline diagnosis.
Environment variables #
| Variable | Description |
|---|---|
DANGI_PROJECT |
Path to your Flutter project (auto-detected from cwd; --project takes precedence) |
ANTHROPIC_API_KEY |
Claude API key for AI diagnosis (CLAUDE_API_KEY also accepted) |
OPENAI_API_KEY |
OpenAI API key for AI diagnosis |
GEMINI_API_KEY |
Gemini API key for AI diagnosis |
GROQ_API_KEY |
Groq API key for AI diagnosis |
Model choices default to current models but are all overridable, so a model rename never requires an update:
| Variable | Default |
|---|---|
DANGI_CLAUDE_MODEL |
claude-opus-4-8 |
DANGI_OPENAI_MODEL |
gpt-4o |
DANGI_GEMINI_MODEL |
gemini-2.5-pro |
DANGI_GROQ_MODEL |
llama-3.1-8b-instant |
DANGI_OLLAMA_MODEL |
llama3.1 |
DANGI_OLLAMA_URL |
http://localhost:11434 |
DANGI_MAX_TOKENS |
4096 |
How it works #
dart run dangi_doctor
│
├── 1. Detect AI provider (Claude / OpenAI / Gemini / Groq / Ollama)
│
├── 2. Connect to Flutter app via Dart VM service WebSocket
│ └── Port remapping: reads /proc/net/tcp on device to find
│ the real VM port, then adb forward tcp:8181 tcp:<actual>
│
├── 3. Wait for splash screen to dismiss
│
├── 4. Static analysis
│ └── Scan lib/ for KnownRisk patterns + detect routes
│ └── LateInitializationError, setState after dispose,
│ stream leaks, build() side effects
│
├── 5. Crawl all screens
│ └── Tap nav triggers (bottom nav, buttons, drawers)
│ └── Capture widget tree per screen via VM service
│ └── Measure frame performance via Flutter.Frame events
│ └── Detect widget tree issues (nesting, anti-patterns)
│
├── 6. AI diagnosis per screen (if API key present)
│ └── 3-layer knowledge prompt → Claude/GPT/Gemini
│
├── 7. Generate integration tests
│ └── integration_test/dangi_doctor/<screen>_smoke_test.dart
│ └── integration_test/dangi_doctor/<screen>_interaction_test.dart
│ └── integration_test/dangi_doctor/<screen>_perf_test.dart
│ └── integration_test/dangi_doctor/test_helper.dart
│
└── 8. Generate HTML report → open in browser
└── .dangi_doctor/report_<timestamp>.html
Knowledge auto-update #
Layer 1 knowledge is bundled as a Dart constant in the package. A weekly GitHub Actions workflow refreshes it in the repo from the Flutter GitHub repo and flutter.dev docs, and every dangi_doctor release ships the knowledge current at release time — so dart pub upgrade dangi_doctor picks up whatever the newest published version bundles. Layer 2 (community anti-patterns) is maintained in knowledge/community_patterns.md — PRs welcome.
To update manually (for contributors):
dart run tool/update_knowledge.dart
Output files #
After a diagnosis run, your Flutter project will contain:
integration_test/
dangi_doctor/
test_helper.dart ← shared setup: Firebase init, auth injection
<screen>_smoke_test.dart ← launch test + bug-specific targeted tests
<screen>_interaction_test.dart ← tap every button, verify navigation
<screen>_perf_test.dart ← frame timing assertions
.dangi_doctor/
project.json ← Layer 3 project fingerprint
report_<timestamp>.html ← HTML health report
vm_url.txt ← cached VM URL for reconnection
License #
MIT