Claude Code Plugins

Community-maintained marketplace

Feedback

flutter-developer

@valeriikot/flutter-cli
0
0

Expert Flutter development assistance for building cross-platform mobile, web, and desktop applications. Use when working with Flutter projects, widgets, layouts, navigation, platform-specific code, or Flutter SDK features. Covers modern Flutter 3.x+ patterns, performance optimization, and best practices.

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name flutter-developer
description Expert Flutter development assistance for building cross-platform mobile, web, and desktop applications. Use when working with Flutter projects, widgets, layouts, navigation, platform-specific code, or Flutter SDK features. Covers modern Flutter 3.x+ patterns, performance optimization, and best practices.
allowed-tools Read, Edit, Write, Grep, Glob, Bash

Flutter Developer Skill

Expert assistance for Flutter development covering modern patterns, architecture, and best practices.

When to Use This Skill

  • Building or modifying Flutter applications
  • Creating custom widgets and layouts
  • Implementing navigation and routing
  • Working with Flutter animations
  • Handling platform-specific functionality
  • Optimizing Flutter app performance
  • Setting up Flutter project structure
  • Implementing responsive and adaptive designs
  • Managing Flutter dependencies and packages

Core Principles

1. Widget Composition

  • Prefer composition over inheritance
  • Break complex widgets into smaller, reusable components
  • Use const constructors whenever possible for performance
  • Follow the single responsibility principle for widgets

2. State Management Readiness

  • Design widgets to be compatible with any state management solution
  • Separate business logic from UI code
  • Use StatelessWidget by default, StatefulWidget only when needed
  • Make widgets testable and independent

3. Performance Best Practices

  • Use const widgets to reduce rebuilds
  • Implement RepaintBoundary for complex custom painters
  • Use ListView.builder for long lists
  • Avoid unnecessary rebuilds with proper widget keys
  • Profile with Flutter DevTools before optimizing

4. Clean Architecture

lib/
├── core/              # Core utilities, constants, extensions
├── features/          # Feature modules
│   └── feature_name/
│       ├── data/      # Data sources, repositories
│       ├── domain/    # Entities, use cases
│       └── presentation/  # UI, widgets, pages
├── shared/            # Shared widgets, utilities
└── main.dart

Project-Specific Guidelines

Required Packages & Usage

IMPORTANT: These are mandatory patterns for this project:

1. HTTP Client - Dio (Required)

  • ALWAYS use Dio for all API calls
  • Never use http package directly for API requests
  • Configure Dio with interceptors for logging, auth, error handling
import 'package:dio/dio.dart';

final dio = Dio(BaseOptions(
  baseUrl: 'https://api.example.com',
  connectTimeout: const Duration(seconds: 5),
  receiveTimeout: const Duration(seconds: 3),
));

// Add interceptors
dio.interceptors.add(LogInterceptor(responseBody: true));

// Example API call
Future<User> fetchUser(String id) async {
  final response = await dio.get('/users/$id');
  return User.fromJson(response.data);
}

2. Data Storage - Hive (Required for non-sensitive data)

  • Use Hive for caching and storing non-sensitive data
  • Never store sensitive data (tokens, passwords, personal info) in Hive
  • Use TypeAdapters for complex objects
import 'package:hive_flutter/hive_flutter.dart';

// Initialize Hive
await Hive.initFlutter();
Hive.registerAdapter(UserAdapter());

// Open box
final box = await Hive.openBox<User>('users');

// Store data
await box.put('user_1', user);

// Retrieve data
final user = box.get('user_1');

3. Secure Storage - flutter_secure_storage (Required for sensitive data)

  • ALWAYS use flutter_secure_storage for sensitive data
  • Use for: auth tokens, API keys, passwords, personal information
  • Never store sensitive data in SharedPreferences or Hive
import 'package:flutter_secure_storage/flutter_secure_storage.dart';

const storage = FlutterSecureStorage();

// Store sensitive data
await storage.write(key: 'auth_token', value: token);
await storage.write(key: 'refresh_token', value: refreshToken);

// Retrieve sensitive data
final token = await storage.read(key: 'auth_token');

// Delete sensitive data
await storage.delete(key: 'auth_token');

4. Localization - easy_localization (Required)

  • Use easy_localization for all internationalization
  • CRITICAL RULE: Translation keys must NEVER be plain strings
  • Always use type-safe key classes
import 'package:easy_localization/easy_localization.dart';

// ❌ WRONG - Never use string literals
Text('welcome_message'.tr())

// ✅ CORRECT - Always use LocaleKeys
Text(LocaleKeys.home_welcomeMessage.tr())

// Plural support
Text(LocaleKeys.items_count.plural(count))

// Parameters
Text(LocaleKeys.greeting_message.tr(args: [userName]))

Locale Keys Structure:

// Generated from translations
abstract class LocaleKeys {
  static const home_welcomeMessage = 'home.welcomeMessage';
  static const home_subtitle = 'home.subtitle';
  static const items_count = 'items.count';
  static const greeting_message = 'greeting.message';
}

5. Code Generation - Freezed & Generators (Required)

  • ALWAYS use Freezed for immutable data classes
  • Use json_serializable for JSON serialization
  • Run flutter pub run build_runner build after changes
import 'package:freezed_annotation/freezed_annotation.dart';

part 'user.freezed.dart';
part 'user.g.dart';

@freezed
class User with _$User {
  const factory User({
    required String id,
    required String name,
    required String email,
    String? avatarUrl,
  }) = _User;

  factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
}

// Usage
final user = User(id: '1', name: 'John', email: 'john@example.com');

// CopyWith
final updatedUser = user.copyWith(name: 'Jane');

// Equality (automatic)
print(user == updatedUser); // false

Data Storage Decision Tree

Need to store data?
├─ Is it sensitive? (tokens, passwords, personal info)
│  ├─ YES → Use flutter_secure_storage
│  └─ NO → Continue
├─ Is it simple key-value? (settings, flags)
│  ├─ YES → Use SharedPreferences (only for non-sensitive)
│  └─ NO → Continue
└─ Is it structured data? (objects, lists)
   └─ YES → Use Hive with TypeAdapters

API Integration Pattern

// 1. Define data model with Freezed
@freezed
class Product with _$Product {
  const factory Product({
    required String id,
    required String name,
    required double price,
  }) = _Product;

  factory Product.fromJson(Map<String, dynamic> json) =>
    _$ProductFromJson(json);
}

// 2. Create repository with Dio
class ProductRepository {
  final Dio _dio;

  ProductRepository(this._dio);

  Future<List<Product>> fetchProducts() async {
    final response = await _dio.get('/products');
    return (response.data as List)
        .map((json) => Product.fromJson(json))
        .toList();
  }

  Future<void> cacheProducts(List<Product> products) async {
    final box = Hive.box<Product>('products');
    await box.clear();
    await box.addAll(products);
  }

  List<Product> getCachedProducts() {
    final box = Hive.box<Product>('products');
    return box.values.toList();
  }
}

Localization Setup

// main.dart
void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await EasyLocalization.ensureInitialized();

  runApp(
    EasyLocalization(
      supportedLocales: const [Locale('en'), Locale('es'), Locale('ar')],
      path: 'assets/translations',
      fallbackLocale: const Locale('en'),
      child: const MyApp(),
    ),
  );
}

// In widgets
class MyWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        // Simple translation
        Text(LocaleKeys.common_save.tr()),

        // With parameters
        Text(LocaleKeys.greeting_hello.tr(args: [userName])),

        // Plurals
        Text(LocaleKeys.items_count.plural(itemCount)),

        // Gender
        Text(LocaleKeys.user_title.tr(gender: 'male')),
      ],
    );
  }
}

Common Patterns

Widget Structure

class MyWidget extends StatelessWidget {
  const MyWidget({
    super.key,
    required this.title,
    this.onTap,
  });

  final String title;
  final VoidCallback? onTap;

  @override
  Widget build(BuildContext context) {
    final theme = Theme.of(context);

    return GestureDetector(
      onTap: onTap,
      child: Container(
        padding: const EdgeInsets.all(16),
        child: Text(
          title,
          style: theme.textTheme.titleMedium,
        ),
      ),
    );
  }
}

Responsive Design

class ResponsiveLayout extends StatelessWidget {
  const ResponsiveLayout({
    super.key,
    required this.mobile,
    this.tablet,
    this.desktop,
  });

  final Widget mobile;
  final Widget? tablet;
  final Widget? desktop;

  @override
  Widget build(BuildContext context) {
    return LayoutBuilder(
      builder: (context, constraints) {
        if (constraints.maxWidth >= 1200 && desktop != null) {
          return desktop!;
        } else if (constraints.maxWidth >= 600 && tablet != null) {
          return tablet!;
        }
        return mobile;
      },
    );
  }
}

Navigation with GoRouter

final router = GoRouter(
  routes: [
    GoRoute(
      path: '/',
      builder: (context, state) => const HomePage(),
      routes: [
        GoRoute(
          path: 'details/:id',
          builder: (context, state) {
            final id = state.pathParameters['id']!;
            return DetailsPage(id: id);
          },
        ),
      ],
    ),
  ],
);

Theme Configuration

class AppTheme {
  static ThemeData lightTheme() {
    return ThemeData(
      useMaterial3: true,
      colorScheme: ColorScheme.fromSeed(
        seedColor: Colors.blue,
        brightness: Brightness.light,
      ),
      textTheme: const TextTheme(
        displayLarge: TextStyle(fontSize: 32, fontWeight: FontWeight.bold),
        bodyLarge: TextStyle(fontSize: 16),
      ),
    );
  }

  static ThemeData darkTheme() {
    return ThemeData(
      useMaterial3: true,
      colorScheme: ColorScheme.fromSeed(
        seedColor: Colors.blue,
        brightness: Brightness.dark,
      ),
    );
  }
}

File Organization

Feature Module Example

features/authentication/
├── data/
│   ├── datasources/
│   │   ├── auth_local_datasource.dart
│   │   └── auth_remote_datasource.dart
│   ├── models/
│   │   └── user_model.dart
│   └── repositories/
│       └── auth_repository_impl.dart
├── domain/
│   ├── entities/
│   │   └── user.dart
│   ├── repositories/
│   │   └── auth_repository.dart
│   └── usecases/
│       ├── login.dart
│       └── logout.dart
└── presentation/
    ├── pages/
    │   ├── login_page.dart
    │   └── register_page.dart
    └── widgets/
        ├── login_form.dart
        └── password_field.dart

Common Tasks

Creating a New Page

  1. Create page file in features/[feature]/presentation/pages/
  2. Implement as StatelessWidget with proper constructor
  3. Extract complex UI into separate widgets
  4. Add to router configuration
  5. Create tests in corresponding test directory

Adding Dependencies

# Add package
flutter pub add package_name

# Add dev dependency
flutter pub add --dev package_name

# Update dependencies
flutter pub upgrade

Platform-Specific Code

import 'dart:io' show Platform;

if (Platform.isIOS) {
  // iOS-specific code
} else if (Platform.isAndroid) {
  // Android-specific code
}

Using Platform Channels

class PlatformService {
  static const platform = MethodChannel('com.example.app/channel');

  Future<String> getPlatformVersion() async {
    try {
      final version = await platform.invokeMethod<String>('getPlatformVersion');
      return version ?? 'Unknown';
    } on PlatformException catch (e) {
      return 'Failed to get platform version: ${e.message}';
    }
  }
}

Testing Patterns

Widget Tests

testWidgets('MyWidget displays title', (tester) async {
  await tester.pumpWidget(
    const MaterialApp(
      home: MyWidget(title: 'Test Title'),
    ),
  );

  expect(find.text('Test Title'), findsOneWidget);
});

Golden Tests

testWidgets('MyWidget golden test', (tester) async {
  await tester.pumpWidget(
    const MaterialApp(
      home: MyWidget(title: 'Test'),
    ),
  );

  await expectLater(
    find.byType(MyWidget),
    matchesGoldenFile('goldens/my_widget.png'),
  );
});

Performance Optimization

Keys for Widget Identity

ListView.builder(
  itemCount: items.length,
  itemBuilder: (context, index) {
    final item = items[index];
    return ItemWidget(
      key: ValueKey(item.id),  // Preserves widget state
      item: item,
    );
  },
)

Const Constructors

// Good: Const constructor allows const instances
class MyWidget extends StatelessWidget {
  const MyWidget({super.key, required this.text});
  final String text;

  @override
  Widget build(BuildContext context) {
    return Text(text);
  }
}

// Usage with const
const MyWidget(text: 'Hello')  // More efficient

Lazy Loading

// Use ListView.builder instead of ListView
ListView.builder(
  itemCount: 1000,
  itemBuilder: (context, index) => ListTile(title: Text('Item $index')),
)

Common Issues and Solutions

Issue: Unbounded Height/Width

Solution: Wrap in Expanded, Flexible, or provide constraints

Column(
  children: [
    Expanded(  // Provides bounds
      child: ListView(...),
    ),
  ],
)

Issue: RenderFlex Overflow

Solution: Use SingleChildScrollView or adjust layout

SingleChildScrollView(
  child: Column(
    children: [...],
  ),
)

Issue: setState Called After Dispose

Solution: Check if mounted before setState

if (mounted) {
  setState(() {
    // Update state
  });
}

Flutter Commands Reference

# Create new project
flutter create my_app

# Run app
flutter run

# Run on specific device
flutter run -d chrome
flutter run -d macos

# Build release
flutter build apk
flutter build ipa
flutter build web

# Analyze code
flutter analyze

# Run tests
flutter test

# Format code
dart format lib/

# Clean build
flutter clean

# Doctor check
flutter doctor -v

Best Practices Checklist

  • Use const constructors for immutable widgets
  • Separate business logic from UI
  • Implement proper error handling
  • Add meaningful widget keys where needed
  • Write widget tests for critical UI
  • Follow Flutter style guide
  • Use meaningful variable and class names
  • Document complex widgets and logic
  • Handle loading and error states
  • Optimize images and assets
  • Use appropriate state management
  • Implement responsive layouts
  • Test on multiple screen sizes
  • Profile performance before optimizing

Resources

Notes

This skill provides Flutter-agnostic guidance. For state management specifics, use the dedicated Riverpod or Bloc skills. This skill focuses on Flutter SDK features, widget composition, layout, navigation, and platform integration.