Dart idioms — null safety, Futures and Streams, widget composition, and Riverpod app architecture. Auto-load when working with .dart files, pubspec.yaml, or when the user mentions Dart, Flutter, widgets, Riverpod, Streams, or null safety.
Scanned 9/3/2026
Install to Claude Code
npx -y skills add lugassawan/swe-workbench --skill language-dart --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Language Dart?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/lugassawan-language-dart)More formats (shields.io, HTML) on the badges page.
---
name: language-dart
description: Dart idioms — null safety, Futures and Streams, widget composition, and Riverpod app architecture. Auto-load when working with .dart files, pubspec.yaml, or when the user mentions Dart, Flutter, widgets, Riverpod, Streams, or null safety.
---
# Dart
## Null safety
- Sound null safety is on by default (Dart ≥2.12) — no unsound nulls sneak through.
- Non-nullable is the default; append `?` only when a value can genuinely be absent (`String? name`).
- `late` defers initialization for values assigned before first read (DI fields, `initState` fields) — a `late` field read before assignment throws.
- `!` (null-assertion) asserts non-null; prefer narrowing (`if (x != null)`) or `?.`/`??` over `!`, since `!` throws at runtime.
- `??=` for lazy default assignment; `??` for fallback expressions.
```dart
String greet(String? name) => 'Hello, ${name ?? 'stranger'}';
```
## Async — Futures and Streams
- `Future<T>` wraps a value that arrives later; `async`/`await` reads like sync code.
- Unhandled Future errors surface as unhandled exceptions — always `try`/`catch` around `await`, or attach `.catchError`.
- `Stream<T>` models a sequence over time; `await for` consumes it, `StreamController` produces it.
- `Completer<T>` bridges callback-based APIs into a `Future`.
- `Future.wait([...])` for concurrent futures; don't `await` sequentially in a loop when the work is independent.
```dart
Future<User> fetchUser(String id) async {
try {
final res = await api.get('/users/$id');
return User.fromJson(res.data);
} on DioException catch (e) {
throw UserFetchException(id, e);
}
}
```
## Widget composition
- `StatelessWidget` is the default; reach for `StatefulWidget` only when the widget owns mutable state across rebuilds.
- `build()` must be pure and fast — no side effects, no I/O; it can run many times per frame.
- Compose small widgets instead of deep inheritance — extract a widget class, not a private `_buildX()` method, when it needs its own `const` or rebuild boundary.
- `const` constructors on leaf widgets let Flutter skip rebuilding subtrees whose inputs didn't change.
- Pass a `Key` (`ValueKey`, `ObjectKey`) when reordering or diffing a list of like-typed widgets, or state attaches to the wrong element.
## State management — Riverpod and Bloc
- **Riverpod**: `Provider`/`NotifierProvider` declares state outside the widget tree; `ref.watch(p)` subscribes and rebuilds, `ref.read(p)` reads once (event handlers, not `build`).
- **Bloc**: a `Cubit` exposes methods that `emit` new states; a `Bloc` maps incoming `Event`s to `State`s via `on<Event>`. Widgets react via `BlocBuilder`/`BlocListener`.
- Both push state out of widgets and make it unit-testable without pumping a widget tree — pick per-team convention, don't mix them within the same feature.
```dart
final counterProvider = NotifierProvider<Counter, int>(Counter.new);
class Counter extends Notifier<int> {
@override
int build() => 0;
void increment() => state++;
}
```
## Doc comments
- **dartdoc** — `///` doc comment: the first sentence is treated as a standalone summary — keep it one sentence, expand only for a non-obvious contract.
```dart
/// Returns the cached price, refreshing it if older than [maxAge].
Future<Decimal> priceFor(String symbol, Duration maxAge) async { ... }
```
## Testing
- `flutter_test`: `testWidgets('description', (tester) async { ... })` drives a widget in an isolated binding.
- `tester.pumpWidget(...)` mounts; `tester.pump()` advances one frame, `tester.pumpAndSettle()` drains animations/microtasks.
- `find.text(...)`, `find.byType(...)`, `find.byKey(...)` locate widgets; assert with `expect(find.text('X'), findsOneWidget)`.
- `integration_test` runs the same API on a real device/emulator for end-to-end flows.
- Unit-test `Notifier`/`Cubit`/`Bloc` logic directly — no widget tree needed.
## Tooling
- **Format:** `dart format .`
- **Lint/analyze:** `dart analyze` (package) or `flutter analyze` (app) — configured via `analysis_options.yaml`.
- **Dependencies:** declared in `pubspec.yaml`; resolved with `dart pub get` / `flutter pub get`.
- **Test:** `flutter test` (widget/unit), `flutter test integration_test` (e2e).
## Idioms cheat sheet
- `final` over `var` when the reference won't be reassigned; `const` when the value is compile-time constant.
- Cascades (`obj..a()..b()`) for fluent multi-call setup.
- Named constructors (`User.fromJson(...)`) beat factory functions with boolean flags.
- Extension methods add behavior to existing types without subclassing.
- Records (`(int, String)`, `({int id, String name})`) for small ad-hoc multi-value returns, in place of a throwaway class.
## Avoid
- `!` as a habitual fix for the analyzer instead of proving non-null.
- Business logic inside `build()` — extract it into the state-management layer.
- Deeply nested `setState` widgets when a `Notifier`/`Cubit` would isolate the rebuild.
- Blocking synchronous work on the UI isolate — use `compute()` or a separate isolate for CPU-heavy work.
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!