flutter
Expert guidance for Flutter, Google's UI toolkit for building natively compiled applications for mobile, web, and desktop from a single Dart codebase. Helps developers build performant cross-platform apps with custom widgets, state management, platform channels, and production deployment.
Usage
Getting Started
- Install the skill using the command above
- Open your AI coding agent (Claude Code, Codex, Gemini CLI, or Cursor)
- Reference the skill in your prompt
- The AI will use the skill's capabilities automatically
Example Prompts
- "Review the open pull requests and summarize what needs attention"
- "Generate a changelog from the last 20 commits on the main branch"
Documentation
Overview
Flutter, Google's UI toolkit for building natively compiled applications for mobile, web, and desktop from a single Dart codebase. Helps developers build performant cross-platform apps with custom widgets, state management, platform channels, and production deployment.
Instructions
Project Setup
# Install Flutter
# macOS
brew install flutter
# Verify installation
flutter doctor
# Create a new project
flutter create my_app --org com.example --platforms ios,android,web
cd my_app
# Run in development
flutter run # Auto-detects connected device/emulator
flutter run -d chrome # Run on web
flutter run -d macos # Run on desktop
Widget Composition
// lib/screens/home_screen.dart — Composable widget architecture
// Flutter UIs are built by composing small, reusable widgets.
import 'package:flutter/material.dart';
class HomeScreen extends StatelessWidget {
const HomeScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Dashboard'),
actions: [
IconButton(
icon: const Icon(Icons.notifications_outlined),
onPressed: () => Navigator.pushNamed(context, '/notifications'),
),
],
),
body: RefreshIndicator(
onRefresh: () async {
// Pull-to-refresh logic
},
child: ListView(
padding: const EdgeInsets.all(16),
children: [
// Stats cards row
const Row(
children: [
Expanded(child: _StatCard(label: 'Revenue', value: '\$12,450', trend: '+12%')),
SizedBox(width: 12),
Expanded(child: _StatCard(label: 'Users', value: '1,234', trend: '+5%')),
],
),
const SizedBox(height: 24),
// Recent activity list
const Text('Recent Activity', style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold)),
const SizedBox(height: 12),
...List.generate(10, (i) => _ActivityTile(index: i)),
],
),
),
bottomNavigationBar: NavigationBar(
selectedIndex: 0,
destinations: const [
NavigationDestination(icon: Icon(Icons.home), label: 'Home'),
NavigationDestination(icon: Icon(Icons.search), label: 'Search'),
NavigationDestination(icon: Icon(Icons.person), label: 'Profile'),
],
),
);
}
}
class _StatCard extends StatelessWidget {
final String label;
final String value;
final String trend;
const _StatCard({required this.label, required this.value, required this.trend});
@override
Widget build(BuildContext context) {
return Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(label, style: Theme.of(context).textTheme.bodySmall),
const SizedBox(height: 8),
Text(value, style: const TextStyle(fontSize: 24, fontWeight: FontWeight.bold)),
Text(trend, style: TextStyle(color: trend.startsWith('+') ? Colors.green : Colors.red)),
],
),
),
);
}
}
State Management with Riverpod
// lib/providers/auth_provider.dart — State management with Riverpod
// Riverpod is the recommended state management solution for Flutter.
import 'package:flutter_riverpod/flutter_riverpod.dart';
// User model
class User {
final String id;
final String email;
final String name;
User({required this.id, required this.email, required this.name});
}
// Auth state
class AuthState {
final User? user;
final bool isLoading;
final String? error;
const AuthState({this.user, this.isLoading = false, this.error});
}
// Auth notifier — manages login/logout state transitions
class AuthNotifier extends StateNotifier<AuthState> {
AuthNotifier() : super(const AuthState());
Future<void> login(String email, String password) async {
state = const AuthState(isLoading: true);
try {
final response = await apiClient.post('/auth/login', {
'email': email,
'password': password,
});
state = AuthState(user: User.fromJson(response.data));
} catch (e) {
state = AuthState(error: e.toString());
}
}
void logout() {
state = const AuthState();
}
}
// Provider (global, accessible from any widget)
final authProvider = StateNotifierProvider<AuthNotifier, AuthState>((ref) {
return AuthNotifier();
});
// Usage in a widget:
// final auth = ref.watch(authProvider);
// if (auth.isLoading) return CircularProgressIndicator();
// if (auth.user != null) return HomeScreen();
// return LoginScreen();
Navigation with GoRouter
// lib/router.dart — Declarative routing
import 'package:go_router/go_router.dart';
final router = GoRouter(
initialLocation: '/',
redirect: (context, state) {
final isLoggedIn = authProvider.currentUser != null;
final isAuthRoute = state.matchedLocation.startsWith('/auth');
if (!isLoggedIn && !isAuthRoute) return '/auth/login';
if (isLoggedIn && isAuthRoute) return '/';
return null; // No redirect needed
},
routes: [
GoRoute(path: '/', builder: (_, __) => const HomeScreen()),
GoRoute(path: '/auth/login', builder: (_, __) => const LoginScreen()),
GoRoute(
path: '/project/:id',
builder: (_, state) => ProjectScreen(id: state.pathParameters['id']!),
),
ShellRoute(
builder: (_, __, child) => ScaffoldWithNavBar(child: child),
routes: [
GoRoute(path: '/dashboard', builder: (_, __) => const DashboardScreen()),
GoRoute(path: '/settings', builder: (_, __) => const SettingsScreen()),
],
),
],
);
HTTP Client with Dio
// lib/services/api_client.dart — HTTP client with interceptors
import 'package:dio/dio.dart';
class ApiClient {
late final Dio _dio;
ApiClient() {
_dio = Dio(BaseOptions(
baseUrl: 'https://api.example.com/v1',
connectTimeout: const Duration(seconds: 10),
receiveTimeout: const Duration(seconds: 15),
));
// Auth interceptor — adds JWT to every request
_dio.interceptors.add(InterceptorsWrapper(
onRequest: (options, handler) async {
final token = await secureStorage.read(key: 'auth_token');
if (token != null) {
options.headers['Authorization'] = 'Bearer $token';
}
handler.next(options);
},
onError: (error, handler) async {
if (error.response?.statusCode == 401) {
// Token expired — try refresh
final refreshed = await _refreshToken();
if (refreshed) {
return handler.resolve(await _dio.fetch(error.requestOptions));
}
}
handler.next(error);
},
));
}
Future<Response> get(String path, {Map<String, dynamic>? params}) =>
_dio.get(path, queryParameters: params);
Future<Response> post(String path, dynamic data) =>
_dio.post(path, data: data);
}
Platform Channels (Native Code)
// lib/services/biometric_service.dart — Call native APIs
import 'package:flutter/services.dart';
class BiometricService {
static const _channel = MethodChannel('com.example/biometric');
/// Check if device supports biometric authentication
Future<bool> isAvailable() async {
return await _channel.invokeMethod('isAvailable') ?? false;
}
/// Authenticate with fingerprint or face
Future<bool> authenticate(String reason) async {
return await _channel.invokeMethod('authenticate', {'reason': reason}) ?? false;
}
}
Installation
# macOS
brew install flutter
# Or download from https://docs.flutter.dev/get-started/install
# Common packages
flutter pub add flutter_riverpod # State management
flutter pub add go_router # Navigation
flutter pub add dio # HTTP client
flutter pub add freezed_annotation # Immutable models
flutter pub add hive # Local storage
Examples
Example 1: Setting up Flutter with a custom configuration
User request:
I just installed Flutter. Help me configure it for my TypeScript + React workflow with my preferred keybindings.
The agent creates the configuration file with TypeScript-aware settings, configures relevant plugins/extensions for React development, sets up keyboard shortcuts matching the user's preferences, and verifies the setup works correctly.
Example 2: Extending Flutter with custom functionality
User request:
I want to add a custom widget composition to Flutter. How do I build one?
The agent scaffolds the extension/plugin project, implements the core functionality following Flutter's API patterns, adds configuration options, and provides testing instructions to verify it works end-to-end.
Guidelines
- Composition over inheritance — Build UIs by composing small widgets; extract widgets when a build method exceeds ~50 lines
- Riverpod for state — Use Riverpod over setState/Provider; it's compile-safe, testable, and handles async naturally
- const constructors — Mark widgets as
constwhen possible; Flutter skips rebuilding const widgets entirely - GoRouter for navigation — Declarative routing with deep linking support; avoid Navigator.push for anything beyond simple flows
- Separate business logic from UI — Keep API calls, data processing in services/providers, not in widget build methods
- Platform-adaptive UI — Use
Platform.isIOS/Platform.isAndroidfor platform-specific behavior; Material and Cupertino widgets for native feel - Test at all levels — Unit tests for logic, widget tests for UI, integration tests for flows; Flutter's test framework is built-in
- Flavors for environments — Use
--dart-defineor flavors for dev/staging/prod configurations; never hardcode API URLs
Information
- Version
- 1.0.0
- Author
- terminal-skills
- Category
- Development
- License
- Apache-2.0