dart-expert — Claude Code skill for Dart and Flutter
A skill that gives your coding agent Dart and Flutter reference material: widgets, state management, async patterns.
npx skills add personamanagmentlayer/pcl --skill dart-expert -a claude-codeWhat dart-expert does
The skill hands the agent working examples for the Dart language and the Flutter framework. It covers type inference, null safety, classes with named constructors, mixins and extensions, plus Future, Stream and StreamController. On the Flutter side you get StatelessWidget and StatefulWidget, routing with Navigator, layouts built from Scaffold and ListView.builder, and state management through Provider with ChangeNotifier and Consumer.
The skill triggers on its own when a task mentions Flutter, mobile work or widgets. Its tool access is narrow: Read, Write, Edit, and Bash limited to dart and flutter commands. The agent can read and edit your code, but it cannot shell out to anything else under this skill — worth knowing before you rely on it for a build step.
Install it with npx skills add on Claude, Codex, Cursor, Gemini CLI or OpenCode. GitHub Copilot uses gh skill install instead.
What it helps with
- sort out null safety and type inference in Dart
- build a widget that holds its own state
- wire screens together with Navigator routing
- move state into a Provider ChangeNotifier
- handle async work with Future and Stream
Example prompts for the agent
- Rewrite this screen in Flutter using Provider instead of setState
- Explain late initialization in Dart and show a short example
- Create a StatefulWidget with a counter and an increment button
How to install dart-expert
1Skills CLI (Vercel)
npx skills add personamanagmentlayer/pcl --skill dart-expert -a claude-code2Manually: put the files in place
~/.claude/skills/dart-expert/SKILL.mdCopy the whole skill folder; for one project use .claude/skills/dart-expert/
1Skills CLI (Vercel)
npx skills add personamanagmentlayer/pcl --skill dart-expert -a codex2Manually: put the files in place
.agents/skills/dart-expert/SKILL.mdFor all projects use ~/.agents/skills/
1Skills CLI (Vercel)
npx skills add personamanagmentlayer/pcl --skill dart-expert -a cursor2Manually: put the files in place
.cursor/skills/dart-expert/SKILL.mdCursor also reads .agents/skills/ and .claude/skills/
1GitHub CLI
gh skill install personamanagmentlayer/pcl dart-expert2Manually: put the files in place
.github/skills/dart-expert/SKILL.md1Skills CLI (Vercel)
npx skills add personamanagmentlayer/pcl --skill dart-expert -a gemini-cli2Platform CLI
gemini skills install https://github.com/personamanagmentlayer/pcl --path stdlib/languages/dart-expertManually: put the files in place
.devin/skills/dart-expert/SKILL.mdThe legacy .windsurf/skills/ path still works
Skills CLI (Vercel)
npx skills add personamanagmentlayer/pcl --skill dart-expert -a opencodeClaude.ai
Zip the folder and upload it in Settings → Capabilities → Skills
Step by step, with commands for each platform: How to install skills in Claude Code: five ways
Questions about dart-expert
How do I install dart-expert in Claude?
Run npx skills add personamanagmentlayer/pcl --skill dart-expert -a claude-code. The skill file lands at ~/.claude/skills/dart-expert/SKILL.md.
What does dart-expert do?
It gives the agent a Dart and Flutter reference: types, null safety, mixins, async patterns, widgets, navigation and Provider-based state management.
Which tools can the skill use?
Read, Write, Edit, and Bash restricted to dart and flutter commands. Nothing else runs through it.
Source text
SKILL.md · Apache-2.0
---
name: dart-expert
version: 1.1.0
description: >-
Expert-level Dart, Flutter, mobile development, and cross-platform apps. Use when the
user mentions Flutter, mobile, cross platform, or widgets, or when the task involves Dart
Language or Flutter Framework.
category: languages
tags: [dart, flutter, mobile, cross-platform, widgets]
allowed-tools:
- Read
- Write
- Edit
- Bash(dart:*, flutter:*)
---
# Dart & Flutter Expert
Expert guidance for Dart programming, Flutter framework, mobile development, and cross-platform applications.
## Core Concepts
### Dart Language
- Strong typing with type inference
- Async/await and Futures
- Streams
- Mixins and extensions
- Null safety
- Collections
### Flutter Framework
- Widgets (Stateless & Stateful)
- State management (Provider, Riverpod, Bloc)
- Navigation and routing
- Material and Cupertino design
- Responsive layouts
- Platform integration
## Dart Fundamentals
```dart
// Variables and types
var name = 'John'; // Type inference
String explicitType = 'Explicit';
final constantValue = 42; // Runtime constant
const compileConstant = 'Compile-time';
// Null safety
String? nullableString;
String nonNullable = 'Never null';
// Late initialization
late String lateInit;
// Collections
List<String> names = ['Alice', 'Bob', 'Charlie'];
Map<String, int> ages = {'Alice': 25, 'Bob': 30};
Set<int> uniqueNumbers = {1, 2, 3, 3}; // {1, 2, 3}
// Functions
int add(int a, int b) => a + b;
// Named parameters
void createUser({
required String name,
int age = 18,
String? email,
}) {
print('Creating user: $name, age: $age');
}
// Classes
class User {
final String id;
final String name;
int age;
User({
required this.id,
required this.name,
this.age = 0,
});
// Named constructor
User.guest() : this(id: 'guest', name: 'Guest');
// Methods
void celebrateBirthday() {
age++;
}
@override
String toString() => 'User($name, $age)';
}
// Mixins
mixin Loggable {
void log(String message) {
print('[${DateTime.now()}] $message');
}
}
class LoggableUser extends User with Loggable {
LoggableUser({required String id, required String name})
: super(id: id, name: name);
}
// Extensions
extension StringExtensions on String {
bool get isValidEmail => contains('@') && contains('.');
String capitalize() => '${this[0].toUpperCase()}${substring(1)}';
}
```
## Async Programming
```dart
import 'dart:async';
// Future
Future<String> fetchUser(String id) async {
await Future.delayed(Duration(seconds: 1));
return 'User $id';
}
// Error handling
Future<User?> fetchUserSafely(String id) async {
try {
final user = await fetchUser(id);
return User(id: id, name: user);
} catch (e) {
print('Error: $e');
return null;
}
}
// Multiple futures
Future<void> fetchMultiple() async {
final results = await Future.wait([
fetchUser('1'),
fetchUser('2'),
fetchUser('3'),
]);
print(results);
}
// Streams
Stream<int> countStream() async* {
for (int i = 1; i <= 5; i++) {
await Future.delayed(Duration(seconds: 1));
yield i;
}
}
// Listen to stream
void listenToStream() {
countStream().listen(
(data) => print('Received: $data'),
onError: (error) => print('Error: $error'),
onDone: () => print('Done'),
);
}
// Stream transformations
Stream<String> transformedStream() {
return countStream()
.where((n) => n % 2 == 0)
.map((n) => 'Number: $n')
.take(3);
}
// StreamController
class Counter {
final _controller = StreamController<int>();
Stream<int> get stream => _controller.stream;
void increment(int value) {
_controller.add(value);
}
void dispose() {
_controller.close();
}
}
```
## Flutter Widgets
```dart
import 'package:flutter/material.dart';
// Stateless Widget
class UserCard extends StatelessWidget {
final User user;
const UserCard({Key? key, required this.user}) : super(key: key);
@override
Widget build(BuildContext context) {
return Card(
child: ListTile(
title: Text(user.name),
subtitle: Text('Age: ${user.age}'),
trailing: Icon(Icons.arrow_forward),
),
);
}
}
// Stateful Widget
class CounterWidget extends StatefulWidget {
const CounterWidget({Key? key}) : super(key: key);
@override
State<CounterWidget> createState() => _CounterWidgetState();
}
class _CounterWidgetState extends State<CounterWidget> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
@override
Widget build(BuildContext context) {
return Column(
children: [
Text('Counter: $_counter'),
ElevatedButton(
onPressed: _incrementCounter,
child: Text('Increment'),
),
],
);
}
}
// Layout
class UserListScreen extends StatelessWidget {
final List<User> users;
const UserListScreen({Key? key, required this.users}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Users'),
actions: [
IconButton(
icon: Icon(Icons.add),
onPressed: () => Navigator.push(
context,
MaterialPageRoute(builder: (_) => AddUserScreen()),
),
),
],
),
body: ListView.builder(
itemCount: users.length,
itemBuilder: (context, index) {
return UserCard(user: users[index]);
},
),
);
}
}
```
## State Management (Provider)
```dart
import 'package:provider/provider.dart';
// State class
class UserProvider extends ChangeNotifier {
List<User> _users = [];
List<User> get users => _users;
Future<void> fetchUsers() async {
_users = await api.getUsers();
notifyListeners();
}
void addUser(User user) {
_users.add(user);
notifyListeners();
}
void removeUser(String id) {
_users.removeWhere((user) => user.id == id);
notifyListeners();
}
}
// Setup provider
void main() {
runApp(
MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => UserProvider()),
ChangeNotifierProvider(create: (_) => AuthProvider()),
],
child: MyApp(),
),
);
}
// Consume provider
class UserList extends StatelessWidget {
@override
Widget build(BuildContext context) {
final userProvider = Provider.of<UserProvider>(context);
return ListView.builder(
itemCount: userProvider.users.length,
itemBuilder: (context, index) {
final user = userProvider.users[index];
return ListTile(
title: Text(user.name),
trailing: IconButton(
icon: Icon(Icons.delete),
onPressed: () => userProvider.removeUser(user.id),
),
);
},
);
}
}
// Consumer widget
class UserCounter extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Consumer<UserProvider>(
builder: (context, userProvider, child) {
return Text('Total users: ${userProvider.users.length}');
},
);
}
}
```
## Navigation
```dart
// Basic navigation
Navigator.push(
context,
MaterialPageRoute(builder: (context) => DetailScreen()),
);
// Named routes
MaterialApp(
routes: {
'/': (context) => HomeScreen(),
'/details': (context) => DetailScreen(),
'/settings': (context) => SettingsScreen(),
},
);
Navigator.pushNamed(context, '/details');
// Pass arguments
Navigator.pushNamed(
context,
'/details',
arguments: {'userId': '123'},
);
// Get arguments
final args = ModalRoute.of(context)!.settings.arguments as Map;
// Return data
final result = await Navigator.push(
context,
MaterialPageRoute(builder: (_) => SelectionScreen()),
);
// Pop with result
Navigator.pop(context, selectedValue);
```
## HTTP and API
```dart
import 'package:http/http.dart' as http;
import 'dart:convert';
class ApiService {
final String baseUrl = 'https://api.example.com';
Future<List<User>> fetchUsers() async {
final response = await http.get(Uri.parse('$baseUrl/users'));
if (response.statusCode == 200) {
final List<dynamic> data = json.decode(response.body);
return data.map((json) => User.fromJson(json)).toList();
} else {
throw Exception('Failed to load users');
}
}
Future<User> createUser(User user) async {
final response = await http.post(
Uri.parse('$baseUrl/users'),
headers: {'Content-Type': 'application/json'},
body: json.encode(user.toJson()),
);
if (response.statusCode == 201) {
return User.fromJson(json.decode(response.body));
} else {
throw Exception('Failed to create user');
}
}
}
// Model with JSON serialization
class User {
final String id;
final String name;
final String email;
User({required this.id, required this.name, required this.email});
factory User.fromJson(Map<String, dynamic> json) {
return User(
id: json['id'],
name: json['name'],
email: json['email'],
);
}
Map<String, dynamic> toJson() {
return {
'id': id,
'name': name,
'email': email,
};
}
}
```
## Best Practices
### Dart
- Use const constructors when possible
- Leverage null safety
- Use final for immutable values
- Prefer async/await over .then()
- Use extensions for utility methods
- Follow Effective Dart guidelines
### Flutter
- Keep widgets small and focused
- Use const widgets for optimization
- Avoid rebuilding entire trees
- Implement proper state management
- Handle errors gracefully
- Test widgets thoroughly
- Use keys when needed
### Performance
- Use ListView.builder for long lists
- Implement pagination
- Cache network images
- Minimize widget rebuilds
- Use const constructors
- Profile app performance
- Lazy load resources
## Anti-Patterns
❌ Large widget trees
❌ State in StatelessWidget
❌ Not disposing controllers
❌ Ignoring null safety
❌ Synchronous I/O in UI
❌ No error handling
❌ Hard-coded strings
## Resources
- Dart: https://dart.dev/
- Flutter: https://flutter.dev/
- Pub.dev: https://pub.dev/
- Flutter Widget Catalog: https://flutter.dev/docs/development/ui/widgets
- Effective Dart: https://dart.dev/guides/language/effective-dart
© personamanagmentlayer · Apache-2.0 · personamanagmentlayer/pcl